@astryxdesign/cli 0.7.2 → 0.7.3-canary.06872b4

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 (212) hide show
  1. package/README.md +99 -66
  2. package/api/build/_adapter.d.mts +58 -0
  3. package/api/build/_adapter.mjs +115 -3
  4. package/api/build/blocks/blocks.d.mts +42 -0
  5. package/api/build/blocks/blocks.mjs +169 -0
  6. package/api/build/blocks/blocks.test.mjs +108 -0
  7. package/api/build/blocks/blocks.weights.json +1 -0
  8. package/api/build/build.mjs +25 -6
  9. package/api/build/kit/answer.d.mts +14 -0
  10. package/api/build/kit/answer.mjs +85 -0
  11. package/api/build/kit/answer.test.mjs +38 -0
  12. package/api/build/kit/kind.d.mts +25 -0
  13. package/api/build/kit/kind.mjs +170 -0
  14. package/api/build/kit/kit.d.mts +2 -4
  15. package/api/build/kit/kit.mjs +8 -398
  16. package/api/build/kit/rank.d.mts +3 -32
  17. package/api/build/kit/rank.mjs +25 -342
  18. package/api/build/kit/rank.test.mjs +2 -1
  19. package/api/build/kit/signals.d.mts +60 -0
  20. package/api/build/kit/signals.mjs +284 -0
  21. package/api/build/kit/start.d.mts +32 -0
  22. package/api/build/kit/start.mjs +163 -0
  23. package/api/build/kit/words.d.mts +72 -0
  24. package/api/build/kit/words.mjs +202 -0
  25. package/api/build/tree.d.mts +20 -0
  26. package/api/build/tree.mjs +52 -0
  27. package/api/build/tree.test.mjs +177 -0
  28. package/api/build/tree.weights.json +1 -0
  29. package/api/component/component.mjs +16 -2
  30. package/api/component/component.test.mjs +15 -0
  31. package/api/discover/_adapter.mjs +36 -9
  32. package/api/discover/_adapter.test.mjs +42 -0
  33. package/api/docs/_adapter.mjs +17 -5
  34. package/api/docs/docs.test.mjs +7 -1
  35. package/api/doctor/doctor.mjs +86 -12
  36. package/api/doctor/doctor.test.mjs +28 -3
  37. package/api/init/init.doc.mjs +20 -6
  38. package/api/init/init.test.mjs +18 -2
  39. package/api/init/init.type.d.mts +3 -3
  40. package/api/init/init.type.mjs +3 -3
  41. package/api/init/remove/remove.mjs +17 -4
  42. package/api/init/remove/remove.test.mjs +114 -2
  43. package/api/init/run/run.mjs +69 -6
  44. package/api/integration/authoring-checks.mjs +83 -9
  45. package/api/integration/pack-check.mjs +123 -4
  46. package/api/integration/pack-check.test.mjs +87 -0
  47. package/api/integration/validate-integration-case-disk.test.mjs +111 -0
  48. package/api/integration/validate-integration-gaps.test.mjs +1964 -0
  49. package/api/integration/validate-integration.d.mts +11 -2
  50. package/api/integration/validate-integration.mjs +226 -32
  51. package/api/integration/validate-integration.type.d.mts +58 -1
  52. package/api/integration/validate-integration.type.mjs +45 -2
  53. package/api/integration/validateIntegration.doc.mjs +1 -1
  54. package/api/search/ORDERING.md +28 -0
  55. package/api/search/_adapter.d.mts +66 -2
  56. package/api/search/_adapter.mjs +118 -10
  57. package/api/search/_candidate-score.d.mts +40 -0
  58. package/api/search/_candidate-score.mjs +238 -0
  59. package/api/search/_cards.json +6098 -0
  60. package/api/search/_compound.d.mts +50 -0
  61. package/api/search/_compound.mjs +170 -0
  62. package/api/search/_doc-searcher.d.mts +4 -0
  63. package/api/search/_doc-searcher.mjs +23 -0
  64. package/api/search/_doc-searcher.test.mjs +36 -0
  65. package/api/search/_doc-searcher.weights.json +1 -0
  66. package/api/search/_doc-text.d.mts +30 -0
  67. package/api/search/_doc-text.mjs +87 -0
  68. package/api/search/_entries.d.mts +52 -0
  69. package/api/search/_entries.mjs +158 -0
  70. package/api/search/_features.d.mts +35 -0
  71. package/api/search/_features.mjs +155 -0
  72. package/api/search/_gather-components.d.mts +26 -0
  73. package/api/search/_gather-components.mjs +284 -0
  74. package/api/search/_gather-docs.d.mts +20 -0
  75. package/api/search/_gather-docs.mjs +254 -0
  76. package/api/search/_gather-hooks.d.mts +12 -0
  77. package/api/search/_gather-hooks.mjs +52 -0
  78. package/api/search/_gather-templates.d.mts +25 -0
  79. package/api/search/_gather-templates.mjs +107 -0
  80. package/api/search/_lexical.d.mts +135 -0
  81. package/api/search/_lexical.mjs +351 -0
  82. package/api/search/_main-searcher.d.mts +19 -0
  83. package/api/search/_main-searcher.mjs +115 -0
  84. package/api/search/_main-searcher.test.mjs +106 -0
  85. package/api/search/_main-searcher.weights.json +1 -0
  86. package/api/search/_module-doc.d.mts +32 -0
  87. package/api/search/_module-doc.mjs +78 -0
  88. package/api/search/_order.d.mts +32 -0
  89. package/api/search/_order.mjs +162 -0
  90. package/api/search/_order.test.mjs +72 -0
  91. package/api/search/_priority.d.mts +50 -0
  92. package/api/search/_priority.mjs +139 -0
  93. package/api/search/_query-words.d.mts +25 -0
  94. package/api/search/_query-words.mjs +92 -0
  95. package/api/search/_query.d.mts +65 -0
  96. package/api/search/_query.mjs +176 -0
  97. package/api/search/_result.d.mts +97 -0
  98. package/api/search/_result.mjs +150 -0
  99. package/api/search/_score.d.mts +60 -0
  100. package/api/search/_score.mjs +283 -0
  101. package/api/search/_synonyms.d.mts +21 -0
  102. package/api/search/_synonyms.mjs +67 -0
  103. package/api/search/_template-searcher.d.mts +4 -0
  104. package/api/search/_template-searcher.mjs +23 -0
  105. package/api/search/_template-searcher.test.mjs +40 -0
  106. package/api/search/_template-searcher.weights.json +1 -0
  107. package/api/search/_titles.d.mts +33 -0
  108. package/api/search/_titles.mjs +65 -0
  109. package/api/search/_words.d.mts +64 -0
  110. package/api/search/_words.mjs +157 -0
  111. package/api/search/search.d.mts +22 -305
  112. package/api/search/search.doc.mjs +7 -5
  113. package/api/search/search.mjs +53 -2483
  114. package/api/search/search.type.d.mts +6 -1
  115. package/api/search/search.type.mjs +6 -1
  116. package/api/search/tree.d.mts +15 -0
  117. package/api/search/tree.mjs +40 -0
  118. package/api/search/tree.test.mjs +66 -0
  119. package/api/search/tree.weights.json +1 -0
  120. package/api/theme/build/build.packed-old-core.test.mjs +1 -0
  121. package/api/upgrade/_adapter.d.mts +57 -9
  122. package/api/upgrade/_adapter.mjs +198 -28
  123. package/api/upgrade/registry/registry.d.mts +10 -4
  124. package/api/upgrade/registry/registry.mjs +33 -16
  125. package/api/upgrade/registry/registry.test.mjs +85 -0
  126. package/api/upgrade/run/run.d.mts +4 -2
  127. package/api/upgrade/run/run.mjs +13 -2
  128. package/api/upgrade/run/run.test.mjs +417 -3
  129. package/api/upgrade/upgrade.doc.mjs +10 -5
  130. package/api/upgrade/upgrade.mjs +13 -2
  131. package/api/upgrade/upgrade.test.mjs +194 -0
  132. package/api/upgrade/upgrade.type.d.mts +5 -3
  133. package/api/upgrade/upgrade.type.mjs +8 -6
  134. package/assets/codemods/transforms/v0.7.0/__tests__/validate-data-token-ownership.test.mjs +11 -0
  135. package/assets/codemods/transforms/v0.7.0/validate-data-token-ownership.mjs +3 -3
  136. package/assets/docs/is-my-code-good.doc.mjs +287 -0
  137. package/assets/docs/tree/api.doc.mjs +6 -0
  138. package/assets/docs/tree/host-entry.doc.mjs +128 -0
  139. package/assets/docs/tree/template-grading-rubric.doc.mjs +16 -37
  140. package/assets/docs/tree/tokens-data.doc.mjs +2 -2
  141. package/assets/docs/working-with-ai.doc.mjs +6 -2
  142. package/assets/templates/themes/butter/butterTheme.ts +4 -2
  143. package/assets/templates/themes/stone/stoneTheme.ts +4 -2
  144. package/assets/templates/themes/y2k/y2kTheme.ts +4 -2
  145. package/authoring/debug/parse.d.mts +1 -1
  146. package/clients/cli/bin/astryx.mjs +7 -78
  147. package/clients/cli/commands/doctor.mjs +30 -0
  148. package/clients/cli/commands/doctor.test.mjs +65 -0
  149. package/clients/cli/commands/init.behavior.test.mjs +230 -1
  150. package/clients/cli/commands/init.doc.mjs +28 -9
  151. package/clients/cli/commands/init.flag-help.test.mjs +1 -0
  152. package/clients/cli/commands/init.mjs +2 -2
  153. package/clients/cli/commands/outside-project.test.mjs +2 -1
  154. package/clients/cli/commands/setup-nudge.test.mjs +61 -0
  155. package/clients/cli/commands/upgrade.doc.mjs +4 -2
  156. package/clients/cli/commands/upgrade.mjs +9 -2
  157. package/clients/cli/commands/upgrade.subfolder-path.test.mjs +97 -0
  158. package/clients/cli/index.mjs +19 -1
  159. package/clients/cli/run.mjs +108 -0
  160. package/foundation/agent-docs/agent-doc-state.d.mts +20 -7
  161. package/foundation/agent-docs/agent-doc-state.mjs +35 -8
  162. package/foundation/agent-docs/agent-docs.d.mts +100 -12
  163. package/foundation/agent-docs/agent-docs.mjs +1010 -99
  164. package/foundation/agent-docs/agent-docs.path-safety.test.mjs +1239 -17
  165. package/foundation/agent-docs/agent-docs.test.mjs +678 -35
  166. package/foundation/config/project.mjs +20 -22
  167. package/foundation/config/project.test.mjs +56 -0
  168. package/foundation/discovery/component-discovery.d.mts +37 -0
  169. package/foundation/discovery/component-discovery.mjs +80 -5
  170. package/foundation/discovery/component-loader.d.mts +1 -1
  171. package/foundation/discovery/docs-discovery.mjs +14 -1
  172. package/foundation/discovery/module-exports.d.mts +41 -0
  173. package/foundation/discovery/module-exports.mjs +253 -0
  174. package/foundation/discovery/template-adapter.mjs +18 -1
  175. package/foundation/discovery/template-needs.mjs +1 -44
  176. package/foundation/discovery/theme-discovery.mjs +147 -3
  177. package/foundation/doc-compiler/doc-loads.test.mjs +8 -0
  178. package/foundation/doc-compiler/read.mjs +59 -1
  179. package/foundation/doc-compiler/read.test.mjs +11 -2
  180. package/foundation/fs/paths.d.mts +70 -8
  181. package/foundation/fs/paths.mjs +556 -38
  182. package/foundation/fs/paths.test.mjs +430 -54
  183. package/foundation/integrations/autolink.d.mts +2 -1
  184. package/foundation/integrations/autolink.mjs +4 -13
  185. package/foundation/integrations/computer-integrations.d.mts +6 -3
  186. package/foundation/integrations/computer-integrations.mjs +38 -9
  187. package/foundation/integrations/contribution-inventory.d.mts +15 -20
  188. package/foundation/integrations/contribution-inventory.mjs +41 -5
  189. package/foundation/integrations/integrations.d.mts +27 -1
  190. package/foundation/integrations/integrations.mjs +61 -2
  191. package/foundation/integrations/validate-contributions.d.mts +115 -0
  192. package/foundation/integrations/validate-contributions.mjs +1168 -13
  193. package/foundation/release/handoff.d.mts +23 -4
  194. package/foundation/release/handoff.mjs +175 -8
  195. package/foundation/release/run-context.mjs +14 -0
  196. package/foundation/response/response-types.doc.mjs +1 -1
  197. package/foundation/route/route.d.mts +285 -0
  198. package/foundation/route/route.mjs +324 -0
  199. package/foundation/route/route.test.mjs +153 -0
  200. package/foundation/runtime/hosted-runtime.test.mjs +403 -0
  201. package/foundation/runtime/locked-versions.d.mts +32 -0
  202. package/foundation/runtime/locked-versions.mjs +65 -0
  203. package/foundation/runtime/package-root.d.mts +91 -0
  204. package/foundation/runtime/package-root.mjs +174 -0
  205. package/foundation/runtime/package-set.d.mts +62 -0
  206. package/foundation/runtime/package-set.mjs +130 -0
  207. package/foundation/runtime/package-set.test.mjs +432 -0
  208. package/host/door-conformance.test.mjs +274 -0
  209. package/host/index.d.ts +116 -0
  210. package/host/index.mjs +295 -0
  211. package/host/index.test.mjs +302 -0
  212. package/package.json +14 -9
