@ykdz/template 0.0.2 → 0.0.4
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/README.md +1 -1
- package/dist/src/cli.d.ts +2 -0
- package/dist/{cli.js → src/cli.js} +91 -58
- package/dist/{declarations.d.ts → src/declarations.d.ts} +3 -5
- package/dist/{declarations.js → src/declarations.js} +48 -145
- package/dist/src/dependency-catalog.d.ts +18 -0
- package/dist/src/dependency-catalog.js +132 -0
- package/dist/src/devcontainer.d.ts +58 -0
- package/dist/src/devcontainer.js +202 -0
- package/dist/src/editor-customization.d.ts +12 -0
- package/dist/src/editor-customization.js +108 -0
- package/dist/{generation-context.d.ts → src/generation-context.d.ts} +2 -3
- package/dist/{generation-context.js → src/generation-context.js} +4 -1
- package/dist/src/module-graph.d.ts +34 -0
- package/dist/src/module-graph.js +58 -0
- package/dist/{next-step-instructions.d.ts → src/next-step-instructions.d.ts} +2 -4
- package/dist/{next-step-instructions.js → src/next-step-instructions.js} +49 -24
- package/dist/{package-addition.d.ts → src/package-addition.d.ts} +1 -1
- package/dist/src/package-addition.js +343 -0
- package/dist/src/preset-projection.d.ts +66 -0
- package/dist/src/preset-projection.js +7 -0
- package/dist/{project-github.d.ts → src/project-github.d.ts} +3 -9
- package/dist/{project-github.js → src/project-github.js} +27 -42
- package/dist/{renderer.d.ts → src/renderer.d.ts} +16 -2
- package/dist/{renderer.js → src/renderer.js} +95 -12
- package/dist/src/runtime-paths.d.ts +1 -0
- package/dist/src/runtime-paths.js +11 -0
- package/dist/src/template-boundary-check.d.ts +34 -0
- package/dist/src/template-boundary-check.js +448 -0
- package/dist/{toolchain-resolution.d.ts → src/toolchain-resolution.d.ts} +0 -1
- package/dist/{toolchain-resolution.js → src/toolchain-resolution.js} +1 -1
- package/dist/templates/hono-api/projection.d.ts +6 -0
- package/dist/templates/hono-api/projection.js +538 -0
- package/dist/templates/projection-plans.d.ts +7 -0
- package/dist/templates/projection-plans.js +94 -0
- package/dist/templates/registry.d.ts +7 -0
- package/dist/templates/registry.js +42 -0
- package/dist/templates/rust-bin/projection.d.ts +9 -0
- package/dist/templates/rust-bin/projection.js +390 -0
- package/dist/templates/ts-lib/projection.d.ts +6 -0
- package/dist/templates/ts-lib/projection.js +523 -0
- package/dist/templates/vue-app/projection.d.ts +6 -0
- package/dist/templates/vue-app/projection.js +757 -0
- package/dist/templates/vue-hono-app/projection.d.ts +8 -0
- package/dist/templates/vue-hono-app/projection.js +693 -0
- package/package.json +38 -28
- package/pnpm-workspace.yaml +35 -0
- package/templates/hono-api/.github/dependabot.yml +13 -0
- package/templates/rust-bin/.github/dependabot.yml +18 -1
- package/templates/rust-bin/rust-toolchain.toml +3 -0
- package/templates/rust-bin/rustfmt.toml +2 -0
- package/templates/shared/devcontainer/browser-test.Dockerfile +2 -0
- package/templates/shared/devcontainer/node-pnpm.Dockerfile +6 -0
- package/templates/shared/devcontainer/rust.Dockerfile +16 -0
- package/templates/shared/editor-customization/capabilities.json +56 -0
- package/templates/ts-lib/.github/dependabot.yml +13 -0
- package/templates/ts-lib/src/index.ts +5 -1
- package/templates/vue-app/.github/dependabot.yml +13 -0
- package/templates/vue-app/.github/workflows/check.yml +1 -1
- package/templates/vue-app/test/e2e/app.spec.ts +4 -1
- package/templates/vue-hono-app/.github/dependabot.yml +13 -0
- package/templates/vue-hono-app/web/test/e2e/app.spec.ts +4 -1
- package/dist/cli.d.ts +0 -3
- package/dist/cli.d.ts.map +0 -1
- package/dist/declarations.d.ts.map +0 -1
- package/dist/devcontainer.d.ts +0 -17
- package/dist/devcontainer.d.ts.map +0 -1
- package/dist/devcontainer.js +0 -23
- package/dist/generation-context.d.ts.map +0 -1
- package/dist/hono-api.d.ts +0 -3
- package/dist/hono-api.d.ts.map +0 -1
- package/dist/hono-api.js +0 -210
- package/dist/module-graph.d.ts +0 -45
- package/dist/module-graph.d.ts.map +0 -1
- package/dist/module-graph.js +0 -187
- package/dist/next-step-instructions.d.ts.map +0 -1
- package/dist/package-addition.d.ts.map +0 -1
- package/dist/package-addition.js +0 -560
- package/dist/project-github.d.ts.map +0 -1
- package/dist/renderer.d.ts.map +0 -1
- package/dist/rust-bin.d.ts +0 -8
- package/dist/rust-bin.d.ts.map +0 -1
- package/dist/rust-bin.js +0 -208
- package/dist/toolchain-resolution.d.ts.map +0 -1
- package/dist/ts-lib.d.ts +0 -8
- package/dist/ts-lib.d.ts.map +0 -1
- package/dist/ts-lib.js +0 -214
- package/dist/vue-app.d.ts +0 -3
- package/dist/vue-app.d.ts.map +0 -1
- package/dist/vue-app.js +0 -301
- package/dist/vue-hono-app.d.ts +0 -7
- package/dist/vue-hono-app.d.ts.map +0 -1
- package/dist/vue-hono-app.js +0 -440
- package/templates/shared/oxc/package.json +0 -18
- package/templates/shared/oxc/tsconfig.json +0 -11
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
function templateRepositoryRoot() {
|
|
5
|
+
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const candidates = [
|
|
7
|
+
path.resolve(moduleDir, ".."),
|
|
8
|
+
path.resolve(moduleDir, "..", ".."),
|
|
9
|
+
];
|
|
10
|
+
return (candidates.find((candidate) => existsSync(path.join(candidate, "pnpm-workspace.yaml"))) ?? candidates[0]);
|
|
11
|
+
}
|
|
12
|
+
function parseTemplateDependencyCatalog(workspaceYaml) {
|
|
13
|
+
const catalog = {};
|
|
14
|
+
const lines = workspaceYaml.split(/\r?\n/);
|
|
15
|
+
const catalogStart = lines.findIndex((line) => line === "catalog:");
|
|
16
|
+
if (catalogStart === -1) {
|
|
17
|
+
return catalog;
|
|
18
|
+
}
|
|
19
|
+
for (const line of lines.slice(catalogStart + 1)) {
|
|
20
|
+
if (line.length > 0 && !line.startsWith(" ")) {
|
|
21
|
+
break;
|
|
22
|
+
}
|
|
23
|
+
const match = /^ (?:"([^"]+)"|([^:]+)): (.+)$/.exec(line);
|
|
24
|
+
if (!match) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
catalog[match[1] ?? match[2]] = match[3];
|
|
28
|
+
}
|
|
29
|
+
return catalog;
|
|
30
|
+
}
|
|
31
|
+
export function loadTemplateDependencyCatalog() {
|
|
32
|
+
return parseTemplateDependencyCatalog(readFileSync(path.join(templateRepositoryRoot(), "pnpm-workspace.yaml"), "utf8"));
|
|
33
|
+
}
|
|
34
|
+
export function selectTemplateDependencyCatalogEntries(dependencies, catalog = loadTemplateDependencyCatalog()) {
|
|
35
|
+
const selected = {};
|
|
36
|
+
for (const dependency of [...dependencies].sort()) {
|
|
37
|
+
const version = catalog[dependency];
|
|
38
|
+
if (version === undefined) {
|
|
39
|
+
throw new Error(`Template Dependency Catalog is missing dependency: ${dependency}`);
|
|
40
|
+
}
|
|
41
|
+
selected[dependency] = version;
|
|
42
|
+
}
|
|
43
|
+
return selected;
|
|
44
|
+
}
|
|
45
|
+
export function collectGeneratedManifestCatalogDependencies(manifests) {
|
|
46
|
+
const dependencies = new Set();
|
|
47
|
+
for (const manifest of manifests) {
|
|
48
|
+
for (const dependencyMap of [
|
|
49
|
+
manifest.dependencies,
|
|
50
|
+
manifest.devDependencies,
|
|
51
|
+
manifest.optionalDependencies,
|
|
52
|
+
manifest.peerDependencies,
|
|
53
|
+
]) {
|
|
54
|
+
for (const [dependency, specifier] of Object.entries(dependencyMap ?? {})) {
|
|
55
|
+
if (specifier !== "catalog:") {
|
|
56
|
+
throw new Error(`Generated manifest dependency ${dependency} must use catalog:, got ${specifier}`);
|
|
57
|
+
}
|
|
58
|
+
dependencies.add(dependency);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return [...dependencies].sort();
|
|
63
|
+
}
|
|
64
|
+
export function collectGeneratedManifestCatalogReferences(manifests) {
|
|
65
|
+
const dependencies = new Set();
|
|
66
|
+
for (const manifest of manifests) {
|
|
67
|
+
for (const dependencyMap of [
|
|
68
|
+
manifest.dependencies,
|
|
69
|
+
manifest.devDependencies,
|
|
70
|
+
manifest.optionalDependencies,
|
|
71
|
+
manifest.peerDependencies,
|
|
72
|
+
]) {
|
|
73
|
+
for (const [dependency, specifier] of Object.entries(dependencyMap ?? {})) {
|
|
74
|
+
if (specifier.startsWith("catalog:")) {
|
|
75
|
+
dependencies.add(dependency);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return [...dependencies].sort();
|
|
81
|
+
}
|
|
82
|
+
function renderCatalogKey(key) {
|
|
83
|
+
return key.startsWith("@") ? JSON.stringify(key) : key;
|
|
84
|
+
}
|
|
85
|
+
export function pnpmWorkspaceYamlWithCatalogDependencies(workspaceYaml, dependencies, templateCatalog = loadTemplateDependencyCatalog()) {
|
|
86
|
+
const catalogStart = workspaceYaml
|
|
87
|
+
.split(/\r?\n/)
|
|
88
|
+
.findIndex((line) => line === "catalog:");
|
|
89
|
+
if (catalogStart === -1) {
|
|
90
|
+
throw new Error("Cannot update pnpm Dependency Catalog: missing catalog section");
|
|
91
|
+
}
|
|
92
|
+
const existingCatalog = parseTemplateDependencyCatalog(workspaceYaml);
|
|
93
|
+
const missingDependencies = dependencies.filter((dependency) => existingCatalog[dependency] === undefined);
|
|
94
|
+
if (missingDependencies.length === 0) {
|
|
95
|
+
return workspaceYaml;
|
|
96
|
+
}
|
|
97
|
+
const nextCatalog = {
|
|
98
|
+
...existingCatalog,
|
|
99
|
+
...selectTemplateDependencyCatalogEntries(missingDependencies, templateCatalog),
|
|
100
|
+
};
|
|
101
|
+
const lines = workspaceYaml.split(/\r?\n/);
|
|
102
|
+
const catalogEnd = lines.findIndex((line, index) => index > catalogStart && line.length > 0 && !line.startsWith(" "));
|
|
103
|
+
const replacementEnd = catalogEnd === -1 ? lines.length : catalogEnd;
|
|
104
|
+
const catalogLines = [
|
|
105
|
+
"catalog:",
|
|
106
|
+
...Object.entries(nextCatalog)
|
|
107
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
108
|
+
.map(([dependency, version]) => ` ${renderCatalogKey(dependency)}: ${version}`),
|
|
109
|
+
"",
|
|
110
|
+
];
|
|
111
|
+
return [
|
|
112
|
+
...lines.slice(0, catalogStart),
|
|
113
|
+
...catalogLines,
|
|
114
|
+
...lines.slice(replacementEnd),
|
|
115
|
+
].join("\n");
|
|
116
|
+
}
|
|
117
|
+
export function renderGeneratedPnpmWorkspaceYaml(options) {
|
|
118
|
+
const catalog = selectTemplateDependencyCatalogEntries(options.dependencies);
|
|
119
|
+
const packages = options.packages ?? ["."];
|
|
120
|
+
const lines = [
|
|
121
|
+
"packages:",
|
|
122
|
+
...packages.map((workspacePackage) => ` - ${workspacePackage}`),
|
|
123
|
+
"",
|
|
124
|
+
];
|
|
125
|
+
if (options.allowBuilds) {
|
|
126
|
+
lines.push("allowBuilds:", ...Object.entries(options.allowBuilds)
|
|
127
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
128
|
+
.map(([dependency, allowed]) => ` ${dependency}: ${allowed}`), "");
|
|
129
|
+
}
|
|
130
|
+
lines.push("catalog:", ...Object.entries(catalog).map(([dependency, version]) => ` ${renderCatalogKey(dependency)}: ${version}`), "");
|
|
131
|
+
return lines.join("\n");
|
|
132
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { WriteTextFromFragmentsOperation } from "./renderer.js";
|
|
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
|
+
type DevelopmentContainerCapabilityLayer = DevelopmentContainerBrowserTestLayer | DevelopmentContainerRustLayer;
|
|
16
|
+
export type DevelopmentContainerPlan = {
|
|
17
|
+
readonly devcontainer: Record<string, unknown>;
|
|
18
|
+
readonly dockerfile: string;
|
|
19
|
+
readonly dockerfileOperation?: WriteTextFromFragmentsOperation;
|
|
20
|
+
};
|
|
21
|
+
export type DevelopmentContainerDockerfileLayer = {
|
|
22
|
+
readonly kind: "base" | "capability";
|
|
23
|
+
readonly name: string;
|
|
24
|
+
readonly text: string;
|
|
25
|
+
};
|
|
26
|
+
export declare function composeDevelopmentContainerDockerfile(options: {
|
|
27
|
+
readonly layers: readonly DevelopmentContainerDockerfileLayer[];
|
|
28
|
+
}): string;
|
|
29
|
+
export declare function nodePnpmToolLayer(options: {
|
|
30
|
+
readonly nodeVersion: string;
|
|
31
|
+
readonly packageManagerPin: string;
|
|
32
|
+
}): DevelopmentContainerNodePnpmLayer;
|
|
33
|
+
export declare function browserTestToolLayer(): DevelopmentContainerBrowserTestLayer;
|
|
34
|
+
export declare function rustToolLayer(options?: {
|
|
35
|
+
readonly toolchain?: string;
|
|
36
|
+
}): DevelopmentContainerRustLayer;
|
|
37
|
+
export declare function dockerfileFirstNodePnpmDevcontainer(options: {
|
|
38
|
+
readonly name: string;
|
|
39
|
+
readonly layer: DevelopmentContainerNodePnpmLayer;
|
|
40
|
+
readonly extensions: readonly string[];
|
|
41
|
+
readonly settings?: Record<string, unknown>;
|
|
42
|
+
}): DevelopmentContainerPlan;
|
|
43
|
+
export declare function checkedDockerfileFirstNodePnpmDevcontainer(options: {
|
|
44
|
+
readonly name: string;
|
|
45
|
+
readonly layer: DevelopmentContainerNodePnpmLayer;
|
|
46
|
+
readonly additionalLayers?: readonly DevelopmentContainerCapabilityLayer[];
|
|
47
|
+
readonly extensions: readonly string[];
|
|
48
|
+
readonly settings?: Record<string, unknown>;
|
|
49
|
+
readonly mounts?: readonly string[];
|
|
50
|
+
}): DevelopmentContainerPlan;
|
|
51
|
+
export declare function dockerfileFirstRustPnpmDevcontainer(options: {
|
|
52
|
+
readonly name: string;
|
|
53
|
+
readonly nodeLayer: DevelopmentContainerNodePnpmLayer;
|
|
54
|
+
readonly rustLayer: DevelopmentContainerRustLayer;
|
|
55
|
+
readonly extensions: readonly string[];
|
|
56
|
+
readonly settings?: Record<string, unknown>;
|
|
57
|
+
}): DevelopmentContainerPlan;
|
|
58
|
+
export {};
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { loadTemplateDependencyCatalog } from "./dependency-catalog.js";
|
|
5
|
+
import { packageTemplateRoot } from "./runtime-paths.js";
|
|
6
|
+
const runtimeDockerfileInstructions = new Set([
|
|
7
|
+
"CMD",
|
|
8
|
+
"ENTRYPOINT",
|
|
9
|
+
"EXPOSE",
|
|
10
|
+
"HEALTHCHECK",
|
|
11
|
+
"STOPSIGNAL",
|
|
12
|
+
"VOLUME",
|
|
13
|
+
]);
|
|
14
|
+
function dockerfileInstructions(text) {
|
|
15
|
+
return text
|
|
16
|
+
.split(/\r?\n/)
|
|
17
|
+
.map((line) => line.trim())
|
|
18
|
+
.filter((line) => line.length > 0 && !line.startsWith("#"))
|
|
19
|
+
.map((line) => line.match(/^([A-Za-z]+)/)?.[1]?.toUpperCase())
|
|
20
|
+
.filter((instruction) => instruction !== undefined);
|
|
21
|
+
}
|
|
22
|
+
export function composeDevelopmentContainerDockerfile(options) {
|
|
23
|
+
const baseImageInstructionCount = options.layers.reduce((count, layer) => count +
|
|
24
|
+
dockerfileInstructions(layer.text).filter((instruction) => instruction === "FROM").length, 0);
|
|
25
|
+
if (baseImageInstructionCount !== 1) {
|
|
26
|
+
throw new Error(`Development Container Dockerfile must have exactly one base layer with a FROM instruction; found ${baseImageInstructionCount}.`);
|
|
27
|
+
}
|
|
28
|
+
for (const layer of options.layers) {
|
|
29
|
+
const instructions = dockerfileInstructions(layer.text);
|
|
30
|
+
if (layer.kind === "capability" && instructions.includes("FROM")) {
|
|
31
|
+
throw new Error(`${layer.name} capability layer must not supply a Dockerfile FROM instruction.`);
|
|
32
|
+
}
|
|
33
|
+
if (layer.kind !== "capability") {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
for (const instruction of instructions) {
|
|
37
|
+
if (runtimeDockerfileInstructions.has(instruction)) {
|
|
38
|
+
throw new Error(`${layer.name} must not use runtime Dockerfile instruction ${instruction}.`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return options.layers.map((layer) => layer.text.trimEnd()).join("\n\n");
|
|
43
|
+
}
|
|
44
|
+
export function nodePnpmToolLayer(options) {
|
|
45
|
+
return {
|
|
46
|
+
kind: "node-pnpm",
|
|
47
|
+
nodeVersion: options.nodeVersion,
|
|
48
|
+
packageManagerPin: options.packageManagerPin,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export function browserTestToolLayer() {
|
|
52
|
+
const playwrightTestVersion = loadTemplateDependencyCatalog()["@playwright/test"];
|
|
53
|
+
if (playwrightTestVersion === undefined) {
|
|
54
|
+
throw new Error("Template Dependency Catalog is missing dependency: @playwright/test");
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
kind: "browser-test",
|
|
58
|
+
playwrightCliPackage: `@playwright/test@${playwrightTestVersion}`,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export function rustToolLayer(options = {}) {
|
|
62
|
+
return { kind: "rust", toolchain: options.toolchain ?? "stable" };
|
|
63
|
+
}
|
|
64
|
+
function sharedDevcontainerSourceRoot() {
|
|
65
|
+
return packageTemplateRoot(path.dirname(fileURLToPath(import.meta.url)), "shared", "devcontainer");
|
|
66
|
+
}
|
|
67
|
+
function checkedDockerfileFragment(name) {
|
|
68
|
+
return readFileSync(path.join(sharedDevcontainerSourceRoot(), name), "utf8");
|
|
69
|
+
}
|
|
70
|
+
function checkedNodePnpmDockerfile(options = {}) {
|
|
71
|
+
return composeDevelopmentContainerDockerfile({
|
|
72
|
+
layers: [
|
|
73
|
+
{
|
|
74
|
+
kind: "base",
|
|
75
|
+
name: "node-pnpm",
|
|
76
|
+
text: checkedDockerfileFragment("node-pnpm.Dockerfile"),
|
|
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: checkedDockerfileFragment("browser-test.Dockerfile"),
|
|
85
|
+
};
|
|
86
|
+
case "rust":
|
|
87
|
+
return {
|
|
88
|
+
kind: "capability",
|
|
89
|
+
name: "rust",
|
|
90
|
+
text: checkedDockerfileFragment("rust.Dockerfile"),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}),
|
|
94
|
+
],
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
function checkedNodePnpmDockerfileFragments(options = {}) {
|
|
98
|
+
return [
|
|
99
|
+
{
|
|
100
|
+
sourceRoot: "sharedDevcontainer",
|
|
101
|
+
from: "node-pnpm.Dockerfile",
|
|
102
|
+
},
|
|
103
|
+
...(options.additionalLayers ?? []).map((layer) => {
|
|
104
|
+
switch (layer.kind) {
|
|
105
|
+
case "browser-test":
|
|
106
|
+
return {
|
|
107
|
+
sourceRoot: "sharedDevcontainer",
|
|
108
|
+
from: "browser-test.Dockerfile",
|
|
109
|
+
};
|
|
110
|
+
case "rust":
|
|
111
|
+
return {
|
|
112
|
+
sourceRoot: "sharedDevcontainer",
|
|
113
|
+
from: "rust.Dockerfile",
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}),
|
|
117
|
+
];
|
|
118
|
+
}
|
|
119
|
+
function browserTestBuildArgs(layers = []) {
|
|
120
|
+
const browserTestLayer = layers.find((layer) => layer.kind === "browser-test");
|
|
121
|
+
return browserTestLayer === undefined
|
|
122
|
+
? {}
|
|
123
|
+
: { PLAYWRIGHT_CLI_PACKAGE: browserTestLayer.playwrightCliPackage };
|
|
124
|
+
}
|
|
125
|
+
function rustBuildArgs(layers = []) {
|
|
126
|
+
const rustLayer = layers.find((layer) => layer.kind === "rust");
|
|
127
|
+
return rustLayer === undefined ? {} : { RUST_TOOLCHAIN: rustLayer.toolchain };
|
|
128
|
+
}
|
|
129
|
+
export function dockerfileFirstNodePnpmDevcontainer(options) {
|
|
130
|
+
return {
|
|
131
|
+
devcontainer: {
|
|
132
|
+
name: options.name,
|
|
133
|
+
build: {
|
|
134
|
+
dockerfile: "Dockerfile",
|
|
135
|
+
args: {
|
|
136
|
+
NODE_VERSION: options.layer.nodeVersion,
|
|
137
|
+
PACKAGE_MANAGER_PIN: options.layer.packageManagerPin,
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
customizations: {
|
|
141
|
+
vscode: {
|
|
142
|
+
extensions: options.extensions,
|
|
143
|
+
...(options.settings ? { settings: options.settings } : {}),
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
dockerfile: [
|
|
148
|
+
`FROM mcr.microsoft.com/devcontainers/typescript-node:${options.layer.nodeVersion}`,
|
|
149
|
+
"",
|
|
150
|
+
'SHELL ["/bin/bash", "-o", "pipefail", "-c"]',
|
|
151
|
+
`RUN corepack enable && corepack prepare ${options.layer.packageManagerPin} --activate`,
|
|
152
|
+
"",
|
|
153
|
+
].join("\n"),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
export function checkedDockerfileFirstNodePnpmDevcontainer(options) {
|
|
157
|
+
return {
|
|
158
|
+
devcontainer: {
|
|
159
|
+
name: options.name,
|
|
160
|
+
build: {
|
|
161
|
+
dockerfile: "Dockerfile",
|
|
162
|
+
args: {
|
|
163
|
+
NODE_VERSION: options.layer.nodeVersion,
|
|
164
|
+
PACKAGE_MANAGER_PIN: options.layer.packageManagerPin,
|
|
165
|
+
...browserTestBuildArgs(options.additionalLayers),
|
|
166
|
+
...rustBuildArgs(options.additionalLayers),
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
customizations: {
|
|
170
|
+
vscode: {
|
|
171
|
+
extensions: options.extensions,
|
|
172
|
+
...(options.settings ? { settings: options.settings } : {}),
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
...(options.mounts ? { mounts: options.mounts } : {}),
|
|
176
|
+
},
|
|
177
|
+
dockerfile: checkedNodePnpmDockerfile({
|
|
178
|
+
additionalLayers: options.additionalLayers,
|
|
179
|
+
}),
|
|
180
|
+
dockerfileOperation: {
|
|
181
|
+
kind: "writeTextFromFragments",
|
|
182
|
+
to: ".devcontainer/Dockerfile",
|
|
183
|
+
fragments: checkedNodePnpmDockerfileFragments({
|
|
184
|
+
additionalLayers: options.additionalLayers,
|
|
185
|
+
}),
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
export function dockerfileFirstRustPnpmDevcontainer(options) {
|
|
190
|
+
return checkedDockerfileFirstNodePnpmDevcontainer({
|
|
191
|
+
name: options.name,
|
|
192
|
+
layer: options.nodeLayer,
|
|
193
|
+
additionalLayers: [options.rustLayer],
|
|
194
|
+
extensions: options.extensions,
|
|
195
|
+
settings: options.settings,
|
|
196
|
+
mounts: [
|
|
197
|
+
"source=${localWorkspaceFolderBasename}-cargo-registry,target=/usr/local/cargo/registry,type=volume",
|
|
198
|
+
"source=${localWorkspaceFolderBasename}-cargo-git,target=/usr/local/cargo/git,type=volume",
|
|
199
|
+
"source=${localWorkspaceFolderBasename}-target,target=${containerWorkspaceFolder}/target,type=volume",
|
|
200
|
+
],
|
|
201
|
+
});
|
|
202
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type EditorCustomizationCapability = "oxc-format-lint" | "rust-tooling" | "tailwind" | "vue" | "vitest";
|
|
2
|
+
export type EditorCustomization = {
|
|
3
|
+
readonly extensions: readonly string[];
|
|
4
|
+
readonly settings: Record<string, unknown>;
|
|
5
|
+
};
|
|
6
|
+
export type EditorCustomizationOptions = {
|
|
7
|
+
readonly oxcConfigPaths?: "nested" | {
|
|
8
|
+
readonly lint?: string;
|
|
9
|
+
readonly formatter?: string;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
export declare function editorCustomizationForCapabilities(capabilities: readonly EditorCustomizationCapability[], options?: EditorCustomizationOptions): EditorCustomization;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
const capabilityOrder = [
|
|
5
|
+
"oxc-format-lint",
|
|
6
|
+
"vue",
|
|
7
|
+
"tailwind",
|
|
8
|
+
"rust-tooling",
|
|
9
|
+
"vitest",
|
|
10
|
+
];
|
|
11
|
+
const defaultOxcConfigPaths = {
|
|
12
|
+
formatter: "./oxfmt.config.ts",
|
|
13
|
+
lint: "./oxlint.config.ts",
|
|
14
|
+
};
|
|
15
|
+
const declarations = loadEditorCustomizationDeclarations();
|
|
16
|
+
function editorCustomizationDeclarationPath() {
|
|
17
|
+
const moduleDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
const relativeDeclarationPath = "templates/shared/editor-customization/capabilities.json";
|
|
19
|
+
const candidates = [
|
|
20
|
+
path.resolve(moduleDirectory, "..", relativeDeclarationPath),
|
|
21
|
+
path.resolve(moduleDirectory, "..", "..", relativeDeclarationPath),
|
|
22
|
+
];
|
|
23
|
+
const declarationPath = candidates.find((candidate) => existsSync(candidate));
|
|
24
|
+
if (!declarationPath) {
|
|
25
|
+
throw new Error(`Unable to find editor customization declarations at ${relativeDeclarationPath}`);
|
|
26
|
+
}
|
|
27
|
+
return declarationPath;
|
|
28
|
+
}
|
|
29
|
+
function isRecord(value) {
|
|
30
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
31
|
+
}
|
|
32
|
+
function assertCapabilityProjection(capability, value) {
|
|
33
|
+
if (!isRecord(value)) {
|
|
34
|
+
throw new Error(`Editor customization ${capability} must be an object`);
|
|
35
|
+
}
|
|
36
|
+
const extensions = value.extensions;
|
|
37
|
+
const settings = value.settings;
|
|
38
|
+
if (!Array.isArray(extensions) ||
|
|
39
|
+
extensions.some((extension) => typeof extension !== "string")) {
|
|
40
|
+
throw new Error(`Editor customization ${capability} must declare string extensions`);
|
|
41
|
+
}
|
|
42
|
+
if (!isRecord(settings)) {
|
|
43
|
+
throw new Error(`Editor customization ${capability} must declare object settings`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function loadEditorCustomizationDeclarations() {
|
|
47
|
+
const source = JSON.parse(readFileSync(editorCustomizationDeclarationPath(), "utf8"));
|
|
48
|
+
if (!isRecord(source) || !isRecord(source.capabilities)) {
|
|
49
|
+
throw new Error("Editor customization declarations must contain capabilities");
|
|
50
|
+
}
|
|
51
|
+
const capabilities = source.capabilities;
|
|
52
|
+
for (const capability of capabilityOrder) {
|
|
53
|
+
assertCapabilityProjection(capability, capabilities[capability]);
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
capabilities: Object.fromEntries(capabilityOrder.map((capability) => [
|
|
57
|
+
capability,
|
|
58
|
+
capabilities[capability],
|
|
59
|
+
])),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function oxcConfigPathSettings(configPaths) {
|
|
63
|
+
if (configPaths === "nested") {
|
|
64
|
+
return {};
|
|
65
|
+
}
|
|
66
|
+
const paths = {
|
|
67
|
+
...defaultOxcConfigPaths,
|
|
68
|
+
...configPaths,
|
|
69
|
+
};
|
|
70
|
+
return {
|
|
71
|
+
"oxc.configPath": paths.lint,
|
|
72
|
+
"oxc.fmt.configPath": paths.formatter,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function oxcProjection(options) {
|
|
76
|
+
const baseProjection = declarations.capabilities["oxc-format-lint"];
|
|
77
|
+
return {
|
|
78
|
+
extensions: baseProjection.extensions,
|
|
79
|
+
settings: {
|
|
80
|
+
...baseProjection.settings,
|
|
81
|
+
...oxcConfigPathSettings(options?.oxcConfigPaths),
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function mergeSettings(base, patch) {
|
|
86
|
+
return { ...base, ...patch };
|
|
87
|
+
}
|
|
88
|
+
export function editorCustomizationForCapabilities(capabilities, options) {
|
|
89
|
+
const selected = new Set(capabilities);
|
|
90
|
+
const extensions = new Set();
|
|
91
|
+
let settings = {};
|
|
92
|
+
for (const capability of capabilityOrder) {
|
|
93
|
+
if (!selected.has(capability)) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const projection = capability === "oxc-format-lint"
|
|
97
|
+
? oxcProjection(options)
|
|
98
|
+
: declarations.capabilities[capability];
|
|
99
|
+
for (const extension of projection.extensions) {
|
|
100
|
+
extensions.add(extension);
|
|
101
|
+
}
|
|
102
|
+
settings = mergeSettings(settings, projection.settings);
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
extensions: [...extensions],
|
|
106
|
+
settings,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PackageManager,
|
|
1
|
+
import type { PackageManager, ProjectBlueprint } from "./declarations.js";
|
|
2
2
|
import type { ResolvedToolchainVersions } from "./toolchain-resolution.js";
|
|
3
3
|
export type ProjectName = {
|
|
4
4
|
readonly kind: "ProjectName";
|
|
@@ -10,7 +10,7 @@ export type GenerationPackageManager = {
|
|
|
10
10
|
};
|
|
11
11
|
export type GenerationContext = {
|
|
12
12
|
readonly projectName: ProjectName;
|
|
13
|
-
readonly preset:
|
|
13
|
+
readonly preset: string;
|
|
14
14
|
readonly packageManager?: GenerationPackageManager;
|
|
15
15
|
readonly blueprint: ProjectBlueprint;
|
|
16
16
|
readonly toolchain: ResolvedToolchainVersions;
|
|
@@ -21,4 +21,3 @@ export type AssembleGenerationContextOptions = {
|
|
|
21
21
|
readonly toolchain: ResolvedToolchainVersions;
|
|
22
22
|
};
|
|
23
23
|
export declare function assembleGenerationContext(options: AssembleGenerationContextOptions): GenerationContext;
|
|
24
|
-
//# sourceMappingURL=generation-context.d.ts.map
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
export function assembleGenerationContext(options) {
|
|
3
3
|
return {
|
|
4
|
-
projectName: {
|
|
4
|
+
projectName: {
|
|
5
|
+
kind: "ProjectName",
|
|
6
|
+
value: path.basename(path.resolve(options.targetDir)),
|
|
7
|
+
},
|
|
5
8
|
preset: options.blueprint.preset,
|
|
6
9
|
packageManager: options.blueprint.packageManager
|
|
7
10
|
? { kind: "PackageManager", value: options.blueprint.packageManager }
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export type PackageBoundaryOwner = {
|
|
2
|
+
readonly kind: "package-boundary";
|
|
3
|
+
readonly path: string;
|
|
4
|
+
};
|
|
5
|
+
export type WorkspaceOrchestrationOwner = {
|
|
6
|
+
readonly kind: "workspace-orchestration";
|
|
7
|
+
readonly path: ".";
|
|
8
|
+
};
|
|
9
|
+
export type ComponentOwner = PackageBoundaryOwner | WorkspaceOrchestrationOwner;
|
|
10
|
+
export type CheckComponentKind = "typescript-typecheck" | "oxc-lint" | "oxc-format-check" | "build" | "unit-test" | "e2e-test" | "turbo-check" | "turbo-package-check" | "rustfmt-check" | "cargo-clippy" | "cargo-test";
|
|
11
|
+
export type FixComponentKind = "oxc-format-write" | "oxc-lint-fix" | "turbo-fix" | "turbo-package-fix" | "rustfmt-write";
|
|
12
|
+
export type CheckEnvironmentNeed = {
|
|
13
|
+
readonly kind: "playwright-browser-assets";
|
|
14
|
+
readonly browser: "chromium";
|
|
15
|
+
readonly owner: ComponentOwner;
|
|
16
|
+
};
|
|
17
|
+
export type CheckComponent = {
|
|
18
|
+
readonly kind: CheckComponentKind;
|
|
19
|
+
readonly owner: ComponentOwner;
|
|
20
|
+
};
|
|
21
|
+
export type FixComponent = {
|
|
22
|
+
readonly kind: FixComponentKind;
|
|
23
|
+
readonly owner: ComponentOwner;
|
|
24
|
+
};
|
|
25
|
+
export type CheckPlan = {
|
|
26
|
+
readonly components: CheckComponent[];
|
|
27
|
+
readonly environmentNeeds: CheckEnvironmentNeed[];
|
|
28
|
+
};
|
|
29
|
+
export type FixPlan = {
|
|
30
|
+
readonly components: FixComponent[];
|
|
31
|
+
};
|
|
32
|
+
export declare function renderRootCheckCommand(plan: CheckPlan): string;
|
|
33
|
+
export declare function renderFixCommand(plan: FixPlan): string;
|
|
34
|
+
export declare function renderPlaywrightBrowserInstallCommand(need: CheckEnvironmentNeed): string;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
function renderTurboPackageFilter(owner) {
|
|
2
|
+
if (owner.kind !== "package-boundary") {
|
|
3
|
+
throw new Error("Turbo package tasks require a Package Boundary owner.");
|
|
4
|
+
}
|
|
5
|
+
return `--filter './${owner.path}'`;
|
|
6
|
+
}
|
|
7
|
+
function renderCheckComponentCommand(component) {
|
|
8
|
+
switch (component.kind) {
|
|
9
|
+
case "typescript-typecheck":
|
|
10
|
+
return "pnpm run typecheck";
|
|
11
|
+
case "oxc-lint":
|
|
12
|
+
return "pnpm run lint";
|
|
13
|
+
case "oxc-format-check":
|
|
14
|
+
return "pnpm run format:check";
|
|
15
|
+
case "build":
|
|
16
|
+
return "pnpm run build";
|
|
17
|
+
case "unit-test":
|
|
18
|
+
return "pnpm run test";
|
|
19
|
+
case "e2e-test":
|
|
20
|
+
return "pnpm run test:e2e";
|
|
21
|
+
case "turbo-check":
|
|
22
|
+
return "turbo run check";
|
|
23
|
+
case "turbo-package-check":
|
|
24
|
+
return `turbo run check ${renderTurboPackageFilter(component.owner)}`;
|
|
25
|
+
case "rustfmt-check":
|
|
26
|
+
return "cargo fmt --all -- --check";
|
|
27
|
+
case "cargo-clippy":
|
|
28
|
+
return "cargo clippy --workspace --all-targets -- -D warnings";
|
|
29
|
+
case "cargo-test":
|
|
30
|
+
return "cargo test --workspace";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function renderFixComponentCommand(component) {
|
|
34
|
+
switch (component.kind) {
|
|
35
|
+
case "oxc-format-write":
|
|
36
|
+
return "pnpm run format:write";
|
|
37
|
+
case "oxc-lint-fix":
|
|
38
|
+
return "pnpm run lint:fix";
|
|
39
|
+
case "turbo-fix":
|
|
40
|
+
return "turbo run fix";
|
|
41
|
+
case "turbo-package-fix":
|
|
42
|
+
return `turbo run fix ${renderTurboPackageFilter(component.owner)}`;
|
|
43
|
+
case "rustfmt-write":
|
|
44
|
+
return "cargo fmt --all";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export function renderRootCheckCommand(plan) {
|
|
48
|
+
return plan.components.map(renderCheckComponentCommand).join(" && ");
|
|
49
|
+
}
|
|
50
|
+
export function renderFixCommand(plan) {
|
|
51
|
+
return plan.components.map(renderFixComponentCommand).join(" && ");
|
|
52
|
+
}
|
|
53
|
+
export function renderPlaywrightBrowserInstallCommand(need) {
|
|
54
|
+
if (need.owner.path === "apps/web") {
|
|
55
|
+
return `pnpm --filter ./apps/web exec playwright install ${need.browser}`;
|
|
56
|
+
}
|
|
57
|
+
return `pnpm exec playwright install ${need.browser}`;
|
|
58
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { PresetProjectionPlan } from "./preset-projection.js";
|
|
2
2
|
export type NextStepInstruction = {
|
|
3
3
|
readonly id: string;
|
|
4
4
|
readonly label: string;
|
|
@@ -10,13 +10,11 @@ export type NextStepInstruction = {
|
|
|
10
10
|
readonly machineVerifiable: boolean;
|
|
11
11
|
};
|
|
12
12
|
export type NextStepInstructionPlan = {
|
|
13
|
-
readonly preset: PresetName;
|
|
14
13
|
readonly targetDir: string;
|
|
15
14
|
readonly steps: readonly NextStepInstruction[];
|
|
16
15
|
};
|
|
17
16
|
export type PlanNextStepInstructionsOptions = {
|
|
18
|
-
readonly preset: PresetName;
|
|
19
17
|
readonly targetDir: string;
|
|
18
|
+
readonly projectionPlan: PresetProjectionPlan;
|
|
20
19
|
};
|
|
21
20
|
export declare function planNextStepInstructions(options: PlanNextStepInstructionsOptions): NextStepInstructionPlan;
|
|
22
|
-
//# sourceMappingURL=next-step-instructions.d.ts.map
|