apphx 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +181 -2
  3. package/dist/apple-types.js +45 -0
  4. package/dist/apple-types.js.map +1 -0
  5. package/dist/apple.js +900 -0
  6. package/dist/apple.js.map +1 -0
  7. package/dist/cli.js +414 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/config.js +80 -0
  10. package/dist/config.js.map +1 -0
  11. package/dist/dependencies.js +157 -0
  12. package/dist/dependencies.js.map +1 -0
  13. package/dist/export-options.js +49 -0
  14. package/dist/export-options.js.map +1 -0
  15. package/dist/flutter.js +171 -0
  16. package/dist/flutter.js.map +1 -0
  17. package/dist/inbox.js +128 -0
  18. package/dist/inbox.js.map +1 -0
  19. package/dist/integrations.js +435 -0
  20. package/dist/integrations.js.map +1 -0
  21. package/dist/preflight.js +43 -0
  22. package/dist/preflight.js.map +1 -0
  23. package/dist/process.js +260 -0
  24. package/dist/process.js.map +1 -0
  25. package/dist/remote.js +143 -0
  26. package/dist/remote.js.map +1 -0
  27. package/dist/requirements.js +37 -0
  28. package/dist/requirements.js.map +1 -0
  29. package/dist/runner.js +722 -0
  30. package/dist/runner.js.map +1 -0
  31. package/dist/scheduler.js +83 -0
  32. package/dist/scheduler.js.map +1 -0
  33. package/dist/schemas.js +232 -0
  34. package/dist/schemas.js.map +1 -0
  35. package/dist/store.js +361 -0
  36. package/dist/store.js.map +1 -0
  37. package/examples/factory.json +28 -0
  38. package/examples/luggage.json +39 -0
  39. package/examples/remote.json +6 -0
  40. package/package.json +30 -3
  41. package/skills/apple-app-factory/LICENSE +21 -0
  42. package/skills/apple-app-factory/SKILL.md +103 -0
  43. package/skills/apple-app-factory/resources/requirement.json +39 -0
  44. package/skills/apple-app-factory/resources/research-result.json +21 -0
  45. package/skills/apple-app-factory/resources/review-result.json +7 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { readFileSync, writeFileSync, mkdirSync } from 'node:fs';\nimport { resolve, dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { parseArgs } from 'node:util';\nimport { loadConfig, loadJSON } from './config.js';\nimport { Store } from './store.js';\nimport { run, executionEnvironment } from './process.js';\nimport { appleDoctor, validateAppleSpec } from './apple.js';\nimport { runStep } from './scheduler.js';\nimport { nextTurn, completeTurn, abandonTurn } from './runner.js';\nimport { hosts, installIntegration, updateIntegration, inspectIntegration, uninstallIntegration } from './integrations.js';\nimport { submitRequirement, scanInbox } from './inbox.js';\nimport { MAX_INPUT_BYTES, parseRequirement, readInputFile } from './requirements.js';\nimport { dispatchRemote } from './remote.js';\nimport { toError } from './schemas.js';\nimport type { Config, Job } from './schemas.js';\n\nconst packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');\nconst print = (value: unknown): void => console.log(JSON.stringify(value, null, 2));\nconst view = (job: Job) => ({ id: job.id, product: job.spec.id, version: job.spec.apple?.version ?? '1.0.0', stage: job.stage, status: job.status, hostTurns: job.hostTurns, attempts: job.attempts, workspace: job.workspace, artifact: job.artifact ?? null, error: job.error ?? null });\n\nfunction inputJSON(path: string): unknown {\n const bytes = path === '-' ? readFileSync(0) : readInputFile(path);\n if (bytes.length > MAX_INPUT_BYTES) throw new Error(`Input exceeds ${MAX_INPUT_BYTES} bytes`);\n return JSON.parse(bytes.toString('utf8')) as unknown;\n}\n\nfunction arity(args: string[], minimum: number, maximum: number, usage: string): void {\n if (args.length < minimum || args.length > maximum) throw new Error(`Usage: app-factory ${usage}`);\n}\n\nfunction outcome(job: Job): void {\n print(view(job));\n if (job.status === 'blocked' || job.status === 'failed') process.exitCode = 2;\n}\n\nasync function probe(argv: string[], config: Config) {\n try {\n const result = await run(argv, { env: executionEnvironment(config), timeoutMs: 30000 });\n return { available: result.code === 0, detail: (result.code === 0 ? result.stdout : result.stderr || result.stdout).trim() };\n } catch (error) { return { available: false, detail: toError(error).message }; }\n}\n\nconst help = `app-factory <command> [--config factory.json] [--build-only]\n hosts List supported agent hosts and activation commands\n install <host> Install the workflow in the current project\n update <host> Update an existing, unmodified host workflow\n uninstall <host> Remove an unchanged installation owned by this package\n --project <directory> Select the project (default: current directory)\n --global Use the host's user-level skill directory instead\n doctor --host <host> Check installed workflow files; does not run the agent\n init [--allow-local-execution] Create factory.json without overwriting it\n doctor Check local build tools and execution permissions\n enqueue <spec.json|-> Persist requirements and return a task ID\n next <id> Resume a host turn or show the next deterministic action\n complete <id> <turn> [json|-] Record research/review, or finish the host's build turn\n abandon <id> <turn> Abandon a host turn without resetting its budget\n step <id> Execute one verification, packaging or Apple stage\n status [id] Read durable task state as JSON\n events <id> Read task transitions and evidence\n retry <id> Resume stopped work; preserve budgets and permissions\n submit <spec.json|-> Write requirements to the configured inbox\n scan [--observe] Validate or ingest inbox files; never start an agent\n inbox Read durable intake results\n reconcile-intake <id> <json|-> Restore a legacy request identity from original input\n authorize <id> --submit [--release] Explicitly grant publication permission\n feedback <id> <file> Resume using actual App Review rejection feedback\n remote <command> Call an installed Mac CLI over SSH (build-only)\n --version Print the installed package version\n\nYour agent supplies the model, session, tools and native delegation.\nNo coding-agent subprocess or autonomous daemon is started by this CLI.\nOnly verified local artifacts reach built; uploads require separate authorization.`;\n\nasync function main(): Promise<void> {\n const { values, positionals, tokens } = parseArgs({\n allowPositionals: true,\n tokens: true,\n options: {\n config: { type: 'string', default: 'factory.json' },\n 'build-only': { type: 'boolean', default: false },\n project: { type: 'string' },\n global: { type: 'boolean' },\n host: { type: 'string' },\n 'allow-local-execution': { type: 'boolean' },\n observe: { type: 'boolean' },\n submit: { type: 'boolean' },\n release: { type: 'boolean' },\n version: { type: 'boolean', short: 'v' },\n help: { type: 'boolean', short: 'h' },\n },\n });\n const [command, ...args] = positionals;\n if (values.version) {\n const metadata = JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf8')) as { version: string };\n console.log(metadata.version);\n return;\n }\n if (!command || command === 'help' || values.help) { console.log(help); return; }\n const flags: Record<string, string[]> = {\n hosts: [], install: ['project', 'global'], update: ['project', 'global'], uninstall: ['project', 'global'],\n init: ['config', 'allow-local-execution'], doctor: ['config', 'build-only', 'host', 'project', 'global'],\n enqueue: ['config', 'build-only'], next: ['config', 'build-only'], complete: ['config', 'build-only'], abandon: ['config', 'build-only'],\n step: ['config', 'build-only'], status: ['config', 'build-only'], events: ['config', 'build-only'], retry: ['config', 'build-only'],\n submit: ['config', 'build-only'], scan: ['config', 'build-only', 'observe'], inbox: ['config', 'build-only'],\n 'reconcile-intake': ['config', 'build-only'], authorize: ['config', 'build-only', 'submit', 'release'], feedback: ['config', 'build-only'],\n remote: ['config', 'observe'],\n };\n if (!(command in flags)) throw new Error(`Unknown command ${command}; run app-factory --help`);\n for (const token of tokens) {\n if (token.kind === 'option' && !flags[command].includes(token.name)) throw new Error(`${command} does not accept --${token.name}`);\n }\n const configPath = values.config;\n const buildOnly = values['build-only'];\n if (values.global && values.project) throw new Error('Choose --project or --global, not both');\n const scope = { project: values.project, global: values.global };\n if (command === 'hosts') { arity(args, 0, 0, 'hosts'); print(hosts); return; }\n if (command === 'install' || command === 'update' || command === 'uninstall') {\n arity(args, 1, 1, `${command} <host> [--project <directory> | --global]`);\n const operation = command === 'install' ? installIntegration : command === 'update' ? updateIntegration : uninstallIntegration;\n print(operation(args[0], scope));\n return;\n }\n if (command === 'doctor' && values.host) {\n arity(args, 0, 0, 'doctor --host <host> [--project <directory> | --global]');\n const result = inspectIntegration(values.host, scope);\n print(result);\n if (!result.installed) process.exitCode = 2;\n return;\n }\n if (command === 'doctor' && (values.project || values.global)) throw new Error('--project and --global require doctor --host <host>');\n if (command === 'init') {\n arity(args, 0, 0, 'init [--config <file>] [--allow-local-execution]');\n const template = JSON.parse(readFileSync(join(packageRoot, 'examples', 'factory.json'), 'utf8')) as Record<string, unknown>;\n if (values['allow-local-execution']) template.allowLocalExecution = true;\n mkdirSync(dirname(resolve(configPath)), { recursive: true });\n writeFileSync(configPath, `${JSON.stringify(template, null, 2)}\\n`, { flag: 'wx', mode: 0o600 });\n print({ config: resolve(configPath), localExecution: template.allowLocalExecution, next: 'Configure Flutter/Xcode and IOS_DEVICE_ID, then run app-factory doctor --build-only' });\n return;\n }\n if (command === 'remote') {\n process.exitCode = await dispatchRemote(loadJSON(configPath), [...args, ...(values.observe ? ['--observe'] : [])]);\n return;\n }\n const config = loadConfig(configPath);\n if (buildOnly && (config.allowSubmit || config.allowRelease)) throw new Error('--build-only refuses configurations with upload or release enabled');\n if (buildOnly && ['authorize', 'feedback'].includes(command)) throw new Error('Publication commands are forbidden in --build-only mode');\n if (command === 'submit') {\n arity(args, 1, 1, 'submit <spec.json|->');\n print(submitRequirement(config, inputJSON(args[0])));\n return;\n }\n if (command === 'doctor') {\n arity(args, 0, 0, 'doctor [--build-only]');\n const flutter = config.toolchain ? join(config.toolchain.flutterSdk, 'bin', 'flutter') : 'flutter';\n const [flutterCheck, xcode] = await Promise.all([probe([flutter, '--version'], config), probe(['xcodebuild', '-version'], config)]);\n const environment = executionEnvironment(config);\n const neededEnv = [...new Set([...config.verification.prerequisites, ...config.verification.checks, ...config.verification.runtime].flat().flatMap(arg => [...arg.matchAll(/\\$\\{([A-Z][A-Z0-9_]*)\\}/g)].map(match => match[1])))];\n const checks = [\n { name: 'local-execution-permission', available: config.allowLocalExecution, detail: config.executionWarning ?? 'Trusted local commands permitted; host tools remain under the host permission policy' },\n { name: 'flutter', ...flutterCheck }, { name: 'xcode', ...xcode },\n ...neededEnv.map(name => ({ name, available: Boolean(environment[name]), detail: environment[name] ? 'Available in the configured execution environment; value not printed' : `Set ${name} and include its name in executionEnv` })),\n ];\n if (config.allowSubmit) checks.push(...await appleDoctor());\n print({ checks, submissionEnabled: config.allowSubmit, model: 'Provided by your agent host; no separate model authentication is configured here' });\n if (checks.some(check => !check.available)) process.exitCode = 2;\n return;\n }\n const store = new Store(config.stateDir);\n try {\n if (command === 'enqueue') {\n arity(args, 1, 1, 'enqueue <spec.json|->');\n const { spec, baseJobId } = parseRequirement(inputJSON(args[0]));\n if (config.allowSubmit) validateAppleSpec(spec);\n print(view(store.enqueue(spec, config, baseJobId)));\n } else if (command === 'scan') {\n arity(args, 0, 0, 'scan [--observe]');\n const result = await scanInbox(store, config, { observe: values.observe });\n print(result);\n if (result.error || result.entries.some(entry => entry.status === 'error')) process.exitCode = 2;\n } else if (command === 'inbox') {\n arity(args, 0, 0, 'inbox');\n print(store.inboxEntries());\n } else if (command === 'status') {\n arity(args, 0, 1, 'status [id]');\n print(args[0] ? store.get(args[0]) : store.list().map(view));\n } else if (command === 'events') {\n arity(args, 1, 1, 'events <id>');\n print(store.events(args[0]));\n } else if (command === 'next') {\n arity(args, 1, 1, 'next <id>');\n const result = await nextTurn(store, args[0], { buildOnly });\n print(result);\n if (result.job.status === 'blocked' || result.job.status === 'failed') process.exitCode = 2;\n } else if (command === 'complete') {\n arity(args, 2, 3, 'complete <id> <turn-id> [result.json|-]');\n outcome(await completeTurn(store, args[0], args[1], args[2] ? inputJSON(args[2]) : undefined, { buildOnly }));\n } else if (command === 'abandon') {\n arity(args, 2, 2, 'abandon <id> <turn-id>');\n outcome(abandonTurn(store, args[0], args[1], { buildOnly }));\n } else if (command === 'step') {\n arity(args, 1, 1, 'step <id>');\n const controller = new AbortController();\n const stop = (): void => controller.abort();\n process.on('SIGINT', stop);\n process.on('SIGTERM', stop);\n try { outcome(await runStep(store, config, args[0], { signal: controller.signal, buildOnly })); }\n finally { process.removeListener('SIGINT', stop); process.removeListener('SIGTERM', stop); }\n } else if (command === 'reconcile-intake') {\n arity(args, 2, 2, 'reconcile-intake <id> <original-spec.json|->');\n const { spec, baseJobId } = parseRequirement(inputJSON(args[1]));\n print(view(store.reconcileIntake(args[0], spec, baseJobId)));\n } else if (command === 'retry') {\n arity(args, 1, 1, 'retry <id>');\n print(view(store.transition(args[0], job => {\n if (buildOnly && (job.config.allowSubmit || job.config.allowRelease)) throw new Error('--build-only refuses a task with snapshotted publication permission');\n if (!['blocked', 'ready', 'built'].includes(job.status)) throw new Error(`Cannot retry ${job.status}; budgets and active work cannot be reset by retry`);\n if (job.turn) throw new Error('Abandon the active host turn before changing task configuration');\n const proofChanged = JSON.stringify(config.verification) !== JSON.stringify(job.config.verification) || JSON.stringify(config.executionEnv) !== JSON.stringify(job.config.executionEnv) || JSON.stringify(config.toolchain) !== JSON.stringify(job.config.toolchain) || (job.verifiedDigest != null && !job.verifiedDependencyDigest);\n if (proofChanged && Object.keys(job.receipt).length) throw new Error('Verification proof is no longer valid after publication began; reconcile the existing upload before creating a new version');\n const patch: Partial<Job> = proofChanged && job.verifiedDigest ? { stage: 'verify', verifiedDigest: null, verifiedDependencyDigest: null, review: null, artifact: null } : {};\n const resumedConfig = { ...config, packageTarget: job.config.allowSubmit ? 'ipa' as const : config.packageTarget, allowSubmit: job.config.allowSubmit, allowRelease: job.config.allowRelease };\n return { ...patch, config: resumedConfig, status: 'queued', manualRecovery: null, nextAt: Date.now(), error: null };\n })));\n } else if (command === 'authorize') {\n arity(args, 1, 1, 'authorize <id> --submit [--release]');\n if (!values.submit) throw new Error('authorize requires --submit');\n print(view(store.transition(args[0], job => {\n if (job.turn || !['ready', 'blocked', 'built'].includes(job.status) || Object.keys(job.receipt).length || job.stage === 'monitor') throw new Error('Publication permission can change only on stopped work before any remote intent');\n const publishing = validateAppleSpec(job.spec);\n if (values.release && !/^[A-Za-z]{2}$/.test(publishing.storefrontCountry ?? '')) throw new Error('--release requires apple.storefrontCountry in the immutable product specification');\n const granted = { ...job.config, packageTarget: 'ipa' as const, allowSubmit: true, allowRelease: Boolean(values.release) };\n return { config: granted, status: ['ready', 'built'].includes(job.status) ? 'queued' : 'blocked', nextAt: Date.now() };\n })));\n } else if (command === 'feedback') {\n arity(args, 2, 2, 'feedback <id> <file>');\n const feedback = readInputFile(args[1], 'App Review feedback').toString('utf8').trim();\n if (!feedback) throw new Error('App Review feedback file is empty');\n print(view(store.transition(args[0], job => {\n if (job.turn || job.stage !== 'monitor' || job.appleReview?.status !== 'rejected' || job.status !== 'blocked') throw new Error('feedback is only allowed for an observed App Review rejection');\n const spec = structuredClone(job.spec);\n if (!spec.apple?.buildNumber || !/^\\d+$/.test(spec.apple.buildNumber)) throw new Error('A numeric apple.buildNumber is required to prepare a replacement build');\n spec.apple.buildNumber = String(BigInt(spec.apple.buildNumber) + 1n);\n const resumedConfig = { ...config, packageTarget: job.config.allowSubmit ? 'ipa' as const : config.packageTarget, allowSubmit: job.config.allowSubmit, allowRelease: job.config.allowRelease };\n return { spec, config: resumedConfig, feedback, status: 'queued', stage: 'build', createdAt: Date.now(), attempts: {}, hostTurns: 0, nextAt: Date.now(), receipt: {}, ipaPath: null, archiveDigest: null, verifiedDigest: null, verifiedDependencyDigest: null, review: null, error: null };\n })));\n }\n } finally { store.close(); }\n}\n\nmain().catch(error => { console.error(JSON.stringify({ error: toError(error).message })); process.exitCode = 1; });\n"],"mappings":"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"}
