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
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
import { GenesisError } from './errors.js';
|
|
2
|
-
import {
|
|
3
|
-
isCanonicalProjectWorkdir,
|
|
4
|
-
isSafeProcessExecutable,
|
|
5
|
-
parseBacktickedArguments,
|
|
6
|
-
} from './stack-process.js';
|
|
7
|
-
|
|
8
|
-
const RUNTIME_ID = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
|
|
9
|
-
const STEP_LINE = /^- (Prepare|Build|Migrate|Serve) `([^`\r\n]+)`:[ \t]+(.+)$/u;
|
|
10
|
-
const RESTORE_PATHS_PREFIX = '- Recreate on restore: ';
|
|
11
|
-
const STEP_ROLE_ORDER = Object.freeze({
|
|
12
|
-
prepare: 0,
|
|
13
|
-
build: 1,
|
|
14
|
-
migrate: 2,
|
|
15
|
-
serve: 3,
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
function invalid(deploymentPath, message, line, details = {}) {
|
|
19
|
-
throw new GenesisError('STACK_DEPLOYMENT_INVALID', message, {
|
|
20
|
-
path: deploymentPath,
|
|
21
|
-
...(line === undefined ? {} : { line }),
|
|
22
|
-
...details,
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function oneToken(source, prefix, deploymentPath, label, line) {
|
|
27
|
-
const values = parseBacktickedArguments(source.slice(prefix.length));
|
|
28
|
-
if (!values || values.length !== 1) {
|
|
29
|
-
invalid(deploymentPath, `${label} accepts exactly one backticked value.`, line);
|
|
30
|
-
}
|
|
31
|
-
return values[0];
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function parseRuntimes(source, deploymentPath, line) {
|
|
35
|
-
const values = parseBacktickedArguments(source.slice('- Runtimes: '.length));
|
|
36
|
-
if (!values || values.some((value) => !RUNTIME_ID.test(value))) {
|
|
37
|
-
invalid(
|
|
38
|
-
deploymentPath,
|
|
39
|
-
'Deployment runtimes must be separate backticked technology ids.',
|
|
40
|
-
line,
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
if (new Set(values).size !== values.length) {
|
|
44
|
-
invalid(deploymentPath, 'Deployment contains a duplicate runtime.', line);
|
|
45
|
-
}
|
|
46
|
-
return values;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function parseDisposablePaths(source, deploymentPath, line) {
|
|
50
|
-
const values = parseBacktickedArguments(source.slice(RESTORE_PATHS_PREFIX.length));
|
|
51
|
-
if (!values || values.length === 0) {
|
|
52
|
-
invalid(
|
|
53
|
-
deploymentPath,
|
|
54
|
-
'Deployment Recreate on restore paths must be separate non-empty backticked values.',
|
|
55
|
-
line,
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
const normalized = values.map((value) => String(value || '').trim());
|
|
59
|
-
if (normalized.some((value) => (
|
|
60
|
-
!value
|
|
61
|
-
|| value.startsWith('/')
|
|
62
|
-
|| value.includes('\\')
|
|
63
|
-
|| value.includes('***')
|
|
64
|
-
|| /[?\[\]{}!]/u.test(value)
|
|
65
|
-
|| value.split('/').some((segment) => !segment || ['.', '..'].includes(segment))
|
|
66
|
-
))) {
|
|
67
|
-
invalid(
|
|
68
|
-
deploymentPath,
|
|
69
|
-
'Deployment Recreate on restore paths must be canonical project-relative globs using only * and ** wildcards.',
|
|
70
|
-
line,
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
if (new Set(normalized).size !== normalized.length) {
|
|
74
|
-
invalid(deploymentPath, 'Deployment contains a duplicate Recreate on restore path.', line);
|
|
75
|
-
}
|
|
76
|
-
return normalized;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function parseUrlPath(value, deploymentPath, line) {
|
|
80
|
-
const normalized = String(value || '').trim();
|
|
81
|
-
if (!normalized.startsWith('/') || normalized.startsWith('//') || /[\\?#]/u.test(normalized)) {
|
|
82
|
-
invalid(
|
|
83
|
-
deploymentPath,
|
|
84
|
-
'Deployment readiness path must begin with one slash and contain no query or fragment.',
|
|
85
|
-
line,
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
return normalized;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function parseReadiness(source, deploymentPath, line) {
|
|
92
|
-
const match = source.match(/^- Ready when: `GET` `([^`\r\n]+)` returns `([0-9]{3})`$/u);
|
|
93
|
-
if (!match) {
|
|
94
|
-
invalid(
|
|
95
|
-
deploymentPath,
|
|
96
|
-
'Deployment readiness must use `- Ready when: `GET` `/path` returns `200``.',
|
|
97
|
-
line,
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
const status = Number(match[2]);
|
|
101
|
-
if (!Number.isInteger(status) || status < 200 || status > 399) {
|
|
102
|
-
invalid(deploymentPath, 'Deployment readiness status must be from 200 through 399.', line);
|
|
103
|
-
}
|
|
104
|
-
return {
|
|
105
|
-
kind: 'http',
|
|
106
|
-
method: 'GET',
|
|
107
|
-
path: parseUrlPath(match[1], deploymentPath, line),
|
|
108
|
-
status,
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function parseStep(match, deploymentPath, line) {
|
|
113
|
-
const label = match[2].trim();
|
|
114
|
-
const argv = parseBacktickedArguments(match[3]);
|
|
115
|
-
if (!label || /[\0\r\n]/u.test(label)) {
|
|
116
|
-
invalid(deploymentPath, 'Deployment step labels must be non-empty single lines.', line);
|
|
117
|
-
}
|
|
118
|
-
if (!argv || argv.some((value) => value.includes('\0')) || !isSafeProcessExecutable(argv[0])) {
|
|
119
|
-
invalid(
|
|
120
|
-
deploymentPath,
|
|
121
|
-
'Deployment command and arguments must be separate non-empty backticked values with a safe executable.',
|
|
122
|
-
line,
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
return {
|
|
126
|
-
label,
|
|
127
|
-
argv,
|
|
128
|
-
role: match[1].toLowerCase(),
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export function parseStackDeploymentLines(lines, {
|
|
133
|
-
path: deploymentPath = 'stack deployment',
|
|
134
|
-
} = {}) {
|
|
135
|
-
if (lines === undefined) return null;
|
|
136
|
-
const entries = lines.map((line) => line.trim()).filter(Boolean);
|
|
137
|
-
if (entries.length === 1 && entries[0] === '- Nothing.') {
|
|
138
|
-
return { version: 2, artifact: { disposablePaths: [] }, steps: [] };
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const result = {
|
|
142
|
-
version: 2,
|
|
143
|
-
artifact: {
|
|
144
|
-
disposablePaths: [],
|
|
145
|
-
},
|
|
146
|
-
workdir: '.',
|
|
147
|
-
runtimeRequirements: [],
|
|
148
|
-
readiness: null,
|
|
149
|
-
steps: [],
|
|
150
|
-
};
|
|
151
|
-
const seen = new Set();
|
|
152
|
-
for (let index = 0; index < lines.length; index += 1) {
|
|
153
|
-
const source = lines[index].trim();
|
|
154
|
-
if (!source) continue;
|
|
155
|
-
const line = index + 1;
|
|
156
|
-
if (source.startsWith('- Workdir: ')) {
|
|
157
|
-
if (seen.has('workdir')) invalid(deploymentPath, 'Duplicate Deployment Workdir.', line);
|
|
158
|
-
seen.add('workdir');
|
|
159
|
-
const workdir = oneToken(source, '- Workdir: ', deploymentPath, 'Deployment Workdir', line);
|
|
160
|
-
if (!isCanonicalProjectWorkdir(workdir)) {
|
|
161
|
-
invalid(deploymentPath, 'Deployment workdir must be a canonical project-relative directory.', line);
|
|
162
|
-
}
|
|
163
|
-
result.workdir = workdir;
|
|
164
|
-
continue;
|
|
165
|
-
}
|
|
166
|
-
if (source.startsWith('- Runtimes: ')) {
|
|
167
|
-
if (seen.has('runtimes')) invalid(deploymentPath, 'Duplicate Deployment Runtimes.', line);
|
|
168
|
-
seen.add('runtimes');
|
|
169
|
-
result.runtimeRequirements = parseRuntimes(source, deploymentPath, line);
|
|
170
|
-
continue;
|
|
171
|
-
}
|
|
172
|
-
if (source.startsWith(RESTORE_PATHS_PREFIX)) {
|
|
173
|
-
if (seen.has('restore-paths')) {
|
|
174
|
-
invalid(deploymentPath, 'Duplicate Deployment Recreate on restore entry.', line);
|
|
175
|
-
}
|
|
176
|
-
seen.add('restore-paths');
|
|
177
|
-
result.artifact.disposablePaths = parseDisposablePaths(source, deploymentPath, line);
|
|
178
|
-
continue;
|
|
179
|
-
}
|
|
180
|
-
if (source.startsWith('- Ready when: ')) {
|
|
181
|
-
if (seen.has('readiness')) invalid(deploymentPath, 'Duplicate Deployment Ready when.', line);
|
|
182
|
-
seen.add('readiness');
|
|
183
|
-
result.readiness = parseReadiness(source, deploymentPath, line);
|
|
184
|
-
continue;
|
|
185
|
-
}
|
|
186
|
-
const step = source.match(STEP_LINE);
|
|
187
|
-
if (step) {
|
|
188
|
-
result.steps.push(parseStep(step, deploymentPath, line));
|
|
189
|
-
continue;
|
|
190
|
-
}
|
|
191
|
-
invalid(deploymentPath, `Unknown Deployment entry: ${source}.`, line);
|
|
192
|
-
}
|
|
193
|
-
if (result.steps.length === 0) {
|
|
194
|
-
invalid(deploymentPath, '## Deployment needs at least one command or exactly `- Nothing.`.');
|
|
195
|
-
}
|
|
196
|
-
const serveSteps = result.steps.filter(({ role }) => role === 'serve');
|
|
197
|
-
if (serveSteps.length !== 1 || result.steps.at(-1).role !== 'serve') {
|
|
198
|
-
invalid(deploymentPath, 'Deployment needs exactly one final Serve step.');
|
|
199
|
-
}
|
|
200
|
-
for (let index = 1; index < result.steps.length; index += 1) {
|
|
201
|
-
if (STEP_ROLE_ORDER[result.steps[index].role] < STEP_ROLE_ORDER[result.steps[index - 1].role]) {
|
|
202
|
-
invalid(
|
|
203
|
-
deploymentPath,
|
|
204
|
-
'Deployment steps must be ordered Prepare, Build, Migrate, then Serve.',
|
|
205
|
-
);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
if (!result.readiness) {
|
|
209
|
-
invalid(deploymentPath, 'Deployment needs one Ready when entry.');
|
|
210
|
-
}
|
|
211
|
-
if (
|
|
212
|
-
result.artifact.disposablePaths.length > 0
|
|
213
|
-
&& !result.steps.some(({ role }) => role === 'prepare')
|
|
214
|
-
) {
|
|
215
|
-
invalid(
|
|
216
|
-
deploymentPath,
|
|
217
|
-
'Deployment may recreate paths on restore only when it declares a Prepare step.',
|
|
218
|
-
);
|
|
219
|
-
}
|
|
220
|
-
return result;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
export function composeStackDeployment(components, projectDeployment) {
|
|
224
|
-
if (projectDeployment) return { ...projectDeployment, source: 'project', diagnostics: [] };
|
|
225
|
-
const declarations = components
|
|
226
|
-
.filter((component) => component.deployment?.steps?.length > 0)
|
|
227
|
-
.map((component) => ({
|
|
228
|
-
...component.deployment,
|
|
229
|
-
source: `component:${component.id}`,
|
|
230
|
-
}));
|
|
231
|
-
if (declarations.length === 0) {
|
|
232
|
-
return {
|
|
233
|
-
version: 2,
|
|
234
|
-
artifact: { disposablePaths: [] },
|
|
235
|
-
workdir: '.',
|
|
236
|
-
runtimeRequirements: [],
|
|
237
|
-
readiness: null,
|
|
238
|
-
steps: [],
|
|
239
|
-
source: null,
|
|
240
|
-
diagnostics: [],
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
if (declarations.length === 1) return { ...declarations[0], diagnostics: [] };
|
|
244
|
-
const sources = declarations.map(({ source }) => source).sort();
|
|
245
|
-
return {
|
|
246
|
-
version: 2,
|
|
247
|
-
artifact: { disposablePaths: [] },
|
|
248
|
-
workdir: '.',
|
|
249
|
-
runtimeRequirements: [],
|
|
250
|
-
readiness: null,
|
|
251
|
-
steps: [],
|
|
252
|
-
source: null,
|
|
253
|
-
diagnostics: [{
|
|
254
|
-
code: 'STACK_DEPLOYMENT_AMBIGUOUS',
|
|
255
|
-
message: `Selected Stack components provide competing Deployment recipes: ${sources.join(', ')}. Add one project ## Deployment section to choose the exact recipe.`,
|
|
256
|
-
details: { sources },
|
|
257
|
-
}],
|
|
258
|
-
};
|
|
259
|
-
}
|