@farmslot/recipe-cli 0.10.1 → 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.
- package/CHANGELOG.md +18 -0
- package/dist/harness/adapter-plugins.d.ts +12 -2
- package/dist/harness/adapter-plugins.d.ts.map +1 -1
- package/dist/harness/adapter-plugins.js +18 -3
- package/dist/harness/adapter-plugins.js.map +1 -1
- package/dist/harness/adapters.d.ts +33 -4
- package/dist/harness/adapters.d.ts.map +1 -1
- package/dist/harness/adapters.js +72 -14
- package/dist/harness/adapters.js.map +1 -1
- package/dist/harness/cli-error.d.ts +5 -0
- package/dist/harness/cli-error.d.ts.map +1 -0
- package/dist/harness/cli-error.js +14 -0
- package/dist/harness/cli-error.js.map +1 -0
- package/dist/harness/command-contract.d.ts +6 -0
- package/dist/harness/command-contract.d.ts.map +1 -1
- package/dist/harness/command-contract.js +22 -0
- package/dist/harness/command-contract.js.map +1 -1
- package/dist/harness/commands/call.d.ts.map +1 -1
- package/dist/harness/commands/call.js +12 -0
- package/dist/harness/commands/call.js.map +1 -1
- package/dist/harness/commands/discover.d.ts.map +1 -1
- package/dist/harness/commands/discover.js +9 -3
- package/dist/harness/commands/discover.js.map +1 -1
- package/dist/harness/commands/doctor.js +3 -2
- package/dist/harness/commands/doctor.js.map +1 -1
- package/dist/harness/commands/prepare.d.ts.map +1 -1
- package/dist/harness/commands/prepare.js +3 -2
- package/dist/harness/commands/prepare.js.map +1 -1
- package/dist/harness/commands/run.d.ts.map +1 -1
- package/dist/harness/commands/run.js +8 -0
- package/dist/harness/commands/run.js.map +1 -1
- package/dist/harness/commands/status-watch.d.ts.map +1 -1
- package/dist/harness/commands/status-watch.js +3 -2
- package/dist/harness/commands/status-watch.js.map +1 -1
- package/dist/harness/commands/status.d.ts.map +1 -1
- package/dist/harness/commands/status.js +7 -2
- package/dist/harness/commands/status.js.map +1 -1
- package/dist/harness/commands/task-init.js +2 -2
- package/dist/harness/commands/task-init.js.map +1 -1
- package/dist/harness/context-state.d.ts +106 -0
- package/dist/harness/context-state.d.ts.map +1 -0
- package/dist/harness/context-state.js +41 -0
- package/dist/harness/context-state.js.map +1 -0
- package/dist/harness/context.d.ts +57 -0
- package/dist/harness/context.d.ts.map +1 -0
- package/dist/harness/context.js +409 -0
- package/dist/harness/context.js.map +1 -0
- package/dist/harness/doctor-report.d.ts +3 -0
- package/dist/harness/doctor-report.d.ts.map +1 -1
- package/dist/harness/doctor-report.js +3 -0
- package/dist/harness/doctor-report.js.map +1 -1
- package/dist/harness/harness-cli.d.ts +15 -0
- package/dist/harness/harness-cli.d.ts.map +1 -1
- package/dist/harness/harness-cli.js +187 -30
- package/dist/harness/harness-cli.js.map +1 -1
- package/dist/harness/index.d.ts +5 -3
- package/dist/harness/index.d.ts.map +1 -1
- package/dist/harness/index.js +5 -3
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/overlay.js +3 -2
- package/dist/harness/overlay.js.map +1 -1
- package/dist/harness/parse-args.d.ts +2 -4
- package/dist/harness/parse-args.d.ts.map +1 -1
- package/dist/harness/parse-args.js +9 -17
- package/dist/harness/parse-args.js.map +1 -1
- package/dist/harness/run-engine.d.ts.map +1 -1
- package/dist/harness/run-engine.js +2 -0
- package/dist/harness/run-engine.js.map +1 -1
- package/dist/harness/shared.d.ts.map +1 -1
- package/dist/harness/shared.js +5 -3
- package/dist/harness/shared.js.map +1 -1
- package/dist/harness/task-view.d.ts.map +1 -1
- package/dist/harness/task-view.js +5 -2
- package/dist/harness/task-view.js.map +1 -1
- package/package.json +5 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-init.js","sourceRoot":"","sources":["../../../src/harness/commands/task-init.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,iFAAiF;AACjF,sEAAsE;AACtE,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAAE,
|
|
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":"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"}
|
|
@@ -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
|