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.
- package/README.md +34 -90
- package/docs/prompt-integration.md +5 -5
- package/docs/stack-components.md +119 -393
- package/package.json +1 -2
- package/plugins/genesis/.codex-plugin/plugin.json +1 -1
- package/prompts/adopt.txt +14 -23
- package/prompts/start.txt +10 -12
- package/prompts/work.txt +8 -9
- package/src/cli.js +27 -55
- package/src/index/check.js +13 -21
- package/src/index/context.js +3 -3
- package/src/index/contracts.js +1 -3
- package/src/index/project-state.js +2 -2
- package/src/index/prompt.js +1 -1
- package/src/index/stack-catalog.js +1 -2
- package/src/index/stack-piece.js +25 -21
- package/src/index/stack-section-inspection.js +33 -0
- package/src/index/stack-section.js +82 -0
- package/src/index/stack-verification.js +54 -0
- package/src/index/stack.js +50 -84
- package/src/index/verification.js +2 -25
- package/src/index.js +3 -20
- package/docs/preview-identity-command.md +0 -113
- package/src/index/deployment.js +0 -34
- package/src/index/launch.js +0 -42
- package/src/index/stack-command.js +0 -65
- package/src/index/stack-deployment.js +0 -259
- package/src/index/stack-launch.js +0 -464
- package/src/index/stack-workspace-setup.js +0 -133
- package/src/index/workspace-setup.js +0 -156
|
@@ -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
|
+
}
|
package/src/index/stack.js
CHANGED
|
@@ -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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
'
|
|
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 (
|
|
63
|
-
throw new GenesisError('STACK_INVALID', `
|
|
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
|
-
|
|
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', '', ...
|
|
144
|
+
: ['', '## Resources', '', ...trimStackSectionLines(resourceLines)]),
|
|
155
145
|
...(environmentDefaultLines === null
|
|
156
146
|
? []
|
|
157
|
-
: ['', '## Environment defaults', '', ...
|
|
147
|
+
: ['', '## Environment defaults', '', ...trimStackSectionLines(environmentDefaultLines)]),
|
|
158
148
|
...(environmentFileLines === null
|
|
159
149
|
? []
|
|
160
|
-
: ['', '## Environment files', '', ...
|
|
161
|
-
...(
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
335
|
-
sections.get('
|
|
308
|
+
const projectVerificationCommands = parseStackVerificationLines(
|
|
309
|
+
sections.get('Verification') || [],
|
|
336
310
|
{ path: STACK_PATH },
|
|
337
311
|
).map(({ line: _line, ...command }) => command);
|
|
338
|
-
const
|
|
339
|
-
const
|
|
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
|
|
360
|
-
|
|
361
|
-
|
|
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
|
-
|
|
348
|
+
verificationCommands: verificationCommands.map(({ label, argv }) => ({ label, argv })),
|
|
382
349
|
environmentDefaults,
|
|
383
350
|
environmentFiles,
|
|
384
|
-
|
|
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
|
-
|
|
358
|
+
verificationCommands,
|
|
394
359
|
environmentDefaults,
|
|
395
360
|
environmentFiles,
|
|
396
|
-
|
|
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
|
-
|
|
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
|
-
|
|
423
|
-
|
|
424
|
-
|
|
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.
|
|
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.
|
|
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 {
|
|
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
|
|
107
|
-
return
|
|
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.
|
package/src/index/deployment.js
DELETED
|
@@ -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
|
-
}
|
package/src/index/launch.js
DELETED
|
@@ -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
|
-
}
|