@wp-typia/project-tools 0.16.11 → 0.16.13

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 (119) hide show
  1. package/README.md +9 -3
  2. package/dist/runtime/block-generator-service-core.d.ts +8 -0
  3. package/dist/runtime/block-generator-service-core.js +274 -0
  4. package/dist/runtime/block-generator-service-spec.d.ts +104 -0
  5. package/dist/runtime/block-generator-service-spec.js +139 -0
  6. package/dist/runtime/block-generator-service.d.ts +2 -110
  7. package/dist/runtime/block-generator-service.js +2 -389
  8. package/dist/runtime/built-in-block-artifact-documents.d.ts +3 -0
  9. package/dist/runtime/built-in-block-artifact-documents.js +2 -0
  10. package/dist/runtime/built-in-block-artifact-types.d.ts +51 -0
  11. package/dist/runtime/built-in-block-artifact-types.js +304 -0
  12. package/dist/runtime/built-in-block-artifacts.js +4 -803
  13. package/dist/runtime/built-in-block-attribute-emitters.d.ts +71 -0
  14. package/dist/runtime/built-in-block-attribute-emitters.js +176 -0
  15. package/dist/runtime/built-in-block-attribute-specs.d.ts +38 -0
  16. package/dist/runtime/built-in-block-attribute-specs.js +358 -0
  17. package/dist/runtime/built-in-block-code-templates/basic.d.ts +4 -0
  18. package/dist/runtime/built-in-block-code-templates/basic.js +249 -0
  19. package/dist/runtime/built-in-block-code-templates/compound-child.d.ts +4 -0
  20. package/dist/runtime/built-in-block-code-templates/compound-child.js +138 -0
  21. package/dist/runtime/built-in-block-code-templates/compound-parent.d.ts +6 -0
  22. package/dist/runtime/built-in-block-code-templates/compound-parent.js +227 -0
  23. package/dist/runtime/built-in-block-code-templates/compound-persistence.d.ts +4 -0
  24. package/dist/runtime/built-in-block-code-templates/compound-persistence.js +478 -0
  25. package/dist/runtime/built-in-block-code-templates/compound.d.ts +3 -0
  26. package/dist/runtime/built-in-block-code-templates/compound.js +3 -0
  27. package/dist/runtime/built-in-block-code-templates/interactivity.d.ts +5 -0
  28. package/dist/runtime/built-in-block-code-templates/interactivity.js +547 -0
  29. package/dist/runtime/built-in-block-code-templates/persistence.d.ts +5 -0
  30. package/dist/runtime/built-in-block-code-templates/persistence.js +550 -0
  31. package/dist/runtime/built-in-block-code-templates/shared.d.ts +16 -0
  32. package/dist/runtime/built-in-block-code-templates/shared.js +53 -0
  33. package/dist/runtime/built-in-block-code-templates.d.ts +5 -32
  34. package/dist/runtime/built-in-block-code-templates.js +5 -2230
  35. package/dist/runtime/cli-add-block-config.d.ts +6 -0
  36. package/dist/runtime/cli-add-block-config.js +143 -0
  37. package/dist/runtime/cli-add-block-legacy-validator.d.ts +4 -0
  38. package/dist/runtime/cli-add-block-legacy-validator.js +168 -0
  39. package/dist/runtime/cli-add-block.js +3 -301
  40. package/dist/runtime/cli-add-workspace-assets.d.ts +38 -0
  41. package/dist/runtime/cli-add-workspace-assets.js +399 -0
  42. package/dist/runtime/cli-add-workspace.d.ts +2 -38
  43. package/dist/runtime/cli-add-workspace.js +5 -396
  44. package/dist/runtime/cli-diagnostics.js +76 -4
  45. package/dist/runtime/cli-doctor-environment.d.ts +12 -0
  46. package/dist/runtime/cli-doctor-environment.js +123 -0
  47. package/dist/runtime/cli-doctor-workspace.d.ts +18 -0
  48. package/dist/runtime/cli-doctor-workspace.js +308 -0
  49. package/dist/runtime/cli-doctor.d.ts +4 -2
  50. package/dist/runtime/cli-doctor.js +10 -405
  51. package/dist/runtime/cli-help.js +1 -1
  52. package/dist/runtime/cli-scaffold.d.ts +8 -1
  53. package/dist/runtime/cli-scaffold.js +47 -4
  54. package/dist/runtime/migration-command-surface.d.ts +67 -0
  55. package/dist/runtime/migration-command-surface.js +189 -0
  56. package/dist/runtime/migration-diff-rename.d.ts +13 -0
  57. package/dist/runtime/migration-diff-rename.js +192 -0
  58. package/dist/runtime/migration-diff-transform.d.ts +14 -0
  59. package/dist/runtime/migration-diff-transform.js +105 -0
  60. package/dist/runtime/migration-diff.js +12 -297
  61. package/dist/runtime/migration-generated-artifacts.d.ts +3 -0
  62. package/dist/runtime/migration-generated-artifacts.js +41 -0
  63. package/dist/runtime/migration-maintenance-fixtures.d.ts +23 -0
  64. package/dist/runtime/migration-maintenance-fixtures.js +126 -0
  65. package/dist/runtime/migration-maintenance-verify.d.ts +26 -0
  66. package/dist/runtime/migration-maintenance-verify.js +262 -0
  67. package/dist/runtime/migration-maintenance.d.ts +2 -0
  68. package/dist/runtime/migration-maintenance.js +2 -0
  69. package/dist/runtime/migration-planning.d.ts +23 -0
  70. package/dist/runtime/migration-planning.js +131 -0
  71. package/dist/runtime/migration-project-config-source.d.ts +6 -0
  72. package/dist/runtime/migration-project-config-source.js +424 -0
  73. package/dist/runtime/migration-project-layout-discovery.d.ts +61 -0
  74. package/dist/runtime/migration-project-layout-discovery.js +337 -0
  75. package/dist/runtime/migration-project-layout-paths.d.ts +135 -0
  76. package/dist/runtime/migration-project-layout-paths.js +288 -0
  77. package/dist/runtime/migration-project-layout.d.ts +3 -0
  78. package/dist/runtime/migration-project-layout.js +2 -0
  79. package/dist/runtime/migration-project-workspace.d.ts +47 -0
  80. package/dist/runtime/migration-project-workspace.js +212 -0
  81. package/dist/runtime/migration-project.d.ts +4 -94
  82. package/dist/runtime/migration-project.js +3 -1101
  83. package/dist/runtime/migration-render-diff-rule.d.ts +5 -0
  84. package/dist/runtime/migration-render-diff-rule.js +120 -0
  85. package/dist/runtime/migration-render-execution.d.ts +3 -0
  86. package/dist/runtime/migration-render-execution.js +428 -0
  87. package/dist/runtime/migration-render-generated.d.ts +27 -0
  88. package/dist/runtime/migration-render-generated.js +230 -0
  89. package/dist/runtime/migration-render-support.d.ts +3 -0
  90. package/dist/runtime/migration-render-support.js +16 -0
  91. package/dist/runtime/migration-render.d.ts +3 -33
  92. package/dist/runtime/migration-render.js +3 -789
  93. package/dist/runtime/migrations.d.ts +24 -121
  94. package/dist/runtime/migrations.js +12 -700
  95. package/dist/runtime/scaffold-apply-utils.d.ts +9 -0
  96. package/dist/runtime/scaffold-apply-utils.js +27 -4
  97. package/dist/runtime/scaffold-bootstrap.d.ts +45 -0
  98. package/dist/runtime/scaffold-bootstrap.js +185 -0
  99. package/dist/runtime/scaffold-onboarding.d.ts +12 -0
  100. package/dist/runtime/scaffold-onboarding.js +42 -5
  101. package/dist/runtime/scaffold-package-manager-files.d.ts +35 -0
  102. package/dist/runtime/scaffold-package-manager-files.js +79 -0
  103. package/dist/runtime/scaffold.d.ts +1 -12
  104. package/dist/runtime/scaffold.js +11 -394
  105. package/dist/runtime/template-source-contracts.d.ts +81 -0
  106. package/dist/runtime/template-source-contracts.js +1 -0
  107. package/dist/runtime/template-source-external.d.ts +21 -0
  108. package/dist/runtime/template-source-external.js +184 -0
  109. package/dist/runtime/template-source-locators.d.ts +4 -0
  110. package/dist/runtime/template-source-locators.js +72 -0
  111. package/dist/runtime/template-source-normalization.d.ts +7 -0
  112. package/dist/runtime/template-source-normalization.js +53 -0
  113. package/dist/runtime/template-source-remote.d.ts +23 -0
  114. package/dist/runtime/template-source-remote.js +336 -0
  115. package/dist/runtime/template-source-seeds.d.ts +12 -0
  116. package/dist/runtime/template-source-seeds.js +243 -0
  117. package/dist/runtime/template-source.d.ts +4 -86
  118. package/dist/runtime/template-source.js +9 -828
  119. package/package.json +4 -4
