@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
|
@@ -479,7 +479,28 @@ function isStructuralTsconfigConfigValue(value) {
|
|
|
479
479
|
function isDevelopmentContainerPlanValue(expression) {
|
|
480
480
|
return isPropertyAccess(expression, "developmentContainer", "devcontainer");
|
|
481
481
|
}
|
|
482
|
+
function isDevelopmentContainerToolLayerMergeValue(value) {
|
|
483
|
+
if (!isRecord(value) ||
|
|
484
|
+
!(hasExactKeys(value, ["build"]) || hasExactKeys(value, ["build", "mounts"])) ||
|
|
485
|
+
!isRecord(value.build) ||
|
|
486
|
+
!hasExactKeys(value.build, ["args"]) ||
|
|
487
|
+
!isRecord(value.build.args) ||
|
|
488
|
+
!Object.values(value.build.args).every((argument) => typeof argument === "string")) {
|
|
489
|
+
return false;
|
|
490
|
+
}
|
|
491
|
+
return (value.mounts === undefined ||
|
|
492
|
+
(Array.isArray(value.mounts) &&
|
|
493
|
+
value.mounts.every((mount) => isRecord(mount) &&
|
|
494
|
+
hasExactKeys(mount, ["type", "source", "target"]) &&
|
|
495
|
+
(mount.type === "bind" || mount.type === "volume") &&
|
|
496
|
+
typeof mount.source === "string" &&
|
|
497
|
+
typeof mount.target === "string")));
|
|
498
|
+
}
|
|
482
499
|
function isAllowedStructuralMachineDeclaration(sourceFile, operation) {
|
|
500
|
+
if (operation.kind === "mergeJson" &&
|
|
501
|
+
operation.to === ".devcontainer/devcontainer.json") {
|
|
502
|
+
return isDevelopmentContainerToolLayerMergeValue(operation.value);
|
|
503
|
+
}
|
|
483
504
|
if (operation.kind !== "writeJson") {
|
|
484
505
|
return false;
|
|
485
506
|
}
|
|
@@ -568,9 +589,170 @@ function pushSourceBackedOperationViolations(options) {
|
|
|
568
589
|
});
|
|
569
590
|
}
|
|
570
591
|
}
|
|
571
|
-
|
|
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 = [], }) {
|
|
572
753
|
const manifestReferencedSourceFileSet = new Set(manifestReferencedSourceFiles.map((sourceFile) => path.resolve(sourceFile)));
|
|
573
754
|
const violations = [];
|
|
755
|
+
violations.push(...(await protectedWorkflowProjectionViolations(protectedWorkflowPlans)));
|
|
574
756
|
for (const projection of projections) {
|
|
575
757
|
const sourceText = await readFile(projection.sourceFilePath, "utf8");
|
|
576
758
|
const sourceFile = ts.createSourceFile(projection.sourceFilePath, sourceText, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
package/package.json
CHANGED
|
@@ -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{{RUST_CI_PREPARATION}}{{DEPLOYMENT_DOCKER_PREPARATION}}
|
|
18
|
-
- run: pnpm install{{CHECK_ENVIRONMENT_PREPARATION}}
|
|
19
|
-
- run: pnpm run check{{ROOT_CHECK_CONDITION}}{{DEPLOYMENT_CHECK}}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "{{PROJECT_NAME}}",
|
|
3
|
-
"build": {
|
|
4
|
-
"dockerfile": "Dockerfile",
|
|
5
|
-
"args": {
|
|
6
|
-
"NODE_VERSION": "{{NODE_LTS_MAJOR}}",
|
|
7
|
-
"PACKAGE_MANAGER_PIN": "{{PACKAGE_MANAGER_PIN}}",
|
|
8
|
-
"RUST_TOOLCHAIN": "{{RUST_TOOLCHAIN}}"
|
|
9
|
-
}
|
|
10
|
-
},
|
|
11
|
-
"customizations": {
|
|
12
|
-
"vscode": {
|
|
13
|
-
"extensions": ["rust-lang.rust-analyzer", "tamasfe.even-better-toml"]
|
|
14
|
-
}
|
|
15
|
-
},
|
|
16
|
-
"mounts": [
|
|
17
|
-
"source=${localWorkspaceFolderBasename}-cargo-registry,target=/usr/local/cargo/registry,type=volume",
|
|
18
|
-
"source=${localWorkspaceFolderBasename}-cargo-git,target=/usr/local/cargo/git,type=volume",
|
|
19
|
-
"source=${localWorkspaceFolderBasename}-target,target=${containerWorkspaceFolder}/target,type=volume"
|
|
20
|
-
]
|
|
21
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
ARG RUST_TOOLCHAIN
|
|
2
|
-
ENV RUSTUP_HOME=/usr/local/rustup
|
|
3
|
-
ENV CARGO_HOME=/usr/local/cargo
|
|
4
|
-
ENV PATH="/usr/local/cargo/bin:${PATH}"
|
|
5
|
-
|
|
6
|
-
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
7
|
-
ca-certificates \
|
|
8
|
-
curl \
|
|
9
|
-
gcc \
|
|
10
|
-
git \
|
|
11
|
-
libc6-dev \
|
|
12
|
-
&& rm -rf /var/lib/apt/lists/* \
|
|
13
|
-
&& git config --system init.defaultBranch main
|
|
14
|
-
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \
|
|
15
|
-
sh -s -- -y --profile minimal --default-toolchain none \
|
|
16
|
-
&& rustup toolchain install ${RUST_TOOLCHAIN} --profile minimal --component rustfmt --component clippy \
|
|
17
|
-
&& rustup default ${RUST_TOOLCHAIN} \
|
|
18
|
-
&& chmod -R a+w ${RUSTUP_HOME} ${CARGO_HOME}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "{{PROJECT_NAME}}",
|
|
3
|
-
"build": {
|
|
4
|
-
"dockerfile": "Dockerfile",
|
|
5
|
-
"args": {
|
|
6
|
-
"NODE_VERSION": "{{NODE_LTS_MAJOR}}",
|
|
7
|
-
"PACKAGE_MANAGER_PIN": "{{PACKAGE_MANAGER_PIN}}",
|
|
8
|
-
"RUST_TOOLCHAIN": "{{RUST_TOOLCHAIN}}"
|
|
9
|
-
}
|
|
10
|
-
},
|
|
11
|
-
"customizations": {
|
|
12
|
-
"vscode": {
|
|
13
|
-
"extensions": ["rust-lang.rust-analyzer", "tamasfe.even-better-toml"]
|
|
14
|
-
}
|
|
15
|
-
},
|
|
16
|
-
"mounts": [
|
|
17
|
-
"source=${localWorkspaceFolderBasename}-cargo-registry,target=/usr/local/cargo/registry,type=volume",
|
|
18
|
-
"source=${localWorkspaceFolderBasename}-cargo-git,target=/usr/local/cargo/git,type=volume",
|
|
19
|
-
"source=${localWorkspaceFolderBasename}-target,target=${containerWorkspaceFolder}/target,type=volume"
|
|
20
|
-
]
|
|
21
|
-
}
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import type { TemplateSourceHandle, WriteTextFromFragmentsOperation } from "./renderer.ts";
|
|
2
|
-
export type DevelopmentContainerNodePnpmLayer = {
|
|
3
|
-
readonly kind: "node-pnpm";
|
|
4
|
-
readonly nodeVersion: string;
|
|
5
|
-
readonly packageManagerPin: string;
|
|
6
|
-
};
|
|
7
|
-
export type DevelopmentContainerBrowserTestLayer = {
|
|
8
|
-
readonly kind: "browser-test";
|
|
9
|
-
readonly playwrightCliPackage: string;
|
|
10
|
-
};
|
|
11
|
-
export type DevelopmentContainerRustLayer = {
|
|
12
|
-
readonly kind: "rust";
|
|
13
|
-
readonly toolchain: string;
|
|
14
|
-
};
|
|
15
|
-
export type DevelopmentContainerShellCheckLayer = {
|
|
16
|
-
readonly kind: "shellcheck";
|
|
17
|
-
};
|
|
18
|
-
type DevelopmentContainerCapabilityLayer = DevelopmentContainerBrowserTestLayer | DevelopmentContainerShellCheckLayer | DevelopmentContainerRustLayer;
|
|
19
|
-
export type DevelopmentContainerDockerfileFragments = {
|
|
20
|
-
readonly source: TemplateSourceHandle;
|
|
21
|
-
readonly nodePnpm: {
|
|
22
|
-
readonly from: string;
|
|
23
|
-
readonly text: string;
|
|
24
|
-
};
|
|
25
|
-
readonly browserTest?: {
|
|
26
|
-
readonly from: string;
|
|
27
|
-
readonly text: string;
|
|
28
|
-
} | undefined;
|
|
29
|
-
readonly shellCheck?: {
|
|
30
|
-
readonly from: string;
|
|
31
|
-
readonly text: string;
|
|
32
|
-
} | undefined;
|
|
33
|
-
readonly rust?: {
|
|
34
|
-
readonly from: string;
|
|
35
|
-
readonly text: string;
|
|
36
|
-
} | undefined;
|
|
37
|
-
};
|
|
38
|
-
export type DevelopmentContainerPlan = {
|
|
39
|
-
readonly devcontainer: Record<string, unknown>;
|
|
40
|
-
readonly dockerfile: string;
|
|
41
|
-
readonly dockerfileOperation?: WriteTextFromFragmentsOperation;
|
|
42
|
-
};
|
|
43
|
-
export type DevelopmentContainerDockerfileLayer = {
|
|
44
|
-
readonly kind: "base" | "capability";
|
|
45
|
-
readonly name: string;
|
|
46
|
-
readonly text: string;
|
|
47
|
-
};
|
|
48
|
-
export declare function composeDevelopmentContainerDockerfile(options: {
|
|
49
|
-
readonly layers: readonly DevelopmentContainerDockerfileLayer[];
|
|
50
|
-
}): string;
|
|
51
|
-
export declare function nodePnpmToolLayer(options: {
|
|
52
|
-
readonly nodeVersion: string;
|
|
53
|
-
readonly packageManagerPin: string;
|
|
54
|
-
}): DevelopmentContainerNodePnpmLayer;
|
|
55
|
-
export declare function browserTestToolLayer(): DevelopmentContainerBrowserTestLayer;
|
|
56
|
-
export declare function shellCheckToolLayer(): DevelopmentContainerShellCheckLayer;
|
|
57
|
-
export declare function rustToolLayer(options?: {
|
|
58
|
-
readonly toolchain?: string;
|
|
59
|
-
}): DevelopmentContainerRustLayer;
|
|
60
|
-
export declare function dockerfileFirstNodePnpmDevcontainer(options: {
|
|
61
|
-
readonly name: string;
|
|
62
|
-
readonly layer: DevelopmentContainerNodePnpmLayer;
|
|
63
|
-
readonly extensions: readonly string[];
|
|
64
|
-
readonly settings?: Record<string, unknown> | undefined;
|
|
65
|
-
}): DevelopmentContainerPlan;
|
|
66
|
-
export declare function checkedDockerfileFirstNodePnpmDevcontainer(options: {
|
|
67
|
-
readonly name: string;
|
|
68
|
-
readonly layer: DevelopmentContainerNodePnpmLayer;
|
|
69
|
-
readonly dockerfileFragments: DevelopmentContainerDockerfileFragments;
|
|
70
|
-
readonly additionalLayers?: readonly DevelopmentContainerCapabilityLayer[] | undefined;
|
|
71
|
-
readonly extensions: readonly string[];
|
|
72
|
-
readonly settings?: Record<string, unknown> | undefined;
|
|
73
|
-
readonly mounts?: readonly string[] | undefined;
|
|
74
|
-
}): DevelopmentContainerPlan;
|
|
75
|
-
export declare function dockerfileFirstRustPnpmDevcontainer(options: {
|
|
76
|
-
readonly name: string;
|
|
77
|
-
readonly nodeLayer: DevelopmentContainerNodePnpmLayer;
|
|
78
|
-
readonly rustLayer: DevelopmentContainerRustLayer;
|
|
79
|
-
readonly dockerfileFragments: DevelopmentContainerDockerfileFragments;
|
|
80
|
-
readonly extensions: readonly string[];
|
|
81
|
-
readonly settings?: Record<string, unknown> | undefined;
|
|
82
|
-
}): DevelopmentContainerPlan;
|
|
83
|
-
export {};
|
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
import { loadTemplateDependencyCatalog } from "./dependency-catalog.js";
|
|
2
|
-
function requireDockerfileFragment(fragments, name) {
|
|
3
|
-
const fragment = fragments[name];
|
|
4
|
-
if (fragment === undefined) {
|
|
5
|
-
throw new Error(`Development Container Dockerfile fragment is missing: ${name}`);
|
|
6
|
-
}
|
|
7
|
-
return fragment;
|
|
8
|
-
}
|
|
9
|
-
const runtimeDockerfileInstructions = new Set([
|
|
10
|
-
"CMD",
|
|
11
|
-
"ENTRYPOINT",
|
|
12
|
-
"EXPOSE",
|
|
13
|
-
"HEALTHCHECK",
|
|
14
|
-
"STOPSIGNAL",
|
|
15
|
-
"VOLUME",
|
|
16
|
-
]);
|
|
17
|
-
function dockerfileInstructions(text) {
|
|
18
|
-
return text
|
|
19
|
-
.split(/\r?\n/)
|
|
20
|
-
.map((line) => line.trim())
|
|
21
|
-
.filter((line) => line.length > 0 && !line.startsWith("#"))
|
|
22
|
-
.map((line) => line.match(/^([A-Za-z]+)/)?.[1]?.toUpperCase())
|
|
23
|
-
.filter((instruction) => instruction !== undefined);
|
|
24
|
-
}
|
|
25
|
-
export function composeDevelopmentContainerDockerfile(options) {
|
|
26
|
-
const baseImageInstructionCount = options.layers.reduce((count, layer) => count +
|
|
27
|
-
dockerfileInstructions(layer.text).filter((instruction) => instruction === "FROM").length, 0);
|
|
28
|
-
if (baseImageInstructionCount !== 1) {
|
|
29
|
-
throw new Error(`Development Container Dockerfile must have exactly one base layer with a FROM instruction; found ${baseImageInstructionCount}.`);
|
|
30
|
-
}
|
|
31
|
-
for (const layer of options.layers) {
|
|
32
|
-
const instructions = dockerfileInstructions(layer.text);
|
|
33
|
-
if (layer.kind === "capability" && instructions.includes("FROM")) {
|
|
34
|
-
throw new Error(`${layer.name} capability layer must not supply a Dockerfile FROM instruction.`);
|
|
35
|
-
}
|
|
36
|
-
if (layer.kind !== "capability") {
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
for (const instruction of instructions) {
|
|
40
|
-
if (runtimeDockerfileInstructions.has(instruction)) {
|
|
41
|
-
throw new Error(`${layer.name} must not use runtime Dockerfile instruction ${instruction}.`);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return options.layers.map((layer) => layer.text.trimEnd()).join("\n\n");
|
|
46
|
-
}
|
|
47
|
-
export function nodePnpmToolLayer(options) {
|
|
48
|
-
return {
|
|
49
|
-
kind: "node-pnpm",
|
|
50
|
-
nodeVersion: options.nodeVersion,
|
|
51
|
-
packageManagerPin: options.packageManagerPin,
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
export function browserTestToolLayer() {
|
|
55
|
-
const playwrightTestVersion = loadTemplateDependencyCatalog()["@playwright/test"];
|
|
56
|
-
if (playwrightTestVersion === undefined) {
|
|
57
|
-
throw new Error("Template Dependency Catalog is missing dependency: @playwright/test");
|
|
58
|
-
}
|
|
59
|
-
return {
|
|
60
|
-
kind: "browser-test",
|
|
61
|
-
playwrightCliPackage: `@playwright/test@${playwrightTestVersion}`,
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
export function shellCheckToolLayer() {
|
|
65
|
-
return { kind: "shellcheck" };
|
|
66
|
-
}
|
|
67
|
-
export function rustToolLayer(options = {}) {
|
|
68
|
-
return { kind: "rust", toolchain: options.toolchain ?? "stable" };
|
|
69
|
-
}
|
|
70
|
-
function checkedNodePnpmDockerfile(options) {
|
|
71
|
-
return composeDevelopmentContainerDockerfile({
|
|
72
|
-
layers: [
|
|
73
|
-
{
|
|
74
|
-
kind: "base",
|
|
75
|
-
name: "node-pnpm",
|
|
76
|
-
text: options.dockerfileFragments.nodePnpm.text,
|
|
77
|
-
},
|
|
78
|
-
...(options.additionalLayers ?? []).map((layer) => {
|
|
79
|
-
switch (layer.kind) {
|
|
80
|
-
case "browser-test":
|
|
81
|
-
return {
|
|
82
|
-
kind: "capability",
|
|
83
|
-
name: "browser-test",
|
|
84
|
-
text: requireDockerfileFragment(options.dockerfileFragments, "browserTest").text,
|
|
85
|
-
};
|
|
86
|
-
case "shellcheck":
|
|
87
|
-
return {
|
|
88
|
-
kind: "capability",
|
|
89
|
-
name: "shellcheck",
|
|
90
|
-
text: requireDockerfileFragment(options.dockerfileFragments, "shellCheck").text,
|
|
91
|
-
};
|
|
92
|
-
case "rust":
|
|
93
|
-
return {
|
|
94
|
-
kind: "capability",
|
|
95
|
-
name: "rust",
|
|
96
|
-
text: requireDockerfileFragment(options.dockerfileFragments, "rust").text,
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
}),
|
|
100
|
-
],
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
function checkedNodePnpmDockerfileFragments(options) {
|
|
104
|
-
return [
|
|
105
|
-
{
|
|
106
|
-
source: options.dockerfileFragments.source,
|
|
107
|
-
from: options.dockerfileFragments.nodePnpm.from,
|
|
108
|
-
},
|
|
109
|
-
...(options.additionalLayers ?? []).map((layer) => {
|
|
110
|
-
switch (layer.kind) {
|
|
111
|
-
case "browser-test":
|
|
112
|
-
return {
|
|
113
|
-
source: options.dockerfileFragments.source,
|
|
114
|
-
from: requireDockerfileFragment(options.dockerfileFragments, "browserTest").from,
|
|
115
|
-
};
|
|
116
|
-
case "shellcheck":
|
|
117
|
-
return {
|
|
118
|
-
source: options.dockerfileFragments.source,
|
|
119
|
-
from: requireDockerfileFragment(options.dockerfileFragments, "shellCheck").from,
|
|
120
|
-
};
|
|
121
|
-
case "rust":
|
|
122
|
-
return {
|
|
123
|
-
source: options.dockerfileFragments.source,
|
|
124
|
-
from: requireDockerfileFragment(options.dockerfileFragments, "rust")
|
|
125
|
-
.from,
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
}),
|
|
129
|
-
];
|
|
130
|
-
}
|
|
131
|
-
function browserTestBuildArgs(layers = []) {
|
|
132
|
-
const browserTestLayer = layers.find((layer) => layer.kind === "browser-test");
|
|
133
|
-
return browserTestLayer === undefined
|
|
134
|
-
? {}
|
|
135
|
-
: { PLAYWRIGHT_CLI_PACKAGE: browserTestLayer.playwrightCliPackage };
|
|
136
|
-
}
|
|
137
|
-
function rustBuildArgs(layers = []) {
|
|
138
|
-
const rustLayer = layers.find((layer) => layer.kind === "rust");
|
|
139
|
-
return rustLayer === undefined ? {} : { RUST_TOOLCHAIN: rustLayer.toolchain };
|
|
140
|
-
}
|
|
141
|
-
export function dockerfileFirstNodePnpmDevcontainer(options) {
|
|
142
|
-
return {
|
|
143
|
-
devcontainer: {
|
|
144
|
-
name: options.name,
|
|
145
|
-
build: {
|
|
146
|
-
dockerfile: "Dockerfile",
|
|
147
|
-
args: {
|
|
148
|
-
NODE_VERSION: options.layer.nodeVersion,
|
|
149
|
-
PACKAGE_MANAGER_PIN: options.layer.packageManagerPin,
|
|
150
|
-
},
|
|
151
|
-
},
|
|
152
|
-
customizations: {
|
|
153
|
-
vscode: {
|
|
154
|
-
extensions: options.extensions,
|
|
155
|
-
...(options.settings ? { settings: options.settings } : {}),
|
|
156
|
-
},
|
|
157
|
-
},
|
|
158
|
-
},
|
|
159
|
-
dockerfile: [
|
|
160
|
-
`FROM mcr.microsoft.com/devcontainers/typescript-node:${options.layer.nodeVersion}`,
|
|
161
|
-
"",
|
|
162
|
-
'SHELL ["/bin/bash", "-o", "pipefail", "-c"]',
|
|
163
|
-
`RUN corepack enable && corepack prepare ${options.layer.packageManagerPin} --activate`,
|
|
164
|
-
"",
|
|
165
|
-
].join("\n"),
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
export function checkedDockerfileFirstNodePnpmDevcontainer(options) {
|
|
169
|
-
return {
|
|
170
|
-
devcontainer: {
|
|
171
|
-
name: options.name,
|
|
172
|
-
build: {
|
|
173
|
-
dockerfile: "Dockerfile",
|
|
174
|
-
args: {
|
|
175
|
-
NODE_VERSION: options.layer.nodeVersion,
|
|
176
|
-
PACKAGE_MANAGER_PIN: options.layer.packageManagerPin,
|
|
177
|
-
...browserTestBuildArgs(options.additionalLayers),
|
|
178
|
-
...rustBuildArgs(options.additionalLayers),
|
|
179
|
-
},
|
|
180
|
-
},
|
|
181
|
-
customizations: {
|
|
182
|
-
vscode: {
|
|
183
|
-
extensions: options.extensions,
|
|
184
|
-
...(options.settings ? { settings: options.settings } : {}),
|
|
185
|
-
},
|
|
186
|
-
},
|
|
187
|
-
...(options.mounts ? { mounts: options.mounts } : {}),
|
|
188
|
-
},
|
|
189
|
-
dockerfile: checkedNodePnpmDockerfile({
|
|
190
|
-
dockerfileFragments: options.dockerfileFragments,
|
|
191
|
-
additionalLayers: options.additionalLayers,
|
|
192
|
-
}),
|
|
193
|
-
dockerfileOperation: {
|
|
194
|
-
kind: "writeTextFromFragments",
|
|
195
|
-
to: ".devcontainer/Dockerfile",
|
|
196
|
-
fragments: checkedNodePnpmDockerfileFragments({
|
|
197
|
-
dockerfileFragments: options.dockerfileFragments,
|
|
198
|
-
additionalLayers: options.additionalLayers,
|
|
199
|
-
}),
|
|
200
|
-
},
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
export function dockerfileFirstRustPnpmDevcontainer(options) {
|
|
204
|
-
return checkedDockerfileFirstNodePnpmDevcontainer({
|
|
205
|
-
name: options.name,
|
|
206
|
-
layer: options.nodeLayer,
|
|
207
|
-
dockerfileFragments: options.dockerfileFragments,
|
|
208
|
-
additionalLayers: [options.rustLayer],
|
|
209
|
-
extensions: options.extensions,
|
|
210
|
-
settings: options.settings,
|
|
211
|
-
mounts: [
|
|
212
|
-
"source=${localWorkspaceFolderBasename}-cargo-registry,target=/usr/local/cargo/registry,type=volume",
|
|
213
|
-
"source=${localWorkspaceFolderBasename}-cargo-git,target=/usr/local/cargo/git,type=volume",
|
|
214
|
-
"source=${localWorkspaceFolderBasename}-target,target=${containerWorkspaceFolder}/target,type=volume",
|
|
215
|
-
],
|
|
216
|
-
});
|
|
217
|
-
}
|