@ykdz/template 0.0.33 → 0.0.36
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/dist/application.d.ts +2 -1
- package/dist/application.js +64 -75
- package/dist/main.js +6 -2
- package/node_modules/@ykdz/template-builtin-presets/dist/src/foundation.d.ts +50 -36
- package/node_modules/@ykdz/template-builtin-presets/dist/src/foundation.js +970 -365
- package/node_modules/@ykdz/template-builtin-presets/dist/src/generation-record.d.ts +24 -0
- package/node_modules/@ykdz/template-builtin-presets/dist/src/generation-record.js +154 -0
- package/node_modules/@ykdz/template-builtin-presets/dist/src/index.d.ts +2 -2
- package/node_modules/@ykdz/template-builtin-presets/dist/src/index.js +1 -1
- package/node_modules/@ykdz/template-builtin-presets/dist/src/rust-bin/definition.d.ts +31 -2
- package/node_modules/@ykdz/template-builtin-presets/dist/src/rust-bin/definition.js +29 -33
- package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/development-container.d.ts +1 -0
- package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/development-container.js +17 -0
- package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/typescript.d.ts +4 -3
- package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/typescript.js +9 -5
- package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/vue.d.ts +1 -1
- package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/vue.js +1 -2
- package/node_modules/@ykdz/template-builtin-presets/dist/src/ts-cli/definition.d.ts +31 -2
- package/node_modules/@ykdz/template-builtin-presets/dist/src/ts-cli/definition.js +79 -58
- package/node_modules/@ykdz/template-builtin-presets/dist/src/ts-lib/definition.d.ts +31 -2
- package/node_modules/@ykdz/template-builtin-presets/dist/src/ts-lib/definition.js +31 -23
- package/node_modules/@ykdz/template-builtin-presets/dist/src/vike-app/definition.d.ts +22 -2
- package/node_modules/@ykdz/template-builtin-presets/dist/src/vike-app/definition.js +61 -35
- package/node_modules/@ykdz/template-builtin-presets/dist/src/vue-app/definition.d.ts +26 -2
- package/node_modules/@ykdz/template-builtin-presets/dist/src/vue-app/definition.js +19 -8
- package/node_modules/@ykdz/template-builtin-presets/dist/src/vue-hono-app/definition.d.ts +22 -2
- package/node_modules/@ykdz/template-builtin-presets/dist/src/vue-hono-app/definition.js +31 -21
- package/node_modules/@ykdz/template-builtin-presets/package.json +12 -11
- package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check-deployment-diagnostics.yml +1 -1
- package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check-deployment.yml +1 -1
- package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.pnpmfile.mjs +19 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/foundation/gitignore +1 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/foundation/tsconfig.packing-hook.json +8 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/shared/devcontainer/github-cli.Dockerfile +9 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/CHANGELOG.md.template +17 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/RELEASING.md +17 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/artifact.ts +1271 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/changelog.ts +283 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/check-artifact.ts +99 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/check-readiness.ts +66 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/cli-command-identity.ts +4 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/handoff.ts +450 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/publish.ts +483 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/readiness.ts +1125 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/release.ts +819 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/release.yml +119 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/root-tsconfig.json +13 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/tsconfig.json +4 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/turbo.json +6 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/README.md +48 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/assets/CHANGELOG.md.template +17 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/assets/LICENSE-APACHE-2.0.txt +202 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/assets/LICENSE-MIT.txt +21 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/assets/README.md.template +5 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/bridge.ts +3336 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/setup.sh +221 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/src/cli-command-identity.ts +133 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/src/cli.ts +4 -3
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/src/main.ts +5 -4
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/test/integration/command.test.ts +14 -13
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/test/unit/cli-command-identity.test.ts +76 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/tsconfig.build.json +2 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/db/src/readiness.ts +1 -1
- package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/db/test/global-setup.ts +21 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/db/tsconfig.json +6 -1
- package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/db/vitest.config.ts +7 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/db-migrations/drizzle.config.ts +5 -1
- package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/web/playwright.config.ts +102 -22
- package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/web/test/playwright-teardown.ts +12 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/web/vite.config.ts +39 -1
- package/node_modules/@ykdz/template-builtin-presets/templates/vue-app/playwright.config.ts +48 -7
- package/node_modules/@ykdz/template-builtin-presets/templates/vue-hono-app/web/playwright.config.ts +59 -8
- package/node_modules/@ykdz/template-core/dist/module-graph.d.ts +15 -53
- package/node_modules/@ykdz/template-core/dist/module-graph.js +134 -101
- package/node_modules/@ykdz/template-core/dist/package-contribution.d.ts +13 -1
- package/node_modules/@ykdz/template-core/dist/package-contribution.js +92 -0
- package/node_modules/@ykdz/template-core/dist/pnpm-workspace.yaml +14 -10
- package/node_modules/@ykdz/template-core/dist/preset-definition.d.ts +71 -13
- package/node_modules/@ykdz/template-core/dist/preset-definition.js +16 -1
- package/node_modules/@ykdz/template-core/dist/project-blueprint.d.ts +56 -0
- package/node_modules/@ykdz/template-core/dist/project-blueprint.js +271 -0
- package/node_modules/@ykdz/template-core/dist/project-linking-v2.d.ts +2 -2
- package/node_modules/@ykdz/template-core/dist/project-linking-v2.js +2 -2
- package/node_modules/@ykdz/template-core/dist/project-projection.d.ts +2 -1
- package/node_modules/@ykdz/template-core/dist/project-projection.js +107 -20
- package/node_modules/@ykdz/template-core/dist/renderer.d.ts +0 -2
- package/node_modules/@ykdz/template-core/dist/renderer.js +17 -38
- package/node_modules/@ykdz/template-core/dist/template-boundary-check.d.ts +2 -2
- package/node_modules/@ykdz/template-core/dist/template-boundary-check.js +6 -0
- package/node_modules/@ykdz/template-core/dist/toolchain-resolution.d.ts +2 -0
- package/node_modules/@ykdz/template-core/dist/toolchain-resolution.js +9 -3
- package/package.json +3 -3
- package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/db-migrations/scripts/prepare-database.ts +0 -45
- package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/web/scripts/run-playwright.ts +0 -151
- package/node_modules/@ykdz/template-builtin-presets/templates/vue-app/scripts/run-playwright.ts +0 -62
- package/node_modules/@ykdz/template-builtin-presets/templates/vue-hono-app/web/scripts/run-playwright.ts +0 -103
- package/node_modules/@ykdz/template-core/dist/project-blueprint-v2.d.ts +0 -33
- package/node_modules/@ykdz/template-core/dist/project-blueprint-v2.js +0 -182
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { isBuiltin } from "node:module";
|
|
2
|
+
import * as v from "valibot";
|
|
3
|
+
/**
|
|
4
|
+
* The durable, preset-agnostic topology used by local template follow-up
|
|
5
|
+
* operations. Preset provenance deliberately belongs in Generation Record.
|
|
6
|
+
*/
|
|
7
|
+
const packageRoles = [
|
|
8
|
+
"cli-tool",
|
|
9
|
+
"runtime-service",
|
|
10
|
+
"shared-library",
|
|
11
|
+
"native-package",
|
|
12
|
+
];
|
|
13
|
+
const packagePath = /^[a-z0-9][a-z0-9-]*\/[a-z0-9][a-z0-9-]*$/;
|
|
14
|
+
const reservedWorkspaceCollections = new Set([
|
|
15
|
+
".git",
|
|
16
|
+
".github",
|
|
17
|
+
".devcontainer",
|
|
18
|
+
".template",
|
|
19
|
+
"node_modules",
|
|
20
|
+
"dist",
|
|
21
|
+
"target",
|
|
22
|
+
]);
|
|
23
|
+
const reservedPackageNames = new Set(["node_modules", "favicon.ico"]);
|
|
24
|
+
const unknownBlueprintField = "Unknown Blueprint v3 field";
|
|
25
|
+
const packageDefinitionEntries = {
|
|
26
|
+
name: v.string("Package name must be a string"),
|
|
27
|
+
path: v.string("Package Path must be a string"),
|
|
28
|
+
role: v.picklist(packageRoles, "Package Role must be cli-tool, runtime-service, shared-library, or native-package"),
|
|
29
|
+
};
|
|
30
|
+
function packageDefinitionMessage(issue) {
|
|
31
|
+
return issue.path?.at(-1)?.origin === "key" && issue.input !== undefined
|
|
32
|
+
? unknownBlueprintField
|
|
33
|
+
: "Package Definition must be an object";
|
|
34
|
+
}
|
|
35
|
+
const packageDefinitionSchema = v.strictObject({
|
|
36
|
+
packageDefinitionId: v.string("Package Definition ID must use the package-<sha256> format"),
|
|
37
|
+
...packageDefinitionEntries,
|
|
38
|
+
}, packageDefinitionMessage);
|
|
39
|
+
const packageLinkIntentSchema = v.strictObject({
|
|
40
|
+
consumerPackagePath: v.string("Package Link Intent consumer Package Path must be a string"),
|
|
41
|
+
providerPackagePath: v.string("Package Link Intent provider Package Path must be a string"),
|
|
42
|
+
}, (issue) => issue.path?.at(-1)?.origin === "key" && issue.input !== undefined
|
|
43
|
+
? unknownBlueprintField
|
|
44
|
+
: "Package Link Intent must be an object");
|
|
45
|
+
const projectBlueprintSchema = v.strictObject({
|
|
46
|
+
schemaVersion: v.literal(3, (issue) => `Unsupported Local Template Metadata schema version ${String(issue.input)}; expected 3`),
|
|
47
|
+
packages: v.array(packageDefinitionSchema, "Package Definitions must be an array"),
|
|
48
|
+
packageLinkIntents: v.optional(v.array(packageLinkIntentSchema, "Package Link Intents must be an array")),
|
|
49
|
+
}, (issue) => issue.path?.at(-1)?.origin === "key" && issue.input !== undefined
|
|
50
|
+
? unknownBlueprintField
|
|
51
|
+
: "Local Template Metadata must be an object");
|
|
52
|
+
/** Canonical npm package-name predicate shared by Blueprint and input preparation. */
|
|
53
|
+
export function isValidNewNpmPackageName(value) {
|
|
54
|
+
if (value.length === 0 ||
|
|
55
|
+
value.length > 214 ||
|
|
56
|
+
value !== value.trim() ||
|
|
57
|
+
value !== value.toLowerCase() ||
|
|
58
|
+
value === "." ||
|
|
59
|
+
value === ".." ||
|
|
60
|
+
reservedPackageNames.has(value) ||
|
|
61
|
+
isBuiltin(value)) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
const scopedMatch = /^@([^/]+)\/([^/]+)$/.exec(value);
|
|
65
|
+
if (value.startsWith("@") && scopedMatch === null)
|
|
66
|
+
return false;
|
|
67
|
+
const scope = scopedMatch?.[1];
|
|
68
|
+
const leaf = scopedMatch?.[2] ?? value;
|
|
69
|
+
if (scope === undefined && /^[._-]/.test(leaf)) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
if (scope !== undefined && leaf.startsWith(".")) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
if (/[~'!()*]/.test(leaf))
|
|
76
|
+
return false;
|
|
77
|
+
return (encodeURIComponent(leaf) === leaf &&
|
|
78
|
+
(scope === undefined || encodeURIComponent(scope) === scope));
|
|
79
|
+
}
|
|
80
|
+
/** Canonical Package Path facts shared by Blueprint and input preparation. */
|
|
81
|
+
export function validateNewPackagePath(value) {
|
|
82
|
+
const workspaceCollection = value.split("/", 1)[0];
|
|
83
|
+
return {
|
|
84
|
+
hasValidShape: packagePath.test(value),
|
|
85
|
+
...(reservedWorkspaceCollections.has(workspaceCollection)
|
|
86
|
+
? { reservedWorkspaceCollection: workspaceCollection }
|
|
87
|
+
: {}),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function blueprintIssuePath(issue) {
|
|
91
|
+
let result = ".";
|
|
92
|
+
for (const item of issue.path ?? []) {
|
|
93
|
+
if (typeof item.key === "number") {
|
|
94
|
+
result += `[${item.key}]`;
|
|
95
|
+
}
|
|
96
|
+
else if (typeof item.key === "string") {
|
|
97
|
+
result += `${result === "." ? "" : "."}${item.key}`;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
function blueprintStructureMessage(issue) {
|
|
103
|
+
if (issue.type !== "strict_object" ||
|
|
104
|
+
issue.path?.at(-1)?.origin !== "key" ||
|
|
105
|
+
issue.input !== undefined) {
|
|
106
|
+
return issue.message;
|
|
107
|
+
}
|
|
108
|
+
const path = blueprintIssuePath(issue);
|
|
109
|
+
if (path === ".schemaVersion") {
|
|
110
|
+
return "Unsupported Local Template Metadata schema version undefined; expected 3";
|
|
111
|
+
}
|
|
112
|
+
if (path === ".packages")
|
|
113
|
+
return "Package Definitions must be an array";
|
|
114
|
+
if (path.endsWith(".name"))
|
|
115
|
+
return "Package name must be a string";
|
|
116
|
+
if (path.endsWith(".packageDefinitionId")) {
|
|
117
|
+
return "Package Definition ID must use the package-<sha256> format";
|
|
118
|
+
}
|
|
119
|
+
if (path.endsWith(".path"))
|
|
120
|
+
return "Package Path must be a string";
|
|
121
|
+
if (path.endsWith(".role")) {
|
|
122
|
+
return "Package Role must be cli-tool, runtime-service, shared-library, or native-package";
|
|
123
|
+
}
|
|
124
|
+
if (path === ".packageLinkIntents") {
|
|
125
|
+
return "Package Link Intents must be an array";
|
|
126
|
+
}
|
|
127
|
+
if (path.endsWith(".consumerPackagePath")) {
|
|
128
|
+
return "Package Link Intent consumer Package Path must be a string";
|
|
129
|
+
}
|
|
130
|
+
if (path.endsWith(".providerPackagePath")) {
|
|
131
|
+
return "Package Link Intent provider Package Path must be a string";
|
|
132
|
+
}
|
|
133
|
+
return issue.message;
|
|
134
|
+
}
|
|
135
|
+
function validateBlueprintTopologySemantics(blueprint) {
|
|
136
|
+
const issues = [];
|
|
137
|
+
for (const [index, definition] of blueprint.packages.entries()) {
|
|
138
|
+
const itemPath = `.packages[${index}]`;
|
|
139
|
+
const pathValidation = validateNewPackagePath(definition.path);
|
|
140
|
+
if (!isValidNewNpmPackageName(definition.name)) {
|
|
141
|
+
issues.push({
|
|
142
|
+
path: `${itemPath}.name`,
|
|
143
|
+
message: "Package name must be a valid npm package name for new packages",
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
if (pathValidation.reservedWorkspaceCollection !== undefined) {
|
|
147
|
+
issues.push({
|
|
148
|
+
path: `${itemPath}.path`,
|
|
149
|
+
message: `Package Path ${definition.path} uses reserved workspace collection ${pathValidation.reservedWorkspaceCollection}`,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
else if (!pathValidation.hasValidShape) {
|
|
153
|
+
issues.push({
|
|
154
|
+
path: `${itemPath}.path`,
|
|
155
|
+
message: "Package Path must be exactly two safe path segments",
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
for (const [property, label] of [
|
|
160
|
+
["name", "Package name"],
|
|
161
|
+
["path", "Package Path"],
|
|
162
|
+
]) {
|
|
163
|
+
const seen = new Set();
|
|
164
|
+
for (const definition of blueprint.packages) {
|
|
165
|
+
const member = definition[property];
|
|
166
|
+
if (seen.has(member)) {
|
|
167
|
+
issues.push({
|
|
168
|
+
path: ".packages",
|
|
169
|
+
message: `${label} must be unique: ${member}`,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
seen.add(member);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const paths = new Set(blueprint.packages.map((definition) => definition.path));
|
|
176
|
+
const links = new Set();
|
|
177
|
+
for (const [index, intent] of (blueprint.packageLinkIntents ?? []).entries()) {
|
|
178
|
+
const itemPath = `.packageLinkIntents[${index}]`;
|
|
179
|
+
if (!paths.has(intent.consumerPackagePath)) {
|
|
180
|
+
issues.push({
|
|
181
|
+
path: `${itemPath}.consumerPackagePath`,
|
|
182
|
+
message: "Package Link Intent references an unknown consumer package",
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
if (!paths.has(intent.providerPackagePath)) {
|
|
186
|
+
issues.push({
|
|
187
|
+
path: `${itemPath}.providerPackagePath`,
|
|
188
|
+
message: "Package Link Intent references an unknown provider package",
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
if (intent.consumerPackagePath === intent.providerPackagePath) {
|
|
192
|
+
issues.push({
|
|
193
|
+
path: itemPath,
|
|
194
|
+
message: "Package Link Intent cannot link a package to itself",
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
const key = `${intent.consumerPackagePath}\u0000${intent.providerPackagePath}`;
|
|
198
|
+
if (links.has(key)) {
|
|
199
|
+
issues.push({
|
|
200
|
+
path: itemPath,
|
|
201
|
+
message: "Package Link Intent must be unique",
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
links.add(key);
|
|
205
|
+
}
|
|
206
|
+
return issues;
|
|
207
|
+
}
|
|
208
|
+
function validatePersistedPackageIds(blueprint) {
|
|
209
|
+
const issues = [];
|
|
210
|
+
const seen = new Set();
|
|
211
|
+
for (const [index, definition] of blueprint.packages.entries()) {
|
|
212
|
+
if (!/^package-[a-f0-9]{64}$/.test(definition.packageDefinitionId)) {
|
|
213
|
+
issues.push({
|
|
214
|
+
path: `.packages[${index}].packageDefinitionId`,
|
|
215
|
+
message: "Package Definition ID must use the package-<sha256> format",
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
if (seen.has(definition.packageDefinitionId)) {
|
|
219
|
+
issues.push({
|
|
220
|
+
path: ".packages",
|
|
221
|
+
message: `Package Definition ID must be unique: ${definition.packageDefinitionId}`,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
seen.add(definition.packageDefinitionId);
|
|
225
|
+
}
|
|
226
|
+
return issues;
|
|
227
|
+
}
|
|
228
|
+
/** Validates persisted metadata before it is used to plan or render changes. */
|
|
229
|
+
export function validateProjectBlueprint(value) {
|
|
230
|
+
const parsed = v.safeParse(projectBlueprintSchema, value);
|
|
231
|
+
if (!parsed.success) {
|
|
232
|
+
return {
|
|
233
|
+
ok: false,
|
|
234
|
+
issues: parsed.issues.map((issue) => ({
|
|
235
|
+
path: blueprintIssuePath(issue),
|
|
236
|
+
message: blueprintStructureMessage(issue),
|
|
237
|
+
})),
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
const blueprint = {
|
|
241
|
+
schemaVersion: parsed.output.schemaVersion,
|
|
242
|
+
packages: parsed.output.packages,
|
|
243
|
+
...(parsed.output.packageLinkIntents === undefined
|
|
244
|
+
? {}
|
|
245
|
+
: { packageLinkIntents: parsed.output.packageLinkIntents }),
|
|
246
|
+
};
|
|
247
|
+
const issues = [
|
|
248
|
+
...validateBlueprintTopologySemantics(blueprint),
|
|
249
|
+
...validatePersistedPackageIds(blueprint),
|
|
250
|
+
];
|
|
251
|
+
return issues.length === 0
|
|
252
|
+
? { ok: true, value: blueprint }
|
|
253
|
+
: { ok: false, issues };
|
|
254
|
+
}
|
|
255
|
+
/** Validates preset-owned topology before any persisted identity is assigned. */
|
|
256
|
+
export function assertProjectBlueprintDraft(value) {
|
|
257
|
+
const issues = validateBlueprintTopologySemantics(value);
|
|
258
|
+
if (issues.length > 0) {
|
|
259
|
+
throw new Error(issues.map((issue) => `${issue.path}: ${issue.message}`).join("\n"));
|
|
260
|
+
}
|
|
261
|
+
return value;
|
|
262
|
+
}
|
|
263
|
+
export function assertProjectBlueprint(value) {
|
|
264
|
+
const result = validateProjectBlueprint(value);
|
|
265
|
+
if (!result.ok) {
|
|
266
|
+
throw new Error(result.issues
|
|
267
|
+
.map((issue) => `${issue.path}: ${issue.message}`)
|
|
268
|
+
.join("\n"));
|
|
269
|
+
}
|
|
270
|
+
return result.value;
|
|
271
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { PackageContribution } from "./package-contribution.ts";
|
|
2
|
-
import { type PackageRole, type
|
|
2
|
+
import { type PackageRole, type ProjectBlueprint } from "./project-blueprint.ts";
|
|
3
3
|
type PackageManifest = Readonly<Record<string, unknown>>;
|
|
4
4
|
export type ExplicitProjectLinkPlan = {
|
|
5
5
|
readonly manifestPatchesByPackagePath: ReadonlyMap<string, {
|
|
@@ -24,7 +24,7 @@ export declare function canProvideSourceConditionPackageNameImport(contribution:
|
|
|
24
24
|
* string resource protocol is involved.
|
|
25
25
|
*/
|
|
26
26
|
export declare function planExplicitProjectLinks(options: {
|
|
27
|
-
readonly blueprint:
|
|
27
|
+
readonly blueprint: ProjectBlueprint;
|
|
28
28
|
readonly contributions: readonly PackageContribution[];
|
|
29
29
|
readonly manifestTruthByPackagePath?: ReadonlyMap<string, PackageManifest>;
|
|
30
30
|
}): ExplicitProjectLinkPlan;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { assertProjectBlueprint, } from "./project-blueprint.js";
|
|
2
2
|
export function canConsumeNodePackageNameImport(contribution, manifest = contribution.manifest) {
|
|
3
3
|
return (contribution.definition.role !== "native-package" &&
|
|
4
4
|
manifest.type === "module");
|
|
@@ -74,7 +74,7 @@ function exposesImportablePackageRoot(manifest) {
|
|
|
74
74
|
* string resource protocol is involved.
|
|
75
75
|
*/
|
|
76
76
|
export function planExplicitProjectLinks(options) {
|
|
77
|
-
const blueprint =
|
|
77
|
+
const blueprint = assertProjectBlueprint(options.blueprint);
|
|
78
78
|
const contributionsByPath = new Map(options.contributions.map((contribution) => [
|
|
79
79
|
contribution.definition.path,
|
|
80
80
|
contribution,
|
|
@@ -87,9 +87,10 @@ export type ProjectProjectionAction = {
|
|
|
87
87
|
};
|
|
88
88
|
export type MaterializeProjectProjectionOptions = {
|
|
89
89
|
readonly operations: readonly RenderOperation[];
|
|
90
|
-
readonly variables?: Readonly<Record<string, string>>;
|
|
91
90
|
readonly reconciliation?: readonly ProjectProjectionReconciliation[];
|
|
92
91
|
};
|
|
92
|
+
/** Pure structural preflight for a renderer-facing Project Projection plan. */
|
|
93
|
+
export declare function validateProjectProjectionPlan(options: MaterializeProjectProjectionOptions): readonly string[];
|
|
93
94
|
export declare function reconcileProjectProjections(options: {
|
|
94
95
|
readonly before: ProjectProjection;
|
|
95
96
|
readonly after: ProjectProjection;
|
|
@@ -11,30 +11,18 @@ class ProjectProjectionPathPreconditionError extends Error {
|
|
|
11
11
|
this.conflicts = conflicts;
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
|
-
function
|
|
15
|
-
return templatePath.replaceAll(/\{\{([A-Za-z][A-Za-z0-9_]*)\}\}/gu, (_placeholder, name) => {
|
|
16
|
-
const value = variables[name];
|
|
17
|
-
if (!value)
|
|
18
|
-
throw new Error(`Missing renderer variable: ${name}`);
|
|
19
|
-
if (!/^[A-Za-z0-9._-]+$/u.test(value)) {
|
|
20
|
-
throw new Error(`Renderer variable ${name} is not safe for a path segment`);
|
|
21
|
-
}
|
|
22
|
-
return value;
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
function normalizedOutputPath(operation, variables) {
|
|
14
|
+
function normalizedOutputPath(operation) {
|
|
26
15
|
const operationPath = operation.kind === "setExecutable" || operation.kind === "replaceAnchors"
|
|
27
16
|
? operation.path
|
|
28
17
|
: operation.to;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
throw new Error(`Project Projection paths must be relative: ${expanded}`);
|
|
18
|
+
if (path.isAbsolute(operationPath)) {
|
|
19
|
+
throw new Error(`Project Projection paths must be relative: ${operationPath}`);
|
|
32
20
|
}
|
|
33
|
-
const normalized = path.posix.normalize(
|
|
21
|
+
const normalized = path.posix.normalize(operationPath.split(path.sep).join("/"));
|
|
34
22
|
if (normalized === "." ||
|
|
35
23
|
normalized === ".." ||
|
|
36
24
|
normalized.startsWith("../")) {
|
|
37
|
-
throw new Error(`Project Projection path escapes its root: ${
|
|
25
|
+
throw new Error(`Project Projection path escapes its root: ${operationPath}`);
|
|
38
26
|
}
|
|
39
27
|
return normalized;
|
|
40
28
|
}
|
|
@@ -55,6 +43,103 @@ function operationDriver(operation) {
|
|
|
55
43
|
return undefined;
|
|
56
44
|
}
|
|
57
45
|
}
|
|
46
|
+
/** Pure structural preflight for a renderer-facing Project Projection plan. */
|
|
47
|
+
export function validateProjectProjectionPlan(options) {
|
|
48
|
+
const diagnostics = [];
|
|
49
|
+
const contentKindByPath = new Map();
|
|
50
|
+
const contentTypeByPath = new Map();
|
|
51
|
+
const transformationKindByPath = new Map();
|
|
52
|
+
const finalPaths = new Set();
|
|
53
|
+
for (const operation of options.operations) {
|
|
54
|
+
let outputPath;
|
|
55
|
+
try {
|
|
56
|
+
outputPath = normalizedOutputPath(operation);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
diagnostics.push(error instanceof Error ? error.message : String(error));
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
finalPaths.add(outputPath);
|
|
63
|
+
const precedingKind = contentKindByPath.get(outputPath);
|
|
64
|
+
if (operation.kind === "setExecutable" ||
|
|
65
|
+
operation.kind === "replaceAnchors") {
|
|
66
|
+
if (precedingKind === undefined) {
|
|
67
|
+
diagnostics.push(`Project Projection ${operation.kind} requires a preceding content-producing operation: ${outputPath}`);
|
|
68
|
+
}
|
|
69
|
+
const hasCompatibleContent = operation.kind !== "replaceAnchors" ||
|
|
70
|
+
contentTypeByPath.get(outputPath) !== "json";
|
|
71
|
+
if (!hasCompatibleContent) {
|
|
72
|
+
diagnostics.push(`Project Projection replaceAnchors requires text-compatible content: ${outputPath} follows ${precedingKind}`);
|
|
73
|
+
}
|
|
74
|
+
if (precedingKind !== undefined && hasCompatibleContent) {
|
|
75
|
+
if (operation.kind === "replaceAnchors") {
|
|
76
|
+
contentTypeByPath.set(outputPath, "text");
|
|
77
|
+
}
|
|
78
|
+
transformationKindByPath.set(outputPath, operation.kind);
|
|
79
|
+
}
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (operation.kind === "mergeJson" ||
|
|
83
|
+
operation.kind === "mergeJsonTemplate") {
|
|
84
|
+
const transformationKind = transformationKindByPath.get(outputPath);
|
|
85
|
+
if (transformationKind !== undefined) {
|
|
86
|
+
diagnostics.push(`Project Projection ${operation.kind} may not overwrite content after ${transformationKind}: ${outputPath}`);
|
|
87
|
+
}
|
|
88
|
+
if (contentTypeByPath.get(outputPath) === "text") {
|
|
89
|
+
diagnostics.push(`Project Projection ${operation.kind} requires JSON-compatible content: ${outputPath} follows ${precedingKind}`);
|
|
90
|
+
}
|
|
91
|
+
if (precedingKind === undefined) {
|
|
92
|
+
contentKindByPath.set(outputPath, operation.kind);
|
|
93
|
+
}
|
|
94
|
+
contentTypeByPath.set(outputPath, "json");
|
|
95
|
+
transformationKindByPath.delete(outputPath);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const overwrite = "overwrite" in operation && operation.overwrite;
|
|
99
|
+
const transformationKind = transformationKindByPath.get(outputPath);
|
|
100
|
+
if (overwrite && transformationKind !== undefined) {
|
|
101
|
+
diagnostics.push(`Project Projection ${operation.kind} may not overwrite content after ${transformationKind}: ${outputPath}`);
|
|
102
|
+
}
|
|
103
|
+
if (precedingKind !== undefined && !overwrite) {
|
|
104
|
+
diagnostics.push(`Project Projection collision at ${outputPath}: ${operation.kind} follows ${precedingKind} without overwrite`);
|
|
105
|
+
}
|
|
106
|
+
contentKindByPath.set(outputPath, operation.kind);
|
|
107
|
+
if (operation.kind === "writeJson") {
|
|
108
|
+
contentTypeByPath.set(outputPath, "json");
|
|
109
|
+
}
|
|
110
|
+
else if (operation.kind === "writeText") {
|
|
111
|
+
try {
|
|
112
|
+
JSON.parse(operation.text);
|
|
113
|
+
contentTypeByPath.set(outputPath, "json");
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
contentTypeByPath.set(outputPath, "text");
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
contentTypeByPath.set(outputPath, "unknown");
|
|
121
|
+
}
|
|
122
|
+
transformationKindByPath.delete(outputPath);
|
|
123
|
+
}
|
|
124
|
+
const orderedPaths = [...finalPaths].toSorted();
|
|
125
|
+
for (const candidate of orderedPaths) {
|
|
126
|
+
const ancestor = orderedPaths.find((other) => other !== candidate && candidate.startsWith(`${other}/`));
|
|
127
|
+
if (ancestor !== undefined) {
|
|
128
|
+
diagnostics.push(`Project Projection path collision: file ${ancestor} is an ancestor of ${candidate}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const reconciliationPaths = new Set();
|
|
132
|
+
for (const reconciliation of options.reconciliation ?? []) {
|
|
133
|
+
if (reconciliationPaths.has(reconciliation.path)) {
|
|
134
|
+
diagnostics.push(`Project Projection reconciliation paths must be unique: ${reconciliation.path}`);
|
|
135
|
+
}
|
|
136
|
+
else if (!finalPaths.has(reconciliation.path)) {
|
|
137
|
+
diagnostics.push(`Project Projection reconciliation references an unknown path: ${reconciliation.path}`);
|
|
138
|
+
}
|
|
139
|
+
reconciliationPaths.add(reconciliation.path);
|
|
140
|
+
}
|
|
141
|
+
return diagnostics;
|
|
142
|
+
}
|
|
58
143
|
function bytesEqual(left, right) {
|
|
59
144
|
return (left.byteLength === right.byteLength &&
|
|
60
145
|
left.every((value, index) => value === right[index]));
|
|
@@ -1031,12 +1116,15 @@ export function createProjectProjectionReconciler(dependencies = {}) {
|
|
|
1031
1116
|
}
|
|
1032
1117
|
export const reconcileAndApplyProjectProjections = createProjectProjectionReconciler();
|
|
1033
1118
|
export async function materializeProjectProjection(options) {
|
|
1034
|
-
const
|
|
1119
|
+
const planDiagnostics = validateProjectProjectionPlan(options);
|
|
1120
|
+
if (planDiagnostics.length > 0) {
|
|
1121
|
+
throw new Error(planDiagnostics.join("\n"));
|
|
1122
|
+
}
|
|
1035
1123
|
const finalPaths = new Set();
|
|
1036
1124
|
const contentPaths = new Set();
|
|
1037
1125
|
const policies = new Map();
|
|
1038
1126
|
for (const operation of options.operations) {
|
|
1039
|
-
const outputPath = normalizedOutputPath(operation
|
|
1127
|
+
const outputPath = normalizedOutputPath(operation);
|
|
1040
1128
|
finalPaths.add(outputPath);
|
|
1041
1129
|
if (operation.kind === "setExecutable") {
|
|
1042
1130
|
if (!contentPaths.has(outputPath)) {
|
|
@@ -1067,7 +1155,6 @@ export async function materializeProjectProjection(options) {
|
|
|
1067
1155
|
try {
|
|
1068
1156
|
await renderProject({
|
|
1069
1157
|
targetRoot,
|
|
1070
|
-
variables: { ...variables },
|
|
1071
1158
|
operations: [...options.operations],
|
|
1072
1159
|
});
|
|
1073
1160
|
const entries = await Promise.all([...finalPaths].toSorted().map(async (relativePath) => {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
type RenderVariables = Record<string, string>;
|
|
2
1
|
export type RenderOperationProvenance = {
|
|
3
2
|
readonly definitionName: string;
|
|
4
3
|
readonly plannerSourceFile: string;
|
|
@@ -96,7 +95,6 @@ export type ReplaceAnchorsOperation = {
|
|
|
96
95
|
export type RenderOperation = CopyFileOperation | WriteJsonOperation | MergeJsonOperation | MergeJsonTemplateOperation | WriteTextOperation | WriteTextFromFragmentsOperation | WriteTextTemplateOperation | SetExecutableOperation | ReplaceAnchorsOperation;
|
|
97
96
|
export type RenderProjectOptions = {
|
|
98
97
|
targetRoot: string;
|
|
99
|
-
variables?: RenderVariables | undefined;
|
|
100
98
|
operations: RenderOperation[];
|
|
101
99
|
};
|
|
102
100
|
export declare function renderProject(options: RenderProjectOptions): Promise<void>;
|
|
@@ -24,18 +24,6 @@ export function resolveTemplateSource(source, relativePath) {
|
|
|
24
24
|
}
|
|
25
25
|
return resolveContainedPath(root, relativePath);
|
|
26
26
|
}
|
|
27
|
-
function expandTemplatePath(templatePath, variables) {
|
|
28
|
-
return templatePath.replaceAll(/\{\{([A-Za-z][A-Za-z0-9_]*)\}\}/g, (_, name) => {
|
|
29
|
-
const value = variables[name];
|
|
30
|
-
if (!value) {
|
|
31
|
-
throw new Error(`Missing renderer variable: ${name}`);
|
|
32
|
-
}
|
|
33
|
-
if (!/^[A-Za-z0-9._-]+$/.test(value)) {
|
|
34
|
-
throw new Error(`Renderer variable ${name} is not safe for a path segment`);
|
|
35
|
-
}
|
|
36
|
-
return value;
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
27
|
function resolveContainedPath(root, relativePath) {
|
|
40
28
|
if (path.isAbsolute(relativePath)) {
|
|
41
29
|
throw new Error(`Renderer paths must be relative: ${relativePath}`);
|
|
@@ -49,13 +37,9 @@ function resolveContainedPath(root, relativePath) {
|
|
|
49
37
|
}
|
|
50
38
|
return resolvedPath;
|
|
51
39
|
}
|
|
52
|
-
function expandOperationPath(relativePath, options) {
|
|
53
|
-
return expandTemplatePath(relativePath, options.variables ?? {});
|
|
54
|
-
}
|
|
55
40
|
async function renderCopyFile(operation, options) {
|
|
56
|
-
const
|
|
57
|
-
const
|
|
58
|
-
const to = resolveContainedPath(options.targetRoot, expandTemplatePath(operation.to, variables));
|
|
41
|
+
const from = resolveTemplateSource(operation.source, operation.from);
|
|
42
|
+
const to = resolveContainedPath(options.targetRoot, operation.to);
|
|
59
43
|
const sourceMode = (await stat(from)).mode;
|
|
60
44
|
await mkdir(path.dirname(to), { recursive: true });
|
|
61
45
|
await copyGeneratedFile(from, to, operation.overwrite ?? false);
|
|
@@ -126,21 +110,20 @@ async function writeJsonFile(targetRoot, toPath, value, multilineArrays, keyOrde
|
|
|
126
110
|
: new Map(nestedKeyOrder.map((key, index) => [key, index]))), overwrite);
|
|
127
111
|
}
|
|
128
112
|
async function renderWriteJson(operation, options) {
|
|
129
|
-
await writeJsonFile(options.targetRoot,
|
|
113
|
+
await writeJsonFile(options.targetRoot, operation.to, operation.value, operation.multilineArrays, operation.keyOrder, operation.nestedKeyOrder, operation.overwrite);
|
|
130
114
|
}
|
|
131
115
|
async function renderMergeJson(operation, options) {
|
|
132
116
|
await mergeJsonIntoFile({
|
|
133
117
|
options,
|
|
134
118
|
patch: operation.value,
|
|
135
|
-
toPath:
|
|
119
|
+
toPath: operation.to,
|
|
136
120
|
multilineArrays: operation.multilineArrays,
|
|
137
121
|
keyOrder: operation.keyOrder,
|
|
138
122
|
nestedKeyOrder: operation.nestedKeyOrder,
|
|
139
123
|
});
|
|
140
124
|
}
|
|
141
125
|
async function renderMergeJsonTemplate(operation, options) {
|
|
142
|
-
const
|
|
143
|
-
const sourcePath = resolveTemplateSource(operation.source, expandTemplatePath(operation.from, variables));
|
|
126
|
+
const sourcePath = resolveTemplateSource(operation.source, operation.from);
|
|
144
127
|
let patch;
|
|
145
128
|
try {
|
|
146
129
|
patch = JSON.parse(await readFile(sourcePath, "utf8"));
|
|
@@ -151,7 +134,7 @@ async function renderMergeJsonTemplate(operation, options) {
|
|
|
151
134
|
await mergeJsonIntoFile({
|
|
152
135
|
options,
|
|
153
136
|
patch,
|
|
154
|
-
toPath:
|
|
137
|
+
toPath: operation.to,
|
|
155
138
|
multilineArrays: operation.multilineArrays,
|
|
156
139
|
keyOrder: operation.keyOrder,
|
|
157
140
|
nestedKeyOrder: operation.nestedKeyOrder,
|
|
@@ -171,16 +154,14 @@ async function mergeJsonIntoFile(options) {
|
|
|
171
154
|
await writeJsonFile(options.options.targetRoot, options.toPath, mergeJsonValue(existing, options.patch), options.multilineArrays, options.keyOrder, options.nestedKeyOrder, true);
|
|
172
155
|
}
|
|
173
156
|
async function renderWriteText(operation, options) {
|
|
174
|
-
const
|
|
175
|
-
const to = resolveContainedPath(options.targetRoot, toPath);
|
|
157
|
+
const to = resolveContainedPath(options.targetRoot, operation.to);
|
|
176
158
|
await mkdir(path.dirname(to), { recursive: true });
|
|
177
159
|
await writeGeneratedFile(to, operation.text);
|
|
178
160
|
}
|
|
179
|
-
async function loadTextFragments(operation
|
|
161
|
+
async function loadTextFragments(operation) {
|
|
180
162
|
return Promise.all(operation.fragments.map(async (fragment) => {
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
return { from: fromPath, text: await readFile(from, "utf8") };
|
|
163
|
+
const from = resolveTemplateSource(fragment.source, fragment.from);
|
|
164
|
+
return { from: fragment.from, text: await readFile(from, "utf8") };
|
|
184
165
|
}));
|
|
185
166
|
}
|
|
186
167
|
async function validateDevelopmentContainerDockerfileOperation(operation, fragments) {
|
|
@@ -212,16 +193,15 @@ async function preflightDevelopmentContainerDockerfiles(options) {
|
|
|
212
193
|
operation.validation !== "development-container-dockerfile") {
|
|
213
194
|
continue;
|
|
214
195
|
}
|
|
215
|
-
const fragments = await loadTextFragments(operation
|
|
196
|
+
const fragments = await loadTextFragments(operation);
|
|
216
197
|
await validateDevelopmentContainerDockerfileOperation(operation, fragments);
|
|
217
198
|
loadedFragments.set(operation, fragments);
|
|
218
199
|
}
|
|
219
200
|
return loadedFragments;
|
|
220
201
|
}
|
|
221
202
|
async function renderWriteTextFromFragments(operation, options, preloadedFragments) {
|
|
222
|
-
const
|
|
223
|
-
const
|
|
224
|
-
const fragments = preloadedFragments ?? (await loadTextFragments(operation, options));
|
|
203
|
+
const to = resolveContainedPath(options.targetRoot, operation.to);
|
|
204
|
+
const fragments = preloadedFragments ?? (await loadTextFragments(operation));
|
|
225
205
|
await validateDevelopmentContainerDockerfileOperation(operation, fragments);
|
|
226
206
|
await mkdir(path.dirname(to), { recursive: true });
|
|
227
207
|
await writeGeneratedFile(to, fragments.map(({ text }) => text.trimEnd()).join("\n\n") + "\n", operation.overwrite ?? false);
|
|
@@ -244,15 +224,14 @@ function replaceTextTemplateVariables(sourceText, replacements) {
|
|
|
244
224
|
return rendered;
|
|
245
225
|
}
|
|
246
226
|
async function renderWriteTextTemplate(operation, options) {
|
|
247
|
-
const
|
|
248
|
-
const
|
|
249
|
-
const to = resolveContainedPath(options.targetRoot, toPath);
|
|
227
|
+
const from = resolveTemplateSource(operation.source, operation.from);
|
|
228
|
+
const to = resolveContainedPath(options.targetRoot, operation.to);
|
|
250
229
|
const sourceText = await readFile(from, "utf8");
|
|
251
230
|
await mkdir(path.dirname(to), { recursive: true });
|
|
252
231
|
await writeGeneratedFile(to, replaceTextTemplateVariables(sourceText, operation.replacements), operation.overwrite);
|
|
253
232
|
}
|
|
254
233
|
async function renderSetExecutable(operation, options) {
|
|
255
|
-
const filePath = resolveContainedPath(options.targetRoot,
|
|
234
|
+
const filePath = resolveContainedPath(options.targetRoot, operation.path);
|
|
256
235
|
const currentMode = (await stat(filePath)).mode;
|
|
257
236
|
const executeBits = 0o111;
|
|
258
237
|
const mode = operation.executable
|
|
@@ -314,7 +293,7 @@ async function renderReplaceAnchors(operation, options) {
|
|
|
314
293
|
if (operation.language !== "typescript") {
|
|
315
294
|
throw new Error("Checked Transform Anchor only supports TypeScript");
|
|
316
295
|
}
|
|
317
|
-
const filePath = resolveContainedPath(options.targetRoot,
|
|
296
|
+
const filePath = resolveContainedPath(options.targetRoot, operation.path);
|
|
318
297
|
const sourceText = await readFile(filePath, "utf8");
|
|
319
298
|
const ranges = await findTypeScriptAnchorRanges(sourceText);
|
|
320
299
|
for (const anchorName of Object.keys(operation.replacements)) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ProjectBlueprint } from "./project-blueprint.ts";
|
|
2
2
|
import { type RenderOperation } from "./renderer.ts";
|
|
3
3
|
export type TemplateBoundaryViolation = {
|
|
4
4
|
readonly preset: string;
|
|
@@ -43,7 +43,7 @@ export type CheckTemplateSourceBoundaryOptions = {
|
|
|
43
43
|
readonly planningContribution?: string;
|
|
44
44
|
readonly generatedPath: ".github/workflows/check.yml";
|
|
45
45
|
/** The original Blueprint, before Foundation composes workflow facts. */
|
|
46
|
-
readonly blueprint:
|
|
46
|
+
readonly blueprint: ProjectBlueprint;
|
|
47
47
|
/** Raw Package Contribution declarations, not the composed plan field. */
|
|
48
48
|
readonly diagnosticArtifactDeclarations: readonly unknown[];
|
|
49
49
|
readonly operations: readonly RenderOperation[];
|
|
@@ -70,6 +70,12 @@ export function isProtectedGeneratedPath(generatedPath) {
|
|
|
70
70
|
normalizedPath === ".github/dependabot.yml" ||
|
|
71
71
|
normalizedPath.startsWith(".github/workflows/") ||
|
|
72
72
|
normalizedPath.startsWith(".vscode/") ||
|
|
73
|
+
normalizedPath.startsWith("scripts/npm-publication/") ||
|
|
74
|
+
normalizedPath.startsWith("scripts/npm-publication-setup/") ||
|
|
75
|
+
fileName === "CHANGELOG.md" ||
|
|
76
|
+
fileName === "LICENSE" ||
|
|
77
|
+
fileName === "README.md" ||
|
|
78
|
+
fileName === "RELEASING.md" ||
|
|
73
79
|
fileName === "oxfmt.config.ts" ||
|
|
74
80
|
fileName === "oxlint.config.ts" ||
|
|
75
81
|
fileName === "playwright.config.ts" ||
|
|
@@ -32,4 +32,6 @@ export type OnlineToolchainResolutionContractResult = {
|
|
|
32
32
|
};
|
|
33
33
|
export declare function resolveToolchainVersions(options?: ResolveToolchainVersionsOptions): Promise<ResolvedToolchainVersions>;
|
|
34
34
|
export declare function checkOnlineToolchainResolutionContract(options?: OnlineToolchainResolutionContractOptions): Promise<OnlineToolchainResolutionContractResult>;
|
|
35
|
+
export declare function isValidNodeLtsMajor(value: string): boolean;
|
|
36
|
+
export declare function isValidPackageManagerPin(value: string): boolean;
|
|
35
37
|
export {};
|