@wp-typia/project-tools 0.16.8 → 0.16.10

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