@ykdz/template 0.0.32 → 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.
Files changed (24) hide show
  1. package/node_modules/@ykdz/template-builtin-presets/dist/src/foundation.d.ts +2 -0
  2. package/node_modules/@ykdz/template-builtin-presets/dist/src/foundation.js +38 -13
  3. package/node_modules/@ykdz/template-builtin-presets/dist/src/vike-app/definition.js +1 -0
  4. package/node_modules/@ykdz/template-builtin-presets/dist/src/vue-app/definition.js +6 -0
  5. package/node_modules/@ykdz/template-builtin-presets/dist/src/vue-hono-app/definition.js +6 -0
  6. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check-deployment-diagnostics.yml +81 -0
  7. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check-deployment.yml +55 -0
  8. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check-diagnostics.yml +64 -0
  9. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check.yml +24 -5
  10. package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/web/playwright.config.ts +1 -0
  11. package/node_modules/@ykdz/template-builtin-presets/templates/vue-app/playwright.config.ts +2 -1
  12. package/node_modules/@ykdz/template-builtin-presets/templates/vue-hono-app/web/playwright.config.ts +2 -0
  13. package/node_modules/@ykdz/template-core/dist/ci-diagnostic-artifact.d.ts +19 -0
  14. package/node_modules/@ykdz/template-core/dist/ci-diagnostic-artifact.js +68 -0
  15. package/node_modules/@ykdz/template-core/dist/package-contribution.d.ts +3 -0
  16. package/node_modules/@ykdz/template-core/dist/package-contribution.js +7 -0
  17. package/node_modules/@ykdz/template-core/dist/project-github.d.ts +50 -6
  18. package/node_modules/@ykdz/template-core/dist/project-github.js +83 -47
  19. package/node_modules/@ykdz/template-core/dist/template-boundary-check.d.ts +15 -1
  20. package/node_modules/@ykdz/template-core/dist/template-boundary-check.js +162 -1
  21. package/package.json +3 -3
  22. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check-playwright.yml +0 -20
  23. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check-rust.yml +0 -23
  24. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check.dynamic.template +0 -19
@@ -1,3 +1,4 @@
1
+ import type { CiDiagnosticArtifactDeclaration } from "#template-core/ci-diagnostic-artifact";
1
2
  import { type DevelopmentContainerToolLayerBuildArgument, type DevelopmentContainerToolLayerMount, type DevelopmentContainerToolLayerProbe, type PlannedDevelopmentContainerToolLayer } from "#template-core/development-container-tool-layer";
2
3
  import type { CheckEnvironmentNeed, DeploymentEnvironmentNeed } from "#template-core/module-graph";
3
4
  import { type PackageContribution } from "#template-core/package-contribution";
@@ -42,6 +43,7 @@ export type GeneratedRepositoryPlan = {
42
43
  };
43
44
  readonly environmentNeeds: readonly CheckEnvironmentNeed[];
44
45
  readonly deploymentEnvironmentNeeds: readonly DeploymentEnvironmentNeed[];
46
+ readonly ciDiagnosticArtifacts: readonly CiDiagnosticArtifactDeclaration[];
45
47
  /** Structured manifests used to derive the generated Dependency Catalog. */
46
48
  readonly manifests: readonly Readonly<Record<string, unknown>>[];
47
49
  readonly dependencyCatalog: Readonly<Record<string, string>>;
@@ -1,13 +1,14 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
+ import { composeCiDiagnosticArtifacts } from "#template-core/ci-diagnostic-artifact";
4
5
  import { collectGeneratedManifestCatalogReferences, selectTemplateDependencyCatalogEntries, } from "#template-core/dependency-catalog";
5
6
  import { planDevelopmentContainerToolLayersSync, } from "#template-core/development-container-tool-layer";
6
7
  import { editorCustomizationForCapabilities, loadEditorCustomizationDeclarations, } from "#template-core/editor-customization";
7
8
  import { checkEnvironmentNeedFact, checkEnvironmentNeedFromFact, deploymentEnvironmentNeedFact, deploymentEnvironmentNeedFromFact, renderDeploymentCheckCommand, renderFixCommand, renderRootCheckCommand, } from "#template-core/module-graph";
8
9
  import { assertPackageContribution, } from "#template-core/package-contribution";
9
10
  import { assertProjectBlueprintV2, validateProjectBlueprintV2 as validateCoreProjectBlueprintV2, } from "#template-core/project-blueprint-v2";
10
- import { projectCheckWorkflowTemplateReplacements, projectDependabotTemplateReplacements, } from "#template-core/project-github";
11
+ import { projectCheckWorkflowTemplateSource, projectCheckWorkflowTemplateReplacements, projectDependabotTemplateReplacements, } from "#template-core/project-github";
11
12
  import { planExplicitProjectLinks } from "#template-core/project-linking-v2";
12
13
  import { resolveTemplateSource, } from "#template-core/renderer";
13
14
  import { rustBinDefinition } from "./rust-bin/definition.js";
@@ -720,12 +721,22 @@ function foundationPlan(options) {
720
721
  scripts !== null &&
721
722
  typeof scripts.deployment === "string");
722
723
  });
723
- const packagePaths = contributions.map((contribution) => contribution.definition.path);
724
- if (new Set(packagePaths).size !== packagePaths.length)
724
+ const contributionPackagePaths = contributions.map((contribution) => contribution.definition.path);
725
+ if (new Set(contributionPackagePaths).size !== contributionPackagePaths.length)
725
726
  throw new Error("Package Contributions must have unique Package Paths");
727
+ const blueprintPackagePaths = options.blueprint.packages.map((definition) => definition.path);
728
+ if (contributionPackagePaths.length !== blueprintPackagePaths.length ||
729
+ contributionPackagePaths.some((packagePath) => !blueprintPackagePaths.includes(packagePath))) {
730
+ throw new Error("Package Contributions must exactly match Project Blueprint Package Paths");
731
+ }
726
732
  const packageNames = contributions.map((contribution) => contribution.definition.name);
