genesis-compiler 1.2.12 → 1.2.13

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.
@@ -0,0 +1,54 @@
1
+ import { GenesisError } from './errors.js';
2
+ import { isSafeProcessExecutable, parseBacktickedArguments } from './stack-process.js';
3
+
4
+ function verificationDiagnostic(stackPath, message, line, details = {}) {
5
+ throw new GenesisError('STACK_VERIFICATION_INVALID', message, {
6
+ path: stackPath,
7
+ ...(line === undefined ? {} : { line }),
8
+ ...details,
9
+ });
10
+ }
11
+
12
+ export function parseStackVerificationLines(lines, { path: stackPath } = {}) {
13
+ const commands = [];
14
+
15
+ for (let index = 0; index < lines.length; index += 1) {
16
+ const source = lines[index];
17
+ const line = index + 1;
18
+ if (!source.trim()) continue;
19
+ const entry = source.match(/^- Verify `([^`\r\n]+)`:[ \t]+(.+)$/u);
20
+ if (!entry) {
21
+ verificationDiagnostic(
22
+ stackPath,
23
+ 'Every Verification entry must use `- Verify `label`: `command` `argument`...`.',
24
+ line,
25
+ { observed: source },
26
+ );
27
+ }
28
+
29
+ const tokenSource = entry[2].trim();
30
+ const argv = parseBacktickedArguments(tokenSource);
31
+ if (!argv) {
32
+ verificationDiagnostic(
33
+ stackPath,
34
+ 'Every verification command and argument must be one separate non-empty backticked value.',
35
+ line,
36
+ { observed: source },
37
+ );
38
+ }
39
+
40
+ const command = argv[0];
41
+ if (argv.some((value) => value.includes('\0')) || !isSafeProcessExecutable(command)) {
42
+ verificationDiagnostic(
43
+ stackPath,
44
+ 'A verification executable must be a PATH name or project-relative path without traversal.',
45
+ line,
46
+ { command },
47
+ );
48
+ }
49
+
50
+ commands.push({ label: entry[1], argv, line });
51
+ }
52
+
53
+ return commands;
54
+ }
@@ -7,9 +7,7 @@ import {
7
7
  normalizeStackPackageName,
8
8
  readStackCatalog,
9
9
  } from './stack-catalog.js';
10
- import { parseStackCommandLines } from './stack-command.js';
11
10
  import { composeStackCityPresentation } from './stack-city-presentation.js';
12
- import { composeStackDeployment, parseStackDeploymentLines } from './stack-deployment.js';
13
11
  import { resolveStackPieces } from './stack-composition.js';
14
12
  import {
15
13
  composeStackEnvironmentDefaults,
@@ -19,11 +17,12 @@ import {
19
17
  composeStackEnvironmentFiles,
20
18
  parseStackEnvironmentFileLines,
21
19
  } from './stack-environment-files.js';
22
- import { composeStackLaunchTargets, parseStackLaunchLines } from './stack-launch.js';
23
20
  import {
24
- composeStackWorkspaceSetup,
25
- parseStackWorkspaceSetupLines,
26
- } from './stack-workspace-setup.js';
21
+ composeStackSections,
22
+ opaqueStackSections,
23
+ trimStackSectionLines,
24
+ } from './stack-section.js';
25
+ import { parseStackVerificationLines } from './stack-verification.js';
27
26
  import {
28
27
  applyStackPieceCustomization,
29
28
  normalizeStackPieceId,
@@ -42,10 +41,7 @@ const STACK_SECTIONS = new Set([
42
41
  'Resources',
43
42
  'Environment defaults',
44
43
  'Environment files',
45
- 'Workspace setup',
46
- 'Commands',
47
- 'Deployment',
48
- 'Launch',
44
+ 'Verification',
49
45
  ]);
50
46
  const STACK_CUSTOMIZATION_ROOT = 'genesis/stack';
51
47
 
@@ -59,8 +55,8 @@ function parseSections(source) {
59
55
  for (const line of lines) {
60
56
  const heading = line.match(/^##\s+(.+?)\s*$/u);
61
57
  if (heading) {
62
- if (!STACK_SECTIONS.has(heading[1]) || sections.has(heading[1])) {
63
- throw new GenesisError('STACK_INVALID', `Unknown or duplicate Stack section: ${heading[1]}.`);
58
+ if (sections.has(heading[1])) {
59
+ throw new GenesisError('STACK_INVALID', `Duplicate Stack section: ${heading[1]}.`);
64
60
  }
65
61
  current = [];
66
62
  sections.set(heading[1], current);
@@ -69,6 +65,9 @@ function parseSections(source) {
69
65
  throw new GenesisError('STACK_INVALID', `${STACK_PATH} contains content outside a Stack section.`);
70
66
  }
71
67
  }
68
+ if (!sections.has('Components')) {
69
+ throw new GenesisError('STACK_INVALID', `${STACK_PATH} needs one \`## Components\` section.`);
70
+ }
72
71
  return sections;
