@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
|
@@ -21,6 +21,21 @@ export type WriteTextOperation = {
|
|
|
21
21
|
to: string;
|
|
22
22
|
text: string;
|
|
23
23
|
};
|
|
24
|
+
export type WriteTextFromFragmentsOperation = {
|
|
25
|
+
kind: "writeTextFromFragments";
|
|
26
|
+
to: string;
|
|
27
|
+
fragments: readonly {
|
|
28
|
+
from: string;
|
|
29
|
+
sourceRoot?: string;
|
|
30
|
+
}[];
|
|
31
|
+
};
|
|
32
|
+
export type WriteTextTemplateOperation = {
|
|
33
|
+
kind: "writeTextTemplate";
|
|
34
|
+
from: string;
|
|
35
|
+
to: string;
|
|
36
|
+
sourceRoot?: string;
|
|
37
|
+
replacements: Record<string, string>;
|
|
38
|
+
};
|
|
24
39
|
export type SetExecutableOperation = {
|
|
25
40
|
kind: "setExecutable";
|
|
26
41
|
path: string;
|
|
@@ -32,7 +47,7 @@ export type ReplaceAnchorsOperation = {
|
|
|
32
47
|
language: "typescript";
|
|
33
48
|
replacements: Record<string, string>;
|
|
34
49
|
};
|
|
35
|
-
export type RenderOperation = CopyFileOperation | WriteJsonOperation | MergeJsonOperation | WriteTextOperation | SetExecutableOperation | ReplaceAnchorsOperation;
|
|
50
|
+
export type RenderOperation = CopyFileOperation | WriteJsonOperation | MergeJsonOperation | WriteTextOperation | WriteTextFromFragmentsOperation | WriteTextTemplateOperation | SetExecutableOperation | ReplaceAnchorsOperation;
|
|
36
51
|
export type RenderProjectOptions = {
|
|
37
52
|
sourceRoot: string;
|
|
38
53
|
sourceRoots?: Record<string, string>;
|
|
@@ -43,4 +58,3 @@ export type RenderProjectOptions = {
|
|
|
43
58
|
export declare function renderProject(options: RenderProjectOptions): Promise<void>;
|
|
44
59
|
export declare function renderNewProject(options: RenderProjectOptions): Promise<void>;
|
|
45
60
|
export {};
|
|
46
|
-
//# sourceMappingURL=renderer.d.ts.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { constants } from "node:fs";
|
|
2
|
-
import { chmod, copyFile, mkdir, mkdtemp, readFile, readdir, rename, rm, rmdir, stat, writeFile } from "node:fs/promises";
|
|
2
|
+
import { chmod, copyFile, mkdir, mkdtemp, readFile, readdir, rename, rm, rmdir, stat, writeFile, } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
function expandTemplatePath(templatePath, variables) {
|
|
5
5
|
return templatePath.replaceAll(/\{\{([A-Za-z][A-Za-z0-9_]*)\}\}/g, (_, name) => {
|
|
@@ -19,7 +19,8 @@ function resolveContainedPath(root, relativePath) {
|
|
|
19
19
|
}
|
|
20
20
|
const resolvedRoot = path.resolve(root);
|
|
21
21
|
const resolvedPath = path.resolve(resolvedRoot, relativePath);
|
|
22
|
-
const insideRoot = resolvedPath === resolvedRoot ||
|
|
22
|
+
const insideRoot = resolvedPath === resolvedRoot ||
|
|
23
|
+
resolvedPath.startsWith(`${resolvedRoot}${path.sep}`);
|
|
23
24
|
if (!insideRoot) {
|
|
24
25
|
throw new Error(`Renderer path escapes its root: ${relativePath}`);
|
|
25
26
|
}
|
|
@@ -47,7 +48,7 @@ function isRecord(value) {
|
|
|
47
48
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
48
49
|
}
|
|
49
50
|
function isScalarJsonValue(value) {
|
|
50
|
-
return value === null || ["boolean", "number", "string"].includes(typeof value);
|
|
51
|
+
return (value === null || ["boolean", "number", "string"].includes(typeof value));
|
|
51
52
|
}
|
|
52
53
|
function serializeJsonValue(value, indentation, pathSegments, multilineArrays, rootKeyOrder) {
|
|
53
54
|
if (Array.isArray(value)) {
|
|
@@ -88,7 +89,13 @@ const packageJsonRootKeyOrder = [
|
|
|
88
89
|
"peerDependencies",
|
|
89
90
|
"optionalDependencies",
|
|
90
91
|
"engines",
|
|
91
|
-
"packageManager"
|
|
92
|
+
"packageManager",
|
|
93
|
+
];
|
|
94
|
+
const devcontainerJsonRootKeyOrder = [
|
|
95
|
+
"name",
|
|
96
|
+
"build",
|
|
97
|
+
"image",
|
|
98
|
+
"customizations",
|
|
92
99
|
];
|
|
93
100
|
function compareJsonKeys(left, right, pathSegments, rootKeyOrder) {
|
|
94
101
|
if (pathSegments.length === 0 && rootKeyOrder) {
|
|
@@ -101,10 +108,13 @@ function compareJsonKeys(left, right, pathSegments, rootKeyOrder) {
|
|
|
101
108
|
return left.localeCompare(right);
|
|
102
109
|
}
|
|
103
110
|
function rootKeyOrderForPath(toPath) {
|
|
104
|
-
if (path.basename(toPath)
|
|
105
|
-
return
|
|
111
|
+
if (path.basename(toPath) === "package.json") {
|
|
112
|
+
return new Map(packageJsonRootKeyOrder.map((key, index) => [key, index]));
|
|
113
|
+
}
|
|
114
|
+
if (toPath.split(path.sep).join("/") === ".devcontainer/devcontainer.json") {
|
|
115
|
+
return new Map(devcontainerJsonRootKeyOrder.map((key, index) => [key, index]));
|
|
106
116
|
}
|
|
107
|
-
return
|
|
117
|
+
return undefined;
|
|
108
118
|
}
|
|
109
119
|
function serializeJson(value, multilineArrays = [], rootKeyOrder) {
|
|
110
120
|
return `${serializeJsonValue(value, 0, [], new Set(multilineArrays), rootKeyOrder)}\n`;
|
|
@@ -151,7 +161,7 @@ const foundationTextFiles = new Set([
|
|
|
151
161
|
"README",
|
|
152
162
|
"README.md",
|
|
153
163
|
"LICENSE",
|
|
154
|
-
"CHANGELOG.md"
|
|
164
|
+
"CHANGELOG.md",
|
|
155
165
|
]);
|
|
156
166
|
function assertFoundationTextPath(relativePath) {
|
|
157
167
|
const normalizedPath = relativePath.split(path.sep).join("/");
|
|
@@ -162,11 +172,20 @@ function assertFoundationTextPath(relativePath) {
|
|
|
162
172
|
if (isRootLevel && relativePath.endsWith(".md")) {
|
|
163
173
|
return;
|
|
164
174
|
}
|
|
165
|
-
if (isRootLevel &&
|
|
175
|
+
if (isRootLevel &&
|
|
176
|
+
(relativePath.endsWith(".yaml") || relativePath.endsWith(".yml"))) {
|
|
166
177
|
return;
|
|
167
178
|
}
|
|
168
179
|
if (isRootLevel &&
|
|
169
|
-
[
|
|
180
|
+
[
|
|
181
|
+
"Cargo.toml",
|
|
182
|
+
"Cargo.lock",
|
|
183
|
+
"rustfmt.toml",
|
|
184
|
+
"rust-toolchain.toml",
|
|
185
|
+
].includes(normalizedPath)) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if (/^packages\/[A-Za-z0-9._-]+\/(?:Cargo\.toml|Cargo\.lock|rustfmt\.toml)$/.test(normalizedPath)) {
|
|
170
189
|
return;
|
|
171
190
|
}
|
|
172
191
|
if (/^\.github\/workflows\/[A-Za-z0-9._-]+\.ya?ml$/.test(normalizedPath)) {
|
|
@@ -175,6 +194,9 @@ function assertFoundationTextPath(relativePath) {
|
|
|
175
194
|
if (/^\.github\/dependabot\.ya?ml$/.test(normalizedPath)) {
|
|
176
195
|
return;
|
|
177
196
|
}
|
|
197
|
+
if (normalizedPath === ".devcontainer/Dockerfile") {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
178
200
|
throw new Error(`Text output is limited to foundation files: ${relativePath}`);
|
|
179
201
|
}
|
|
180
202
|
async function renderWriteText(operation, options) {
|
|
@@ -184,6 +206,56 @@ async function renderWriteText(operation, options) {
|
|
|
184
206
|
await mkdir(path.dirname(to), { recursive: true });
|
|
185
207
|
await writeGeneratedFile(to, operation.text);
|
|
186
208
|
}
|
|
209
|
+
async function renderWriteTextFromFragments(operation, options) {
|
|
210
|
+
const toPath = expandOperationPath(operation.to, options);
|
|
211
|
+
assertFoundationTextPath(toPath);
|
|
212
|
+
const to = resolveContainedPath(options.targetRoot, toPath);
|
|
213
|
+
const texts = await Promise.all(operation.fragments.map(async (fragment) => {
|
|
214
|
+
const sourceRoot = fragment.sourceRoot === undefined
|
|
215
|
+
? options.sourceRoot
|
|
216
|
+
: options.sourceRoots?.[fragment.sourceRoot];
|
|
217
|
+
if (sourceRoot === undefined) {
|
|
218
|
+
throw new Error(`Unknown renderer source root: ${fragment.sourceRoot}`);
|
|
219
|
+
}
|
|
220
|
+
const fromPath = expandTemplatePath(fragment.from, options.variables ?? {});
|
|
221
|
+
const from = resolveContainedPath(sourceRoot, fromPath);
|
|
222
|
+
return readFile(from, "utf8");
|
|
223
|
+
}));
|
|
224
|
+
await mkdir(path.dirname(to), { recursive: true });
|
|
225
|
+
await writeGeneratedFile(to, texts.map((text) => text.trimEnd()).join("\n\n") + "\n");
|
|
226
|
+
}
|
|
227
|
+
function replaceTextTemplateVariables(sourceText, replacements) {
|
|
228
|
+
const used = new Set();
|
|
229
|
+
const rendered = sourceText.replaceAll(/\{\{([A-Za-z][A-Za-z0-9_]*)\}\}/g, (_placeholder, name) => {
|
|
230
|
+
const replacement = replacements[name];
|
|
231
|
+
if (replacement === undefined) {
|
|
232
|
+
throw new Error(`Missing text template variable: ${name}`);
|
|
233
|
+
}
|
|
234
|
+
used.add(name);
|
|
235
|
+
return replacement;
|
|
236
|
+
});
|
|
237
|
+
for (const name of Object.keys(replacements)) {
|
|
238
|
+
if (!used.has(name)) {
|
|
239
|
+
throw new Error(`Unused text template variable: ${name}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return rendered;
|
|
243
|
+
}
|
|
244
|
+
async function renderWriteTextTemplate(operation, options) {
|
|
245
|
+
const sourceRoot = operation.sourceRoot === undefined
|
|
246
|
+
? options.sourceRoot
|
|
247
|
+
: options.sourceRoots?.[operation.sourceRoot];
|
|
248
|
+
if (sourceRoot === undefined) {
|
|
249
|
+
throw new Error(`Unknown renderer source root: ${operation.sourceRoot}`);
|
|
250
|
+
}
|
|
251
|
+
const toPath = expandOperationPath(operation.to, options);
|
|
252
|
+
assertFoundationTextPath(toPath);
|
|
253
|
+
const from = resolveContainedPath(sourceRoot, expandTemplatePath(operation.from, options.variables ?? {}));
|
|
254
|
+
const to = resolveContainedPath(options.targetRoot, toPath);
|
|
255
|
+
const sourceText = await readFile(from, "utf8");
|
|
256
|
+
await mkdir(path.dirname(to), { recursive: true });
|
|
257
|
+
await writeGeneratedFile(to, replaceTextTemplateVariables(sourceText, operation.replacements));
|
|
258
|
+
}
|
|
187
259
|
async function renderSetExecutable(operation, options) {
|
|
188
260
|
const filePath = resolveContainedPath(options.targetRoot, expandOperationPath(operation.path, options));
|
|
189
261
|
const currentMode = (await stat(filePath)).mode;
|
|
@@ -224,7 +296,7 @@ async function findTypeScriptAnchorRanges(sourceText) {
|
|
|
224
296
|
anchors.push({
|
|
225
297
|
name,
|
|
226
298
|
start: comment.pos,
|
|
227
|
-
end: comment.end
|
|
299
|
+
end: comment.end,
|
|
228
300
|
});
|
|
229
301
|
}
|
|
230
302
|
ts.forEachChild(node, collectNodeAnchors);
|
|
@@ -296,7 +368,10 @@ async function copyGeneratedFile(from, to) {
|
|
|
296
368
|
}
|
|
297
369
|
async function writeGeneratedFile(to, text, overwrite = false) {
|
|
298
370
|
try {
|
|
299
|
-
await writeFile(to, text, {
|
|
371
|
+
await writeFile(to, text, {
|
|
372
|
+
encoding: "utf8",
|
|
373
|
+
flag: overwrite ? "w" : "wx",
|
|
374
|
+
});
|
|
300
375
|
}
|
|
301
376
|
catch (error) {
|
|
302
377
|
if (isNodeError(error) && error.code === "EEXIST") {
|
|
@@ -323,6 +398,14 @@ export async function renderProject(options) {
|
|
|
323
398
|
await renderWriteText(operation, options);
|
|
324
399
|
continue;
|
|
325
400
|
}
|
|
401
|
+
if (operation.kind === "writeTextFromFragments") {
|
|
402
|
+
await renderWriteTextFromFragments(operation, options);
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
if (operation.kind === "writeTextTemplate") {
|
|
406
|
+
await renderWriteTextTemplate(operation, options);
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
326
409
|
if (operation.kind === "setExecutable") {
|
|
327
410
|
await renderSetExecutable(operation, options);
|
|
328
411
|
continue;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function packageTemplateRoot(moduleDir: string, ...segments: string[]): string;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export function packageTemplateRoot(moduleDir, ...segments) {
|
|
4
|
+
const candidates = [
|
|
5
|
+
path.join(moduleDir, "..", "..", "templates", ...segments),
|
|
6
|
+
path.join(moduleDir, "..", "templates", ...segments),
|
|
7
|
+
path.join(moduleDir, "..", "..", "..", "templates", ...segments),
|
|
8
|
+
];
|
|
9
|
+
const found = candidates.find((candidate) => existsSync(candidate));
|
|
10
|
+
return found ?? candidates[0];
|
|
11
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { PresetProjectionPlan } from "./preset-projection.js";
|
|
2
|
+
import type { RenderOperation } from "./renderer.js";
|
|
3
|
+
export type TemplateBoundaryDebt = {
|
|
4
|
+
readonly preset: string;
|
|
5
|
+
readonly generatedPath: string;
|
|
6
|
+
readonly owningFunction: string;
|
|
7
|
+
readonly reason: string;
|
|
8
|
+
};
|
|
9
|
+
export type TemplateBoundaryViolation = {
|
|
10
|
+
readonly preset: string;
|
|
11
|
+
readonly generatedPath: string;
|
|
12
|
+
readonly owningFunction: string;
|
|
13
|
+
readonly operationKind: RenderOperation["kind"];
|
|
14
|
+
readonly sourceFilePath: string;
|
|
15
|
+
readonly allowlistReason?: string;
|
|
16
|
+
};
|
|
17
|
+
export type TemplateBoundaryCheckProjection = {
|
|
18
|
+
readonly name: string;
|
|
19
|
+
readonly sourceFilePath: string;
|
|
20
|
+
readonly plan: PresetProjectionPlan;
|
|
21
|
+
};
|
|
22
|
+
export type TemplateBoundaryCheckResult = {
|
|
23
|
+
readonly ok: boolean;
|
|
24
|
+
readonly violations: readonly TemplateBoundaryViolation[];
|
|
25
|
+
readonly allowlistedDebt: readonly TemplateBoundaryViolation[];
|
|
26
|
+
readonly unusedAllowlistEntries: readonly TemplateBoundaryDebt[];
|
|
27
|
+
};
|
|
28
|
+
export type CheckTemplateSourceBoundaryOptions = {
|
|
29
|
+
readonly projections: readonly TemplateBoundaryCheckProjection[];
|
|
30
|
+
readonly allowlist?: readonly TemplateBoundaryDebt[];
|
|
31
|
+
};
|
|
32
|
+
export declare const templateBoundaryDebtAllowlist: readonly TemplateBoundaryDebt[];
|
|
33
|
+
export declare function isProtectedGeneratedPath(generatedPath: string): boolean;
|
|
34
|
+
export declare function checkTemplateSourceBoundary({ projections, allowlist, }: CheckTemplateSourceBoundaryOptions): Promise<TemplateBoundaryCheckResult>;
|