@farmslot/recipe-cli 0.10.0 → 0.11.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 (75) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/harness/adapter-plugins.d.ts +12 -2
  3. package/dist/harness/adapter-plugins.d.ts.map +1 -1
  4. package/dist/harness/adapter-plugins.js +18 -3
  5. package/dist/harness/adapter-plugins.js.map +1 -1
  6. package/dist/harness/adapters.d.ts +33 -4
  7. package/dist/harness/adapters.d.ts.map +1 -1
  8. package/dist/harness/adapters.js +72 -14
  9. package/dist/harness/adapters.js.map +1 -1
  10. package/dist/harness/cli-error.d.ts +5 -0
  11. package/dist/harness/cli-error.d.ts.map +1 -0
  12. package/dist/harness/cli-error.js +14 -0
  13. package/dist/harness/cli-error.js.map +1 -0
  14. package/dist/harness/command-contract.d.ts +6 -0
  15. package/dist/harness/command-contract.d.ts.map +1 -1
  16. package/dist/harness/command-contract.js +22 -0
  17. package/dist/harness/command-contract.js.map +1 -1
  18. package/dist/harness/commands/call.d.ts.map +1 -1
  19. package/dist/harness/commands/call.js +12 -0
  20. package/dist/harness/commands/call.js.map +1 -1
  21. package/dist/harness/commands/discover.d.ts.map +1 -1
  22. package/dist/harness/commands/discover.js +9 -3
  23. package/dist/harness/commands/discover.js.map +1 -1
  24. package/dist/harness/commands/doctor.js +3 -2
  25. package/dist/harness/commands/doctor.js.map +1 -1
  26. package/dist/harness/commands/prepare.d.ts.map +1 -1
  27. package/dist/harness/commands/prepare.js +3 -2
  28. package/dist/harness/commands/prepare.js.map +1 -1
  29. package/dist/harness/commands/run.d.ts.map +1 -1
  30. package/dist/harness/commands/run.js +8 -0
  31. package/dist/harness/commands/run.js.map +1 -1
  32. package/dist/harness/commands/status-watch.d.ts.map +1 -1
  33. package/dist/harness/commands/status-watch.js +3 -2
  34. package/dist/harness/commands/status-watch.js.map +1 -1
  35. package/dist/harness/commands/status.d.ts.map +1 -1
  36. package/dist/harness/commands/status.js +7 -2
  37. package/dist/harness/commands/status.js.map +1 -1
  38. package/dist/harness/commands/task-init.js +2 -2
  39. package/dist/harness/commands/task-init.js.map +1 -1
  40. package/dist/harness/context-state.d.ts +106 -0
  41. package/dist/harness/context-state.d.ts.map +1 -0
  42. package/dist/harness/context-state.js +41 -0
  43. package/dist/harness/context-state.js.map +1 -0
  44. package/dist/harness/context.d.ts +57 -0
  45. package/dist/harness/context.d.ts.map +1 -0
  46. package/dist/harness/context.js +409 -0
  47. package/dist/harness/context.js.map +1 -0
  48. package/dist/harness/doctor-report.d.ts +3 -0
  49. package/dist/harness/doctor-report.d.ts.map +1 -1
  50. package/dist/harness/doctor-report.js +3 -0
  51. package/dist/harness/doctor-report.js.map +1 -1
  52. package/dist/harness/harness-cli.d.ts +15 -0
  53. package/dist/harness/harness-cli.d.ts.map +1 -1
  54. package/dist/harness/harness-cli.js +187 -30
  55. package/dist/harness/harness-cli.js.map +1 -1
  56. package/dist/harness/index.d.ts +5 -3
  57. package/dist/harness/index.d.ts.map +1 -1
  58. package/dist/harness/index.js +5 -3
  59. package/dist/harness/index.js.map +1 -1
  60. package/dist/harness/overlay.js +3 -2
  61. package/dist/harness/overlay.js.map +1 -1
  62. package/dist/harness/parse-args.d.ts +2 -4
  63. package/dist/harness/parse-args.d.ts.map +1 -1
  64. package/dist/harness/parse-args.js +9 -17
  65. package/dist/harness/parse-args.js.map +1 -1
  66. package/dist/harness/run-engine.d.ts.map +1 -1
  67. package/dist/harness/run-engine.js +2 -0
  68. package/dist/harness/run-engine.js.map +1 -1
  69. package/dist/harness/shared.d.ts.map +1 -1
  70. package/dist/harness/shared.js +21 -6
  71. package/dist/harness/shared.js.map +1 -1
  72. package/dist/harness/task-view.d.ts.map +1 -1
  73. package/dist/harness/task-view.js +5 -2
  74. package/dist/harness/task-view.js.map +1 -1
  75. package/package.json +5 -5
