@ykdz/template 0.0.4 → 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 +79 -34
- package/dist/src/declarations.d.ts +36 -0
- package/dist/src/declarations.js +31 -2
- package/dist/src/generated-scenarios.d.ts +62 -0
- package/dist/src/generated-scenarios.js +361 -0
- package/dist/src/module-graph.d.ts +1 -1
- package/dist/src/module-graph.js +10 -2
- package/dist/src/package-addition-support.d.ts +5 -0
- package/dist/src/package-addition-support.js +4 -0
- package/dist/src/package-addition.d.ts +1 -0
- package/dist/src/package-addition.js +409 -69
- package/dist/src/package-linking.d.ts +55 -0
- package/dist/src/package-linking.js +116 -0
- package/dist/src/preset-projection.d.ts +4 -1
- 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 +20 -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/hono-api/src/server.ts +1 -1
- package/templates/hono-api/test/app.test.ts +1 -1
- package/templates/preset-source.json +499 -0
- package/templates/ts-lib/src/index.ts +2 -2
- package/templates/ts-lib/src/name-schema.ts +3 -0
- package/templates/vue-app/playwright.package-addition.config.ts +22 -0
- package/templates/vue-app/src/App.vue +1 -1
- package/templates/vue-app/test/app.test.ts +1 -1
- package/templates/vue-hono-app/web/src/App.vue +2 -2
- package/templates/vue-hono-app/web/test/app.test.ts +1 -1
- package/dist/templates/hono-api/projection.d.ts +0 -6
- package/dist/templates/hono-api/projection.js +0 -538
- 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 -42
- package/dist/templates/rust-bin/projection.d.ts +0 -9
- package/dist/templates/rust-bin/projection.js +0 -390
- package/dist/templates/ts-lib/projection.d.ts +0 -6
- package/dist/templates/ts-lib/projection.js +0 -523
- package/dist/templates/vue-app/projection.d.ts +0 -6
- package/dist/templates/vue-app/projection.js +0 -757
- package/dist/templates/vue-hono-app/projection.d.ts +0 -8
- package/dist/templates/vue-hono-app/projection.js +0 -693
package/dist/src/renderer.js
CHANGED
|
@@ -82,6 +82,7 @@ const packageJsonRootKeyOrder = [
|
|
|
82
82
|
"files",
|
|
83
83
|
"type",
|
|
84
84
|
"types",
|
|
85
|
+
"imports",
|
|
85
86
|
"exports",
|
|
86
87
|
"scripts",
|
|
87
88
|
"dependencies",
|
|
@@ -97,6 +98,14 @@ const devcontainerJsonRootKeyOrder = [
|
|
|
97
98
|
"image",
|
|
98
99
|
"customizations",
|
|
99
100
|
];
|
|
101
|
+
const tsconfigJsonRootKeyOrder = [
|
|
102
|
+
"extends",
|
|
103
|
+
"compilerOptions",
|
|
104
|
+
"files",
|
|
105
|
+
"references",
|
|
106
|
+
"include",
|
|
107
|
+
"exclude",
|
|
108
|
+
];
|
|
100
109
|
function compareJsonKeys(left, right, pathSegments, rootKeyOrder) {
|
|
101
110
|
if (pathSegments.length === 0 && rootKeyOrder) {
|
|
102
111
|
const leftOrder = rootKeyOrder.get(left) ?? Number.POSITIVE_INFINITY;
|
|
@@ -111,6 +120,9 @@ function rootKeyOrderForPath(toPath) {
|
|
|
111
120
|
if (path.basename(toPath) === "package.json") {
|
|
112
121
|
return new Map(packageJsonRootKeyOrder.map((key, index) => [key, index]));
|
|
113
122
|
}
|
|
123
|
+
if (/^tsconfig(?:\..*)?\.json$/.test(path.basename(toPath))) {
|
|
124
|
+
return new Map(tsconfigJsonRootKeyOrder.map((key, index) => [key, index]));
|
|
125
|
+
}
|
|
114
126
|
if (toPath.split(path.sep).join("/") === ".devcontainer/devcontainer.json") {
|
|
115
127
|
return new Map(devcontainerJsonRootKeyOrder.map((key, index) => [key, index]));
|
|
116
128
|
}
|
|
@@ -199,6 +211,13 @@ function assertFoundationTextPath(relativePath) {
|
|
|
199
211
|
}
|
|
200
212
|
throw new Error(`Text output is limited to foundation files: ${relativePath}`);
|
|
201
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
|
+
}
|
|
202
221
|
async function renderWriteText(operation, options) {
|
|
203
222
|
const toPath = expandOperationPath(operation.to, options);
|
|
204
223
|
assertFoundationTextPath(toPath);
|
|
@@ -249,7 +268,7 @@ async function renderWriteTextTemplate(operation, options) {
|
|
|
249
268
|
throw new Error(`Unknown renderer source root: ${operation.sourceRoot}`);
|
|
250
269
|
}
|
|
251
270
|
const toPath = expandOperationPath(operation.to, options);
|
|
252
|
-
|
|
271
|
+
assertTextTemplatePath(toPath);
|
|
253
272
|
const from = resolveContainedPath(sourceRoot, expandTemplatePath(operation.from, options.variables ?? {}));
|
|
254
273
|
const to = resolveContainedPath(options.targetRoot, toPath);
|
|
255
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:
|
|
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",
|