fix(companion): make reference-app exports reproducible by default
No diagram change needed: this change only makes generated reference-app timestamps deterministic and adds an override flag.
This commit is contained in:
@@ -1752,7 +1752,7 @@ Minimal companion CLI:
|
|||||||
- `flynn companion --platform ios --export-shell-template ./dist/companion-ios-template` writes a platform-native starter template directory (`companion.bootstrap.json`, bootstrap/runtime starter files, `README.md`) and exits.
|
- `flynn companion --platform ios --export-shell-template ./dist/companion-ios-template` writes a platform-native starter template directory (`companion.bootstrap.json`, bootstrap/runtime starter files, `README.md`) and exits.
|
||||||
- `flynn companion --verify-release-bundle ./dist/companion-macos --verify-signing-key ./keys/release-public.pem --verify-signing-key-id team-k1 --require-signature` verifies checksums and signature metadata before install.
|
- `flynn companion --verify-release-bundle ./dist/companion-macos --verify-signing-key ./keys/release-public.pem --verify-signing-key-id team-k1 --require-signature` verifies checksums and signature metadata before install.
|
||||||
- `pnpm companion:bundle -- --output ./dist/companion-macos --platform macos --signing-key ./keys/release-private.pem --signing-key-id team-k1` builds and verifies a release bundle in one step.
|
- `pnpm companion:bundle -- --output ./dist/companion-macos --platform macos --signing-key ./keys/release-private.pem --signing-key-id team-k1` builds and verifies a release bundle in one step.
|
||||||
- `pnpm companion:reference-apps -- --output ./apps/companion` regenerates macOS/iOS/Android reference app starter shells plus `apps/companion/macos-app` runnable scaffold.
|
- `pnpm companion:reference-apps -- --output ./apps/companion` regenerates macOS/iOS/Android reference app starter shells plus `apps/companion/macos-app` runnable scaffold using a reproducible default `generatedAt` timestamp (override with `--generated-at <iso>`).
|
||||||
- GitHub Actions workflow `.github/workflows/companion-release-bundle.yml` runs build-and-verify bundle automation and uploads release artifacts on manual dispatch.
|
- GitHub Actions workflow `.github/workflows/companion-release-bundle.yml` runs build-and-verify bundle automation and uploads release artifacts on manual dispatch.
|
||||||
|
|
||||||
`run-companion.sh` verifies bundle checksums (`CHECKSUMS.sha256`) before launching `flynn companion`.
|
`run-companion.sh` verifies bundle checksums (`CHECKSUMS.sha256`) before launching `flynn companion`.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"schemaVersion": 1,
|
"schemaVersion": 1,
|
||||||
"generatedAt": "2026-02-27T04:56:19.684Z",
|
"generatedAt": "2026-02-27T00:00:00.000Z",
|
||||||
"gateway": {
|
"gateway": {
|
||||||
"url": "ws://127.0.0.1:18800"
|
"url": "ws://127.0.0.1:18800"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"schemaVersion": 1,
|
"schemaVersion": 1,
|
||||||
"generatedAt": "2026-02-27T04:56:19.684Z",
|
"generatedAt": "2026-02-27T00:00:00.000Z",
|
||||||
"gateway": {
|
"gateway": {
|
||||||
"url": "ws://127.0.0.1:18800"
|
"url": "ws://127.0.0.1:18800"
|
||||||
},
|
},
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"schemaVersion": 1,
|
"schemaVersion": 1,
|
||||||
"generatedAt": "2026-02-27T04:56:19.684Z",
|
"generatedAt": "2026-02-27T00:00:00.000Z",
|
||||||
"gateway": {
|
"gateway": {
|
||||||
"url": "ws://127.0.0.1:18800"
|
"url": "ws://127.0.0.1:18800"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"schemaVersion": 1,
|
"schemaVersion": 1,
|
||||||
"generatedAt": "2026-02-27T04:56:19.684Z",
|
"generatedAt": "2026-02-27T00:00:00.000Z",
|
||||||
"gateway": {
|
"gateway": {
|
||||||
"url": "ws://127.0.0.1:18800"
|
"url": "ws://127.0.0.1:18800"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ pnpm companion:reference-apps -- --output ./apps/companion
|
|||||||
```
|
```
|
||||||
|
|
||||||
This also regenerates `apps/companion/macos-app`, a runnable Swift Package menu-bar reference app scaffold.
|
This also regenerates `apps/companion/macos-app`, a runnable Swift Package menu-bar reference app scaffold.
|
||||||
|
By default it uses a reproducible `generatedAt` timestamp (`2026-02-27T00:00:00.000Z`); pass `--generated-at <iso>` if you need a different value.
|
||||||
|
|
||||||
CI automation:
|
CI automation:
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ Within 8-10 weeks, ship a stable "Personal Assistant Mode" that supports:
|
|||||||
2. Ship a minimal mobile companion shell (iOS + Android) for registration, status, push token, and message handoff.
|
2. Ship a minimal mobile companion shell (iOS + Android) for registration, status, push token, and message handoff.
|
||||||
3. Add signed release artifacts and installation docs.
|
3. Add signed release artifacts and installation docs.
|
||||||
|
|
||||||
Status update (2026-02-27): companion bootstrap-manifest export is now available via `flynn companion --export-bootstrap <path|->` as a packaging contract for desktop/mobile shells, `flynn companion --export-release-bundle <dir>` now emits bundle artifacts (bootstrap JSON + launcher + README + `CHECKSUMS.sha256` + `RELEASE_MANIFEST.json`, optional `CHECKSUMS.sha256.sig` with `--signing-key`), `flynn companion --verify-release-bundle <dir>` now validates checksum/signature artifacts before install, `pnpm companion:bundle -- --output <dir> ...` now provides one-pass build-and-verify automation, `.github/workflows/companion-release-bundle.yml` provides CI artifact build/verify/upload, `flynn companion --export-shell-template <dir>` now emits macOS/iOS/Android starter shell templates including iOS/Android runtime skeletons for register/status/location/push/handoff flows, `pnpm companion:reference-apps` now regenerates in-repo macOS/iOS/Android reference app starter directories plus `apps/companion/macos-app` runnable menu-bar scaffold, and `flynn companion` supports one-shot status/location/push bootstrap flags (`--app-version`, `--latitude/--longitude`, `--push-token`) so thin shells can initialize companion metadata in a single run.
|
Status update (2026-02-27): companion bootstrap-manifest export is now available via `flynn companion --export-bootstrap <path|->` as a packaging contract for desktop/mobile shells, `flynn companion --export-release-bundle <dir>` now emits bundle artifacts (bootstrap JSON + launcher + README + `CHECKSUMS.sha256` + `RELEASE_MANIFEST.json`, optional `CHECKSUMS.sha256.sig` with `--signing-key`), `flynn companion --verify-release-bundle <dir>` now validates checksum/signature artifacts before install, `pnpm companion:bundle -- --output <dir> ...` now provides one-pass build-and-verify automation, `.github/workflows/companion-release-bundle.yml` provides CI artifact build/verify/upload, `flynn companion --export-shell-template <dir>` now emits macOS/iOS/Android starter shell templates including iOS/Android runtime skeletons for register/status/location/push/handoff flows, `pnpm companion:reference-apps` now regenerates in-repo macOS/iOS/Android reference app starter directories plus `apps/companion/macos-app` runnable menu-bar scaffold with a reproducible default `generatedAt` timestamp, and `flynn companion` supports one-shot status/location/push bootstrap flags (`--app-version`, `--latitude/--longitude`, `--push-token`) so thin shells can initialize companion metadata in a single run.
|
||||||
|
|
||||||
### Implementation Anchors
|
### Implementation Anchors
|
||||||
|
|
||||||
|
|||||||
@@ -7125,7 +7125,7 @@
|
|||||||
"status": "completed",
|
"status": "completed",
|
||||||
"date": "2026-02-27",
|
"date": "2026-02-27",
|
||||||
"updated": "2026-02-27",
|
"updated": "2026-02-27",
|
||||||
"summary": "Added repo-shipped companion reference app surfaces for macOS/iOS/Android and a runnable macOS menu-bar Swift Package scaffold (`apps/companion/macos-app`). `generateReferenceCompanionApps()` and `pnpm companion:reference-apps` regenerate these starter directories from typed bootstrap contracts.",
|
"summary": "Added repo-shipped companion reference app surfaces for macOS/iOS/Android and a runnable macOS menu-bar Swift Package scaffold (`apps/companion/macos-app`). `generateReferenceCompanionApps()` and `pnpm companion:reference-apps` regenerate these starter directories from typed bootstrap contracts with a reproducible default `generatedAt` timestamp (override via `--generated-at`).",
|
||||||
"files_modified": [
|
"files_modified": [
|
||||||
"src/companion/referenceApps.ts",
|
"src/companion/referenceApps.ts",
|
||||||
"src/companion/referenceApps.test.ts",
|
"src/companion/referenceApps.test.ts",
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { resolve } from 'node:path';
|
import { resolve } from 'node:path';
|
||||||
import { generateReferenceCompanionApps } from '../src/companion/index.js';
|
import { generateReferenceCompanionApps } from '../src/companion/index.js';
|
||||||
|
|
||||||
|
const DEFAULT_GENERATED_AT = '2026-02-27T00:00:00.000Z';
|
||||||
|
|
||||||
function getArg(name: string): string | undefined {
|
function getArg(name: string): string | undefined {
|
||||||
const idx = process.argv.indexOf(name);
|
const idx = process.argv.indexOf(name);
|
||||||
if (idx < 0) {
|
if (idx < 0) {
|
||||||
@@ -16,10 +18,16 @@ function getArg(name: string): string | undefined {
|
|||||||
async function main(): Promise<void> {
|
async function main(): Promise<void> {
|
||||||
const outputDir = resolve(getArg('--output') ?? 'apps/companion');
|
const outputDir = resolve(getArg('--output') ?? 'apps/companion');
|
||||||
const gatewayUrl = getArg('--url') ?? 'ws://127.0.0.1:18800';
|
const gatewayUrl = getArg('--url') ?? 'ws://127.0.0.1:18800';
|
||||||
|
const generatedAtArg = getArg('--generated-at');
|
||||||
|
const generatedAt = new Date(generatedAtArg ?? DEFAULT_GENERATED_AT);
|
||||||
|
if (Number.isNaN(generatedAt.getTime())) {
|
||||||
|
throw new Error(`Invalid --generated-at value: ${generatedAtArg}`);
|
||||||
|
}
|
||||||
|
|
||||||
const result = await generateReferenceCompanionApps({
|
const result = await generateReferenceCompanionApps({
|
||||||
outputDir,
|
outputDir,
|
||||||
gatewayUrl,
|
gatewayUrl,
|
||||||
|
generatedAt,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`Generated companion reference apps in ${result.rootDir}`);
|
console.log(`Generated companion reference apps in ${result.rootDir}`);
|
||||||
@@ -28,6 +36,7 @@ async function main(): Promise<void> {
|
|||||||
}
|
}
|
||||||
console.log(`- macos-app: ${result.macosMenuBarAppDir}`);
|
console.log(`- macos-app: ${result.macosMenuBarAppDir}`);
|
||||||
console.log(`- readme: ${result.readmePath}`);
|
console.log(`- readme: ${result.readmePath}`);
|
||||||
|
console.log(`- generatedAt: ${generatedAt.toISOString()}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
main().catch((error: unknown) => {
|
main().catch((error: unknown) => {
|
||||||
|
|||||||
@@ -8,18 +8,23 @@ describe('generateReferenceCompanionApps', () => {
|
|||||||
it('writes macos/ios/android reference app templates', async () => {
|
it('writes macos/ios/android reference app templates', async () => {
|
||||||
const tempDir = await mkdtemp(join(tmpdir(), 'flynn-reference-apps-'));
|
const tempDir = await mkdtemp(join(tmpdir(), 'flynn-reference-apps-'));
|
||||||
const outputDir = join(tempDir, 'apps');
|
const outputDir = join(tempDir, 'apps');
|
||||||
|
const generatedAt = new Date('2026-02-27T12:34:56.000Z');
|
||||||
const result = await generateReferenceCompanionApps({
|
const result = await generateReferenceCompanionApps({
|
||||||
outputDir,
|
outputDir,
|
||||||
gatewayUrl: 'ws://127.0.0.1:18800',
|
gatewayUrl: 'ws://127.0.0.1:18800',
|
||||||
generatedAt: new Date('2026-02-27T00:00:00.000Z'),
|
generatedAt,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result.generated.map((entry) => entry.platform)).toEqual(['macos', 'ios', 'android']);
|
expect(result.generated.map((entry) => entry.platform)).toEqual(['macos', 'ios', 'android']);
|
||||||
const macosTemplate = await readFile(`${outputDir}/macos/MenuBarCompanion.swift`, 'utf8');
|
const macosTemplate = await readFile(`${outputDir}/macos/MenuBarCompanion.swift`, 'utf8');
|
||||||
const macosAppMain = await readFile(`${outputDir}/macos-app/Sources/FlynnCompanionMenuBar/main.swift`, 'utf8');
|
const macosAppMain = await readFile(`${outputDir}/macos-app/Sources/FlynnCompanionMenuBar/main.swift`, 'utf8');
|
||||||
|
const iosBootstrapRaw = await readFile(`${outputDir}/ios/companion.bootstrap.json`, 'utf8');
|
||||||
|
const macosAppBootstrapRaw = await readFile(`${outputDir}/macos-app/Sources/FlynnCompanionMenuBar/Resources/companion.bootstrap.json`, 'utf8');
|
||||||
const iosTemplate = await readFile(`${outputDir}/ios/CompanionBootstrap.swift`, 'utf8');
|
const iosTemplate = await readFile(`${outputDir}/ios/CompanionBootstrap.swift`, 'utf8');
|
||||||
const androidTemplate = await readFile(`${outputDir}/android/CompanionBootstrap.kt`, 'utf8');
|
const androidTemplate = await readFile(`${outputDir}/android/CompanionBootstrap.kt`, 'utf8');
|
||||||
const rootReadme = await readFile(`${outputDir}/README.md`, 'utf8');
|
const rootReadme = await readFile(`${outputDir}/README.md`, 'utf8');
|
||||||
|
const iosBootstrap = JSON.parse(iosBootstrapRaw) as { generatedAt: string };
|
||||||
|
const macosAppBootstrap = JSON.parse(macosAppBootstrapRaw) as { generatedAt: string };
|
||||||
|
|
||||||
expect(macosTemplate).toContain('launchFlynnCompanion');
|
expect(macosTemplate).toContain('launchFlynnCompanion');
|
||||||
expect(macosAppMain).toContain('NSStatusBar.system.statusItem');
|
expect(macosAppMain).toContain('NSStatusBar.system.statusItem');
|
||||||
@@ -28,6 +33,8 @@ describe('generateReferenceCompanionApps', () => {
|
|||||||
expect(rootReadme).toContain('Companion Reference Apps');
|
expect(rootReadme).toContain('Companion Reference Apps');
|
||||||
expect(result.macosMenuBarAppDir).toBe(`${outputDir}/macos-app`);
|
expect(result.macosMenuBarAppDir).toBe(`${outputDir}/macos-app`);
|
||||||
expect(result.macosMenuBarAppFiles.length).toBeGreaterThan(0);
|
expect(result.macosMenuBarAppFiles.length).toBeGreaterThan(0);
|
||||||
|
expect(iosBootstrap.generatedAt).toBe(generatedAt.toISOString());
|
||||||
|
expect(macosAppBootstrap.generatedAt).toBe(generatedAt.toISOString());
|
||||||
|
|
||||||
await rm(tempDir, { recursive: true, force: true });
|
await rm(tempDir, { recursive: true, force: true });
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user