@@ -0,0 +1,184 @@
1
+ /// <reference path="./external-template-modules.d.ts" />
2
+ import fs from 'node:fs';
3
+ import { promises as fsp } from 'node:fs';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { pathToFileURL } from 'node:url';
7
+ import { isPlainObject } from './object-utils.js';
8
+ import { toSegmentPascalCase } from './string-case.js';
9
+ import { copyRawDirectory, copyRenderedDirectory } from './template-render.js';
10
+ /**
11
+ * Candidate filenames for official external template config entrypoints.
12
+ */
13
+ export const EXTERNAL_TEMPLATE_ENTRY_CANDIDATES = [
14
+ 'index.js',
15
+ 'index.cjs',
16
+ 'index.mjs',
17
+ ];
18
+ const TEMPLATE_WARNING_MESSAGE = 'wp-typia owns package/tooling/sync setup for generated projects, so this external template setting is ignored.';
19
+ function getTemplateWarning(key) {
20
+ return `Ignoring external template config key "${key}": ${TEMPLATE_WARNING_MESSAGE}`;
21
+ }
22
+ function resolveSourceSubpath(sourceDir, relativePath) {
23
+ const targetPath = path.resolve(sourceDir, relativePath);
24
+ const relativeTarget = path.relative(sourceDir, targetPath);
25
+ if (relativeTarget.startsWith('..') || path.isAbsolute(relativeTarget)) {
26
+ throw new Error(`Template path "${relativePath}" must stay within ${sourceDir}.`);
27
+ }
28
+ return targetPath;
29
+ }
30
+ /**
31
+ * Search a source directory for the first supported external template entry.
32
+ *
33
+ * @param sourceDir Directory that may contain an external template config entry.
34
+ * @returns The first matching entry path, or null when no supported entry exists.
35
+ */
36
+ export 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
+ async function loadExternalTemplateConfig(sourceDir) {
46
+ const entryPath = getExternalTemplateEntry(sourceDir);
47
+ if (!entryPath) {
48
+ throw new Error(`No external template config entry found in ${sourceDir}.`);
49
+ }
50
+ const moduleUrl = `${pathToFileURL(entryPath).href}?mtime=${fs.statSync(entryPath).mtimeMs}`;
51
+ const loadedModule = (await import(moduleUrl));
52
+ const loadedConfig = loadedModule.default ?? loadedModule;
53
+ if (!isPlainObject(loadedConfig)) {
54
+ throw new Error(`External template config must export an object: ${entryPath}`);
55
+ }
56
+ const warnings = [];
57
+ for (const ignoredKey of [
58
+ 'pluginTemplatesPath',
59
+ 'wpScripts',
60
+ 'wpEnv',
61
+ 'customScripts',
62
+ 'npmDependencies',
63
+ 'npmDevDependencies',
64
+ 'customPackageJSON',
65
+ 'pluginReadme',
66
+ 'pluginHeader',
67
+ ]) {
68
+ if (ignoredKey in loadedConfig) {
69
+ warnings.push(getTemplateWarning(ignoredKey));
70
+ }
71
+ }
72
+ return {
73
+ config: loadedConfig,
74
+ warnings,
75
+ };
76
+ }
77
+ function getVariantFlagName(variantName) {
78
+ return `is${toSegmentPascalCase(variantName)}Variant`;
79
+ }
80
+ function getVariantKeys(config) {
81
+ return isPlainObject(config.variants) ? Object.keys(config.variants) : [];
82
+ }
83
+ function getVariantConfig(config, requestedVariant) {
84
+ const variantKeys = getVariantKeys(config);
85
+ if (variantKeys.length === 0) {
86
+ if (requestedVariant) {
87
+ throw new Error(`Variant "${requestedVariant}" was requested, but the external template does not define any variants.`);
88
+ }
89
+ return {
90
+ selectedVariant: null,
91
+ variantConfig: {},
92
+ };
93
+ }
94
+ const selectedVariant = requestedVariant ?? variantKeys[0];
95
+ if (!selectedVariant || !isPlainObject(config.variants?.[selectedVariant])) {
96
+ throw new Error(`Unknown template variant "${requestedVariant}". Expected one of: ${variantKeys.join(', ')}`);
97
+ }
98
+ return {
99
+ selectedVariant,
100
+ variantConfig: config.variants?.[selectedVariant] ?? {},
101
+ };
102
+ }
103
+ function extractVariantRenderValues(variantConfig) {
104
+ const values = { ...variantConfig };
105
+ delete values.assetsPath;
106
+ delete values.blockTemplatesPath;
107
+ delete values.folderName;
108
+ delete values.transformer;
109
+ return values;
110
+ }
111
+ async function buildExternalTemplateView(context, config, selectedVariant, variantConfig) {
112
+ const mergedView = {
113
+ ...(config.defaultValues ?? {}),
114
+ ...extractVariantRenderValues(variantConfig),
115
+ ...context,
116
+ };
117
+ if (selectedVariant) {
118
+ mergedView.variant = selectedVariant;
119
+ mergedView[getVariantFlagName(selectedVariant)] = true;
120
+ }
121
+ if (!config.transformer) {
122
+ return mergedView;
123
+ }
124
+ const transformed = await config.transformer(mergedView);
125
+ if (!isPlainObject(transformed)) {
126
+ throw new Error('External template transformer(view) must return an object.');
127
+ }
128
+ return {
129
+ ...mergedView,
130
+ ...transformed,
131
+ };
132
+ }
133
+ /**
134
+ * Load an official external create-block template config and render its seed.
135
+ *
136
+ * @param sourceDir Source directory that contains the external template config.
137
+ * @param context Template render context used for the selected variant.
138
+ * @param requestedVariant Optional explicit variant override.
139
+ * @returns A rendered temporary seed directory with optional assets and cleanup.
140
+ */
141
+ export async function renderCreateBlockExternalTemplate(sourceDir, context, requestedVariant) {
142
+ const { config, warnings } = await loadExternalTemplateConfig(sourceDir);
143
+ const { selectedVariant, variantConfig } = getVariantConfig(config, requestedVariant);
144
+ const blockTemplatesPath = (typeof variantConfig.blockTemplatesPath === 'string'
145
+ ? variantConfig.blockTemplatesPath
146
+ : config.blockTemplatesPath) ?? null;
147
+ if (!blockTemplatesPath) {
148
+ throw new Error('External template config must define blockTemplatesPath.');
149
+ }
150
+ const tempRoot = await fsp.mkdtemp(path.join(os.tmpdir(), 'wp-typia-create-block-external-'));
151
+ const cleanup = async () => {
152
+ await fsp.rm(tempRoot, { force: true, recursive: true });
153
+ };
154
+ try {
155
+ const renderedRoot = path.join(tempRoot, 'rendered');
156
+ const folderName = (typeof variantConfig.folderName === 'string'
157
+ ? variantConfig.folderName
158
+ : config.folderName) || '.';
159
+ const blockDir = resolveSourceSubpath(renderedRoot, folderName);
160
+ const view = await buildExternalTemplateView(context, config, selectedVariant, variantConfig);
161
+ const blockTemplateDir = resolveSourceSubpath(sourceDir, blockTemplatesPath);
162
+ await copyRenderedDirectory(blockTemplateDir, blockDir, view);
163
+ const assetsPath = typeof variantConfig.assetsPath === 'string'
164
+ ? variantConfig.assetsPath
165
+ : config.assetsPath;
166
+ if (typeof assetsPath === 'string' && assetsPath.trim().length > 0) {
167
+ await copyRawDirectory(resolveSourceSubpath(sourceDir, assetsPath), path.join(tempRoot, 'assets'));
168
+ }
169
+ return {
170
+ assetsDir: fs.existsSync(path.join(tempRoot, 'assets'))
171
+ ? path.join(tempRoot, 'assets')
172
+ : undefined,
173
+ blockDir,
174
+ cleanup,
175
+ rootDir: tempRoot,
176
+ selectedVariant,
177
+ warnings,
178
+ };
179
+ }
180
+ catch (error) {
181
+ await cleanup();
182
+ throw error;
183
+ }
184
+ }
@@ -0,0 +1,4 @@
1
+ import type { GitHubTemplateLocator, NpmTemplateLocator, RemoteTemplateLocator } from './template-source-contracts.js';
2
+ export declare function parseGitHubTemplateLocator(templateId: string): GitHubTemplateLocator | null;
3
+ export declare function parseNpmTemplateLocator(templateId: string): NpmTemplateLocator | null;
4
+ export declare function parseTemplateLocator(templateId: string): RemoteTemplateLocator;
@@ -0,0 +1,72 @@
1
+ import path from 'node:path';
2
+ import npa from 'npm-package-arg';
3
+ import { BUILTIN_TEMPLATE_IDS, isBuiltInTemplateId, } from './template-registry.js';
4
+ import { getRemovedBuiltInTemplateMessage, isRemovedBuiltInTemplateId, } from './template-defaults.js';
5
+ function isTemplatePathLocator(templateId) {
6
+ return (path.isAbsolute(templateId) ||
7
+ templateId.startsWith('./') ||
8
+ templateId.startsWith('../'));
9
+ }
10
+ export function parseGitHubTemplateLocator(templateId) {
11
+ if (!templateId.startsWith('github:')) {
12
+ return null;
13
+ }
14
+ const [locatorBody, refSegment] = templateId
15
+ .slice('github:'.length)
16
+ .split('#', 2);
17
+ const segments = locatorBody.split('/').filter(Boolean);
18
+ if (segments.length < 3) {
19
+ throw new Error(`GitHub template locators must look like github:owner/repo/path[#ref]. Received: ${templateId}`);
20
+ }
21
+ const [owner, repo, ...sourcePathSegments] = segments;
22
+ return {
23
+ owner,
24
+ repo,
25
+ ref: refSegment ?? null,
26
+ sourcePath: sourcePathSegments.join('/'),
27
+ };
28
+ }
29
+ export function parseNpmTemplateLocator(templateId) {
30
+ if (isBuiltInTemplateId(templateId) ||
31
+ isTemplatePathLocator(templateId) ||
32
+ templateId.startsWith('github:')) {
33
+ return null;
34
+ }
35
+ try {
36
+ const parsed = npa(templateId);
37
+ if (!parsed.registry || !parsed.name) {
38
+ return null;
39
+ }
40
+ const parsedWithRawSpec = parsed;
41
+ const rawSpec = typeof parsedWithRawSpec.rawSpec === 'string'
42
+ ? parsedWithRawSpec.rawSpec
43
+ : '';
44
+ return {
45
+ fetchSpec: typeof parsed.fetchSpec === 'string' ? parsed.fetchSpec : '',
46
+ name: parsed.name,
47
+ raw: templateId,
48
+ rawSpec,
49
+ type: parsed.type,
50
+ };
51
+ }
52
+ catch {
53
+ return null;
54
+ }
55
+ }
56
+ export function parseTemplateLocator(templateId) {
57
+ if (isRemovedBuiltInTemplateId(templateId)) {
58
+ throw new Error(getRemovedBuiltInTemplateMessage(templateId));
59
+ }
60
+ const githubLocator = parseGitHubTemplateLocator(templateId);
61
+ if (githubLocator) {
62
+ return { kind: 'github', locator: githubLocator };
63
+ }
64
+ if (isTemplatePathLocator(templateId)) {
65
+ return { kind: 'path', templatePath: templateId };
66
+ }
67
+ const npmLocator = parseNpmTemplateLocator(templateId);
68
+ if (npmLocator) {
69
+ return { kind: 'npm', locator: npmLocator };
70
+ }
71
+ 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.`);
72
+ }
@@ -0,0 +1,7 @@
1
+ export { renderCreateBlockExternalTemplate } from './template-source-external.js';
2
+ export { getDefaultCategory, normalizeCreateBlockSubset, normalizeWpTypiaTemplateSeed, } from './template-source-remote.js';
3
+ import type { TemplateSourceFormat, TemplateVariableContext } from './template-source-contracts.js';
4
+ export declare function getTemplateVariableContext(variables: {
5
+ [key: string]: string;
6
+ }): TemplateVariableContext;
7
+ export declare function detectTemplateSourceFormat(sourceDir: string): Promise<TemplateSourceFormat>;
@@ -0,0 +1,53 @@
1
+ /// <reference path="./external-template-modules.d.ts" />
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { loadExternalTemplateLayerManifest } from './template-layers.js';
5
+ import { getPackageVersions } from './package-versions.js';
6
+ import { getExternalTemplateEntry } from './template-source-external.js';
7
+ export { renderCreateBlockExternalTemplate } from './template-source-external.js';
8
+ export { getDefaultCategory, normalizeCreateBlockSubset, normalizeWpTypiaTemplateSeed, } from './template-source-remote.js';
9
+ export function getTemplateVariableContext(variables) {
10
+ const { apiClientPackageVersion, blockRuntimePackageVersion, blockTypesPackageVersion, projectToolsPackageVersion, restPackageVersion, } = getPackageVersions();
11
+ return {
12
+ ...variables,
13
+ apiClientPackageVersion: variables.apiClientPackageVersion ?? apiClientPackageVersion,
14
+ blockRuntimePackageVersion: variables.blockRuntimePackageVersion ?? blockRuntimePackageVersion,
15
+ blockTypesPackageVersion: variables.blockTypesPackageVersion ?? blockTypesPackageVersion,
16
+ projectToolsPackageVersion: variables.projectToolsPackageVersion ?? projectToolsPackageVersion,
17
+ description: variables.description,
18
+ keyword: variables.keyword,
19
+ namespace: variables.namespace,
20
+ pascalCase: variables.pascalCase,
21
+ phpPrefix: variables.phpPrefix,
22
+ restPackageVersion: variables.restPackageVersion ?? restPackageVersion,
23
+ slug: variables.slug,
24
+ textDomain: variables.textDomain,
25
+ title: variables.title,
26
+ };
27
+ }
28
+ export async function detectTemplateSourceFormat(sourceDir) {
29
+ if (fs.existsSync(path.join(sourceDir, 'package.json.mustache'))) {
30
+ return 'wp-typia';
31
+ }
32
+ if (await loadExternalTemplateLayerManifest(sourceDir)) {
33
+ 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.`);
34
+ }
35
+ if (getExternalTemplateEntry(sourceDir)) {
36
+ return 'create-block-external';
37
+ }
38
+ const sourceRoot = fs.existsSync(path.join(sourceDir, 'src'))
39
+ ? path.join(sourceDir, 'src')
40
+ : sourceDir;
41
+ const blockJsonCandidates = [
42
+ path.join(sourceDir, 'block.json'),
43
+ path.join(sourceRoot, 'block.json'),
44
+ ];
45
+ const hasBlockJson = blockJsonCandidates.some((candidate) => fs.existsSync(candidate));
46
+ const hasIndexFile = ['index.js', 'index.jsx', 'index.ts', 'index.tsx'].some((filename) => fs.existsSync(path.join(sourceRoot, filename)));
47
+ const hasEditFile = ['edit.js', 'edit.jsx', 'edit.ts', 'edit.tsx'].some((filename) => fs.existsSync(path.join(sourceRoot, filename)));
48
+ const hasSaveFile = ['save.js', 'save.jsx', 'save.ts', 'save.tsx'].some((filename) => fs.existsSync(path.join(sourceRoot, filename)));
49
+ if (hasBlockJson && hasIndexFile && hasEditFile && hasSaveFile) {
50
+ return 'create-block-subset';
51
+ }
52
+ 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.`);
53
+ }
@@ -0,0 +1,23 @@
1
+ import type { ResolvedTemplateSource, SeedSource, TemplateVariableContext } from './template-source-contracts.js';
2
+ /**
3
+ * Read a remote block source and return its default block category.
4
+ *
5
+ * @param sourceDir Block source directory that may contain a block.json file.
6
+ * @returns The declared block category, or "widgets" when detection fails.
7
+ */
8
+ export declare function getDefaultCategory(sourceDir: string): string;
9
+ /**
10
+ * Copy a wp-typia seed into a normalized temporary template directory.
11
+ *
12
+ * @param seed Seed source whose files should be normalized into a temp root.
13
+ * @returns A cloned seed whose cleanup removes the temp root and original seed.
14
+ */
15
+ export declare function normalizeWpTypiaTemplateSeed(seed: SeedSource): Promise<SeedSource>;
16
+ /**
17
+ * Convert a create-block subset seed into a normalized wp-typia template source.
18
+ *
19
+ * @param seed Seed source produced from a remote create-block subset.
20
+ * @param context Template variable context used for generated artifacts.
21
+ * @returns A normalized template source rooted in a temporary directory.
22
+ */
23
+ export declare function normalizeCreateBlockSubset(seed: SeedSource, context: TemplateVariableContext): Promise<ResolvedTemplateSource>;
@@ -0,0 +1,336 @@
1
+ import fs from 'node:fs';
2
+ import { promises as fsp } from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { getBuiltInTemplateLayerDirs, isOmittableBuiltInTemplateLayerDir, } from './template-builtins.js';
6
+ import { copyRawDirectory } from './template-render.js';
7
+ function getDefaultCategoryFromBlockJson(blockJson) {
8
+ return typeof blockJson.category === 'string' &&
9
+ blockJson.category.trim().length > 0
10
+ ? blockJson.category.trim()
11
+ : 'widgets';
12
+ }
13
+ function readRemoteBlockJson(blockDir) {
14
+ const sourceRoot = fs.existsSync(path.join(blockDir, 'src'))
15
+ ? path.join(blockDir, 'src')
16
+ : blockDir;
17
+ for (const candidate of [
18
+ path.join(blockDir, 'block.json'),
19
+ path.join(sourceRoot, 'block.json'),
20
+ ]) {
21
+ if (fs.existsSync(candidate)) {
22
+ return JSON.parse(fs.readFileSync(candidate, 'utf8'));
23
+ }
24
+ }
25
+ throw new Error(`Unable to locate block.json in ${blockDir}`);
26
+ }
27
+ /**
28
+ * Read a remote block source and return its default block category.
29
+ *
30
+ * @param sourceDir Block source directory that may contain a block.json file.
31
+ * @returns The declared block category, or "widgets" when detection fails.
32
+ */
33
+ export function getDefaultCategory(sourceDir) {
34
+ try {
35
+ const blockJson = readRemoteBlockJson(sourceDir);
36
+ return getDefaultCategoryFromBlockJson(blockJson);
37
+ }
38
+ catch {
39
+ return 'widgets';
40
+ }
41
+ }
42
+ /**
43
+ * Copy a wp-typia seed into a normalized temporary template directory.
44
+ *
45
+ * @param seed Seed source whose files should be normalized into a temp root.
46
+ * @returns A cloned seed whose cleanup removes the temp root and original seed.
47
+ */
48
+ export async function normalizeWpTypiaTemplateSeed(seed) {
49
+ const tempRoot = await fsp.mkdtemp(path.join(os.tmpdir(), 'wp-typia-template-source-'));
50
+ const normalizedDir = path.join(tempRoot, 'template');
51
+ try {
52
+ await copyRawDirectory(seed.blockDir, normalizedDir, {
53
+ filter: (sourcePath, _targetPath, entry) => {
54
+ const mustacheVariantPath = path.join(path.dirname(sourcePath), `${entry.name}.mustache`);
55
+ return !(entry.isFile() &&
56
+ (entry.name === 'package.json' || entry.name === 'README.md') &&
57
+ fs.existsSync(mustacheVariantPath));
58
+ },
59
+ });
60
+ }
61
+ catch (error) {
62
+ await fsp.rm(tempRoot, { force: true, recursive: true });
63
+ throw error;
64
+ }
65
+ return {
66
+ blockDir: normalizedDir,
67
+ cleanup: async () => {
68
+ await fsp.rm(tempRoot, { force: true, recursive: true });
69
+ await seed.cleanup?.();
70
+ },
71
+ rootDir: normalizedDir,
72
+ selectedVariant: seed.selectedVariant,
73
+ warnings: seed.warnings,
74
+ };
75
+ }
76
+ function renderTypeScriptLiteral(value) {
77
+ if (typeof value === 'string') {
78
+ return JSON.stringify(value);
79
+ }
80
+ if (typeof value === 'number' || typeof value === 'boolean') {
81
+ return String(value);
82
+ }
83
+ return 'undefined';
84
+ }
85
+ function renderTagsForAttribute(attribute) {
86
+ const tags = [];
87
+ if (typeof attribute.default === 'string' ||
88
+ typeof attribute.default === 'number' ||
89
+ typeof attribute.default === 'boolean') {
90
+ tags.push(`tags.Default<${renderTypeScriptLiteral(attribute.default)}>`);
91
+ }
92
+ return tags;
93
+ }
94
+ function renderAttributeBaseType(attributeName, attribute) {
95
+ if (Array.isArray(attribute.enum) && attribute.enum.length > 0) {
96
+ return attribute.enum
97
+ .map((item) => renderTypeScriptLiteral(item))
98
+ .join(' | ');
99
+ }
100
+ switch (attribute.type) {
101
+ case 'string':
102
+ return 'string';
103
+ case 'number':
104
+ return 'number';
105
+ case 'boolean':
106
+ return 'boolean';
107
+ case 'array':
108
+ return 'unknown[]';
109
+ case 'object':
110
+ return 'Record<string, unknown>';
111
+ default:
112
+ if (typeof attributeName === 'string' &&
113
+ attributeName.toLowerCase().includes('class')) {
114
+ return 'string';
115
+ }
116
+ return 'unknown';
117
+ }
118
+ }
119
+ function buildRemoteTypesSource(blockJson, context) {
120
+ const attributes = (blockJson.attributes ?? {});
121
+ const lines = [
122
+ 'import { tags } from "typia";',
123
+ '',
124
+ `export interface ${context.pascalCase}Attributes {`,
125
+ ];
126
+ for (const [name, attribute] of Object.entries(attributes)) {
127
+ const baseType = renderAttributeBaseType(name, attribute);
128
+ const tagList = renderTagsForAttribute(attribute);
129
+ const baseTypeWithGrouping = tagList.length > 0 && baseType.includes(' | ')
130
+ ? `(${baseType})`
131
+ : baseType;
132
+ const renderedType = [baseTypeWithGrouping, ...tagList].join(' & ');
133
+ lines.push(` ${JSON.stringify(name)}?: ${renderedType};`);
134
+ }
135
+ lines.push('}', '');
136
+ return lines.join('\n');
137
+ }
138
+ function buildRemoteBlockJsonTemplate(blockJson) {
139
+ const merged = {
140
+ ...blockJson,
141
+ description: '{{description}}',
142
+ name: '{{namespace}}/{{slug}}',
143
+ textdomain: '{{textDomain}}',
144
+ title: '{{title}}',
145
+ };
146
+ if (!Array.isArray(merged.keywords) || merged.keywords.length === 0) {
147
+ merged.keywords = ['{{keyword}}', 'typia', 'block'];
148
+ }
149
+ return `${JSON.stringify(merged, null, '\t')}\n`;
150
+ }
151
+ async function rewriteBlockJsonImports(directory) {
152
+ const textExtensions = new Set(['.js', '.jsx', '.ts', '.tsx']);
153
+ const targetBlockJsonPath = path.join(directory, 'block.json');
154
+ async function visit(currentPath) {
155
+ const stats = await fsp.stat(currentPath);
156
+ if (stats.isDirectory()) {
157
+ const entries = await fsp.readdir(currentPath);
158
+ for (const entry of entries) {
159
+ await visit(path.join(currentPath, entry));
160
+ }
161
+ return;
162
+ }
163
+ if (!textExtensions.has(path.extname(currentPath))) {
164
+ return;
165
+ }
166
+ const content = await fsp.readFile(currentPath, 'utf8');
167
+ const relativeSpecifier = path
168
+ .relative(path.dirname(currentPath), targetBlockJsonPath)
169
+ .replace(/\\/g, '/');
170
+ const normalizedSpecifier = relativeSpecifier.startsWith('.')
171
+ ? relativeSpecifier
172
+ : `./${relativeSpecifier}`;
173
+ const next = content.replace(/(['"])\.{1,2}\/[^'"]*block\.json\1/g, `$1${normalizedSpecifier}$1`);
174
+ if (next !== content) {
175
+ await fsp.writeFile(currentPath, next, 'utf8');
176
+ }
177
+ }
178
+ await visit(directory);
179
+ }
180
+ async function patchRemotePackageJson(templateDir, needsInteractivity) {
181
+ const packageJsonPath = path.join(templateDir, 'package.json.mustache');
182
+ const packageJson = JSON.parse(await fsp.readFile(packageJsonPath, 'utf8'));
183
+ const existingDependencies = { ...(packageJson.dependencies ?? {}) };
184
+ const existingDevDependencies = { ...(packageJson.devDependencies ?? {}) };
185
+ delete existingDependencies['@wp-typia/project-tools'];
186
+ delete existingDevDependencies['@wp-typia/project-tools'];
187
+ packageJson.devDependencies = {
188
+ '@wp-typia/block-runtime': '{{blockRuntimePackageVersion}}',
189
+ '@wp-typia/block-types': '{{blockTypesPackageVersion}}',
190
+ ...existingDevDependencies,
191
+ };
192
+ if (needsInteractivity) {
193
+ packageJson.dependencies = {
194
+ ...existingDependencies,
195
+ '@wordpress/interactivity': '^6.29.0',
196
+ };
197
+ }
198
+ else if (Object.keys(existingDependencies).length > 0) {
199
+ packageJson.dependencies = existingDependencies;
200
+ }
201
+ else {
202
+ delete packageJson.dependencies;
203
+ }
204
+ await fsp.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`, 'utf8');
205
+ }
206
+ function getSeedSourceRoot(blockDir) {
207
+ return fs.existsSync(path.join(blockDir, 'src'))
208
+ ? path.join(blockDir, 'src')
209
+ : blockDir;
210
+ }
211
+ function findSeedRenderPhp(seed) {
212
+ for (const candidate of [
213
+ path.join(seed.blockDir, 'render.php'),
214
+ path.join(seed.rootDir, 'render.php'),
215
+ ]) {
216
+ if (fs.existsSync(candidate)) {
217
+ return candidate;
218
+ }
219
+ }
220
+ return null;
221
+ }
222
+ async function removeSeedEntryConflicts(templateDir) {
223
+ for (const filename of [
224
+ 'block.json',
225
+ 'block.json.mustache',
226
+ 'edit.js',
227
+ 'edit.jsx',
228
+ 'edit.ts',
229
+ 'edit.tsx',
230
+ 'edit.tsx.mustache',
231
+ 'hooks.ts',
232
+ 'hooks.ts.mustache',
233
+ 'index.js',
234
+ 'index.jsx',
235
+ 'index.ts',
236
+ 'index.tsx',
237
+ 'index.tsx.mustache',
238
+ 'save.js',
239
+ 'save.jsx',
240
+ 'save.ts',
241
+ 'save.tsx',
242
+ 'save.tsx.mustache',
243
+ 'style.css',
244
+ 'style.scss',
245
+ 'style.scss.mustache',
246
+ 'types.ts',
247
+ 'types.ts.mustache',
248
+ 'validators.ts',
249
+ 'validators.ts.mustache',
250
+ 'view.js',
251
+ 'view.jsx',
252
+ 'view.ts',
253
+ 'view.tsx',
254
+ ]) {
255
+ await fsp.rm(path.join(templateDir, 'src', filename), { force: true });
256
+ }
257
+ }
258
+ /**
259
+ * Convert a create-block subset seed into a normalized wp-typia template source.
260
+ *
261
+ * @param seed Seed source produced from a remote create-block subset.
262
+ * @param context Template variable context used for generated artifacts.
263
+ * @returns A normalized template source rooted in a temporary directory.
264
+ */
265
+ export async function normalizeCreateBlockSubset(seed, context) {
266
+ const tempRoot = await fsp.mkdtemp(path.join(os.tmpdir(), 'wp-typia-remote-template-'));
267
+ try {
268
+ const templateDir = path.join(tempRoot, 'template');
269
+ const blockJson = readRemoteBlockJson(seed.blockDir);
270
+ const sourceRoot = getSeedSourceRoot(seed.blockDir);
271
+ await fsp.mkdir(templateDir, { recursive: true });
272
+ for (const layerDir of getBuiltInTemplateLayerDirs('basic')) {
273
+ if (!fs.existsSync(layerDir)) {
274
+ if (isOmittableBuiltInTemplateLayerDir('basic', layerDir)) {
275
+ continue;
276
+ }
277
+ throw new Error(`Built-in template layer is missing: ${layerDir}`);
278
+ }
279
+ await fsp.cp(layerDir, templateDir, {
280
+ recursive: true,
281
+ force: true,
282
+ });
283
+ }
284
+ await removeSeedEntryConflicts(templateDir);
285
+ await fsp.cp(sourceRoot, path.join(templateDir, 'src'), {
286
+ recursive: true,
287
+ force: true,
288
+ });
289
+ const remoteRenderPath = findSeedRenderPhp(seed);
290
+ if (remoteRenderPath) {
291
+ await fsp.copyFile(remoteRenderPath, path.join(templateDir, 'render.php'));
292
+ }
293
+ if (seed.assetsDir && fs.existsSync(seed.assetsDir)) {
294
+ await fsp.cp(seed.assetsDir, path.join(templateDir, 'assets'), {
295
+ recursive: true,
296
+ force: true,
297
+ });
298
+ }
299
+ await fsp.writeFile(path.join(templateDir, 'src', 'types.ts'), buildRemoteTypesSource(blockJson, context), 'utf8');
300
+ await fsp.writeFile(path.join(templateDir, 'src', 'block.json'), buildRemoteBlockJsonTemplate(blockJson), 'utf8');
301
+ await rewriteBlockJsonImports(path.join(templateDir, 'src'));
302
+ const needsInteractivity = typeof blockJson.viewScriptModule === 'string' ||
303
+ typeof blockJson.viewScript === 'string' ||
304
+ fs.existsSync(path.join(templateDir, 'src', 'view.js')) ||
305
+ fs.existsSync(path.join(templateDir, 'src', 'view.ts')) ||
306
+ fs.existsSync(path.join(templateDir, 'src', 'view.tsx')) ||
307
+ fs.existsSync(path.join(templateDir, 'src', 'interactivity.js')) ||
308
+ fs.existsSync(path.join(templateDir, 'src', 'interactivity.ts'));
309
+ await patchRemotePackageJson(templateDir, needsInteractivity);
310
+ return {
311
+ id: 'remote:create-block-subset',
312
+ defaultCategory: getDefaultCategoryFromBlockJson(blockJson),
313
+ description: 'A wp-typia scaffold normalized from a create-block subset source',
314
+ features: [
315
+ 'Remote source',
316
+ 'create-block adapter',
317
+ 'Typia metadata pipeline',
318
+ ],
319
+ format: 'create-block-subset',
320
+ selectedVariant: seed.selectedVariant ?? null,
321
+ templateDir,
322
+ warnings: seed.warnings ?? [],
323
+ cleanup: async () => {
324
+ await fsp.rm(tempRoot, { force: true, recursive: true });
325
+ if (seed.cleanup) {
326
+ await seed.cleanup();
327
+ }
328
+ },
329
+ };
330
+ }
331
+ catch (error) {
332
+ await fsp.rm(tempRoot, { force: true, recursive: true });
333
+ await seed.cleanup?.();
334
+ throw error;
335
+ }
336
+ }