@ykdz/template 0.0.30 → 0.0.32
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/node_modules/@ykdz/template-builtin-presets/dist/src/foundation.d.ts +8 -1
- package/node_modules/@ykdz/template-builtin-presets/dist/src/foundation.js +283 -149
- package/node_modules/@ykdz/template-builtin-presets/dist/src/rust-bin/definition.js +66 -9
- package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/development-container.d.ts +2 -0
- package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/development-container.js +35 -0
- package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/typescript.d.ts +13 -0
- package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/typescript.js +70 -0
- package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/vue.d.ts +2 -1
- package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/vue.js +27 -8
- package/node_modules/@ykdz/template-builtin-presets/dist/src/ts-cli/definition.js +4 -3
- package/node_modules/@ykdz/template-builtin-presets/dist/src/ts-lib/definition.js +4 -3
- package/node_modules/@ykdz/template-builtin-presets/dist/src/vike-app/definition.js +81 -11
- package/node_modules/@ykdz/template-builtin-presets/dist/src/vue-app/definition.js +3 -2
- package/node_modules/@ykdz/template-builtin-presets/dist/src/vue-hono-app/definition.js +21 -10
- package/node_modules/@ykdz/template-builtin-presets/package.json +2 -1
- package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check.dynamic.template +2 -2
- package/node_modules/@ykdz/template-builtin-presets/templates/foundation/devcontainer.json +3 -3
- package/node_modules/@ykdz/template-builtin-presets/templates/foundation/gitignore +1 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/foundation/turbo.json +1 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/foundation/typescript-config/base.json +15 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/foundation/typescript-config/turbo.json +4 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/rust-bin/devcontainer/rust.Dockerfile +3 -3
- package/node_modules/@ykdz/template-builtin-presets/templates/shared/devcontainer/browser-test.Dockerfile +1 -1
- package/node_modules/@ykdz/template-builtin-presets/templates/shared/oxc/tsconfig.config.json +2 -12
- package/node_modules/@ykdz/template-builtin-presets/templates/shared/vue/tsconfig.app.json +6 -13
- package/node_modules/@ykdz/template-builtin-presets/templates/shared/vue/tsconfig.node.json +2 -12
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/tsconfig.json +2 -6
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-lib/tsconfig.json +3 -3
- package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/db/tsconfig.json +2 -11
- package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/db-migrations/tsconfig.json +2 -11
- package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/devcontainer/docker-client.Dockerfile +3 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/web/scripts/check-standalone-deployment.ts +67 -27
- package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/web/tsconfig.app.json +6 -12
- package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/web/tsconfig.node.json +2 -11
- package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/web/tsconfig.test.json +0 -1
- package/node_modules/@ykdz/template-builtin-presets/templates/vue-hono-app/api/tsconfig.json +2 -12
- package/node_modules/@ykdz/template-core/dist/development-container-tool-layer.d.ts +56 -0
- package/node_modules/@ykdz/template-core/dist/development-container-tool-layer.js +361 -0
- package/node_modules/@ykdz/template-core/dist/package-contribution.d.ts +12 -1
- package/node_modules/@ykdz/template-core/dist/pnpm-workspace.yaml +7 -5
- package/node_modules/@ykdz/template-core/dist/project-github.d.ts +1 -9
- package/node_modules/@ykdz/template-core/dist/project-github.js +0 -49
- package/node_modules/@ykdz/template-core/dist/project-projection.d.ts +9 -0
- package/node_modules/@ykdz/template-core/dist/project-projection.js +84 -5
- package/node_modules/@ykdz/template-core/dist/renderer.d.ts +1 -0
- package/node_modules/@ykdz/template-core/dist/renderer.js +47 -7
- package/node_modules/@ykdz/template-core/dist/template-boundary-check.js +21 -0
- package/package.json +4 -4
- package/node_modules/@ykdz/template-builtin-presets/templates/foundation/rust/.vscode/extensions.json +0 -3
- package/node_modules/@ykdz/template-builtin-presets/templates/foundation/rust/.vscode/settings.json +0 -5
- package/node_modules/@ykdz/template-builtin-presets/templates/foundation/rust/devcontainer/devcontainer.json +0 -21
- package/node_modules/@ykdz/template-builtin-presets/templates/foundation/rust/devcontainer/rust.Dockerfile +0 -18
- package/node_modules/@ykdz/template-builtin-presets/templates/foundation/rust/rust-toolchain.toml +0 -3
- package/node_modules/@ykdz/template-builtin-presets/templates/rust-bin/devcontainer/devcontainer.json +0 -21
- package/node_modules/@ykdz/template-core/dist/devcontainer.d.ts +0 -83
- package/node_modules/@ykdz/template-core/dist/devcontainer.js +0 -217
- /package/node_modules/@ykdz/template-builtin-presets/templates/{shared → vike-app}/devcontainer/shellcheck.Dockerfile +0 -0
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { resolveTemplateSource, } from "./renderer.js";
|
|
5
|
+
function hasContinuation(line, escapeCharacter) {
|
|
6
|
+
const trimmed = line.trimEnd();
|
|
7
|
+
let escapeCount = 0;
|
|
8
|
+
for (let index = trimmed.length - 1; index >= 0 && trimmed[index] === escapeCharacter; index -= 1) {
|
|
9
|
+
escapeCount += 1;
|
|
10
|
+
}
|
|
11
|
+
return escapeCount % 2 === 1;
|
|
12
|
+
}
|
|
13
|
+
function parseLogicalDockerfileInstructions(text, sourceFile) {
|
|
14
|
+
const instructions = [];
|
|
15
|
+
let escapeCharacter = "\\";
|
|
16
|
+
let logicalLine = "";
|
|
17
|
+
for (const physicalLine of text.split(/\r?\n/u)) {
|
|
18
|
+
const trimmed = physicalLine.trim();
|
|
19
|
+
if (logicalLine.length === 0 && trimmed.startsWith("#")) {
|
|
20
|
+
const escapeDirective = trimmed.match(/^#\s*escape\s*=\s*([\\`])\s*$/iu);
|
|
21
|
+
if (escapeDirective?.[1] !== undefined) {
|
|
22
|
+
escapeCharacter = escapeDirective[1];
|
|
23
|
+
}
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (trimmed.length === 0 || trimmed.startsWith("#")) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
const continuation = hasContinuation(physicalLine, escapeCharacter);
|
|
30
|
+
const physicalContent = continuation
|
|
31
|
+
? physicalLine.trimEnd().slice(0, -1)
|
|
32
|
+
: physicalLine;
|
|
33
|
+
logicalLine += physicalContent.trimStart();
|
|
34
|
+
if (continuation) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const match = logicalLine.match(/^([A-Za-z]+)(?:[ \t]+(.*))?$/u);
|
|
38
|
+
if (match?.[1] === undefined) {
|
|
39
|
+
throw new Error(`Development Container Dockerfile fragment ${sourceFile} contains an invalid logical instruction: ${logicalLine}`);
|
|
40
|
+
}
|
|
41
|
+
instructions.push({
|
|
42
|
+
name: match[1].toUpperCase(),
|
|
43
|
+
arguments: match[2] ?? "",
|
|
44
|
+
});
|
|
45
|
+
logicalLine = "";
|
|
46
|
+
}
|
|
47
|
+
if (logicalLine.length > 0) {
|
|
48
|
+
throw new Error(`Development Container Dockerfile fragment ${sourceFile} ends with an unterminated line continuation.`);
|
|
49
|
+
}
|
|
50
|
+
return instructions;
|
|
51
|
+
}
|
|
52
|
+
function instructionNames(instructions, instruction) {
|
|
53
|
+
return new Set(instructions
|
|
54
|
+
.filter(({ name }) => name === instruction)
|
|
55
|
+
.map(({ arguments: value }) => value.trim().split(/[\s=]/u)[0])
|
|
56
|
+
.filter((name) => name.length > 0));
|
|
57
|
+
}
|
|
58
|
+
function dockerfileWords(value) {
|
|
59
|
+
const words = [];
|
|
60
|
+
let word = "";
|
|
61
|
+
let quote;
|
|
62
|
+
let escaped = false;
|
|
63
|
+
for (const character of value) {
|
|
64
|
+
if (escaped) {
|
|
65
|
+
word += character;
|
|
66
|
+
escaped = false;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (character === "\\" && quote !== "'") {
|
|
70
|
+
escaped = true;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (quote !== undefined) {
|
|
74
|
+
if (character === quote) {
|
|
75
|
+
quote = undefined;
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
word += character;
|
|
79
|
+
}
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (character === '"' || character === "'") {
|
|
83
|
+
quote = character;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (/\s/u.test(character)) {
|
|
87
|
+
if (word.length > 0) {
|
|
88
|
+
words.push(word);
|
|
89
|
+
word = "";
|
|
90
|
+
}
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
word += character;
|
|
94
|
+
}
|
|
95
|
+
if (escaped) {
|
|
96
|
+
word += "\\";
|
|
97
|
+
}
|
|
98
|
+
if (word.length > 0) {
|
|
99
|
+
words.push(word);
|
|
100
|
+
}
|
|
101
|
+
return words;
|
|
102
|
+
}
|
|
103
|
+
function environmentNames(instructions) {
|
|
104
|
+
const names = new Set();
|
|
105
|
+
for (const instruction of instructions) {
|
|
106
|
+
if (instruction.name !== "ENV") {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const words = dockerfileWords(instruction.arguments);
|
|
110
|
+
const first = words[0];
|
|
111
|
+
if (first === undefined) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (!first.includes("=")) {
|
|
115
|
+
names.add(first);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
for (const assignment of words) {
|
|
119
|
+
const separator = assignment.indexOf("=");
|
|
120
|
+
if (separator > 0) {
|
|
121
|
+
names.add(assignment.slice(0, separator));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return names;
|
|
126
|
+
}
|
|
127
|
+
function validateDockerfileFragment(fragment) {
|
|
128
|
+
if (!fragment.from.endsWith(".Dockerfile")) {
|
|
129
|
+
throw new Error(`Development Container Tool Layer ${fragment.identity} must reference checked .Dockerfile template source; received ${fragment.from}.`);
|
|
130
|
+
}
|
|
131
|
+
const instructions = parseLogicalDockerfileInstructions(fragment.text, fragment.from);
|
|
132
|
+
const allowedInstructions = fragment.kind === "base"
|
|
133
|
+
? new Set(["ARG", "ENV", "FROM", "RUN", "SHELL"])
|
|
134
|
+
: new Set(["ARG", "ENV", "RUN"]);
|
|
135
|
+
for (const instruction of instructions) {
|
|
136
|
+
if (!allowedInstructions.has(instruction.name)) {
|
|
137
|
+
const role = fragment.kind === "base" ? "base" : "optional";
|
|
138
|
+
const allowed = fragment.kind === "base"
|
|
139
|
+
? "ARG, ENV, FROM, RUN, and SHELL"
|
|
140
|
+
: "ARG, ENV, and RUN";
|
|
141
|
+
throw new Error(`Development Container Tool Layer ${fragment.identity} ${role} fragment ${fragment.from} must not use Dockerfile instruction ${instruction.name}; allowed instructions are ${allowed}.`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (fragment.kind === "base") {
|
|
145
|
+
const fromCount = instructions.filter((instruction) => instruction.name === "FROM").length;
|
|
146
|
+
if (fromCount !== 1) {
|
|
147
|
+
throw new Error(`Development Container Tool Layer ${fragment.identity} base fragment ${fragment.from} must contain exactly one FROM instruction; found ${fromCount}.`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return instructions;
|
|
151
|
+
}
|
|
152
|
+
export function validateDevelopmentContainerDockerfileFragments(options) {
|
|
153
|
+
validateDockerfileFragment({ ...options.baseFragment, kind: "base" });
|
|
154
|
+
for (const fragment of options.fragments ?? []) {
|
|
155
|
+
validateDockerfileFragment({ ...fragment, kind: "optional" });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function compareText(left, right) {
|
|
159
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
160
|
+
}
|
|
161
|
+
function assertDockerfileTemplateSource(layer) {
|
|
162
|
+
if (!layer.dockerfile.from.endsWith(".Dockerfile")) {
|
|
163
|
+
throw new Error(`Development Container Tool Layer ${layer.identity} must reference checked .Dockerfile template source; received ${layer.dockerfile.from}.`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function normalizeLayer(layer) {
|
|
167
|
+
assertDockerfileTemplateSource(layer);
|
|
168
|
+
const buildArgumentNames = new Set();
|
|
169
|
+
for (const argument of layer.buildArguments ?? []) {
|
|
170
|
+
if (buildArgumentNames.has(argument.name)) {
|
|
171
|
+
throw new Error(`Development Container Tool Layer ${layer.identity} declares build argument ${argument.name} more than once.`);
|
|
172
|
+
}
|
|
173
|
+
buildArgumentNames.add(argument.name);
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
...layer,
|
|
177
|
+
requires: [...new Set(layer.requires ?? [])].toSorted(compareText),
|
|
178
|
+
buildArguments: [...(layer.buildArguments ?? [])].toSorted((left, right) => compareText(left.name, right.name) ||
|
|
179
|
+
compareText(left.value, right.value)),
|
|
180
|
+
mounts: [...(layer.mounts ?? [])]
|
|
181
|
+
.map((mount) => ({
|
|
182
|
+
...mount,
|
|
183
|
+
target: path.posix.normalize(mount.target),
|
|
184
|
+
}))
|
|
185
|
+
.toSorted((left, right) => compareText(left.identity, right.identity) ||
|
|
186
|
+
compareText(left.target, right.target)),
|
|
187
|
+
probes: [...(layer.probes ?? [])]
|
|
188
|
+
.map((probe) => ({
|
|
189
|
+
...probe,
|
|
190
|
+
args: [...(probe.args ?? [])],
|
|
191
|
+
}))
|
|
192
|
+
.toSorted((left, right) => compareText(left.identity, right.identity) ||
|
|
193
|
+
compareText(left.command, right.command) ||
|
|
194
|
+
compareText(JSON.stringify(left.args), JSON.stringify(right.args))),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
function layerFingerprint(layer, kind) {
|
|
198
|
+
return JSON.stringify({
|
|
199
|
+
kind,
|
|
200
|
+
identity: layer.identity,
|
|
201
|
+
dockerfile: resolveTemplateSource(layer.dockerfile.source, layer.dockerfile.from),
|
|
202
|
+
requires: layer.requires,
|
|
203
|
+
buildArguments: layer.buildArguments,
|
|
204
|
+
mounts: layer.mounts,
|
|
205
|
+
probes: layer.probes,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
function findDependencyCycle(layers) {
|
|
209
|
+
const byIdentity = new Map(layers.map((layer) => [layer.identity, layer]));
|
|
210
|
+
const complete = new Set();
|
|
211
|
+
const active = [];
|
|
212
|
+
const activeSet = new Set();
|
|
213
|
+
const visit = (identity) => {
|
|
214
|
+
if (activeSet.has(identity)) {
|
|
215
|
+
return active.slice(active.indexOf(identity)).toSorted(compareText);
|
|
216
|
+
}
|
|
217
|
+
if (complete.has(identity)) {
|
|
218
|
+
return undefined;
|
|
219
|
+
}
|
|
220
|
+
active.push(identity);
|
|
221
|
+
activeSet.add(identity);
|
|
222
|
+
const layer = byIdentity.get(identity);
|
|
223
|
+
for (const dependency of layer?.requires ?? []) {
|
|
224
|
+
if (!byIdentity.has(dependency)) {
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
const cycle = visit(dependency);
|
|
228
|
+
if (cycle !== undefined) {
|
|
229
|
+
return cycle;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
active.pop();
|
|
233
|
+
activeSet.delete(identity);
|
|
234
|
+
complete.add(identity);
|
|
235
|
+
return undefined;
|
|
236
|
+
};
|
|
237
|
+
for (const identity of [...byIdentity.keys()].toSorted(compareText)) {
|
|
238
|
+
const cycle = visit(identity);
|
|
239
|
+
if (cycle !== undefined) {
|
|
240
|
+
return cycle;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return undefined;
|
|
244
|
+
}
|
|
245
|
+
function orderLayers(baseLayer, optionalLayers) {
|
|
246
|
+
const normalizedBase = normalizeLayer(baseLayer);
|
|
247
|
+
if ((normalizedBase.requires?.length ?? 0) > 0) {
|
|
248
|
+
throw new Error(`Development Container Tool Layer ${normalizedBase.identity} is the base foundation and must not require other layers.`);
|
|
249
|
+
}
|
|
250
|
+
const base = {
|
|
251
|
+
...normalizedBase,
|
|
252
|
+
kind: "base",
|
|
253
|
+
};
|
|
254
|
+
const identities = new Map([[base.identity, layerFingerprint(base, "base")]]);
|
|
255
|
+
const uniqueOptionalLayers = [];
|
|
256
|
+
for (const layer of optionalLayers) {
|
|
257
|
+
const normalized = normalizeLayer(layer);
|
|
258
|
+
const fingerprint = layerFingerprint(normalized, "optional");
|
|
259
|
+
const previous = identities.get(normalized.identity);
|
|
260
|
+
if (previous === fingerprint) {
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
if (previous !== undefined) {
|
|
264
|
+
throw new Error(`Development Container Tool Layer identity ${normalized.identity} has conflicting descriptors.`);
|
|
265
|
+
}
|
|
266
|
+
identities.set(normalized.identity, fingerprint);
|
|
267
|
+
uniqueOptionalLayers.push(normalized);
|
|
268
|
+
}
|
|
269
|
+
for (const layer of uniqueOptionalLayers) {
|
|
270
|
+
for (const requiredIdentity of layer.requires ?? []) {
|
|
271
|
+
if (!identities.has(requiredIdentity)) {
|
|
272
|
+
throw new Error(`Development Container Tool Layer ${layer.identity} requires missing layer ${requiredIdentity}.`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
const cycle = findDependencyCycle(uniqueOptionalLayers);
|
|
277
|
+
if (cycle !== undefined) {
|
|
278
|
+
throw new Error(`Development Container Tool Layer dependency cycle involves: ${cycle.join(", ")}.`);
|
|
279
|
+
}
|
|
280
|
+
const remaining = uniqueOptionalLayers
|
|
281
|
+
.map((layer) => ({
|
|
282
|
+
...layer,
|
|
283
|
+
kind: "optional",
|
|
284
|
+
}))
|
|
285
|
+
.toSorted((left, right) => compareText(left.identity, right.identity));
|
|
286
|
+
const ordered = [base];
|
|
287
|
+
const resolved = new Set([base.identity]);
|
|
288
|
+
while (remaining.length > 0) {
|
|
289
|
+
const readyIndex = remaining.findIndex((layer) => (layer.requires ?? []).every((identity) => resolved.has(identity)));
|
|
290
|
+
if (readyIndex === -1) {
|
|
291
|
+
throw new Error("Development Container Tool Layer dependencies cannot be resolved.");
|
|
292
|
+
}
|
|
293
|
+
const [next] = remaining.splice(readyIndex, 1);
|
|
294
|
+
if (next === undefined) {
|
|
295
|
+
throw new Error("Development Container Tool Layer dependency resolution failed.");
|
|
296
|
+
}
|
|
297
|
+
ordered.push(next);
|
|
298
|
+
resolved.add(next.identity);
|
|
299
|
+
}
|
|
300
|
+
return ordered;
|
|
301
|
+
}
|
|
302
|
+
function completeDevelopmentContainerToolLayerPlan(layers, fragmentTexts) {
|
|
303
|
+
const buildArgumentOwners = new Map();
|
|
304
|
+
const environmentOwners = new Map();
|
|
305
|
+
const mountIdentityOwners = new Map();
|
|
306
|
+
const mountTargetOwners = new Map();
|
|
307
|
+
for (const [index, layer] of layers.entries()) {
|
|
308
|
+
for (const mount of layer.mounts ?? []) {
|
|
309
|
+
const previousIdentityOwner = mountIdentityOwners.get(mount.identity);
|
|
310
|
+
if (previousIdentityOwner !== undefined) {
|
|
311
|
+
throw new Error(`Development Container Tool Layer mount identity ${mount.identity} is declared by both ${previousIdentityOwner} and ${layer.identity}.`);
|
|
312
|
+
}
|
|
313
|
+
const previousTargetOwner = mountTargetOwners.get(mount.target);
|
|
314
|
+
if (previousTargetOwner !== undefined) {
|
|
315
|
+
throw new Error(`Development Container Tool Layer mount target ${mount.target} is declared by both ${previousTargetOwner} and ${layer.identity}.`);
|
|
316
|
+
}
|
|
317
|
+
mountIdentityOwners.set(mount.identity, layer.identity);
|
|
318
|
+
mountTargetOwners.set(mount.target, layer.identity);
|
|
319
|
+
}
|
|
320
|
+
const instructions = validateDockerfileFragment({
|
|
321
|
+
identity: layer.identity,
|
|
322
|
+
kind: layer.kind,
|
|
323
|
+
from: layer.dockerfile.from,
|
|
324
|
+
text: fragmentTexts[index],
|
|
325
|
+
});
|
|
326
|
+
const declaredArguments = instructionNames(instructions, "ARG");
|
|
327
|
+
for (const name of declaredArguments) {
|
|
328
|
+
const previousOwner = buildArgumentOwners.get(name);
|
|
329
|
+
if (previousOwner !== undefined) {
|
|
330
|
+
throw new Error(`Development Container Tool Layer build argument ${name} is declared by both ${previousOwner} and ${layer.identity}.`);
|
|
331
|
+
}
|
|
332
|
+
buildArgumentOwners.set(name, layer.identity);
|
|
333
|
+
}
|
|
334
|
+
for (const name of environmentNames(instructions)) {
|
|
335
|
+
const previousOwner = environmentOwners.get(name);
|
|
336
|
+
if (previousOwner !== undefined) {
|
|
337
|
+
throw new Error(`Development Container Tool Layer environment ${name} is declared by both ${previousOwner} and ${layer.identity}.`);
|
|
338
|
+
}
|
|
339
|
+
environmentOwners.set(name, layer.identity);
|
|
340
|
+
}
|
|
341
|
+
for (const argument of layer.buildArguments ?? []) {
|
|
342
|
+
if (!declaredArguments.has(argument.name)) {
|
|
343
|
+
throw new Error(`Development Container Tool Layer ${layer.identity} supplies build argument ${argument.name}, but ${layer.dockerfile.from} does not declare it with ARG.`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return {
|
|
348
|
+
layers,
|
|
349
|
+
buildArguments: layers.flatMap((layer) => layer.buildArguments ?? []),
|
|
350
|
+
mounts: layers.flatMap((layer) => layer.mounts ?? []),
|
|
351
|
+
probes: layers.flatMap((layer) => layer.probes ?? []),
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
export function planDevelopmentContainerToolLayersSync(options) {
|
|
355
|
+
const layers = orderLayers(options.baseLayer, options.layers ?? []);
|
|
356
|
+
return completeDevelopmentContainerToolLayerPlan(layers, layers.map((layer) => readFileSync(resolveTemplateSource(layer.dockerfile.source, layer.dockerfile.from), "utf8")));
|
|
357
|
+
}
|
|
358
|
+
export async function planDevelopmentContainerToolLayers(options) {
|
|
359
|
+
const layers = orderLayers(options.baseLayer, options.layers ?? []);
|
|
360
|
+
return completeDevelopmentContainerToolLayerPlan(layers, await Promise.all(layers.map((layer) => readFile(resolveTemplateSource(layer.dockerfile.source, layer.dockerfile.from), "utf8"))));
|
|
361
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import type { DevelopmentContainerToolLayer } from "./development-container-tool-layer.ts";
|
|
1
2
|
import type { EditorCustomizationCapability } from "./editor-customization.ts";
|
|
2
3
|
import type { CheckEnvironmentNeed, DeploymentEnvironmentNeed } from "./module-graph.ts";
|
|
3
4
|
import type { PackageDefinition } from "./project-blueprint-v2.ts";
|
|
4
5
|
import type { DependencyMaintenancePolicy } from "./project-github.ts";
|
|
5
|
-
import type { RenderOperation } from "./renderer.ts";
|
|
6
|
+
import type { RenderOperation, TemplateSourceHandle } from "./renderer.ts";
|
|
6
7
|
export type FoundationContribution = {
|
|
7
8
|
/** Toolchains the Foundation must install and project into coordinated root files. */
|
|
8
9
|
readonly toolchains: {
|
|
@@ -19,6 +20,16 @@ export type FoundationContribution = {
|
|
|
19
20
|
readonly workspacePackageGlobs?: readonly string[];
|
|
20
21
|
/** Dependency Catalog entries required by package-owned manifests. */
|
|
21
22
|
readonly dependencyCatalog?: Readonly<Record<string, string>>;
|
|
23
|
+
/** Source-backed Development Container capabilities coordinated by Foundation. */
|
|
24
|
+
readonly developmentContainerToolLayers?: readonly DevelopmentContainerToolLayer[] | undefined;
|
|
25
|
+
/** Source-backed coordinated files projected by Foundation. */
|
|
26
|
+
readonly templateFiles?: readonly {
|
|
27
|
+
readonly identity: string;
|
|
28
|
+
readonly source: TemplateSourceHandle;
|
|
29
|
+
readonly from: string;
|
|
30
|
+
readonly to: string;
|
|
31
|
+
readonly replacements?: Readonly<Record<string, string>> | undefined;
|
|
32
|
+
}[] | undefined;
|
|
22
33
|
};
|
|
23
34
|
/** A preset-agnostic package-sized part of a Generated Repository Plan. */
|
|
24
35
|
export type PackageContribution = {
|
|
@@ -21,13 +21,15 @@ overrides:
|
|
|
21
21
|
"vue>typescript": "-"
|
|
22
22
|
|
|
23
23
|
catalog:
|
|
24
|
-
"@
|
|
24
|
+
"@devcontainers/cli": 0.88.0
|
|
25
|
+
"@hono/node-server": ^2.0.12
|
|
25
26
|
"@playwright/test": ^1.61.1
|
|
26
|
-
"@tailwindcss/vite": ^4.3.
|
|
27
|
+
"@tailwindcss/vite": ^4.3.3
|
|
27
28
|
"@types/node": ^24.13.3
|
|
28
29
|
"@types/web-bluetooth": ^0.0.21
|
|
29
30
|
"@vitejs/plugin-vue": ^6.0.8
|
|
30
31
|
"@vikejs/hono": ^0.2.1
|
|
32
|
+
"@vue/devtools-api": ^8.1.5
|
|
31
33
|
"@vue/tsconfig": ^0.9.1
|
|
32
34
|
commander: ^15.0.0
|
|
33
35
|
drizzle-kit: 1.0.0-rc.4
|
|
@@ -37,10 +39,10 @@ catalog:
|
|
|
37
39
|
node-diff3: ^3.2.1
|
|
38
40
|
oxfmt: ^0.58.0
|
|
39
41
|
oxlint: ^1.73.0
|
|
40
|
-
oxlint-tsgolint: ^0.
|
|
42
|
+
oxlint-tsgolint: ^7.0.2001
|
|
41
43
|
semver: ^7.8.5
|
|
42
44
|
"@types/semver": ^7.7.1
|
|
43
|
-
pinia: ^
|
|
45
|
+
pinia: ^4.0.2
|
|
44
46
|
srvx: ^0.12.1
|
|
45
47
|
tailwindcss: ^4.3.2
|
|
46
48
|
telefunc: ^0.2.22
|
|
@@ -48,7 +50,7 @@ catalog:
|
|
|
48
50
|
typescript: npm:@typescript/typescript6@^6.0.2
|
|
49
51
|
typescript-7: npm:typescript@^7.0.2
|
|
50
52
|
valibot: ^1.4.2
|
|
51
|
-
vite: ^8.1.
|
|
53
|
+
vite: ^8.1.5
|
|
52
54
|
vike: ^0.4.260
|
|
53
55
|
vike-vue: ^0.9.12
|
|
54
56
|
vitest: ^4.1.10
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import type { DeploymentEnvironmentNeed } from "./module-graph.ts";
|
|
2
2
|
export type CiCapability = {
|
|
3
3
|
readonly workflowName: "Check";
|
|
4
4
|
readonly jobName: "check";
|
|
@@ -6,7 +6,6 @@ export type CiCapability = {
|
|
|
6
6
|
};
|
|
7
7
|
export type CiEnvironmentPreparation = {
|
|
8
8
|
readonly nodeFromPackageMetadata: boolean;
|
|
9
|
-
readonly rustToolchain: boolean;
|
|
10
9
|
};
|
|
11
10
|
export type PnpmTaskLayer = {
|
|
12
11
|
readonly installCommand: "pnpm install";
|
|
@@ -21,9 +20,6 @@ export type DependencyMaintenancePolicy = {
|
|
|
21
20
|
readonly interval: "weekly";
|
|
22
21
|
};
|
|
23
22
|
type ProjectCheckWorkflowOptions = {
|
|
24
|
-
readonly environment: {
|
|
25
|
-
readonly needs: readonly CheckEnvironmentNeed[];
|
|
26
|
-
};
|
|
27
23
|
readonly deploymentEnvironmentNeeds?: readonly DeploymentEnvironmentNeed[];
|
|
28
24
|
readonly hasDeploymentTask?: boolean | undefined;
|
|
29
25
|
readonly capability?: CiCapability | undefined;
|
|
@@ -33,12 +29,8 @@ type ProjectCheckWorkflowOptions = {
|
|
|
33
29
|
export declare function projectCheckWorkflow(options: ProjectCheckWorkflowOptions): string;
|
|
34
30
|
/** Limited substitutions for the Foundation-owned workflow Template Source. */
|
|
35
31
|
export declare function projectCheckWorkflowTemplateReplacements(options: {
|
|
36
|
-
readonly environment: {
|
|
37
|
-
readonly needs: readonly CheckEnvironmentNeed[];
|
|
38
|
-
};
|
|
39
32
|
readonly deploymentEnvironmentNeeds?: readonly DeploymentEnvironmentNeed[];
|
|
40
33
|
readonly hasDeploymentTask?: boolean | undefined;
|
|
41
|
-
readonly environmentPreparation?: Partial<CiEnvironmentPreparation>;
|
|
42
34
|
}): Record<string, string>;
|
|
43
35
|
export declare function projectDependabotConfig(policy: DependencyMaintenancePolicy): string;
|
|
44
36
|
/** Limited substitution for the Foundation-owned Dependabot Template Source. */
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { renderPlaywrightBrowserInstallCommand, } from "./module-graph.js";
|
|
2
1
|
const defaultCiCapability = {
|
|
3
2
|
workflowName: "Check",
|
|
4
3
|
jobName: "check",
|
|
@@ -12,11 +11,8 @@ export function projectCheckWorkflow(options) {
|
|
|
12
11
|
const capability = options.capability ?? defaultCiCapability;
|
|
13
12
|
const environmentPreparation = {
|
|
14
13
|
nodeFromPackageMetadata: true,
|
|
15
|
-
rustToolchain: false,
|
|
16
14
|
...options.environmentPreparation,
|
|
17
15
|
};
|
|
18
|
-
const requiresRustToolchain = environmentPreparation.rustToolchain ||
|
|
19
|
-
options.environment.needs.some((need) => need.kind === "rust-toolchain");
|
|
20
16
|
const taskLayer = options.taskLayer ?? pnpmTaskLayer;
|
|
21
17
|
const hasDeploymentTask = options.hasDeploymentTask === true;
|
|
22
18
|
const needsDocker = (options.deploymentEnvironmentNeeds ?? []).some((need) => need.kind === "docker-engine");
|
|
@@ -44,20 +40,7 @@ export function projectCheckWorkflow(options) {
|
|
|
44
40
|
if (hasDeploymentTask && needsDocker) {
|
|
45
41
|
lines.push(" - uses: docker/setup-buildx-action@v3", " if: matrix.check == 'deployment'");
|
|
46
42
|
}
|
|
47
|
-
if (requiresRustToolchain)
|
|
48
|
-
lines.push(...rustCiPreparationLines());
|
|
49
43
|
lines.push(` - run: ${taskLayer.installCommand}`);
|
|
50
|
-
const checkEnvironmentLines = [];
|
|
51
|
-
for (const need of options.environment.needs) {
|
|
52
|
-
if (need.kind === "rust-toolchain") {
|
|
53
|
-
continue;
|
|
54
|
-
}
|
|
55
|
-
checkEnvironmentLines.push(` - run: ${renderCiEnvironmentNeedCommand(need)}`);
|
|
56
|
-
if (hasDeploymentTask && need.kind === "shellcheck-command") {
|
|
57
|
-
checkEnvironmentLines.push(" if: matrix.check == 'root'");
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
lines.push(...checkEnvironmentLines);
|
|
61
44
|
lines.push(` - run: ${taskLayer.checkCommand}`);
|
|
62
45
|
if (hasDeploymentTask) {
|
|
63
46
|
lines.push(" if: matrix.check == 'root'");
|
|
@@ -68,33 +51,11 @@ export function projectCheckWorkflow(options) {
|
|
|
68
51
|
lines.push("");
|
|
69
52
|
return lines.join("\n");
|
|
70
53
|
}
|
|
71
|
-
function rustCiPreparationLines() {
|
|
72
|
-
return [
|
|
73
|
-
" - uses: dtolnay/rust-toolchain@stable",
|
|
74
|
-
" with:",
|
|
75
|
-
" components: rustfmt, clippy",
|
|
76
|
-
" - uses: Swatinem/rust-cache@v2",
|
|
77
|
-
];
|
|
78
|
-
}
|
|
79
54
|
/** Limited substitutions for the Foundation-owned workflow Template Source. */
|
|
80
55
|
export function projectCheckWorkflowTemplateReplacements(options) {
|
|
81
|
-
const requiresRustToolchain = options.environmentPreparation?.rustToolchain === true ||
|
|
82
|
-
options.environment.needs.some((need) => need.kind === "rust-toolchain");
|
|
83
56
|
const hasDeploymentTask = options.hasDeploymentTask === true;
|
|
84
57
|
const needsDocker = (options.deploymentEnvironmentNeeds ?? []).some((need) => need.kind === "docker-engine");
|
|
85
|
-
const environmentSteps = options.environment.needs
|
|
86
|
-
.filter((need) => need.kind !== "rust-toolchain")
|
|
87
|
-
.map((need) => [
|
|
88
|
-
` - run: ${renderCiEnvironmentNeedCommand(need)}`,
|
|
89
|
-
...(hasDeploymentTask && need.kind === "shellcheck-command"
|
|
90
|
-
? [" if: matrix.check == 'root'"]
|
|
91
|
-
: []),
|
|
92
|
-
].join("\n"));
|
|
93
58
|
return {
|
|
94
|
-
RUST_CI_PREPARATION: requiresRustToolchain
|
|
95
|
-
? `\n${rustCiPreparationLines().join("\n")}`
|
|
96
|
-
: "",
|
|
97
|
-
CHECK_ENVIRONMENT_PREPARATION: environmentSteps.length === 0 ? "" : `\n${environmentSteps.join("\n")}`,
|
|
98
59
|
DEPLOYMENT_MATRIX: hasDeploymentTask
|
|
99
60
|
? "\n strategy:\n matrix:\n check: [root, deployment]"
|
|
100
61
|
: "",
|
|
@@ -152,13 +113,3 @@ function renderDependabotDirectory(ecosystem, directory) {
|
|
|
152
113
|
function defaultDependabotDirectory(ecosystem) {
|
|
153
114
|
return ecosystem === "docker" ? "/.devcontainer" : "/";
|
|
154
115
|
}
|
|
155
|
-
function renderCiEnvironmentNeedCommand(need) {
|
|
156
|
-
switch (need.kind) {
|
|
157
|
-
case "playwright-browser-assets":
|
|
158
|
-
return renderPlaywrightBrowserInstallCommand(need, { withDeps: true });
|
|
159
|
-
case "shellcheck-command":
|
|
160
|
-
return need.nextStep.display;
|
|
161
|
-
case "rust-toolchain":
|
|
162
|
-
return need.nextStep.display;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
@@ -17,6 +17,15 @@ export type StructuredIdentitySetPolicy = {
|
|
|
17
17
|
} | {
|
|
18
18
|
readonly kind: "fields";
|
|
19
19
|
readonly fields: readonly string[];
|
|
20
|
+
} | {
|
|
21
|
+
readonly kind: "projection";
|
|
22
|
+
readonly members: readonly {
|
|
23
|
+
readonly identity: string;
|
|
24
|
+
readonly match: Readonly<Record<string, string | number | boolean | null>>;
|
|
25
|
+
}[];
|
|
26
|
+
readonly fallback: {
|
|
27
|
+
readonly fields: readonly string[];
|
|
28
|
+
};
|
|
20
29
|
};
|
|
21
30
|
};
|
|
22
31
|
export type ProjectProjectionReconciliation = {
|