feat(companion): add generated macos ios android reference app surfaces
This commit is contained in:
@@ -14,6 +14,7 @@ export { writeCompanionReleaseBundle } from './releaseBundle.js';
|
||||
export { writeCompanionShellTemplate } from './shellTemplate.js';
|
||||
export { verifyCompanionReleaseBundle } from './releaseVerify.js';
|
||||
export { buildAndVerifyCompanionReleaseBundle } from './releasePipeline.js';
|
||||
export { generateReferenceCompanionApps } from './referenceApps.js';
|
||||
|
||||
export type {
|
||||
CompanionRuntimeClientOptions,
|
||||
@@ -98,3 +99,8 @@ export type {
|
||||
BuildAndVerifyCompanionReleaseBundleInput,
|
||||
BuildAndVerifyCompanionReleaseBundleResult,
|
||||
} from './releasePipeline.js';
|
||||
export type {
|
||||
GenerateReferenceCompanionAppsInput,
|
||||
GenerateReferenceCompanionAppsResult,
|
||||
GeneratedReferenceCompanionApp,
|
||||
} from './referenceApps.js';
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { mkdtemp, readFile, rm } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { generateReferenceCompanionApps } from './referenceApps.js';
|
||||
|
||||
describe('generateReferenceCompanionApps', () => {
|
||||
it('writes macos/ios/android reference app templates', async () => {
|
||||
const tempDir = await mkdtemp(join(tmpdir(), 'flynn-reference-apps-'));
|
||||
const outputDir = join(tempDir, 'apps');
|
||||
const result = await generateReferenceCompanionApps({
|
||||
outputDir,
|
||||
gatewayUrl: 'ws://127.0.0.1:18800',
|
||||
generatedAt: new Date('2026-02-27T00:00:00.000Z'),
|
||||
});
|
||||
|
||||
expect(result.generated.map((entry) => entry.platform)).toEqual(['macos', 'ios', 'android']);
|
||||
const macosTemplate = await readFile(`${outputDir}/macos/MenuBarCompanion.swift`, 'utf8');
|
||||
const iosTemplate = await readFile(`${outputDir}/ios/CompanionBootstrap.swift`, 'utf8');
|
||||
const androidTemplate = await readFile(`${outputDir}/android/CompanionBootstrap.kt`, 'utf8');
|
||||
const rootReadme = await readFile(`${outputDir}/README.md`, 'utf8');
|
||||
|
||||
expect(macosTemplate).toContain('launchFlynnCompanion');
|
||||
expect(iosTemplate).toContain('CompanionBootstrap');
|
||||
expect(androidTemplate).toContain('data class CompanionBootstrap');
|
||||
expect(rootReadme).toContain('Companion Reference Apps');
|
||||
|
||||
await rm(tempDir, { recursive: true, force: true });
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,86 @@
|
||||
import { mkdir, writeFile } from 'node:fs/promises';
|
||||
import { writeCompanionShellTemplate } from './shellTemplate.js';
|
||||
import type { CompanionBootstrapPlatform } from './bootstrapManifest.js';
|
||||
|
||||
export interface GenerateReferenceCompanionAppsInput {
|
||||
outputDir: string;
|
||||
gatewayUrl: string;
|
||||
generatedAt?: Date;
|
||||
}
|
||||
|
||||
export interface GeneratedReferenceCompanionApp {
|
||||
platform: 'macos' | 'ios' | 'android';
|
||||
outputDir: string;
|
||||
files: string[];
|
||||
}
|
||||
|
||||
export interface GenerateReferenceCompanionAppsResult {
|
||||
rootDir: string;
|
||||
generated: GeneratedReferenceCompanionApp[];
|
||||
readmePath: string;
|
||||
}
|
||||
|
||||
function defaultCapabilities(platform: CompanionBootstrapPlatform): string[] {
|
||||
if (platform === 'ios' || platform === 'macos' || platform === 'android') {
|
||||
return ['ui.canvas', 'node.status.write', 'node.location.write', 'node.push.register'];
|
||||
}
|
||||
return ['ui.canvas', 'node.status.write'];
|
||||
}
|
||||
|
||||
function rootReadmeBody(): string {
|
||||
return `# Companion Reference Apps
|
||||
|
||||
This directory contains generated companion starter shells for:
|
||||
|
||||
- macOS menu-bar style wrapper
|
||||
- iOS shell
|
||||
- Android shell
|
||||
|
||||
These are reference starters, not production binaries. Use them as a baseline for app packaging and distribution workflows.
|
||||
`;
|
||||
}
|
||||
|
||||
export async function generateReferenceCompanionApps(
|
||||
input: GenerateReferenceCompanionAppsInput,
|
||||
): Promise<GenerateReferenceCompanionAppsResult> {
|
||||
await mkdir(input.outputDir, { recursive: true });
|
||||
const generatedAt = input.generatedAt ?? new Date();
|
||||
const generated: GeneratedReferenceCompanionApp[] = [];
|
||||
for (const platform of ['macos', 'ios', 'android'] as const) {
|
||||
const outDir = `${input.outputDir}/${platform}`;
|
||||
const result = await writeCompanionShellTemplate({
|
||||
outputDir: outDir,
|
||||
platform,
|
||||
manifest: {
|
||||
schemaVersion: 1,
|
||||
generatedAt: generatedAt.toISOString(),
|
||||
gateway: {
|
||||
url: input.gatewayUrl,
|
||||
},
|
||||
node: {
|
||||
nodeId: `${platform}-reference-shell`,
|
||||
role: 'companion',
|
||||
platform,
|
||||
capabilities: defaultCapabilities(platform),
|
||||
},
|
||||
runtime: {
|
||||
heartbeatSeconds: 30,
|
||||
handoffTimeoutMs: 120000,
|
||||
autoReconnect: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
generated.push({
|
||||
platform,
|
||||
outputDir: result.outputDir,
|
||||
files: result.files,
|
||||
});
|
||||
}
|
||||
const readmePath = `${input.outputDir}/README.md`;
|
||||
await writeFile(readmePath, rootReadmeBody(), 'utf8');
|
||||
return {
|
||||
rootDir: input.outputDir,
|
||||
generated,
|
||||
readmePath,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user