@ykdz/template 0.0.5 → 0.0.6
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/dist/src/cli.js +65 -32
- package/dist/src/declarations.js +2 -2
- package/dist/src/generated-scenarios.d.ts +62 -0
- package/dist/src/generated-scenarios.js +361 -0
- package/dist/src/package-addition.js +22 -29
- package/dist/src/preset-projection.d.ts +1 -0
- package/dist/src/preset-source.d.ts +430 -0
- package/dist/src/preset-source.js +940 -0
- package/dist/src/projection-capabilities.d.ts +67 -0
- package/dist/src/projection-capabilities.js +2207 -0
- package/dist/src/renderer.js +19 -1
- package/dist/src/template-boundary-check.d.ts +7 -3
- package/dist/src/template-boundary-check.js +104 -10
- package/package.json +3 -2
- package/templates/preset-source.json +499 -0
- package/templates/vue-app/playwright.package-addition.config.ts +22 -0
- package/dist/templates/hono-api/projection.d.ts +0 -6
- package/dist/templates/hono-api/projection.js +0 -559
- package/dist/templates/projection-plans.d.ts +0 -7
- package/dist/templates/projection-plans.js +0 -94
- package/dist/templates/registry.d.ts +0 -7
- package/dist/templates/registry.js +0 -45
- package/dist/templates/rust-bin/projection.d.ts +0 -9
- package/dist/templates/rust-bin/projection.js +0 -400
- package/dist/templates/ts-lib/projection.d.ts +0 -6
- package/dist/templates/ts-lib/projection.js +0 -536
- package/dist/templates/vue-app/projection.d.ts +0 -6
- package/dist/templates/vue-app/projection.js +0 -766
- package/dist/templates/vue-hono-app/projection.d.ts +0 -8
- package/dist/templates/vue-hono-app/projection.js +0 -713
package/dist/src/renderer.js
CHANGED
|
@@ -98,6 +98,14 @@ const devcontainerJsonRootKeyOrder = [
|
|
|
98
98
|
"image",
|
|
99
99
|
"customizations",
|
|
100
100
|
];
|
|
101
|
+
const tsconfigJsonRootKeyOrder = [
|
|
102
|
+
"extends",
|
|
103
|
+
"compilerOptions",
|
|
104
|
+
"files",
|
|
105
|
+
"references",
|
|
106
|
+
"include",
|
|
107
|
+
"exclude",
|
|
108
|
+
];
|
|
101
109
|
function compareJsonKeys(left, right, pathSegments, rootKeyOrder) {
|
|
102
110
|
if (pathSegments.length === 0 && rootKeyOrder) {
|
|
103
111
|
const leftOrder = rootKeyOrder.get(left) ?? Number.POSITIVE_INFINITY;
|
|
@@ -112,6 +120,9 @@ function rootKeyOrderForPath(toPath) {
|
|
|
112
120
|
if (path.basename(toPath) === "package.json") {
|
|
113
121
|
return new Map(packageJsonRootKeyOrder.map((key, index) => [key, index]));
|
|
114
122
|
}
|
|
123
|
+
if (/^tsconfig(?:\..*)?\.json$/.test(path.basename(toPath))) {
|
|
124
|
+
return new Map(tsconfigJsonRootKeyOrder.map((key, index) => [key, index]));
|
|
125
|
+
}
|
|
115
126
|
if (toPath.split(path.sep).join("/") === ".devcontainer/devcontainer.json") {
|
|
116
127
|
return new Map(devcontainerJsonRootKeyOrder.map((key, index) => [key, index]));
|
|
117
128
|
}
|
|
@@ -200,6 +211,13 @@ function assertFoundationTextPath(relativePath) {
|
|
|
200
211
|
}
|
|
201
212
|
throw new Error(`Text output is limited to foundation files: ${relativePath}`);
|
|
202
213
|
}
|
|
214
|
+
function assertTextTemplatePath(relativePath) {
|
|
215
|
+
const normalizedPath = relativePath.split(path.sep).join("/");
|
|
216
|
+
if (/^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+\/(?:playwright|vite|vitest|oxfmt|oxlint)\.config\.ts$/.test(normalizedPath)) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
assertFoundationTextPath(relativePath);
|
|
220
|
+
}
|
|
203
221
|
async function renderWriteText(operation, options) {
|
|
204
222
|
const toPath = expandOperationPath(operation.to, options);
|
|
205
223
|
assertFoundationTextPath(toPath);
|
|
@@ -250,7 +268,7 @@ async function renderWriteTextTemplate(operation, options) {
|
|
|
250
268
|
throw new Error(`Unknown renderer source root: ${operation.sourceRoot}`);
|
|
251
269
|
}
|
|
252
270
|
const toPath = expandOperationPath(operation.to, options);
|
|
253
|
-
|
|
271
|
+
assertTextTemplatePath(toPath);
|
|
254
272
|
const from = resolveContainedPath(sourceRoot, expandTemplatePath(operation.from, options.variables ?? {}));
|
|
255
273
|
const to = resolveContainedPath(options.targetRoot, toPath);
|
|
256
274
|
const sourceText = await readFile(from, "utf8");
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { PresetProjectionPlan } from "./preset-projection.js";
|
|
2
1
|
import type { RenderOperation } from "./renderer.js";
|
|
3
2
|
export type TemplateBoundaryDebt = {
|
|
4
3
|
readonly preset: string;
|
|
@@ -17,7 +16,11 @@ export type TemplateBoundaryViolation = {
|
|
|
17
16
|
export type TemplateBoundaryCheckProjection = {
|
|
18
17
|
readonly name: string;
|
|
19
18
|
readonly sourceFilePath: string;
|
|
20
|
-
readonly plan:
|
|
19
|
+
readonly plan: {
|
|
20
|
+
readonly sourceRoot: string;
|
|
21
|
+
readonly sourceRoots?: Record<string, string>;
|
|
22
|
+
readonly operations: readonly RenderOperation[];
|
|
23
|
+
};
|
|
21
24
|
};
|
|
22
25
|
export type TemplateBoundaryCheckResult = {
|
|
23
26
|
readonly ok: boolean;
|
|
@@ -27,8 +30,9 @@ export type TemplateBoundaryCheckResult = {
|
|
|
27
30
|
};
|
|
28
31
|
export type CheckTemplateSourceBoundaryOptions = {
|
|
29
32
|
readonly projections: readonly TemplateBoundaryCheckProjection[];
|
|
33
|
+
readonly manifestReferencedSourceFiles?: readonly string[];
|
|
30
34
|
readonly allowlist?: readonly TemplateBoundaryDebt[];
|
|
31
35
|
};
|
|
32
36
|
export declare const templateBoundaryDebtAllowlist: readonly TemplateBoundaryDebt[];
|
|
33
37
|
export declare function isProtectedGeneratedPath(generatedPath: string): boolean;
|
|
34
|
-
export declare function checkTemplateSourceBoundary({ projections, allowlist, }: CheckTemplateSourceBoundaryOptions): Promise<TemplateBoundaryCheckResult>;
|
|
38
|
+
export declare function checkTemplateSourceBoundary({ projections, manifestReferencedSourceFiles, allowlist, }: CheckTemplateSourceBoundaryOptions): Promise<TemplateBoundaryCheckResult>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
2
3
|
import ts from "typescript";
|
|
3
4
|
const inlineOperationKinds = new Set([
|
|
4
5
|
"writeText",
|
|
@@ -18,6 +19,14 @@ function isInlineProtectedOperation(operation) {
|
|
|
18
19
|
inlineOperationKinds.has(operation.kind) &&
|
|
19
20
|
isProtectedGeneratedPath(generatedPath));
|
|
20
21
|
}
|
|
22
|
+
function isProtectedSourceBackedOperation(operation) {
|
|
23
|
+
const generatedPath = operationTarget(operation);
|
|
24
|
+
return (generatedPath !== undefined &&
|
|
25
|
+
(operation.kind === "copyFile" ||
|
|
26
|
+
operation.kind === "writeTextTemplate" ||
|
|
27
|
+
operation.kind === "writeTextFromFragments") &&
|
|
28
|
+
isProtectedGeneratedPath(generatedPath));
|
|
29
|
+
}
|
|
21
30
|
export function isProtectedGeneratedPath(generatedPath) {
|
|
22
31
|
const normalizedPath = generatedPath.split("\\").join("/");
|
|
23
32
|
const fileName = normalizedPath.split("/").at(-1);
|
|
@@ -36,20 +45,28 @@ export function isProtectedGeneratedPath(generatedPath) {
|
|
|
36
45
|
fileName === "vite.config.ts" ||
|
|
37
46
|
fileName === "vitest.config.ts");
|
|
38
47
|
}
|
|
39
|
-
function
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
48
|
+
function expressionStringValue(expression) {
|
|
49
|
+
if (ts.isParenthesizedExpression(expression) ||
|
|
50
|
+
ts.isAsExpression(expression) ||
|
|
51
|
+
ts.isSatisfiesExpression(expression) ||
|
|
52
|
+
ts.isTypeAssertionExpression(expression)) {
|
|
53
|
+
return expressionStringValue(expression.expression);
|
|
43
54
|
}
|
|
44
|
-
if (ts.isStringLiteralLike(
|
|
45
|
-
return
|
|
55
|
+
if (ts.isStringLiteralLike(expression)) {
|
|
56
|
+
return expression.text;
|
|
46
57
|
}
|
|
47
|
-
if (ts.isNoSubstitutionTemplateLiteral(
|
|
48
|
-
ts.isTemplateExpression(
|
|
49
|
-
return
|
|
58
|
+
if (ts.isNoSubstitutionTemplateLiteral(expression) ||
|
|
59
|
+
ts.isTemplateExpression(expression)) {
|
|
60
|
+
return expression.getText();
|
|
50
61
|
}
|
|
51
62
|
return undefined;
|
|
52
63
|
}
|
|
64
|
+
function propertyStringValue(objectLiteral, name) {
|
|
65
|
+
const initializer = propertyInitializer(objectLiteral, name);
|
|
66
|
+
return initializer === undefined
|
|
67
|
+
? undefined
|
|
68
|
+
: expressionStringValue(initializer);
|
|
69
|
+
}
|
|
53
70
|
function propertyInitializer(objectLiteral, name) {
|
|
54
71
|
const property = objectLiteral.properties.find((candidate) => {
|
|
55
72
|
return (ts.isPropertyAssignment(candidate) &&
|
|
@@ -270,6 +287,30 @@ function findOwningFunctionsForOperation(sourceFile, operation) {
|
|
|
270
287
|
}
|
|
271
288
|
return ["<unknown>"];
|
|
272
289
|
}
|
|
290
|
+
function findOwningFunctionsForProtectedOperation(sourceFile, operation) {
|
|
291
|
+
const generatedPath = operationTarget(operation);
|
|
292
|
+
if (generatedPath === undefined) {
|
|
293
|
+
return ["<unknown>"];
|
|
294
|
+
}
|
|
295
|
+
const targetPath = generatedPath;
|
|
296
|
+
const owners = new Set();
|
|
297
|
+
function visit(node) {
|
|
298
|
+
if (!ts.isObjectLiteralExpression(node)) {
|
|
299
|
+
ts.forEachChild(node, visit);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
if (propertyStringValue(node, "kind") === operation.kind &&
|
|
303
|
+
propertyPathMatches(node, "to", targetPath)) {
|
|
304
|
+
owners.add(owningFunctionName(node));
|
|
305
|
+
}
|
|
306
|
+
ts.forEachChild(node, visit);
|
|
307
|
+
}
|
|
308
|
+
visit(sourceFile);
|
|
309
|
+
if (owners.size > 0) {
|
|
310
|
+
return [...owners];
|
|
311
|
+
}
|
|
312
|
+
return ["<unknown>"];
|
|
313
|
+
}
|
|
273
314
|
function isPropertyAccess(expression, objectName, propertyName) {
|
|
274
315
|
return (ts.isPropertyAccessExpression(expression) &&
|
|
275
316
|
ts.isIdentifier(expression.expression) &&
|
|
@@ -392,7 +433,49 @@ function isAllowedStructuralMachineDeclaration(sourceFile, operation) {
|
|
|
392
433
|
function allowlistKey(preset, generatedPath, owningFunction) {
|
|
393
434
|
return `${preset}\0${owningFunction}\0${generatedPath}`;
|
|
394
435
|
}
|
|
395
|
-
|
|
436
|
+
function resolveOperationSourceRoot(projection, sourceRootName) {
|
|
437
|
+
if (sourceRootName === undefined) {
|
|
438
|
+
return projection.plan.sourceRoot;
|
|
439
|
+
}
|
|
440
|
+
return projection.plan.sourceRoots?.[sourceRootName];
|
|
441
|
+
}
|
|
442
|
+
function sourceBackedOperationSourceFiles(projection, operation) {
|
|
443
|
+
if (operation.kind === "writeTextFromFragments") {
|
|
444
|
+
return operation.fragments.map((fragment) => {
|
|
445
|
+
const root = resolveOperationSourceRoot(projection, fragment.sourceRoot);
|
|
446
|
+
return root === undefined
|
|
447
|
+
? `missing-source-root:${fragment.sourceRoot}`
|
|
448
|
+
: path.resolve(root, fragment.from);
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
const root = resolveOperationSourceRoot(projection, operation.sourceRoot);
|
|
452
|
+
return [
|
|
453
|
+
root === undefined
|
|
454
|
+
? `missing-source-root:${operation.sourceRoot}`
|
|
455
|
+
: path.resolve(root, operation.from),
|
|
456
|
+
];
|
|
457
|
+
}
|
|
458
|
+
function operationUsesManifestReferencedSource(projection, operation, manifestReferencedSourceFiles) {
|
|
459
|
+
return sourceBackedOperationSourceFiles(projection, operation).every((sourceFile) => manifestReferencedSourceFiles.has(sourceFile));
|
|
460
|
+
}
|
|
461
|
+
function pushSourceBackedOperationViolations(options) {
|
|
462
|
+
const generatedPath = operationTarget(options.operation);
|
|
463
|
+
if (generatedPath === undefined) {
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
const owningFunctions = findOwningFunctionsForProtectedOperation(options.sourceFile, options.operation);
|
|
467
|
+
for (const owningFunction of owningFunctions) {
|
|
468
|
+
options.violations.push({
|
|
469
|
+
preset: options.projection.name,
|
|
470
|
+
generatedPath,
|
|
471
|
+
owningFunction,
|
|
472
|
+
operationKind: options.operation.kind,
|
|
473
|
+
sourceFilePath: options.projection.sourceFilePath,
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
export async function checkTemplateSourceBoundary({ projections, manifestReferencedSourceFiles = [], allowlist = [], }) {
|
|
478
|
+
const manifestReferencedSourceFileSet = new Set(manifestReferencedSourceFiles.map((sourceFile) => path.resolve(sourceFile)));
|
|
396
479
|
const allowed = new Map(allowlist.map((entry) => [
|
|
397
480
|
allowlistKey(entry.preset, entry.generatedPath, entry.owningFunction),
|
|
398
481
|
entry.reason,
|
|
@@ -405,6 +488,17 @@ export async function checkTemplateSourceBoundary({ projections, allowlist = [],
|
|
|
405
488
|
const sourceText = await readFile(projection.sourceFilePath, "utf8");
|
|
406
489
|
const sourceFile = ts.createSourceFile(projection.sourceFilePath, sourceText, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
407
490
|
for (const operation of projection.plan.operations) {
|
|
491
|
+
if (isProtectedSourceBackedOperation(operation)) {
|
|
492
|
+
if (!operationUsesManifestReferencedSource(projection, operation, manifestReferencedSourceFileSet)) {
|
|
493
|
+
pushSourceBackedOperationViolations({
|
|
494
|
+
projection,
|
|
495
|
+
sourceFile,
|
|
496
|
+
operation,
|
|
497
|
+
violations,
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
408
502
|
if (!isInlineProtectedOperation(operation)) {
|
|
409
503
|
continue;
|
|
410
504
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ykdz/template",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Project template generator for YKDZ starter projects.",
|
|
6
6
|
"keywords": [
|
|
@@ -53,9 +53,10 @@
|
|
|
53
53
|
},
|
|
54
54
|
"scripts": {
|
|
55
55
|
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|
|
56
|
-
"check": "pnpm run check:format && pnpm run typecheck && pnpm run test && pnpm run check:templates",
|
|
56
|
+
"check": "pnpm run check:format && pnpm run typecheck && pnpm run test && pnpm run check:generated && pnpm run check:templates",
|
|
57
57
|
"check:format": "oxfmt --check --config templates/shared/oxc/oxfmt.config.ts --ignore-path .oxfmtignore .",
|
|
58
58
|
"check:fixtures": "tsx scripts/check-fixtures.ts",
|
|
59
|
+
"check:generated": "tsx scripts/check-generated.ts",
|
|
59
60
|
"check:templates": "pnpm run check:templates:shared-oxc && pnpm run check:templates:github-yaml && pnpm run check:templates:boundary && pnpm run check:templates:static-source",
|
|
60
61
|
"check:toolchain:online": "tsx scripts/check-online-toolchain-resolution-contract.ts",
|
|
61
62
|
"check:templates:boundary": "tsx scripts/check-template-boundary.ts",
|