73
72
  }
74
73
 
@@ -123,23 +122,14 @@ function knownPieces(resolution) {
123
122
  return resolution.pieces;
124
123
  }
125
124
 
126
- function withoutOuterBlankLines(lines = []) {
127
- const result = [...lines];
128
- while (result[0]?.trim() === '') result.shift();
129
- while (result.at(-1)?.trim() === '') result.pop();
130
- return result;
131
- }
132
-
133
125
  function renderStack({
134
- commandLines = [],
135
126
  componentIds: componentIdsValue,
136
- deploymentLines = null,
137
127
  environmentDefaultLines = null,
138
128
  environmentFileLines = null,
139
- launchLines = null,
140
129
  resourceLines = null,
130
+ extensionSections = [],
141
131
  stackPackages = [],
142
- workspaceSetupLines = null,
132
+ verificationLines = [],
143
133
  }) {
144
134
  return [
145
135
  '# Stack',
@@ -151,21 +141,17 @@ function renderStack({
151
141
  ...componentIdsValue.map((id) => `- \`${id}\``),
152
142
  ...(resourceLines === null
153
143
  ? []
154
- : ['', '## Resources', '', ...withoutOuterBlankLines(resourceLines)]),
144
+ : ['', '## Resources', '', ...trimStackSectionLines(resourceLines)]),
155
145
  ...(environmentDefaultLines === null
156
146
  ? []
157
- : ['', '## Environment defaults', '', ...withoutOuterBlankLines(environmentDefaultLines)]),
147
+ : ['', '## Environment defaults', '', ...trimStackSectionLines(environmentDefaultLines)]),
158
148
  ...(environmentFileLines === null
159
149
  ? []
160
- : ['', '## Environment files', '', ...withoutOuterBlankLines(environmentFileLines)]),
161
- ...(workspaceSetupLines === null
162
- ? []
163
- : ['', '## Workspace setup', '', ...withoutOuterBlankLines(workspaceSetupLines)]),
164
- ...(commandLines.length > 0 ? ['', '## Commands', ...commandLines] : []),
165
- ...(launchLines === null ? [] : ['', '## Launch', '', ...withoutOuterBlankLines(launchLines)]),
166
- ...(deploymentLines === null
167
- ? []
168
- : ['', '## Deployment', '', ...withoutOuterBlankLines(deploymentLines)]),
150
+ : ['', '## Environment files', '', ...trimStackSectionLines(environmentFileLines)]),
151
+ ...(verificationLines.length > 0 ? ['', '## Verification', ...verificationLines] : []),
152
+ ...extensionSections.flatMap(({ name, lines }) => (
153
+ ['', `## ${name}`, '', ...trimStackSectionLines(lines)]
154
+ )),
169
155
  '',
170
156
  ].join('\n');
171
157
  }
@@ -203,7 +189,10 @@ export async function addStackPieces({ pieces, projectRoot, stackPackages = [] }
203
189
  ...declaredPackages,
204
190
  ...selectedPieces.map(({ stackPackage }) => stackPackage).filter(Boolean),
205
191
  ])].sort();
