@wp-typia/project-tools 0.11.1

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 (187) hide show
  1. package/README.md +32 -0
  2. package/dist/runtime/cli-add.d.ts +38 -0
  3. package/dist/runtime/cli-add.js +561 -0
  4. package/dist/runtime/cli-core.d.ts +25 -0
  5. package/dist/runtime/cli-core.js +25 -0
  6. package/dist/runtime/cli-doctor.d.ts +34 -0
  7. package/dist/runtime/cli-doctor.js +131 -0
  8. package/dist/runtime/cli-help.d.ts +9 -0
  9. package/dist/runtime/cli-help.js +37 -0
  10. package/dist/runtime/cli-prompt.d.ts +21 -0
  11. package/dist/runtime/cli-prompt.js +53 -0
  12. package/dist/runtime/cli-scaffold.d.ts +79 -0
  13. package/dist/runtime/cli-scaffold.js +206 -0
  14. package/dist/runtime/cli-templates.d.ts +30 -0
  15. package/dist/runtime/cli-templates.js +61 -0
  16. package/dist/runtime/index.d.ts +9 -0
  17. package/dist/runtime/index.js +7 -0
  18. package/dist/runtime/json-utils.d.ts +10 -0
  19. package/dist/runtime/json-utils.js +12 -0
  20. package/dist/runtime/local-dev-presets.d.ts +26 -0
  21. package/dist/runtime/local-dev-presets.js +132 -0
  22. package/dist/runtime/metadata-analysis.d.ts +11 -0
  23. package/dist/runtime/metadata-analysis.js +285 -0
  24. package/dist/runtime/metadata-model.d.ts +84 -0
  25. package/dist/runtime/metadata-model.js +59 -0
  26. package/dist/runtime/metadata-parser.d.ts +53 -0
  27. package/dist/runtime/metadata-parser.js +794 -0
  28. package/dist/runtime/metadata-php-render.d.ts +29 -0
  29. package/dist/runtime/metadata-php-render.js +549 -0
  30. package/dist/runtime/metadata-projection.d.ts +7 -0
  31. package/dist/runtime/metadata-projection.js +233 -0
  32. package/dist/runtime/migration-constants.d.ts +15 -0
  33. package/dist/runtime/migration-constants.js +16 -0
  34. package/dist/runtime/migration-diff.d.ts +2 -0
  35. package/dist/runtime/migration-diff.js +537 -0
  36. package/dist/runtime/migration-fixtures.d.ts +8 -0
  37. package/dist/runtime/migration-fixtures.js +94 -0
  38. package/dist/runtime/migration-fuzz-plan.d.ts +2 -0
  39. package/dist/runtime/migration-fuzz-plan.js +50 -0
  40. package/dist/runtime/migration-manifest.d.ts +19 -0
  41. package/dist/runtime/migration-manifest.js +129 -0
  42. package/dist/runtime/migration-project.d.ts +94 -0
  43. package/dist/runtime/migration-project.js +1101 -0
  44. package/dist/runtime/migration-render.d.ts +11 -0
  45. package/dist/runtime/migration-render.js +741 -0
  46. package/dist/runtime/migration-risk.d.ts +4 -0
  47. package/dist/runtime/migration-risk.js +52 -0
  48. package/dist/runtime/migration-types.d.ts +249 -0
  49. package/dist/runtime/migration-types.js +1 -0
  50. package/dist/runtime/migration-ui-capability.d.ts +17 -0
  51. package/dist/runtime/migration-ui-capability.js +190 -0
  52. package/dist/runtime/migration-utils.d.ts +69 -0
  53. package/dist/runtime/migration-utils.js +246 -0
  54. package/dist/runtime/migrations.d.ts +249 -0
  55. package/dist/runtime/migrations.js +1061 -0
  56. package/dist/runtime/object-utils.d.ts +12 -0
  57. package/dist/runtime/object-utils.js +14 -0
  58. package/dist/runtime/package-managers.d.ts +28 -0
  59. package/dist/runtime/package-managers.js +156 -0
  60. package/dist/runtime/package-versions.d.ts +10 -0
  61. package/dist/runtime/package-versions.js +68 -0
  62. package/dist/runtime/scaffold-onboarding.d.ts +32 -0
  63. package/dist/runtime/scaffold-onboarding.js +99 -0
  64. package/dist/runtime/scaffold.d.ts +146 -0
  65. package/dist/runtime/scaffold.js +612 -0
  66. package/dist/runtime/schema-core.d.ts +267 -0
  67. package/dist/runtime/schema-core.js +597 -0
  68. package/dist/runtime/starter-manifests.d.ts +25 -0
  69. package/dist/runtime/starter-manifests.js +383 -0
  70. package/dist/runtime/string-case.d.ts +36 -0
  71. package/dist/runtime/string-case.js +69 -0
  72. package/dist/runtime/template-builtins.d.ts +38 -0
  73. package/dist/runtime/template-builtins.js +72 -0
  74. package/dist/runtime/template-defaults.d.ts +75 -0
  75. package/dist/runtime/template-defaults.js +65 -0
  76. package/dist/runtime/template-registry.d.ts +36 -0
  77. package/dist/runtime/template-registry.js +94 -0
  78. package/dist/runtime/template-render.d.ts +24 -0
  79. package/dist/runtime/template-render.js +113 -0
  80. package/dist/runtime/template-source.d.ts +71 -0
  81. package/dist/runtime/template-source.js +821 -0
  82. package/dist/runtime/typia-tags.d.ts +1 -0
  83. package/dist/runtime/typia-tags.js +1 -0
  84. package/package.json +79 -0
  85. package/templates/_shared/base/languages/.gitkeep +1 -0
  86. package/templates/_shared/base/package.json.mustache +41 -0
  87. package/templates/_shared/base/scripts/sync-types-to-block-json.ts.mustache +118 -0
  88. package/templates/_shared/base/src/hooks.ts.mustache +19 -0
  89. package/templates/_shared/base/src/validator-toolkit.ts.mustache +31 -0
  90. package/templates/_shared/base/tsconfig.json.mustache +21 -0
  91. package/templates/_shared/base/webpack.config.js.mustache +99 -0
  92. package/templates/_shared/base/{{slugKebabCase}}.php.mustache +53 -0
  93. package/templates/_shared/compound/core/package.json.mustache +45 -0
  94. package/templates/_shared/compound/core/scripts/add-compound-child.ts.mustache +559 -0
  95. package/templates/_shared/compound/core/scripts/block-config.ts.mustache +13 -0
  96. package/templates/_shared/compound/core/scripts/sync-types-to-block-json.ts.mustache +53 -0
  97. package/templates/_shared/compound/core/webpack.config.js.mustache +141 -0
  98. package/templates/_shared/compound/core/{{slugKebabCase}}.php.mustache +51 -0
  99. package/templates/_shared/compound/persistence/package.json.mustache +50 -0
  100. package/templates/_shared/compound/persistence/scripts/block-config.ts.mustache +59 -0
  101. package/templates/_shared/compound/persistence/scripts/sync-rest-contracts.ts.mustache +101 -0
  102. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/api-types.ts.mustache +21 -0
  103. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/api-validators.ts.mustache +32 -0
  104. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/api.ts.mustache +68 -0
  105. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/block.json.mustache +52 -0
  106. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/data.ts.mustache +192 -0
  107. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/edit.tsx.mustache +123 -0
  108. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/hooks.ts.mustache +11 -0
  109. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/interactivity.ts.mustache +132 -0
  110. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/render.php.mustache +158 -0
  111. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/save.tsx.mustache +3 -0
  112. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/types.ts.mustache +56 -0
  113. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/validators.ts.mustache +32 -0
  114. package/templates/_shared/compound/persistence-auth/{{slugKebabCase}}.php.mustache +294 -0
  115. package/templates/_shared/compound/persistence-public/{{slugKebabCase}}.php.mustache +312 -0
  116. package/templates/_shared/migration-ui/common/src/admin/migration-dashboard.tsx +394 -0
  117. package/templates/_shared/migration-ui/common/src/migration-detector.ts +9 -0
  118. package/templates/_shared/migration-ui/common/src/migrations/helpers.ts +490 -0
  119. package/templates/_shared/migration-ui/common/src/migrations/index.ts +886 -0
  120. package/templates/_shared/persistence/auth/{{slugKebabCase}}.php.mustache +290 -0
  121. package/templates/_shared/persistence/core/package.json.mustache +46 -0
  122. package/templates/_shared/persistence/core/scripts/sync-rest-contracts.ts.mustache +113 -0
  123. package/templates/_shared/persistence/core/scripts/sync-types-to-block-json.ts.mustache +125 -0
  124. package/templates/_shared/persistence/core/src/api-types.ts.mustache +21 -0
  125. package/templates/_shared/persistence/core/src/api-validators.ts.mustache +32 -0
  126. package/templates/_shared/persistence/core/src/api.ts.mustache +68 -0
  127. package/templates/_shared/persistence/core/src/data.ts.mustache +192 -0
  128. package/templates/_shared/persistence/core/src/index.tsx.mustache +25 -0
  129. package/templates/_shared/persistence/core/src/interactivity.ts.mustache +134 -0
  130. package/templates/_shared/persistence/core/src/save.tsx.mustache +5 -0
  131. package/templates/_shared/persistence/core/src/validators.ts.mustache +32 -0
  132. package/templates/_shared/persistence/core/{{slugKebabCase}}.php.mustache +336 -0
  133. package/templates/_shared/persistence/public/{{slugKebabCase}}.php.mustache +308 -0
  134. package/templates/_shared/presets/test-preset/.wp-env.test.json.mustache +16 -0
  135. package/templates/_shared/presets/test-preset/playwright.config.ts.mustache +22 -0
  136. package/templates/_shared/presets/test-preset/scripts/wait-for-wp-env.mjs.mustache +102 -0
  137. package/templates/_shared/presets/test-preset/scripts/wp-env-utils.cjs.mustache +32 -0
  138. package/templates/_shared/presets/test-preset/tests/e2e/smoke.spec.ts.mustache +34 -0
  139. package/templates/_shared/presets/wp-env/.wp-env.json.mustache +16 -0
  140. package/templates/_shared/rest-helpers/auth/inc/rest-auth.php.mustache +37 -0
  141. package/templates/_shared/rest-helpers/public/inc/rest-public.php.mustache +314 -0
  142. package/templates/_shared/rest-helpers/shared/inc/rest-shared.php.mustache +58 -0
  143. package/templates/_shared/workspace/persistence-auth/inc/rest-auth.php.mustache +36 -0
  144. package/templates/_shared/workspace/persistence-auth/inc/rest-shared.php.mustache +55 -0
  145. package/templates/_shared/workspace/persistence-auth/server.php.mustache +237 -0
  146. package/templates/_shared/workspace/persistence-public/inc/rest-public.php.mustache +273 -0
  147. package/templates/_shared/workspace/persistence-public/inc/rest-shared.php.mustache +55 -0
  148. package/templates/_shared/workspace/persistence-public/server.php.mustache +252 -0
  149. package/templates/basic/src/block.json.mustache +51 -0
  150. package/templates/basic/src/edit.tsx.mustache +128 -0
  151. package/templates/basic/src/editor.scss.mustache +8 -0
  152. package/templates/basic/src/hooks.ts.mustache +18 -0
  153. package/templates/basic/src/index.tsx.mustache +45 -0
  154. package/templates/basic/src/save.tsx.mustache +30 -0
  155. package/templates/basic/src/style.scss.mustache +40 -0
  156. package/templates/basic/src/types.ts.mustache +56 -0
  157. package/templates/basic/src/validators.ts.mustache +26 -0
  158. package/templates/compound/src/blocks/{{slugKebabCase}}/block.json.mustache +37 -0
  159. package/templates/compound/src/blocks/{{slugKebabCase}}/children.ts.mustache +25 -0
  160. package/templates/compound/src/blocks/{{slugKebabCase}}/edit.tsx.mustache +93 -0
  161. package/templates/compound/src/blocks/{{slugKebabCase}}/hooks.ts.mustache +11 -0
  162. package/templates/compound/src/blocks/{{slugKebabCase}}/index.tsx.mustache +25 -0
  163. package/templates/compound/src/blocks/{{slugKebabCase}}/save.tsx.mustache +32 -0
  164. package/templates/compound/src/blocks/{{slugKebabCase}}/style.scss.mustache +31 -0
  165. package/templates/compound/src/blocks/{{slugKebabCase}}/types.ts.mustache +13 -0
  166. package/templates/compound/src/blocks/{{slugKebabCase}}/validators.ts.mustache +17 -0
  167. package/templates/compound/src/blocks/{{slugKebabCase}}-item/block.json.mustache +35 -0
  168. package/templates/compound/src/blocks/{{slugKebabCase}}-item/edit.tsx.mustache +50 -0
  169. package/templates/compound/src/blocks/{{slugKebabCase}}-item/hooks.ts.mustache +11 -0
  170. package/templates/compound/src/blocks/{{slugKebabCase}}-item/index.tsx.mustache +25 -0
  171. package/templates/compound/src/blocks/{{slugKebabCase}}-item/save.tsx.mustache +24 -0
  172. package/templates/compound/src/blocks/{{slugKebabCase}}-item/types.ts.mustache +12 -0
  173. package/templates/compound/src/blocks/{{slugKebabCase}}-item/validators.ts.mustache +17 -0
  174. package/templates/interactivity/package.json.mustache +42 -0
  175. package/templates/interactivity/src/block.json.mustache +73 -0
  176. package/templates/interactivity/src/edit.tsx.mustache +270 -0
  177. package/templates/interactivity/src/index.tsx.mustache +32 -0
  178. package/templates/interactivity/src/interactivity.ts.mustache +152 -0
  179. package/templates/interactivity/src/save.tsx.mustache +101 -0
  180. package/templates/interactivity/src/style.scss.mustache +60 -0
  181. package/templates/interactivity/src/types.ts.mustache +32 -0
  182. package/templates/interactivity/src/validators.ts.mustache +36 -0
  183. package/templates/persistence/src/block.json.mustache +52 -0
  184. package/templates/persistence/src/edit.tsx.mustache +165 -0
  185. package/templates/persistence/src/render.php.mustache +126 -0
  186. package/templates/persistence/src/style.scss.mustache +46 -0
  187. package/templates/persistence/src/types.ts.mustache +55 -0