727
733
  if (new Set(packageNames).size !== packageNames.length)
728
734
  throw new Error("Package Contributions must have unique package names");
735
+ const ciDiagnosticArtifactDeclarations = contributions.flatMap((contribution) => contribution.ciDiagnosticArtifacts ?? []);
736
+ const ciDiagnosticArtifacts = composeCiDiagnosticArtifacts({
737
+ packagePaths: blueprintPackagePaths,
738
+ declarations: ciDiagnosticArtifactDeclarations,
739
+ });
729
740
  assertCompatibleRustToolchainFacts(contributions);
730
741
  const contributedToolLayers = contributions.flatMap((contribution) => contribution.foundation.developmentContainerToolLayers ?? []);
731
742
  const developmentContainer = contributedDevcontainerComposition({
@@ -810,16 +821,29 @@ function foundationPlan(options) {
810
821
  ].join("\n"),
811
822
  },
812
823
  };
813
- const workflowOperation = {
814
- kind: "writeTextTemplate",
815
- source: templateSources.foundation,
816
- from: ".github/workflows/check.dynamic.template",
817
- to: ".github/workflows/check.yml",
818
- replacements: projectCheckWorkflowTemplateReplacements({
819
- deploymentEnvironmentNeeds,
820
- hasDeploymentTask,
821
- }),
822
- };
824
+ const workflowTemplateSource = projectCheckWorkflowTemplateSource({
825
+ packagePaths: blueprintPackagePaths,
826
+ deploymentEnvironmentNeeds,
827
+ hasDeploymentTask,
828
+ diagnosticArtifacts: ciDiagnosticArtifacts,
829
+ });
830
+ const workflowOperation = ciDiagnosticArtifacts.length === 0
831
+ ? {
832
+ kind: "copyFile",
833
+ source: templateSources.foundation,
834
+ from: workflowTemplateSource,
835
+ to: ".github/workflows/check.yml",
836
+ }
837
+ : {
838
+ kind: "writeTextTemplate",
839
+ source: templateSources.foundation,
840
+ from: workflowTemplateSource,
841
+ to: ".github/workflows/check.yml",
842
+ replacements: projectCheckWorkflowTemplateReplacements({
843
+ packagePaths: blueprintPackagePaths,
844
+ diagnosticArtifacts: ciDiagnosticArtifacts,
845
+ }),
846
+ };
823
847
  const workflowOperations = [
824
848
  workflowOperation,
825
849
  {
@@ -1021,6 +1045,7 @@ function foundationPlan(options) {
1021
1045
  },
1022
1046
  environmentNeeds,
1023
1047
  deploymentEnvironmentNeeds,
1048
+ ciDiagnosticArtifacts,
1024
1049
  manifests: [...contributions.map((item) => item.manifest), rootManifest],
1025
1050
  dependencyCatalog,
1026
1051
  dependencyMaintenancePolicy,
@@ -315,6 +315,7 @@ function webContribution(context) {
315
315
  playwrightBrowserAssetsEnvironmentNeed({ browser: "chromium", owner }),
316
316
  shellCheckEnvironmentNeed(owner),
317
317
  ],
318
+ ciDiagnosticArtifacts: [{ kind: "playwright", owner }],
318
319
  deploymentEnvironmentNeeds: [dockerEngineEnvironmentNeed()],
319
320
  foundation: {
320
321
  ...foundation(),
@@ -44,6 +44,12 @@ function appContribution(options) {
44
44
  }),
45
45
  operations,
46
46
  environmentNeeds: vueApplicationEnvironmentNeeds(definition.path),
47
+ ciDiagnosticArtifacts: [
48
+ {
49
+ kind: "playwright",
50
+ owner: { kind: "package-boundary", path: definition.path },
51
+ },
52
+ ],
47
53
  foundation: {
48
54
  toolchains: {},
49
55
  editorCapabilities: ["oxc-format-lint", "vue", "tailwind", "vitest"],
@@ -140,6 +140,12 @@ function webContribution(context) {
140
140
  }),
141
141
  operations,
142
142
  environmentNeeds: vueApplicationEnvironmentNeeds(definition.path),
143
+ ciDiagnosticArtifacts: [
144
+ {
145
+ kind: "playwright",
146
+ owner: { kind: "package-boundary", path: definition.path },
147
+ },
148
+ ],
143
149
  foundation: {
144
150
  ...packageFoundation(),
145
151
  developmentContainerToolLayers: vueApplicationDevelopmentContainerToolLayers(),
@@ -0,0 +1,81 @@
1
+ name: Check
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches:
7
+ - main
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ concurrency:
13
+ group: ${{ github.workflow }}-${{ github.ref }}
14
+ cancel-in-progress: true
15
+
16
+ jobs:
17
+ check:
18
+ name: ${{ matrix.job_name }}
19
+ runs-on: ubuntu-latest
20
+ timeout-minutes: ${{ matrix.timeout_minutes }}
21
+ strategy:
22
+ fail-fast: false
23
+ matrix:
24
+ include:
25
+ - capability: root
26
+ job_name: Root Check
27
+ task_entrypoint: pnpm run check
28
+ timeout_minutes: 30
29
+ requires_docker: false
30
+ - capability: deployment
31
+ job_name: Deployment Check
32
+ task_entrypoint: pnpm run check:deployment
33
+ timeout_minutes: 45
34
+ requires_docker: true
35
+ steps:
36
+ - name: Checkout source
37
+ uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
38
+ with:
39
+ persist-credentials: false
40
+ fetch-depth: 1
41
+ - name: Set up Node.js
42
+ uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
43
+ with:
44
+ node-version-file: package.json
45
+ - name: Set up pnpm
46
+ uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0
47
+ with:
48
+ cache: true
49
+ - name: Set up Docker Buildx
50
+ uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
51
+ if: matrix.requires_docker
52
+ - name: Install dependencies
53
+ run: pnpm install --frozen-lockfile
54
+ - name: Run selected Check
55
+ run: ${{ matrix.task_entrypoint }}
56
+ - name: Stage Root Check diagnostics
57
+ if: failure() && matrix.capability == 'root'
58
+ env:
59
+ DIAGNOSTIC_OWNER_PATHS: |-
60
+ {{DIAGNOSTIC_OWNER_PATHS}}
61
+ run: |
62
+ rm -rf .template-ci-diagnostics
63
+ mkdir -p .template-ci-diagnostics
64
+ printf '%s\n' "$DIAGNOSTIC_OWNER_PATHS" | while IFS= read -r owner_path; do
65
+ for diagnostic_directory in test-results playwright-report; do
66
+ source_path="$owner_path/$diagnostic_directory"
67
+ destination_path=".template-ci-diagnostics/$owner_path/$diagnostic_directory"
68
+ if [ -d "$source_path" ]; then
69
+ mkdir -p "$(dirname "$destination_path")"
70
+ cp -R "$source_path" "$destination_path"
71
+ fi
72
+ done
73
+ done
74
+ - name: Upload Root Check diagnostics
75
+ if: failure() && matrix.capability == 'root'
76
+ uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4
77
+ with:
78
+ name: root-check-diagnostics
79
+ path: .template-ci-diagnostics
80
+ if-no-files-found: ignore
81
+ retention-days: 7
@@ -0,0 +1,55 @@
1
+ name: Check
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches:
7
+ - main
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ concurrency:
13
+ group: ${{ github.workflow }}-${{ github.ref }}
14
+ cancel-in-progress: true
15
+
16
+ jobs:
17
+ check:
18
+ name: ${{ matrix.job_name }}
19
+ runs-on: ubuntu-latest
20
+ timeout-minutes: ${{ matrix.timeout_minutes }}
21
+ strategy:
22
+ fail-fast: false
23
+ matrix:
24
+ include:
25
+ - capability: root
26
+ job_name: Root Check
27
+ task_entrypoint: pnpm run check
28
+ timeout_minutes: 30
29
+ requires_docker: false
30
+ - capability: deployment
31
+ job_name: Deployment Check
32
+ task_entrypoint: pnpm run check:deployment
33
+ timeout_minutes: 45
34
+ requires_docker: true
35
+ steps:
36
+ - name: Checkout source
37
+ uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
38
+ with:
39
+ persist-credentials: false
40
+ fetch-depth: 1
41
+ - name: Set up Node.js
42
+ uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
43
+ with:
44
+ node-version-file: package.json
45
+ - name: Set up pnpm
46
+ uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0
47
+ with:
48
+ cache: true
49
+ - name: Set up Docker Buildx
50
+ uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
51
+ if: matrix.requires_docker
52
+ - name: Install dependencies
53
+ run: pnpm install --frozen-lockfile
54
+ - name: Run selected Check
55
+ run: ${{ matrix.task_entrypoint }}
@@ -0,0 +1,64 @@
1
+ name: Check
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches:
7
+ - main
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ concurrency:
13
+ group: ${{ github.workflow }}-${{ github.ref }}
14
+ cancel-in-progress: true
15
+
16
+ jobs:
17
+ check:
18
+ name: Root Check
19
+ runs-on: ubuntu-latest
20
+ timeout-minutes: 30
21
+ steps:
22
+ - name: Checkout source
23
+ uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
24
+ with:
25
+ persist-credentials: false
26
+ fetch-depth: 1
27
+ - name: Set up Node.js
28
+ uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
29
+ with:
30
+ node-version-file: package.json
31
+ - name: Set up pnpm
32
+ uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0
33
+ with:
34
+ cache: true
35
+ - name: Install dependencies
36
+ run: pnpm install --frozen-lockfile
37
+ - name: Run Root Check
38
+ run: pnpm run check
39
+ - name: Stage Root Check diagnostics
40
+ if: failure()
41
+ env:
42
+ DIAGNOSTIC_OWNER_PATHS: |-
43
+ {{DIAGNOSTIC_OWNER_PATHS}}
44
+ run: |
45
+ rm -rf .template-ci-diagnostics
46
+ mkdir -p .template-ci-diagnostics
47
+ printf '%s\n' "$DIAGNOSTIC_OWNER_PATHS" | while IFS= read -r owner_path; do
48
+ for diagnostic_directory in test-results playwright-report; do
49
+ source_path="$owner_path/$diagnostic_directory"
50
+ destination_path=".template-ci-diagnostics/$owner_path/$diagnostic_directory"
51
+ if [ -d "$source_path" ]; then
52
+ mkdir -p "$(dirname "$destination_path")"
53
+ cp -R "$source_path" "$destination_path"
54
+ fi
55
+ done
56
+ done
57
+ - name: Upload Root Check diagnostics
58
+ if: failure()
59
+ uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4
60
+ with:
61
+ name: root-check-diagnostics
62
+ path: .template-ci-diagnostics
63
+ if-no-files-found: ignore
64
+ retention-days: 7
@@ -6,14 +6,33 @@ on:
6
6
  branches:
7
7
  - main
8
8
 
9
+ permissions:
10
+ contents: read
11
+
12
+ concurrency:
13
+ group: ${{ github.workflow }}-${{ github.ref }}
14
+ cancel-in-progress: true
15
+
9
16
  jobs:
10
17
  check:
18
+ name: Root Check
11
19
  runs-on: ubuntu-latest
20
+ timeout-minutes: 30
12
21
  steps:
13
- - uses: actions/checkout@v7
14
- - uses: actions/setup-node@v7
22
+ - name: Checkout source
23
+ uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
24
+ with:
25
+ persist-credentials: false
26
+ fetch-depth: 1
27
+ - name: Set up Node.js
28
+ uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
15
29
  with:
16
30
  node-version-file: package.json
17
- - run: corepack enable
18
- - run: pnpm install
19
- - run: pnpm run check
31
+ - name: Set up pnpm
32
+ uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0
33
+ with:
34
+ cache: true
35
+ - name: Install dependencies
36
+ run: pnpm install --frozen-lockfile
37
+ - name: Run Root Check
38
+ run: pnpm run check
@@ -60,6 +60,7 @@ export default defineConfig({
60
60
  baseURL: previewUrl,
61
61
  trace: "retain-on-failure",
62
62
  },
63
+ reporter: [["list"], ["html"]],
63
64
  ...(externalServiceUrl
64
65
  ? {}
65
66
  : {
@@ -17,8 +17,9 @@ export default defineConfig({
17
17
  testDir: "./test/e2e",
18
18
  use: {
19
19
  baseURL: previewUrl,
20
- trace: "on-first-retry",
20
+ trace: "retain-on-failure",
21
21
  },
22
+ reporter: [["list"], ["html"]],
22
23
  webServer: {
23
24
  command: `pnpm run preview --host 127.0.0.1 --port ${previewPort}`,
24
25
  reuseExistingServer: !process.env.CI,
@@ -20,8 +20,10 @@ export default defineConfig({
20
20
  testDir: "./test/e2e",
21
21
  use: {
22
22
  baseURL: webBaseUrl,
23
+ trace: "retain-on-failure",
23
24
  ...devices["Desktop Chrome"],
24
25
  },
26
+ reporter: [["list"], ["html"]],
25
27
  webServer: [
26
28
  {
27
29
  command: `PORT=${apiPort} pnpm --dir ../api run start`,
@@ -0,0 +1,19 @@
1
+ import type { PackageBoundaryOwner } from "./module-graph.ts";
2
+ /** Closed, owner-scoped native evidence a Package Contribution may retain in CI. */
3
+ export type CiDiagnosticArtifactDeclaration = {
4
+ readonly kind: "playwright";
5
+ readonly owner: PackageBoundaryOwner;
6
+ };
7
+ /**
8
+ * Validates the intentionally closed diagnostic declaration surface. It does
9
+ * not accept paths: native paths are derived only after the owner is known.
10
+ */
11
+ export declare function assertCiDiagnosticArtifactDeclaration(value: unknown): CiDiagnosticArtifactDeclaration;
12
+ /**
13
+ * Composes closed native diagnostics independently of presets and environment
14
+ * preparation. Equivalent declarations collapse into stable owner order.
15
+ */
16
+ export declare function composeCiDiagnosticArtifacts(options: {
17
+ readonly packagePaths: readonly string[];
18
+ readonly declarations: readonly unknown[];
19
+ }): readonly CiDiagnosticArtifactDeclaration[];
@@ -0,0 +1,68 @@
1
+ function isRecord(value) {
2
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3
+ }
4
+ function hasExactKeys(value, keys) {
5
+ const actual = Object.keys(value).toSorted();
6
+ const expected = [...keys].toSorted();
7
+ return (actual.length === expected.length &&
8
+ actual.every((key, index) => key === expected[index]));
9
+ }
10
+ const reservedWorkspaceCollections = new Set([
11
+ ".git",
12
+ ".github",
13
+ ".devcontainer",
14
+ ".template",
15
+ "node_modules",
16
+ "dist",
17
+ "target",
18
+ ]);
19
+ function isSafePackagePath(value) {
20
+ return (/^[a-z0-9][a-z0-9-]*\/[a-z0-9][a-z0-9-]*$/u.test(value) &&
21
+ !reservedWorkspaceCollections.has(value.split("/", 1)[0]));
22
+ }
23
+ /**
24
+ * Validates the intentionally closed diagnostic declaration surface. It does
25
+ * not accept paths: native paths are derived only after the owner is known.
26
+ */
27
+ export function assertCiDiagnosticArtifactDeclaration(value) {
28
+ if (!isRecord(value) || !hasExactKeys(value, ["kind", "owner"])) {
29
+ throw new Error("CI Diagnostic Artifact declarations may contain only kind and owner");
30
+ }
31
+ if (value.kind !== "playwright") {
32
+ throw new Error("Unsupported CI Diagnostic Artifact kind");
33
+ }
34
+ if (!isRecord(value.owner) || !hasExactKeys(value.owner, ["kind", "path"])) {
35
+ throw new Error("CI Diagnostic Artifact requires a Package Boundary owner");
36
+ }
37
+ if (value.owner.kind !== "package-boundary" ||
38
+ typeof value.owner.path !== "string" ||
39
+ !isSafePackagePath(value.owner.path)) {
40
+ throw new Error("CI Diagnostic Artifact owner has an unsafe Package Boundary path");
41
+ }
42
+ return {
43
+ kind: "playwright",
44
+ owner: { kind: "package-boundary", path: value.owner.path },
45
+ };
46
+ }
47
+ /**
48
+ * Composes closed native diagnostics independently of presets and environment
49
+ * preparation. Equivalent declarations collapse into stable owner order.
50
+ */
51
+ export function composeCiDiagnosticArtifacts(options) {
52
+ for (const packagePath of options.packagePaths) {
53
+ if (!isSafePackagePath(packagePath)) {
54
+ throw new Error(`CI Diagnostic Artifact Package Boundary path is unsafe: ${packagePath}`);
55
+ }
56
+ }
57
+ const owners = new Set(options.packagePaths);
58
+ const artifacts = new Map();
59
+ for (const value of options.declarations) {
60
+ const declaration = assertCiDiagnosticArtifactDeclaration(value);
61
+ if (!owners.has(declaration.owner.path)) {
62
+ throw new Error(`CI Diagnostic Artifact owner is not a declared Package Boundary: ${declaration.owner.path}`);
63
+ }
64
+ const key = `${declaration.kind}:${declaration.owner.path}`;
65
+ artifacts.set(key, declaration);
66
+ }
67
+ return [...artifacts.values()].toSorted((left, right) => left.owner.path.localeCompare(right.owner.path));
68
+ }
@@ -1,3 +1,4 @@
1
+ import { type CiDiagnosticArtifactDeclaration } from "./ci-diagnostic-artifact.ts";
1
2
  import type { DevelopmentContainerToolLayer } from "./development-container-tool-layer.ts";
2
3
  import type { EditorCustomizationCapability } from "./editor-customization.ts";
3
4
  import type { CheckEnvironmentNeed, DeploymentEnvironmentNeed } from "./module-graph.ts";
@@ -43,6 +44,8 @@ export type PackageContribution = {
43
44
  /** Typed requirements consumed by the Foundation for coordinated root outputs. */
44
45
  readonly foundation: FoundationContribution;
45
46
  readonly environmentNeeds: readonly CheckEnvironmentNeed[];
47
+ /** Closed native CI evidence owned by this Package Boundary. */
48
+ readonly ciDiagnosticArtifacts?: readonly CiDiagnosticArtifactDeclaration[];
46
49
  /** Requirements prepared only by a focused deployment entrypoint. */
47
50
  readonly deploymentEnvironmentNeeds?: readonly DeploymentEnvironmentNeed[];
48
51
  };
@@ -1,3 +1,4 @@
1
+ import { assertCiDiagnosticArtifactDeclaration, } from "./ci-diagnostic-artifact.js";
1
2
  const operationPath = (operation) => {
2
3
  if ("to" in operation)
3
4
  return operation.to;
@@ -20,5 +21,11 @@ export function assertPackageContribution(contribution, provenance = {}) {
20
21
  : `${provenance.definitionName}: ${provenance.planner ?? "Package Contribution"} `;
21
22
  throw new Error(`${owner}${rule}; ${contribution.definition.path} attempted ${target}`);
22
23
  }
24
+ for (const artifact of contribution.ciDiagnosticArtifacts ?? []) {
25
+ const declaration = assertCiDiagnosticArtifactDeclaration(artifact);
26
+ if (declaration.owner.path !== contribution.definition.path) {
27
+ throw new Error(`CI Diagnostic Artifact owner must match its Package Contribution: ${contribution.definition.path}`);
28
+ }
29
+ }
23
30
  return contribution;
24
31
  }
@@ -1,16 +1,52 @@
1
+ import { type CiDiagnosticArtifactDeclaration } from "./ci-diagnostic-artifact.ts";
1
2
  import type { DeploymentEnvironmentNeed } from "./module-graph.ts";
2
3
  export type CiCapability = {
3
4
  readonly workflowName: "Check";
4
5
  readonly jobName: "check";
6
+ readonly jobDisplayName: "Root Check";
5
7
  readonly runner: "ubuntu-latest";
8
+ readonly timeoutMinutes: 30;
6
9
  };
7
10
  export type CiEnvironmentPreparation = {
8
11
  readonly nodeFromPackageMetadata: boolean;
12
+ readonly packageManagerFromPackageMetadata: boolean;
13
+ readonly pnpmStoreCache: boolean;
9
14
  };
10
15
  export type PnpmTaskLayer = {
11
- readonly installCommand: "pnpm install";
16
+ readonly installCommand: "pnpm install --frozen-lockfile";
12
17
  readonly checkCommand: "pnpm run check";
13
18
  };
19
+ export type CiCheckMatrixEntry = {
20
+ readonly capability: "root" | "deployment";
21
+ readonly jobDisplayName: "Root Check" | "Deployment Check";
22
+ readonly taskEntrypoint: "pnpm run check" | "pnpm run check:deployment";
23
+ readonly timeoutMinutes: 30 | 45;
24
+ readonly requiresDocker: boolean;
25
+ };
26
+ export type ProjectCheckWorkflowPlan = {
27
+ readonly workflowName: "Check";
28
+ readonly triggers: {
29
+ readonly pullRequest: true;
30
+ readonly pushBranches: readonly ["main"];
31
+ };
32
+ readonly permissions: {
33
+ readonly contents: "read";
34
+ };
35
+ readonly concurrency: {
36
+ readonly group: "${{ github.workflow }}-${{ github.ref }}";
37
+ readonly cancelInProgress: true;
38
+ };
39
+ readonly rootCheck: CiCapability;
40
+ readonly matrix: {
41
+ readonly failFast: false;
42
+ readonly include: readonly CiCheckMatrixEntry[];
43
+ } | undefined;
44
+ readonly environmentPreparation: CiEnvironmentPreparation;
45
+ readonly taskLayer: PnpmTaskLayer;
46
+ /** Actual Package Boundaries allowed to own retained native diagnostics. */
47
+ readonly packagePaths: readonly string[];
48
+ readonly diagnosticArtifacts: readonly CiDiagnosticArtifactDeclaration[];
49
+ };
14
50
  export type DependencyEcosystem = "npm" | "cargo" | "github-actions" | "docker" | "rust-toolchain";
15
51
  export type DependabotDirectory = `/${string}`;
16
52
  export type DependencyMaintenancePolicy = {
@@ -19,19 +55,27 @@ export type DependencyMaintenancePolicy = {
19
55
  readonly extraDirectories?: Partial<Record<DependencyEcosystem, readonly DependabotDirectory[]>>;
20
56
  readonly interval: "weekly";
21
57
  };
22
- type ProjectCheckWorkflowOptions = {
58
+ export type ProjectCheckWorkflowDiagnosticOptions = {
59
+ /** Package Paths from the real Project Blueprint or Package Contributions. */
60
+ readonly packagePaths: readonly string[];
61
+ readonly diagnosticArtifacts?: readonly unknown[] | undefined;
62
+ };
63
+ type ProjectCheckWorkflowOptions = ProjectCheckWorkflowDiagnosticOptions & {
23
64
  readonly deploymentEnvironmentNeeds?: readonly DeploymentEnvironmentNeed[];
24
65
  readonly hasDeploymentTask?: boolean | undefined;
25
66
  readonly capability?: CiCapability | undefined;
26
67
  readonly environmentPreparation?: Partial<CiEnvironmentPreparation> | undefined;
27
68
  readonly taskLayer?: PnpmTaskLayer | undefined;
28
69
  };
29
- export declare function projectCheckWorkflow(options: ProjectCheckWorkflowOptions): string;
30
- /** Limited substitutions for the Foundation-owned workflow Template Source. */
31
- export declare function projectCheckWorkflowTemplateReplacements(options: {
70
+ /** Capability facts for the Foundation-owned GitHub Actions Template Source. */
71
+ export declare function projectCheckWorkflowPlan(options: ProjectCheckWorkflowOptions): ProjectCheckWorkflowPlan;
72
+ /** Selects a complete Foundation-owned workflow Template Source. */
73
+ export declare function projectCheckWorkflowTemplateSource(options: ProjectCheckWorkflowDiagnosticOptions & {
32
74
  readonly deploymentEnvironmentNeeds?: readonly DeploymentEnvironmentNeed[];
33
75
  readonly hasDeploymentTask?: boolean | undefined;
34
- }): Record<string, string>;
76
+ }): ".github/workflows/check.yml" | ".github/workflows/check-diagnostics.yml" | ".github/workflows/check-deployment.yml" | ".github/workflows/check-deployment-diagnostics.yml";
77
+ /** Limited, validated facts for static Foundation diagnostic staging. */
78
+ export declare function projectCheckWorkflowTemplateReplacements(options: ProjectCheckWorkflowDiagnosticOptions): Record<string, string>;
35
79
  export declare function projectDependabotConfig(policy: DependencyMaintenancePolicy): string;
36
80
  /** Limited substitution for the Foundation-owned Dependabot Template Source. */
37
81
  export declare function projectDependabotTemplateReplacements(policy: DependencyMaintenancePolicy): Record<string, string>;
@@ -1,73 +1,109 @@
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
+ }
1
8
  const defaultCiCapability = {
2
9
  workflowName: "Check",
3
10
  jobName: "check",
11
+ jobDisplayName: "Root Check",
4
12
  runner: "ubuntu-latest",
13
+ timeoutMinutes: 30,
5
14
  };
6
15
  const pnpmTaskLayer = {
7
- installCommand: "pnpm install",
16
+ installCommand: "pnpm install --frozen-lockfile",
8
17
  checkCommand: "pnpm run check",
9
18
  };
10
- export function projectCheckWorkflow(options) {
19
+ /** Capability facts for the Foundation-owned GitHub Actions Template Source. */
20
+ export function projectCheckWorkflowPlan(options) {
11
21
  const capability = options.capability ?? defaultCiCapability;
12
22
  const environmentPreparation = {
13
23
  nodeFromPackageMetadata: true,
24
+ packageManagerFromPackageMetadata: true,
25
+ pnpmStoreCache: true,
14
26
  ...options.environmentPreparation,
15
27
  };
16
28
  const taskLayer = options.taskLayer ?? pnpmTaskLayer;
17
29
  const hasDeploymentTask = options.hasDeploymentTask === true;
18
30
  const needsDocker = (options.deploymentEnvironmentNeeds ?? []).some((need) => need.kind === "docker-engine");
19
- const lines = [
20
- `name: ${capability.workflowName}`,
21
- "",
22
- "on:",
23
- " pull_request:",
24
- " push:",
25
- " branches:",
26
- " - main",
27
- "",
28
- "jobs:",
29
- ` ${capability.jobName}:`,
30
- ` runs-on: ${capability.runner}`,
31
- ...(hasDeploymentTask
32
- ? [" strategy:", " matrix:", " check: [root, deployment]"]
33
- : []),
34
- " steps:",
35
- " - uses: actions/checkout@v7",
36
- ];
37
- if (environmentPreparation.nodeFromPackageMetadata) {
38
- lines.push(" - uses: actions/setup-node@v7", " with:", " node-version-file: package.json", " - run: corepack enable");
39
- }
40
- if (hasDeploymentTask && needsDocker) {
41
- lines.push(" - uses: docker/setup-buildx-action@v3", " if: matrix.check == 'deployment'");
31
+ if (hasDeploymentTask && !needsDocker) {
32
+ throw new Error("Deployment Check requires a Docker environment need");
42
33
  }
43
- lines.push(` - run: ${taskLayer.installCommand}`);
44
- lines.push(` - run: ${taskLayer.checkCommand}`);
45
- if (hasDeploymentTask) {
46
- lines.push(" if: matrix.check == 'root'");
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");
47
81
  }
82
+ const hasDiagnostics = validatedDiagnosticArtifacts({
83
+ packagePaths: options.packagePaths,
84
+ artifacts: options.diagnosticArtifacts ?? [],
85
+ }).length > 0;
48
86
  if (hasDeploymentTask) {
49
- lines.push(" - run: pnpm run check:deployment", " if: matrix.check == 'deployment'");
87
+ return hasDiagnostics
88
+ ? ".github/workflows/check-deployment-diagnostics.yml"
89
+ : ".github/workflows/check-deployment.yml";
50
90
  }
51
- lines.push("");
52
- return lines.join("\n");
91
+ return hasDiagnostics
92
+ ? ".github/workflows/check-diagnostics.yml"
93
+ : ".github/workflows/check.yml";
53
94
  }
54
- /** Limited substitutions for the Foundation-owned workflow Template Source. */
95
+ /** Limited, validated facts for static Foundation diagnostic staging. */
55
96
  export function projectCheckWorkflowTemplateReplacements(options) {
56
- const hasDeploymentTask = options.hasDeploymentTask === true;
57
- const needsDocker = (options.deploymentEnvironmentNeeds ?? []).some((need) => need.kind === "docker-engine");
97
+ const declarations = validatedDiagnosticArtifacts({
98
+ packagePaths: options.packagePaths,
99
+ artifacts: options.diagnosticArtifacts ?? [],
100
+ });
101
+ if (declarations.length === 0)
102
+ return {};
58
103
  return {
59
- DEPLOYMENT_MATRIX: hasDeploymentTask
60
- ? "\n strategy:\n matrix:\n check: [root, deployment]"
61
- : "",
62
- DEPLOYMENT_DOCKER_PREPARATION: hasDeploymentTask && needsDocker
63
- ? "\n - uses: docker/setup-buildx-action@v3\n if: matrix.check == 'deployment'"
64
- : "",
65
- ROOT_CHECK_CONDITION: hasDeploymentTask
66
- ? "\n if: matrix.check == 'root'"
67
- : "",
68
- DEPLOYMENT_CHECK: hasDeploymentTask
69
- ? "\n - run: pnpm run check:deployment\n if: matrix.check == 'deployment'"
70
- : "",
104
+ DIAGNOSTIC_OWNER_PATHS: declarations
105
+ .map((declaration) => declaration.owner.path)
106
+ .join("\n "),
71
107
  };
72
108
  }
73
109
  export function projectDependabotConfig(policy) {
@@ -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>;
@@ -589,9 +589,170 @@ function pushSourceBackedOperationViolations(options) {
589
589
  });
590
590
  }
591
591
  }
592
- export async function checkTemplateSourceBoundary({ projections, templateSourceContexts = [], manifestReferencedSourceFiles = [], }) {
592
+ const workflowTemplateVariants = new Map([
593
+ [
594
+ ".github/workflows/check.yml",
595
+ { operationKind: "copyFile", replacementKeys: [] },
596
+ ],
597
+ [
598
+ ".github/workflows/check-deployment.yml",
599
+ { operationKind: "copyFile", replacementKeys: [] },
600
+ ],
601
+ [
602
+ ".github/workflows/check-diagnostics.yml",
603
+ {
604
+ operationKind: "writeTextTemplate",
605
+ replacementKeys: ["DIAGNOSTIC_OWNER_PATHS"],
606
+ },
607
+ ],
608
+ [
609
+ ".github/workflows/check-deployment-diagnostics.yml",
610
+ {
611
+ operationKind: "writeTextTemplate",
612
+ replacementKeys: ["DIAGNOSTIC_OWNER_PATHS"],
613
+ },
614
+ ],
615
+ ]);
616
+ function hasExactReplacementKeys(replacements, expectedKeys) {
617
+ const keys = Object.keys(replacements).toSorted();
618
+ const expected = [...expectedKeys].toSorted();
619
+ return (keys.length === expected.length &&
620
+ keys.every((key, index) => key === expected[index]));
621
+ }
622
+ const reservedWorkspaceCollections = new Set([
623
+ ".git",
624
+ ".github",
625
+ ".devcontainer",
626
+ ".template",
627
+ "node_modules",
628
+ "dist",
629
+ "target",
630
+ ]);
631
+ function isSafePackageBoundaryPath(value) {
632
+ return (/^[a-z0-9][a-z0-9-]*\/[a-z0-9][a-z0-9-]*$/u.test(value) &&
633
+ !reservedWorkspaceCollections.has(value.split("/", 1)[0]));
634
+ }
635
+ /**
636
+ * Independent oracle for the Foundation workflow's limited substitution.
637
+ * It deliberately consumes raw Blueprint and Package Contribution facts rather
638
+ * than the production CI artifact composer or its composed plan output.
639
+ */
640
+ function expectedDiagnosticOwnerPaths(options) {
641
+ const blueprintPackagePaths = options.blueprint.packages.map((packageDefinition) => packageDefinition.path);
642
+ if (!blueprintPackagePaths.every(isSafePackageBoundaryPath)) {
643
+ return { valid: false, value: "" };
644
+ }
645
+ const blueprintOwners = new Set(blueprintPackagePaths);
646
+ const owners = new Set();
647
+ for (const declaration of options.diagnosticArtifactDeclarations) {
648
+ if (!isRecord(declaration) ||
649
+ !hasExactKeys(declaration, ["kind", "owner"])) {
650
+ return { valid: false, value: "" };
651
+ }
652
+ if (declaration.kind !== "playwright" || !isRecord(declaration.owner)) {
653
+ return { valid: false, value: "" };
654
+ }
655
+ if (!hasExactKeys(declaration.owner, ["kind", "path"])) {
656
+ return { valid: false, value: "" };
657
+ }
658
+ const ownerPath = declaration.owner.path;
659
+ if (declaration.owner.kind !== "package-boundary" ||
660
+ typeof ownerPath !== "string" ||
661
+ !isSafePackageBoundaryPath(ownerPath) ||
662
+ !blueprintOwners.has(ownerPath)) {
663
+ return { valid: false, value: "" };
664
+ }
665
+ owners.add(ownerPath);
666
+ }
667
+ return {
668
+ valid: true,
669
+ value: [...owners].toSorted().join("\n "),
670
+ };
671
+ }
672
+ function sourceFileForWorkflowOperation(options) {
673
+ return options.operation?.provenance?.plannerSourceFile ?? options.fallback;
674
+ }
675
+ function findWorkflowOperationOwners(sourceFile, operation) {
676
+ if (operation === undefined)
677
+ return ["<unknown>"];
678
+ const targetPath = operationTarget(operation);
679
+ if (targetPath === undefined)
680
+ return ["<unknown>"];
681
+ const expectedTargetPath = targetPath;
682
+ const operationKind = operation.kind;
683
+ const owners = new Set();
684
+ function visit(node) {
685
+ if (ts.isObjectLiteralExpression(node)) {
686
+ if (propertyStringValue(node, "kind") === operationKind &&
687
+ propertyPathMatches(node, "to", expectedTargetPath)) {
688
+ owners.add(owningFunctionName(node));
689
+ }
690
+ }
691
+ ts.forEachChild(node, visit);
692
+ }
693
+ visit(sourceFile);
694
+ return owners.size === 0 ? ["<unknown>"] : [...owners];
695
+ }
696
+ async function protectedWorkflowProjectionViolations(plans) {
697
+ const violations = [];
698
+ for (const plan of plans) {
699
+ const matchingOperations = plan.operations.filter((operation) => operationTarget(operation) === plan.generatedPath);
700
+ const operation = matchingOperations[0];
701
+ const sourceFilePath = sourceFileForWorkflowOperation({
702
+ operation,
703
+ fallback: plan.sourceFilePath,
704
+ });
705
+ const sourceText = await readFile(sourceFilePath, "utf8");
706
+ const sourceFile = ts.createSourceFile(sourceFilePath, sourceText, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
707
+ const expectedDiagnosticOwners = expectedDiagnosticOwnerPaths(plan);
708
+ const variant = operation !== undefined &&
709
+ (operation.kind === "copyFile" || operation.kind === "writeTextTemplate")
710
+ ? workflowTemplateVariants.get(operation.from)
711
+ : undefined;
712
+ const replacementValid = operation?.kind === "writeTextTemplate" &&
713
+ hasExactReplacementKeys(operation.replacements, [
714
+ "DIAGNOSTIC_OWNER_PATHS",
715
+ ]) &&
716
+ operation.replacements.DIAGNOSTIC_OWNER_PATHS ===
717
+ expectedDiagnosticOwners.value;
718
+ const sourceBackedExactlyOnce = matchingOperations.length === 1 &&
719
+ operation !== undefined &&
720
+ (operation.kind === "copyFile" || operation.kind === "writeTextTemplate");
721
+ const diagnosticsExpected = expectedDiagnosticOwners.value !== "";
722
+ const operationValid = expectedDiagnosticOwners.valid &&
723
+ sourceBackedExactlyOnce &&
724
+ variant !== undefined &&
725
+ operation?.kind === variant.operationKind &&
726
+ (operation.kind === "copyFile"
727
+ ? variant.replacementKeys.length === 0 && !diagnosticsExpected
728
+ : variant.replacementKeys.length === 1 &&
729
+ diagnosticsExpected &&
730
+ replacementValid);
731
+ if (operationValid)
732
+ continue;
733
+ for (const owningFunction of findWorkflowOperationOwners(sourceFile, operation)) {
734
+ violations.push({
735
+ preset: plan.name,
736
+ generatedPath: plan.generatedPath,
737
+ owningFunction,
738
+ operationKind: operation?.kind ?? "writeTextTemplate",
739
+ sourceFilePath,
740
+ ...(plan.definitionName === undefined
741
+ ? {}
742
+ : { definitionName: plan.definitionName }),
743
+ ...(plan.planningContribution === undefined
744
+ ? {}
745
+ : { planningContribution: plan.planningContribution }),
746
+ ownershipRule: "Foundation workflow projections require exactly one source-backed template operation with closed diagnostic owner facts",
747
+ });
748
+ }
749
+ }
750
+ return violations;
751
+ }
752
+ export async function checkTemplateSourceBoundary({ projections, templateSourceContexts = [], manifestReferencedSourceFiles = [], protectedWorkflowPlans = [], }) {
593
753
  const manifestReferencedSourceFileSet = new Set(manifestReferencedSourceFiles.map((sourceFile) => path.resolve(sourceFile)));
594
754
  const violations = [];
755
+ violations.push(...(await protectedWorkflowProjectionViolations(protectedWorkflowPlans)));
595
756
  for (const projection of projections) {
596
757
  const sourceText = await readFile(projection.sourceFilePath, "utf8");
597
758
  const sourceFile = ts.createSourceFile(projection.sourceFilePath, sourceText, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ykdz/template",
3
- "version": "0.0.32",
3
+ "version": "0.0.33",
4
4
  "private": false,
5
5
  "description": "Project template generator for YKDZ starter projects.",
6
6
  "keywords": [
@@ -60,8 +60,8 @@
60
60
  "semver": "^7.8.5",
61
61
  "typescript": "npm:typescript@^6.0.3",
62
62
  "valibot": "^1.4.2",
63
- "@ykdz/template-core": "0.0.0",
64
- "@ykdz/template-builtin-presets": "0.0.0"
63
+ "@ykdz/template-builtin-presets": "0.0.0",
64
+ "@ykdz/template-core": "0.0.0"
65
65
  },
66
66
  "devDependencies": {
67
67
  "@types/node": "^24.13.3",
@@ -1,20 +0,0 @@
1
- name: Check
2
-
3
- on:
4
- pull_request:
5
- push:
6
- branches:
7
- - main
8
-
9
- jobs:
10
- check:
11
- runs-on: ubuntu-latest
12
- steps:
13
- - uses: actions/checkout@v7
14
- - uses: actions/setup-node@v7
15
- with:
16
- node-version-file: package.json
17
- - run: corepack enable
18
- - run: pnpm install
19
- - run: pnpm --filter ./apps/web exec playwright install --with-deps chromium
20
- - run: pnpm run check
@@ -1,23 +0,0 @@
1
- name: Check
2
-
3
- on:
4
- pull_request:
5
- push:
6
- branches:
7
- - main
8
-
9
- jobs:
10
- check:
11
- runs-on: ubuntu-latest
12
- steps:
13
- - uses: actions/checkout@v7
14
- - uses: actions/setup-node@v7
15
- with:
16
- node-version-file: package.json
17
- - run: corepack enable
18
- - uses: dtolnay/rust-toolchain@stable
19
- with:
20
- components: rustfmt, clippy
21
- - uses: Swatinem/rust-cache@v2
22
- - run: pnpm install
23
- - run: pnpm run check
@@ -1,19 +0,0 @@
1
- name: Check
2
-
3
- on:
4
- pull_request:
5
- push:
6
- branches:
7
- - main
8
-
9
- jobs:
10
- check:
11
- runs-on: ubuntu-latest{{DEPLOYMENT_MATRIX}}
12
- steps:
13
- - uses: actions/checkout@v7
14
- - uses: actions/setup-node@v7
15
- with:
16
- node-version-file: package.json
17
- - run: corepack enable{{DEPLOYMENT_DOCKER_PREPARATION}}
18
- - run: pnpm install
19
- - run: pnpm run check{{ROOT_CHECK_CONDITION}}{{DEPLOYMENT_CHECK}}