@wp-typia/project-tools 0.16.7 → 0.16.9
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 +26 -1
- package/dist/runtime/block-generator-service.d.ts +9 -1
- package/dist/runtime/block-generator-service.js +137 -16
- package/dist/runtime/block-generator-tool-contract.d.ts +93 -0
- package/dist/runtime/block-generator-tool-contract.js +157 -0
- package/dist/runtime/built-in-block-artifacts.js +171 -423
- package/dist/runtime/built-in-block-code-artifacts.js +96 -46
- package/dist/runtime/built-in-block-code-templates.d.ts +36 -0
- package/dist/runtime/built-in-block-code-templates.js +2233 -0
- package/dist/runtime/cli-add-block.d.ts +2 -1
- package/dist/runtime/cli-add-block.js +163 -25
- package/dist/runtime/cli-add-shared.d.ts +7 -0
- package/dist/runtime/cli-add-shared.js +4 -6
- package/dist/runtime/cli-add-workspace.js +56 -17
- package/dist/runtime/cli-core.d.ts +4 -0
- package/dist/runtime/cli-core.js +3 -0
- package/dist/runtime/cli-diagnostics.d.ts +58 -0
- package/dist/runtime/cli-diagnostics.js +101 -0
- package/dist/runtime/cli-doctor.d.ts +2 -1
- package/dist/runtime/cli-doctor.js +16 -5
- package/dist/runtime/cli-help.js +4 -4
- package/dist/runtime/cli-scaffold.d.ts +5 -1
- package/dist/runtime/cli-scaffold.js +138 -111
- package/dist/runtime/external-layer-selection.d.ts +14 -0
- package/dist/runtime/external-layer-selection.js +35 -0
- package/dist/runtime/index.d.ts +4 -2
- package/dist/runtime/index.js +2 -1
- package/dist/runtime/migration-render.d.ts +23 -1
- package/dist/runtime/migration-render.js +58 -10
- package/dist/runtime/migration-ui-capability.js +17 -8
- package/dist/runtime/migration-utils.d.ts +7 -6
- package/dist/runtime/migration-utils.js +76 -73
- package/dist/runtime/migrations.js +2 -2
- package/dist/runtime/object-utils.d.ts +8 -1
- package/dist/runtime/object-utils.js +21 -1
- package/dist/runtime/scaffold-apply-utils.d.ts +14 -2
- package/dist/runtime/scaffold-apply-utils.js +19 -6
- package/dist/runtime/scaffold-repository-reference.d.ts +22 -0
- package/dist/runtime/scaffold-repository-reference.js +119 -0
- package/dist/runtime/scaffold.d.ts +5 -1
- package/dist/runtime/scaffold.js +15 -37
- package/dist/runtime/template-builtins.d.ts +11 -1
- package/dist/runtime/template-builtins.js +118 -25
- package/dist/runtime/template-layers.d.ts +37 -0
- package/dist/runtime/template-layers.js +184 -0
- package/dist/runtime/template-render.d.ts +28 -2
- package/dist/runtime/template-render.js +122 -55
- package/dist/runtime/template-source.d.ts +23 -5
- package/dist/runtime/template-source.js +296 -217
- package/package.json +8 -3
- package/templates/_shared/base/src/validator-toolkit.ts.mustache +2 -2
- package/templates/_shared/compound/core/scripts/add-compound-child.ts.mustache +58 -12
- package/templates/_shared/migration-ui/common/src/migrations/helpers.ts +19 -47
- package/templates/_shared/migration-ui/common/src/migrations/index.ts +40 -11
|
@@ -14,6 +14,7 @@ import { stringifyBuiltInBlockJsonDocument, } from "./built-in-block-artifacts.j
|
|
|
14
14
|
import { OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, } from "./template-registry.js";
|
|
15
15
|
import { copyInterpolatedDirectory } from "./template-render.js";
|
|
16
16
|
import { formatInstallCommand, formatPackageExecCommand, formatRunScript, getPackageManager, transformPackageManagerText, } from "./package-managers.js";
|
|
17
|
+
import { replaceRepositoryReferencePlaceholders, resolveScaffoldRepositoryReference, } from "./scaffold-repository-reference.js";
|
|
17
18
|
const EPHEMERAL_NODE_MODULES_LINK_TYPE = process.platform === "win32" ? "junction" : "dir";
|
|
18
19
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
19
20
|
const LOCKFILES = {
|
|
@@ -265,7 +266,11 @@ export async function removeUnexpectedLockfiles(targetDir, packageManagerId) {
|
|
|
265
266
|
}
|
|
266
267
|
}));
|
|
267
268
|
}
|
|
268
|
-
|
|
269
|
+
/**
|
|
270
|
+
* Recursively normalizes generated text files for the selected package manager
|
|
271
|
+
* and repository reference.
|
|
272
|
+
*/
|
|
273
|
+
export async function replaceTextRecursively(targetDir, packageManagerId, { repositoryManifestPaths, repositoryReference, } = {}) {
|
|
269
274
|
const textExtensions = new Set([
|
|
270
275
|
".css",
|
|
271
276
|
".js",
|
|
@@ -278,6 +283,10 @@ export async function replaceTextRecursively(targetDir, packageManagerId) {
|
|
|
278
283
|
".tsx",
|
|
279
284
|
".txt",
|
|
280
285
|
]);
|
|
286
|
+
const resolvedRepositoryReference = repositoryReference ??
|
|
287
|
+
resolveScaffoldRepositoryReference({
|
|
288
|
+
manifestPaths: repositoryManifestPaths,
|
|
289
|
+
});
|
|
281
290
|
async function visit(currentPath) {
|
|
282
291
|
const stats = await fsp.stat(currentPath);
|
|
283
292
|
if (stats.isDirectory()) {
|
|
@@ -291,9 +300,7 @@ export async function replaceTextRecursively(targetDir, packageManagerId) {
|
|
|
291
300
|
return;
|
|
292
301
|
}
|
|
293
302
|
const content = await fsp.readFile(currentPath, "utf8");
|
|
294
|
-
const nextContent = transformPackageManagerText(content, packageManagerId)
|
|
295
|
-
.replace(/yourusername\/wp-typia-boilerplate/g, "imjlk/wp-typia")
|
|
296
|
-
.replace(/yourusername\/wp-typia/g, "imjlk/wp-typia");
|
|
303
|
+
const nextContent = replaceRepositoryReferencePlaceholders(transformPackageManagerText(content, packageManagerId), resolvedRepositoryReference);
|
|
297
304
|
if (nextContent !== content) {
|
|
298
305
|
await fsp.writeFile(currentPath, nextContent, "utf8");
|
|
299
306
|
}
|
|
@@ -344,7 +351,11 @@ export async function applyWorkspaceMigrationCapability(projectDir, packageManag
|
|
|
344
351
|
ensureMigrationDirectories(projectDir, []);
|
|
345
352
|
writeInitialMigrationScaffold(projectDir, "v1", []);
|
|
346
353
|
}
|
|
347
|
-
|
|
354
|
+
/**
|
|
355
|
+
* Applies a built-in scaffold into the target directory, including generated
|
|
356
|
+
* code artifacts, starter manifests, preset files, and placeholder rewrites.
|
|
357
|
+
*/
|
|
358
|
+
export async function applyBuiltInScaffoldProjectFiles({ projectDir, templateDir, templateId, variables, artifacts, codeArtifacts, readmeContent, gitignoreContent, allowExistingDir = false, packageManager, withMigrationUi = false, withTestPreset = false, withWpEnv = false, noInstall = false, installDependencies, repositoryReference, }) {
|
|
348
359
|
await ensureDirectory(projectDir, allowExistingDir);
|
|
349
360
|
await copyInterpolatedDirectory(templateDir, projectDir, variables);
|
|
350
361
|
if (codeArtifacts && codeArtifacts.length > 0) {
|
|
@@ -394,7 +405,9 @@ export async function applyBuiltInScaffoldProjectFiles({ projectDir, templateDir
|
|
|
394
405
|
});
|
|
395
406
|
await normalizePackageManagerFiles(projectDir, packageManager);
|
|
396
407
|
await removeUnexpectedLockfiles(projectDir, packageManager);
|
|
397
|
-
await replaceTextRecursively(projectDir, packageManager
|
|
408
|
+
await replaceTextRecursively(projectDir, packageManager, {
|
|
409
|
+
repositoryReference,
|
|
410
|
+
});
|
|
398
411
|
if (!noInstall) {
|
|
399
412
|
const installer = installDependencies ?? defaultInstallDependencies;
|
|
400
413
|
await installer({
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default scaffold repository reference used when no GitHub repository metadata
|
|
3
|
+
* can be resolved from the current runtime package manifests.
|
|
4
|
+
*/
|
|
5
|
+
export declare const DEFAULT_SCAFFOLD_REPOSITORY_REFERENCE = "imjlk/wp-typia";
|
|
6
|
+
/**
|
|
7
|
+
* Resolves the canonical scaffold repository reference in `owner/repo` format.
|
|
8
|
+
*
|
|
9
|
+
* The resolver checks candidate package manifests in priority order, extracts
|
|
10
|
+
* their `repository` field, and returns the first GitHub slug it can parse.
|
|
11
|
+
* When no candidate yields a GitHub repository reference, the fallback value
|
|
12
|
+
* is returned instead.
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolveScaffoldRepositoryReference({ fallback, manifestPaths, }?: {
|
|
15
|
+
fallback?: string;
|
|
16
|
+
manifestPaths?: readonly string[];
|
|
17
|
+
}): string;
|
|
18
|
+
/**
|
|
19
|
+
* Replaces legacy scaffold repository placeholders with a concrete
|
|
20
|
+
* `owner/repo` reference.
|
|
21
|
+
*/
|
|
22
|
+
export declare function replaceRepositoryReferencePlaceholders(source: string, repositoryReference: string): string;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { PROJECT_TOOLS_PACKAGE_ROOT } from "./template-registry.js";
|
|
5
|
+
const require = createRequire(import.meta.url);
|
|
6
|
+
/**
|
|
7
|
+
* Default scaffold repository reference used when no GitHub repository metadata
|
|
8
|
+
* can be resolved from the current runtime package manifests.
|
|
9
|
+
*/
|
|
10
|
+
export const DEFAULT_SCAFFOLD_REPOSITORY_REFERENCE = "imjlk/wp-typia";
|
|
11
|
+
function getErrorCode(error) {
|
|
12
|
+
return typeof error === "object" && error !== null && "code" in error
|
|
13
|
+
? String(error.code)
|
|
14
|
+
: undefined;
|
|
15
|
+
}
|
|
16
|
+
function readRepositoryPackageManifest(packageJsonPath) {
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
if (getErrorCode(error) === "ENOENT") {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function resolveInstalledPackageManifestPath(packageName) {
|
|
28
|
+
try {
|
|
29
|
+
return require.resolve(`${packageName}/package.json`);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
if (getErrorCode(error) === "MODULE_NOT_FOUND") {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function getRepositoryFieldValue(manifest) {
|
|
39
|
+
if (!manifest?.repository) {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
return typeof manifest.repository === "string"
|
|
43
|
+
? manifest.repository
|
|
44
|
+
: manifest.repository.url;
|
|
45
|
+
}
|
|
46
|
+
function parseRepositoryReference(value) {
|
|
47
|
+
const trimmed = value?.trim();
|
|
48
|
+
if (!trimmed) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
const githubShorthandMatch = trimmed.match(/^github:([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+?)(?:#.*)?$/u);
|
|
52
|
+
if (githubShorthandMatch) {
|
|
53
|
+
return `${githubShorthandMatch[1]}/${githubShorthandMatch[2]}`;
|
|
54
|
+
}
|
|
55
|
+
if (/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u.test(trimmed)) {
|
|
56
|
+
return trimmed;
|
|
57
|
+
}
|
|
58
|
+
const githubScpMatch = trimmed.match(/^git@github\.com:([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+?)(?:\.git)?(?:#.*)?$/u);
|
|
59
|
+
if (githubScpMatch) {
|
|
60
|
+
return `${githubScpMatch[1]}/${githubScpMatch[2]}`;
|
|
61
|
+
}
|
|
62
|
+
const normalizedValue = trimmed
|
|
63
|
+
.replace(/^git\+/u, "")
|
|
64
|
+
.replace(/#.*$/u, "");
|
|
65
|
+
let parsedUrl;
|
|
66
|
+
try {
|
|
67
|
+
parsedUrl = new URL(normalizedValue);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
if (parsedUrl.hostname !== "github.com") {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const pathSegments = parsedUrl.pathname
|
|
76
|
+
.replace(/^\/+/u, "")
|
|
77
|
+
.replace(/\.git$/u, "")
|
|
78
|
+
.split("/");
|
|
79
|
+
if (pathSegments.length < 2 || !pathSegments[0] || !pathSegments[1]) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
return `${pathSegments[0]}/${pathSegments[1]}`;
|
|
83
|
+
}
|
|
84
|
+
function getDefaultRepositoryManifestPaths() {
|
|
85
|
+
const candidatePaths = [
|
|
86
|
+
path.resolve(PROJECT_TOOLS_PACKAGE_ROOT, "..", "..", "package.json"),
|
|
87
|
+
path.resolve(PROJECT_TOOLS_PACKAGE_ROOT, "..", "wp-typia", "package.json"),
|
|
88
|
+
path.join(PROJECT_TOOLS_PACKAGE_ROOT, "package.json"),
|
|
89
|
+
resolveInstalledPackageManifestPath("wp-typia"),
|
|
90
|
+
resolveInstalledPackageManifestPath("@wp-typia/project-tools"),
|
|
91
|
+
].filter((candidatePath) => Boolean(candidatePath));
|
|
92
|
+
return candidatePaths.filter((candidatePath, index, allPaths) => allPaths.indexOf(candidatePath) === index);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Resolves the canonical scaffold repository reference in `owner/repo` format.
|
|
96
|
+
*
|
|
97
|
+
* The resolver checks candidate package manifests in priority order, extracts
|
|
98
|
+
* their `repository` field, and returns the first GitHub slug it can parse.
|
|
99
|
+
* When no candidate yields a GitHub repository reference, the fallback value
|
|
100
|
+
* is returned instead.
|
|
101
|
+
*/
|
|
102
|
+
export function resolveScaffoldRepositoryReference({ fallback = DEFAULT_SCAFFOLD_REPOSITORY_REFERENCE, manifestPaths = getDefaultRepositoryManifestPaths(), } = {}) {
|
|
103
|
+
for (const manifestPath of manifestPaths) {
|
|
104
|
+
const repositoryReference = parseRepositoryReference(getRepositoryFieldValue(readRepositoryPackageManifest(manifestPath)));
|
|
105
|
+
if (repositoryReference) {
|
|
106
|
+
return repositoryReference;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return fallback;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Replaces legacy scaffold repository placeholders with a concrete
|
|
113
|
+
* `owner/repo` reference.
|
|
114
|
+
*/
|
|
115
|
+
export function replaceRepositoryReferencePlaceholders(source, repositoryReference) {
|
|
116
|
+
return source
|
|
117
|
+
.replace(/yourusername\/wp-typia-boilerplate/g, repositoryReference)
|
|
118
|
+
.replace(/yourusername\/wp-typia/g, repositoryReference);
|
|
119
|
+
}
|
|
@@ -115,11 +115,15 @@ interface ScaffoldProjectOptions {
|
|
|
115
115
|
answers: ScaffoldAnswers;
|
|
116
116
|
cwd?: string;
|
|
117
117
|
dataStorageMode?: DataStorageMode;
|
|
118
|
+
externalLayerId?: string;
|
|
119
|
+
externalLayerSource?: string;
|
|
120
|
+
externalLayerSourceLabel?: string;
|
|
118
121
|
installDependencies?: ((options: InstallDependenciesOptions) => Promise<void>) | undefined;
|
|
119
122
|
noInstall?: boolean;
|
|
120
123
|
packageManager: PackageManagerId;
|
|
121
124
|
persistencePolicy?: PersistencePolicy;
|
|
122
125
|
projectDir: string;
|
|
126
|
+
repositoryReference?: string;
|
|
123
127
|
templateId: string;
|
|
124
128
|
variant?: string;
|
|
125
129
|
withMigrationUi?: boolean;
|
|
@@ -153,5 +157,5 @@ export declare function resolveTemplateId({ templateId, yes, isInteractive, sele
|
|
|
153
157
|
export declare function resolvePackageManagerId({ packageManager, yes, isInteractive, selectPackageManager, }: ResolvePackageManagerOptions): Promise<PackageManagerId>;
|
|
154
158
|
export declare function collectScaffoldAnswers({ projectName, templateId, yes, dataStorageMode, namespace, persistencePolicy, phpPrefix, promptText, textDomain, }: CollectScaffoldAnswersOptions): Promise<ScaffoldAnswers>;
|
|
155
159
|
export declare function getTemplateVariables(templateId: string, answers: ScaffoldAnswers): ScaffoldTemplateVariables;
|
|
156
|
-
export declare function scaffoldProject({ projectDir, templateId, answers, dataStorageMode, persistencePolicy, packageManager, cwd, allowExistingDir, noInstall, installDependencies, variant, withMigrationUi, withTestPreset, withWpEnv, }: ScaffoldProjectOptions): Promise<ScaffoldProjectResult>;
|
|
160
|
+
export declare function scaffoldProject({ projectDir, templateId, answers, dataStorageMode, persistencePolicy, packageManager, externalLayerId, externalLayerSource, externalLayerSourceLabel, repositoryReference, cwd, allowExistingDir, noInstall, installDependencies, variant, withMigrationUi, withTestPreset, withWpEnv, }: ScaffoldProjectOptions): Promise<ScaffoldProjectResult>;
|
|
157
161
|
export {};
|
package/dist/runtime/scaffold.js
CHANGED
|
@@ -3,6 +3,7 @@ import { promises as fsp } from "node:fs";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { execSync } from "node:child_process";
|
|
5
5
|
import { PACKAGE_MANAGER_IDS, formatPackageExecCommand, formatInstallCommand, formatRunScript, getPackageManager, transformPackageManagerText, } from "./package-managers.js";
|
|
6
|
+
import { replaceTextRecursively } from "./scaffold-apply-utils.js";
|
|
6
7
|
import { applyGeneratedProjectDxPackageJson, applyLocalDevPresetFiles, getPrimaryDevelopmentScript, } from "./local-dev-presets.js";
|
|
7
8
|
import { applyMigrationUiCapability } from "./migration-ui-capability.js";
|
|
8
9
|
import { getPackageVersions } from "./package-versions.js";
|
|
@@ -563,41 +564,6 @@ async function removeUnexpectedLockfiles(targetDir, packageManagerId) {
|
|
|
563
564
|
}
|
|
564
565
|
}));
|
|
565
566
|
}
|
|
566
|
-
async function replaceTextRecursively(targetDir, packageManagerId) {
|
|
567
|
-
const textExtensions = new Set([
|
|
568
|
-
".css",
|
|
569
|
-
".js",
|
|
570
|
-
".json",
|
|
571
|
-
".jsx",
|
|
572
|
-
".md",
|
|
573
|
-
".php",
|
|
574
|
-
".scss",
|
|
575
|
-
".ts",
|
|
576
|
-
".tsx",
|
|
577
|
-
".txt",
|
|
578
|
-
]);
|
|
579
|
-
async function visit(currentPath) {
|
|
580
|
-
const stats = await fsp.stat(currentPath);
|
|
581
|
-
if (stats.isDirectory()) {
|
|
582
|
-
const entries = await fsp.readdir(currentPath);
|
|
583
|
-
for (const entry of entries) {
|
|
584
|
-
await visit(path.join(currentPath, entry));
|
|
585
|
-
}
|
|
586
|
-
return;
|
|
587
|
-
}
|
|
588
|
-
if (path.basename(currentPath) === "package.json" || !textExtensions.has(path.extname(currentPath))) {
|
|
589
|
-
return;
|
|
590
|
-
}
|
|
591
|
-
const content = await fsp.readFile(currentPath, "utf8");
|
|
592
|
-
const nextContent = transformPackageManagerText(content, packageManagerId)
|
|
593
|
-
.replace(/yourusername\/wp-typia-boilerplate/g, "imjlk/wp-typia")
|
|
594
|
-
.replace(/yourusername\/wp-typia/g, "imjlk/wp-typia");
|
|
595
|
-
if (nextContent !== content) {
|
|
596
|
-
await fsp.writeFile(currentPath, nextContent, "utf8");
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
await visit(targetDir);
|
|
600
|
-
}
|
|
601
567
|
async function defaultInstallDependencies({ projectDir, packageManager, }) {
|
|
602
568
|
execSync(formatInstallCommand(packageManager), {
|
|
603
569
|
cwd: projectDir,
|
|
@@ -642,10 +608,13 @@ async function applyWorkspaceMigrationCapability(projectDir, packageManager) {
|
|
|
642
608
|
ensureMigrationDirectories(projectDir, []);
|
|
643
609
|
writeInitialMigrationScaffold(projectDir, "v1", []);
|
|
644
610
|
}
|
|
645
|
-
export async function scaffoldProject({ projectDir, templateId, answers, dataStorageMode, persistencePolicy, packageManager, cwd = process.cwd(), allowExistingDir = false, noInstall = false, installDependencies = undefined, variant, withMigrationUi = false, withTestPreset = false, withWpEnv = false, }) {
|
|
611
|
+
export async function scaffoldProject({ projectDir, templateId, answers, dataStorageMode, persistencePolicy, packageManager, externalLayerId, externalLayerSource, externalLayerSourceLabel, repositoryReference, cwd = process.cwd(), allowExistingDir = false, noInstall = false, installDependencies = undefined, variant, withMigrationUi = false, withTestPreset = false, withWpEnv = false, }) {
|
|
646
612
|
const resolvedTemplateId = normalizeTemplateSelection(templateId);
|
|
647
613
|
const resolvedPackageManager = getPackageManager(packageManager).id;
|
|
648
614
|
const isBuiltInTemplate = isBuiltInTemplateId(resolvedTemplateId);
|
|
615
|
+
if (externalLayerId && !externalLayerSource) {
|
|
616
|
+
throw new Error("externalLayerId requires externalLayerSource when composing built-in template layers.");
|
|
617
|
+
}
|
|
649
618
|
if (isBuiltInTemplate) {
|
|
650
619
|
const blockGeneratorService = new BlockGeneratorService();
|
|
651
620
|
const plan = await blockGeneratorService.plan({
|
|
@@ -653,10 +622,14 @@ export async function scaffoldProject({ projectDir, templateId, answers, dataSto
|
|
|
653
622
|
answers,
|
|
654
623
|
cwd,
|
|
655
624
|
dataStorageMode: dataStorageMode ?? answers.dataStorageMode,
|
|
625
|
+
externalLayerId,
|
|
626
|
+
externalLayerSource,
|
|
627
|
+
externalLayerSourceLabel,
|
|
656
628
|
noInstall,
|
|
657
629
|
packageManager: resolvedPackageManager,
|
|
658
630
|
persistencePolicy: persistencePolicy ?? answers.persistencePolicy,
|
|
659
631
|
projectDir,
|
|
632
|
+
repositoryReference,
|
|
660
633
|
templateId: resolvedTemplateId,
|
|
661
634
|
variant,
|
|
662
635
|
withMigrationUi,
|
|
@@ -670,6 +643,9 @@ export async function scaffoldProject({ projectDir, templateId, answers, dataSto
|
|
|
670
643
|
rendered,
|
|
671
644
|
});
|
|
672
645
|
}
|
|
646
|
+
if (externalLayerSource || externalLayerId) {
|
|
647
|
+
throw new Error("External template layers currently compose only with built-in templates via `wp-typia create --template <basic|interactivity|persistence|compound>` or `wp-typia add block --template <family>`.");
|
|
648
|
+
}
|
|
673
649
|
const variables = getTemplateVariables(resolvedTemplateId, {
|
|
674
650
|
...answers,
|
|
675
651
|
dataStorageMode: dataStorageMode ?? answers.dataStorageMode,
|
|
@@ -738,7 +714,9 @@ export async function scaffoldProject({ projectDir, templateId, answers, dataSto
|
|
|
738
714
|
}
|
|
739
715
|
await normalizePackageManagerFiles(projectDir, resolvedPackageManager);
|
|
740
716
|
await removeUnexpectedLockfiles(projectDir, resolvedPackageManager);
|
|
741
|
-
await replaceTextRecursively(projectDir, resolvedPackageManager
|
|
717
|
+
await replaceTextRecursively(projectDir, resolvedPackageManager, {
|
|
718
|
+
repositoryReference,
|
|
719
|
+
});
|
|
742
720
|
if (!noInstall) {
|
|
743
721
|
const installer = installDependencies ?? defaultInstallDependencies;
|
|
744
722
|
await installer({
|
|
@@ -19,6 +19,15 @@ export interface MaterializedBuiltInTemplateSource {
|
|
|
19
19
|
selectedVariant?: string | null;
|
|
20
20
|
warnings?: string[];
|
|
21
21
|
}
|
|
22
|
+
export interface BuiltInSharedTemplateLayer {
|
|
23
|
+
dir: string;
|
|
24
|
+
id: string;
|
|
25
|
+
}
|
|
26
|
+
export declare function listBuiltInSharedTemplateLayers(): readonly BuiltInSharedTemplateLayer[];
|
|
27
|
+
export declare function isBuiltInSharedTemplateLayerId(layerId: string): boolean;
|
|
28
|
+
export declare function getBuiltInSharedTemplateLayerDir(layerId: string): string;
|
|
29
|
+
export declare function getBuiltInTemplateOverlayDir(templateId: BuiltInTemplateId): string;
|
|
30
|
+
export declare function getBuiltInTemplateSharedLayerDirs(templateId: BuiltInTemplateId, { persistenceEnabled, persistencePolicy, }?: BuiltInTemplateVariantOptions): string[];
|
|
22
31
|
/**
|
|
23
32
|
* Returns the ordered overlay directories for a built-in template.
|
|
24
33
|
*
|
|
@@ -26,7 +35,7 @@ export interface MaterializedBuiltInTemplateSource {
|
|
|
26
35
|
* the selected policy layer, and the thin template overlay. All other built-ins
|
|
27
36
|
* resolve to the shared base plus their own template directory.
|
|
28
37
|
*/
|
|
29
|
-
export declare function getBuiltInTemplateLayerDirs(templateId: BuiltInTemplateId,
|
|
38
|
+
export declare function getBuiltInTemplateLayerDirs(templateId: BuiltInTemplateId, options?: BuiltInTemplateVariantOptions): string[];
|
|
30
39
|
/**
|
|
31
40
|
* Returns whether a missing built-in overlay directory is expected because the
|
|
32
41
|
* template family no longer ships any Mustache assets in that layer.
|
|
@@ -36,6 +45,7 @@ export declare function getBuiltInTemplateLayerDirs(templateId: BuiltInTemplateI
|
|
|
36
45
|
* @returns True when the missing layer can be skipped safely.
|
|
37
46
|
*/
|
|
38
47
|
export declare function isOmittableBuiltInTemplateLayerDir(templateId: BuiltInTemplateId, layerDir: string): boolean;
|
|
48
|
+
export declare function resolveBuiltInTemplateSourceFromLayerDirs(templateId: BuiltInTemplateId, layerDirs: readonly string[]): Promise<MaterializedBuiltInTemplateSource>;
|
|
39
49
|
/**
|
|
40
50
|
* Materializes a built-in template into a temporary directory by copying each
|
|
41
51
|
* resolved layer in order.
|
|
@@ -2,20 +2,96 @@ import fs from "node:fs";
|
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { promises as fsp } from "node:fs";
|
|
5
|
-
import { getTemplateById, SHARED_BASE_TEMPLATE_ROOT, SHARED_COMPOUND_TEMPLATE_ROOT, SHARED_PERSISTENCE_TEMPLATE_ROOT, SHARED_REST_HELPER_TEMPLATE_ROOT, } from "./template-registry.js";
|
|
5
|
+
import { getTemplateById, SHARED_BASE_TEMPLATE_ROOT, SHARED_COMPOUND_TEMPLATE_ROOT, SHARED_MIGRATION_UI_TEMPLATE_ROOT, SHARED_PERSISTENCE_TEMPLATE_ROOT, SHARED_PRESET_TEMPLATE_ROOT, SHARED_REST_HELPER_TEMPLATE_ROOT, SHARED_WORKSPACE_TEMPLATE_ROOT, } from "./template-registry.js";
|
|
6
|
+
const BUILT_IN_SHARED_TEMPLATE_LAYERS = Object.freeze([
|
|
7
|
+
{
|
|
8
|
+
dir: SHARED_BASE_TEMPLATE_ROOT,
|
|
9
|
+
id: "builtin:shared/base",
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
dir: path.join(SHARED_REST_HELPER_TEMPLATE_ROOT, "shared"),
|
|
13
|
+
id: "builtin:shared/rest-helpers/shared",
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
dir: path.join(SHARED_REST_HELPER_TEMPLATE_ROOT, "public"),
|
|
17
|
+
id: "builtin:shared/rest-helpers/public",
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
dir: path.join(SHARED_REST_HELPER_TEMPLATE_ROOT, "auth"),
|
|
21
|
+
id: "builtin:shared/rest-helpers/auth",
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
dir: path.join(SHARED_PERSISTENCE_TEMPLATE_ROOT, "core"),
|
|
25
|
+
id: "builtin:shared/persistence/core",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
dir: path.join(SHARED_PERSISTENCE_TEMPLATE_ROOT, "public"),
|
|
29
|
+
id: "builtin:shared/persistence/public",
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
dir: path.join(SHARED_PERSISTENCE_TEMPLATE_ROOT, "auth"),
|
|
33
|
+
id: "builtin:shared/persistence/auth",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
dir: path.join(SHARED_COMPOUND_TEMPLATE_ROOT, "core"),
|
|
37
|
+
id: "builtin:shared/compound/core",
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
dir: path.join(SHARED_COMPOUND_TEMPLATE_ROOT, "persistence"),
|
|
41
|
+
id: "builtin:shared/compound/persistence",
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
dir: path.join(SHARED_COMPOUND_TEMPLATE_ROOT, "persistence-public"),
|
|
45
|
+
id: "builtin:shared/compound/persistence-public",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
dir: path.join(SHARED_COMPOUND_TEMPLATE_ROOT, "persistence-auth"),
|
|
49
|
+
id: "builtin:shared/compound/persistence-auth",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
dir: path.join(SHARED_MIGRATION_UI_TEMPLATE_ROOT, "common"),
|
|
53
|
+
id: "builtin:shared/migration-ui/common",
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
dir: path.join(SHARED_PRESET_TEMPLATE_ROOT, "wp-env"),
|
|
57
|
+
id: "builtin:shared/presets/wp-env",
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
dir: path.join(SHARED_PRESET_TEMPLATE_ROOT, "test-preset"),
|
|
61
|
+
id: "builtin:shared/presets/test-preset",
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
dir: path.join(SHARED_WORKSPACE_TEMPLATE_ROOT, "persistence-public"),
|
|
65
|
+
id: "builtin:shared/workspace/persistence-public",
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
dir: path.join(SHARED_WORKSPACE_TEMPLATE_ROOT, "persistence-auth"),
|
|
69
|
+
id: "builtin:shared/workspace/persistence-auth",
|
|
70
|
+
},
|
|
71
|
+
]);
|
|
72
|
+
const BUILT_IN_SHARED_TEMPLATE_LAYER_DIRS = new Map(BUILT_IN_SHARED_TEMPLATE_LAYERS.map((layer) => [layer.id, layer.dir]));
|
|
6
73
|
const OMITTABLE_BUILT_IN_OVERLAY_TEMPLATE_IDS = new Set([
|
|
7
74
|
"basic",
|
|
8
75
|
"persistence",
|
|
9
76
|
"compound",
|
|
10
77
|
]);
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
78
|
+
export function listBuiltInSharedTemplateLayers() {
|
|
79
|
+
return BUILT_IN_SHARED_TEMPLATE_LAYERS;
|
|
80
|
+
}
|
|
81
|
+
export function isBuiltInSharedTemplateLayerId(layerId) {
|
|
82
|
+
return BUILT_IN_SHARED_TEMPLATE_LAYER_DIRS.has(layerId);
|
|
83
|
+
}
|
|
84
|
+
export function getBuiltInSharedTemplateLayerDir(layerId) {
|
|
85
|
+
const layerDir = BUILT_IN_SHARED_TEMPLATE_LAYER_DIRS.get(layerId);
|
|
86
|
+
if (!layerDir) {
|
|
87
|
+
throw new Error(`Unknown built-in shared template layer id: ${layerId}`);
|
|
88
|
+
}
|
|
89
|
+
return layerDir;
|
|
90
|
+
}
|
|
91
|
+
export function getBuiltInTemplateOverlayDir(templateId) {
|
|
92
|
+
return getTemplateById(templateId).templateDir;
|
|
93
|
+
}
|
|
94
|
+
export function getBuiltInTemplateSharedLayerDirs(templateId, { persistenceEnabled = false, persistencePolicy = "authenticated", } = {}) {
|
|
19
95
|
if (templateId === "persistence") {
|
|
20
96
|
return [
|
|
21
97
|
SHARED_BASE_TEMPLATE_ROOT,
|
|
@@ -23,21 +99,32 @@ export function getBuiltInTemplateLayerDirs(templateId, { persistenceEnabled = f
|
|
|
23
99
|
path.join(SHARED_PERSISTENCE_TEMPLATE_ROOT, "core"),
|
|
24
100
|
path.join(SHARED_REST_HELPER_TEMPLATE_ROOT, persistencePolicy === "public" ? "public" : "auth"),
|
|
25
101
|
path.join(SHARED_PERSISTENCE_TEMPLATE_ROOT, persistencePolicy === "public" ? "public" : "auth"),
|
|
26
|
-
getTemplateById(templateId).templateDir,
|
|
27
102
|
];
|
|
28
103
|
}
|
|
29
104
|
if (templateId === "compound") {
|
|
30
105
|
const layers = [
|
|
31
106
|
SHARED_BASE_TEMPLATE_ROOT,
|
|
32
107
|
path.join(SHARED_COMPOUND_TEMPLATE_ROOT, "core"),
|
|
33
|
-
getTemplateById(templateId).templateDir,
|
|
34
108
|
];
|
|
35
109
|
if (persistenceEnabled) {
|
|
36
110
|
layers.push(path.join(SHARED_REST_HELPER_TEMPLATE_ROOT, "shared"), path.join(SHARED_COMPOUND_TEMPLATE_ROOT, "persistence"), path.join(SHARED_REST_HELPER_TEMPLATE_ROOT, persistencePolicy === "public" ? "public" : "auth"), path.join(SHARED_COMPOUND_TEMPLATE_ROOT, persistencePolicy === "public" ? "persistence-public" : "persistence-auth"));
|
|
37
111
|
}
|
|
38
112
|
return layers;
|
|
39
113
|
}
|
|
40
|
-
return [SHARED_BASE_TEMPLATE_ROOT
|
|
114
|
+
return [SHARED_BASE_TEMPLATE_ROOT];
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Returns the ordered overlay directories for a built-in template.
|
|
118
|
+
*
|
|
119
|
+
* Persistence templates include the shared base, the persistence core layer,
|
|
120
|
+
* the selected policy layer, and the thin template overlay. All other built-ins
|
|
121
|
+
* resolve to the shared base plus their own template directory.
|
|
122
|
+
*/
|
|
123
|
+
export function getBuiltInTemplateLayerDirs(templateId, options = {}) {
|
|
124
|
+
return [
|
|
125
|
+
...getBuiltInTemplateSharedLayerDirs(templateId, options),
|
|
126
|
+
getBuiltInTemplateOverlayDir(templateId),
|
|
127
|
+
];
|
|
41
128
|
}
|
|
42
129
|
/**
|
|
43
130
|
* Returns whether a missing built-in overlay directory is expected because the
|
|
@@ -51,8 +138,8 @@ export function isOmittableBuiltInTemplateLayerDir(templateId, layerDir) {
|
|
|
51
138
|
return (OMITTABLE_BUILT_IN_OVERLAY_TEMPLATE_IDS.has(templateId) &&
|
|
52
139
|
layerDir === getTemplateById(templateId).templateDir);
|
|
53
140
|
}
|
|
54
|
-
function
|
|
55
|
-
return
|
|
141
|
+
function resolveMaterializedTemplateLayerDirs(templateId, layerDirs) {
|
|
142
|
+
return layerDirs.flatMap((layerDir) => {
|
|
56
143
|
if (fs.existsSync(layerDir)) {
|
|
57
144
|
return [layerDir];
|
|
58
145
|
}
|
|
@@ -62,22 +149,14 @@ function resolveMaterializedBuiltInTemplateLayerDirs(templateId, options) {
|
|
|
62
149
|
throw new Error(`Built-in template layer is missing: ${layerDir}`);
|
|
63
150
|
});
|
|
64
151
|
}
|
|
65
|
-
|
|
66
|
-
* Materializes a built-in template into a temporary directory by copying each
|
|
67
|
-
* resolved layer in order.
|
|
68
|
-
*
|
|
69
|
-
* Callers should invoke the returned `cleanup` function when they no longer
|
|
70
|
-
* need the materialized directory. If copying fails, the temporary directory is
|
|
71
|
-
* removed before the error is rethrown.
|
|
72
|
-
*/
|
|
73
|
-
export async function resolveBuiltInTemplateSource(templateId, options = {}) {
|
|
152
|
+
async function materializeBuiltInTemplateSource(templateId, layerDirs) {
|
|
74
153
|
const template = getTemplateById(templateId);
|
|
75
|
-
const
|
|
154
|
+
const materializedLayerDirs = resolveMaterializedTemplateLayerDirs(templateId, layerDirs);
|
|
76
155
|
const tempRoot = await fsp.mkdtemp(path.join(os.tmpdir(), "wp-typia-template-"));
|
|
77
156
|
const templateDir = path.join(tempRoot, templateId);
|
|
78
157
|
try {
|
|
79
158
|
await fsp.mkdir(templateDir, { recursive: true });
|
|
80
|
-
for (const layerDir of
|
|
159
|
+
for (const layerDir of materializedLayerDirs) {
|
|
81
160
|
await fsp.cp(layerDir, templateDir, {
|
|
82
161
|
recursive: true,
|
|
83
162
|
force: true,
|
|
@@ -100,3 +179,17 @@ export async function resolveBuiltInTemplateSource(templateId, options = {}) {
|
|
|
100
179
|
},
|
|
101
180
|
};
|
|
102
181
|
}
|
|
182
|
+
export async function resolveBuiltInTemplateSourceFromLayerDirs(templateId, layerDirs) {
|
|
183
|
+
return materializeBuiltInTemplateSource(templateId, layerDirs);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Materializes a built-in template into a temporary directory by copying each
|
|
187
|
+
* resolved layer in order.
|
|
188
|
+
*
|
|
189
|
+
* Callers should invoke the returned `cleanup` function when they no longer
|
|
190
|
+
* need the materialized directory. If copying fails, the temporary directory is
|
|
191
|
+
* removed before the error is rethrown.
|
|
192
|
+
*/
|
|
193
|
+
export async function resolveBuiltInTemplateSource(templateId, options = {}) {
|
|
194
|
+
return materializeBuiltInTemplateSource(templateId, getBuiltInTemplateLayerDirs(templateId, options));
|
|
195
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export declare const TEMPLATE_LAYER_MANIFEST_FILENAME = "wp-typia.layers.json";
|
|
2
|
+
declare const TEMPLATE_LAYER_MANIFEST_VERSION: 1;
|
|
3
|
+
export interface ExternalTemplateLayerDefinition {
|
|
4
|
+
description?: string;
|
|
5
|
+
extends?: string[];
|
|
6
|
+
path: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ExternalTemplateLayerManifest {
|
|
9
|
+
layers: Record<string, ExternalTemplateLayerDefinition>;
|
|
10
|
+
version: typeof TEMPLATE_LAYER_MANIFEST_VERSION;
|
|
11
|
+
}
|
|
12
|
+
export interface ResolvedTemplateLayerEntry {
|
|
13
|
+
dir: string;
|
|
14
|
+
id: string;
|
|
15
|
+
kind: "built-in" | "external";
|
|
16
|
+
}
|
|
17
|
+
export interface ResolvedExternalTemplateLayers {
|
|
18
|
+
entries: ResolvedTemplateLayerEntry[];
|
|
19
|
+
selectedLayerId: string;
|
|
20
|
+
}
|
|
21
|
+
export interface SelectableExternalTemplateLayer {
|
|
22
|
+
description?: string;
|
|
23
|
+
extends: string[];
|
|
24
|
+
id: string;
|
|
25
|
+
}
|
|
26
|
+
export declare function loadExternalTemplateLayerManifest(sourceRoot: string): Promise<ExternalTemplateLayerManifest | null>;
|
|
27
|
+
export declare function listSelectableExternalTemplateLayers(sourceRoot: string): Promise<SelectableExternalTemplateLayer[]>;
|
|
28
|
+
export declare function resolveExternalTemplateLayers({ externalLayerId, sourceRoot, }: {
|
|
29
|
+
externalLayerId?: string;
|
|
30
|
+
sourceRoot: string;
|
|
31
|
+
}): Promise<ResolvedExternalTemplateLayers>;
|
|
32
|
+
export declare function assertExternalTemplateLayersDoNotWriteProtectedOutputs({ externalEntries, protectedOutputPaths, view, }: {
|
|
33
|
+
externalEntries: readonly ResolvedTemplateLayerEntry[];
|
|
34
|
+
protectedOutputPaths: ReadonlySet<string>;
|
|
35
|
+
view: Record<string, string>;
|
|
36
|
+
}): Promise<void>;
|
|
37
|
+
export {};
|