206
- const commandLines = parseStackCommandLines(sections.get('Commands') || [], { path: STACK_PATH })
192
+ const verificationLines = parseStackVerificationLines(
193
+ sections.get('Verification') || [],
194
+ { path: STACK_PATH },
195
+ )
207
196
  .map(({ label, argv }) => `- Verify \`${label}\`: ${argv.map((value) => `\`${value}\``).join(' ')}`);
208
197
  const resourceLines = sections.has('Resources') ? sections.get('Resources') : null;
209
198
  parseStackResourceLines(resourceLines === null ? undefined : resourceLines, { path: STACK_PATH });
@@ -221,30 +210,15 @@ export async function addStackPieces({ pieces, projectRoot, stackPackages = [] }
221
210
  environmentFileLines === null ? undefined : environmentFileLines,
222
211
  { path: STACK_PATH },
223
212
  );
224
- const launchLines = sections.has('Launch') ? sections.get('Launch') : null;
225
- parseStackLaunchLines(launchLines === null ? undefined : launchLines, { path: STACK_PATH });
226
- const deploymentLines = sections.has('Deployment') ? sections.get('Deployment') : null;
227
- parseStackDeploymentLines(
228
- deploymentLines === null ? undefined : deploymentLines,
229
- { path: STACK_PATH },
230
- );
231
- const workspaceSetupLines = sections.has('Workspace setup')
232
- ? sections.get('Workspace setup')
233
- : null;
234
- parseStackWorkspaceSetupLines(
235
- workspaceSetupLines === null ? undefined : workspaceSetupLines,
236
- { path: STACK_PATH },
237
- );
213
+ const extensionSections = opaqueStackSections(sections, STACK_SECTIONS);
238
214
  const rendered = renderStack({
239
- commandLines,
240
215
  componentIds: selected,
241
- deploymentLines,
242
216
  environmentDefaultLines,
243
217
  environmentFileLines,
244
- launchLines,
245
218
  resourceLines,
219
+ extensionSections,
246
220
  stackPackages: selectedPackages,
247
- workspaceSetupLines,
221
+ verificationLines,
248
222
  });
249
223
  const stackChanged = rendered !== source;
250
224
  if (stackChanged) await writeFileAtomic(location, rendered);
@@ -267,7 +241,7 @@ export async function addStackPieces({ pieces, projectRoot, stackPackages = [] }
267
241
  };
268
242
  }
269
243
 
270
- function distinctCommands(commands) {
244
+ function distinctVerificationCommands(commands) {
271
245
  const seen = new Set();
272
246
  return commands.filter((command) => {
273
247
  const key = JSON.stringify([command.label, command.argv]);
@@ -331,12 +305,16 @@ export async function readStack(projectRoot, { stackPackages = [] } = {}) {
331
305
  catalog,
332
306
  requested: componentIds(sections),
333
307
  })).map((piece) => customizePiece(projectRoot, piece)));
334
- const projectCommands = parseStackCommandLines(
335
- sections.get('Commands') || [],
308
+ const projectVerificationCommands = parseStackVerificationLines(
309
+ sections.get('Verification') || [],
336
310
  { path: STACK_PATH },
337
311
  ).map(({ line: _line, ...command }) => command);
338
- const componentCommands = components.flatMap((piece) => piece.commands);
339
- const commands = distinctCommands(projectCommands.length > 0 ? projectCommands : componentCommands);
312
+ const componentVerificationCommands = components.flatMap((piece) => piece.verificationCommands);
313
+ const verificationCommands = distinctVerificationCommands(
314
+ projectVerificationCommands.length > 0
315
+ ? projectVerificationCommands
316
+ : componentVerificationCommands,
317
+ );
340
318
  const cityPresentation = composeStackCityPresentation(components);
341
319
  const projectResources = sections.has('Resources')
342
320
  ? parseStackResourceLines(sections.get('Resources'), { path: STACK_PATH })
@@ -356,21 +334,10 @@ export async function readStack(projectRoot, { stackPackages = [] } = {}) {
356
334
  { path: STACK_PATH },
357
335
  );
358
336
  const environmentFiles = composeStackEnvironmentFiles(components, projectEnvironmentFiles);
359
- const projectLaunch = parseStackLaunchLines(
360
- sections.has('Launch') ? sections.get('Launch') : undefined,
361
- { path: STACK_PATH },
362
- );
363
- const launchTargets = composeStackLaunchTargets(components, projectLaunch);
364
- const projectDeployment = parseStackDeploymentLines(
365
- sections.has('Deployment') ? sections.get('Deployment') : undefined,
366
- { path: STACK_PATH },
367
- );
368
- const deployment = composeStackDeployment(components, projectDeployment);
369
- const projectWorkspaceSetup = parseStackWorkspaceSetupLines(
370
- sections.has('Workspace setup') ? sections.get('Workspace setup') : undefined,
371
- { path: STACK_PATH },
337
+ const extensions = composeStackSections(
338
+ components,
339
+ opaqueStackSections(sections, STACK_SECTIONS),
372
340
  );
373
- const workspaceSetup = composeStackWorkspaceSetup(components, projectWorkspaceSetup);
374
341
  return {
375
342
  path: STACK_PATH,
376
343
  identityHash: sha256(stableJson({
@@ -378,24 +345,20 @@ export async function readStack(projectRoot, { stackPackages = [] } = {}) {
378
345
  components: components.map(({ id }) => id),
379
346
  cityExclusions: cityPresentation.exclusions,
380
347
  cityRegions: cityPresentation.regions,
381
- commands: commands.map(({ label, argv }) => ({ label, argv })),
348
+ verificationCommands: verificationCommands.map(({ label, argv }) => ({ label, argv })),
382
349
  environmentDefaults,
383
350
  environmentFiles,
384
- launchTargets,
385
- deployment,
351
+ extensions,
386
352
  resources,
387
- workspaceSetup,
388
353
  })),
389
354
  components,
390
355
  stackPackages: declaredPackages,
391
356
  cityExclusions: cityPresentation.exclusions,
392
357
  cityRegions: cityPresentation.regions,
393
- commands,
358
+ verificationCommands,
394
359
  environmentDefaults,
395
360
  environmentFiles,
396
- launchTargets,
397
- deployment,
398
- workspaceSetup,
361
+ extensions,
399
362
  resources,
400
363
  guidance: composedProse(components, 'guidance'),
401
364
  adoption: composedProse(components, 'adoption'),
@@ -415,12 +378,15 @@ export function stackPromptContext(stack) {
415
378
  })),
416
379
  cityExclusions: stack.cityExclusions,
417
380
  cityRegions: stack.cityRegions,
418
- verifyCommands: stack.commands.map(({ label, argv }) => ({ label, argv })),
381
+ verificationCommands: stack.verificationCommands.map(({ label, argv }) => ({ label, argv })),
419
382
  environmentDefaults: stack.environmentDefaults,
420
383
  resources: stack.resources,
421
384
  environmentFiles: stack.environmentFiles,
422
- launchTargets: stack.launchTargets,
423
- deployment: stack.deployment,
424
- workspaceSetup: stack.workspaceSetup,
385
+ extensions: stack.extensions.map(({ name, source, lines, diagnostics }) => ({
386
+ name,
387
+ source,
388
+ content: lines.join('\n'),
389
+ diagnostics,
390
+ })),
425
391
  };
426
392
  }
@@ -5,7 +5,6 @@ import { clearVerification, writeVerification } from './project-state.js';
5
5
  import { withStackEnvironmentDefaults } from './stack-environment-defaults.js';
6
6
  import { missingStackResources } from './stack-preflight.js';
7
7
  import { readStack } from './stack.js';
8
- import { inspectWorkspaceSetupForStack } from './workspace-setup.js';
9
8
  import { GENESIS_CONTRACTS } from './contracts.js';
10
9
 
11
10
  const DEFAULT_FINITE_COMMAND_TIMEOUT_MS = 30 * 60 * 1_000;
@@ -25,28 +24,6 @@ export async function verifyProject({
25
24
  } = {}) {
26
25
  const root = (await gitContext(projectRoot)).repositoryRoot;
27
26
  const stack = await readStack(root, { stackPackages });
28
- const workspaceSetup = await inspectWorkspaceSetupForStack({ projectRoot: root, stack });
29
- if (workspaceSetup.status === 'blocked') {
30
- return {
31
- contract: GENESIS_CONTRACTS.verification,
32
- status: 'blocked',
33
- summary: workspaceSetup.diagnostics.map(({ message }) => message).join(' '),
34
- commands: [],
35
- diagnostics: workspaceSetup.diagnostics,
36
- };
37
- }
38
- const waiting = workspaceSetup.diagnostics.filter(
39
- ({ code }) => code === 'STACK_WORKSPACE_SETUP_WAITING',
40
- );
41
- if (waiting.length > 0) {
42
- return {
43
- contract: GENESIS_CONTRACTS.verification,
44
- status: 'unconfigured',
45
- summary: waiting.map(({ message }) => message).join(' '),
46
- commands: [],
47
- diagnostics: waiting,
48
- };
49
- }
50
27
  const resolvedEnvironment = withStackEnvironmentDefaults(
51
28
  environment,
52
29
  stack.environmentDefaults,
@@ -64,7 +41,7 @@ export async function verifyProject({
64
41
  diagnostics: missing,
65
42
  };
66
43
  }
67
- if (stack.commands.length === 0) {
44
+ if (stack.verificationCommands.length === 0) {
68
45
  return {
69
46
  contract: GENESIS_CONTRACTS.verification,
70
47
  status: 'unconfigured',
@@ -77,7 +54,7 @@ export async function verifyProject({
77
54
  await clearVerification(root);
78
55
  try {
79
56
  const commands = [];
80
- for (const command of stack.commands) {
57
+ for (const command of stack.verificationCommands) {
81
58
  await emit(onEvent, {
82
59
  type: 'genesis.verification',
83
60
  code: 'VERIFICATION_STARTED',
package/src/index.js CHANGED
@@ -8,15 +8,10 @@ import { generateProjectPrompt } from './index/prompt.js';
8
8
  import { initializeProject } from './index/init.js';
9
9
  import { installCodexPlugin } from './index/codex-plugin.js';
10
10
  import { inspectProjectEnvironment } from './index/environment-files.js';
11
- import { inspectProjectDeployment } from './index/deployment.js';
12
- import { inspectProjectLaunch } from './index/launch.js';
11
+ import { inspectProjectStackSection } from './index/stack-section-inspection.js';
13
12
  import { listStackCatalogPieces } from './index/stack-catalog.js';
14
13
  import { addStackPieces, readStack } from './index/stack.js';
15
14
  import { verifyProject } from './index/verification.js';
16
- import {
17
- inspectProjectWorkspaceSetup,
18
- prepareProjectWorkspace,
19
- } from './index/workspace-setup.js';
20
15
 
21
16
  export { GENESIS_CONTRACTS };
22
17
 
@@ -95,24 +90,12 @@ export async function listStackPieces({
95
90
  });
96
91
  }
97
92
 
98
- export function inspectLaunch(options) {
99
- return inspectProjectLaunch(options);
100
- }
101
-
102
93
  export function inspectEnvironment(options) {
103
94
  return inspectProjectEnvironment(options);
104
95
  }
105
96
 
106
- export function inspectDeployment(options) {
107
- return inspectProjectDeployment(options);
108
- }
109
-
110
- export function inspectWorkspaceSetup(options) {
111
- return inspectProjectWorkspaceSetup(options);
112
- }
113
-
114
- export function prepareWorkspace(options) {
115
- return prepareProjectWorkspace(options);
97
+ export function inspectStackSection(options) {
98
+ return inspectProjectStackSection(options);
116
99
  }
117
100
 
118
101
  export function generatePrompt(options) {
@@ -1,113 +0,0 @@
1
- # Preview identity command protocol
2
-
3
- `genesis.preview-identity.command.v1` lets an application expose its ordinary
4
- development sign-in and sign-out behavior to any preview host. It is language-
5
- and framework-neutral: `tools/preview-identity` may be a native C/C++ binary, a
6
- script, or another committed executable supported by the target's declared
7
- runtimes.
8
-
9
- Genesis only validates and reports the declaration. The preview host resolves
10
- the command beneath the project root, rejects traversal and symlinks, supplies
11
- the declared runtime and environment policy, enforces the declared timeout,
12
- and controls the browser. The application owns user lookup and its normal
13
- session cookies.
14
-
15
- ## Invocation
16
-
17
- The host runs the exact declared argv with the project root as the working
18
- directory. Standard input contains exactly one UTF-8 JSON object followed by a
19
- newline and EOF. Standard output contains exactly one JSON object; diagnostic
20
- logs belong on standard error. There is no streaming or multi-message framing.
21
-
22
- The host supplies a fresh opaque `requestId`. A sign-in request is:
23
-
24
- ```json
25
- {
26
- "protocol": "genesis.preview-identity.command.v1",
27
- "requestId": "opaque-request-id",
28
- "operation": "login-as",
29
- "subject": {
30
- "kind": "selector",
31
- "selector": {
32
- "type": "email",
33
- "value": "person@example.com"
34
- }
35
- },
36
- "target": {
37
- "href": "http://127.0.0.1:4100/home",
38
- "origin": "http://127.0.0.1:4100"
39
- }
40
- }
41
- ```
42
-
43
- `subject.selector.type` must be one of the identity types advertised by the
44
- Launch declaration: `email`, `login`, or `user-id`. The application treats the
45
- selector as an existing application identity; a preview host does not create
46
- users, memberships, roles, or seed data.
47
-
48
- Sign-out uses the same envelope with `operation` set to `logout` and omits
49
- `subject`.
50
-
51
- ## Responses
52
-
53
- Every protocol response repeats the exact `protocol` and `requestId` and exits
54
- with status zero. A successful sign-in response is:
55
-
56
- ```json
57
- {
58
- "protocol": "genesis.preview-identity.command.v1",
59
- "requestId": "opaque-request-id",
60
- "ok": true,
61
- "signedOut": false,
62
- "identity": {
63
- "displayName": "Person",
64
- "email": "person@example.com",
65
- "userId": "42"
66
- },
67
- "setCookie": [
68
- "app_session=opaque; Path=/; HttpOnly; SameSite=Lax"
69
- ]
70
- }
71
- ```
72
-
73
- `identity` contains at least one non-empty application identifier. Supported
74
- descriptive fields are `displayName`, `email`, `login`, `userId`, and
75
- `username`. `setCookie` contains the application's ordinary Set-Cookie header
76
- values. Cookies must contain no CR/LF or `Domain` attribute; the host may reject
77
- additional cookie names reserved by its own preview transport.
78
-
79
- A successful logout sets `signedOut` to true, omits `identity`, and returns the
80
- cookie expirations needed to clear the application's session. A structured
81
- application rejection still exits zero and uses:
82
-
83
- ```json
84
- {
85
- "protocol": "genesis.preview-identity.command.v1",
86
- "requestId": "opaque-request-id",
87
- "ok": false,
88
- "code": "user_not_found",
89
- "error": "User not found.",
90
- "statusCode": 404,
91
- "signedOut": true,
92
- "setCookie": []
93
- }
94
- ```
95
-
96
- `statusCode` is an integer from 400 through 599. A malformed request, command
97
- crash, timeout, nonzero exit, invalid JSON, mismatched request ID, oversized
98
- output, or invalid cookie is a command/transport failure rather than an
99
- application rejection. Hosts should bound output to 512 KiB, at most 64
100
- cookies, and at most 16 KiB per cookie.
101
-
102
- ## Environment and safety
103
-
104
- The optional Enabled and Secret environment entries in the Launch declaration
105
- are application-owned variable names, never values. A host that offers identity
106
- switching supplies `true` through the enabled name and a fresh launch-scoped
107
- secret through the secret name. The helper and any private application endpoint
108
- it calls must remain disabled unless those values are present and valid.
109
-
110
- The command is a development-preview control, not a production sign-in API.
111
- Genesis does not store selectors, generate credentials, materialize environment
112
- files, execute the helper, or decide which identities a host is allowed to
113
- offer.
@@ -1,34 +0,0 @@
1
- import { gitContext } from './git.js';
2
- import { readStack } from './stack.js';
3
- import { sha256, stableJson, uniqueSorted } from './utils.js';
4
- import { GENESIS_CONTRACTS } from './contracts.js';
5
-
6
- /** Read the Stack's production recipe without provisioning or publishing anything. */
7
- export async function inspectProjectDeployment({ projectRoot, stackPackages = [] } = {}) {
8
- const root = (await gitContext(projectRoot)).repositoryRoot;
9
- const stack = await readStack(root, { stackPackages });
10
- const diagnostics = [...stack.deployment.diagnostics];
11
- let status = 'unconfigured';
12
- if (diagnostics.length > 0) status = 'blocked';
13
- else if (stack.deployment.steps.length > 0) status = 'ready';
14
- const recipe = {
15
- version: stack.deployment.version,
16
- artifact: stack.deployment.artifact,
17
- workdir: stack.deployment.workdir,
18
- runtimeRequirements: stack.deployment.runtimeRequirements,
19
- readiness: stack.deployment.readiness,
20
- steps: stack.deployment.steps,
21
- };
22
- return {
23
- contract: GENESIS_CONTRACTS.deployment,
24
- status,
25
- stackHash: stack.identityHash,
26
- recipeHash: status === 'ready' ? sha256(stableJson(recipe)) : '',
27
- components: stack.components.map(({ id }) => id),
28
- source: stack.deployment.source,
29
- runtimeRequirements: uniqueSorted(stack.deployment.runtimeRequirements),
30
- resources: stack.resources,
31
- ...recipe,
32
- diagnostics,
33
- };
34
- }
@@ -1,42 +0,0 @@
1
- import { gitContext } from './git.js';
2
- import { missingStackResources } from './stack-preflight.js';
3
- import { withStackEnvironmentDefaults } from './stack-environment-defaults.js';
4
- import { readStack } from './stack.js';
5
- import { uniqueSorted } from './utils.js';
6
- import { GENESIS_CONTRACTS } from './contracts.js';
7
-
8
- /** Read the Stack's launch declaration without choosing or starting a runtime. */
9
- export async function inspectProjectLaunch({
10
- environment = process.env,
11
- projectRoot,
12
- stackPackages = [],
13
- } = {}) {
14
- const root = (await gitContext(projectRoot)).repositoryRoot;
15
- const stack = await readStack(root, { stackPackages });
16
- const diagnostics = missingStackResources({
17
- environment: withStackEnvironmentDefaults(environment, stack.environmentDefaults),
18
- resources: stack.resources,
19
- });
20
- const disabledReason = diagnostics.length === 0
21
- ? null
22
- : diagnostics.map(({ message }) => message).join(' ');
23
- const targets = stack.launchTargets.map((target) => ({
24
- ...target,
25
- available: diagnostics.length === 0,
26
- disabledReason,
27
- }));
28
- let status = 'ready';
29
- if (targets.length === 0) status = 'unconfigured';
30
- else if (diagnostics.length > 0) status = 'blocked';
31
- return {
32
- contract: GENESIS_CONTRACTS.launch,
33
- status,
34
- stackHash: stack.identityHash,
35
- components: stack.components.map(({ id }) => id),
36
- environmentDefaults: stack.environmentDefaults,
37
- runtimeRequirements: uniqueSorted(targets.flatMap((target) => target.runtimeRequirements)),
38
- resources: stack.resources,
39
- targets,
40
- diagnostics,
41
- };
42
- }
@@ -1,65 +0,0 @@
1
- import { GenesisError } from './errors.js';
2
- import { isSafeProcessExecutable, parseBacktickedArguments } from './stack-process.js';
3
-
4
- function commandDiagnostic(stackPath, message, line, details = {}) {
5
- throw new GenesisError(
6
- 'STACK_COMMANDS_INVALID',
7
- message,
8
- {
9
- path: stackPath,
10
- ...(line === undefined ? {} : { line }),
11
- ...details,
12
- },
13
- );
14
- }
15
-
16
- export function parseStackCommandLines(lines, { path: stackPath } = {}) {
17
- const commands = [];
18
-
19
- for (let index = 0; index < lines.length; index += 1) {
20
- const source = lines[index];
21
- const line = index + 1;
22
- if (!source.trim()) continue;
23
- const entry = source.match(/^- Verify `([^`\r\n]+)`:[ \t]+(.+)$/u);
24
- if (!entry) {
25
- commandDiagnostic(
26
- stackPath,
27
- 'Every Commands entry must use `- Verify `label`: `command` `argument`...`.',
28
- line,
29
- { observed: source },
30
- );
31
- }
32
-
33
- const tokenSource = entry[2].trim();
34
- const argv = parseBacktickedArguments(tokenSource);
35
- if (!argv) {
36
- commandDiagnostic(
37
- stackPath,
38
- 'Every command and argument must be one separate non-empty backticked value.',
39
- line,
40
- { observed: source },
41
- );
42
- }
43
-
44
- const command = argv[0];
45
- if (
46
- argv.some((value) => value.includes('\0'))
47
- || !isSafeProcessExecutable(command)
48
- ) {
49
- commandDiagnostic(
50
- stackPath,
51
- 'A stack command executable must be a PATH name or project-relative path without traversal.',
52
- line,
53
- { command },
54
- );
55
- }
56
-
57
- commands.push({
58
- label: entry[1],
59
- argv,
60
- line,
61
- });
62
- }
63
-
64
- return commands;
65
- }