@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
@@ -1,5 +1,5 @@
1
- import { getBuiltInTemplateLayerDirs } from "./template-builtins.js";
2
- import { getTemplateById, getTemplateSelectOptions, isBuiltInTemplateId, listTemplates, } from "./template-registry.js";
1
+ import { OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, getTemplateById, getTemplateSelectOptions, isBuiltInTemplateId, listTemplates, } from "./template-registry.js";
2
+ const WORKSPACE_TEMPLATE_ALIAS = "workspace";
3
3
  /**
4
4
  * Format one line of template list output for a built-in template.
5
5
  *
@@ -10,62 +10,102 @@ export function formatTemplateSummary(template) {
10
10
  return `${template.id.padEnd(14)} ${template.description}`;
11
11
  }
12
12
  /**
13
- * Format the feature hint line shown under a template summary.
13
+ * Format the feature and capability hint lines shown under a template summary.
14
14
  *
15
15
  * @param template Template metadata including the `features` list.
16
- * @returns Indented feature text for CLI list output.
16
+ * @returns Indented feature and capability text for CLI list output.
17
17
  */
18
18
  export function formatTemplateFeatures(template) {
19
- return ` ${template.features.join(" • ")}`;
19
+ const lines = [` Features: ${template.features.join(" • ")}`];
20
+ const capabilityHints = getTemplateCapabilityHints(template);
21
+ if (capabilityHints.length > 0) {
22
+ lines.push(` Supports: ${capabilityHints.join(" • ")}`);
23
+ }
24
+ if (template.id === OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE) {
25
+ lines.push(` Alias: ${WORKSPACE_TEMPLATE_ALIAS} (\`--template ${WORKSPACE_TEMPLATE_ALIAS}\`)`);
26
+ }
27
+ return lines.join("\n");
20
28
  }
21
29
  /**
22
30
  * Format the detailed template description for `templates inspect`.
23
31
  *
24
32
  * This expands special layer combinations for the `persistence` and `compound`
25
- * templates and returns a multi-line block including category, overlay path,
26
- * resolved layers, and feature labels.
33
+ * templates and returns a multi-line block centered on human-facing identity,
34
+ * capabilities, and logical layer composition.
27
35
  *
28
36
  * @param template Template metadata including `id`, `defaultCategory`,
29
37
  * `templateDir`, and `features`.
30
38
  * @returns Multi-line template details text for CLI output.
31
39
  */
32
40
  export function formatTemplateDetails(template) {
41
+ const detailLines = [
42
+ template.id,
43
+ `Summary: ${template.description}`,
44
+ ...getTemplateIdentityLines(template),
45
+ `Category: ${template.defaultCategory}`,
46
+ ];
47
+ const capabilityHints = getTemplateCapabilityHints(template);
48
+ if (capabilityHints.length > 0) {
49
+ detailLines.push("Capabilities:");
50
+ for (const capabilityHint of capabilityHints) {
51
+ detailLines.push(` - ${capabilityHint}`);
52
+ }
53
+ }
54
+ detailLines.push("Logical layers:");
55
+ for (const logicalLayer of getTemplateLogicalLayerSummaries(template)) {
56
+ detailLines.push(` - ${logicalLayer}`);
57
+ }
58
+ detailLines.push(`Features: ${template.features.join(", ")}`);
59
+ return detailLines.join("\n");
60
+ }
61
+ function getTemplateCapabilityHints(template) {
62
+ if (template.id === "persistence" || template.id === "compound") {
63
+ return ["--data-storage", "--persistence-policy", "external layers"];
64
+ }
65
+ if (template.id === "query-loop") {
66
+ return ["--query-post-type", "external layers"];
67
+ }
68
+ if (isBuiltInTemplateId(template.id)) {
69
+ return ["external layers"];
70
+ }
71
+ return [];
72
+ }
73
+ function getTemplateIdentityLines(template) {
74
+ if (template.id === OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE) {
75
+ return [
76
+ "Identity:",
77
+ ` - Official package: ${OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE}`,
78
+ ` - Alias: ${WORKSPACE_TEMPLATE_ALIAS} (\`--template ${WORKSPACE_TEMPLATE_ALIAS}\`)`,
79
+ "Type: official workspace scaffold",
80
+ ];
81
+ }
82
+ return [
83
+ "Identity:",
84
+ ` - Built-in template id: ${template.id}`,
85
+ "Type: built-in block scaffold",
86
+ ];
87
+ }
88
+ function getTemplateLogicalLayerSummaries(template) {
33
89
  if (!isBuiltInTemplateId(template.id)) {
90
+ return ["workspace package scaffold"];
91
+ }
92
+ if (template.id === "persistence") {
93
+ return [
94
+ "authenticated write policy: shared/base -> rest helpers (shared) -> persistence core -> authenticated write policy -> persistence overlay",
95
+ "public write policy: shared/base -> rest helpers (shared) -> persistence core -> public write policy -> persistence overlay",
96
+ ];
97
+ }
98
+ if (template.id === "compound") {
34
99
  return [
35
- template.id,
36
- template.description,
37
- `Category: ${template.defaultCategory}`,
38
- `Overlay path: ${template.templateDir}`,
39
- "Layers: workspace package scaffold",
40
- `Features: ${template.features.join(", ")}`,
41
- ].join("\n");
100
+ "pure block family: shared/base -> compound core -> compound overlay",
101
+ "authenticated persistence: shared/base -> compound core -> rest helpers (shared) -> compound persistence core -> authenticated write policy -> compound overlay",
102
+ "public persistence: shared/base -> compound core -> rest helpers (shared) -> compound persistence core -> public write policy -> compound overlay",
103
+ ];
42
104
  }
