@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.
Files changed (54) hide show
  1. package/README.md +26 -1
  2. package/dist/runtime/block-generator-service.d.ts +9 -1
  3. package/dist/runtime/block-generator-service.js +137 -16
  4. package/dist/runtime/block-generator-tool-contract.d.ts +93 -0
  5. package/dist/runtime/block-generator-tool-contract.js +157 -0
  6. package/dist/runtime/built-in-block-artifacts.js +171 -423
  7. package/dist/runtime/built-in-block-code-artifacts.js +96 -46
  8. package/dist/runtime/built-in-block-code-templates.d.ts +36 -0
  9. package/dist/runtime/built-in-block-code-templates.js +2233 -0
  10. package/dist/runtime/cli-add-block.d.ts +2 -1
  11. package/dist/runtime/cli-add-block.js +163 -25
  12. package/dist/runtime/cli-add-shared.d.ts +7 -0
  13. package/dist/runtime/cli-add-shared.js +4 -6
  14. package/dist/runtime/cli-add-workspace.js +56 -17
  15. package/dist/runtime/cli-core.d.ts +4 -0
  16. package/dist/runtime/cli-core.js +3 -0
  17. package/dist/runtime/cli-diagnostics.d.ts +58 -0
  18. package/dist/runtime/cli-diagnostics.js +101 -0
  19. package/dist/runtime/cli-doctor.d.ts +2 -1
  20. package/dist/runtime/cli-doctor.js +16 -5
  21. package/dist/runtime/cli-help.js +4 -4
  22. package/dist/runtime/cli-scaffold.d.ts +5 -1
  23. package/dist/runtime/cli-scaffold.js +138 -111
  24. package/dist/runtime/external-layer-selection.d.ts +14 -0
  25. package/dist/runtime/external-layer-selection.js +35 -0
  26. package/dist/runtime/index.d.ts +4 -2
  27. package/dist/runtime/index.js +2 -1
  28. package/dist/runtime/migration-render.d.ts +23 -1
  29. package/dist/runtime/migration-render.js +58 -10
  30. package/dist/runtime/migration-ui-capability.js +17 -8
  31. package/dist/runtime/migration-utils.d.ts +7 -6
  32. package/dist/runtime/migration-utils.js +76 -73
  33. package/dist/runtime/migrations.js +2 -2
  34. package/dist/runtime/object-utils.d.ts +8 -1
  35. package/dist/runtime/object-utils.js +21 -1
  36. package/dist/runtime/scaffold-apply-utils.d.ts +14 -2
  37. package/dist/runtime/scaffold-apply-utils.js +19 -6
  38. package/dist/runtime/scaffold-repository-reference.d.ts +22 -0
  39. package/dist/runtime/scaffold-repository-reference.js +119 -0
  40. package/dist/runtime/scaffold.d.ts +5 -1
  41. package/dist/runtime/scaffold.js +15 -37
  42. package/dist/runtime/template-builtins.d.ts +11 -1
  43. package/dist/runtime/template-builtins.js +118 -25
  44. package/dist/runtime/template-layers.d.ts +37 -0
  45. package/dist/runtime/template-layers.js +184 -0
  46. package/dist/runtime/template-render.d.ts +28 -2
  47. package/dist/runtime/template-render.js +122 -55
  48. package/dist/runtime/template-source.d.ts +23 -5
  49. package/dist/runtime/template-source.js +296 -217
  50. package/package.json +8 -3
  51. package/templates/_shared/base/src/validator-toolkit.ts.mustache +2 -2
  52. package/templates/_shared/compound/core/scripts/add-compound-child.ts.mustache +58 -12
  53. package/templates/_shared/migration-ui/common/src/migrations/helpers.ts +19 -47
  54. 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
- export async function replaceTextRecursively(targetDir, packageManagerId) {
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
- export async function applyBuiltInScaffoldProjectFiles({ projectDir, templateDir, templateId, variables, artifacts, codeArtifacts, readmeContent, gitignoreContent, allowExistingDir = false, packageManager, withMigrationUi = false, withTestPreset = false, withWpEnv = false, noInstall = false, installDependencies, }) {
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 {};
@@ -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, { persistenceEnabled, persistencePolicy, }?: BuiltInTemplateVariantOptions): string[];
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
- * Returns the ordered overlay directories for a built-in template.
13
- *
14
- * Persistence templates include the shared base, the persistence core layer,
15
- * the selected policy layer, and the thin template overlay. All other built-ins
16
- * resolve to the shared base plus their own template directory.
17
- */
18
- export function getBuiltInTemplateLayerDirs(templateId, { persistenceEnabled = false, persistencePolicy = "authenticated", } = {}) {
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, getTemplateById(templateId).templateDir];
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 resolveMaterializedBuiltInTemplateLayerDirs(templateId, options) {
55
- return getBuiltInTemplateLayerDirs(templateId, options).flatMap((layerDir) => {
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 layerDirs = resolveMaterializedBuiltInTemplateLayerDirs(templateId, options);
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 layerDirs) {
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 {};