@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
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { planPresetChecks, renderPlaywrightBrowserInstallCommand } from "./module-graph.js";
|
|
1
|
+
import { renderPlaywrightBrowserInstallCommand } from "./module-graph.js";
|
|
3
2
|
function rootCheckInstruction() {
|
|
4
3
|
return {
|
|
5
4
|
id: "run-root-check",
|
|
@@ -12,6 +11,40 @@ function rootCheckInstruction() {
|
|
|
12
11
|
machineVerifiable: true,
|
|
13
12
|
};
|
|
14
13
|
}
|
|
14
|
+
function optionalGitInstructions() {
|
|
15
|
+
return [
|
|
16
|
+
{
|
|
17
|
+
id: "optional-git-init",
|
|
18
|
+
label: "Optional: initialize git",
|
|
19
|
+
kind: "command",
|
|
20
|
+
command: "git",
|
|
21
|
+
args: ["init"],
|
|
22
|
+
cwd: "",
|
|
23
|
+
display: "git init",
|
|
24
|
+
machineVerifiable: false,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: "optional-git-add",
|
|
28
|
+
label: "Optional: stage files",
|
|
29
|
+
kind: "command",
|
|
30
|
+
command: "git",
|
|
31
|
+
args: ["add", "."],
|
|
32
|
+
cwd: "",
|
|
33
|
+
display: "git add .",
|
|
34
|
+
machineVerifiable: false,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: "optional-git-commit",
|
|
38
|
+
label: "Optional: create your first commit",
|
|
39
|
+
kind: "command",
|
|
40
|
+
command: "git",
|
|
41
|
+
args: ["commit", "-m", "Initial commit"],
|
|
42
|
+
cwd: "",
|
|
43
|
+
display: 'git commit -m "Initial commit"',
|
|
44
|
+
machineVerifiable: false,
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
}
|
|
15
48
|
function installDependenciesInstruction() {
|
|
16
49
|
return {
|
|
17
50
|
id: "install-dependencies",
|
|
@@ -36,12 +69,8 @@ function fixInstruction() {
|
|
|
36
69
|
machineVerifiable: true,
|
|
37
70
|
};
|
|
38
71
|
}
|
|
39
|
-
function checkEnvironmentInstructions(
|
|
40
|
-
|
|
41
|
-
if (!checkPlan) {
|
|
42
|
-
return [];
|
|
43
|
-
}
|
|
44
|
-
return checkPlan.environmentNeeds.map((need) => {
|
|
72
|
+
function checkEnvironmentInstructions(plan) {
|
|
73
|
+
return plan.checkPlan.environmentNeeds.map((need) => {
|
|
45
74
|
const display = renderPlaywrightBrowserInstallCommand(need);
|
|
46
75
|
const [command, ...args] = display.split(" ");
|
|
47
76
|
return {
|
|
@@ -60,24 +89,21 @@ function checkEnvironmentInstructions(preset) {
|
|
|
60
89
|
};
|
|
61
90
|
});
|
|
62
91
|
}
|
|
63
|
-
function
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
fixInstruction(),
|
|
72
|
-
...checkEnvironmentInstructions(preset),
|
|
73
|
-
rootCheckInstruction(),
|
|
74
|
-
];
|
|
92
|
+
function projectionInstructions(plan) {
|
|
93
|
+
const steps = [installDependenciesInstruction()];
|
|
94
|
+
if (plan.capabilities.fixCommand) {
|
|
95
|
+
steps.push(fixInstruction());
|
|
96
|
+
}
|
|
97
|
+
steps.push(...checkEnvironmentInstructions(plan));
|
|
98
|
+
if (plan.capabilities.rootCheck) {
|
|
99
|
+
steps.push(rootCheckInstruction());
|
|
75
100
|
}
|
|
76
|
-
|
|
101
|
+
steps.push(...optionalGitInstructions());
|
|
102
|
+
return steps;
|
|
77
103
|
}
|
|
78
104
|
export function planNextStepInstructions(options) {
|
|
79
|
-
const targetDir =
|
|
80
|
-
const commandSteps =
|
|
105
|
+
const targetDir = options.targetDir;
|
|
106
|
+
const commandSteps = projectionInstructions(options.projectionPlan).map((step) => ({
|
|
81
107
|
...step,
|
|
82
108
|
cwd: targetDir,
|
|
83
109
|
}));
|
|
@@ -92,7 +118,6 @@ export function planNextStepInstructions(options) {
|
|
|
92
118
|
machineVerifiable: false,
|
|
93
119
|
};
|
|
94
120
|
return Object.freeze({
|
|
95
|
-
preset: options.preset,
|
|
96
121
|
targetDir,
|
|
97
122
|
steps: Object.freeze([navigationStep, ...commandSteps].map((step) => Object.freeze(step))),
|
|
98
123
|
});
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { findBuiltInPresetProjection } from "../templates/registry.js";
|
|
4
|
+
import { validateProjectBlueprint, } from "./declarations.js";
|
|
5
|
+
import { collectGeneratedManifestCatalogReferences, pnpmWorkspaceYamlWithCatalogDependencies, } from "./dependency-catalog.js";
|
|
6
|
+
import { renderProject } from "./renderer.js";
|
|
7
|
+
function projectNameFromBlueprint(blueprint) {
|
|
8
|
+
const firstPackage = blueprint.packages?.[0];
|
|
9
|
+
const match = firstPackage?.name.match(/^@([^/]+)\//);
|
|
10
|
+
if (!match) {
|
|
11
|
+
throw new Error("Cannot infer workspace package scope from the stored Project Blueprint");
|
|
12
|
+
}
|
|
13
|
+
return match[1];
|
|
14
|
+
}
|
|
15
|
+
function assertSafePackageLeaf(name) {
|
|
16
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(name)) {
|
|
17
|
+
throw new Error("--name must be a lowercase package leaf name using letters, numbers, and hyphens");
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const reservedPackagePathCollections = new Set([
|
|
21
|
+
".devcontainer",
|
|
22
|
+
".git",
|
|
23
|
+
".github",
|
|
24
|
+
".template",
|
|
25
|
+
"build",
|
|
26
|
+
"coverage",
|
|
27
|
+
"dist",
|
|
28
|
+
"node_modules",
|
|
29
|
+
"out",
|
|
30
|
+
"target",
|
|
31
|
+
]);
|
|
32
|
+
function assertSafePackagePathSegment(packagePath, segment) {
|
|
33
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(segment)) {
|
|
34
|
+
throw new Error(`Package Path ${packagePath} must use safe path segments with lowercase letters, numbers, and hyphens`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function validateExplicitPackagePath(packagePath) {
|
|
38
|
+
if (path.isAbsolute(packagePath)) {
|
|
39
|
+
throw new Error(`Package Path ${packagePath} must be relative`);
|
|
40
|
+
}
|
|
41
|
+
if (packagePath !== packagePath.trim()) {
|
|
42
|
+
throw new Error(`Package Path ${packagePath} must not contain whitespace`);
|
|
43
|
+
}
|
|
44
|
+
if (packagePath === ".." ||
|
|
45
|
+
packagePath.startsWith("../") ||
|
|
46
|
+
packagePath.endsWith("/..") ||
|
|
47
|
+
packagePath.includes("/../")) {
|
|
48
|
+
throw new Error(`Package Path ${packagePath} must not escape the workspace`);
|
|
49
|
+
}
|
|
50
|
+
const normalized = path.posix.normalize(packagePath);
|
|
51
|
+
if (normalized !== packagePath || packagePath.includes("\\")) {
|
|
52
|
+
throw new Error(`Package Path ${packagePath} must be exactly two safe path segments`);
|
|
53
|
+
}
|
|
54
|
+
const segments = packagePath.split("/");
|
|
55
|
+
if (segments.length !== 2) {
|
|
56
|
+
throw new Error(`Package Path ${packagePath} must be exactly two safe path segments`);
|
|
57
|
+
}
|
|
58
|
+
const [collection, packageDirectory] = segments;
|
|
59
|
+
if (!collection || !packageDirectory) {
|
|
60
|
+
throw new Error(`Package Path ${packagePath} must be exactly two safe path segments`);
|
|
61
|
+
}
|
|
62
|
+
if (reservedPackagePathCollections.has(collection)) {
|
|
63
|
+
throw new Error(`Package Path ${packagePath} uses reserved collection ${collection}`);
|
|
64
|
+
}
|
|
65
|
+
assertSafePackagePathSegment(packagePath, collection);
|
|
66
|
+
assertSafePackagePathSegment(packagePath, packageDirectory);
|
|
67
|
+
if (reservedPackagePathCollections.has(packageDirectory)) {
|
|
68
|
+
throw new Error(`Package Path ${packagePath} uses reserved package directory ${packageDirectory}`);
|
|
69
|
+
}
|
|
70
|
+
return packagePath;
|
|
71
|
+
}
|
|
72
|
+
function defaultPackagePathForPreset(preset, packageLeafName) {
|
|
73
|
+
return preset === "ts-lib"
|
|
74
|
+
? `packages/${packageLeafName}`
|
|
75
|
+
: `apps/${packageLeafName}`;
|
|
76
|
+
}
|
|
77
|
+
function assertNoPackageConflict(blueprint, packageName, packagePath) {
|
|
78
|
+
const existingByName = blueprint.packages?.find((pkg) => pkg.name === packageName);
|
|
79
|
+
if (existingByName) {
|
|
80
|
+
throw new Error(`Package Path ${packagePath} conflicts with existing package ${existingByName.name} at ${existingByName.path}`);
|
|
81
|
+
}
|
|
82
|
+
const existingByPath = blueprint.packages?.find((pkg) => pkg.path === packagePath);
|
|
83
|
+
if (existingByPath) {
|
|
84
|
+
throw new Error(`Package Path ${packagePath} conflicts with existing package ${existingByPath.name} at ${existingByPath.path}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async function readJson(filePath) {
|
|
88
|
+
return JSON.parse(await readFile(filePath, "utf8"));
|
|
89
|
+
}
|
|
90
|
+
async function writeJson(filePath, value) {
|
|
91
|
+
await writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
92
|
+
}
|
|
93
|
+
async function assertMissingPackagePath(packagePath, targetPath) {
|
|
94
|
+
try {
|
|
95
|
+
await stat(targetPath);
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
if (isNodeError(error) && error.code === "ENOENT") {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
throw new Error(`Package Path ${packagePath} conflicts with existing filesystem path ${targetPath}`);
|
|
104
|
+
}
|
|
105
|
+
function resolveGeneratedPath(root, relativePath) {
|
|
106
|
+
if (path.isAbsolute(relativePath)) {
|
|
107
|
+
throw new Error(`Package Addition paths must be relative: ${relativePath}`);
|
|
108
|
+
}
|
|
109
|
+
const resolvedRoot = path.resolve(root);
|
|
110
|
+
const resolvedPath = path.resolve(resolvedRoot, relativePath);
|
|
111
|
+
const insideRoot = resolvedPath === resolvedRoot ||
|
|
112
|
+
resolvedPath.startsWith(`${resolvedRoot}${path.sep}`);
|
|
113
|
+
if (!insideRoot) {
|
|
114
|
+
throw new Error(`Package Addition path escapes the Generated Repository: ${relativePath}`);
|
|
115
|
+
}
|
|
116
|
+
return resolvedPath;
|
|
117
|
+
}
|
|
118
|
+
function isNodeError(error) {
|
|
119
|
+
return error instanceof Error && "code" in error;
|
|
120
|
+
}
|
|
121
|
+
function isRecord(value) {
|
|
122
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
123
|
+
}
|
|
124
|
+
function assertRootPackageJson(input) {
|
|
125
|
+
if (!isRecord(input)) {
|
|
126
|
+
throw new Error("Cannot update root Package Addition scripts: package.json must be an object");
|
|
127
|
+
}
|
|
128
|
+
if (!isRecord(input.scripts)) {
|
|
129
|
+
throw new Error("Cannot update root Package Addition scripts: scripts must be an object");
|
|
130
|
+
}
|
|
131
|
+
for (const scriptName of ["check", "fix"]) {
|
|
132
|
+
if (typeof input.scripts[scriptName] !== "string") {
|
|
133
|
+
throw new Error(`Cannot update root Package Addition scripts: scripts.${scriptName} must be a string`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
async function readGeneratedWorkspaceBlueprint(root) {
|
|
138
|
+
const blueprintPath = path.join(root, ".template/blueprint.json");
|
|
139
|
+
let blueprintJson;
|
|
140
|
+
try {
|
|
141
|
+
blueprintJson = await readJson(blueprintPath);
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
if (error instanceof SyntaxError ||
|
|
145
|
+
(isNodeError(error) && error.code === "ENOENT")) {
|
|
146
|
+
throw new Error("Package Addition requires a valid .template/blueprint.json");
|
|
147
|
+
}
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
const result = validateProjectBlueprint(blueprintJson);
|
|
151
|
+
if (!result.ok) {
|
|
152
|
+
if (isRecord(blueprintJson) &&
|
|
153
|
+
blueprintJson.projectKind === "single-package") {
|
|
154
|
+
throw new Error("Package Addition only supports existing workspace Generated Repositories");
|
|
155
|
+
}
|
|
156
|
+
throw new Error("Package Addition requires a valid .template/blueprint.json");
|
|
157
|
+
}
|
|
158
|
+
const blueprint = result.value;
|
|
159
|
+
if (blueprint.projectKind !== "multi-package") {
|
|
160
|
+
throw new Error("Package Addition only supports existing workspace Generated Repositories");
|
|
161
|
+
}
|
|
162
|
+
if (blueprint.packageManager !== "pnpm") {
|
|
163
|
+
throw new Error("Package Addition only supports pnpm workspace Generated Repositories");
|
|
164
|
+
}
|
|
165
|
+
if (!blueprint.packages || blueprint.packages.length === 0) {
|
|
166
|
+
throw new Error("Package Addition requires package definitions in the stored Project Blueprint");
|
|
167
|
+
}
|
|
168
|
+
await stat(path.join(root, "turbo.json"));
|
|
169
|
+
await stat(path.join(root, "pnpm-workspace.yaml"));
|
|
170
|
+
return blueprint;
|
|
171
|
+
}
|
|
172
|
+
async function readGeneratedRepositoryNodeVersion(root) {
|
|
173
|
+
const packageJson = await readJson(path.join(root, "package.json"));
|
|
174
|
+
if (!isRecord(packageJson) || !isRecord(packageJson.engines)) {
|
|
175
|
+
throw new Error("Package Addition requires root package.json to declare engines.node");
|
|
176
|
+
}
|
|
177
|
+
const nodeVersion = packageJson.engines.node;
|
|
178
|
+
if (typeof nodeVersion !== "string" || nodeVersion.length === 0) {
|
|
179
|
+
throw new Error("Package Addition requires root package.json to declare engines.node");
|
|
180
|
+
}
|
|
181
|
+
return nodeVersion;
|
|
182
|
+
}
|
|
183
|
+
function workspaceTextWithPackageGlob(text, glob) {
|
|
184
|
+
if (text.includes(` - ${glob}`)) {
|
|
185
|
+
return text;
|
|
186
|
+
}
|
|
187
|
+
const nextText = text.replace(/^packages:\n/m, `packages:\n - ${glob}\n`);
|
|
188
|
+
if (nextText === text) {
|
|
189
|
+
throw new Error("Cannot update pnpm workspace membership: missing packages section");
|
|
190
|
+
}
|
|
191
|
+
return nextText;
|
|
192
|
+
}
|
|
193
|
+
function assertRootTsconfig(input) {
|
|
194
|
+
if (!isRecord(input)) {
|
|
195
|
+
throw new Error("Cannot update root TypeScript project references: tsconfig.json must be an object");
|
|
196
|
+
}
|
|
197
|
+
const references = input.references;
|
|
198
|
+
if (references === undefined) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (!Array.isArray(references)) {
|
|
202
|
+
throw new Error("Cannot update root TypeScript project references: references must be an array");
|
|
203
|
+
}
|
|
204
|
+
for (const reference of references) {
|
|
205
|
+
if (!isRecord(reference) || typeof reference.path !== "string") {
|
|
206
|
+
throw new Error("Cannot update root TypeScript project references: each reference must have a string path");
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
function rootTsconfigWithReferences(input, referencePaths) {
|
|
211
|
+
assertRootTsconfig(input);
|
|
212
|
+
const tsconfig = input;
|
|
213
|
+
const references = tsconfig.references ?? [];
|
|
214
|
+
for (const referencePath of referencePaths) {
|
|
215
|
+
if (!references.some((reference) => reference.path === referencePath)) {
|
|
216
|
+
references.push({ path: referencePath });
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return { ...tsconfig, references };
|
|
220
|
+
}
|
|
221
|
+
function packageAdditionCatalogDependencies(operations) {
|
|
222
|
+
const manifests = [];
|
|
223
|
+
for (const operation of operations) {
|
|
224
|
+
if (operation.kind !== "writeJson" ||
|
|
225
|
+
path.basename(operation.to) !== "package.json") {
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
if (!isRecord(operation.value)) {
|
|
229
|
+
throw new Error(`Package Addition package manifest must be an object: ${operation.to}`);
|
|
230
|
+
}
|
|
231
|
+
manifests.push(operation.value);
|
|
232
|
+
}
|
|
233
|
+
return collectGeneratedManifestCatalogReferences(manifests);
|
|
234
|
+
}
|
|
235
|
+
function blueprintWithPackage(blueprint, packageName, packagePath) {
|
|
236
|
+
const nextBlueprint = {
|
|
237
|
+
...blueprint,
|
|
238
|
+
packages: [
|
|
239
|
+
...(blueprint.packages ?? []),
|
|
240
|
+
{ name: packageName, path: packagePath },
|
|
241
|
+
],
|
|
242
|
+
};
|
|
243
|
+
const result = validateProjectBlueprint(nextBlueprint);
|
|
244
|
+
if (!result.ok) {
|
|
245
|
+
throw new Error("Package Addition would write an invalid .template/blueprint.json");
|
|
246
|
+
}
|
|
247
|
+
return result.value;
|
|
248
|
+
}
|
|
249
|
+
function workspacePackageGlobsFromBlueprint(blueprint) {
|
|
250
|
+
const globs = [];
|
|
251
|
+
for (const packageDefinition of blueprint.packages ?? []) {
|
|
252
|
+
const [workspaceDir] = packageDefinition.path.split("/");
|
|
253
|
+
if (!workspaceDir) {
|
|
254
|
+
throw new Error(`Cannot update root Package Addition scripts: invalid package path ${packageDefinition.path}`);
|
|
255
|
+
}
|
|
256
|
+
const glob = `${workspaceDir}/*`;
|
|
257
|
+
if (!globs.includes(glob)) {
|
|
258
|
+
globs.push(glob);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return globs;
|
|
262
|
+
}
|
|
263
|
+
function turboPackageTaskCommand(task, workspacePackageGlobs) {
|
|
264
|
+
const filters = workspacePackageGlobs.map((glob) => `--filter './${glob}'`);
|
|
265
|
+
return [`turbo run ${task}`, ...filters].join(" ");
|
|
266
|
+
}
|
|
267
|
+
function rootScriptWithTurboPackageTask(script, task, workspacePackageGlobs) {
|
|
268
|
+
const commands = script.split(" && ");
|
|
269
|
+
const turboCommandIndex = commands.findIndex((command) => command.startsWith(`turbo run ${task}`));
|
|
270
|
+
if (turboCommandIndex === -1) {
|
|
271
|
+
throw new Error(`Cannot update root Package Addition scripts: scripts.${task} must run Turbo package tasks`);
|
|
272
|
+
}
|
|
273
|
+
commands[turboCommandIndex] = turboPackageTaskCommand(task, workspacePackageGlobs);
|
|
274
|
+
return commands.join(" && ");
|
|
275
|
+
}
|
|
276
|
+
function rootPackageJsonWithPackageTaskFilters(input, blueprint) {
|
|
277
|
+
assertRootPackageJson(input);
|
|
278
|
+
const workspacePackageGlobs = workspacePackageGlobsFromBlueprint(blueprint);
|
|
279
|
+
return {
|
|
280
|
+
...input,
|
|
281
|
+
scripts: {
|
|
282
|
+
...input.scripts,
|
|
283
|
+
check: rootScriptWithTurboPackageTask(input.scripts.check, "check", workspacePackageGlobs),
|
|
284
|
+
fix: rootScriptWithTurboPackageTask(input.scripts.fix, "fix", workspacePackageGlobs),
|
|
285
|
+
},
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
async function planRootUpdates(root, blueprint, packageName, packagePath, workspacePackageGlob, rootTsconfigReferences, catalogDependencies) {
|
|
289
|
+
const nextBlueprint = blueprintWithPackage(blueprint, packageName, packagePath);
|
|
290
|
+
const workspaceText = pnpmWorkspaceYamlWithCatalogDependencies(workspaceTextWithPackageGlob(await readFile(path.join(root, "pnpm-workspace.yaml"), "utf8"), workspacePackageGlob), catalogDependencies);
|
|
291
|
+
const rootTsconfig = rootTsconfigWithReferences(await readJson(path.join(root, "tsconfig.json")), rootTsconfigReferences);
|
|
292
|
+
const rootPackageJson = rootPackageJsonWithPackageTaskFilters(await readJson(path.join(root, "package.json")), nextBlueprint);
|
|
293
|
+
return {
|
|
294
|
+
blueprint: nextBlueprint,
|
|
295
|
+
rootPackageJson,
|
|
296
|
+
rootTsconfig,
|
|
297
|
+
workspaceText,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
export async function addPackage(options) {
|
|
301
|
+
assertSafePackageLeaf(options.name);
|
|
302
|
+
const root = path.resolve(options.cwd);
|
|
303
|
+
const blueprint = await readGeneratedWorkspaceBlueprint(root);
|
|
304
|
+
const nodeVersion = await readGeneratedRepositoryNodeVersion(root);
|
|
305
|
+
const projectName = projectNameFromBlueprint(blueprint);
|
|
306
|
+
const packageName = `@${projectName}/${options.name}`;
|
|
307
|
+
const packagePath = options.path
|
|
308
|
+
? validateExplicitPackagePath(options.path)
|
|
309
|
+
: defaultPackagePathForPreset(options.preset, options.name);
|
|
310
|
+
const projection = findBuiltInPresetProjection(options.preset);
|
|
311
|
+
const packageAddition = projection?.capabilities?.packageAddition;
|
|
312
|
+
if (!projection) {
|
|
313
|
+
throw new Error(`Unknown preset for Package Addition: ${options.preset}`);
|
|
314
|
+
}
|
|
315
|
+
if (!packageAddition) {
|
|
316
|
+
throw new Error(`Package Addition is not supported by preset: ${options.preset}`);
|
|
317
|
+
}
|
|
318
|
+
const additionPlan = await packageAddition.planPackageAddition({
|
|
319
|
+
root,
|
|
320
|
+
blueprint,
|
|
321
|
+
packageLeafName: options.name,
|
|
322
|
+
packageName,
|
|
323
|
+
packagePath,
|
|
324
|
+
nodeVersion,
|
|
325
|
+
});
|
|
326
|
+
assertNoPackageConflict(blueprint, packageName, additionPlan.packagePath);
|
|
327
|
+
await assertMissingPackagePath(additionPlan.packagePath, path.join(root, additionPlan.packagePath));
|
|
328
|
+
const rootUpdatePlan = await planRootUpdates(root, blueprint, packageName, additionPlan.packagePath, additionPlan.workspacePackageGlob, additionPlan.rootTsconfigReferences, packageAdditionCatalogDependencies(additionPlan.operations));
|
|
329
|
+
await mkdir(path.join(root, additionPlan.packagePath), { recursive: true });
|
|
330
|
+
await renderProject({
|
|
331
|
+
sourceRoot: additionPlan.sourceRoot,
|
|
332
|
+
sourceRoots: additionPlan.sourceRoots,
|
|
333
|
+
targetRoot: root,
|
|
334
|
+
operations: [...additionPlan.operations],
|
|
335
|
+
});
|
|
336
|
+
for (const textFile of additionPlan.textFiles ?? []) {
|
|
337
|
+
await writeFile(resolveGeneratedPath(root, textFile.path), textFile.text, "utf8");
|
|
338
|
+
}
|
|
339
|
+
await writeFile(path.join(root, "pnpm-workspace.yaml"), rootUpdatePlan.workspaceText, "utf8");
|
|
340
|
+
await writeJson(path.join(root, "package.json"), rootUpdatePlan.rootPackageJson);
|
|
341
|
+
await writeJson(path.join(root, "tsconfig.json"), rootUpdatePlan.rootTsconfig);
|
|
342
|
+
await writeJson(path.join(root, ".template/blueprint.json"), rootUpdatePlan.blueprint);
|
|
343
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { BuiltInPreset, ProjectBlueprint } from "./declarations.js";
|
|
2
|
+
import type { GenerationContext } from "./generation-context.js";
|
|
3
|
+
import { type CheckPlan, type FixPlan } from "./module-graph.js";
|
|
4
|
+
import { type NextStepInstruction } from "./next-step-instructions.js";
|
|
5
|
+
import type { DependencyMaintenancePolicy } from "./project-github.js";
|
|
6
|
+
import type { RenderOperation } from "./renderer.js";
|
|
7
|
+
export type PresetPackageAdditionOptions = {
|
|
8
|
+
readonly root: string;
|
|
9
|
+
readonly blueprint: ProjectBlueprint;
|
|
10
|
+
readonly packageLeafName: string;
|
|
11
|
+
readonly packageName: string;
|
|
12
|
+
readonly packagePath: string;
|
|
13
|
+
readonly nodeVersion: string;
|
|
14
|
+
};
|
|
15
|
+
export type PresetPackageAdditionPlan = {
|
|
16
|
+
readonly packagePath: string;
|
|
17
|
+
readonly workspacePackageGlob: string;
|
|
18
|
+
readonly rootTsconfigReferences: readonly string[];
|
|
19
|
+
readonly sourceRoot: string;
|
|
20
|
+
readonly sourceRoots?: Record<string, string>;
|
|
21
|
+
readonly operations: readonly RenderOperation[];
|
|
22
|
+
readonly textFiles?: readonly {
|
|
23
|
+
readonly path: string;
|
|
24
|
+
readonly text: string;
|
|
25
|
+
}[];
|
|
26
|
+
};
|
|
27
|
+
export type PresetPackageAdditionCapability = {
|
|
28
|
+
planPackageAddition(options: PresetPackageAdditionOptions): PresetPackageAdditionPlan | Promise<PresetPackageAdditionPlan>;
|
|
29
|
+
};
|
|
30
|
+
export type PresetProjectionPlan = {
|
|
31
|
+
readonly sourceRoot: string;
|
|
32
|
+
readonly sourceRoots?: Record<string, string>;
|
|
33
|
+
readonly operations: readonly RenderOperation[];
|
|
34
|
+
readonly checkPlan: CheckPlan;
|
|
35
|
+
readonly fixPlan: FixPlan;
|
|
36
|
+
readonly dependencyMaintenancePolicy: DependencyMaintenancePolicy;
|
|
37
|
+
readonly packageScripts: Record<string, string>;
|
|
38
|
+
readonly capabilities: {
|
|
39
|
+
readonly rootCheck: true;
|
|
40
|
+
readonly fixCommand: true;
|
|
41
|
+
readonly githubActions: true;
|
|
42
|
+
readonly dependabot: true;
|
|
43
|
+
readonly devcontainer: true;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
export type PresetBlueprintOptions = {
|
|
47
|
+
readonly targetDir: string;
|
|
48
|
+
readonly scope?: string;
|
|
49
|
+
};
|
|
50
|
+
export type RenderPresetProjectionOptions = {
|
|
51
|
+
readonly targetDir: string;
|
|
52
|
+
readonly plan: PresetProjectionPlan;
|
|
53
|
+
};
|
|
54
|
+
export type PresetProjection = {
|
|
55
|
+
readonly metadata: BuiltInPreset;
|
|
56
|
+
readonly capabilities?: {
|
|
57
|
+
readonly packageAddition?: PresetPackageAdditionCapability;
|
|
58
|
+
};
|
|
59
|
+
blueprint(options: PresetBlueprintOptions): ProjectBlueprint;
|
|
60
|
+
project(context: GenerationContext): PresetProjectionPlan;
|
|
61
|
+
render(options: RenderPresetProjectionOptions): Promise<void>;
|
|
62
|
+
};
|
|
63
|
+
export declare function planNextStepInstructionsForProjection(options: {
|
|
64
|
+
readonly targetDir: string;
|
|
65
|
+
readonly plan: PresetProjectionPlan;
|
|
66
|
+
}): readonly NextStepInstruction[];
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { PresetName } from "./declarations.js";
|
|
2
1
|
import { type CheckPlan } from "./module-graph.js";
|
|
3
2
|
export type CiCapability = {
|
|
4
3
|
readonly workflowName: "Check";
|
|
@@ -13,9 +12,11 @@ export type PnpmTaskLayer = {
|
|
|
13
12
|
readonly installCommand: "pnpm install";
|
|
14
13
|
readonly checkCommand: "pnpm run check";
|
|
15
14
|
};
|
|
16
|
-
export type DependencyEcosystem = "npm" | "cargo" | "github-actions";
|
|
15
|
+
export type DependencyEcosystem = "npm" | "cargo" | "github-actions" | "docker" | "rust-toolchain";
|
|
16
|
+
export type DependabotDirectory = `/${string}`;
|
|
17
17
|
export type DependencyMaintenancePolicy = {
|
|
18
18
|
readonly ecosystems: DependencyEcosystem[];
|
|
19
|
+
readonly directories?: Partial<Record<DependencyEcosystem, DependabotDirectory>>;
|
|
19
20
|
readonly interval: "weekly";
|
|
20
21
|
};
|
|
21
22
|
type ProjectCheckWorkflowOptions = {
|
|
@@ -23,14 +24,7 @@ type ProjectCheckWorkflowOptions = {
|
|
|
23
24
|
readonly capability?: CiCapability;
|
|
24
25
|
readonly environmentPreparation?: Partial<CiEnvironmentPreparation>;
|
|
25
26
|
readonly taskLayer?: PnpmTaskLayer;
|
|
26
|
-
readonly rustToolchain?: boolean;
|
|
27
27
|
};
|
|
28
|
-
export declare function projectPresetGithubCheckWorkflow(preset: PresetName): string;
|
|
29
28
|
export declare function projectCheckWorkflow(options: ProjectCheckWorkflowOptions): string;
|
|
30
|
-
export declare function projectPresetDependencyMaintenancePolicy(preset: PresetName): DependencyMaintenancePolicy;
|
|
31
|
-
export declare function projectPresetDependabotConfig(preset: PresetName): string;
|
|
32
29
|
export declare function projectDependabotConfig(policy: DependencyMaintenancePolicy): string;
|
|
33
|
-
export declare function projectTsLibGithubCheckWorkflow(): string;
|
|
34
|
-
export declare function projectRustBinGithubCheckWorkflow(): string;
|
|
35
30
|
export {};
|
|
36
|
-
//# sourceMappingURL=project-github.d.ts.map
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { planNodeChecks, planPresetChecks, planRustBinChecks, } from "./module-graph.js";
|
|
2
1
|
const defaultCiCapability = {
|
|
3
2
|
workflowName: "Check",
|
|
4
3
|
jobName: "check",
|
|
@@ -8,21 +7,11 @@ const pnpmTaskLayer = {
|
|
|
8
7
|
installCommand: "pnpm install",
|
|
9
8
|
checkCommand: "pnpm run check",
|
|
10
9
|
};
|
|
11
|
-
export function projectPresetGithubCheckWorkflow(preset) {
|
|
12
|
-
const checkPlan = planPresetChecks(preset);
|
|
13
|
-
if (!checkPlan) {
|
|
14
|
-
throw new Error(`Unsupported preset for GitHub check workflow projection: ${preset}`);
|
|
15
|
-
}
|
|
16
|
-
return projectCheckWorkflow({
|
|
17
|
-
checkPlan,
|
|
18
|
-
rustToolchain: preset === "rust-bin",
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
10
|
export function projectCheckWorkflow(options) {
|
|
22
11
|
const capability = options.capability ?? defaultCiCapability;
|
|
23
12
|
const environmentPreparation = {
|
|
24
13
|
nodeFromPackageMetadata: true,
|
|
25
|
-
rustToolchain:
|
|
14
|
+
rustToolchain: false,
|
|
26
15
|
...options.environmentPreparation,
|
|
27
16
|
};
|
|
28
17
|
const taskLayer = options.taskLayer ?? pnpmTaskLayer;
|
|
@@ -52,45 +41,41 @@ export function projectCheckWorkflow(options) {
|
|
|
52
41
|
lines.push(` - run: ${taskLayer.checkCommand}`, "");
|
|
53
42
|
return lines.join("\n");
|
|
54
43
|
}
|
|
55
|
-
export function projectPresetDependencyMaintenancePolicy(preset) {
|
|
56
|
-
switch (preset) {
|
|
57
|
-
case "ts-lib":
|
|
58
|
-
case "hono-api":
|
|
59
|
-
case "vue-app":
|
|
60
|
-
case "vue-hono-app":
|
|
61
|
-
return { ecosystems: ["npm", "github-actions"], interval: "weekly" };
|
|
62
|
-
case "rust-bin":
|
|
63
|
-
return { ecosystems: ["npm", "cargo", "github-actions"], interval: "weekly" };
|
|
64
|
-
case "ts-app":
|
|
65
|
-
case "node-cli":
|
|
66
|
-
throw new Error(`Unsupported preset for Dependency Maintenance Policy projection: ${preset}`);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
export function projectPresetDependabotConfig(preset) {
|
|
70
|
-
return projectDependabotConfig(projectPresetDependencyMaintenancePolicy(preset));
|
|
71
|
-
}
|
|
72
44
|
export function projectDependabotConfig(policy) {
|
|
73
45
|
return [
|
|
74
46
|
"version: 2",
|
|
47
|
+
"",
|
|
75
48
|
"updates:",
|
|
76
|
-
...policy.ecosystems.flatMap((ecosystem) => [
|
|
77
|
-
|
|
78
|
-
" directory: /",
|
|
79
|
-
" schedule:",
|
|
80
|
-
` interval: ${policy.interval}`,
|
|
81
|
-
]),
|
|
49
|
+
...policy.ecosystems.flatMap((ecosystem) => renderDependabotUpdate(ecosystem, policy.directories?.[ecosystem] ??
|
|
50
|
+
defaultDependabotDirectory(ecosystem), policy.interval)),
|
|
82
51
|
"",
|
|
83
52
|
].join("\n");
|
|
84
53
|
}
|
|
54
|
+
function renderDependabotUpdate(ecosystem, directory, interval) {
|
|
55
|
+
const lines = [
|
|
56
|
+
` - package-ecosystem: ${ecosystem}`,
|
|
57
|
+
` directory: ${renderDependabotDirectory(ecosystem, directory)}`,
|
|
58
|
+
" schedule:",
|
|
59
|
+
` interval: ${interval}`,
|
|
60
|
+
];
|
|
61
|
+
if (ecosystem === "npm") {
|
|
62
|
+
lines.push(" ignore:", ' - dependency-name: "@types/node"', " update-types:", " - version-update:semver-major");
|
|
63
|
+
}
|
|
64
|
+
if (ecosystem === "docker") {
|
|
65
|
+
lines.push(" ignore:", " - dependency-name: mcr.microsoft.com/devcontainers/typescript-node", " update-types:", " - version-update:semver-major");
|
|
66
|
+
}
|
|
67
|
+
return lines;
|
|
68
|
+
}
|
|
69
|
+
function renderDependabotDirectory(ecosystem, directory) {
|
|
70
|
+
return ecosystem === "cargo" ? JSON.stringify(directory) : directory;
|
|
71
|
+
}
|
|
72
|
+
function defaultDependabotDirectory(ecosystem) {
|
|
73
|
+
return ecosystem === "docker" ? "/.devcontainer" : "/";
|
|
74
|
+
}
|
|
85
75
|
function renderCiEnvironmentNeedCommand(need) {
|
|
86
|
-
if (need.kind === "playwright-browser-assets" &&
|
|
76
|
+
if (need.kind === "playwright-browser-assets" &&
|
|
77
|
+
need.owner.path === "apps/web") {
|
|
87
78
|
return `pnpm --filter ./apps/web exec playwright install --with-deps ${need.browser}`;
|
|
88
79
|
}
|
|
89
80
|
return `pnpm exec playwright install --with-deps ${need.browser}`;
|
|
90
81
|
}
|
|
91
|
-
export function projectTsLibGithubCheckWorkflow() {
|
|
92
|
-
return projectCheckWorkflow({ checkPlan: planNodeChecks("ts-lib") });
|
|
93
|
-
}
|
|
94
|
-
export function projectRustBinGithubCheckWorkflow() {
|
|
95
|
-
return projectCheckWorkflow({ checkPlan: planRustBinChecks(), rustToolchain: true });
|
|
96
|
-
}
|