@wp-typia/project-tools 0.16.10 → 0.16.12

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