@ykdz/template 0.0.31 → 0.0.33
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 +10 -1
- package/node_modules/@ykdz/template-builtin-presets/dist/src/foundation.js +320 -161
- 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 +22 -7
- 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 +82 -11
- package/node_modules/@ykdz/template-builtin-presets/dist/src/vue-app/definition.js +9 -2
- package/node_modules/@ykdz/template-builtin-presets/dist/src/vue-hono-app/definition.js +27 -10
- package/node_modules/@ykdz/template-builtin-presets/package.json +1 -1
- package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check-deployment-diagnostics.yml +81 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check-deployment.yml +55 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check-diagnostics.yml +64 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check.yml +24 -5
- 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/playwright.config.ts +1 -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-app/playwright.config.ts +2 -1
- package/node_modules/@ykdz/template-builtin-presets/templates/vue-hono-app/api/tsconfig.json +2 -12
- package/node_modules/@ykdz/template-builtin-presets/templates/vue-hono-app/web/playwright.config.ts +2 -0
- package/node_modules/@ykdz/template-core/dist/ci-diagnostic-artifact.d.ts +19 -0
- package/node_modules/@ykdz/template-core/dist/ci-diagnostic-artifact.js +68 -0
- 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 +15 -1
- package/node_modules/@ykdz/template-core/dist/package-contribution.js +7 -0
- package/node_modules/@ykdz/template-core/dist/pnpm-workspace.yaml +1 -0
- package/node_modules/@ykdz/template-core/dist/project-github.d.ts +51 -15
- package/node_modules/@ykdz/template-core/dist/project-github.js +83 -96
- 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.d.ts +15 -1
- package/node_modules/@ykdz/template-core/dist/template-boundary-check.js +183 -1
- package/package.json +1 -1
- package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check-playwright.yml +0 -20
- package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check-rust.yml +0 -23
- package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check.dynamic.template +0 -19
- 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
|
@@ -1,112 +1,109 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { composeCiDiagnosticArtifacts, } from "./ci-diagnostic-artifact.js";
|
|
2
|
+
function validatedDiagnosticArtifacts(options) {
|
|
3
|
+
return composeCiDiagnosticArtifacts({
|
|
4
|
+
packagePaths: options.packagePaths,
|
|
5
|
+
declarations: options.artifacts,
|
|
6
|
+
});
|
|
7
|
+
}
|
|
2
8
|
const defaultCiCapability = {
|
|
3
9
|
workflowName: "Check",
|
|
4
10
|
jobName: "check",
|
|
11
|
+
jobDisplayName: "Root Check",
|
|
5
12
|
runner: "ubuntu-latest",
|
|
13
|
+
timeoutMinutes: 30,
|
|
6
14
|
};
|
|
7
15
|
const pnpmTaskLayer = {
|
|
8
|
-
installCommand: "pnpm install",
|
|
16
|
+
installCommand: "pnpm install --frozen-lockfile",
|
|
9
17
|
checkCommand: "pnpm run check",
|
|
10
18
|
};
|
|
11
|
-
|
|
19
|
+
/** Capability facts for the Foundation-owned GitHub Actions Template Source. */
|
|
20
|
+
export function projectCheckWorkflowPlan(options) {
|
|
12
21
|
const capability = options.capability ?? defaultCiCapability;
|
|
13
22
|
const environmentPreparation = {
|
|
14
23
|
nodeFromPackageMetadata: true,
|
|
15
|
-
|
|
24
|
+
packageManagerFromPackageMetadata: true,
|
|
25
|
+
pnpmStoreCache: true,
|
|
16
26
|
...options.environmentPreparation,
|
|
17
27
|
};
|
|
18
|
-
const requiresRustToolchain = environmentPreparation.rustToolchain ||
|
|
19
|
-
options.environment.needs.some((need) => need.kind === "rust-toolchain");
|
|
20
28
|
const taskLayer = options.taskLayer ?? pnpmTaskLayer;
|
|
21
29
|
const hasDeploymentTask = options.hasDeploymentTask === true;
|
|
22
30
|
const needsDocker = (options.deploymentEnvironmentNeeds ?? []).some((need) => need.kind === "docker-engine");
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
"",
|
|
26
|
-
"on:",
|
|
27
|
-
" pull_request:",
|
|
28
|
-
" push:",
|
|
29
|
-
" branches:",
|
|
30
|
-
" - main",
|
|
31
|
-
"",
|
|
32
|
-
"jobs:",
|
|
33
|
-
` ${capability.jobName}:`,
|
|
34
|
-
` runs-on: ${capability.runner}`,
|
|
35
|
-
...(hasDeploymentTask
|
|
36
|
-
? [" strategy:", " matrix:", " check: [root, deployment]"]
|
|
37
|
-
: []),
|
|
38
|
-
" steps:",
|
|
39
|
-
" - uses: actions/checkout@v7",
|
|
40
|
-
];
|
|
41
|
-
if (environmentPreparation.nodeFromPackageMetadata) {
|
|
42
|
-
lines.push(" - uses: actions/setup-node@v7", " with:", " node-version-file: package.json", " - run: corepack enable");
|
|
31
|
+
if (hasDeploymentTask && !needsDocker) {
|
|
32
|
+
throw new Error("Deployment Check requires a Docker environment need");
|
|
43
33
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
34
|
+
const packagePaths = [...options.packagePaths];
|
|
35
|
+
const diagnosticArtifacts = validatedDiagnosticArtifacts({
|
|
36
|
+
packagePaths,
|
|
37
|
+
artifacts: options.diagnosticArtifacts ?? [],
|
|
38
|
+
});
|
|
39
|
+
return {
|
|
40
|
+
workflowName: capability.workflowName,
|
|
41
|
+
triggers: { pullRequest: true, pushBranches: ["main"] },
|
|
42
|
+
permissions: { contents: "read" },
|
|
43
|
+
concurrency: {
|
|
44
|
+
group: "${{ github.workflow }}-${{ github.ref }}",
|
|
45
|
+
cancelInProgress: true,
|
|
46
|
+
},
|
|
47
|
+
rootCheck: capability,
|
|
48
|
+
matrix: hasDeploymentTask
|
|
49
|
+
? {
|
|
50
|
+
failFast: false,
|
|
51
|
+
include: [
|
|
52
|
+
{
|
|
53
|
+
capability: "root",
|
|
54
|
+
jobDisplayName: capability.jobDisplayName,
|
|
55
|
+
taskEntrypoint: taskLayer.checkCommand,
|
|
56
|
+
timeoutMinutes: capability.timeoutMinutes,
|
|
57
|
+
requiresDocker: false,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
capability: "deployment",
|
|
61
|
+
jobDisplayName: "Deployment Check",
|
|
62
|
+
taskEntrypoint: "pnpm run check:deployment",
|
|
63
|
+
timeoutMinutes: 45,
|
|
64
|
+
requiresDocker: true,
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
}
|
|
68
|
+
: undefined,
|
|
69
|
+
environmentPreparation,
|
|
70
|
+
taskLayer,
|
|
71
|
+
packagePaths,
|
|
72
|
+
diagnosticArtifacts,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/** Selects a complete Foundation-owned workflow Template Source. */
|
|
76
|
+
export function projectCheckWorkflowTemplateSource(options) {
|
|
77
|
+
const hasDeploymentTask = options.hasDeploymentTask === true;
|
|
78
|
+
const needsDocker = (options.deploymentEnvironmentNeeds ?? []).some((need) => need.kind === "docker-engine");
|
|
79
|
+
if (hasDeploymentTask && !needsDocker) {
|
|
80
|
+
throw new Error("Deployment Check requires a Docker environment need");
|
|
64
81
|
}
|
|
82
|
+
const hasDiagnostics = validatedDiagnosticArtifacts({
|
|
83
|
+
packagePaths: options.packagePaths,
|
|
84
|
+
artifacts: options.diagnosticArtifacts ?? [],
|
|
85
|
+
}).length > 0;
|
|
65
86
|
if (hasDeploymentTask) {
|
|
66
|
-
|
|
87
|
+
return hasDiagnostics
|
|
88
|
+
? ".github/workflows/check-deployment-diagnostics.yml"
|
|
89
|
+
: ".github/workflows/check-deployment.yml";
|
|
67
90
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
function rustCiPreparationLines() {
|
|
72
|
-
return [
|
|
73
|
-
" - uses: dtolnay/rust-toolchain@stable",
|
|
74
|
-
" with:",
|
|
75
|
-
" components: rustfmt, clippy",
|
|
76
|
-
" - uses: Swatinem/rust-cache@v2",
|
|
77
|
-
];
|
|
91
|
+
return hasDiagnostics
|
|
92
|
+
? ".github/workflows/check-diagnostics.yml"
|
|
93
|
+
: ".github/workflows/check.yml";
|
|
78
94
|
}
|
|
79
|
-
/** Limited
|
|
95
|
+
/** Limited, validated facts for static Foundation diagnostic staging. */
|
|
80
96
|
export function projectCheckWorkflowTemplateReplacements(options) {
|
|
81
|
-
const
|
|
82
|
-
options.
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
.map((need) => [
|
|
88
|
-
` - run: ${renderCiEnvironmentNeedCommand(need)}`,
|
|
89
|
-
...(hasDeploymentTask && need.kind === "shellcheck-command"
|
|
90
|
-
? [" if: matrix.check == 'root'"]
|
|
91
|
-
: []),
|
|
92
|
-
].join("\n"));
|
|
97
|
+
const declarations = validatedDiagnosticArtifacts({
|
|
98
|
+
packagePaths: options.packagePaths,
|
|
99
|
+
artifacts: options.diagnosticArtifacts ?? [],
|
|
100
|
+
});
|
|
101
|
+
if (declarations.length === 0)
|
|
102
|
+
return {};
|
|
93
103
|
return {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
CHECK_ENVIRONMENT_PREPARATION: environmentSteps.length === 0 ? "" : `\n${environmentSteps.join("\n")}`,
|
|
98
|
-
DEPLOYMENT_MATRIX: hasDeploymentTask
|
|
99
|
-
? "\n strategy:\n matrix:\n check: [root, deployment]"
|
|
100
|
-
: "",
|
|
101
|
-
DEPLOYMENT_DOCKER_PREPARATION: hasDeploymentTask && needsDocker
|
|
102
|
-
? "\n - uses: docker/setup-buildx-action@v3\n if: matrix.check == 'deployment'"
|
|
103
|
-
: "",
|
|
104
|
-
ROOT_CHECK_CONDITION: hasDeploymentTask
|
|
105
|
-
? "\n if: matrix.check == 'root'"
|
|
106
|
-
: "",
|
|
107
|
-
DEPLOYMENT_CHECK: hasDeploymentTask
|
|
108
|
-
? "\n - run: pnpm run check:deployment\n if: matrix.check == 'deployment'"
|
|
109
|
-
: "",
|
|
104
|
+
DIAGNOSTIC_OWNER_PATHS: declarations
|
|
105
|
+
.map((declaration) => declaration.owner.path)
|
|
106
|
+
.join("\n "),
|
|
110
107
|
};
|
|
111
108
|
}
|
|
112
109
|
export function projectDependabotConfig(policy) {
|
|
@@ -152,13 +149,3 @@ function renderDependabotDirectory(ecosystem, directory) {
|
|
|
152
149
|
function defaultDependabotDirectory(ecosystem) {
|
|
153
150
|
return ecosystem === "docker" ? "/.devcontainer" : "/";
|
|
154
151
|
}
|
|
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 = {
|
|
@@ -248,6 +248,21 @@ function memberIdentity(member, policy) {
|
|
|
248
248
|
return scalarIdentity(member);
|
|
249
249
|
if (!isStructuredObject(member))
|
|
250
250
|
return undefined;
|
|
251
|
+
if (policy.identity.kind === "projection") {
|
|
252
|
+
const matches = policy.identity.members.filter(({ match }) => Object.entries(match).every(([field, value]) => Object.hasOwn(member, field) &&
|
|
253
|
+
structuredValuesEqual(member[field], value)));
|
|
254
|
+
const matchingIdentities = new Set(matches.map(({ identity }) => identity));
|
|
255
|
+
if (matchingIdentities.size > 1)
|
|
256
|
+
return undefined;
|
|
257
|
+
const [matchingIdentity] = matchingIdentities;
|
|
258
|
+
if (matchingIdentity !== undefined) {
|
|
259
|
+
return `projection:${JSON.stringify(matchingIdentity)}`;
|
|
260
|
+
}
|
|
261
|
+
const fallbackFields = policy.identity.fallback.fields.map((field) => Object.hasOwn(member, field) ? scalarIdentity(member[field]) : undefined);
|
|
262
|
+
return fallbackFields.every((field) => field !== undefined)
|
|
263
|
+
? `fallback:${JSON.stringify(fallbackFields)}`
|
|
264
|
+
: undefined;
|
|
265
|
+
}
|
|
251
266
|
const fields = policy.identity.fields.map((field) => Object.hasOwn(member, field) ? scalarIdentity(member[field]) : undefined);
|
|
252
267
|
return fields.every((field) => field !== undefined)
|
|
253
268
|
? JSON.stringify(fields)
|
|
@@ -472,8 +487,52 @@ function assertProjectionContract(projection) {
|
|
|
472
487
|
}
|
|
473
488
|
return policies;
|
|
474
489
|
}
|
|
475
|
-
function
|
|
476
|
-
|
|
490
|
+
function reconciliationPoliciesCompatible(left, right) {
|
|
491
|
+
if (left.path !== right.path ||
|
|
492
|
+
left.driver !== right.driver ||
|
|
493
|
+
left.driver !== "structured" ||
|
|
494
|
+
right.driver !== "structured") {
|
|
495
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
496
|
+
}
|
|
497
|
+
const leftIdentitySets = left.identitySets ?? [];
|
|
498
|
+
const rightIdentitySets = right.identitySets ?? [];
|
|
499
|
+
if (leftIdentitySets.length !== rightIdentitySets.length)
|
|
500
|
+
return false;
|
|
501
|
+
return leftIdentitySets.every((leftPolicy, index) => {
|
|
502
|
+
const rightPolicy = rightIdentitySets[index];
|
|
503
|
+
if (rightPolicy === undefined ||
|
|
504
|
+
leftPolicy.location !== rightPolicy.location ||
|
|
505
|
+
leftPolicy.identity.kind !== rightPolicy.identity.kind) {
|
|
506
|
+
return false;
|
|
507
|
+
}
|
|
508
|
+
if (leftPolicy.identity.kind !== "projection" ||
|
|
509
|
+
rightPolicy.identity.kind !== "projection") {
|
|
510
|
+
return JSON.stringify(leftPolicy) === JSON.stringify(rightPolicy);
|
|
511
|
+
}
|
|
512
|
+
return (JSON.stringify(leftPolicy.identity.fallback) ===
|
|
513
|
+
JSON.stringify(rightPolicy.identity.fallback));
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
function combinedIdentitySets(before, after) {
|
|
517
|
+
const beforeIdentitySets = before?.driver === "structured" ? (before.identitySets ?? []) : [];
|
|
518
|
+
const afterIdentitySets = after?.driver === "structured" ? (after.identitySets ?? []) : [];
|
|
519
|
+
return afterIdentitySets.map((afterPolicy, index) => {
|
|
520
|
+
const beforePolicy = beforeIdentitySets[index];
|
|
521
|
+
if (beforePolicy?.identity.kind !== "projection" ||
|
|
522
|
+
afterPolicy.identity.kind !== "projection") {
|
|
523
|
+
return afterPolicy;
|
|
524
|
+
}
|
|
525
|
+
return {
|
|
526
|
+
...afterPolicy,
|
|
527
|
+
identity: {
|
|
528
|
+
...afterPolicy.identity,
|
|
529
|
+
members: [
|
|
530
|
+
...beforePolicy.identity.members,
|
|
531
|
+
...afterPolicy.identity.members,
|
|
532
|
+
],
|
|
533
|
+
},
|
|
534
|
+
};
|
|
535
|
+
});
|
|
477
536
|
}
|
|
478
537
|
function structuredValueAtLocation(root, pathSegments) {
|
|
479
538
|
let value = root;
|
|
@@ -521,6 +580,24 @@ function assertIdentitySetPolicies(entry, reconciliation) {
|
|
|
521
580
|
policy.identity.fields.some((field) => field.length === 0))) {
|
|
522
581
|
throw new Error(`Project Projection identity-set fields must be unique non-empty names: ${entry.path} ${location}`);
|
|
523
582
|
}
|
|
583
|
+
if (policy.identity.kind === "projection") {
|
|
584
|
+
const fallbackFields = policy.identity.fallback.fields;
|
|
585
|
+
if (fallbackFields.length === 0 ||
|
|
586
|
+
new Set(fallbackFields).size !== fallbackFields.length ||
|
|
587
|
+
fallbackFields.some((field) => field.length === 0)) {
|
|
588
|
+
throw new Error(`Project Projection identity-set projection fallback fields must be unique non-empty names: ${entry.path} ${location}`);
|
|
589
|
+
}
|
|
590
|
+
const memberIdentities = policy.identity.members.map((member) => member.identity);
|
|
591
|
+
if (memberIdentities.some((identity) => identity.length === 0) ||
|
|
592
|
+
new Set(memberIdentities).size !== memberIdentities.length ||
|
|
593
|
+
policy.identity.members.some((member) => Object.keys(member.match).length === 0 ||
|
|
594
|
+
Object.values(member.match).some((value) => value !== null &&
|
|
595
|
+
typeof value !== "string" &&
|
|
596
|
+
typeof value !== "number" &&
|
|
597
|
+
typeof value !== "boolean"))) {
|
|
598
|
+
throw new Error(`Project Projection identity-set projection members require unique non-empty identities and non-empty scalar matches: ${entry.path} ${location}`);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
524
601
|
const value = structuredValueAtLocation(parsed.value, pathSegments);
|
|
525
602
|
if (!Array.isArray(value)) {
|
|
526
603
|
throw new Error(`Project Projection identity-set location must reference an array: ${entry.path} ${location}`);
|
|
@@ -547,7 +624,7 @@ export async function reconcileProjectProjections(options) {
|
|
|
547
624
|
}
|
|
548
625
|
if (afterPolicy !== undefined &&
|
|
549
626
|
beforePolicy !== undefined &&
|
|
550
|
-
!
|
|
627
|
+
!reconciliationPoliciesCompatible(beforePolicy, afterPolicy)) {
|
|
551
628
|
throw new Error(`Project Projection reconciliation policy changed for ${projectionPath}`);
|
|
552
629
|
}
|
|
553
630
|
}
|
|
@@ -561,7 +638,9 @@ export async function reconcileProjectProjections(options) {
|
|
|
561
638
|
for (const projectionPath of deltaPaths) {
|
|
562
639
|
const before = beforeByPath.get(projectionPath);
|
|
563
640
|
const after = afterByPath.get(projectionPath);
|
|
564
|
-
const
|
|
641
|
+
const beforePolicy = beforePolicies.get(projectionPath);
|
|
642
|
+
const afterPolicy = afterPolicies.get(projectionPath);
|
|
643
|
+
const policy = afterPolicy ?? beforePolicy;
|
|
565
644
|
const driver = policy.driver;
|
|
566
645
|
const current = await options.readCurrent(projectionPath);
|
|
567
646
|
if (entriesEqual(current, after))
|
|
@@ -622,7 +701,7 @@ export async function reconcileProjectProjections(options) {
|
|
|
622
701
|
before,
|
|
623
702
|
current,
|
|
624
703
|
after,
|
|
625
|
-
identitySets:
|
|
704
|
+
identitySets: combinedIdentitySets(beforePolicy, afterPolicy),
|
|
626
705
|
})
|
|
627
706
|
: undefined;
|
|
628
707
|
const text = driver === "text"
|
|
@@ -176,16 +176,55 @@ async function renderWriteText(operation, options) {
|
|
|
176
176
|
await mkdir(path.dirname(to), { recursive: true });
|
|
177
177
|
await writeGeneratedFile(to, operation.text);
|
|
178
178
|
}
|
|
179
|
-
async function
|
|
180
|
-
|
|
181
|
-
const to = resolveContainedPath(options.targetRoot, toPath);
|
|
182
|
-
const texts = await Promise.all(operation.fragments.map(async (fragment) => {
|
|
179
|
+
async function loadTextFragments(operation, options) {
|
|
180
|
+
return Promise.all(operation.fragments.map(async (fragment) => {
|
|
183
181
|
const fromPath = expandTemplatePath(fragment.from, options.variables ?? {});
|
|
184
182
|
const from = resolveTemplateSource(fragment.source, fromPath);
|
|
185
|
-
return readFile(from, "utf8");
|
|
183
|
+
return { from: fromPath, text: await readFile(from, "utf8") };
|
|
186
184
|
}));
|
|
185
|
+
}
|
|
186
|
+
async function validateDevelopmentContainerDockerfileOperation(operation, fragments) {
|
|
187
|
+
if (operation.validation !== "development-container-dockerfile") {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const [baseFragment, ...optionalFragments] = fragments;
|
|
191
|
+
if (baseFragment === undefined) {
|
|
192
|
+
throw new Error("Development Container Dockerfile requires a base fragment.");
|
|
193
|
+
}
|
|
194
|
+
const { validateDevelopmentContainerDockerfileFragments } = await import("./development-container-tool-layer.js");
|
|
195
|
+
validateDevelopmentContainerDockerfileFragments({
|
|
196
|
+
baseFragment: {
|
|
197
|
+
identity: baseFragment.from,
|
|
198
|
+
from: baseFragment.from,
|
|
199
|
+
text: baseFragment.text,
|
|
200
|
+
},
|
|
201
|
+
fragments: optionalFragments.map((fragment) => ({
|
|
202
|
+
identity: fragment.from,
|
|
203
|
+
from: fragment.from,
|
|
204
|
+
text: fragment.text,
|
|
205
|
+
})),
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
async function preflightDevelopmentContainerDockerfiles(options) {
|
|
209
|
+
const loadedFragments = new Map();
|
|
210
|
+
for (const operation of options.operations) {
|
|
211
|
+
if (operation.kind !== "writeTextFromFragments" ||
|
|
212
|
+
operation.validation !== "development-container-dockerfile") {
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
const fragments = await loadTextFragments(operation, options);
|
|
216
|
+
await validateDevelopmentContainerDockerfileOperation(operation, fragments);
|
|
217
|
+
loadedFragments.set(operation, fragments);
|
|
218
|
+
}
|
|
219
|
+
return loadedFragments;
|
|
220
|
+
}
|
|
221
|
+
async function renderWriteTextFromFragments(operation, options, preloadedFragments) {
|
|
222
|
+
const toPath = expandOperationPath(operation.to, options);
|
|
223
|
+
const to = resolveContainedPath(options.targetRoot, toPath);
|
|
224
|
+
const fragments = preloadedFragments ?? (await loadTextFragments(operation, options));
|
|
225
|
+
await validateDevelopmentContainerDockerfileOperation(operation, fragments);
|
|
187
226
|
await mkdir(path.dirname(to), { recursive: true });
|
|
188
|
-
await writeGeneratedFile(to,
|
|
227
|
+
await writeGeneratedFile(to, fragments.map(({ text }) => text.trimEnd()).join("\n\n") + "\n", operation.overwrite ?? false);
|
|
189
228
|
}
|
|
190
229
|
function replaceTextTemplateVariables(sourceText, replacements) {
|
|
191
230
|
const used = new Set();
|
|
@@ -341,6 +380,7 @@ async function writeGeneratedFile(to, text, overwrite = false) {
|
|
|
341
380
|
}
|
|
342
381
|
}
|
|
343
382
|
export async function renderProject(options) {
|
|
383
|
+
const preloadedDevelopmentContainerDockerfiles = await preflightDevelopmentContainerDockerfiles(options);
|
|
344
384
|
for (const operation of options.operations) {
|
|
345
385
|
if (operation.kind === "copyFile") {
|
|
346
386
|
await renderCopyFile(operation, options);
|
|
@@ -363,7 +403,7 @@ export async function renderProject(options) {
|
|
|
363
403
|
continue;
|
|
364
404
|
}
|
|
365
405
|
if (operation.kind === "writeTextFromFragments") {
|
|
366
|
-
await renderWriteTextFromFragments(operation, options);
|
|
406
|
+
await renderWriteTextFromFragments(operation, options, preloadedDevelopmentContainerDockerfiles.get(operation));
|
|
367
407
|
continue;
|
|
368
408
|
}
|
|
369
409
|
if (operation.kind === "writeTextTemplate") {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ProjectBlueprintV2 } from "./project-blueprint-v2.ts";
|
|
1
2
|
import { type RenderOperation } from "./renderer.ts";
|
|
2
3
|
export type TemplateBoundaryViolation = {
|
|
3
4
|
readonly preset: string;
|
|
@@ -34,6 +35,19 @@ export type CheckTemplateSourceBoundaryOptions = {
|
|
|
34
35
|
readonly projections: readonly TemplateBoundaryCheckProjection[];
|
|
35
36
|
readonly templateSourceContexts?: readonly TemplateSourceContext[];
|
|
36
37
|
readonly manifestReferencedSourceFiles?: readonly string[];
|
|
38
|
+
/** Complete Built-in plans whose Foundation workflow is a protected output. */
|
|
39
|
+
readonly protectedWorkflowPlans?: readonly {
|
|
40
|
+
readonly name: string;
|
|
41
|
+
readonly sourceFilePath: string;
|
|
42
|
+
readonly definitionName?: string;
|
|
43
|
+
readonly planningContribution?: string;
|
|
44
|
+
readonly generatedPath: ".github/workflows/check.yml";
|
|
45
|
+
/** The original Blueprint, before Foundation composes workflow facts. */
|
|
46
|
+
readonly blueprint: ProjectBlueprintV2;
|
|
47
|
+
/** Raw Package Contribution declarations, not the composed plan field. */
|
|
48
|
+
readonly diagnosticArtifactDeclarations: readonly unknown[];
|
|
49
|
+
readonly operations: readonly RenderOperation[];
|
|
50
|
+
}[];
|
|
37
51
|
};
|
|
38
52
|
/**
|
|
39
53
|
* Reads each registered Template Source context before any plan is inspected.
|
|
@@ -41,4 +55,4 @@ export type CheckTemplateSourceBoundaryOptions = {
|
|
|
41
55
|
*/
|
|
42
56
|
export declare function checkTemplateSourceContexts(contexts: readonly Omit<TemplateSourceContext, "checkedFiles">[]): Promise<readonly TemplateSourceContext[]>;
|
|
43
57
|
export declare function isProtectedGeneratedPath(generatedPath: string): boolean;
|
|
44
|
-
export declare function checkTemplateSourceBoundary({ projections, templateSourceContexts, manifestReferencedSourceFiles, }: CheckTemplateSourceBoundaryOptions): Promise<TemplateBoundaryCheckResult>;
|
|
58
|
+
export declare function checkTemplateSourceBoundary({ projections, templateSourceContexts, manifestReferencedSourceFiles, protectedWorkflowPlans, }: CheckTemplateSourceBoundaryOptions): Promise<TemplateBoundaryCheckResult>;
|