@wp-typia/project-tools 0.16.14 → 0.18.0

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 (79) hide show
  1. package/dist/runtime/block-generator-service-core.d.ts +1 -1
  2. package/dist/runtime/block-generator-service-core.js +2 -1
  3. package/dist/runtime/block-generator-service-spec.d.ts +8 -1
  4. package/dist/runtime/block-generator-service-spec.js +27 -0
  5. package/dist/runtime/built-in-block-artifacts.js +1 -0
  6. package/dist/runtime/built-in-block-code-artifacts.js +14 -1
  7. package/dist/runtime/built-in-block-code-templates/compound-child.d.ts +2 -2
  8. package/dist/runtime/built-in-block-code-templates/compound-child.js +30 -2
  9. package/dist/runtime/built-in-block-code-templates/compound-parent.d.ts +1 -1
  10. package/dist/runtime/built-in-block-code-templates/compound-parent.js +139 -19
  11. package/dist/runtime/built-in-block-code-templates/query-loop.d.ts +1 -0
  12. package/dist/runtime/built-in-block-code-templates/query-loop.js +70 -0
  13. package/dist/runtime/built-in-block-code-templates.d.ts +1 -0
  14. package/dist/runtime/built-in-block-code-templates.js +1 -0
  15. package/dist/runtime/built-in-block-non-ts-artifacts.js +2 -0
  16. package/dist/runtime/cli-add-block.d.ts +2 -1
  17. package/dist/runtime/cli-add-block.js +19 -1
  18. package/dist/runtime/cli-add-shared.d.ts +55 -1
  19. package/dist/runtime/cli-add-shared.js +101 -2
  20. package/dist/runtime/cli-add-workspace-assets.d.ts +21 -1
  21. package/dist/runtime/cli-add-workspace-assets.js +417 -1
  22. package/dist/runtime/cli-add-workspace-rest.d.ts +14 -0
  23. package/dist/runtime/cli-add-workspace-rest.js +1060 -0
  24. package/dist/runtime/cli-add-workspace.d.ts +10 -1
  25. package/dist/runtime/cli-add-workspace.js +10 -1
  26. package/dist/runtime/cli-add.d.ts +3 -3
  27. package/dist/runtime/cli-add.js +2 -2
  28. package/dist/runtime/cli-core.d.ts +3 -1
  29. package/dist/runtime/cli-core.js +2 -1
  30. package/dist/runtime/cli-doctor-workspace.js +135 -1
  31. package/dist/runtime/cli-help.js +10 -5
  32. package/dist/runtime/cli-scaffold.d.ts +11 -2
  33. package/dist/runtime/cli-scaffold.js +137 -36
  34. package/dist/runtime/cli-templates.d.ts +4 -4
  35. package/dist/runtime/cli-templates.js +79 -39
  36. package/dist/runtime/index.d.ts +4 -3
  37. package/dist/runtime/index.js +3 -2
  38. package/dist/runtime/local-dev-presets.js +27 -12
  39. package/dist/runtime/rest-resource-artifacts.d.ts +35 -0
  40. package/dist/runtime/rest-resource-artifacts.js +158 -0
  41. package/dist/runtime/scaffold-answer-resolution.d.ts +1 -1
  42. package/dist/runtime/scaffold-answer-resolution.js +94 -3
  43. package/dist/runtime/scaffold-apply-utils.d.ts +4 -3
  44. package/dist/runtime/scaffold-apply-utils.js +34 -17
  45. package/dist/runtime/scaffold-bootstrap.d.ts +15 -0
  46. package/dist/runtime/scaffold-bootstrap.js +29 -7
  47. package/dist/runtime/scaffold-documents.js +9 -9
  48. package/dist/runtime/scaffold-onboarding.js +17 -1
  49. package/dist/runtime/scaffold-package-manager-files.js +6 -1
  50. package/dist/runtime/scaffold-template-variables.js +6 -0
  51. package/dist/runtime/scaffold.d.ts +15 -1
  52. package/dist/runtime/scaffold.js +50 -8
  53. package/dist/runtime/template-defaults.d.ts +7 -0
  54. package/dist/runtime/template-defaults.js +4 -0
  55. package/dist/runtime/template-registry.d.ts +1 -1
  56. package/dist/runtime/template-registry.js +14 -1
  57. package/dist/runtime/template-render.d.ts +5 -2
  58. package/dist/runtime/template-render.js +9 -3
  59. package/dist/runtime/template-source-contracts.d.ts +11 -0
  60. package/dist/runtime/template-source-external.d.ts +1 -1
  61. package/dist/runtime/template-source-external.js +45 -13
  62. package/dist/runtime/template-source-normalization.d.ts +1 -1
  63. package/dist/runtime/template-source-normalization.js +5 -1
  64. package/dist/runtime/template-source-remote.d.ts +5 -0
  65. package/dist/runtime/template-source-remote.js +33 -0
  66. package/dist/runtime/template-source.js +30 -1
  67. package/dist/runtime/workspace-inventory.d.ts +43 -1
  68. package/dist/runtime/workspace-inventory.js +132 -1
  69. package/dist/runtime/workspace-project.d.ts +1 -1
  70. package/dist/runtime/workspace-project.js +2 -2
  71. package/package.json +3 -3
  72. package/templates/_shared/compound/core/scripts/add-compound-child.ts.mustache +428 -49
  73. package/templates/query-loop/inc/query-runtime.php.mustache +85 -0
  74. package/templates/query-loop/package.json.mustache +32 -0
  75. package/templates/query-loop/src/patterns/grid.php.mustache +49 -0
  76. package/templates/query-loop/src/patterns/list.php.mustache +48 -0
  77. package/templates/query-loop/src/query-extension.ts.mustache +41 -0
  78. package/templates/query-loop/webpack.config.js.mustache +16 -0
  79. package/templates/query-loop/{{slugKebabCase}}.php.mustache +84 -0