@@ -0,0 +1,821 @@
1
+ /// <reference path="./external-template-modules.d.ts" />
2
+ import fs from "node:fs";
3
+ import { promises as fsp } from "node:fs";
4
+ import { createRequire } from "node:module";
5
+ import os from "node:os";
6
+ import path from "node:path";
7
+ import { execFileSync } from "node:child_process";
8
+ import { pathToFileURL } from "node:url";
9
+ import npa from "npm-package-arg";
10
+ import semver from "semver";
11
+ import { x as extractTarball } from "tar";
12
+ import { BUILTIN_TEMPLATE_IDS, PROJECT_TOOLS_PACKAGE_ROOT, SHARED_BASE_TEMPLATE_ROOT, TEMPLATE_ROOT, isBuiltInTemplateId, } from "./template-registry.js";
13
+ import { isPlainObject } from "./object-utils.js";
14
+ import { getRemovedBuiltInTemplateMessage, isRemovedBuiltInTemplateId, } from "./template-defaults.js";
15
+ import { resolveBuiltInTemplateSource } from "./template-builtins.js";
16
+ import { getPackageVersions } from "./package-versions.js";
17
+ import { toSegmentPascalCase } from "./string-case.js";
18
+ import { copyRawDirectory, copyRenderedDirectory } from "./template-render.js";
19
+ const EXTERNAL_TEMPLATE_ENTRY_CANDIDATES = ["index.js", "index.cjs", "index.mjs"];
20
+ const TEMPLATE_WARNING_MESSAGE = "wp-typia owns package/tooling/sync setup for generated projects, so this external template setting is ignored.";
21
+ const OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE = "@wp-typia/create-workspace-template";
22
+ function isTemplatePathLocator(templateId) {
23
+ return path.isAbsolute(templateId) || templateId.startsWith("./") || templateId.startsWith("../");
24
+ }
25
+ function getTemplateWarning(key) {
26
+ return `Ignoring external template config key "${key}": ${TEMPLATE_WARNING_MESSAGE}`;
27
+ }
28
+ function resolveSourceSubpath(sourceDir, relativePath) {
29
+ const targetPath = path.resolve(sourceDir, relativePath);
30
+ const relativeTarget = path.relative(sourceDir, targetPath);
31
+ if (relativeTarget.startsWith("..") || path.isAbsolute(relativeTarget)) {
32
+ throw new Error(`Template path "${relativePath}" must stay within ${sourceDir}.`);
33
+ }
34
+ return targetPath;
35
+ }
36
+ function getExternalTemplateEntry(sourceDir) {
37
+ for (const filename of EXTERNAL_TEMPLATE_ENTRY_CANDIDATES) {
38
+ const candidate = path.join(sourceDir, filename);
39
+ if (fs.existsSync(candidate)) {
40
+ return candidate;
41
+ }
42
+ }
43
+ return null;
44
+ }
45
+ function selectRegistryVersion(metadata, locator) {
46
+ const distTags = isPlainObject(metadata["dist-tags"]) ? metadata["dist-tags"] : {};
47
+ const versions = isPlainObject(metadata.versions) ? metadata.versions : {};
48
+ const versionKeys = Object.keys(versions);
49
+ if (locator.type === "version") {
50
+ if (!versions[locator.fetchSpec]) {
51
+ throw new Error(`npm template package version not found: ${locator.raw}`);
52
+ }
53
+ return locator.fetchSpec;
54
+ }
55
+ if (locator.type === "tag") {
56
+ const taggedVersion = distTags[locator.fetchSpec];
57
+ if (typeof taggedVersion !== "string") {
58
+ throw new Error(`npm template package tag not found: ${locator.raw}`);
59
+ }
60
+ return taggedVersion;
61
+ }
62
+ const range = locator.fetchSpec.trim().length > 0 ? locator.fetchSpec : "*";
63
+ const matchedVersion = semver.maxSatisfying(versionKeys, range);
64
+ if (matchedVersion) {
65
+ return matchedVersion;
66
+ }
67
+ if (locator.fetchSpec.trim().length > 0) {
68
+ throw new Error(`Unable to resolve npm template version for ${locator.raw}. Requested "${locator.fetchSpec}" but available versions are: ${versionKeys.join(", ") || "(none)"}.`);
69
+ }
70
+ const latestVersion = distTags.latest;
71
+ if (typeof latestVersion === "string" && versions[latestVersion]) {
72
+ return latestVersion;
73
+ }
74
+ throw new Error(`Unable to resolve a published npm template version for ${locator.raw}.`);
75
+ }
76
+ async function fetchNpmTemplateSource(locator) {
77
+ const registryBase = (process.env.NPM_CONFIG_REGISTRY ?? "https://registry.npmjs.org").replace(/\/$/, "");
78
+ const metadataResponse = await fetch(`${registryBase}/${encodeURIComponent(locator.name)}`);
79
+ if (!metadataResponse.ok) {
80
+ throw new Error(`Failed to fetch npm template metadata for ${locator.raw}: ${metadataResponse.status}`);
81
+ }
82
+ const metadata = (await metadataResponse.json());
83
+ const resolvedVersion = selectRegistryVersion(metadata, locator);
84
+ const versions = isPlainObject(metadata.versions) ? metadata.versions : {};
85
+ const versionMetadata = versions[resolvedVersion];
86
+ if (!isPlainObject(versionMetadata) || !isPlainObject(versionMetadata.dist)) {
87
+ throw new Error(`npm template metadata is missing dist information for ${locator.raw}@${resolvedVersion}.`);
88
+ }
89
+ const tarballUrl = versionMetadata.dist.tarball;
90
+ if (typeof tarballUrl !== "string" || tarballUrl.length === 0) {
91
+ throw new Error(`npm template metadata is missing tarball URL for ${locator.raw}@${resolvedVersion}.`);
92
+ }
93
+ const tempRoot = await fsp.mkdtemp(path.join(os.tmpdir(), "wp-typia-template-source-"));
94
+ const cleanup = async () => {
95
+ await fsp.rm(tempRoot, { force: true, recursive: true });
96
+ };
97
+ try {
98
+ const tarballResponse = await fetch(tarballUrl);
99
+ if (!tarballResponse.ok) {
100
+ throw new Error(`Failed to download npm template tarball for ${locator.raw}: ${tarballResponse.status}`);
101
+ }
102
+ const tarballPath = path.join(tempRoot, "template.tgz");
103
+ const unpackDir = path.join(tempRoot, "source");
104
+ await fsp.mkdir(unpackDir, { recursive: true });
105
+ await fsp.writeFile(tarballPath, Buffer.from(await tarballResponse.arrayBuffer()));
106
+ await extractTarball({
107
+ cwd: unpackDir,
108
+ file: tarballPath,
109
+ strip: 1,
110
+ });
111
+ await assertNoSymlinks(unpackDir);
112
+ return {
113
+ blockDir: unpackDir,
114
+ cleanup,
115
+ rootDir: unpackDir,
116
+ };
117
+ }
118
+ catch (error) {
119
+ await cleanup();
120
+ throw error;
121
+ }
122
+ }
123
+ async function normalizeWpTypiaTemplateSeed(seed) {
124
+ const tempRoot = await fsp.mkdtemp(path.join(os.tmpdir(), "wp-typia-template-source-"));
125
+ const normalizedDir = path.join(tempRoot, "template");
126
+ try {
127
+ await copyRawDirectory(seed.blockDir, normalizedDir, {
128
+ filter: (sourcePath, _targetPath, entry) => {
129
+ const mustacheVariantPath = path.join(path.dirname(sourcePath), `${entry.name}.mustache`);
130
+ return !(entry.isFile() &&
131
+ (entry.name === "package.json" || entry.name === "README.md") &&
132
+ fs.existsSync(mustacheVariantPath));
133
+ },
134
+ });
135
+ }
136
+ catch (error) {
137
+ await fsp.rm(tempRoot, { force: true, recursive: true });
138
+ throw error;
139
+ }
140
+ return {
141
+ blockDir: normalizedDir,
142
+ cleanup: async () => {
143
+ await fsp.rm(tempRoot, { force: true, recursive: true });
144
+ await seed.cleanup?.();
145
+ },
146
+ rootDir: normalizedDir,
147
+ selectedVariant: seed.selectedVariant,
148
+ warnings: seed.warnings,
149
+ };
150
+ }
151
+ /**
152
+ * Resolve a locally installed npm template package from the caller workspace.
153
+ *
154
+ * Bare package ids are preferred here so monorepo and offline workflows can
155
+ * use an already-installed template without forcing a registry fetch.
156
+ */
157
+ function resolveInstalledNpmTemplateSource(locator, cwd) {
158
+ if (locator.rawSpec !== "" && locator.rawSpec !== "*") {
159
+ return null;
160
+ }
161
+ const workspacePackagesRoot = path.resolve(PROJECT_TOOLS_PACKAGE_ROOT, "..");
162
+ if (fs.existsSync(workspacePackagesRoot)) {
163
+ for (const entry of fs.readdirSync(workspacePackagesRoot, { withFileTypes: true })) {
164
+ if (!entry.isDirectory()) {
165
+ continue;
166
+ }
167
+ const packageDir = path.join(workspacePackagesRoot, entry.name);
168
+ const packageJsonPath = path.join(packageDir, "package.json");
169
+ if (!fs.existsSync(packageJsonPath)) {
170
+ continue;
171
+ }
172
+ const manifest = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
173
+ if (manifest.name === locator.name) {
174
+ return {
175
+ blockDir: packageDir,
176
+ rootDir: packageDir,
177
+ };
178
+ }
179
+ }
180
+ }
181
+ const workspaceRequire = createRequire(path.join(path.resolve(cwd), "__wp_typia_template_resolver__.cjs"));
182
+ try {
183
+ const packageJsonPath = fs.realpathSync(workspaceRequire.resolve(`${locator.name}/package.json`));
184
+ const sourceDir = path.dirname(packageJsonPath);
185
+ return {
186
+ blockDir: sourceDir,
187
+ rootDir: sourceDir,
188
+ };
189
+ }
190
+ catch (error) {
191
+ const errorCode = typeof error === "object" && error !== null && "code" in error
192
+ ? String(error.code)
193
+ : "";
194
+ if (errorCode === "MODULE_NOT_FOUND" ||
195
+ errorCode === "ERR_PACKAGE_PATH_NOT_EXPORTED") {
196
+ for (const basePath of workspaceRequire.resolve.paths(locator.name) ?? []) {
197
+ const packageJsonPath = path.join(basePath, locator.name, "package.json");
198
+ if (!fs.existsSync(packageJsonPath)) {
199
+ continue;
200
+ }
201
+ const sourceDir = path.dirname(fs.realpathSync(packageJsonPath));
202
+ return {
203
+ blockDir: sourceDir,
204
+ rootDir: sourceDir,
205
+ };
206
+ }
207
+ return null;
208
+ }
209
+ throw error;
210
+ }
211
+ }
212
+ function isOfficialWorkspaceTemplateSeed(seed) {
213
+ const packageJsonPath = path.join(seed.rootDir, "package.json");
214
+ if (!fs.existsSync(packageJsonPath)) {
215
+ return false;
216
+ }
217
+ try {
218
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
219
+ return packageJson.name === OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE;
220
+ }
221
+ catch {
222
+ return false;
223
+ }
224
+ }
225
+ export function parseGitHubTemplateLocator(templateId) {
226
+ if (!templateId.startsWith("github:")) {
227
+ return null;
228
+ }
229
+ const [locatorBody, refSegment] = templateId.slice("github:".length).split("#", 2);
230
+ const segments = locatorBody.split("/").filter(Boolean);
231
+ if (segments.length < 3) {
232
+ throw new Error(`GitHub template locators must look like github:owner/repo/path[#ref]. Received: ${templateId}`);
233
+ }
234
+ const [owner, repo, ...sourcePathSegments] = segments;
235
+ return {
236
+ owner,
237
+ repo,
238
+ ref: refSegment ?? null,
239
+ sourcePath: sourcePathSegments.join("/"),
240
+ };
241
+ }
242
+ export function parseNpmTemplateLocator(templateId) {
243
+ if (isBuiltInTemplateId(templateId) || isTemplatePathLocator(templateId) || templateId.startsWith("github:")) {
244
+ return null;
245
+ }
246
+ try {
247
+ const parsed = npa(templateId);
248
+ if (!parsed.registry || !parsed.name) {
249
+ return null;
250
+ }
251
+ const parsedWithRawSpec = parsed;
252
+ const rawSpec = typeof parsedWithRawSpec.rawSpec === "string" ? parsedWithRawSpec.rawSpec : "";
253
+ return {
254
+ fetchSpec: typeof parsed.fetchSpec === "string" ? parsed.fetchSpec : "",
255
+ name: parsed.name,
256
+ raw: templateId,
257
+ rawSpec,
258
+ type: parsed.type,
259
+ };
260
+ }
261
+ catch {
262
+ return null;
263
+ }
264
+ }
265
+ export function parseTemplateLocator(templateId) {
266
+ if (isRemovedBuiltInTemplateId(templateId)) {
267
+ throw new Error(getRemovedBuiltInTemplateMessage(templateId));
268
+ }
269
+ const githubLocator = parseGitHubTemplateLocator(templateId);
270
+ if (githubLocator) {
271
+ return { kind: "github", locator: githubLocator };
272
+ }
273
+ if (isTemplatePathLocator(templateId)) {
274
+ return { kind: "path", templatePath: templateId };
275
+ }
276
+ const npmLocator = parseNpmTemplateLocator(templateId);
277
+ if (npmLocator) {
278
+ return { kind: "npm", locator: npmLocator };
279
+ }
280
+ throw new Error(`Unknown template "${templateId}". Expected one of: ${BUILTIN_TEMPLATE_IDS.join(", ")}, a local path, github:owner/repo/path[#ref], or an npm package spec.`);
281
+ }
282
+ function getDefaultCategoryFromBlockJson(blockJson) {
283
+ return typeof blockJson.category === "string" && blockJson.category.trim().length > 0
284
+ ? blockJson.category.trim()
285
+ : "widgets";
286
+ }
287
+ function getDefaultCategory(sourceDir) {
288
+ try {
289
+ const blockJson = readRemoteBlockJson(sourceDir);
290
+ return getDefaultCategoryFromBlockJson(blockJson);
291
+ }
292
+ catch {
293
+ return "widgets";
294
+ }
295
+ }
296
+ function getTemplateVariableContext(variables) {
297
+ const { apiClientPackageVersion, blockRuntimePackageVersion, blockTypesPackageVersion, projectToolsPackageVersion, restPackageVersion, } = getPackageVersions();
298
+ return {
299
+ ...variables,
300
+ apiClientPackageVersion: variables.apiClientPackageVersion ?? apiClientPackageVersion,
301
+ blockRuntimePackageVersion: variables.blockRuntimePackageVersion ?? blockRuntimePackageVersion,
302
+ blockTypesPackageVersion: variables.blockTypesPackageVersion ?? blockTypesPackageVersion,
303
+ projectToolsPackageVersion: variables.projectToolsPackageVersion ?? projectToolsPackageVersion,
304
+ description: variables.description,
305
+ keyword: variables.keyword,
306
+ namespace: variables.namespace,
307
+ pascalCase: variables.pascalCase,
308
+ phpPrefix: variables.phpPrefix,
309
+ restPackageVersion: variables.restPackageVersion ?? restPackageVersion,
310
+ slug: variables.slug,
311
+ textDomain: variables.textDomain,
312
+ title: variables.title,
313
+ };
314
+ }
315
+ async function detectTemplateSourceFormat(sourceDir) {
316
+ if (fs.existsSync(path.join(sourceDir, "package.json.mustache"))) {
317
+ return "wp-typia";
318
+ }
319
+ if (getExternalTemplateEntry(sourceDir)) {
320
+ return "create-block-external";
321
+ }
322
+ const sourceRoot = fs.existsSync(path.join(sourceDir, "src")) ? path.join(sourceDir, "src") : sourceDir;
323
+ const blockJsonCandidates = [
324
+ path.join(sourceDir, "block.json"),
325
+ path.join(sourceRoot, "block.json"),
326
+ ];
327
+ const hasBlockJson = blockJsonCandidates.some((candidate) => fs.existsSync(candidate));
328
+ const hasIndexFile = ["index.js", "index.jsx", "index.ts", "index.tsx"].some((filename) => fs.existsSync(path.join(sourceRoot, filename)));
329
+ const hasEditFile = ["edit.js", "edit.jsx", "edit.ts", "edit.tsx"].some((filename) => fs.existsSync(path.join(sourceRoot, filename)));
330
+ const hasSaveFile = ["save.js", "save.jsx", "save.ts", "save.tsx"].some((filename) => fs.existsSync(path.join(sourceRoot, filename)));
331
+ if (hasBlockJson && hasIndexFile && hasEditFile && hasSaveFile) {
332
+ return "create-block-subset";
333
+ }
334
+ throw new Error(`Unsupported template source at ${sourceDir}. Expected a wp-typia template directory, an official create-block external template config, or a create-block subset with block.json and src/index/edit/save files.`);
335
+ }
336
+ function readRemoteBlockJson(blockDir) {
337
+ const sourceRoot = fs.existsSync(path.join(blockDir, "src")) ? path.join(blockDir, "src") : blockDir;
338
+ for (const candidate of [path.join(blockDir, "block.json"), path.join(sourceRoot, "block.json")]) {
339
+ if (fs.existsSync(candidate)) {
340
+ return JSON.parse(fs.readFileSync(candidate, "utf8"));
341
+ }
342
+ }
343
+ throw new Error(`Unable to locate block.json in ${blockDir}`);
344
+ }
345
+ async function assertNoSymlinks(sourceDir) {
346
+ const stats = await fsp.lstat(sourceDir);
347
+ if (stats.isSymbolicLink()) {
348
+ throw new Error(`Template sources may not include symbolic links: ${sourceDir}`);
349
+ }
350
+ if (!stats.isDirectory()) {
351
+ return;
352
+ }
353
+ for (const entry of await fsp.readdir(sourceDir)) {
354
+ await assertNoSymlinks(path.join(sourceDir, entry));
355
+ }
356
+ }
357
+ function renderTypeScriptLiteral(value) {
358
+ if (typeof value === "string") {
359
+ return JSON.stringify(value);
360
+ }
361
+ if (typeof value === "number" || typeof value === "boolean") {
362
+ return String(value);
363
+ }
364
+ return "undefined";
365
+ }
366
+ function renderTagsForAttribute(attribute) {
367
+ const tags = [];
368
+ if (typeof attribute.default === "string" || typeof attribute.default === "number" || typeof attribute.default === "boolean") {
369
+ tags.push(`tags.Default<${renderTypeScriptLiteral(attribute.default)}>`);
370
+ }
371
+ return tags;
372
+ }
373
+ function renderAttributeBaseType(attributeName, attribute) {
374
+ if (Array.isArray(attribute.enum) && attribute.enum.length > 0) {
375
+ return attribute.enum.map((item) => renderTypeScriptLiteral(item)).join(" | ");
376
+ }
377
+ switch (attribute.type) {
378
+ case "string":
379
+ return "string";
380
+ case "number":
381
+ return "number";
382
+ case "boolean":
383
+ return "boolean";
384
+ case "array":
385
+ return "unknown[]";
386
+ case "object":
387
+ return "Record<string, unknown>";
388
+ default:
389
+ if (typeof attributeName === "string" && attributeName.toLowerCase().includes("class")) {
390
+ return "string";
391
+ }
392
+ return "unknown";
393
+ }
394
+ }
395
+ function buildRemoteTypesSource(blockJson, context) {
396
+ const attributes = (blockJson.attributes ?? {});
397
+ const lines = ['import { tags } from "typia";', "", `export interface ${context.pascalCase}Attributes {`];
398
+ for (const [name, attribute] of Object.entries(attributes)) {
399
+ const baseType = renderAttributeBaseType(name, attribute);
400
+ const tagList = renderTagsForAttribute(attribute);
401
+ const baseTypeWithGrouping = tagList.length > 0 && baseType.includes(" | ") ? `(${baseType})` : baseType;
402
+ const renderedType = [baseTypeWithGrouping, ...tagList].join(" & ");
403
+ lines.push(` ${JSON.stringify(name)}?: ${renderedType};`);
404
+ }
405
+ lines.push("}", "");
406
+ return lines.join("\n");
407
+ }
408
+ function buildRemoteBlockJsonTemplate(blockJson) {
409
+ const merged = {
410
+ ...blockJson,
411
+ description: "{{description}}",
412
+ name: "{{namespace}}/{{slug}}",
413
+ textdomain: "{{textDomain}}",
414
+ title: "{{title}}",
415
+ };
416
+ if (!Array.isArray(merged.keywords) || merged.keywords.length === 0) {
417
+ merged.keywords = ["{{keyword}}", "typia", "block"];
418
+ }
419
+ return `${JSON.stringify(merged, null, "\t")}\n`;
420
+ }
421
+ async function rewriteBlockJsonImports(directory) {
422
+ const textExtensions = new Set([".js", ".jsx", ".ts", ".tsx"]);
423
+ const targetBlockJsonPath = path.join(directory, "block.json");
424
+ async function visit(currentPath) {
425
+ const stats = await fsp.stat(currentPath);
426
+ if (stats.isDirectory()) {
427
+ const entries = await fsp.readdir(currentPath);
428
+ for (const entry of entries) {
429
+ await visit(path.join(currentPath, entry));
430
+ }
431
+ return;
432
+ }
433
+ if (!textExtensions.has(path.extname(currentPath))) {
434
+ return;
435
+ }
436
+ const content = await fsp.readFile(currentPath, "utf8");
437
+ const relativeSpecifier = path.relative(path.dirname(currentPath), targetBlockJsonPath).replace(/\\/g, "/");
438
+ const normalizedSpecifier = relativeSpecifier.startsWith(".") ? relativeSpecifier : `./${relativeSpecifier}`;
439
+ const next = content.replace(/(['"])\.{1,2}\/[^'"]*block\.json\1/g, `$1${normalizedSpecifier}$1`);
440
+ if (next !== content) {
441
+ await fsp.writeFile(currentPath, next, "utf8");
442
+ }
443
+ }
444
+ await visit(directory);
445
+ }
446
+ async function patchRemotePackageJson(templateDir, needsInteractivity) {
447
+ const packageJsonPath = path.join(templateDir, "package.json.mustache");
448
+ const packageJson = JSON.parse(await fsp.readFile(packageJsonPath, "utf8"));
449
+ const existingDependencies = { ...(packageJson.dependencies ?? {}) };
450
+ const existingDevDependencies = { ...(packageJson.devDependencies ?? {}) };
451
+ delete existingDependencies["@wp-typia/project-tools"];
452
+ delete existingDevDependencies["@wp-typia/project-tools"];
453
+ packageJson.devDependencies = {
454
+ "@wp-typia/block-runtime": "{{blockRuntimePackageVersion}}",
455
+ "@wp-typia/block-types": "{{blockTypesPackageVersion}}",
456
+ ...existingDevDependencies,
457
+ };
458
+ if (needsInteractivity) {
459
+ packageJson.dependencies = {
460
+ ...existingDependencies,
461
+ "@wordpress/interactivity": "^6.29.0",
462
+ };
463
+ }
464
+ else if (Object.keys(existingDependencies).length > 0) {
465
+ packageJson.dependencies = existingDependencies;
466
+ }
467
+ else {
468
+ delete packageJson.dependencies;
469
+ }
470
+ await fsp.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`, "utf8");
471
+ }
472
+ function getSeedSourceRoot(blockDir) {
473
+ return fs.existsSync(path.join(blockDir, "src")) ? path.join(blockDir, "src") : blockDir;
474
+ }
475
+ function findSeedRenderPhp(seed) {
476
+ for (const candidate of [path.join(seed.blockDir, "render.php"), path.join(seed.rootDir, "render.php")]) {
477
+ if (fs.existsSync(candidate)) {
478
+ return candidate;
479
+ }
480
+ }
481
+ return null;
482
+ }
483
+ async function removeSeedEntryConflicts(templateDir) {
484
+ for (const filename of [
485
+ "block.json",
486
+ "block.json.mustache",
487
+ "edit.js",
488
+ "edit.jsx",
489
+ "edit.ts",
490
+ "edit.tsx",
491
+ "edit.tsx.mustache",
492
+ "hooks.ts",
493
+ "hooks.ts.mustache",
494
+ "index.js",
495
+ "index.jsx",
496
+ "index.ts",
497
+ "index.tsx",
498
+ "index.tsx.mustache",
499
+ "save.js",
500
+ "save.jsx",
501
+ "save.ts",
502
+ "save.tsx",
503
+ "save.tsx.mustache",
504
+ "style.css",
505
+ "style.scss",
506
+ "style.scss.mustache",
507
+ "types.ts",
508
+ "types.ts.mustache",
509
+ "validators.ts",
510
+ "validators.ts.mustache",
511
+ "view.js",
512
+ "view.jsx",
513
+ "view.ts",
514
+ "view.tsx",
515
+ ]) {
516
+ await fsp.rm(path.join(templateDir, "src", filename), { force: true });
517
+ }
518
+ }
519
+ async function normalizeCreateBlockSubset(seed, context) {
520
+ const tempRoot = await fsp.mkdtemp(path.join(os.tmpdir(), "wp-typia-remote-template-"));
521
+ const templateDir = path.join(tempRoot, "template");
522
+ const blockJson = readRemoteBlockJson(seed.blockDir);
523
+ const sourceRoot = getSeedSourceRoot(seed.blockDir);
524
+ await fsp.mkdir(templateDir, { recursive: true });
525
+ for (const layerDir of [SHARED_BASE_TEMPLATE_ROOT, path.join(TEMPLATE_ROOT, "basic")]) {
526
+ await fsp.cp(layerDir, templateDir, {
527
+ recursive: true,
528
+ force: true,
529
+ });
530
+ }
531
+ await removeSeedEntryConflicts(templateDir);
532
+ await fsp.cp(sourceRoot, path.join(templateDir, "src"), { recursive: true, force: true });
533
+ const remoteRenderPath = findSeedRenderPhp(seed);
534
+ if (remoteRenderPath) {
535
+ await fsp.copyFile(remoteRenderPath, path.join(templateDir, "render.php"));
536
+ }
537
+ if (seed.assetsDir && fs.existsSync(seed.assetsDir)) {
538
+ await fsp.cp(seed.assetsDir, path.join(templateDir, "assets"), { recursive: true, force: true });
539
+ }
540
+ await fsp.writeFile(path.join(templateDir, "src", "types.ts"), buildRemoteTypesSource(blockJson, context), "utf8");
541
+ await fsp.writeFile(path.join(templateDir, "src", "block.json"), buildRemoteBlockJsonTemplate(blockJson), "utf8");
542
+ await rewriteBlockJsonImports(path.join(templateDir, "src"));
543
+ const needsInteractivity = typeof blockJson.viewScriptModule === "string" ||
544
+ typeof blockJson.viewScript === "string" ||
545
+ fs.existsSync(path.join(templateDir, "src", "view.js")) ||
546
+ fs.existsSync(path.join(templateDir, "src", "view.ts")) ||
547
+ fs.existsSync(path.join(templateDir, "src", "view.tsx")) ||
548
+ fs.existsSync(path.join(templateDir, "src", "interactivity.js")) ||
549
+ fs.existsSync(path.join(templateDir, "src", "interactivity.ts"));
550
+ await patchRemotePackageJson(templateDir, needsInteractivity);
551
+ return {
552
+ id: "remote:create-block-subset",
553
+ defaultCategory: getDefaultCategoryFromBlockJson(blockJson),
554
+ description: "A wp-typia scaffold normalized from a create-block subset source",
555
+ features: ["Remote source", "create-block adapter", "Typia metadata pipeline"],
556
+ format: "create-block-subset",
557
+ selectedVariant: seed.selectedVariant ?? null,
558
+ templateDir,
559
+ warnings: seed.warnings ?? [],
560
+ cleanup: async () => {
561
+ await fsp.rm(tempRoot, { force: true, recursive: true });
562
+ if (seed.cleanup) {
563
+ await seed.cleanup();
564
+ }
565
+ },
566
+ };
567
+ }
568
+ async function loadExternalTemplateConfig(sourceDir) {
569
+ const entryPath = getExternalTemplateEntry(sourceDir);
570
+ if (!entryPath) {
571
+ throw new Error(`No external template config entry found in ${sourceDir}.`);
572
+ }
573
+ const moduleUrl = `${pathToFileURL(entryPath).href}?mtime=${fs.statSync(entryPath).mtimeMs}`;
574
+ const loadedModule = (await import(moduleUrl));
575
+ const loadedConfig = loadedModule.default ?? loadedModule;
576
+ if (!isPlainObject(loadedConfig)) {
577
+ throw new Error(`External template config must export an object: ${entryPath}`);
578
+ }
579
+ const warnings = [];
580
+ for (const ignoredKey of [
581
+ "pluginTemplatesPath",
582
+ "wpScripts",
583
+ "wpEnv",
584
+ "customScripts",
585
+ "npmDependencies",
586
+ "npmDevDependencies",
587
+ "customPackageJSON",
588
+ "pluginReadme",
589
+ "pluginHeader",
590
+ ]) {
591
+ if (ignoredKey in loadedConfig) {
592
+ warnings.push(getTemplateWarning(ignoredKey));
593
+ }
594
+ }
595
+ return {
596
+ config: loadedConfig,
597
+ warnings,
598
+ };
599
+ }
600
+ function getVariantFlagName(variantName) {
601
+ return `is${toSegmentPascalCase(variantName)}Variant`;
602
+ }
603
+ function getVariantKeys(config) {
604
+ return isPlainObject(config.variants) ? Object.keys(config.variants) : [];
605
+ }
606
+ function getVariantConfig(config, requestedVariant) {
607
+ const variantKeys = getVariantKeys(config);
608
+ if (variantKeys.length === 0) {
609
+ if (requestedVariant) {
610
+ throw new Error(`Variant "${requestedVariant}" was requested, but the external template does not define any variants.`);
611
+ }
612
+ return {
613
+ selectedVariant: null,
614
+ variantConfig: {},
615
+ };
616
+ }
617
+ const selectedVariant = requestedVariant ?? variantKeys[0];
618
+ if (!selectedVariant || !isPlainObject(config.variants?.[selectedVariant])) {
619
+ throw new Error(`Unknown template variant "${requestedVariant}". Expected one of: ${variantKeys.join(", ")}`);
620
+ }
621
+ return {
622
+ selectedVariant,
623
+ variantConfig: config.variants?.[selectedVariant],
624
+ };
625
+ }
626
+ function extractVariantRenderValues(variantConfig) {
627
+ const values = { ...variantConfig };
628
+ delete values.assetsPath;
629
+ delete values.blockTemplatesPath;
630
+ delete values.folderName;
631
+ delete values.transformer;
632
+ return values;
633
+ }
634
+ async function buildExternalTemplateView(context, config, selectedVariant, variantConfig) {
635
+ const mergedView = {
636
+ ...(config.defaultValues ?? {}),
637
+ ...extractVariantRenderValues(variantConfig),
638
+ ...context,
639
+ };
640
+ if (selectedVariant) {
641
+ mergedView.variant = selectedVariant;
642
+ mergedView[getVariantFlagName(selectedVariant)] = true;
643
+ }
644
+ if (!config.transformer) {
645
+ return mergedView;
646
+ }
647
+ const transformed = await config.transformer(mergedView);
648
+ if (!isPlainObject(transformed)) {
649
+ throw new Error("External template transformer(view) must return an object.");
650
+ }
651
+ return {
652
+ ...mergedView,
653
+ ...transformed,
654
+ };
655
+ }
656
+ async function renderCreateBlockExternalTemplate(sourceDir, context, requestedVariant) {
657
+ const { config, warnings } = await loadExternalTemplateConfig(sourceDir);
658
+ const { selectedVariant, variantConfig } = getVariantConfig(config, requestedVariant);
659
+ const blockTemplatesPath = (typeof variantConfig.blockTemplatesPath === "string" ? variantConfig.blockTemplatesPath : config.blockTemplatesPath) ??
660
+ null;
661
+ if (!blockTemplatesPath) {
662
+ throw new Error("External template config must define blockTemplatesPath.");
663
+ }
664
+ const tempRoot = await fsp.mkdtemp(path.join(os.tmpdir(), "wp-typia-create-block-external-"));
665
+ const cleanup = async () => {
666
+ await fsp.rm(tempRoot, { force: true, recursive: true });
667
+ };
668
+ try {
669
+ const renderedRoot = path.join(tempRoot, "rendered");
670
+ const folderName = (typeof variantConfig.folderName === "string" ? variantConfig.folderName : config.folderName) || ".";
671
+ const blockDir = resolveSourceSubpath(renderedRoot, folderName);
672
+ const view = await buildExternalTemplateView(context, config, selectedVariant, variantConfig);
673
+ const blockTemplateDir = resolveSourceSubpath(sourceDir, blockTemplatesPath);
674
+ await copyRenderedDirectory(blockTemplateDir, blockDir, view);
675
+ const assetsPath = typeof variantConfig.assetsPath === "string" ? variantConfig.assetsPath : config.assetsPath;
676
+ if (typeof assetsPath === "string" && assetsPath.trim().length > 0) {
677
+ await copyRawDirectory(resolveSourceSubpath(sourceDir, assetsPath), path.join(tempRoot, "assets"));
678
+ }
679
+ return {
680
+ assetsDir: fs.existsSync(path.join(tempRoot, "assets")) ? path.join(tempRoot, "assets") : undefined,
681
+ blockDir,
682
+ cleanup,
683
+ rootDir: tempRoot,
684
+ selectedVariant,
685
+ warnings,
686
+ };
687
+ }
688
+ catch (error) {
689
+ await cleanup();
690
+ throw error;
691
+ }
692
+ }
693
+ async function resolveGitHubTemplateSource(locator) {
694
+ const remoteRoot = await fsp.mkdtemp(path.join(os.tmpdir(), "wp-typia-template-source-"));
695
+ const cleanup = async () => {
696
+ await fsp.rm(remoteRoot, { force: true, recursive: true });
697
+ };
698
+ const checkoutDir = path.join(remoteRoot, "source");
699
+ try {
700
+ const args = ["clone", "--depth", "1"];
701
+ if (locator.ref) {
702
+ args.push("--branch", locator.ref);
703
+ }
704
+ args.push(`https://github.com/${locator.owner}/${locator.repo}.git`, checkoutDir);
705
+ execFileSync("git", args, { stdio: "ignore" });
706
+ const sourceDir = path.resolve(checkoutDir, locator.sourcePath);
707
+ const relativeSourceDir = path.relative(checkoutDir, sourceDir);
708
+ if (relativeSourceDir.startsWith("..") || path.isAbsolute(relativeSourceDir)) {
709
+ throw new Error("GitHub template path must stay within the cloned repository.");
710
+ }
711
+ if (!fs.existsSync(sourceDir)) {
712
+ throw new Error(`GitHub template path does not exist: ${locator.sourcePath}`);
713
+ }
714
+ await assertNoSymlinks(sourceDir);
715
+ return {
716
+ blockDir: sourceDir,
717
+ cleanup,
718
+ rootDir: sourceDir,
719
+ };
720
+ }
721
+ catch (error) {
722
+ await cleanup();
723
+ throw error;
724
+ }
725
+ }
726
+ async function resolveTemplateSeed(locator, cwd) {
727
+ if (locator.kind === "path") {
728
+ const sourceDir = path.resolve(cwd, locator.templatePath);
729
+ if (!fs.existsSync(sourceDir)) {
730
+ throw new Error(`Template path does not exist: ${sourceDir}`);
731
+ }
732
+ await assertNoSymlinks(sourceDir);
733
+ return {
734
+ blockDir: sourceDir,
735
+ rootDir: sourceDir,
736
+ };
737
+ }
738
+ if (locator.kind === "github") {
739
+ return resolveGitHubTemplateSource(locator.locator);
740
+ }
741
+ const installedSource = resolveInstalledNpmTemplateSource(locator.locator, cwd);
742
+ if (installedSource) {
743
+ await assertNoSymlinks(installedSource.blockDir);
744
+ return installedSource;
745
+ }
746
+ return fetchNpmTemplateSource(locator.locator);
747
+ }
748
+ export async function resolveTemplateSource(templateId, cwd, variables, variant) {
749
+ if (isBuiltInTemplateId(templateId)) {
750
+ if (variant) {
751
+ throw new Error(`--variant is only supported for official external template configs. Received variant "${variant}" for built-in template "${templateId}".`);
752
+ }
753
+ return resolveBuiltInTemplateSource(templateId, {
754
+ persistenceEnabled: variables.compoundPersistenceEnabled === "true",
755
+ persistencePolicy: variables.persistencePolicy === "public" ? "public" : "authenticated",
756
+ });
757
+ }
758
+ const locator = parseTemplateLocator(templateId);
759
+ const context = getTemplateVariableContext(variables);
760
+ const seed = await resolveTemplateSeed(locator, cwd);
761
+ const isOfficialWorkspaceTemplate = isOfficialWorkspaceTemplateSeed(seed);
762
+ let normalizedSeed = null;
763
+ try {
764
+ const format = await detectTemplateSourceFormat(seed.blockDir);
765
+ if (format === "wp-typia") {
766
+ if (variant) {
767
+ throw new Error(`--variant is only supported for official external template configs. Received variant "${variant}" for "${templateId}".`);
768
+ }
769
+ normalizedSeed = await normalizeWpTypiaTemplateSeed(seed);
770
+ return {
771
+ id: templateId,
772
+ defaultCategory: getDefaultCategory(seed.blockDir),
773
+ description: "A remote wp-typia template source",
774
+ features: ["Remote source", "wp-typia format"],
775
+ format,
776
+ isOfficialWorkspaceTemplate,
777
+ templateDir: normalizedSeed.blockDir,
778
+ cleanup: normalizedSeed.cleanup,
779
+ };
780
+ }
781
+ normalizedSeed =
782
+ format === "create-block-external"
783
+ ? await renderCreateBlockExternalTemplate(seed.blockDir, context, variant)
784
+ : variant
785
+ ? (() => {
786
+ throw new Error(`--variant is only supported for official external template configs. Received variant "${variant}" for "${templateId}".`);
787
+ })()
788
+ : seed;
789
+ if (format === "create-block-external") {
790
+ const normalized = await normalizeCreateBlockSubset(normalizedSeed, context);
791
+ return {
792
+ ...normalized,
793
+ cleanup: async () => {
794
+ await normalized.cleanup?.();
795
+ await seed.cleanup?.();
796
+ },
797
+ description: "A wp-typia scaffold normalized from an official create-block external template",
798
+ features: ["Remote source", "official external template", "Typia metadata pipeline"],
799
+ format,
800
+ id: "remote:create-block-external",
801
+ isOfficialWorkspaceTemplate,
802
+ selectedVariant: normalizedSeed.selectedVariant ?? null,
803
+ warnings: normalizedSeed.warnings ?? [],
804
+ };
805
+ }
806
+ const normalized = await normalizeCreateBlockSubset(normalizedSeed, context);
807
+ return {
808
+ ...normalized,
809
+ isOfficialWorkspaceTemplate,
810
+ };
811
+ }
812
+ catch (error) {
813
+ if (normalizedSeed?.cleanup && normalizedSeed !== seed) {
814
+ await normalizedSeed.cleanup();
815
+ }
816
+ if (seed.cleanup) {
817
+ await seed.cleanup();
818
+ }
819
+ throw error;
820
+ }
821
+ }