43
- const layers = template.id === "persistence"
44
- ? [
45
- `authenticated: ${getBuiltInTemplateLayerDirs(template.id, { persistencePolicy: "authenticated" }).join(" -> ")}`,
46
- `public: ${getBuiltInTemplateLayerDirs(template.id, { persistencePolicy: "public" }).join(" -> ")}`,
47
- ]
48
- : template.id === "compound"
49
- ? [
50
- `pure: ${getBuiltInTemplateLayerDirs(template.id).join(" -> ")}`,
51
- `authenticated+persistence: ${getBuiltInTemplateLayerDirs(template.id, {
52
- persistenceEnabled: true,
53
- persistencePolicy: "authenticated",
54
- }).join(" -> ")}`,
55
- `public+persistence: ${getBuiltInTemplateLayerDirs(template.id, {
56
- persistenceEnabled: true,
57
- persistencePolicy: "public",
58
- }).join(" -> ")}`,
59
- ]
60
- : [getBuiltInTemplateLayerDirs(template.id).join(" -> ")];
105
+ const overlayName = template.id === "query-loop" ? "query-loop overlay" : `${template.id} overlay`;
61
106
  return [
62
- template.id,
63
- template.description,
64
- `Category: ${template.defaultCategory}`,
65
- `Overlay path: ${template.templateDir}`,
66
- `Layers: ${layers.join("\n")}`,
67
- `Features: ${template.features.join(", ")}`,
68
- ].join("\n");
107
+ `shared/base -> ${overlayName}`,
108
+ ];
69
109
  }
70
110
  export { getTemplateById, getTemplateSelectOptions, listTemplates };
71
111
  export { isBuiltInTemplateId };
@@ -6,7 +6,8 @@
6
6
  * Consumers should prefer these exports for scaffold, add, migrate, doctor,
7
7
  * and workspace-aware helpers such as `getWorkspaceBlockSelectOptions`,
8
8
  * `runAddBlockCommand`, `runAddVariationCommand`, `runAddPatternCommand`,
9
- * `runAddBindingSourceCommand`, `runAddHookedBlockCommand`,
9
+ * `runAddBindingSourceCommand`, `runAddEditorPluginCommand`,
10
+ * `runAddHookedBlockCommand`,
10
11
  * `HOOKED_BLOCK_POSITION_IDS`, and `runDoctor`.
11
12
  */
12
13
  export { scaffoldProject, collectScaffoldAnswers, getDefaultAnswers, getTemplateVariables, resolvePackageManagerId, resolveTemplateId, } from "./scaffold.js";