@@ -66,15 +66,19 @@ function stripTemplateExtension(entryName) {
66
66
  function renderTemplateDestinationName(entryName, view, renderString) {
67
67
  return renderString(stripTemplateExtension(entryName), view);
68
68
  }
69
- async function traverseTemplateDirectory({ prepareDirectory, renderString, sourceDir, targetDir, view, visitFile, }) {
69
+ async function traverseTemplateDirectory({ filter, prepareDirectory, renderString, sourceDir, targetDir, view, visitFile, }) {
70
70
  const entries = await fsp.readdir(sourceDir, { withFileTypes: true });
71
71
  for (const entry of entries) {
72
72
  const sourcePath = path.join(sourceDir, entry.name);
73
73
  const destinationName = renderTemplateDestinationName(entry.name, view, renderString);
74
74
  const destinationPath = resolveRenderedPath(targetDir, destinationName);
75
+ if (filter && !(await filter(sourcePath, destinationPath, entry))) {
76
+ continue;
77
+ }
75
78
  if (entry.isDirectory()) {
76
79
  await prepareDirectory?.(destinationPath);
77
80
  await traverseTemplateDirectory({
81
+ filter,
78
82
  prepareDirectory,
79
83
  renderString,
80
84
  sourceDir: sourcePath,
@@ -90,9 +94,10 @@ async function traverseTemplateDirectory({ prepareDirectory, renderString, sourc
90
94
  });
91
95
  }
92
96
  }
93
- async function copyTemplateDirectory({ renderString, sourceDir, targetDir, view, }) {
97
+ async function copyTemplateDirectory({ filter, renderString, sourceDir, targetDir, view, }) {
94
98
  await fsp.mkdir(targetDir, { recursive: true });
95
99
  await traverseTemplateDirectory({
100
+ filter,
96
101
  prepareDirectory: async (directoryPath) => {
97
102
  await fsp.mkdir(directoryPath, { recursive: true });
98
103
  },
@@ -134,8 +139,9 @@ export async function copyRawDirectory(sourceDir, targetDir, options = {}) {
134
139
  * Copy a template directory using full Mustache semantics for filenames and
135
140
  * text-file contents while leaving rendered values unescaped.
136
141
  */
137
- export async function copyRenderedDirectory(sourceDir, targetDir, view) {
142
+ export async function copyRenderedDirectory(sourceDir, targetDir, view, options = {}) {
138
143
  await copyTemplateDirectory({
144
+ filter: options.filter,
139
145
  renderString: renderMustacheTemplateString,
140
146
  sourceDir,
141
147
  targetDir,
@@ -35,6 +35,11 @@ export interface ResolvedTemplateSource {
35
35
  features: string[];
36
36
  format: TemplateSourceFormat;
37
37
  isOfficialWorkspaceTemplate?: boolean;
38
+ /**
39
+ * True when the resolved template can participate in scaffold migration UI
40
+ * seeding, such as rendered wp-typia workspace templates.
41
+ */
42
+ supportsMigrationUi?: boolean;
38
43
  templateDir: string;
39
44
  cleanup?: () => Promise<void>;
40
45
  selectedVariant?: string | null;
@@ -58,6 +63,11 @@ export interface ExternalTemplateConfig<TView extends UnknownRecord = TemplateVa
58
63
  blockTemplatesPath?: string;
59
64
  defaultValues?: Partial<TView>;
60
65
  folderName?: string;
66
+ /**
67
+ * Relative template root that renders a fuller wp-typia plugin/workspace
68
+ * scaffold instead of a create-block subset.
69
+ */
70
+ pluginTemplatesPath?: string;
61
71
  transformer?: (view: TView) => UnknownRecord | Promise<UnknownRecord>;
62
72
  variants?: Record<string, Partial<TView>>;
63
73
  }
@@ -65,6 +75,7 @@ export interface SeedSource {
65
75
  assetsDir?: string;
66
76
  blockDir: string;
67
77
  cleanup?: () => Promise<void>;
78
+ formatHint?: 'create-block-subset' | 'wp-typia';
68
79
  rootDir: string;
69
80
  selectedVariant?: string | null;
70
81
  warnings?: string[];
@@ -11,7 +11,7 @@ export declare const EXTERNAL_TEMPLATE_ENTRY_CANDIDATES: readonly ["index.js", "
11
11
  */
12
12
  export declare function getExternalTemplateEntry(sourceDir: string): string | null;
13
13
  /**
14
- * Load an official external create-block template config and render its seed.
14
+ * Load an official external template config and render its seed.
15
15
  *
16
16
  * @param sourceDir Source directory that contains the external template config.
17
17
  * @param context Template render context used for the selected variant.
@@ -55,7 +55,6 @@ async function loadExternalTemplateConfig(sourceDir) {
55
55
  }
56
56
  const warnings = [];
57
57
  for (const ignoredKey of [
58
- 'pluginTemplatesPath',
59
58
  'wpScripts',
60
59
  'wpEnv',
61
60
  'customScripts',
@@ -105,9 +104,42 @@ function extractVariantRenderValues(variantConfig) {
105
104
  delete values.assetsPath;
106
105
  delete values.blockTemplatesPath;
107
106
  delete values.folderName;
107
+ delete values.pluginTemplatesPath;
108
108
  delete values.transformer;
109
109
  return values;
110
110
  }
111
+ function resolveConfiguredTemplatePath(config, variantConfig) {
112
+ const variantPluginTemplatesPath = typeof variantConfig.pluginTemplatesPath === 'string'
113
+ ? variantConfig.pluginTemplatesPath
114
+ : null;
115
+ const variantBlockTemplatesPath = typeof variantConfig.blockTemplatesPath === 'string'
116
+ ? variantConfig.blockTemplatesPath
117
+ : null;
118
+ const configBlockTemplatesPath = typeof config.blockTemplatesPath === 'string'
119
+ ? config.blockTemplatesPath
120
+ : null;
121
+ const configPluginTemplatesPath = typeof config.pluginTemplatesPath === 'string'
122
+ ? config.pluginTemplatesPath
123
+ : null;
124
+ const templatePath = variantPluginTemplatesPath ??
125
+ variantBlockTemplatesPath ??
126
+ configBlockTemplatesPath ??
127
+ configPluginTemplatesPath;
128
+ if (!templatePath) {
129
+ throw new Error('External template config must define blockTemplatesPath or pluginTemplatesPath.');
130
+ }
131
+ const configuredFolderName = (typeof variantConfig.folderName === 'string'
132
+ ? variantConfig.folderName
133
+ : config.folderName) ?? null;
134
+ return {
135
+ folderName: configuredFolderName || '.',
136
+ formatHint: templatePath === variantPluginTemplatesPath ||
137
+ templatePath === configPluginTemplatesPath
138
+ ? 'wp-typia'
139
+ : 'create-block-subset',
140
+ templatePath,
141
+ };
142
+ }
111
143
  async function buildExternalTemplateView(context, config, selectedVariant, variantConfig) {
112
144
  const mergedView = {
113
145
  ...(config.defaultValues ?? {}),
@@ -131,7 +163,7 @@ async function buildExternalTemplateView(context, config, selectedVariant, varia
131
163
  };
132
164
  }
133
165
  /**
134
- * Load an official external create-block template config and render its seed.
166
+ * Load an official external template config and render its seed.
135
167
  *
136
168
  * @param sourceDir Source directory that contains the external template config.
137
169
  * @param context Template render context used for the selected variant.
@@ -141,25 +173,24 @@ async function buildExternalTemplateView(context, config, selectedVariant, varia
141
173
  export async function renderCreateBlockExternalTemplate(sourceDir, context, requestedVariant) {
142
174
  const { config, warnings } = await loadExternalTemplateConfig(sourceDir);
143
175
  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
- }
176
+ const { folderName, formatHint, templatePath } = resolveConfiguredTemplatePath(config, variantConfig);
150
177
  const tempRoot = await fsp.mkdtemp(path.join(os.tmpdir(), 'wp-typia-create-block-external-'));
151
178
  const cleanup = async () => {
152
179
  await fsp.rm(tempRoot, { force: true, recursive: true });
153
180
  };
154
181
  try {
155
182
  const renderedRoot = path.join(tempRoot, 'rendered');
156
- const folderName = (typeof variantConfig.folderName === 'string'
157
- ? variantConfig.folderName
158
- : config.folderName) || '.';
159
183
  const blockDir = resolveSourceSubpath(renderedRoot, folderName);
160
184
  const view = await buildExternalTemplateView(context, config, selectedVariant, variantConfig);
161
- const blockTemplateDir = resolveSourceSubpath(sourceDir, blockTemplatesPath);
162
- await copyRenderedDirectory(blockTemplateDir, blockDir, view);
185
+ const blockTemplateDir = resolveSourceSubpath(sourceDir, templatePath);
186
+ await copyRenderedDirectory(blockTemplateDir, blockDir, view, {
187
+ filter: (sourcePath, _destinationPath, entry) => {
188
+ const mustacheVariantPath = path.join(path.dirname(sourcePath), `${entry.name}.mustache`);
189
+ return !(entry.isFile() &&
190
+ (entry.name === 'package.json' || entry.name === 'README.md') &&
191
+ fs.existsSync(mustacheVariantPath));
192
+ },
193
+ });
163
194
  const assetsPath = typeof variantConfig.assetsPath === 'string'
164
195
  ? variantConfig.assetsPath
165
196
  : config.assetsPath;
@@ -172,6 +203,7 @@ export async function renderCreateBlockExternalTemplate(sourceDir, context, requ
172
203
  : undefined,
173
204
  blockDir,
174
205
  cleanup,
206
+ formatHint,
175
207
  rootDir: tempRoot,
176
208
  selectedVariant,
177
209
  warnings,
@@ -1,5 +1,5 @@
1
1
  export { renderCreateBlockExternalTemplate } from './template-source-external.js';
2
- export { getDefaultCategory, normalizeCreateBlockSubset, normalizeWpTypiaTemplateSeed, } from './template-source-remote.js';
2
+ export { getTemplateProjectType, getDefaultCategory, normalizeCreateBlockSubset, normalizeWpTypiaTemplateSeed, } from './template-source-remote.js';
3
3
  import type { TemplateSourceFormat, TemplateVariableContext } from './template-source-contracts.js';
4
4
  export declare function getTemplateVariableContext(variables: {
5
5
  [key: string]: string;
@@ -4,8 +4,9 @@ import path from 'node:path';
4
4
  import { loadExternalTemplateLayerManifest } from './template-layers.js';
5
5
  import { getPackageVersions } from './package-versions.js';
6
6
  import { getExternalTemplateEntry } from './template-source-external.js';
7
+ import { getTemplateProjectType } from './template-source-remote.js';
7
8
  export { renderCreateBlockExternalTemplate } from './template-source-external.js';
8
- export { getDefaultCategory, normalizeCreateBlockSubset, normalizeWpTypiaTemplateSeed, } from './template-source-remote.js';
9
+ export { getTemplateProjectType, getDefaultCategory, normalizeCreateBlockSubset, normalizeWpTypiaTemplateSeed, } from './template-source-remote.js';
9
10
  export function getTemplateVariableContext(variables) {
10
11
  const { apiClientPackageVersion, blockRuntimePackageVersion, blockTypesPackageVersion, projectToolsPackageVersion, restPackageVersion, } = getPackageVersions();
11
12
  return {
@@ -35,6 +36,9 @@ export async function detectTemplateSourceFormat(sourceDir) {
35
36
  if (getExternalTemplateEntry(sourceDir)) {
36
37
  return 'create-block-external';
37
38
  }
39
+ if (getTemplateProjectType(sourceDir) !== null) {
40
+ return 'wp-typia';
41
+ }
38
42
  const sourceRoot = fs.existsSync(path.join(sourceDir, 'src'))
39
43
  ? path.join(sourceDir, 'src')
40
44
  : sourceDir;
@@ -6,6 +6,11 @@ import type { ResolvedTemplateSource, SeedSource, TemplateVariableContext } from
6
6
  * @returns The declared block category, or "widgets" when detection fails.
7
7
  */
8
8
  export declare function getDefaultCategory(sourceDir: string): string;
9
+ /**
10
+ * Read `wpTypia.projectType` from a rendered or source template package
11
+ * manifest and return it when present.
12
+ */
13
+ export declare function getTemplateProjectType(sourceDir: string): string | null;
9
14
  /**
10
15
  * Copy a wp-typia seed into a normalized temporary template directory.
11
16
  *
@@ -39,6 +39,33 @@ export function getDefaultCategory(sourceDir) {
39
39
  return 'widgets';
40
40
  }
41
41
  }
42
+ function readTemplatePackageJson(sourceDir) {
43
+ for (const candidate of [
44
+ path.join(sourceDir, 'package.json.mustache'),
45
+ path.join(sourceDir, 'package.json'),
46
+ ]) {
47
+ if (!fs.existsSync(candidate)) {
48
+ continue;
49
+ }
50
+ try {
51
+ return JSON.parse(fs.readFileSync(candidate, 'utf8'));
52
+ }
53
+ catch {
54
+ continue;
55
+ }
56
+ }
57
+ return null;
58
+ }
59
+ /**
60
+ * Read `wpTypia.projectType` from a rendered or source template package
61
+ * manifest and return it when present.
62
+ */
63
+ export function getTemplateProjectType(sourceDir) {
64
+ const packageJson = readTemplatePackageJson(sourceDir);
65
+ return typeof packageJson?.wpTypia?.projectType === 'string'
66
+ ? packageJson.wpTypia.projectType
67
+ : null;
68
+ }
42
69
  /**
43
70
  * Copy a wp-typia seed into a normalized temporary template directory.
44
71
  *
@@ -57,6 +84,12 @@ export async function normalizeWpTypiaTemplateSeed(seed) {
57
84
  fs.existsSync(mustacheVariantPath));
58
85
  },
59
86
  });
87
+ if (seed.assetsDir && fs.existsSync(seed.assetsDir)) {
88
+ await fsp.cp(seed.assetsDir, path.join(normalizedDir, 'assets'), {
89
+ recursive: true,
90
+ force: true,
91
+ });
92
+ }
60
93
  }
61
94
  catch (error) {
62
95
  await fsp.rm(tempRoot, { force: true, recursive: true });
@@ -1,7 +1,7 @@
1
1
  import { OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, isBuiltInTemplateId, } from './template-registry.js';
2
2
  import { resolveBuiltInTemplateSource } from './template-builtins.js';
3
3
  import { parseTemplateLocator, } from './template-source-locators.js';
4
- import { detectTemplateSourceFormat, getDefaultCategory, getTemplateVariableContext, normalizeCreateBlockSubset, normalizeWpTypiaTemplateSeed, renderCreateBlockExternalTemplate, } from './template-source-normalization.js';
4
+ import { detectTemplateSourceFormat, getTemplateProjectType, getDefaultCategory, getTemplateVariableContext, normalizeCreateBlockSubset, normalizeWpTypiaTemplateSeed, renderCreateBlockExternalTemplate, } from './template-source-normalization.js';
5
5
  import { isOfficialWorkspaceTemplateSeed, resolveTemplateSeed, } from './template-source-seeds.js';
6
6
  export { parseGitHubTemplateLocator, parseNpmTemplateLocator, parseTemplateLocator, } from './template-source-locators.js';
7
7
  export { resolveTemplateSeed } from './template-source-seeds.js';
@@ -28,6 +28,7 @@ export async function resolveTemplateSource(templateId, cwd, variables, variant)
28
28
  throw new Error(`--variant is only supported for official external template configs. Received variant "${variant}" for "${templateId}".`);
29
29
  }
30
30
  normalizedSeed = await normalizeWpTypiaTemplateSeed(seed);
31
+ const supportsMigrationUi = getTemplateProjectType(seed.blockDir) === 'workspace';
31
32
  return {
32
33
  id: templateId,
33
34
  defaultCategory: getDefaultCategory(seed.blockDir),
@@ -35,6 +36,7 @@ export async function resolveTemplateSource(templateId, cwd, variables, variant)
35
36
  features: ['Remote source', 'wp-typia format'],
36
37
  format,
37
38
  isOfficialWorkspaceTemplate,
39
+ supportsMigrationUi,
38
40
  templateDir: normalizedSeed.blockDir,
39
41
  cleanup: normalizedSeed.cleanup,
40
42
  };
@@ -48,6 +50,33 @@ export async function resolveTemplateSource(templateId, cwd, variables, variant)
48
50
  })()
49
51
  : seed;
50
52
  if (format === 'create-block-external') {
53
+ const renderedFormat = normalizedSeed.formatHint ??
54
+ (await detectTemplateSourceFormat(normalizedSeed.blockDir));
55
+ if (renderedFormat === 'wp-typia') {
56
+ const normalized = await normalizeWpTypiaTemplateSeed(normalizedSeed);
57
+ const supportsMigrationUi = getTemplateProjectType(normalizedSeed.blockDir) === 'workspace';
58
+ return {
59
+ cleanup: async () => {
60
+ await normalized.cleanup?.();
61
+ await seed.cleanup?.();
62
+ },
63
+ defaultCategory: getDefaultCategory(normalizedSeed.blockDir),
64
+ description: 'A wp-typia scaffold normalized from an official external template config',
65
+ features: [
66
+ 'Remote source',
67
+ 'official external template',
68
+ 'wp-typia format',
69
+ ...(supportsMigrationUi ? ['workspace-capable scaffold'] : []),
70
+ ],
71
+ format,
72
+ id: 'remote:create-block-external',
73
+ isOfficialWorkspaceTemplate,
74
+ selectedVariant: normalizedSeed.selectedVariant ?? null,
75
+ supportsMigrationUi,
76
+ templateDir: normalized.blockDir,
77
+ warnings: normalizedSeed.warnings ?? [],
78
+ };
79
+ }
51
80
  const normalized = await normalizeCreateBlockSubset(normalizedSeed, context);
52
81
  return {
53
82
  ...normalized,
@@ -19,14 +19,49 @@ export interface WorkspaceBindingSourceInventoryEntry {
19
19
  serverFile: string;
20
20
  slug: string;
21
21
  }
22
+ /**
23
+ * REST-resource entry parsed from `scripts/block-config.ts`.
24
+ *
25
+ * Each file path is stored relative to the workspace root so doctor checks and
26
+ * workspace mutation helpers can resolve the generated TypeScript, OpenAPI, and
27
+ * PHP route artifacts without guessing their locations.
28
+ */
29
+ export interface WorkspaceRestResourceInventoryEntry {
30
+ apiFile: string;
31
+ clientFile: string;
32
+ dataFile: string;
33
+ methods: string[];
34
+ namespace: string;
35
+ openApiFile: string;
36
+ phpFile: string;
37
+ slug: string;
38
+ typesFile: string;
39
+ validatorsFile: string;
40
+ }
41
+ /**
42
+ * Editor-plugin entry parsed from `scripts/block-config.ts`.
43
+ *
44
+ * @property file Relative path to the generated editor plugin entry file.
45
+ * @property slug Normalized editor plugin slug.
46
+ * @property slot Canonical editor shell slot for the plugin scaffold.
47
+ */
48
+ export interface WorkspaceEditorPluginInventoryEntry {
49
+ file: string;
50
+ slug: string;
51
+ slot: string;
52
+ }
22
53
  export interface WorkspaceInventory {
23
54
  bindingSources: WorkspaceBindingSourceInventoryEntry[];
24
55
  blockConfigPath: string;
25
56
  blocks: WorkspaceBlockInventoryEntry[];
26
57
  hasBindingSourcesSection: boolean;
58
+ hasEditorPluginsSection: boolean;
27
59
  hasPatternsSection: boolean;
60
+ hasRestResourcesSection: boolean;
28
61
  hasVariationsSection: boolean;
62
+ editorPlugins: WorkspaceEditorPluginInventoryEntry[];
29
63
  patterns: WorkspacePatternInventoryEntry[];
64
+ restResources: WorkspaceRestResourceInventoryEntry[];
30
65
  source: string;
31
66
  variations: WorkspaceVariationInventoryEntry[];
32
67
  }
@@ -34,6 +69,11 @@ export declare const BLOCK_CONFIG_ENTRY_MARKER = "\t// wp-typia add block entrie
34
69
  export declare const VARIATION_CONFIG_ENTRY_MARKER = "\t// wp-typia add variation entries";
35
70
  export declare const PATTERN_CONFIG_ENTRY_MARKER = "\t// wp-typia add pattern entries";
36
71
  export declare const BINDING_SOURCE_CONFIG_ENTRY_MARKER = "\t// wp-typia add binding-source entries";
72
+ export declare const REST_RESOURCE_CONFIG_ENTRY_MARKER = "\t// wp-typia add rest-resource entries";
73
+ /**
74
+ * Marker used to append generated editor-plugin entries into `EDITOR_PLUGINS`.
75
+ */
76
+ export declare const EDITOR_PLUGIN_CONFIG_ENTRY_MARKER = "\t// wp-typia add editor-plugin entries";
37
77
  /**
38
78
  * Parse workspace inventory entries from the source of `scripts/block-config.ts`.
39
79
  *
@@ -75,10 +115,12 @@ export declare function getWorkspaceBlockSelectOptions(projectDir: string): Arra
75
115
  * @param options Entry lists plus an optional source transformer.
76
116
  * @returns Updated source text with all requested inventory entries appended.
77
117
  */
78
- export declare function updateWorkspaceInventorySource(source: string, { blockEntries, bindingSourceEntries, patternEntries, variationEntries, transformSource, }?: {
118
+ export declare function updateWorkspaceInventorySource(source: string, { blockEntries, bindingSourceEntries, editorPluginEntries, patternEntries, restResourceEntries, variationEntries, transformSource, }?: {
79
119
  blockEntries?: string[];
80
120
  bindingSourceEntries?: string[];
121
+ editorPluginEntries?: string[];
81
122
  patternEntries?: string[];
123
+ restResourceEntries?: string[];
82
124
  transformSource?: (source: string) => string;
83
125
  variationEntries?: string[];
84
126
  }): string;
@@ -2,10 +2,16 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { readFile, writeFile } from "node:fs/promises";
4
4
  import ts from "typescript";
5
+ import { REST_RESOURCE_METHOD_IDS } from "./cli-add-shared.js";
5
6
  export const BLOCK_CONFIG_ENTRY_MARKER = "\t// wp-typia add block entries";
6
7
  export const VARIATION_CONFIG_ENTRY_MARKER = "\t// wp-typia add variation entries";
7
8
  export const PATTERN_CONFIG_ENTRY_MARKER = "\t// wp-typia add pattern entries";
8
9
  export const BINDING_SOURCE_CONFIG_ENTRY_MARKER = "\t// wp-typia add binding-source entries";
10
+ export const REST_RESOURCE_CONFIG_ENTRY_MARKER = "\t// wp-typia add rest-resource entries";
11
+ /**
12
+ * Marker used to append generated editor-plugin entries into `EDITOR_PLUGINS`.
13
+ */
14
+ export const EDITOR_PLUGIN_CONFIG_ENTRY_MARKER = "\t// wp-typia add editor-plugin entries";
9
15
  const VARIATIONS_INTERFACE_SECTION = `
10
16
 
11
17
  export interface WorkspaceVariationConfig {
@@ -47,6 +53,44 @@ export const BINDING_SOURCES: WorkspaceBindingSourceConfig[] = [
47
53
  \t// wp-typia add binding-source entries
48
54
  ];
49
55
  `;
56
+ const REST_RESOURCES_INTERFACE_SECTION = `
57
+
58
+ export interface WorkspaceRestResourceConfig {
59
+ \tapiFile: string;
60
+ \tclientFile: string;
61
+ \tdataFile: string;
62
+ \tmethods: Array< 'list' | 'read' | 'create' | 'update' | 'delete' >;
63
+ \tnamespace: string;
64
+ \topenApiFile: string;
65
+ \tphpFile: string;
66
+ \trestManifest?: ReturnType<
67
+ \t\ttypeof import( '@wp-typia/block-runtime/metadata-core' ).defineEndpointManifest
68
+ \t>;
69
+ \tslug: string;
70
+ \ttypesFile: string;
71
+ \tvalidatorsFile: string;
72
+ }
73
+ `;
74
+ const REST_RESOURCES_CONST_SECTION = `
75
+
76
+ export const REST_RESOURCES: WorkspaceRestResourceConfig[] = [
77
+ \t// wp-typia add rest-resource entries
78
+ ];
79
+ `;
80
+ const EDITOR_PLUGINS_INTERFACE_SECTION = `
81
+
82
+ export interface WorkspaceEditorPluginConfig {
83
+ \tfile: string;
84
+ \tslug: string;
85
+ \tslot: string;
86
+ }
87
+ `;
88
+ const EDITOR_PLUGINS_CONST_SECTION = `
89
+
90
+ export const EDITOR_PLUGINS: WorkspaceEditorPluginConfig[] = [
91
+ \t// wp-typia add editor-plugin entries
92
+ ];
93
+ `;
50
94
  function getPropertyNameText(name) {
51
95
  if (ts.isIdentifier(name) || ts.isStringLiteral(name)) {
52
96
  return name.text;
@@ -105,6 +149,27 @@ function getRequiredStringProperty(entryName, elementIndex, objectLiteral, key)
105
149
  }
106
150
  return value;
107
151
  }
152
+ function getRequiredStringArrayProperty(entryName, elementIndex, objectLiteral, key) {
153
+ for (const property of objectLiteral.properties) {
154
+ if (!ts.isPropertyAssignment(property)) {
155
+ continue;
156
+ }
157
+ const propertyName = getPropertyNameText(property.name);
158
+ if (propertyName !== key) {
159
+ continue;
160
+ }
161
+ if (!ts.isArrayLiteralExpression(property.initializer)) {
162
+ throw new Error(`${entryName}[${elementIndex}] must use an array literal for "${key}" in scripts/block-config.ts.`);
163
+ }
164
+ return property.initializer.elements.map((element, itemIndex) => {
165
+ if (!ts.isStringLiteralLike(element)) {
166
+ throw new Error(`${entryName}[${elementIndex}].${key}[${itemIndex}] must use a string literal in scripts/block-config.ts.`);
167
+ }
168
+ return element.text;
169
+ });
170
+ }
171
+ throw new Error(`${entryName}[${elementIndex}] is missing required "${key}" in scripts/block-config.ts.`);
172
+ }
108
173
  function parseBlockEntries(arrayLiteral) {
109
174
  return arrayLiteral.elements.map((element, elementIndex) => {
110
175
  if (!ts.isObjectLiteralExpression(element)) {
@@ -154,6 +219,42 @@ function parseBindingSourceEntries(arrayLiteral) {
154
219
  };
155
220
  });
156
221
  }
222
+ function parseRestResourceEntries(arrayLiteral) {
223
+ return arrayLiteral.elements.map((element, elementIndex) => {
224
+ if (!ts.isObjectLiteralExpression(element)) {
225
+ throw new Error(`REST_RESOURCES[${elementIndex}] must be an object literal in scripts/block-config.ts.`);
226
+ }
227
+ const methods = getRequiredStringArrayProperty("REST_RESOURCES", elementIndex, element, "methods");
228
+ const invalidMethods = methods.filter((method) => !REST_RESOURCE_METHOD_IDS.includes(method));
229
+ if (invalidMethods.length > 0) {
230
+ throw new Error(`REST_RESOURCES[${elementIndex}].methods includes unsupported values: ${invalidMethods.join(", ")}.`);
231
+ }
232
+ return {
233
+ apiFile: getRequiredStringProperty("REST_RESOURCES", elementIndex, element, "apiFile"),
234
+ clientFile: getRequiredStringProperty("REST_RESOURCES", elementIndex, element, "clientFile"),
235
+ dataFile: getRequiredStringProperty("REST_RESOURCES", elementIndex, element, "dataFile"),
236
+ methods,
237
+ namespace: getRequiredStringProperty("REST_RESOURCES", elementIndex, element, "namespace"),
238
+ openApiFile: getRequiredStringProperty("REST_RESOURCES", elementIndex, element, "openApiFile"),
239
+ phpFile: getRequiredStringProperty("REST_RESOURCES", elementIndex, element, "phpFile"),
240
+ slug: getRequiredStringProperty("REST_RESOURCES", elementIndex, element, "slug"),
241
+ typesFile: getRequiredStringProperty("REST_RESOURCES", elementIndex, element, "typesFile"),
242
+ validatorsFile: getRequiredStringProperty("REST_RESOURCES", elementIndex, element, "validatorsFile"),
243
+ };
244
+ });
245
+ }
246
+ function parseEditorPluginEntries(arrayLiteral) {
247
+ return arrayLiteral.elements.map((element, elementIndex) => {
248
+ if (!ts.isObjectLiteralExpression(element)) {
249
+ throw new Error(`EDITOR_PLUGINS[${elementIndex}] must be an object literal in scripts/block-config.ts.`);
250
+ }
251
+ return {
252
+ file: getRequiredStringProperty("EDITOR_PLUGINS", elementIndex, element, "file"),
253
+ slug: getRequiredStringProperty("EDITOR_PLUGINS", elementIndex, element, "slug"),
254
+ slot: getRequiredStringProperty("EDITOR_PLUGINS", elementIndex, element, "slot"),
255
+ };
256
+ });
257
+ }
157
258
  /**
158
259
  * Parse workspace inventory entries from the source of `scripts/block-config.ts`.
159
260
  *
@@ -170,6 +271,8 @@ export function parseWorkspaceInventorySource(source) {
170
271
  const variationArray = findExportedArrayLiteral(sourceFile, "VARIATIONS");
171
272
  const patternArray = findExportedArrayLiteral(sourceFile, "PATTERNS");
172
273
  const bindingSourceArray = findExportedArrayLiteral(sourceFile, "BINDING_SOURCES");
274
+ const restResourceArray = findExportedArrayLiteral(sourceFile, "REST_RESOURCES");
275
+ const editorPluginArray = findExportedArrayLiteral(sourceFile, "EDITOR_PLUGINS");
173
276
  if (variationArray.found && !variationArray.array) {
174
277
  throw new Error("scripts/block-config.ts must export VARIATIONS as an array literal.");
175
278
  }
@@ -179,15 +282,29 @@ export function parseWorkspaceInventorySource(source) {
179
282
  if (bindingSourceArray.found && !bindingSourceArray.array) {
180
283
  throw new Error("scripts/block-config.ts must export BINDING_SOURCES as an array literal.");
181
284
  }
285
+ if (restResourceArray.found && !restResourceArray.array) {
286
+ throw new Error("scripts/block-config.ts must export REST_RESOURCES as an array literal.");
287
+ }
288
+ if (editorPluginArray.found && !editorPluginArray.array) {
289
+ throw new Error("scripts/block-config.ts must export EDITOR_PLUGINS as an array literal.");
290
+ }
182
291
  return {
183
292
  bindingSources: bindingSourceArray.array
184
293
  ? parseBindingSourceEntries(bindingSourceArray.array)
185
294
  : [],
186
295
  blocks: parseBlockEntries(blockArray.array),
187
296
  hasBindingSourcesSection: bindingSourceArray.found,
297
+ hasEditorPluginsSection: editorPluginArray.found,
188
298
  hasPatternsSection: patternArray.found,
299
+ hasRestResourcesSection: restResourceArray.found,
189
300
  hasVariationsSection: variationArray.found,
301
+ editorPlugins: editorPluginArray.array
302
+ ? parseEditorPluginEntries(editorPluginArray.array)
303
+ : [],
190
304
  patterns: patternArray.array ? parsePatternEntries(patternArray.array) : [],
305
+ restResources: restResourceArray.array
306
+ ? parseRestResourceEntries(restResourceArray.array)
307
+ : [],
191
308
  source,
192
309
  variations: variationArray.array ? parseVariationEntries(variationArray.array) : [],
193
310
  };
@@ -255,6 +372,18 @@ function ensureWorkspaceInventorySections(source) {
255
372
  if (!/export\s+const\s+BINDING_SOURCES\b/u.test(nextSource)) {
256
373
  nextSource += BINDING_SOURCES_CONST_SECTION;
257
374
  }
375
+ if (!/export\s+interface\s+WorkspaceRestResourceConfig\b/u.test(nextSource)) {
376
+ nextSource += REST_RESOURCES_INTERFACE_SECTION;
377
+ }
378
+ if (!/export\s+const\s+REST_RESOURCES\b/u.test(nextSource)) {
379
+ nextSource += REST_RESOURCES_CONST_SECTION;
380
+ }
381
+ if (!/export\s+interface\s+WorkspaceEditorPluginConfig\b/u.test(nextSource)) {
382
+ nextSource += EDITOR_PLUGINS_INTERFACE_SECTION;
383
+ }
384
+ if (!/export\s+const\s+EDITOR_PLUGINS\b/u.test(nextSource)) {
385
+ nextSource += EDITOR_PLUGINS_CONST_SECTION;
386
+ }
258
387
  return `${nextSource}\n`;
259
388
  }
260
389
  function appendEntriesAtMarker(source, marker, entries) {
@@ -277,7 +406,7 @@ function appendEntriesAtMarker(source, marker, entries) {
277
406
  * @param options Entry lists plus an optional source transformer.
278
407
  * @returns Updated source text with all requested inventory entries appended.
279
408
  */
280
- export function updateWorkspaceInventorySource(source, { blockEntries = [], bindingSourceEntries = [], patternEntries = [], variationEntries = [], transformSource, } = {}) {
409
+ export function updateWorkspaceInventorySource(source, { blockEntries = [], bindingSourceEntries = [], editorPluginEntries = [], patternEntries = [], restResourceEntries = [], variationEntries = [], transformSource, } = {}) {
281
410
  let nextSource = ensureWorkspaceInventorySections(source);
282
411
  if (transformSource) {
283
412
  nextSource = transformSource(nextSource);
@@ -286,6 +415,8 @@ export function updateWorkspaceInventorySource(source, { blockEntries = [], bind
286
415
  nextSource = appendEntriesAtMarker(nextSource, VARIATION_CONFIG_ENTRY_MARKER, variationEntries);
287
416
  nextSource = appendEntriesAtMarker(nextSource, PATTERN_CONFIG_ENTRY_MARKER, patternEntries);
288
417
  nextSource = appendEntriesAtMarker(nextSource, BINDING_SOURCE_CONFIG_ENTRY_MARKER, bindingSourceEntries);
418
+ nextSource = appendEntriesAtMarker(nextSource, REST_RESOURCE_CONFIG_ENTRY_MARKER, restResourceEntries);
419
+ nextSource = appendEntriesAtMarker(nextSource, EDITOR_PLUGIN_CONFIG_ENTRY_MARKER, editorPluginEntries);
289
420
  return nextSource;
290
421
  }
291
422
  /**
@@ -43,7 +43,7 @@ export declare function getInvalidWorkspaceProjectReason(startDir: string): stri
43
43
  * Parse a package-manager identifier from a `packageManager` field.
44
44
  *
45
45
  * @param packageManagerField Raw package-manager field such as `bun@1.3.11`.
46
- * @returns A normalized `PackageManagerId`, defaulting to `"bun"` when the
46
+ * @returns A normalized `PackageManagerId`, defaulting to `"npm"` when the
47
47
  * field is missing or unsupported.
48
48
  */
49
49
  export declare function parseWorkspacePackageManagerId(packageManagerField: string | undefined): PackageManagerId;
@@ -77,7 +77,7 @@ export function getInvalidWorkspaceProjectReason(startDir) {
77
77
  * Parse a package-manager identifier from a `packageManager` field.
78
78
  *
79
79
  * @param packageManagerField Raw package-manager field such as `bun@1.3.11`.
80
- * @returns A normalized `PackageManagerId`, defaulting to `"bun"` when the
80
+ * @returns A normalized `PackageManagerId`, defaulting to `"npm"` when the
81
81
  * field is missing or unsupported.
82
82
  */
83
83
  export function parseWorkspacePackageManagerId(packageManagerField) {
@@ -89,7 +89,7 @@ export function parseWorkspacePackageManagerId(packageManagerField) {
89
89
  case "yarn":
90
90
  return packageManagerId;
91
91
  default:
92
- return "bun";
92
+ return "npm";
93
93
  }
94
94
  }
95
95
  /**