@@ -1 +1 @@
1
- {"version":3,"file":"task-init.js","sourceRoot":"","sources":["../../../src/harness/commands/task-init.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"task-init.js","sourceRoot":"","sources":["../../../src/harness/commands/task-init.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,106 @@
1
+ import { CliError } from './cli-error.js';
2
+ /** Where a context value came from, strongest first. */
3
+ export type ContextSource = 'flag' | 'binding' | 'slot' | 'detect' | 'default';
4
+ /** The detect predicate that matched a checkout. */
5
+ export type DetectMatch = 'remote' | 'files';
6
+ export interface HarnessContext {
7
+ adapter?: {
8
+ value: string;
9
+ source: ContextSource;
10
+ /**
11
+ * The human label: '--adapter', '--platform' or 'positional' (flag),
12
+ * 'runtime-context' (binding), 'slot-config' (slot), the matched predicates
13
+ * joined by '+' (detect), 'default'.
14
+ */
15
+ detail: string;
16
+ /** The predicates that matched, for source 'detect'. */
17
+ matched?: DetectMatch[];
18
+ /** The library that declares the adapter, when it is a plugin. */
19
+ library?: string;
20
+ };
21
+ target: {
22
+ value: string;
23
+ source: 'flag' | 'default';
24
+ detail: '--target' | 'cwd';
25
+ };
26
+ /**
27
+ * The checkout's slot. `source: 'none'` (value null) when the runtime context
28
+ * names no slot either: detail 'no-pool-dir' when no pool directory is known,
29
+ * 'not-in-pool' (with `poolDir`) when the pool maps no slot to the checkout.
30
+ */
31
+ slot?: {
32
+ value: string;
33
+ source: 'slot' | 'binding';
34
+ detail: 'slot-config' | 'slot-config (~/farmslot-node/pool)' | 'runtime-context';
35
+ session?: string;
36
+ poolFile?: string;
37
+ ports: Record<string, number>;
38
+ } | {
39
+ value: null;
40
+ source: 'none';
41
+ detail: 'no-pool-dir';
42
+ } | {
43
+ value: null;
44
+ source: 'none';
45
+ detail: 'not-in-pool';
46
+ poolDir: string;
47
+ };
48
+ /**
49
+ * A runtime context the checkout would have read (an inherited
50
+ * RECIPE_RUNTIME_CONTEXT) whose repoRoot is another checkout: it binds no
51
+ * adapter or slot here.
52
+ */
53
+ ignoredBinding?: {
54
+ path: string;
55
+ repoRoot: string | null;
56
+ };
57
+ /**
58
+ * The generic ports the command takes. 'flag': a spelling of the option was
59
+ * typed (`value` when the spellings agree). 'env': the user's environment
60
+ * holds it (`filled: false`, left to the adapter). 'slot': the slot's port,
61
+ * set in the environment the adapters read (`filled: true`, `via: 'env'`,
62
+ * `names` the variables set; `invalidEnv` names a non-port value it replaced). The command's argv is
63
+ * never changed. Adapter-specific ports stay in `slot.ports`.
64
+ */
65
+ ports?: Partial<Record<ContextPortName, {
66
+ value?: number;
67
+ source: 'flag' | 'env' | 'slot';
68
+ filled?: boolean;
69
+ via?: 'env';
70
+ /** The environment names a fill set. */
71
+ names?: readonly string[];
72
+ invalidEnv?: {
73
+ name: string;
74
+ value: string;
75
+ };
76
+ }>>;
77
+ }
78
+ /** A generic port option a command may take: `--cdp-port`, `--watcher-port`. */
79
+ export type ContextPortName = 'cdp' | 'watcher';
80
+ /** One adapter whose detect predicates matched a checkout. */
81
+ export interface AdapterCandidate {
82
+ adapter: string;
83
+ matched: DetectMatch[];
84
+ library?: string;
85
+ }
86
+ /** More than one adapter matched a checkout, with nothing to choose between them. Exit 2. */
87
+ export declare class AdapterAmbiguousError extends CliError {
88
+ readonly code = "ADAPTER_AMBIGUOUS";
89
+ readonly userAction: string;
90
+ readonly candidates: AdapterCandidate[];
91
+ constructor(target: string, candidates: AdapterCandidate[]);
92
+ }
93
+ /** Record the invocation's context; undefined clears it. */
94
+ export declare function setHarnessContext(context: HarnessContext | undefined): void;
95
+ /** The context createHarnessCli resolved for this invocation, if any. */
96
+ export declare function harnessContext(): HarnessContext | undefined;
97
+ /**
98
+ * The adapter the resolved context names for `target`, or undefined when no
99
+ * context was resolved for it. Commands take it after their own flags.
100
+ */
101
+ export declare function contextAdapter(target: string): string | undefined;
102
+ /** `{ context }` for a command's --json envelope, or nothing outside a resolved invocation. */
103
+ export declare function harnessContextField(): {
104
+ context?: HarnessContext;
105
+ };
106
+ //# sourceMappingURL=context-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-state.d.ts","sourceRoot":"","sources":["../../src/harness/context-state.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C,wDAAwD;AACxD,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE/E,oDAAoD;AACpD,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,OAAO,CAAC;AAE7C,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,EAAE;QACR,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,aAAa,CAAC;QACtB;;;;WAIG;QACH,MAAM,EAAE,MAAM,CAAC;QACf,wDAAwD;QACxD,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;QACxB,kEAAkE;QAClE,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;QAAC,MAAM,EAAE,UAAU,GAAG,KAAK,CAAA;KAAE,CAAC;IAClF;;;;OAIG;IACH,IAAI,CAAC,EACD;QACE,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;QAC3B,MAAM,EAAE,aAAa,GAAG,oCAAoC,GAAG,iBAAiB,CAAC;QACjF,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KAC/B,GACD;QAAE,KAAK,EAAE,IAAI,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,aAAa,CAAA;KAAE,GACtD;QAAE,KAAK,EAAE,IAAI,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,aAAa,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5E;;;;OAIG;IACH,cAAc,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAC3D;;;;;;;OAOG;IACH,KAAK,CAAC,EAAE,OAAO,CACb,MAAM,CACJ,eAAe,EACf;QACE,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,CAAC;QAChC,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,GAAG,CAAC,EAAE,KAAK,CAAC;QACZ,wCAAwC;QACxC,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QAC1B,UAAU,CAAC,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC;KAC9C,CACF,CACF,CAAC;CACH;AAED,gFAAgF;AAChF,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,SAAS,CAAC;AAEhD,8DAA8D;AAC9D,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,6FAA6F;AAC7F,qBAAa,qBAAsB,SAAQ,QAAQ;IACjD,QAAQ,CAAC,IAAI,uBAAuB;IACpC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,gBAAgB,EAAE,CAAC;gBAC5B,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,gBAAgB,EAAE;CAU3D;AAID,4DAA4D;AAC5D,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,cAAc,GAAG,SAAS,GAAG,IAAI,CAE3E;AAED,yEAAyE;AACzE,wBAAgB,cAAc,IAAI,cAAc,GAAG,SAAS,CAE3D;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEjE;AAED,+FAA+F;AAC/F,wBAAgB,mBAAmB,IAAI;IAAE,OAAO,CAAC,EAAE,cAAc,CAAA;CAAE,CAElE"}
@@ -0,0 +1,41 @@
1
+ // The context one invocation resolved (context.ts): which adapter, target and
2
+ // slot a command acts on, and where each came from. Kept apart from the
3
+ // resolver so the commands and output modules read it without importing the
4
+ // plugin loader.
5
+ import path from 'node:path';
6
+ import { CliError } from './cli-error.js';
7
+ /** More than one adapter matched a checkout, with nothing to choose between them. Exit 2. */
8
+ export class AdapterAmbiguousError extends CliError {
9
+ constructor(target, candidates) {
10
+ const listed = candidates
11
+ .map((candidate) => `${candidate.adapter} (${candidate.matched.join(', ')})`)
12
+ .join(', ');
13
+ // EXIT.usage; shared.ts imports this module, so the value is spelled here.
14
+ super(`${target} matches more than one adapter: ${listed}`, 2);
15
+ this.code = 'ADAPTER_AMBIGUOUS';
16
+ this.name = 'AdapterAmbiguousError';
17
+ this.candidates = candidates;
18
+ this.userAction = `pass --adapter <${candidates.map((candidate) => candidate.adapter).join('|')}>`;
19
+ }
20
+ }
21
+ let current;
22
+ /** Record the invocation's context; undefined clears it. */
23
+ export function setHarnessContext(context) {
24
+ current = context;
25
+ }
26
+ /** The context createHarnessCli resolved for this invocation, if any. */
27
+ export function harnessContext() {
28
+ return current;
29
+ }
30
+ /**
31
+ * The adapter the resolved context names for `target`, or undefined when no
32
+ * context was resolved for it. Commands take it after their own flags.
33
+ */
34
+ export function contextAdapter(target) {
35
+ return current?.target.value === path.resolve(target) ? current.adapter?.value : undefined;
36
+ }
37
+ /** `{ context }` for a command's --json envelope, or nothing outside a resolved invocation. */
38
+ export function harnessContextField() {
39
+ return current ? { context: current } : {};
40
+ }
41
+ //# sourceMappingURL=context-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-state.js","sourceRoot":"","sources":["../../src/harness/context-state.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,wEAAwE;AACxE,4EAA4E;AAC5E,iBAAiB;AACjB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAgF1C,6FAA6F;AAC7F,MAAM,OAAO,qBAAsB,SAAQ,QAAQ;IAIjD,YAAY,MAAc,EAAE,UAA8B;QACxD,MAAM,MAAM,GAAG,UAAU;aACtB,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,GAAG,SAAS,CAAC,OAAO,KAAK,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;aAC5E,IAAI,CAAC,IAAI,CAAC,CAAC;QACd,2EAA2E;QAC3E,KAAK,CAAC,GAAG,MAAM,mCAAmC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC;QARxD,SAAI,GAAG,mBAAmB,CAAC;QASlC,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;QACpC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,UAAU,GAAG,mBAAmB,UAAU,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;IACrG,CAAC;CACF;AAED,IAAI,OAAmC,CAAC;AAExC,4DAA4D;AAC5D,MAAM,UAAU,iBAAiB,CAAC,OAAmC;IACnE,OAAO,GAAG,OAAO,CAAC;AACpB,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,cAAc;IAC5B,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,MAAc;IAC3C,OAAO,OAAO,EAAE,MAAM,CAAC,KAAK,KAAK,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AAC7F,CAAC;AAED,+FAA+F;AAC/F,MAAM,UAAU,mBAAmB;IACjC,OAAO,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AAC7C,CAAC"}
@@ -0,0 +1,57 @@
1
+ import { type AdapterLibraryOptions } from './adapter-plugins.js';
2
+ import type { HarnessContext } from './context-state.js';
3
+ export interface ResolveHarnessContextOptions {
4
+ /** The command's tokens, after the command name. */
5
+ tokens: readonly string[];
6
+ /**
7
+ * The command's positionals as its grammar reads them (`contractPositionals`);
8
+ * the first may be a platform target (`launch --json ios`). Default: the
9
+ * first token, when it is not an option.
10
+ */
11
+ positionals?: readonly string[];
12
+ /** false: resolve the target and slot only, never an adapter. Default true. */
13
+ adapter?: boolean;
14
+ /** Default: process.cwd(). */
15
+ cwd?: string;
16
+ /** The libraries whose declarations are candidates: the loader's trust set. */
17
+ load?: AdapterLibraryOptions;
18
+ /** The adapter a runtime context's or slot's `platform` names, when it is not an adapter id. */
19
+ slotAdapter?(platform: string): string | undefined;
20
+ /**
21
+ * The pool directory slot-config reads. Default (`resolveSlotPoolDir`):
22
+ * FARMSLOT_POOL_DIR, else $FARMSLOT_ROOT/pool, else ~/farmslot-node/pool when
23
+ * it exists. Without one, a checkout whose runtime context names no slot
24
+ * reports `slot: { value: null, source: 'none', detail: 'no-pool-dir' }`.
25
+ */
26
+ slotPoolDir?: string;
27
+ /** The adapter when nothing else decides. */
28
+ defaultAdapter?: string;
29
+ }
30
+ /**
31
+ * Resolve the invocation's context. Throws AdapterAmbiguousError when detection
32
+ * is reached and more than one adapter matches the target.
33
+ */
34
+ export declare function resolveHarnessContext(options: ResolveHarnessContextOptions): Promise<HarnessContext>;
35
+ /**
36
+ * The generic ports `options` (a command's grammar) takes, and the
37
+ * environment that fills the ones the user left open. The command's argv is
38
+ * never touched: a slot port reaches the adapter through the environment it
39
+ * already reads, which every adapter ranks below a typed flag, an alias and a
40
+ * passthrough port. A port is filled only when no spelling of it was typed and
41
+ * none of its environment names holds a port; a registered adapter's own port
42
+ * names (`devServer.portEnv`) are read and set with the watcher port's.
43
+ */
44
+ export declare function contextPorts(context: HarnessContext, tokens: readonly string[], options: Readonly<Record<string, unknown>>, env?: NodeJS.ProcessEnv): {
45
+ ports?: NonNullable<HarnessContext['ports']>;
46
+ env: Record<string, string>;
47
+ };
48
+ /** The one human line: `context: adapter <id> (<source>), target <path> (<source>), slot <id> (<source>)`. */
49
+ export declare function formatHarnessContext(context: HarnessContext): string;
50
+ /**
51
+ * Whether the runtime context read from `file` belongs to the checkout `root`:
52
+ * its `repoRoot` is `root`'s Git top level (real paths), or, without
53
+ * `repoRoot`, the file sits inside `root`. Anything else is another
54
+ * checkout's (an inherited RECIPE_RUNTIME_CONTEXT) and describes nothing here.
55
+ */
56
+ export declare function runtimeContextOwned(root: string, file: string, context: Readonly<Record<string, unknown>>): boolean;
57
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/harness/context.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,KAAK,qBAAqB,EAAmC,MAAM,sBAAsB,CAAC;AAUnG,OAAO,KAAK,EAAmB,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAI1E,MAAM,WAAW,4BAA4B;IAC3C,oDAAoD;IACpD,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B;;;;OAIG;IACH,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,+EAA+E;IAC/E,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,8BAA8B;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,+EAA+E;IAC/E,IAAI,CAAC,EAAE,qBAAqB,CAAC;IAC7B,gGAAgG;IAChG,WAAW,CAAC,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IACnD;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6CAA6C;IAC7C,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAKD;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,4BAA4B,GACpC,OAAO,CAAC,cAAc,CAAC,CA8EzB;AAoCD;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,cAAc,EACvB,MAAM,EAAE,SAAS,MAAM,EAAE,EACzB,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAC1C,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC;IAAE,KAAK,CAAC,EAAE,WAAW,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAoD/E;AAmBD,8GAA8G;AAC9G,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM,CA4BpE;AAoGD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GACzC,OAAO,CAMT"}
@@ -0,0 +1,409 @@
1
+ // One context per invocation: the adapter, target and slot a command acts on,
2
+ // and where each came from. The adapter resolves in this order: an explicit
3
+ // flag, the checkout's binding (the runtime context a run's prepare wrote), the
4
+ // slot whose checkout this is (slot-config), a unique detect match among the
5
+ // built-ins and the plugins the operator's libraries declare, then the host's
6
+ // default. Plugins are matched by their declaration's `detect`, never imported
7
+ // to detect; the host loads only the winner. Approvals (--approve-plan, mainnet
8
+ // and funding flags) are never part of the context: they stay explicit.
9
+ import { execFileSync } from 'node:child_process';
10
+ import fs from 'node:fs';
11
+ import os from 'node:os';
12
+ import path from 'node:path';
13
+ import { findSlotByRepo, resolveSlotPoolDir } from '@farmslot/protocol/node/slot-by-repo';
14
+ import { adapterPlugin, declaredAdapters } from './adapter-plugins.js';
15
+ import { adapterForPlatform, adapterPortEnv, detectAdapterMatch, harnessAdapters, isPlatformTarget, } from './adapters.js';
16
+ import { optionValues } from './command-contract.js';
17
+ import { validateRelativeRecipePath } from './host.js';
18
+ import { DEFAULT_RECIPE_RUNTIME_DIR, recipeRuntimeDir } from './paths.js';
19
+ /**
20
+ * Resolve the invocation's context. Throws AdapterAmbiguousError when detection
21
+ * is reached and more than one adapter matches the target.
22
+ */
23
+ export async function resolveHarnessContext(options) {
24
+ const cwd = options.cwd ?? process.cwd();
25
+ const targetFlag = optionValues(options.tokens, '--target').at(-1);
26
+ const target = targetFlag
27
+ ? { value: path.resolve(cwd, targetFlag), source: 'flag', detail: '--target' }
28
+ : { value: path.resolve(cwd), source: 'default', detail: 'cwd' };
29
+ const registry = harnessAdapters();
30
+ const declared = await declaredAdapters(options.load);
31
+ const known = (id) => id !== undefined && (registry.has(id) || declared.some((entry) => entry.id === id))
32
+ ? id
33
+ : undefined;
34
+ const platformAdapter = (platform) => typeof platform === 'string' && platform
35
+ ? known(options.slotAdapter?.(platform) ?? adapterForPlatform(platform))
36
+ : undefined;
37
+ // Without --target, a subdirectory answers for its checkout: the binding, the
38
+ // slot and detection read the Git top level.
39
+ const root = targetFlag ? target.value : (gitTopLevel(target.value) ?? target.value);
40
+ const runtimeDir = optionValues(options.tokens, '--runtime-dir').at(-1);
41
+ const binding = readBinding(root, runtimeDir);
42
+ const runtime = binding.runtime;
43
+ const pool = options.slotPoolDir
44
+ ? { dir: options.slotPoolDir, source: 'option' }
45
+ : resolveSlotPoolDir();
46
+ const pooled = pool
47
+ ? await poolSlot(root, pool.dir, pool.source === 'farmslot-node' ? 'slot-config (~/farmslot-node/pool)' : 'slot-config')
48
+ : 'no-pool-dir';
49
+ const pooledSlot = typeof pooled === 'object' ? pooled : undefined;
50
+ // The pool's slot keeps the owned runtime context's ports beside its own
51
+ // (camelCase runtime keys, snake_case pool keys), so they can win the fill.
52
+ const owned = runtimeSlot(runtime);
53
+ const slot = (pooledSlot
54
+ ? owned &&
55
+ owned.value !== pooledSlot.slot.value &&
56
+ runtimeDir !== undefined &&
57
+ path.normalize(runtimeDir) !== DEFAULT_RECIPE_RUNTIME_DIR
58
+ ? // An explicit --runtime-dir scratch runtime names its own slot:
59
+ // identity and ports describe that runtime, the pool's ports filling
60
+ // only what it lacks. The default context keeps the pool's identity.
61
+ { ...owned, ports: { ...pooledSlot.slot.ports, ...owned.ports } }
62
+ : {
63
+ ...pooledSlot.slot,
64
+ ports: { ...pooledSlot.slot.ports, ...runtimePorts(runtime) },
65
+ }
66
+ : undefined) ??
67
+ owned ??
68
+ (pool === undefined || pooled === 'no-pool-dir'
69
+ ? { value: null, source: 'none', detail: 'no-pool-dir' }
70
+ : { value: null, source: 'none', detail: 'not-in-pool', poolDir: pool.dir });
71
+ const adapter = options.adapter === false
72
+ ? undefined
73
+ : (flagAdapter(options.tokens, options.positionals) ??
74
+ sourced(platformAdapter(runtime?.platform), 'binding', 'runtime-context') ??
75
+ sourced(platformAdapter(pooledSlot?.platform), 'slot', 'slot-config') ??
76
+ detectedAdapter(root,
77
+ // A declaration that claims a built-in id is no candidate; selecting
78
+ // it reports the conflict.
79
+ declared.filter((entry) => !registry.has(entry.id) || adapterPlugin(entry.id))) ??
80
+ sourced(options.defaultAdapter, 'default', 'default'));
81
+ return {
82
+ ...(adapter ? { adapter } : {}),
83
+ target,
84
+ ...(slot ? { slot } : {}),
85
+ ...(binding.ignored ? { ignoredBinding: binding.ignored } : {}),
86
+ };
87
+ }
88
+ // Each generic port option, and the slot ports that fill it, in order.
89
+ // Per port: the option and its aliases (a typed one is the adapter's to read),
90
+ // the environment names the adapters read (`read`: any one set means the user
91
+ // chose; the fill never replaces it), and the names the fill sets, which are
92
+ // the ones recipe-cli itself sets for a typed flag. The slot's ports: the owned
93
+ // runtime context's first (a --runtime-dir scratch runtime names its own),
94
+ // then the pool's.
95
+ const PORT_OPTIONS = [
96
+ {
97
+ name: 'cdp',
98
+ option: '--cdp-port',
99
+ aliases: [],
100
+ read: ['RECIPE_CDP_PORT', 'CDP_PORT'],
101
+ set: ['RECIPE_CDP_PORT', 'CDP_PORT'],
102
+ slot: ['cdpPort', 'cdp_port'],
103
+ },
104
+ {
105
+ name: 'watcher',
106
+ option: '--watcher-port',
107
+ // `doctor`/`stop --port` and the retired `--metro-port` name the same port.
108
+ aliases: ['--port', '--metro-port'],
109
+ read: ['TERMINAL_APP_PORT', 'RECIPE_WATCHER_PORT', 'WATCHER_PORT', 'METRO_PORT'],
110
+ set: ['RECIPE_WATCHER_PORT', 'WATCHER_PORT', 'METRO_PORT'],
111
+ slot: ['devServerPort', 'watcherPort', 'metroPort', 'port'],
112
+ },
113
+ ];
114
+ /**
115
+ * The generic ports `options` (a command's grammar) takes, and the
116
+ * environment that fills the ones the user left open. The command's argv is
117
+ * never touched: a slot port reaches the adapter through the environment it
118
+ * already reads, which every adapter ranks below a typed flag, an alias and a
119
+ * passthrough port. A port is filled only when no spelling of it was typed and
120
+ * none of its environment names holds a port; a registered adapter's own port
121
+ * names (`devServer.portEnv`) are read and set with the watcher port's.
122
+ */
123
+ export function contextPorts(context, tokens, options, env = process.env) {
124
+ const ports = {};
125
+ const fill = {};
126
+ const slotPorts = context.slot?.value ? context.slot.ports : {};
127
+ for (const { name, option, aliases, read, set, slot } of PORT_OPTIONS) {
128
+ if (!(option in options))
129
+ continue;
130
+ const extra = name === 'watcher' ? adapterPortEnv() : [];
131
+ const typed = [
132
+ ...[option, ...aliases]
133
+ .filter((spelling) => spelling in options)
134
+ .flatMap((spelling) => optionValues(tokens, spelling)),
135
+ // A leaf's own port after `--` holds too: no fill competes with it.
136
+ ...passthroughValues(tokens, [option, ...aliases]),
137
+ ];
138
+ if (typed.length > 0) {
139
+ // The handler picks among the spellings; report the value only when they agree.
140
+ const values = new Set(typed.map(Number));
141
+ const [value] = [...values];
142
+ ports[name] =
143
+ values.size === 1 && Number.isInteger(value) && value > 0
144
+ ? { value: value, source: 'flag' }
145
+ : { source: 'flag' };
146
+ continue;
147
+ }
148
+ let invalidEnv;
149
+ const held = [...read, ...extra].find((key) => {
150
+ const raw = env[key];
151
+ if (raw === undefined || raw === '')
152
+ return false;
153
+ const port = Number(raw);
154
+ if (Number.isInteger(port) && port > 0)
155
+ return true;
156
+ // Not a port: it holds nothing, and the report shows what was there.
157
+ invalidEnv ??= { name: key, value: raw };
158
+ return false;
159
+ });
160
+ if (held !== undefined) {
161
+ ports[name] = { value: Number(env[held]), source: 'env', filled: false };
162
+ continue;
163
+ }
164
+ const fromSlot = slot.map((key) => slotPorts[key]).find((port) => port !== undefined);
165
+ if (fromSlot === undefined)
166
+ continue;
167
+ const names = [...new Set([...set, ...extra])];
168
+ for (const key of names)
169
+ fill[key] = String(fromSlot);
170
+ ports[name] = {
171
+ value: fromSlot,
172
+ source: 'slot',
173
+ filled: true,
174
+ via: 'env',
175
+ names,
176
+ ...(invalidEnv ? { invalidEnv } : {}),
177
+ };
178
+ }
179
+ return { ...(Object.keys(ports).length > 0 ? { ports } : {}), env: fill };
180
+ }
181
+ // The values `spellings` take after `--` (separate or `=value`), where the
182
+ // command's grammar does not apply and a leaf reads its own arguments.
183
+ function passthroughValues(tokens, spellings) {
184
+ const divider = tokens.indexOf('--');
185
+ if (divider === -1)
186
+ return [];
187
+ const values = [];
188
+ const rest = tokens.slice(divider + 1);
189
+ for (let index = 0; index < rest.length; index += 1) {
190
+ const token = rest[index] ?? '';
191
+ for (const spelling of spellings) {
192
+ if (token === spelling)
193
+ values.push(rest[index + 1] ?? '');
194
+ else if (token.startsWith(`${spelling}=`))
195
+ values.push(token.slice(spelling.length + 1));
196
+ }
197
+ }
198
+ return values;
199
+ }
200
+ /** The one human line: `context: adapter <id> (<source>), target <path> (<source>), slot <id> (<source>)`. */
201
+ export function formatHarnessContext(context) {
202
+ const adapter = context.adapter
203
+ ? `adapter ${context.adapter.value} (${context.adapter.source === 'detect'
204
+ ? `detected: ${context.adapter.detail}`
205
+ : context.adapter.detail})`
206
+ : 'adapter none';
207
+ const parts = [adapter, `target ${context.target.value} (${context.target.detail})`];
208
+ if (context.slot?.value === null)
209
+ parts.push(context.slot.detail === 'not-in-pool'
210
+ ? `slot unknown (not in ${homeRelative(context.slot.poolDir)})`
211
+ : 'slot unknown (no pool dir)');
212
+ else if (context.slot)
213
+ parts.push(`slot ${context.slot.value} (${context.slot.detail})`);
214
+ const ports = Object.entries(context.ports ?? {}).map(([name, port]) => `${name}${port.value === undefined ? '' : ` ${port.value}`} (${port.source === 'env' ? 'env, not filled' : port.source})`);
215
+ if (ports.length > 0)
216
+ parts.push(`ports ${ports.join(', ')}`);
217
+ if (context.ignoredBinding)
218
+ parts.push(`binding ignored (belongs to ${context.ignoredBinding.repoRoot ?? 'an unnamed checkout'})`);
219
+ return `context: ${parts.join(', ')}`;
220
+ }
221
+ function sourced(value, source, detail) {
222
+ return value === undefined ? undefined : { value, source, detail };
223
+ }
224
+ // The last --adapter, else the adapter of the last --platform, else a first
225
+ // positional that is a platform target (`launch ios`, `launch --json ios`).
226
+ function flagAdapter(tokens, positionals) {
227
+ const adapter = optionValues(tokens, '--adapter').at(-1);
228
+ if (adapter !== undefined)
229
+ return { value: adapter, source: 'flag', detail: '--adapter' };
230
+ const platform = optionValues(tokens, '--platform').at(-1);
231
+ if (platform !== undefined)
232
+ return {
233
+ value: adapterForPlatform(platform) ?? platform,
234
+ source: 'flag',
235
+ detail: '--platform',
236
+ };
237
+ const first = positionals ? positionals[0] : tokens[0];
238
+ if (first !== undefined && !first.startsWith('-') && isPlatformTarget(first))
239
+ return { value: adapterForPlatform(first) ?? first, source: 'flag', detail: 'positional' };
240
+ return undefined;
241
+ }
242
+ // Built-ins by their `detect`, plugins by their declaration's: a plugin is
243
+ // never imported to detect it.
244
+ function detectedAdapter(target, declared) {
245
+ const winner = detectAdapterMatch(target, declared);
246
+ return winner
247
+ ? {
248
+ value: winner.adapter,
249
+ source: 'detect',
250
+ detail: winner.matched.join('+'),
251
+ matched: winner.matched,
252
+ ...(winner.library ? { library: winner.library } : {}),
253
+ }
254
+ : undefined;
255
+ }
256
+ // The Git work tree `dir` is in, or undefined outside one.
257
+ function gitTopLevel(dir) {
258
+ try {
259
+ const top = execFileSync('git', ['-C', dir, 'rev-parse', '--show-toplevel'], {
260
+ encoding: 'utf8',
261
+ stdio: ['ignore', 'pipe', 'ignore'],
262
+ }).trim();
263
+ return top ? fs.realpathSync(top) : undefined;
264
+ }
265
+ catch {
266
+ // Not inside a Git work tree, or no git: the directory answers for itself.
267
+ return undefined;
268
+ }
269
+ }
270
+ // The runtime context a run's prepare wrote for this checkout, read where the
271
+ // commands read it: RECIPE_RUNTIME_CONTEXT, else <root>/<runtime dir>, where an
272
+ // explicit --runtime-dir counts before RECIPE_RUNTIME_DIR. A context whose
273
+ // repoRoot is another checkout (an inherited RECIPE_RUNTIME_CONTEXT) binds
274
+ // nothing and is reported; one without repoRoot binds only from inside `root`.
275
+ function readBinding(root, runtimeDir) {
276
+ let file;
277
+ let value;
278
+ try {
279
+ file =
280
+ process.env.RECIPE_RUNTIME_CONTEXT ??
281
+ path.join(root, runtimeDir === undefined
282
+ ? recipeRuntimeDir()
283
+ : validateRelativeRecipePath('--runtime-dir', runtimeDir), 'agentic-runtime.json');
284
+ value = JSON.parse(fs.readFileSync(file, 'utf8'));
285
+ }
286
+ catch {
287
+ // No context, one mid-write, an unreadable one, or a runtime dir the
288
+ // command will refuse itself: the checkout is unbound.
289
+ return {};
290
+ }
291
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
292
+ return {};
293
+ const runtime = value;
294
+ const repoRoot = typeof runtime.repoRoot === 'string' ? runtime.repoRoot : undefined;
295
+ return runtimeContextOwned(root, file, runtime)
296
+ ? { runtime }
297
+ : { ignored: { path: file, repoRoot: repoRoot ?? null } };
298
+ }
299
+ /**
300
+ * Whether the runtime context read from `file` belongs to the checkout `root`:
301
+ * its `repoRoot` is `root`'s Git top level (real paths), or, without
302
+ * `repoRoot`, the file sits inside `root`. Anything else is another
303
+ * checkout's (an inherited RECIPE_RUNTIME_CONTEXT) and describes nothing here.
304
+ */
305
+ export function runtimeContextOwned(root, file, context) {
306
+ // The checkout itself, not one nested in it: a clone under <checkout>/temp
307
+ // has its own top level and inherits nothing.
308
+ return typeof context.repoRoot === 'string'
309
+ ? samePath(context.repoRoot, gitTopLevel(root) ?? root)
310
+ : within(root, file);
311
+ }
312
+ function samePath(left, right) {
313
+ try {
314
+ return fs.realpathSync(left) === fs.realpathSync(right);
315
+ }
316
+ catch {
317
+ // A path that does not exist is nobody's checkout.
318
+ return false;
319
+ }
320
+ }
321
+ // `dir` with the home directory spelled `~`, for the human line.
322
+ function homeRelative(dir) {
323
+ const home = os.homedir();
324
+ return dir === home || dir.startsWith(`${home}${path.sep}`) ? `~${dir.slice(home.length)}` : dir;
325
+ }
326
+ // Whether `inner` is `outer` or inside it, comparing real paths.
327
+ function within(outer, inner) {
328
+ try {
329
+ const relative = path.relative(fs.realpathSync(outer), fs.realpathSync(inner));
330
+ return !(relative === '..' ||
331
+ relative.startsWith(`..${path.sep}`) ||
332
+ path.isAbsolute(relative));
333
+ }
334
+ catch {
335
+ // A path that does not exist owns nothing.
336
+ return false;
337
+ }
338
+ }
339
+ const RUNTIME_PORTS = ['watcherPort', 'devServerPort', 'metroPort', 'cdpPort'];
340
+ function runtimeSlot(runtime) {
341
+ if (typeof runtime?.slotId !== 'string' || !runtime.slotId)
342
+ return undefined;
343
+ return {
344
+ value: runtime.slotId,
345
+ source: 'binding',
346
+ detail: 'runtime-context',
347
+ ports: runtimePorts(runtime),
348
+ };
349
+ }
350
+ function runtimePorts(runtime) {
351
+ const ports = {};
352
+ for (const field of RUNTIME_PORTS) {
353
+ const port = Number(runtime?.[field]);
354
+ if (runtime?.[field] !== undefined && Number.isInteger(port) && port > 0)
355
+ ports[field] = port;
356
+ }
357
+ return ports;
358
+ }
359
+ // The slot slot-config maps this checkout to, with its own platform (never the
360
+ // pool's) for the adapter step; 'no-pool-dir' when the directory does not read.
361
+ async function poolSlot(target, poolDir, detail) {
362
+ let real;
363
+ try {
364
+ real = fs.realpathSync(target);
365
+ }
366
+ catch (error) {
367
+ // A missing checkout has no slot; any other IO failure is real.
368
+ if (missing(error))
369
+ return undefined;
370
+ throw error;
371
+ }
372
+ let match;
373
+ try {
374
+ match = await findSlotByRepo(poolDir, real);
375
+ }
376
+ catch (error) {
377
+ // A pool directory that does not exist is none; one that does not read
378
+ // (EACCES and the like) is a real failure.
379
+ if (missing(error))
380
+ return 'no-pool-dir';
381
+ throw error;
382
+ }
383
+ if (!match)
384
+ return undefined;
385
+ const ports = {};
386
+ for (const resource of Object.values(match.slot.resources ?? {})) {
387
+ for (const [field, value] of Object.entries(resource)) {
388
+ const port = Number(value);
389
+ if (/port$/u.test(field) && Number.isInteger(port) && port > 0)
390
+ ports[field] = port;
391
+ }
392
+ }
393
+ return {
394
+ slot: {
395
+ value: match.slot.id,
396
+ source: 'slot',
397
+ detail,
398
+ ...(match.slot.session ? { session: match.slot.session } : {}),
399
+ poolFile: match.poolFile,
400
+ ports,
401
+ },
402
+ ...(match.slot.platform ? { platform: match.slot.platform } : {}),
403
+ };
404
+ }
405
+ function missing(error) {
406
+ const code = error?.code;
407
+ return code === 'ENOENT' || code === 'ENOTDIR';
408
+ }
409
+ //# sourceMappingURL=context.js.map