@@ -21,5 +22,5 @@ export { buildCompoundChildStarterManifestDocument, getStarterManifestFiles, str
21
22
  export type { EndpointAuthIntent, EndpointOpenApiAuthMode, EndpointOpenApiContractDocument, EndpointOpenApiDocumentOptions, EndpointOpenApiEndpointDefinition, EndpointOpenApiMethod, EndpointWordPressAuthDefinition, EndpointWordPressAuthMechanism, JsonSchemaDocument, JsonSchemaProjectionProfile, JsonSchemaObject, NormalizedEndpointAuthDefinition, OpenApiDocument, OpenApiInfo, OpenApiOperation, OpenApiParameter, OpenApiPathItem, OpenApiSecurityScheme, } from "./schema-core.js";
22
23
  export { PACKAGE_MANAGER_IDS, PACKAGE_MANAGERS, formatPackageExecCommand, formatInstallCommand, formatRunScript, getPackageManager, getPackageManagerSelectOptions, transformPackageManagerText, } from "./package-managers.js";
23
24
  export { TEMPLATE_IDS, TEMPLATE_REGISTRY, getTemplateById, getTemplateSelectOptions, listTemplates, } from "./template-registry.js";
24
- export { createReadlinePrompt, createCliCommandError, CliDiagnosticError, formatCliDiagnosticError, formatAddHelpText, formatDoctorCheckLine, formatDoctorSummaryLine, formatHelpText, formatTemplateDetails, formatTemplateFeatures, formatTemplateSummary, getDoctorChecks, getDoctorFailureDetailLines, getFailingDoctorChecks, getNextSteps, getOptionalOnboarding, getWorkspaceBlockSelectOptions, HOOKED_BLOCK_POSITION_IDS, isCliDiagnosticError, runAddBindingSourceCommand, runAddBlockCommand, runAddHookedBlockCommand, runAddPatternCommand, runDoctor, runAddVariationCommand, runScaffoldFlow, } from "./cli-core.js";
25
- export type { CliDiagnosticMessage, DoctorCheck, HookedBlockPositionId, ReadlinePrompt, } from "./cli-core.js";
25
+ export { createReadlinePrompt, createCliCommandError, CliDiagnosticError, formatCliDiagnosticError, formatAddHelpText, formatDoctorCheckLine, formatDoctorSummaryLine, formatHelpText, formatTemplateDetails, formatTemplateFeatures, formatTemplateSummary, getDoctorChecks, getDoctorFailureDetailLines, getFailingDoctorChecks, getNextSteps, getOptionalOnboarding, getWorkspaceBlockSelectOptions, HOOKED_BLOCK_POSITION_IDS, EDITOR_PLUGIN_SLOT_IDS, isCliDiagnosticError, runAddBindingSourceCommand, runAddBlockCommand, runAddEditorPluginCommand, runAddHookedBlockCommand, runAddPatternCommand, runDoctor, runAddVariationCommand, runScaffoldFlow, } from "./cli-core.js";
26
+ export type { CliDiagnosticMessage, DoctorCheck, EditorPluginSlotId, HookedBlockPositionId, ReadlinePrompt, } from "./cli-core.js";
@@ -6,7 +6,8 @@
6
6
  * Consumers should prefer these exports for scaffold, add, migrate, doctor,
7
7
  * and workspace-aware helpers such as `getWorkspaceBlockSelectOptions`,
8
8
  * `runAddBlockCommand`, `runAddVariationCommand`, `runAddPatternCommand`,
9
- * `runAddBindingSourceCommand`, `runAddHookedBlockCommand`,
9
+ * `runAddBindingSourceCommand`, `runAddEditorPluginCommand`,
10
+ * `runAddHookedBlockCommand`,
10
11
  * `HOOKED_BLOCK_POSITION_IDS`, and `runDoctor`.
11
12
  */
12
13
  export { scaffoldProject, collectScaffoldAnswers, getDefaultAnswers, getTemplateVariables, resolvePackageManagerId, resolveTemplateId, } from "./scaffold.js";
@@ -18,4 +19,4 @@ export { manifestAttributeToJsonSchema, projectJsonSchemaDocument, manifestToJso
18
19
  export { buildCompoundChildStarterManifestDocument, getStarterManifestFiles, stringifyStarterManifest, } from "./starter-manifests.js";
19
20
  export { PACKAGE_MANAGER_IDS, PACKAGE_MANAGERS, formatPackageExecCommand, formatInstallCommand, formatRunScript, getPackageManager, getPackageManagerSelectOptions, transformPackageManagerText, } from "./package-managers.js";
20
21
  export { TEMPLATE_IDS, TEMPLATE_REGISTRY, getTemplateById, getTemplateSelectOptions, listTemplates, } from "./template-registry.js";
21
- export { createReadlinePrompt, createCliCommandError, CliDiagnosticError, formatCliDiagnosticError, formatAddHelpText, formatDoctorCheckLine, formatDoctorSummaryLine, formatHelpText, formatTemplateDetails, formatTemplateFeatures, formatTemplateSummary, getDoctorChecks, getDoctorFailureDetailLines, getFailingDoctorChecks, getNextSteps, getOptionalOnboarding, getWorkspaceBlockSelectOptions, HOOKED_BLOCK_POSITION_IDS, isCliDiagnosticError, runAddBindingSourceCommand, runAddBlockCommand, runAddHookedBlockCommand, runAddPatternCommand, runDoctor, runAddVariationCommand, runScaffoldFlow, } from "./cli-core.js";
22
+ export { createReadlinePrompt, createCliCommandError, CliDiagnosticError, formatCliDiagnosticError, formatAddHelpText, formatDoctorCheckLine, formatDoctorSummaryLine, formatHelpText, formatTemplateDetails, formatTemplateFeatures, formatTemplateSummary, getDoctorChecks, getDoctorFailureDetailLines, getFailingDoctorChecks, getNextSteps, getOptionalOnboarding, getWorkspaceBlockSelectOptions, HOOKED_BLOCK_POSITION_IDS, EDITOR_PLUGIN_SLOT_IDS, isCliDiagnosticError, runAddBindingSourceCommand, runAddBlockCommand, runAddEditorPluginCommand, runAddHookedBlockCommand, runAddPatternCommand, runDoctor, runAddVariationCommand, runScaffoldFlow, } from "./cli-core.js";
@@ -8,11 +8,22 @@ import fs from "node:fs";
8
8
  import { promises as fsp } from "node:fs";
9
9
  import path from "node:path";
10
10
  import { formatRunScript, } from "./package-managers.js";
11
- import { SHARED_TEST_PRESET_TEMPLATE_ROOT, SHARED_WP_ENV_PRESET_TEMPLATE_ROOT, } from "./template-registry.js";
11
+ import { OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, SHARED_TEST_PRESET_TEMPLATE_ROOT, SHARED_WP_ENV_PRESET_TEMPLATE_ROOT, } from "./template-registry.js";
12
12
  import { copyInterpolatedDirectory } from "./template-render.js";
13
13
  function templateHasPersistenceSync(templateId, compoundPersistenceEnabled) {
14
14
  return templateId === "persistence" || (templateId === "compound" && compoundPersistenceEnabled);
15
15
  }
16
+ function templateSupportsGeneratedSyncWatchers(templateId) {
17
+ return (templateId === "basic" ||
18
+ templateId === "interactivity" ||
19
+ templateId === "persistence" ||
20
+ templateId === "compound");
21
+ }
22
+ function templateUsesDevAsPrimaryEntrypoint(templateId) {
23
+ return (templateSupportsGeneratedSyncWatchers(templateId) ||
24
+ templateId === "query-loop" ||
25
+ templateId === OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE);
26
+ }
16
27
  function getWatchSyncTypesScript(packageManager, templateId) {
17
28
  if (templateId === "compound") {
18
29
  return `chokidar "src/blocks/**/types.ts" "scripts/block-config.ts" --debounce 200 -c "${formatRunScript(packageManager, "sync-types")}"`;
@@ -80,11 +91,16 @@ export async function applyLocalDevPresetFiles({ projectDir, variables, withTest
80
91
  */
81
92
  export async function applyGeneratedProjectDxPackageJson({ compoundPersistenceEnabled = false, packageManager, projectDir, templateId, withTestPreset = false, withWpEnv = false, }) {
82
93
  const hasPersistenceSync = templateHasPersistenceSync(templateId, compoundPersistenceEnabled);
94
+ const supportsGeneratedSyncWatchers = templateSupportsGeneratedSyncWatchers(templateId);
83
95
  await mutatePackageJson(projectDir, (packageJson) => {
84
96
  packageJson.devDependencies = {
85
97
  ...(packageJson.devDependencies ?? {}),
86
- "chokidar-cli": "^3.0.0",
87
- concurrently: "^9.0.1",
98
+ ...(supportsGeneratedSyncWatchers
99
+ ? {
100
+ "chokidar-cli": "^3.0.0",
101
+ concurrently: "^9.0.1",
102
+ }
103
+ : {}),
88
104
  };
89
105
  if (withWpEnv || withTestPreset) {
90
106
  packageJson.devDependencies["@wordpress/env"] = "^11.2.0";
@@ -95,12 +111,16 @@ export async function applyGeneratedProjectDxPackageJson({ compoundPersistenceEn
95
111
  const scripts = {
96
112
  ...(packageJson.scripts ?? {}),
97
113
  };
98
- scripts["start:editor"] = "wp-scripts start --experimental-modules";
99
- scripts["watch:sync-types"] = getWatchSyncTypesScript(packageManager, templateId);
114
+ if (supportsGeneratedSyncWatchers) {
115
+ scripts["start:editor"] = "wp-scripts start --experimental-modules";
116
+ scripts["watch:sync-types"] = getWatchSyncTypesScript(packageManager, templateId);
117
+ }
100
118
  if (hasPersistenceSync) {
101
119
  scripts["watch:sync-rest"] = getWatchSyncRestScript(packageManager, templateId);
102
120
  }
103
- scripts.dev = getDevScript(packageManager, compoundPersistenceEnabled, templateId);
121
+ if (supportsGeneratedSyncWatchers) {
122
+ scripts.dev = getDevScript(packageManager, compoundPersistenceEnabled, templateId);
123
+ }
104
124
  if (withWpEnv) {
105
125
  scripts["wp-env:start"] = "wp-env start";
106
126
  scripts["wp-env:stop"] = "wp-env stop";
@@ -123,10 +143,5 @@ export async function applyGeneratedProjectDxPackageJson({ compoundPersistenceEn
123
143
  * scaffolded template.
124
144
  */
125
145
  export function getPrimaryDevelopmentScript(templateId) {
126
- return templateId === "basic" ||
127
- templateId === "interactivity" ||
128
- templateId === "persistence" ||
129
- templateId === "compound"
130
- ? "dev"
131
- : "start";
146
+ return templateUsesDevAsPrimaryEntrypoint(templateId) ? "dev" : "start";
132
147
  }
@@ -0,0 +1,35 @@
1
+ import type { RestResourceMethodId } from "./cli-add-shared.js";
2
+ interface RestResourceTemplateVariablesLike {
3
+ namespace: string;
4
+ pascalCase: string;
5
+ slugKebabCase: string;
6
+ title: string;
7
+ }
8
+ interface SyncRestResourceArtifactsOptions {
9
+ clientFile: string;
10
+ methods: RestResourceMethodId[];
11
+ outputDir: string;
12
+ projectDir: string;
13
+ typesFile: string;
14
+ validatorsFile: string;
15
+ variables: RestResourceTemplateVariablesLike;
16
+ }
17
+ /**
18
+ * Build the endpoint manifest for a workspace-level REST resource scaffold.
19
+ *
20
+ * @param variables Template naming data used for contract names, routes, and OpenAPI info.
21
+ * @param methods Enabled REST methods for the generated resource.
22
+ * @returns Endpoint manifest consumed by schema, OpenAPI, and client generators.
23
+ */
24
+ export declare function buildRestResourceEndpointManifest(variables: RestResourceTemplateVariablesLike, methods: RestResourceMethodId[]): import("@wp-typia/block-runtime/metadata-core").EndpointManifestDefinition<Record<string, {
25
+ sourceTypeName: string;
26
+ }>, import("@wp-typia/block-runtime/schema-core").EndpointOpenApiEndpointDefinition[]>;
27
+ /**
28
+ * Synchronize generated schemas, OpenAPI output, and endpoint client code for
29
+ * a workspace-level REST resource scaffold.
30
+ *
31
+ * @param options Resource file paths, enabled methods, and naming variables.
32
+ * @returns A promise that resolves after every generated REST artifact has been refreshed.
33
+ */
34
+ export declare function syncRestResourceArtifacts({ clientFile, methods, outputDir, projectDir, typesFile, validatorsFile, variables, }: SyncRestResourceArtifactsOptions): Promise<void>;
35
+ export {};
@@ -0,0 +1,158 @@
1
+ import path from "node:path";
2
+ import { defineEndpointManifest, syncEndpointClient, syncRestOpenApi, syncTypeSchemas, } from "@wp-typia/block-runtime/metadata-core";
3
+ /**
4
+ * Build the endpoint manifest for a workspace-level REST resource scaffold.
5
+ *
6
+ * @param variables Template naming data used for contract names, routes, and OpenAPI info.
7
+ * @param methods Enabled REST methods for the generated resource.
8
+ * @returns Endpoint manifest consumed by schema, OpenAPI, and client generators.
9
+ */
10
+ export function buildRestResourceEndpointManifest(variables, methods) {
11
+ const basePath = `/${variables.namespace}/${variables.slugKebabCase}`;
12
+ const itemPath = `${basePath}/item`;
13
+ const contracts = {};
14
+ const endpoints = [];
15
+ if (methods.includes("list")) {
16
+ contracts["list-query"] = {
17
+ sourceTypeName: `${variables.pascalCase}ListQuery`,
18
+ };
19
+ contracts["list-response"] = {
20
+ sourceTypeName: `${variables.pascalCase}ListResponse`,
21
+ };
22
+ endpoints.push({
23
+ auth: "public",
24
+ method: "GET",
25
+ operationId: `list${variables.pascalCase}Resources`,
26
+ path: basePath,
27
+ queryContract: "list-query",
28
+ responseContract: "list-response",
29
+ summary: `List ${variables.title} resources.`,
30
+ tags: [variables.title],
31
+ });
32
+ }
33
+ if (methods.includes("read")) {
34
+ contracts["read-query"] = {
35
+ sourceTypeName: `${variables.pascalCase}ReadQuery`,
36
+ };
37
+ contracts["read-response"] = {
38
+ sourceTypeName: `${variables.pascalCase}ReadResponse`,
39
+ };
40
+ endpoints.push({
41
+ auth: "public",
42
+ method: "GET",
43
+ operationId: `read${variables.pascalCase}Resource`,
44
+ path: itemPath,
45
+ queryContract: "read-query",
46
+ responseContract: "read-response",
47
+ summary: `Read one ${variables.title} resource.`,
48
+ tags: [variables.title],
49
+ });
50
+ }
51
+ if (methods.includes("create")) {
52
+ contracts["create-request"] = {
53
+ sourceTypeName: `${variables.pascalCase}CreateRequest`,
54
+ };
55
+ contracts["create-response"] = {
56
+ sourceTypeName: `${variables.pascalCase}CreateResponse`,
57
+ };
58
+ endpoints.push({
59
+ auth: "authenticated",
60
+ bodyContract: "create-request",
61
+ method: "POST",
62
+ operationId: `create${variables.pascalCase}Resource`,
63
+ path: basePath,
64
+ responseContract: "create-response",
65
+ summary: `Create one ${variables.title} resource.`,
66
+ tags: [variables.title],
67
+ wordpressAuth: {
68
+ mechanism: "rest-nonce",
69
+ },
70
+ });
71
+ }
72
+ if (methods.includes("update")) {
73
+ contracts["update-query"] = {
74
+ sourceTypeName: `${variables.pascalCase}UpdateQuery`,
75
+ };
76
+ contracts["update-request"] = {
77
+ sourceTypeName: `${variables.pascalCase}UpdateRequest`,
78
+ };
79
+ contracts["update-response"] = {
80
+ sourceTypeName: `${variables.pascalCase}UpdateResponse`,
81
+ };
82
+ endpoints.push({
83
+ auth: "authenticated",
84
+ bodyContract: "update-request",
85
+ method: "POST",
86
+ operationId: `update${variables.pascalCase}Resource`,
87
+ path: itemPath,
88
+ queryContract: "update-query",
89
+ responseContract: "update-response",
90
+ summary: `Update one ${variables.title} resource.`,
91
+ tags: [variables.title],
92
+ wordpressAuth: {
93
+ mechanism: "rest-nonce",
94
+ },
95
+ });
96
+ }
97
+ if (methods.includes("delete")) {
98
+ contracts["delete-query"] = {
99
+ sourceTypeName: `${variables.pascalCase}DeleteQuery`,
100
+ };
101
+ contracts["delete-response"] = {
102
+ sourceTypeName: `${variables.pascalCase}DeleteResponse`,
103
+ };
104
+ endpoints.push({
105
+ auth: "authenticated",
106
+ method: "DELETE",
107
+ operationId: `delete${variables.pascalCase}Resource`,
108
+ path: itemPath,
109
+ queryContract: "delete-query",
110
+ responseContract: "delete-response",
111
+ summary: `Delete one ${variables.title} resource.`,
112
+ tags: [variables.title],
113
+ wordpressAuth: {
114
+ mechanism: "rest-nonce",
115
+ },
116
+ });
117
+ }
118
+ return defineEndpointManifest({
119
+ contracts,
120
+ endpoints,
121
+ info: {
122
+ title: `${variables.title} REST API`,
123
+ version: "1.0.0",
124
+ },
125
+ });
126
+ }
127
+ /**
128
+ * Synchronize generated schemas, OpenAPI output, and endpoint client code for
129
+ * a workspace-level REST resource scaffold.
130
+ *
131
+ * @param options Resource file paths, enabled methods, and naming variables.
132
+ * @returns A promise that resolves after every generated REST artifact has been refreshed.
133
+ */
134
+ export async function syncRestResourceArtifacts({ clientFile, methods, outputDir, projectDir, typesFile, validatorsFile, variables, }) {
135
+ const manifest = buildRestResourceEndpointManifest(variables, methods);
136
+ for (const [baseName, contract] of Object.entries(manifest.contracts)) {
137
+ await syncTypeSchemas({
138
+ jsonSchemaFile: path.join(outputDir, "api-schemas", `${baseName}.schema.json`),
139
+ openApiFile: path.join(outputDir, "api-schemas", `${baseName}.openapi.json`),
140
+ projectRoot: projectDir,
141
+ sourceTypeName: contract.sourceTypeName,
142
+ typesFile,
143
+ });
144
+ }
145
+ await syncRestOpenApi({
146
+ manifest,
147
+ openApiFile: path.join(outputDir, "api.openapi.json"),
148
+ projectRoot: projectDir,
149
+ typesFile,
150
+ });
151
+ await syncEndpointClient({
152
+ clientFile,
153
+ manifest,
154
+ projectRoot: projectDir,
155
+ typesFile,
156
+ validatorsFile,
157
+ });
158
+ }
@@ -34,4 +34,4 @@ export declare function resolvePackageManagerId({ packageManager, yes, isInterac
34
34
  * @param options Answer collection inputs including prompt callbacks and explicit overrides.
35
35
  * @returns The normalized scaffold answers used for rendering and file generation.
36
36
  */
37
- export declare function collectScaffoldAnswers({ projectName, templateId, yes, dataStorageMode, namespace, persistencePolicy, phpPrefix, promptText, textDomain, }: CollectScaffoldAnswersOptions): Promise<ScaffoldAnswers>;
37
+ export declare function collectScaffoldAnswers({ projectName, templateId, yes, dataStorageMode, namespace, persistencePolicy, phpPrefix, promptText, queryPostType, textDomain, }: CollectScaffoldAnswersOptions): Promise<ScaffoldAnswers>;
@@ -1,9 +1,15 @@
1
+ import { execSync } from 'node:child_process';
1
2
  import { PACKAGE_MANAGER_IDS, getPackageManager, } from './package-managers.js';
2
3
  import { normalizeBlockSlug, resolveScaffoldIdentifiers, validateBlockSlug, validateNamespace, } from './scaffold-identifiers.js';
3
4
  import { OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, TEMPLATE_IDS, getTemplateById, isBuiltInTemplateId, } from './template-registry.js';
4
5
  import { getRemovedBuiltInTemplateMessage, isRemovedBuiltInTemplateId, } from './template-defaults.js';
5
6
  import { toSnakeCase, toTitleCase, } from './string-case.js';
6
7
  const WORKSPACE_TEMPLATE_ALIAS = 'workspace';
8
+ const TEMPLATE_SELECTION_HINT = `--template <${[
9
+ ...TEMPLATE_IDS,
10
+ WORKSPACE_TEMPLATE_ALIAS,
11
+ ].join('|')}|./path|github:owner/repo/path[#ref]|npm-package>`;
12
+ const TEMPLATE_SUGGESTION_IDS = [...TEMPLATE_IDS, WORKSPACE_TEMPLATE_ALIAS];
7
13
  /**
8
14
  * Detect the current author name from local Git config.
9
15
  *
@@ -37,16 +43,91 @@ export function getDefaultAnswers(projectName, templateId) {
37
43
  namespace: slugDefault,
38
44
  persistencePolicy: templateId === 'persistence' ? 'authenticated' : undefined,
39
45
  phpPrefix: toSnakeCase(slugDefault),
46
+ queryPostType: templateId === 'query-loop' ? 'post' : undefined,
40
47
  slug: slugDefault,
41
48
  textDomain: slugDefault,
42
49
  title: toTitleCase(slugDefault),
43
50
  };
44
51
  }
52
+ function validateQueryPostType(value) {
53
+ const normalizedValue = value.trim().toLowerCase();
54
+ if (normalizedValue.length === 0) {
55
+ return 'Query post type is required.';
56
+ }
57
+ if (!/^[a-z0-9_-]{1,20}$/u.test(normalizedValue)) {
58
+ return 'Query post type must be lowercase, 1-20 chars, and only a-z, 0-9, "_" or "-".';
59
+ }
60
+ return true;
61
+ }
62
+ function normalizeQueryPostType(value) {
63
+ if (typeof value !== 'string') {
64
+ return undefined;
65
+ }
66
+ const normalizedValue = value.trim().toLowerCase();
67
+ if (validateQueryPostType(normalizedValue) !== true) {
68
+ throw new Error('Query post type must be lowercase, 1-20 chars, and only a-z, 0-9, "_" or "-".');
69
+ }
70
+ return normalizedValue;
71
+ }
45
72
  function normalizeTemplateSelection(templateId) {
46
73
  return templateId === WORKSPACE_TEMPLATE_ALIAS
47
74
  ? OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE
48
75
  : templateId;
49
76
  }
77
+ function looksLikeExplicitExternalTemplateLocator(templateId) {
78
+ return (templateId.startsWith('./') ||
79
+ templateId.startsWith('../') ||
80
+ templateId.startsWith('/') ||
81
+ templateId.startsWith('@') ||
82
+ templateId.startsWith('github:') ||
83
+ templateId.includes('/'));
84
+ }
85
+ function getEditDistance(left, right) {
86
+ const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
87
+ const current = new Array(right.length + 1);
88
+ for (let leftIndex = 0; leftIndex < left.length; leftIndex += 1) {
89
+ current[0] = leftIndex + 1;
90
+ for (let rightIndex = 0; rightIndex < right.length; rightIndex += 1) {
91
+ const substitutionCost = left[leftIndex] === right[rightIndex] ? 0 : 1;
92
+ current[rightIndex + 1] = Math.min(current[rightIndex] + 1, previous[rightIndex + 1] + 1, previous[rightIndex] + substitutionCost);
93
+ }
94
+ for (let index = 0; index < current.length; index += 1) {
95
+ previous[index] = current[index];
96
+ }
97
+ }
98
+ return previous[right.length];
99
+ }
100
+ function findMistypedBuiltInTemplateSuggestion(templateId) {
101
+ const normalizedTemplateId = templateId.trim().toLowerCase();
102
+ if (normalizedTemplateId.length === 0 ||
103
+ looksLikeExplicitExternalTemplateLocator(normalizedTemplateId)) {
104
+ return null;
105
+ }
106
+ let bestCandidate = null;
107
+ for (const candidateId of TEMPLATE_SUGGESTION_IDS) {
108
+ const distance = getEditDistance(normalizedTemplateId, candidateId);
109
+ if (bestCandidate === null ||
110
+ distance < bestCandidate.distance) {
111
+ bestCandidate = {
112
+ distance,
113
+ id: candidateId,
114
+ };
115
+ }
116
+ }
117
+ return bestCandidate && bestCandidate.distance <= 2
118
+ ? bestCandidate.id
119
+ : null;
120
+ }
121
+ function getMistypedBuiltInTemplateMessage(templateId) {
122
+ const suggestion = findMistypedBuiltInTemplateSuggestion(templateId);
123
+ if (!suggestion) {
124
+ return null;
125
+ }
126
+ const suggestionDescription = suggestion === WORKSPACE_TEMPLATE_ALIAS
127
+ ? 'official workspace scaffold'
128
+ : 'built-in scaffold';
129
+ return `Unknown template "${templateId}". Did you mean "${suggestion}"? Use \`--template ${suggestion}\` for the ${suggestionDescription}, or pass a local path, \`github:owner/repo/path[#ref]\`, or an npm package spec for an external template.`;
130
+ }
50
131
  /**
51
132
  * Resolve the scaffold template id from flags, defaults, and interactive selection.
52
133
  *
@@ -59,16 +140,23 @@ export async function resolveTemplateId({ templateId, yes = false, isInteractive
59
140
  if (isRemovedBuiltInTemplateId(templateId)) {
60
141
  throw new Error(getRemovedBuiltInTemplateMessage(templateId));
61
142
  }
143
+ if (normalizedTemplateId === OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE) {
144
+ return normalizedTemplateId;
145
+ }
62
146
  if (isBuiltInTemplateId(normalizedTemplateId)) {
63
147
  return getTemplateById(normalizedTemplateId).id;
64
148
  }
149
+ const mistypedBuiltInTemplateMessage = getMistypedBuiltInTemplateMessage(templateId);
150
+ if (mistypedBuiltInTemplateMessage) {
151
+ throw new Error(mistypedBuiltInTemplateMessage);
152
+ }
65
153
  return normalizedTemplateId;
66
154
  }
67
155
  if (yes) {
68
156
  return 'basic';
69
157
  }
70
158
  if (!isInteractive || !selectTemplate) {
71
- throw new Error(`Template is required in non-interactive mode. Use --template <${TEMPLATE_IDS.join('|')}|./path|github:owner/repo/path[#ref]|npm-package>.`);
159
+ throw new Error(`Template is required in non-interactive mode. Use ${TEMPLATE_SELECTION_HINT}.`);
72
160
  }
73
161
  return normalizeTemplateSelection(await selectTemplate());
74
162
  }
@@ -96,7 +184,7 @@ export async function resolvePackageManagerId({ packageManager, yes = false, isI
96
184
  * @param options Answer collection inputs including prompt callbacks and explicit overrides.
97
185
  * @returns The normalized scaffold answers used for rendering and file generation.
98
186
  */
99
- export async function collectScaffoldAnswers({ projectName, templateId, yes = false, dataStorageMode, namespace, persistencePolicy, phpPrefix, promptText, textDomain, }) {
187
+ export async function collectScaffoldAnswers({ projectName, templateId, yes = false, dataStorageMode, namespace, persistencePolicy, phpPrefix, promptText, queryPostType, textDomain, }) {
100
188
  const defaults = getDefaultAnswers(projectName, templateId);
101
189
  if (yes) {
102
190
  const identifiers = resolveScaffoldIdentifiers({
@@ -111,6 +199,7 @@ export async function collectScaffoldAnswers({ projectName, templateId, yes = fa
111
199
  namespace: identifiers.namespace,
112
200
  persistencePolicy: persistencePolicy ?? defaults.persistencePolicy,
113
201
  phpPrefix: identifiers.phpPrefix,
202
+ queryPostType: normalizeQueryPostType(queryPostType ?? defaults.queryPostType),
114
203
  textDomain: identifiers.textDomain,
115
204
  };
116
205
  }
@@ -130,9 +219,11 @@ export async function collectScaffoldAnswers({ projectName, templateId, yes = fa
130
219
  namespace: identifiers.namespace,
131
220
  persistencePolicy: persistencePolicy ?? defaults.persistencePolicy,
132
221
  phpPrefix: identifiers.phpPrefix,
222
+ queryPostType: templateId === 'query-loop'
223
+ ? normalizeQueryPostType(await promptText('Query post type', queryPostType ?? defaults.queryPostType ?? 'post', validateQueryPostType))
224
+ : normalizeQueryPostType(queryPostType ?? defaults.queryPostType),
133
225
  slug: identifiers.slug,
134
226
  textDomain: identifiers.textDomain,
135
227
  title: await promptText('Block title', toTitleCase(identifiers.slug)),
136
228
  };
137
229
  }
138
- import { execSync } from 'node:child_process';
@@ -2,7 +2,7 @@ import { type BuiltInBlockArtifact } from "./built-in-block-artifacts.js";
2
2
  import type { BuiltInCodeArtifact } from "./built-in-block-code-artifacts.js";
3
3
  import { type BuiltInTemplateId } from "./template-registry.js";
4
4
  import type { PackageManagerId } from "./package-managers.js";
5
- import type { ScaffoldTemplateVariables } from "./scaffold.js";
5
+ import type { ScaffoldProgressEvent, ScaffoldTemplateVariables } from "./scaffold.js";
6
6
  export { buildGitignore, buildReadme, mergeTextLines, } from "./scaffold-documents.js";
7
7
  export interface InstallDependenciesOptions {
8
8
  packageManager: PackageManagerId;
@@ -16,7 +16,7 @@ export declare function writeStarterManifestFiles(targetDir: string, templateId:
16
16
  */
17
17
  export declare function seedBuiltInPersistenceArtifacts(targetDir: string, templateId: BuiltInTemplateId, variables: ScaffoldTemplateVariables): Promise<void>;
18
18
  export declare function normalizePackageManagerFiles(targetDir: string, packageManagerId: PackageManagerId): Promise<void>;
19
- export declare function normalizePackageJson(targetDir: string, packageManagerId: PackageManagerId): Promise<void>;
19
+ export declare function removeQueryLoopPlaceholderFiles(projectDir: string, templateId: string): Promise<void>;
20
20
  export declare function removeUnexpectedLockfiles(targetDir: string, packageManagerId: PackageManagerId): Promise<void>;
21
21
  /**
22
22
  * Recursively normalizes generated text files for the selected package manager
@@ -33,7 +33,7 @@ export declare function applyWorkspaceMigrationCapability(projectDir: string, pa
33
33
  * Applies a built-in scaffold into the target directory, including generated
34
34
  * code artifacts, starter manifests, preset files, and placeholder rewrites.
35
35
  */
36
- export declare function applyBuiltInScaffoldProjectFiles({ projectDir, templateDir, templateId, variables, artifacts, codeArtifacts, readmeContent, gitignoreContent, allowExistingDir, packageManager, withMigrationUi, withTestPreset, withWpEnv, noInstall, installDependencies, repositoryReference, }: {
36
+ export declare function applyBuiltInScaffoldProjectFiles({ projectDir, templateDir, templateId, variables, artifacts, codeArtifacts, readmeContent, gitignoreContent, allowExistingDir, packageManager, withMigrationUi, withTestPreset, withWpEnv, noInstall, installDependencies, repositoryReference, onProgress, }: {
37
37
  projectDir: string;
38
38
  templateDir: string;
39
39
  templateId: BuiltInTemplateId;
@@ -50,4 +50,5 @@ export declare function applyBuiltInScaffoldProjectFiles({ projectDir, templateD
50
50
  noInstall?: boolean;
51
51
  installDependencies?: ((options: InstallDependenciesOptions) => Promise<void>) | undefined;
52
52
  repositoryReference?: string;
53
+ onProgress?: ((event: ScaffoldProgressEvent) => void | Promise<void>) | undefined;
53
54
  }): Promise<void>;