@@ -0,0 +1,116 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * `@astryxdesign/cli/host`: the programmatic host entry. A tool that carries
5
+ * this CLI starts one run of it per process with explicit inputs
6
+ * (`spec:AST-081` FR1).
7
+ */
8
+
9
+ import type {DiscoverSource} from '../authoring/discover/type.js';
10
+
11
+ /** The inputs of one hosted run. Every option is optional. */
12
+ export interface HostedCliOptions {
13
+ /** The arguments after `astryx`. Default: none. */
14
+ argv?: readonly string[];
15
+ /**
16
+ * The working folder of the run. The run changes the process's working
17
+ * folder to it, finds its project there, and writes where the command
18
+ * writes from there. Default: the process's working folder.
19
+ */
20
+ cwd?: string;
21
+ /**
22
+ * The command the tool's users type to run the CLI, such as
23
+ * `acme astryx`. Printed commands start with it unless the integrations
24
+ * the run loads declare their own. A non-blank single line of at most 120
25
+ * characters, with no surrounding spaces and no Astryx agent-docs marker
26
+ * text. Default: none; printed commands use the package manager's name.
27
+ */
28
+ cliCommand?: string;
29
+ /**
30
+ * When the tool found the project's own CLI and did not run it, why, in the
31
+ * tool's own words, such as `cannot-start` or `untrusted`: a non-blank
32
+ * single line of at most 120 characters, with no surrounding spaces. The
33
+ * run is then never handed to that CLI: its release answers in its place
34
+ * when this copy, or a package set's host entry, is that release;
35
+ * otherwise this copy answers with one warning line naming the reason and
36
+ * `meta.answeredBy.copy` set to `fallback`. Default: none; a project's own
37
+ * CLI answers its project.
38
+ */
39
+ declinedProjectCli?: string;
40
+ /**
41
+ * The tool's package sets, in its order of preference: folders whose
42
+ * `node_modules` each hold a CLI, the Core equal to it, and the
43
+ * integrations installed with them. Inside a project, a set fills only the
44
+ * `@astryxdesign/*` packages the project leaves uninstalled that are not
45
+ * integrations. When a release other than this copy answers the folder,
46
+ * the first set whose CLI and Core are that release and that holds what the
47
+ * project would install for those packages serves the run. A run never
48
+ * installs a release: with no such set, this copy answers with one warning
49
+ * line. Default: none.
50
+ */
51
+ roots?: readonly string[];
52
+ /**
53
+ * Integrations the tool approves for use outside a project, by package
54
+ * name. Each loads from the package set that serves the run, as an
55
+ * integration installed for the computer does, and a name that set does not
56
+ * hold is left out. Default: none.
57
+ */
58
+ integrations?: readonly string[];
59
+ /**
60
+ * The tool's own discover sources. `astryx discover` calls them after every
61
+ * integration's source and before the official list, and reports each as
62
+ * the host's. Another package set's host entry that answers the run gets
63
+ * them too; a run handed to a CLI's bin does not. Default: none.
64
+ */
65
+ discoverSources?: readonly DiscoverSource[];
66
+ }
67
+
68
+ /** One package a hosted run resolved. */
69
+ export interface HostedPackage {
70
+ /** The package name. */
71
+ name: string;
72
+ /** Its version, or null when its package.json gives none. */
73
+ version: string | null;
74
+ /** Whether it came from the project or from a package set. */
75
+ source: 'project' | 'package-set';
76
+ }
77
+
78
+ /**
79
+ * The outcome of a hosted run that returned: this copy's, or, when another
80
+ * package set's release answered through its own host entry, that entry's.
81
+ */
82
+ export interface HostedCliResult {
83
+ /** The run's exit status, as the `astryx` bin would exit. */
84
+ status: number;
85
+ /** Each package the run resolved: the CLI, its Core, and its integrations. */
86
+ packages: HostedPackage[];
87
+ }
88
+
89
+ /**
90
+ * Run the CLI once in this process, as the `astryx` bin would with the same
91
+ * arguments in the same folder.
92
+ *
93
+ * The run owns the process's working folder, arguments, standard streams,
94
+ * and exit. The call settles when the command returns. Like the bin, the run
95
+ * instead ends the process, with the exit status the bin would exit with, and
96
+ * the call never settles, when it:
97
+ * - prints help or the version (`--help`, `-h`, `help`, `--version`, `-V`,
98
+ * `--version --json`), with status 0;
99
+ * - rejects the command line, such as an unknown command or option;
100
+ * - reports an error it stops on, including every error in `--json` mode;
101
+ * - meets an unexpected error, with status 1;
102
+ * - hands the run to another CLI's bin: the project's own CLI when
103
+ * `declinedProjectCli` is absent, or a package set whose CLI ships no host
104
+ * entry.
105
+ *
106
+ * When another package set's release answers through its own host entry,
107
+ * that entry runs in this process with the same inputs, and the call settles
108
+ * with its result.
109
+ *
110
+ * Rejects with a `TypeError`, before anything runs, for an unknown option or
111
+ * an invalid value; and with an `Error`, before anything runs, when a run has
112
+ * already started in this process.
113
+ */
114
+ export declare function runHostedCli(
115
+ options?: HostedCliOptions,
116
+ ): Promise<HostedCliResult>;
package/host/index.mjs ADDED
@@ -0,0 +1,295 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file `@astryxdesign/cli/host`, the programmatic host entry (spec AST-081
5
+ * FR1). A tool that carries this CLI starts one run of it with explicit
6
+ * inputs: the arguments, the working folder, the command name its users type,
7
+ * and, when it found the project's own CLI and did not run it, the reason.
8
+ *
9
+ * Its package sets, the integrations it approves, and its own discover sources
10
+ * are matched, loaded, and recorded as architecture:cli-surface INV30 says; a
11
+ * hosted run installs nothing. When another package set's release answers
12
+ * through its own host entry, that entry runs in this process with the same
13
+ * inputs, and the call settles with its result.
14
+ *
15
+ * @input `runHostedCli` options (see host/index.d.ts for the contract).
16
+ * @output The run's exit status and the packages it resolved, each with its
17
+ * source. The run owns the process's working folder, arguments, standard
18
+ * streams, and exit, exactly as a run of the `astryx` bin does: every path
19
+ * on which the bin exits the process exits it here too (host/index.d.ts
20
+ * lists them).
21
+ * @position host — the package's `./host` export; calls clients/cli/run.mjs.
22
+ */
23
+
24
+ import * as fs from 'node:fs';
25
+ import * as path from 'node:path';
26
+ import {fileURLToPath, pathToFileURL} from 'node:url';
27
+
28
+ const CLI_ROOT = path.resolve(
29
+ path.dirname(fileURLToPath(import.meta.url)),
30
+ '..',
31
+ );
32
+ const BIN = path.join(CLI_ROOT, 'clients', 'cli', 'bin', 'astryx.mjs');
33
+
34
+ /**
35
+ * The process, seen as the holder of one mark: whether a hosted run started in
36
+ * it, whichever copy of the CLI started it.
37
+ */
38
+ const processMarks = /** @type {{__astryxHostRunStarted?: boolean}} */ (
39
+ /** @type {unknown} */ (process)
40
+ );
41
+
42
+ /** The longest reason a host may give for not running the project's CLI. */
43
+ const MAX_REASON_LENGTH = 120;
44
+
45
+ const OPTION_NAMES = new Set([
46
+ 'argv',
47
+ 'cwd',
48
+ 'cliCommand',
49
+ 'declinedProjectCli',
50
+ 'roots',
51
+ 'integrations',
52
+ 'discoverSources',
53
+ ]);
54
+
55
+ /** A bare npm package name, scoped or not. */
56
+ const PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._~-]*\/)?[a-z0-9][a-z0-9._~-]*$/u;
57
+
58
+ /**
59
+ * @param {string} message
60
+ * @returns {TypeError}
61
+ */
62
+ function invalid(message) {
63
+ return new TypeError(`runHostedCli: ${message}`);
64
+ }
65
+
66
+ /**
67
+ * Check every input before anything runs.
68
+ * @param {unknown} options
69
+ * @returns {Promise<{argv: string[], cwd: string, cliCommand: string | undefined, declinedProjectCli: string | undefined, roots: string[], integrations: string[], discoverSources: Function[]}>}
70
+ */
71
+ async function readOptions(options) {
72
+ if (options === undefined) options = {};
73
+ if (
74
+ options === null ||
75
+ typeof options !== 'object' ||
76
+ Array.isArray(options)
77
+ ) {
78
+ throw invalid('options must be an object');
79
+ }
80
+ const record = /** @type {Record<string, unknown>} */ (options);
81
+ for (const key of Object.keys(record)) {
82
+ if (!OPTION_NAMES.has(key)) {
83
+ throw invalid(
84
+ `unknown option "${key}"; the options are ${[...OPTION_NAMES].join(', ')}`,
85
+ );
86
+ }
87
+ }
88
+
89
+ const argv = record.argv ?? [];
90
+ if (!Array.isArray(argv) || !argv.every(arg => typeof arg === 'string')) {
91
+ throw invalid('argv must be an array of strings');
92
+ }
93
+
94
+ const cwdInput = record.cwd ?? process.cwd();
95
+ if (typeof cwdInput !== 'string' || cwdInput === '') {
96
+ throw invalid('cwd must be a folder path');
97
+ }
98
+ const cwd = path.resolve(cwdInput);
99
+ let isFolder = false;
100
+ try {
101
+ isFolder = fs.statSync(cwd).isDirectory();
102
+ } catch {
103
+ isFolder = false;
104
+ }
105
+ if (!isFolder) throw invalid(`cwd is not a folder: ${cwd}`);
106
+
107
+ let cliCommand;
108
+ if (record.cliCommand !== undefined) {
109
+ const {parseCliCommandField} =
110
+ await import('../authoring/integration/schema.mjs');
111
+ try {
112
+ cliCommand = parseCliCommandField(record.cliCommand, 'cliCommand');
113
+ } catch (err) {
114
+ throw invalid(err instanceof Error ? err.message : String(err));
115
+ }
116
+ }
117
+
118
+ const declinedProjectCli = record.declinedProjectCli;
119
+ if (
120
+ declinedProjectCli !== undefined &&
121
+ (typeof declinedProjectCli !== 'string' ||
122
+ declinedProjectCli === '' ||
123
+ declinedProjectCli !== declinedProjectCli.trim() ||
124
+ /[\r\n]/u.test(declinedProjectCli) ||
125
+ declinedProjectCli.length > MAX_REASON_LENGTH)
126
+ ) {
127
+ throw invalid(
128
+ `declinedProjectCli must be a non-blank single line of at most ${MAX_REASON_LENGTH} characters, with no surrounding spaces`,
129
+ );
130
+ }
131
+
132
+ const rootsInput = record.roots ?? [];
133
+ if (
134
+ !Array.isArray(rootsInput) ||
135
+ !rootsInput.every(root => typeof root === 'string' && root !== '')
136
+ ) {
137
+ throw invalid('roots must be an array of folder paths');
138
+ }
139
+ /** @type {string[]} */
140
+ const roots = [];
141
+ for (const root of rootsInput) {
142
+ const folder = path.resolve(root);
143
+ let isRoot = false;
144
+ try {
145
+ isRoot = fs.statSync(folder).isDirectory();
146
+ } catch {
147
+ isRoot = false;
148
+ }
149
+ if (!isRoot) {
150
+ throw invalid(`roots names a path that is not a folder: ${folder}`);
151
+ }
152
+ if (!roots.includes(folder)) roots.push(folder);
153
+ }
154
+
155
+ const integrations = record.integrations ?? [];
156
+ if (
157
+ !Array.isArray(integrations) ||
158
+ !integrations.every(
159
+ name => typeof name === 'string' && PACKAGE_NAME.test(name),
160
+ )
161
+ ) {
162
+ throw invalid('integrations must be an array of package names');
163
+ }
164
+
165
+ const discoverSources = record.discoverSources ?? [];
166
+ if (
167
+ !Array.isArray(discoverSources) ||
168
+ !discoverSources.every(source => typeof source === 'function')
169
+ ) {
170
+ throw invalid('discoverSources must be an array of functions');
171
+ }
172
+
173
+ return {
174
+ argv: [...argv],
175
+ cwd,
176
+ cliCommand,
177
+ declinedProjectCli: /** @type {string | undefined} */ (declinedProjectCli),
178
+ roots,
179
+ integrations: [...new Set(/** @type {string[]} */ (integrations))],
180
+ discoverSources: [...discoverSources],
181
+ };
182
+ }
183
+
184
+ /**
185
+ * The bin's Node version gate, for a host process: same message, same exit
186
+ * status, same JSON envelope.
187
+ * @param {string[]} argv
188
+ * @returns {Promise<boolean>} whether the run may go on
189
+ */
190
+ async function nodeVersionGate(argv) {
191
+ const {isNodeVersionSupported, unsupportedNodeMessage} =
192
+ await import('../foundation/env/node-version.mjs');
193
+ if (isNodeVersionSupported(process.versions.node)) return true;
194
+ const msg = unsupportedNodeMessage(process.versions.node);
195
+ if (argv.includes('--json')) {
196
+ process.stdout.write(
197
+ `${JSON.stringify({apiVersion: 1, error: msg, code: 'ERR_NODE_VERSION'}, null, 2)}\n`,
198
+ );
199
+ } else {
200
+ process.stderr.write(`${msg}\n`);
201
+ }
202
+ process.exitCode = 1;
203
+ return false;
204
+ }
205
+
206
+ /**
207
+ * Load another release's host entry, from one of the host's package sets, to
208
+ * hand this run over to in this process (architecture:cli-surface INV30). The
209
+ * run stays one call of the host's: the one-call mark passes to the entry that
210
+ * answers, and the call settles with that entry's result.
211
+ * @param {string} hostEntry the package set's `host/index.mjs`
212
+ * @returns {Promise<(options: import('./index.js').HostedCliOptions) => Promise<import('./index.js').HostedCliResult>>}
213
+ */
214
+ async function loadHostEntry(hostEntry) {
215
+ const entry = await import(pathToFileURL(hostEntry).href);
216
+ if (typeof entry.runHostedCli !== 'function') {
217
+ throw new Error(`${hostEntry} exports no runHostedCli`);
218
+ }
219
+ return options => {
220
+ processMarks.__astryxHostRunStarted = false;
221
+ return entry.runHostedCli(options);
222
+ };
223
+ }
224
+
225
+ /**
226
+ * Run the CLI once in this process, as the `astryx` bin would run with the
227
+ * same arguments in the same folder, for a tool that carries this CLI.
228
+ *
229
+ * @param {import('./index.js').HostedCliOptions} [options]
230
+ * @returns {Promise<import('./index.js').HostedCliResult>}
231
+ */
232
+ export async function runHostedCli(options) {
233
+ const input = await readOptions(options);
234
+ if (processMarks.__astryxHostRunStarted) {
235
+ throw new Error(
236
+ 'runHostedCli: a run already started in this process; start each run in a new process',
237
+ );
238
+ }
239
+ processMarks.__astryxHostRunStarted = true;
240
+
241
+ process.chdir(input.cwd);
242
+ process.argv = [process.argv[0] ?? process.execPath, BIN, ...input.argv];
243
+ if (!(await nodeVersionGate(input.argv))) return {status: 1, packages: []};
244
+
245
+ const {installPlaceOf, projectRootOf} =
246
+ await import('../foundation/release/choose.mjs');
247
+ const {readReleaseFacts} = await import('../foundation/release/project.mjs');
248
+ const {activateHostedRun, recordResolved, resolvedPackages} =
249
+ await import('../foundation/runtime/package-root.mjs');
250
+ const real = fs.realpathSync(CLI_ROOT);
251
+ let projectRoot = null;
252
+ try {
253
+ projectRoot = projectRootOf(readReleaseFacts(input.cwd));
254
+ } catch {
255
+ projectRoot = null;
256
+ }
257
+ activateHostedRun({
258
+ roots: input.roots,
259
+ integrations: input.integrations,
260
+ discoverSources: input.discoverSources,
261
+ // The root that holds this CLI, or, for a CLI that is not installed in a
262
+ // node_modules folder, the folder beside it that holds its Core.
263
+ startingRoot: installPlaceOf(real).owner ?? path.dirname(real),
264
+ projectRoot,
265
+ loadHostEntry,
266
+ });
267
+ recordResolved(
268
+ '@astryxdesign/cli',
269
+ JSON.parse(fs.readFileSync(path.join(CLI_ROOT, 'package.json'), 'utf8'))
270
+ .version,
271
+ CLI_ROOT,
272
+ );
273
+
274
+ const {runCli} = await import('../clients/cli/run.mjs');
275
+ const run = () =>
276
+ runCli({
277
+ cliRoot: CLI_ROOT,
278
+ argv: [...input.argv],
279
+ declinedProjectCli: input.declinedProjectCli,
280
+ });
281
+ let answered;
282
+ if (input.cliCommand === undefined) {
283
+ answered = await run();
284
+ } else {
285
+ const {runWithCliDoor} = await import('../foundation/env/cli-command.mjs');
286
+ answered = await runWithCliDoor(input.cliCommand, run);
287
+ }
288
+ // Another package set's host entry answered the run (INV30).
289
+ if (answered) return answered;
290
+ const code = process.exitCode;
291
+ return {
292
+ status: typeof code === 'number' ? code : Number(code ?? 0) || 0,
293
+ packages: resolvedPackages(),
294
+ };
295
+ }