package/dist/config.js ADDED
@@ -0,0 +1,80 @@
1
+ import { ConfigSchema, SpecSchema, toError } from "./schemas.js";
2
+ import { z } from "zod";
3
+ import { basename, dirname, isAbsolute, relative, resolve, sep } from "node:path";
4
+ import { existsSync, lstatSync, readFileSync, realpathSync } from "node:fs";
5
+ // src/config.ts
6
+ function loadJSON(path) {
7
+ try {
8
+ return JSON.parse(readFileSync(path, "utf8"));
9
+ } catch (cause) {
10
+ throw new Error(`Cannot read JSON at ${path}: ${toError(cause).message}`);
11
+ }
12
+ }
13
+ function validateSpec(spec) {
14
+ return SpecSchema.parse(spec);
15
+ }
16
+ function canonicalPath(path) {
17
+ const absolute = resolve(path);
18
+ if (existsSync(absolute)) return realpathSync(absolute);
19
+ if (lstatSync(absolute, { throwIfNoEntry: false })?.isSymbolicLink()) throw new Error(`Cannot resolve dangling symlink at ${absolute}; point it to an existing directory before continuing`);
20
+ return resolve(canonicalPath(dirname(absolute)), basename(absolute));
21
+ }
22
+ function loadConfig(path = "factory.json") {
23
+ const configPath = resolve(path);
24
+ const source = z.record(z.string(), z.unknown()).parse(loadJSON(configPath));
25
+ const legacyKeys = [
26
+ "builder",
27
+ "reviewer",
28
+ "researcher",
29
+ "agentEnv",
30
+ "maxAgentCalls",
31
+ "maxConcurrentJobs"
32
+ ].filter((key) => key in source);
33
+ if (legacyKeys.length) throw new Error(`Legacy provider/daemon configuration is not supported (${legacyKeys.join(", ")}). Remove builder/reviewer/researcher/maxConcurrentJobs, rename agentEnv to executionEnv and maxAgentCalls to maxHostTurns. Models and authentication now belong to your host. Existing queue snapshots are migrated without deleting task history.`);
34
+ const root = dirname(configPath);
35
+ const config = ConfigSchema.parse({
36
+ ...source,
37
+ configPath,
38
+ stateDir: resolve(root, z.string().min(1).default(".factory").parse(source.stateDir)),
39
+ workspaceRoot: resolve(root, z.string().min(1).default("workspaces").parse(source.workspaceRoot))
40
+ });
41
+ if (config.toolchain) {
42
+ config.toolchain.flutterSdk = resolve(root, config.toolchain.flutterSdk);
43
+ if (existsSync(config.toolchain.flutterSdk)) config.toolchain.flutterSdk = realpathSync(config.toolchain.flutterSdk);
44
+ const workspace = existsSync(config.workspaceRoot) ? realpathSync(config.workspaceRoot) : config.workspaceRoot;
45
+ const child = relative(workspace, config.toolchain.flutterSdk);
46
+ if (!child || !(child === ".." || child.startsWith(`..${sep}`) || isAbsolute(child))) throw new Error("toolchain.flutterSdk must be outside workspaceRoot so verification does not trust an agent-writable SDK");
47
+ }
48
+ if (config.apple?.exportOptionsPlist) config.apple.exportOptionsPlist = resolve(root, config.apple.exportOptionsPlist);
49
+ if (config.inbox) {
50
+ config.inbox.directory = resolve(root, config.inbox.directory);
51
+ const workspace = canonicalPath(config.workspaceRoot);
52
+ const inbox = canonicalPath(config.inbox.directory);
53
+ const child = relative(workspace, inbox);
54
+ if (!child || !(child === ".." || child.startsWith(`..${sep}`) || isAbsolute(child))) throw new Error("inbox.directory must be outside workspaceRoot so agent output cannot become new requirements");
55
+ }
56
+ if (!config.allowLocalExecution) config.executionWarning = "Local execution is disabled. Use a dedicated OS account or VM, then set allowLocalExecution=true; a workspace is not a security sandbox.";
57
+ const stateChild = relative(canonicalPath(config.workspaceRoot), canonicalPath(config.stateDir));
58
+ if (!stateChild || !(stateChild === ".." || stateChild.startsWith(`..${sep}`) || isAbsolute(stateChild))) throw new Error("stateDir must be outside workspaceRoot so host tools do not own task state");
59
+ return {
60
+ ...config,
61
+ configPath
62
+ };
63
+ }
64
+ function inside(root, path) {
65
+ const resolved = resolve(root, path);
66
+ const child = relative(root, resolved);
67
+ if (!child || child === ".." || child.startsWith(`..${sep}`) || isAbsolute(child)) throw new Error(`Expected a path inside the workspace: ${path}`);
68
+ const actual = relative(canonicalPath(root), canonicalPath(resolved));
69
+ if (!actual || actual === ".." || actual.startsWith(`..${sep}`) || isAbsolute(actual)) throw new Error(`Workspace path escapes through a symlink: ${path}`);
70
+ return resolved;
71
+ }
72
+ function expandArgv(argv, env) {
73
+ return argv.map((arg) => arg.replace(/\$\{([A-Z][A-Z0-9_]*)\}/g, (_, key) => {
74
+ if (/^(ASC_|APPLE_|FASTLANE_|MATCH_)/.test(key) || !env[key]) throw Object.assign(/* @__PURE__ */ new Error(`Verification requires ${key} in the allowed worker environment; publisher secrets cannot be command arguments`), { code: "SETUP_REQUIRED" });
75
+ return env[key];
76
+ }));
77
+ }
78
+ export { canonicalPath, expandArgv, inside, loadConfig, loadJSON, validateSpec };
79
+
80
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","names":[],"sources":["../src/config.ts"],"sourcesContent":["import { readFileSync, existsSync, realpathSync, lstatSync } from 'node:fs';\nimport { basename, dirname, resolve, isAbsolute, relative, sep } from 'node:path';\nimport { z } from 'zod';\nimport { ConfigSchema, SpecSchema, toError } from './schemas.js';\nimport type { Config, Spec } from './schemas.js';\n\nexport function loadJSON(path: string): unknown {\n try { return JSON.parse(readFileSync(path, 'utf8')) as unknown; }\n catch (cause) { throw new Error(`Cannot read JSON at ${path}: ${toError(cause).message}`); }\n}\n\nexport function validateSpec(spec: unknown): Spec {\n return SpecSchema.parse(spec);\n}\n\nexport function canonicalPath(path: string): string {\n const absolute = resolve(path);\n if (existsSync(absolute)) return realpathSync(absolute);\n if (lstatSync(absolute, { throwIfNoEntry: false })?.isSymbolicLink()) throw new Error(`Cannot resolve dangling symlink at ${absolute}; point it to an existing directory before continuing`);\n return resolve(canonicalPath(dirname(absolute)), basename(absolute));\n}\n\nexport function loadConfig(path = 'factory.json'): Config & { configPath: string } {\n const configPath = resolve(path);\n const source = z.record(z.string(), z.unknown()).parse(loadJSON(configPath));\n const legacyKeys = ['builder', 'reviewer', 'researcher', 'agentEnv', 'maxAgentCalls', 'maxConcurrentJobs'].filter(key => key in source);\n if (legacyKeys.length) throw new Error(`Legacy provider/daemon configuration is not supported (${legacyKeys.join(', ')}). Remove builder/reviewer/researcher/maxConcurrentJobs, rename agentEnv to executionEnv and maxAgentCalls to maxHostTurns. Models and authentication now belong to your host. Existing queue snapshots are migrated without deleting task history.`);\n const root = dirname(configPath);\n const config = ConfigSchema.parse({\n ...source, configPath,\n stateDir: resolve(root, z.string().min(1).default('.factory').parse(source.stateDir)),\n workspaceRoot: resolve(root, z.string().min(1).default('workspaces').parse(source.workspaceRoot)),\n });\n if (config.toolchain) {\n config.toolchain.flutterSdk = resolve(root, config.toolchain.flutterSdk);\n if (existsSync(config.toolchain.flutterSdk)) config.toolchain.flutterSdk = realpathSync(config.toolchain.flutterSdk);\n const workspace = existsSync(config.workspaceRoot) ? realpathSync(config.workspaceRoot) : config.workspaceRoot;\n const child = relative(workspace, config.toolchain.flutterSdk);\n if (!child || !(child === '..' || child.startsWith(`..${sep}`) || isAbsolute(child))) throw new Error('toolchain.flutterSdk must be outside workspaceRoot so verification does not trust an agent-writable SDK');\n }\n if (config.apple?.exportOptionsPlist) config.apple.exportOptionsPlist = resolve(root, config.apple.exportOptionsPlist);\n if (config.inbox) {\n config.inbox.directory = resolve(root, config.inbox.directory);\n const workspace = canonicalPath(config.workspaceRoot);\n const inbox = canonicalPath(config.inbox.directory);\n const child = relative(workspace, inbox);\n if (!child || !(child === '..' || child.startsWith(`..${sep}`) || isAbsolute(child))) throw new Error('inbox.directory must be outside workspaceRoot so agent output cannot become new requirements');\n }\n if (!config.allowLocalExecution) config.executionWarning = 'Local execution is disabled. Use a dedicated OS account or VM, then set allowLocalExecution=true; a workspace is not a security sandbox.';\n const stateChild = relative(canonicalPath(config.workspaceRoot), canonicalPath(config.stateDir));\n if (!stateChild || !(stateChild === '..' || stateChild.startsWith(`..${sep}`) || isAbsolute(stateChild))) throw new Error('stateDir must be outside workspaceRoot so host tools do not own task state');\n return { ...config, configPath };\n}\n\nexport function inside(root: string, path: string): string {\n const resolved = resolve(root, path);\n const child = relative(root, resolved);\n if (!child || child === '..' || child.startsWith(`..${sep}`) || isAbsolute(child)) throw new Error(`Expected a path inside the workspace: ${path}`);\n const actual = relative(canonicalPath(root), canonicalPath(resolved));\n if (!actual || actual === '..' || actual.startsWith(`..${sep}`) || isAbsolute(actual)) throw new Error(`Workspace path escapes through a symlink: ${path}`);\n return resolved;\n}\n\nexport function expandArgv(argv: string[], env: NodeJS.ProcessEnv): string[] {\n return argv.map(arg => arg.replace(/\\$\\{([A-Z][A-Z0-9_]*)\\}/g, (_, key: string) => {\n if (/^(ASC_|APPLE_|FASTLANE_|MATCH_)/.test(key) || !env[key]) throw Object.assign(new Error(`Verification requires ${key} in the allowed worker environment; publisher secrets cannot be command arguments`), { code: 'SETUP_REQUIRED' });\n return env[key];\n }));\n}\n"],"mappings":"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"}
@@ -0,0 +1,157 @@
1
+ import { canonicalPath, inside } from "./config.js";
2
+ import { createHash } from "node:crypto";
3
+ import { isAbsolute, join, relative, resolve, sep } from "node:path";
4
+ import { existsSync, readFileSync } from "node:fs";
5
+ import { fileURLToPath, pathToFileURL } from "node:url";
6
+ import { parseDocument } from "yaml";
7
+ // src/dependencies.ts
8
+ const GENERATED_PATHS = /* @__PURE__ */ new Set([
9
+ ".git",
10
+ ".dart_tool",
11
+ ".idea",
12
+ ".factory-input",
13
+ ".factory-cache",
14
+ "build",
15
+ ".flutter-plugins-dependencies",
16
+ "ios/Pods",
17
+ "ios/.symlinks",
18
+ "ios/Flutter/Generated.xcconfig",
19
+ "ios/Flutter/flutter_export_environment.sh",
20
+ "ios/Flutter/ephemeral"
21
+ ]);
22
+ function isGeneratedPath(path) {
23
+ let candidate = sep === "/" ? path : path.split(sep).join("/");
24
+ while (candidate) {
25
+ if (GENERATED_PATHS.has(candidate)) return true;
26
+ const slash = candidate.lastIndexOf("/");
27
+ if (slash < 0) return false;
28
+ candidate = candidate.slice(0, slash);
29
+ }
30
+ return false;
31
+ }
32
+ function mapping(value, path) {
33
+ if (value === void 0 || value === null) return {};
34
+ if (typeof value !== "object" || Array.isArray(value)) throw new Error(`Expected a YAML mapping in ${path}; run flutter pub get after repairing the dependency metadata`);
35
+ return value;
36
+ }
37
+ function yaml(path) {
38
+ const document = parseDocument(readFileSync(path, "utf8"));
39
+ if (document.errors.length) throw new Error(`Cannot parse dependency metadata at ${path}: ${document.errors.map((error) => error.message).join("; ")}`);
40
+ return mapping(document.toJS(), path);
41
+ }
42
+ function validateLocalDependencies(root) {
43
+ const manifest = join(root, "pubspec.yaml");
44
+ if (!existsSync(manifest)) return;
45
+ const workspace = canonicalPath(root);
46
+ const rootSpec = yaml(manifest);
47
+ const overridesFile = join(root, "pubspec_overrides.yaml");
48
+ const overrides = {
49
+ ...mapping(rootSpec.dependency_overrides, manifest),
50
+ ...mapping(existsSync(overridesFile) ? yaml(overridesFile).dependency_overrides : void 0, overridesFile)
51
+ };
52
+ const localPackages = /* @__PURE__ */ new Map();
53
+ const immutablePackages = /* @__PURE__ */ new Set();
54
+ const visited = /* @__PURE__ */ new Set();
55
+ function owned(path) {
56
+ const absolute = resolve(path);
57
+ const lexical = relative(resolve(root), absolute);
58
+ const actual = relative(workspace, canonicalPath(absolute));
59
+ if (isAbsolute(lexical) || lexical === ".." || lexical.startsWith(`..${sep}`) || isAbsolute(actual) || actual === ".." || actual.startsWith(`..${sep}`) || isGeneratedPath(lexical) || isGeneratedPath(actual)) throw new Error(`Local dependency ${path} is outside fingerprinted workspace source; move the package into a non-generated workspace directory and run flutter pub get`);
60
+ if (lexical) inside(root, absolute);
61
+ return absolute;
62
+ }
63
+ function pathDependency(name, value, directory) {
64
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return;
65
+ const dependency = value;
66
+ if (!Object.hasOwn(dependency, "path")) return;
67
+ if (typeof dependency.path !== "string" || !dependency.path) throw new Error(`Local dependency ${name} in ${join(directory, "pubspec.yaml")} requires a nonempty path`);
68
+ if (isAbsolute(dependency.path)) throw new Error(`Local dependency ${name} in ${join(directory, "pubspec.yaml")} requires a workspace-relative path so verified source can be copied to a successor`);
69
+ const packageRoot = owned(resolve(directory, dependency.path));
70
+ const previous = localPackages.get(name);
71
+ if (previous && canonicalPath(previous) !== canonicalPath(packageRoot)) throw new Error(`Local dependency ${name} resolves to conflicting package directories; run flutter pub get before verification`);
72
+ localPackages.set(name, packageRoot);
73
+ visit(packageRoot, false);
74
+ }
75
+ function visit(directory, isRoot) {
76
+ directory = owned(directory);
77
+ if (visited.has(directory)) return;
78
+ visited.add(directory);
79
+ const path = join(directory, "pubspec.yaml");
80
+ const spec = isRoot ? rootSpec : yaml(path);
81
+ const dependencies = {
82
+ ...mapping(spec.dependencies, path),
83
+ ...isRoot ? mapping(spec.dev_dependencies, path) : {}
84
+ };
85
+ for (const [name, dependency] of Object.entries(dependencies)) {
86
+ const overridden = Object.hasOwn(overrides, name);
87
+ pathDependency(name, overridden ? overrides[name] : dependency, overridden ? root : directory);
88
+ }
89
+ }
90
+ visit(root, true);
91
+ const lockFile = join(root, "pubspec.lock");
92
+ if (existsSync(lockFile)) {
93
+ const packages = mapping(yaml(lockFile).packages, lockFile);
94
+ for (const [name, value] of Object.entries(packages)) {
95
+ const entry = mapping(value, lockFile);
96
+ if (entry.source === "path") pathDependency(name, { path: mapping(entry.description, lockFile).path }, root);
97
+ else if (entry.source === "hosted" || entry.source === "sdk" || entry.source === "git") immutablePackages.add(name);
98
+ }
99
+ }
100
+ const packageConfig = join(root, ".dart_tool", "package_config.json");
101
+ if (!existsSync(packageConfig)) return;
102
+ const packages = mapping(JSON.parse(readFileSync(packageConfig, "utf8")), packageConfig).packages;
103
+ if (!Array.isArray(packages)) throw new Error(`Invalid package resolution at ${packageConfig}; run flutter pub get before verification`);
104
+ for (const value of packages) {
105
+ const entry = mapping(value, packageConfig);
106
+ if (typeof entry.name !== "string" || typeof entry.rootUri !== "string") throw new Error(`Invalid package resolution at ${packageConfig}; run flutter pub get before verification`);
107
+ if (immutablePackages.has(entry.name) && !localPackages.has(entry.name)) continue;
108
+ const uri = new URL(entry.rootUri, pathToFileURL(packageConfig));
109
+ if (uri.protocol !== "file:") throw new Error(`Cannot establish local package source for ${entry.name} in ${packageConfig}; run flutter pub get before verification`);
110
+ const packageRoot = owned(fileURLToPath(uri));
111
+ const declared = localPackages.get(entry.name);
112
+ if (declared && canonicalPath(declared) !== canonicalPath(packageRoot)) throw new Error(`Resolved local dependency ${entry.name} differs from its declared path; run flutter pub get before verification`);
113
+ visit(packageRoot, canonicalPath(packageRoot) === workspace);
114
+ }
115
+ }
116
+ function dependencyDigest(root) {
117
+ const path = join(root, ".dart_tool", "package_config.json");
118
+ const hash = createHash("sha256").update("factory-dependency-resolution-v1\0");
119
+ if (!existsSync(path)) return hash.update("missing").digest("hex");
120
+ const config = JSON.parse(readFileSync(path, "utf8"));
121
+ const invalid = () => /* @__PURE__ */ new Error(`Invalid package resolution at ${path}; run flutter pub get before verification`);
122
+ if (!config || typeof config !== "object" || Array.isArray(config)) throw invalid();
123
+ const metadata = config;
124
+ if (!Number.isInteger(metadata.configVersion) || !Array.isArray(metadata.packages)) throw invalid();
125
+ const names = /* @__PURE__ */ new Set();
126
+ function normalized(uri) {
127
+ if (uri.protocol !== "file:") return uri.href;
128
+ const canonical = pathToFileURL(canonicalPath(fileURLToPath(uri)));
129
+ if (uri.pathname.endsWith("/") && !canonical.pathname.endsWith("/")) canonical.pathname += "/";
130
+ canonical.search = uri.search;
131
+ canonical.hash = uri.hash;
132
+ return canonical.href;
133
+ }
134
+ const packages = metadata.packages.map((value) => {
135
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw invalid();
136
+ const entry = value;
137
+ if (typeof entry.name !== "string" || !entry.name || names.has(entry.name) || typeof entry.rootUri !== "string" || entry.packageUri !== void 0 && typeof entry.packageUri !== "string" || entry.languageVersion !== void 0 && typeof entry.languageVersion !== "string") throw invalid();
138
+ names.add(entry.name);
139
+ const rootUri = new URL(entry.rootUri, pathToFileURL(path));
140
+ if (!rootUri.pathname.endsWith("/")) rootUri.pathname += "/";
141
+ const packageUri = new URL(entry.packageUri ?? "", rootUri);
142
+ if (!packageUri.pathname.endsWith("/")) packageUri.pathname += "/";
143
+ return {
144
+ name: entry.name,
145
+ rootUri: normalized(rootUri),
146
+ packageUri: normalized(packageUri),
147
+ languageVersion: entry.languageVersion ?? null
148
+ };
149
+ }).sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
150
+ return hash.update(JSON.stringify({
151
+ configVersion: metadata.configVersion,
152
+ packages
153
+ })).digest("hex");
154
+ }
155
+ export { GENERATED_PATHS, dependencyDigest, isGeneratedPath, validateLocalDependencies };
156
+
157
+ //# sourceMappingURL=dependencies.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dependencies.js","names":[],"sources":["../src/dependencies.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport { isAbsolute, join, relative, resolve, sep } from 'node:path';\nimport { fileURLToPath, pathToFileURL } from 'node:url';\nimport { createHash } from 'node:crypto';\nimport { parseDocument } from 'yaml';\nimport { canonicalPath, inside } from './config.js';\n\nexport const GENERATED_PATHS = new Set([\n '.git', '.dart_tool', '.idea', '.factory-input', '.factory-cache', 'build',\n '.flutter-plugins-dependencies', 'ios/Pods', 'ios/.symlinks',\n 'ios/Flutter/Generated.xcconfig', 'ios/Flutter/flutter_export_environment.sh', 'ios/Flutter/ephemeral',\n]);\n\nexport function isGeneratedPath(path: string): boolean {\n let candidate = sep === '/' ? path : path.split(sep).join('/');\n while (candidate) {\n if (GENERATED_PATHS.has(candidate)) return true;\n const slash = candidate.lastIndexOf('/');\n if (slash < 0) return false;\n candidate = candidate.slice(0, slash);\n }\n return false;\n}\n\nfunction mapping(value: unknown, path: string): Record<string, unknown> {\n if (value === undefined || value === null) return {};\n if (typeof value !== 'object' || Array.isArray(value)) throw new Error(`Expected a YAML mapping in ${path}; run flutter pub get after repairing the dependency metadata`);\n return value as Record<string, unknown>;\n}\n\nfunction yaml(path: string): Record<string, unknown> {\n const document = parseDocument(readFileSync(path, 'utf8'));\n if (document.errors.length) throw new Error(`Cannot parse dependency metadata at ${path}: ${document.errors.map(error => error.message).join('; ')}`);\n return mapping(document.toJS(), path);\n}\n\nexport function validateLocalDependencies(root: string): void {\n const manifest = join(root, 'pubspec.yaml');\n if (!existsSync(manifest)) return;\n const workspace = canonicalPath(root);\n const rootSpec = yaml(manifest);\n const overridesFile = join(root, 'pubspec_overrides.yaml');\n const overrides = {\n ...mapping(rootSpec.dependency_overrides, manifest),\n ...mapping(existsSync(overridesFile) ? yaml(overridesFile).dependency_overrides : undefined, overridesFile),\n };\n const localPackages = new Map<string, string>();\n const immutablePackages = new Set<string>();\n const visited = new Set<string>();\n\n function owned(path: string): string {\n const absolute = resolve(path);\n const lexical = relative(resolve(root), absolute);\n const actual = relative(workspace, canonicalPath(absolute));\n if (isAbsolute(lexical) || lexical === '..' || lexical.startsWith(`..${sep}`) || isAbsolute(actual) || actual === '..' || actual.startsWith(`..${sep}`) || isGeneratedPath(lexical) || isGeneratedPath(actual)) {\n throw new Error(`Local dependency ${path} is outside fingerprinted workspace source; move the package into a non-generated workspace directory and run flutter pub get`);\n }\n if (lexical) inside(root, absolute);\n return absolute;\n }\n\n function pathDependency(name: string, value: unknown, directory: string): void {\n if (value === null || typeof value !== 'object' || Array.isArray(value)) return;\n const dependency = value as Record<string, unknown>;\n if (!Object.hasOwn(dependency, 'path')) return;\n if (typeof dependency.path !== 'string' || !dependency.path) throw new Error(`Local dependency ${name} in ${join(directory, 'pubspec.yaml')} requires a nonempty path`);\n if (isAbsolute(dependency.path)) throw new Error(`Local dependency ${name} in ${join(directory, 'pubspec.yaml')} requires a workspace-relative path so verified source can be copied to a successor`);\n const packageRoot = owned(resolve(directory, dependency.path));\n const previous = localPackages.get(name);\n if (previous && canonicalPath(previous) !== canonicalPath(packageRoot)) throw new Error(`Local dependency ${name} resolves to conflicting package directories; run flutter pub get before verification`);\n localPackages.set(name, packageRoot);\n visit(packageRoot, false);\n }\n\n function visit(directory: string, isRoot: boolean): void {\n directory = owned(directory);\n if (visited.has(directory)) return;\n visited.add(directory);\n const path = join(directory, 'pubspec.yaml');\n const spec = isRoot ? rootSpec : yaml(path);\n const dependencies = { ...mapping(spec.dependencies, path), ...(isRoot ? mapping(spec.dev_dependencies, path) : {}) };\n for (const [name, dependency] of Object.entries(dependencies)) {\n const overridden = Object.hasOwn(overrides, name);\n pathDependency(name, overridden ? overrides[name] : dependency, overridden ? root : directory);\n }\n }\n\n\n visit(root, true);\n const lockFile = join(root, 'pubspec.lock');\n if (existsSync(lockFile)) {\n const packages = mapping(yaml(lockFile).packages, lockFile);\n for (const [name, value] of Object.entries(packages)) {\n const entry = mapping(value, lockFile);\n if (entry.source === 'path') {\n const description = mapping(entry.description, lockFile);\n pathDependency(name, { path: description.path }, root);\n } else if (entry.source === 'hosted' || entry.source === 'sdk' || entry.source === 'git') immutablePackages.add(name);\n }\n }\n\n const packageConfig = join(root, '.dart_tool', 'package_config.json');\n if (!existsSync(packageConfig)) return;\n const config: unknown = JSON.parse(readFileSync(packageConfig, 'utf8'));\n const packages = mapping(config, packageConfig).packages;\n if (!Array.isArray(packages)) throw new Error(`Invalid package resolution at ${packageConfig}; run flutter pub get before verification`);\n for (const value of packages) {\n const entry = mapping(value, packageConfig);\n if (typeof entry.name !== 'string' || typeof entry.rootUri !== 'string') throw new Error(`Invalid package resolution at ${packageConfig}; run flutter pub get before verification`);\n if (immutablePackages.has(entry.name) && !localPackages.has(entry.name)) continue;\n const uri = new URL(entry.rootUri, pathToFileURL(packageConfig));\n if (uri.protocol !== 'file:') throw new Error(`Cannot establish local package source for ${entry.name} in ${packageConfig}; run flutter pub get before verification`);\n const packageRoot = owned(fileURLToPath(uri));\n const declared = localPackages.get(entry.name);\n if (declared && canonicalPath(declared) !== canonicalPath(packageRoot)) throw new Error(`Resolved local dependency ${entry.name} differs from its declared path; run flutter pub get before verification`);\n visit(packageRoot, canonicalPath(packageRoot) === workspace);\n }\n}\n\nexport function dependencyDigest(root: string): string {\n const path = join(root, '.dart_tool', 'package_config.json');\n const hash = createHash('sha256').update('factory-dependency-resolution-v1\\0');\n if (!existsSync(path)) return hash.update('missing').digest('hex');\n const config: unknown = JSON.parse(readFileSync(path, 'utf8'));\n const invalid = () => new Error(`Invalid package resolution at ${path}; run flutter pub get before verification`);\n if (!config || typeof config !== 'object' || Array.isArray(config)) throw invalid();\n const metadata = config as Record<string, unknown>;\n if (!Number.isInteger(metadata.configVersion) || !Array.isArray(metadata.packages)) throw invalid();\n const names = new Set<string>();\n\n function normalized(uri: URL): string {\n if (uri.protocol !== 'file:') return uri.href;\n const canonical = pathToFileURL(canonicalPath(fileURLToPath(uri)));\n if (uri.pathname.endsWith('/') && !canonical.pathname.endsWith('/')) canonical.pathname += '/';\n canonical.search = uri.search;\n canonical.hash = uri.hash;\n return canonical.href;\n }\n\n const packages = metadata.packages.map((value: unknown) => {\n if (!value || typeof value !== 'object' || Array.isArray(value)) throw invalid();\n const entry = value as Record<string, unknown>;\n if (typeof entry.name !== 'string' || !entry.name || names.has(entry.name) || typeof entry.rootUri !== 'string' || (entry.packageUri !== undefined && typeof entry.packageUri !== 'string') || (entry.languageVersion !== undefined && typeof entry.languageVersion !== 'string')) throw invalid();\n names.add(entry.name);\n const rootUri = new URL(entry.rootUri, pathToFileURL(path));\n // Dart treats both URI fields as directories even without a trailing slash.\n if (!rootUri.pathname.endsWith('/')) rootUri.pathname += '/';\n const packageUri = new URL(entry.packageUri ?? '', rootUri);\n if (!packageUri.pathname.endsWith('/')) packageUri.pathname += '/';\n return { name: entry.name, rootUri: normalized(rootUri), packageUri: normalized(packageUri), languageVersion: entry.languageVersion ?? null };\n }).sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);\n return hash.update(JSON.stringify({ configVersion: metadata.configVersion, packages })).digest('hex');\n}\n"],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ import { z } from "zod";
2
+ import { join } from "node:path";
3
+ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
4
+ // src/export-options.ts
5
+ const ExportOptionsSchema = z.object({ destination: z.literal("export").default("export") }).passthrough();
6
+ async function prepareExportOptions(source, stateDir, run) {
7
+ const decoded = await run([
8
+ "/usr/bin/plutil",
9
+ "-convert",
10
+ "json",
11
+ "-o",
12
+ "-",
13
+ source
14
+ ]);
15
+ if (decoded.code !== 0) throw Object.assign(/* @__PURE__ */ new Error(`Cannot read IPA export options: ${decoded.stderr || decoded.stdout}`), { code: "SETUP_REQUIRED" });
16
+ let input;
17
+ try {
18
+ input = JSON.parse(decoded.stdout);
19
+ } catch {
20
+ throw Object.assign(/* @__PURE__ */ new Error("IPA export options did not decode to valid JSON"), { code: "SETUP_REQUIRED" });
21
+ }
22
+ const options = ExportOptionsSchema.safeParse(input);
23
+ if (!options.success) throw Object.assign(/* @__PURE__ */ new Error("IPA packaging only permits destination=export; destination=upload would bypass publication permission. Use local export options."), { code: "SETUP_REQUIRED" });
24
+ const directory = mkdtempSync(join(stateDir, "export-options-"));
25
+ try {
26
+ const path = join(directory, "ExportOptions.plist");
27
+ writeFileSync(path, JSON.stringify(options.data), { mode: 384 });
28
+ const converted = await run([
29
+ "/usr/bin/plutil",
30
+ "-convert",
31
+ "xml1",
32
+ path
33
+ ]);
34
+ if (converted.code !== 0) throw Object.assign(/* @__PURE__ */ new Error(`Cannot prepare local IPA export options: ${converted.stderr || converted.stdout}`), { code: "SETUP_REQUIRED" });
35
+ return {
36
+ directory,
37
+ path
38
+ };
39
+ } catch (error) {
40
+ rmSync(directory, {
41
+ recursive: true,
42
+ force: true
43
+ });
44
+ throw error;
45
+ }
46
+ }
47
+ export { prepareExportOptions };
48
+
49
+ //# sourceMappingURL=export-options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export-options.js","names":[],"sources":["../src/export-options.ts"],"sourcesContent":["import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { z } from 'zod';\nimport type { Run } from './process.js';\n\nconst ExportOptionsSchema = z.object({ destination: z.literal('export').default('export') }).passthrough();\n\nexport async function prepareExportOptions(source: string, stateDir: string, run: Run): Promise<{ directory: string; path: string }> {\n const decoded = await run(['/usr/bin/plutil', '-convert', 'json', '-o', '-', source]);\n if (decoded.code !== 0) throw Object.assign(new Error(`Cannot read IPA export options: ${decoded.stderr || decoded.stdout}`), { code: 'SETUP_REQUIRED' });\n let input: unknown;\n try { input = JSON.parse(decoded.stdout) as unknown; }\n catch { throw Object.assign(new Error('IPA export options did not decode to valid JSON'), { code: 'SETUP_REQUIRED' }); }\n const options = ExportOptionsSchema.safeParse(input);\n if (!options.success) throw Object.assign(new Error('IPA packaging only permits destination=export; destination=upload would bypass publication permission. Use local export options.'), { code: 'SETUP_REQUIRED' });\n const directory = mkdtempSync(join(stateDir, 'export-options-'));\n try {\n const path = join(directory, 'ExportOptions.plist');\n writeFileSync(path, JSON.stringify(options.data), { mode: 0o600 });\n const converted = await run(['/usr/bin/plutil', '-convert', 'xml1', path]);\n if (converted.code !== 0) throw Object.assign(new Error(`Cannot prepare local IPA export options: ${converted.stderr || converted.stdout}`), { code: 'SETUP_REQUIRED' });\n return { directory, path };\n } catch (error) {\n rmSync(directory, { recursive: true, force: true });\n throw error;\n }\n}\n"],"mappings":";;;;AAKA,MAAM,sBAAsB,EAAE,OAAO,EAAE,aAAa,EAAE,QAAQ,QAAQ,CAAC,CAAC,QAAQ,QAAQ,EAAE,CAAC,CAAC,CAAC,YAAY;AAEzG,eAAsB,qBAAqB,QAAgB,UAAkB,KAAwD;CACnI,MAAM,UAAU,MAAM,IAAI;EAAC;EAAmB;EAAY;EAAQ;EAAM;EAAK;CAAM,CAAC;CACpF,IAAI,QAAQ,SAAS,GAAG,MAAM,OAAO,uBAAO,IAAI,MAAM,mCAAmC,QAAQ,UAAU,QAAQ,QAAQ,GAAG,EAAE,MAAM,iBAAiB,CAAC;CACxJ,IAAI;CACJ,IAAI;EAAE,QAAQ,KAAK,MAAM,QAAQ,MAAM;CAAc,QAC/C;EAAE,MAAM,OAAO,uBAAO,IAAI,MAAM,iDAAiD,GAAG,EAAE,MAAM,iBAAiB,CAAC;CAAG;CACvH,MAAM,UAAU,oBAAoB,UAAU,KAAK;CACnD,IAAI,CAAC,QAAQ,SAAS,MAAM,OAAO,uBAAO,IAAI,MAAM,kIAAkI,GAAG,EAAE,MAAM,iBAAiB,CAAC;CACnN,MAAM,YAAY,YAAY,KAAK,UAAU,iBAAiB,CAAC;CAC/D,IAAI;EACF,MAAM,OAAO,KAAK,WAAW,qBAAqB;EAClD,cAAc,MAAM,KAAK,UAAU,QAAQ,IAAI,GAAG,EAAE,MAAM,IAAM,CAAC;EACjE,MAAM,YAAY,MAAM,IAAI;GAAC;GAAmB;GAAY;GAAQ;EAAI,CAAC;EACzE,IAAI,UAAU,SAAS,GAAG,MAAM,OAAO,uBAAO,IAAI,MAAM,4CAA4C,UAAU,UAAU,UAAU,QAAQ,GAAG,EAAE,MAAM,iBAAiB,CAAC;EACvK,OAAO;GAAE;GAAW;EAAK;CAC3B,SAAS,OAAO;EACd,OAAO,WAAW;GAAE,WAAW;GAAM,OAAO;EAAK,CAAC;EAClD,MAAM;CACR;AACF"}
@@ -0,0 +1,171 @@
1
+ import { ToolchainSchema, toError } from "./schemas.js";
2
+ import { inside } from "./config.js";
3
+ import { delimiter, join, relative, sep } from "node:path";
4
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
5
+ // src/flutter.ts
6
+ const FlutterIdentitySchema = ToolchainSchema.pick({
7
+ frameworkRevision: true,
8
+ engineRevision: true
9
+ }).strip();
10
+ function unavailable(message, reason, path) {
11
+ return Object.assign(new Error(message), {
12
+ code: "PREREQUISITE_FAILED",
13
+ prerequisite: {
14
+ reason,
15
+ path
16
+ }
17
+ });
18
+ }
19
+ function flutterEnvironment(config, workspace, base, trusted = false) {
20
+ if (!config.toolchain?.flutterSdk) return base;
21
+ const cache = trusted ? join(config.stateDir, "verification-cache") : join(workspace, ".factory-cache");
22
+ const sdk = trusted ? config.toolchain.flutterSdk : join(cache, "flutter");
23
+ return {
24
+ ...base,
25
+ PATH: `${join(sdk, "bin")}${delimiter}${base.PATH ?? ""}`,
26
+ FLUTTER_ROOT: sdk,
27
+ PUB_CACHE: join(cache, "pub"),
28
+ ANALYZER_STATE_LOCATION_OVERRIDE: join(cache, "analyzer"),
29
+ DART_SUPPRESS_ANALYTICS: "true",
30
+ FLUTTER_SUPPRESS_ANALYTICS: "true"
31
+ };
32
+ }
33
+ async function prepareFlutter(job, { run, env, signal }) {
34
+ if (!job.config.toolchain?.flutterSdk) return null;
35
+ signal?.throwIfAborted();
36
+ if (process.platform !== "darwin") throw unavailable("Isolated iOS Flutter SDK preparation requires macOS clone-capable storage.", "platform", job.workspace);
37
+ let source;
38
+ try {
39
+ source = realpathSync(job.config.toolchain.flutterSdk);
40
+ } catch (error) {
41
+ throw unavailable(`Flutter SDK is unavailable at the configured toolchain.flutterSdk: ${toError(error).message}`, "missing-sdk", job.config.toolchain.flutterSdk);
42
+ }
43
+ if (!existsSync(join(source, "bin", "flutter"))) throw unavailable("Configured toolchain.flutterSdk does not contain bin/flutter.", "missing-sdk", source);
44
+ const cache = inside(job.workspace, ".factory-cache");
45
+ const sdk = inside(job.workspace, ".factory-cache/flutter");
46
+ const overlap = relative(source, cache);
47
+ if (!overlap || !(overlap === ".." || overlap.startsWith(`..${sep}`))) throw unavailable("The Flutter SDK source cannot contain its workspace cache.", "overlapping-sdk", source);
48
+ if (job.toolchain && job.toolchain.source !== source) throw unavailable("This task is pinned to a different Flutter SDK. Restore its configured SDK or enqueue a new version; cached SDKs are not silently replaced.", "sdk-mismatch", join(cache, "flutter.json"));
49
+ mkdirSync(cache, {
50
+ recursive: true,
51
+ mode: 448
52
+ });
53
+ if (!existsSync(join(cache, ".gitignore"))) writeFileSync(join(cache, ".gitignore"), "*\n", { mode: 384 });
54
+ const marker = join(cache, "flutter.json");
55
+ let metadata;
56
+ if (existsSync(marker)) {
57
+ try {
58
+ metadata = ToolchainSchema.parse(JSON.parse(readFileSync(marker, "utf8")));
59
+ } catch (error) {
60
+ throw unavailable(`Cannot read the owned Flutter cache metadata: ${toError(error).message}`, "cache-metadata", marker);
61
+ }
62
+ if (metadata.source !== source) throw unavailable("This task is pinned to a different Flutter SDK. Restore its configured SDK or enqueue a new version; cached SDKs are not silently replaced.", "sdk-mismatch", marker);
63
+ } else if (existsSync(sdk)) throw unavailable("An unmanaged Flutter cache already exists in this workspace; it will not be overwritten.", "cache-conflict", sdk);
64
+ const pin = job.toolchain ?? metadata;
65
+ const checkVersion = (identity) => {
66
+ let current;
67
+ try {
68
+ current = FlutterIdentitySchema.parse(JSON.parse(readFileSync(join(source, "bin", "cache", "flutter.version.json"), "utf8")));
69
+ } catch (error) {
70
+ throw unavailable(`Cannot establish the verifier SDK identity: ${toError(error).message}`, "sdk-version-unavailable", source);
71
+ }
72
+ if (current.frameworkRevision !== identity.frameworkRevision || current.engineRevision !== identity.engineRevision) throw unavailable("The configured verifier SDK changed during this task. Restore the pinned SDK or enqueue a new version; old verification cannot be reused.", "sdk-version-changed", source);
73
+ };
74
+ if (pin) {
75
+ checkVersion(pin);
76
+ if (metadata && (metadata.frameworkRevision !== pin.frameworkRevision || metadata.engineRevision !== pin.engineRevision)) throw unavailable("The configured verifier SDK changed during this task. Restore the pinned SDK or enqueue a new version; old verification cannot be reused.", "sdk-version-changed", source);
77
+ }
78
+ if (!existsSync(sdk)) {
79
+ const initialized = await run([
80
+ join(source, "bin", "flutter"),
81
+ "--suppress-analytics",
82
+ "--version",
83
+ "--machine"
84
+ ], {
85
+ env: {
86
+ ...flutterEnvironment(job.config, job.workspace, env, true),
87
+ FLUTTER_ROOT: source
88
+ },
89
+ signal
90
+ });
91
+ if (initialized.code !== 0) throw unavailable(`Cannot initialize the configured Flutter SDK: ${initialized.stderr || initialized.stdout}`, "sdk-initialization", source);
92
+ let version;
93
+ try {
94
+ version = FlutterIdentitySchema.parse(JSON.parse(initialized.stdout));
95
+ } catch {
96
+ throw unavailable("Flutter --version --machine returned no valid version metadata.", "sdk-version", source);
97
+ }
98
+ const prepared = await run([
99
+ join(source, "bin", "flutter"),
100
+ "--suppress-analytics",
101
+ "precache",
102
+ "--ios"
103
+ ], {
104
+ env: {
105
+ ...flutterEnvironment(job.config, job.workspace, env, true),
106
+ FLUTTER_ROOT: source
107
+ },
108
+ signal
109
+ });
110
+ if (prepared.code !== 0) throw unavailable(`Cannot prepare Flutter artifacts before agent execution: ${prepared.stderr || prepared.stdout}`, "sdk-artifacts", source);
111
+ const identity = {
112
+ source,
113
+ frameworkRevision: version.frameworkRevision,
114
+ engineRevision: version.engineRevision
115
+ };
116
+ checkVersion(pin ?? identity);
117
+ if (pin && (identity.frameworkRevision !== pin.frameworkRevision || identity.engineRevision !== pin.engineRevision)) throw unavailable("The configured verifier SDK changed during this task. Restore the pinned SDK or enqueue a new version; old verification cannot be reused.", "sdk-version-changed", source);
118
+ metadata = {
119
+ ...identity,
120
+ ready: false
121
+ };
122
+ writeFileSync(marker, JSON.stringify(metadata), { mode: 384 });
123
+ const temporary = mkdtempSync(join(cache, "flutter-clone-"));
124
+ try {
125
+ const cloned = await run([
126
+ "/bin/cp",
127
+ "-cRp",
128
+ `${source}/.`,
129
+ temporary
130
+ ], { signal });
131
+ if (cloned.code !== 0) throw unavailable(`Cannot create the workspace-local copy-on-write SDK: ${cloned.stderr || cloned.stdout}`, "sdk-clone", source);
132
+ signal?.throwIfAborted();
133
+ renameSync(temporary, sdk);
134
+ } finally {
135
+ rmSync(temporary, {
136
+ recursive: true,
137
+ force: true
138
+ });
139
+ }
140
+ }
141
+ if (metadata?.ready) return metadata;
142
+ if (job.stage === "build" && !existsSync(join(job.workspace, "pubspec.yaml"))) {
143
+ const bundle = job.spec.apple?.bundleId;
144
+ const organization = typeof bundle === "string" && bundle.includes(".") ? bundle.slice(0, bundle.lastIndexOf(".")) : "com.example";
145
+ const created = await run([
146
+ join(sdk, "bin", "flutter"),
147
+ "--suppress-analytics",
148
+ "create",
149
+ "--no-pub",
150
+ "--platforms=ios",
151
+ "--project-name",
152
+ job.spec.id.replaceAll("-", "_"),
153
+ "--org",
154
+ organization,
155
+ "."
156
+ ], {
157
+ env,
158
+ signal
159
+ });
160
+ if (created.code !== 0) throw unavailable(`Cannot bootstrap the Flutter project: ${created.stderr || created.stdout}`, "sdk-bootstrap", sdk);
161
+ }
162
+ metadata = ToolchainSchema.parse({
163
+ ...metadata,
164
+ ready: true
165
+ });
166
+ writeFileSync(marker, JSON.stringify(metadata), { mode: 384 });
167
+ return metadata;
168
+ }
169
+ export { flutterEnvironment, prepareFlutter };
170
+
171
+ //# sourceMappingURL=flutter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flutter.js","names":[],"sources":["../src/flutter.ts"],"sourcesContent":["import { existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync } from 'node:fs';\nimport { join, delimiter, relative, sep } from 'node:path';\nimport { inside } from './config.js';\nimport { ToolchainSchema, toError, type Config, type Job, type Toolchain } from './schemas.js';\nimport type { Run } from './process.js';\n\nconst FlutterIdentitySchema = ToolchainSchema.pick({ frameworkRevision: true, engineRevision: true }).strip();\ntype FlutterIdentity = Pick<Toolchain, 'frameworkRevision' | 'engineRevision'>;\n\nexport interface PrepareFlutterOptions {\n run: Run;\n env: NodeJS.ProcessEnv;\n signal?: AbortSignal;\n}\n\nfunction unavailable(message: string, reason: string, path: string) {\n return Object.assign(new Error(message), { code: 'PREREQUISITE_FAILED', prerequisite: { reason, path } });\n}\n\nexport function flutterEnvironment(config: Config, workspace: string, base: NodeJS.ProcessEnv, trusted = false): NodeJS.ProcessEnv {\n if (!config.toolchain?.flutterSdk) return base;\n const cache = trusted ? join(config.stateDir, 'verification-cache') : join(workspace, '.factory-cache');\n const sdk = trusted ? config.toolchain.flutterSdk : join(cache, 'flutter');\n return {\n ...base,\n PATH: `${join(sdk, 'bin')}${delimiter}${base.PATH ?? ''}`,\n FLUTTER_ROOT: sdk,\n PUB_CACHE: join(cache, 'pub'),\n ANALYZER_STATE_LOCATION_OVERRIDE: join(cache, 'analyzer'),\n DART_SUPPRESS_ANALYTICS: 'true',\n FLUTTER_SUPPRESS_ANALYTICS: 'true',\n };\n}\n\nexport async function prepareFlutter(job: Pick<Job, 'config' | 'workspace' | 'spec' | 'stage' | 'toolchain'>, { run, env, signal }: PrepareFlutterOptions): Promise<Toolchain | null> {\n if (!job.config.toolchain?.flutterSdk) return null;\n signal?.throwIfAborted();\n if (process.platform !== 'darwin') throw unavailable('Isolated iOS Flutter SDK preparation requires macOS clone-capable storage.', 'platform', job.workspace);\n let source: string;\n try { source = realpathSync(job.config.toolchain.flutterSdk); }\n catch (error) { throw unavailable(`Flutter SDK is unavailable at the configured toolchain.flutterSdk: ${toError(error).message}`, 'missing-sdk', job.config.toolchain.flutterSdk); }\n if (!existsSync(join(source, 'bin', 'flutter'))) throw unavailable('Configured toolchain.flutterSdk does not contain bin/flutter.', 'missing-sdk', source);\n const cache = inside(job.workspace, '.factory-cache');\n const sdk = inside(job.workspace, '.factory-cache/flutter');\n const overlap = relative(source, cache);\n if (!overlap || !(overlap === '..' || overlap.startsWith(`..${sep}`))) throw unavailable('The Flutter SDK source cannot contain its workspace cache.', 'overlapping-sdk', source);\n if (job.toolchain && job.toolchain.source !== source) throw unavailable('This task is pinned to a different Flutter SDK. Restore its configured SDK or enqueue a new version; cached SDKs are not silently replaced.', 'sdk-mismatch', join(cache, 'flutter.json'));\n mkdirSync(cache, { recursive: true, mode: 0o700 });\n if (!existsSync(join(cache, '.gitignore'))) writeFileSync(join(cache, '.gitignore'), '*\\n', { mode: 0o600 });\n const marker = join(cache, 'flutter.json');\n let metadata: Toolchain | undefined;\n if (existsSync(marker)) {\n try { metadata = ToolchainSchema.parse(JSON.parse(readFileSync(marker, 'utf8'))); }\n catch (error) { throw unavailable(`Cannot read the owned Flutter cache metadata: ${toError(error).message}`, 'cache-metadata', marker); }\n if (metadata.source !== source) throw unavailable('This task is pinned to a different Flutter SDK. Restore its configured SDK or enqueue a new version; cached SDKs are not silently replaced.', 'sdk-mismatch', marker);\n } else if (existsSync(sdk)) {\n throw unavailable('An unmanaged Flutter cache already exists in this workspace; it will not be overwritten.', 'cache-conflict', sdk);\n }\n const pin = job.toolchain ?? metadata;\n const checkVersion = (identity: FlutterIdentity): void => {\n let current: FlutterIdentity;\n try { current = FlutterIdentitySchema.parse(JSON.parse(readFileSync(join(source, 'bin', 'cache', 'flutter.version.json'), 'utf8'))); }\n catch (error) { throw unavailable(`Cannot establish the verifier SDK identity: ${toError(error).message}`, 'sdk-version-unavailable', source); }\n if (current.frameworkRevision !== identity.frameworkRevision || current.engineRevision !== identity.engineRevision) throw unavailable('The configured verifier SDK changed during this task. Restore the pinned SDK or enqueue a new version; old verification cannot be reused.', 'sdk-version-changed', source);\n };\n if (pin) {\n checkVersion(pin);\n if (metadata && (metadata.frameworkRevision !== pin.frameworkRevision || metadata.engineRevision !== pin.engineRevision)) throw unavailable('The configured verifier SDK changed during this task. Restore the pinned SDK or enqueue a new version; old verification cannot be reused.', 'sdk-version-changed', source);\n }\n if (!existsSync(sdk)) {\n const initialized = await run([join(source, 'bin', 'flutter'), '--suppress-analytics', '--version', '--machine'], { env: { ...flutterEnvironment(job.config, job.workspace, env, true), FLUTTER_ROOT: source }, signal });\n if (initialized.code !== 0) throw unavailable(`Cannot initialize the configured Flutter SDK: ${initialized.stderr || initialized.stdout}`, 'sdk-initialization', source);\n let version: FlutterIdentity;\n try { version = FlutterIdentitySchema.parse(JSON.parse(initialized.stdout)); }\n catch { throw unavailable('Flutter --version --machine returned no valid version metadata.', 'sdk-version', source); }\n const prepared = await run([join(source, 'bin', 'flutter'), '--suppress-analytics', 'precache', '--ios'], { env: { ...flutterEnvironment(job.config, job.workspace, env, true), FLUTTER_ROOT: source }, signal });\n if (prepared.code !== 0) throw unavailable(`Cannot prepare Flutter artifacts before agent execution: ${prepared.stderr || prepared.stdout}`, 'sdk-artifacts', source);\n const identity = { source, frameworkRevision: version.frameworkRevision, engineRevision: version.engineRevision };\n checkVersion(pin ?? identity);\n if (pin && (identity.frameworkRevision !== pin.frameworkRevision || identity.engineRevision !== pin.engineRevision)) throw unavailable('The configured verifier SDK changed during this task. Restore the pinned SDK or enqueue a new version; old verification cannot be reused.', 'sdk-version-changed', source);\n metadata = { ...identity, ready: false };\n writeFileSync(marker, JSON.stringify(metadata), { mode: 0o600 });\n const temporary = mkdtempSync(join(cache, 'flutter-clone-'));\n try {\n const cloned = await run(['/bin/cp', '-cRp', `${source}/.`, temporary], { signal });\n if (cloned.code !== 0) throw unavailable(`Cannot create the workspace-local copy-on-write SDK: ${cloned.stderr || cloned.stdout}`, 'sdk-clone', source);\n signal?.throwIfAborted();\n renameSync(temporary, sdk);\n } finally { rmSync(temporary, { recursive: true, force: true }); }\n }\n if (metadata?.ready) return metadata;\n if (job.stage === 'build' && !existsSync(join(job.workspace, 'pubspec.yaml'))) {\n const bundle = job.spec.apple?.bundleId;\n const organization = typeof bundle === 'string' && bundle.includes('.') ? bundle.slice(0, bundle.lastIndexOf('.')) : 'com.example';\n const created = await run([join(sdk, 'bin', 'flutter'), '--suppress-analytics', 'create', '--no-pub', '--platforms=ios', '--project-name', job.spec.id.replaceAll('-', '_'), '--org', organization, '.'], { env, signal });\n if (created.code !== 0) throw unavailable(`Cannot bootstrap the Flutter project: ${created.stderr || created.stdout}`, 'sdk-bootstrap', sdk);\n }\n metadata = ToolchainSchema.parse({ ...metadata, ready: true });\n writeFileSync(marker, JSON.stringify(metadata), { mode: 0o600 });\n return metadata;\n}\n"],"mappings":"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"}