@wp-typia/project-tools 0.16.6 → 0.16.8

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 (36) hide show
  1. package/README.md +25 -8
  2. package/dist/runtime/block-generator-service.d.ts +5 -1
  3. package/dist/runtime/block-generator-service.js +132 -15
  4. package/dist/runtime/block-generator-tool-contract.d.ts +93 -0
  5. package/dist/runtime/block-generator-tool-contract.js +157 -0
  6. package/dist/runtime/built-in-block-code-artifacts.js +5 -5
  7. package/dist/runtime/cli-add-block.d.ts +36 -0
  8. package/dist/runtime/cli-add-block.js +518 -0
  9. package/dist/runtime/cli-add-shared.d.ts +93 -0
  10. package/dist/runtime/cli-add-shared.js +201 -0
  11. package/dist/runtime/cli-add-workspace.d.ts +81 -0
  12. package/dist/runtime/cli-add-workspace.js +582 -0
  13. package/dist/runtime/cli-add.d.ts +11 -131
  14. package/dist/runtime/cli-add.js +10 -1250
  15. package/dist/runtime/cli-prompt.d.ts +25 -0
  16. package/dist/runtime/cli-prompt.js +32 -20
  17. package/dist/runtime/cli-scaffold.js +1 -2
  18. package/dist/runtime/index.d.ts +2 -0
  19. package/dist/runtime/index.js +1 -0
  20. package/dist/runtime/migration-types.d.ts +9 -53
  21. package/dist/runtime/scaffold.js +1 -2
  22. package/dist/runtime/template-builtins.d.ts +11 -1
  23. package/dist/runtime/template-builtins.js +118 -25
  24. package/dist/runtime/template-layers.d.ts +31 -0
  25. package/dist/runtime/template-layers.js +171 -0
  26. package/dist/runtime/template-render.d.ts +15 -0
  27. package/dist/runtime/template-render.js +36 -0
  28. package/dist/runtime/template-source.d.ts +17 -0
  29. package/dist/runtime/template-source.js +14 -3
  30. package/package.json +6 -8
  31. package/templates/_shared/base/package.json.mustache +1 -0
  32. package/templates/_shared/compound/core/package.json.mustache +1 -1
  33. package/templates/_shared/compound/core/scripts/add-compound-child.ts.mustache +5 -5
  34. package/templates/_shared/compound/persistence/package.json.mustache +1 -1
  35. package/templates/_shared/persistence/core/package.json.mustache +1 -0
  36. package/templates/interactivity/package.json.mustache +2 -1
package/README.md CHANGED
@@ -6,7 +6,8 @@ Package roles:
6
6
 
7
7
  - `wp-typia` owns the CLI, help, TUI, completions, skills, MCP, and bin entry.
8
8
  - `@wp-typia/project-tools` owns scaffold, add-block, migrate, template, doctor, and schema project helpers.
9
- It also owns the typed generator boundary via `BlockSpec` and `BlockGeneratorService`,
9
+ It also owns the typed generator boundary via `BlockSpec`, `BlockGeneratorService`,
10
+ and `inspectBlockGeneration(...)`,
10
11
  plus the emitter-owned built-in structural/code path where built-in
11
12
  templates no longer ship structural, TS/TSX, style, or block-local `render.php`
12
13
  Mustache files.
@@ -23,21 +24,23 @@ Implementation note:
23
24
  - `@wp-typia/project-tools/schema-core` remains the preferred project-tooling
24
25
  import path.
25
26
  - The shared implementation now lives in `@wp-typia/block-runtime/schema-core`.
27
+ - Shared manifest/migration contract types now live in
28
+ `@wp-typia/block-runtime/migration-types`.
26
29
 
27
30
  Example:
28
31
 
29
32
  ```ts
30
33
  import {
31
- BlockGeneratorService,
32
- getTemplateById,
33
- parseMigrationArgs,
34
- projectJsonSchemaDocument,
35
- resolvePackageManagerId,
36
- } from "@wp-typia/project-tools";
34
+ BlockGeneratorService,
35
+ getTemplateById,
36
+ parseMigrationArgs,
37
+ projectJsonSchemaDocument,
38
+ resolvePackageManagerId,
39
+ } from '@wp-typia/project-tools';
37
40
  ```
38
41
 
39
42
  ```ts
40
- import { normalizeEndpointAuthDefinition } from "@wp-typia/project-tools/schema-core";
43
+ import { normalizeEndpointAuthDefinition } from '@wp-typia/project-tools/schema-core';
41
44
  ```
42
45
 
43
46
  `BlockGeneratorService` is the additive typed orchestration boundary for built-in
@@ -48,4 +51,18 @@ files and starter `typia.manifest.json` now come from the emitter path, while
48
51
  project bootstrap/package-manager files, sync scripts, shared REST helpers, and
49
52
  the remaining non-block assets still come from Mustache-backed template copy.
50
53
 
54
+ The higher-level generator architecture record, including the current phase map
55
+ and the non-mutating `plan -> validate -> render -> apply` tool-facing usage
56
+ model, lives in
57
+ [`docs/block-generator-architecture.md`](../../docs/block-generator-architecture.md).
58
+ The public non-mutating controller/tool contract now lives in
59
+ [`docs/block-generator-tool-contract.md`](../../docs/block-generator-tool-contract.md).
60
+
61
+ Reusable external layer packages on top of the built-in shared scaffold model
62
+ are now available programmatically through `scaffoldProject(...)`,
63
+ `BlockGeneratorService`, and `inspectBlockGeneration(...)` via
64
+ `externalLayerSource` and optional `externalLayerId`. The RFC/CLI UX record
65
+ still lives in
66
+ [`docs/external-template-layer-composition.md`](../../docs/external-template-layer-composition.md).
67
+
51
68
  If you need metadata sync, editor helpers, validation helpers, or other generated-project runtime utilities, import them directly from `@wp-typia/block-runtime/*`.
@@ -41,6 +41,8 @@ export interface BlockSpec {
41
41
  export interface BlockGenerationTarget {
42
42
  allowExistingDir: boolean;
43
43
  cwd: string;
44
+ externalLayerId?: string;
45
+ externalLayerSource?: string;
44
46
  noInstall: boolean;
45
47
  packageManager: PackageManagerId;
46
48
  projectDir: string;
@@ -51,6 +53,8 @@ export interface PlanBlockInput {
51
53
  answers: ScaffoldAnswers;
52
54
  cwd?: string;
53
55
  dataStorageMode?: DataStorageMode;
56
+ externalLayerId?: string;
57
+ externalLayerSource?: string;
54
58
  noInstall?: boolean;
55
59
  packageManager: PackageManagerId;
56
60
  persistencePolicy?: PersistencePolicy;
@@ -95,7 +99,7 @@ export interface ApplyBlockInput {
95
99
  export declare function createBuiltInBlockSpec({ answers, dataStorageMode, persistencePolicy, templateId, withMigrationUi, withTestPreset, withWpEnv, }: Omit<PlanBlockInput, "allowExistingDir" | "cwd" | "noInstall" | "packageManager" | "projectDir" | "variant">): BlockSpec;
96
100
  export declare function buildTemplateVariablesFromBlockSpec(spec: BlockSpec): ScaffoldTemplateVariables;
97
101
  export declare class BlockGeneratorService {
98
- plan({ allowExistingDir, answers, cwd, dataStorageMode, noInstall, packageManager, persistencePolicy, projectDir, templateId, variant, withMigrationUi, withTestPreset, withWpEnv, }: PlanBlockInput): Promise<PlanBlockResult>;
102
+ plan({ allowExistingDir, answers, cwd, dataStorageMode, externalLayerId, externalLayerSource, noInstall, packageManager, persistencePolicy, projectDir, templateId, variant, withMigrationUi, withTestPreset, withWpEnv, }: PlanBlockInput): Promise<PlanBlockResult>;
99
103
  validate({ plan }: ValidateBlockInput): Promise<ValidateBlockResult>;
100
104
  render({ validated }: RenderBlockInput): Promise<RenderBlockResult>;
101
105
  apply({ rendered, installDependencies, }: ApplyBlockInput): Promise<ScaffoldProjectResult>;
@@ -7,10 +7,62 @@ import { applyBuiltInScaffoldProjectFiles, buildGitignore, buildReadme, } from "
7
7
  import { buildBlockCssClassName, buildFrontendCssClassName, resolveScaffoldIdentifiers, } from "./scaffold-identifiers.js";
8
8
  import { buildBuiltInBlockArtifacts, } from "./built-in-block-artifacts.js";
9
9
  import { buildBuiltInCodeArtifacts, } from "./built-in-block-code-artifacts.js";
10
+ import { getStarterManifestFiles } from "./starter-manifests.js";
11
+ import { resolveTemplateSeed, parseTemplateLocator } from "./template-source.js";
12
+ import { assertExternalTemplateLayersDoNotWriteProtectedOutputs, resolveExternalTemplateLayers, } from "./template-layers.js";
13
+ import { getBuiltInTemplateOverlayDir, getBuiltInTemplateSharedLayerDirs, resolveBuiltInTemplateSourceFromLayerDirs, } from "./template-builtins.js";
10
14
  const renderedArtifactCache = new WeakMap();
11
15
  function createVariablesFingerprint(variables) {
12
16
  return JSON.stringify(variables);
13
17
  }
18
+ function buildProtectedTemplateOutputPaths({ codeArtifacts, spec, variables, artifacts, }) {
19
+ const protectedOutputs = new Set([
20
+ ".gitignore",
21
+ "package.json",
22
+ "scripts/add-compound-child.ts",
23
+ "scripts/block-config.ts",
24
+ "scripts/sync-project.ts",
25
+ "scripts/sync-rest-contracts.ts",
26
+ "scripts/sync-types-to-block-json.ts",
27
+ "tsconfig.json",
28
+ "webpack.config.js",
29
+ `${variables.slugKebabCase}.php`,
30
+ ]);
31
+ for (const artifact of codeArtifacts) {
32
+ protectedOutputs.add(artifact.relativePath);
33
+ }
34
+ for (const artifact of artifacts) {
35
+ protectedOutputs.add(`${artifact.relativeDir}/block.json`);
36
+ protectedOutputs.add(`${artifact.relativeDir}/types.ts`);
37
+ }
38
+ for (const manifest of getStarterManifestFiles(spec.template.family, variables)) {
39
+ protectedOutputs.add(manifest.relativePath);
40
+ }
41
+ return protectedOutputs;
42
+ }
43
+ function buildCombinedTemplateLayerDirs({ baseLayerDirs, externalEntries, templateId, }) {
44
+ const orderedLayerDirs = [];
45
+ const seenLayerDirs = new Set();
46
+ for (const layerDir of baseLayerDirs) {
47
+ if (seenLayerDirs.has(layerDir)) {
48
+ continue;
49
+ }
50
+ orderedLayerDirs.push(layerDir);
51
+ seenLayerDirs.add(layerDir);
52
+ }
53
+ for (const entry of externalEntries) {
54
+ if (seenLayerDirs.has(entry.dir)) {
55
+ continue;
56
+ }
57
+ orderedLayerDirs.push(entry.dir);
58
+ seenLayerDirs.add(entry.dir);
59
+ }
60
+ const overlayDir = getBuiltInTemplateOverlayDir(templateId);
61
+ if (!seenLayerDirs.has(overlayDir)) {
62
+ orderedLayerDirs.push(overlayDir);
63
+ }
64
+ return orderedLayerDirs;
65
+ }
14
66
  function getBuiltInPersistenceSpec({ templateId, dataStorageMode, persistencePolicy, }) {
15
67
  if (templateId === "persistence") {
16
68
  return {
@@ -146,7 +198,7 @@ export function buildTemplateVariablesFromBlockSpec(spec) {
146
198
  };
147
199
  }
148
200
  export class BlockGeneratorService {
149
- async plan({ allowExistingDir = false, answers, cwd = process.cwd(), dataStorageMode, noInstall = false, packageManager, persistencePolicy, projectDir, templateId, variant, withMigrationUi = false, withTestPreset = false, withWpEnv = false, }) {
201
+ async plan({ allowExistingDir = false, answers, cwd = process.cwd(), dataStorageMode, externalLayerId, externalLayerSource, noInstall = false, packageManager, persistencePolicy, projectDir, templateId, variant, withMigrationUi = false, withTestPreset = false, withWpEnv = false, }) {
150
202
  return {
151
203
  spec: createBuiltInBlockSpec({
152
204
  answers,
@@ -160,6 +212,8 @@ export class BlockGeneratorService {
160
212
  target: {
161
213
  allowExistingDir,
162
214
  cwd,
215
+ externalLayerId,
216
+ externalLayerSource,
163
217
  noInstall,
164
218
  packageManager,
165
219
  projectDir,
@@ -168,21 +222,90 @@ export class BlockGeneratorService {
168
222
  };
169
223
  }
170
224
  async validate({ plan }) {
225
+ if (plan.target.externalLayerId && !plan.target.externalLayerSource) {
226
+ throw new Error("externalLayerId requires externalLayerSource when composing built-in template layers.");
227
+ }
171
228
  if (plan.target.variant) {
172
229
  throw new Error(`--variant is only supported for official external template configs. Received variant "${plan.target.variant}" for built-in template "${plan.spec.template.family}".`);
173
230
  }
174
231
  return plan;
175
232
  }
176
233
  async render({ validated }) {
177
- const templateSource = await resolveBuiltInTemplateSource(validated.spec.template.family, {
178
- persistenceEnabled: validated.spec.persistence.enabled,
234
+ const variables = buildTemplateVariablesFromBlockSpec(validated.spec);
235
+ const persistenceEnabled = validated.spec.persistence.enabled;
236
+ const artifacts = buildBuiltInBlockArtifacts({
237
+ templateId: validated.spec.template.family,
238
+ variables,
239
+ });
240
+ const codeArtifacts = buildBuiltInCodeArtifacts({
241
+ templateId: validated.spec.template.family,
242
+ variables,
243
+ });
244
+ const templateVariantOptions = {
245
+ persistenceEnabled,
179
246
  persistencePolicy: validated.spec.persistence.enabled &&
180
247
  validated.spec.persistence.persistencePolicy === "public"
181
248
  ? "public"
182
249
  : "authenticated",
183
- });
184
- const variables = buildTemplateVariablesFromBlockSpec(validated.spec);
185
- const persistenceEnabled = validated.spec.persistence.enabled;
250
+ };
251
+ let templateSource = await resolveBuiltInTemplateSource(validated.spec.template.family, templateVariantOptions);
252
+ const warnings = [...(templateSource.warnings ?? [])];
253
+ if (validated.target.externalLayerSource) {
254
+ let layerSeed;
255
+ try {
256
+ layerSeed = await resolveTemplateSeed(parseTemplateLocator(validated.target.externalLayerSource), validated.target.cwd);
257
+ const resolvedLayers = await resolveExternalTemplateLayers({
258
+ externalLayerId: validated.target.externalLayerId,
259
+ sourceRoot: layerSeed.rootDir,
260
+ });
261
+ const baseLayerDirs = getBuiltInTemplateSharedLayerDirs(validated.spec.template.family, templateVariantOptions);
262
+ await assertExternalTemplateLayersDoNotWriteProtectedOutputs({
263
+ externalEntries: resolvedLayers.entries,
264
+ protectedOutputPaths: buildProtectedTemplateOutputPaths({
265
+ artifacts,
266
+ codeArtifacts,
267
+ spec: validated.spec,
268
+ variables,
269
+ }),
270
+ view: variables,
271
+ });
272
+ await templateSource.cleanup?.();
273
+ templateSource = await resolveBuiltInTemplateSourceFromLayerDirs(validated.spec.template.family, buildCombinedTemplateLayerDirs({
274
+ baseLayerDirs,
275
+ externalEntries: resolvedLayers.entries,
276
+ templateId: validated.spec.template.family,
277
+ }));
278
+ const layerSourceCleanup = layerSeed.cleanup;
279
+ const templateCleanup = templateSource.cleanup;
280
+ templateSource.cleanup = async () => {
281
+ const cleanupErrors = [];
282
+ try {
283
+ await templateCleanup?.();
284
+ }
285
+ catch (error) {
286
+ cleanupErrors.push(error instanceof Error ? error : new Error(String(error)));
287
+ }
288
+ try {
289
+ await layerSourceCleanup?.();
290
+ }
291
+ catch (error) {
292
+ cleanupErrors.push(error instanceof Error ? error : new Error(String(error)));
293
+ }
294
+ if (cleanupErrors.length > 0) {
295
+ throw new Error([
296
+ "Failed to cleanup composed template sources.",
297
+ ...cleanupErrors.map((error) => `- ${error.message}`),
298
+ ].join("\n"));
299
+ }
300
+ };
301
+ warnings.push(`Applied external layer "${resolvedLayers.selectedLayerId}" from "${validated.target.externalLayerSource}".`);
302
+ }
303
+ catch (error) {
304
+ await templateSource.cleanup?.();
305
+ await layerSeed?.cleanup?.();
306
+ throw error;
307
+ }
308
+ }
186
309
  const rendered = {
187
310
  ...validated,
188
311
  cleanup: templateSource.cleanup,
@@ -202,17 +325,11 @@ export class BlockGeneratorService {
202
325
  selectedVariant: null,
203
326
  templateDir: templateSource.templateDir,
204
327
  variables,
205
- warnings: templateSource.warnings ?? [],
328
+ warnings,
206
329
  };
207
330
  renderedArtifactCache.set(rendered, {
208
- artifacts: buildBuiltInBlockArtifacts({
209
- templateId: validated.spec.template.family,
210
- variables,
211
- }),
212
- codeArtifacts: buildBuiltInCodeArtifacts({
213
- templateId: validated.spec.template.family,
214
- variables,
215
- }),
331
+ artifacts,
332
+ codeArtifacts,
216
333
  variablesFingerprint: createVariablesFingerprint(variables),
217
334
  });
218
335
  return rendered;
@@ -0,0 +1,93 @@
1
+ import { BlockGeneratorService, type PlanBlockInput, type PlanBlockResult, type RenderBlockResult, type ValidateBlockResult } from "./block-generator-service.js";
2
+ import type { ManifestDocument } from "./migration-types.js";
3
+ import type { BuiltInTemplateId } from "./template-registry.js";
4
+ /**
5
+ * Semantic version marker for the public block generation tool contract.
6
+ *
7
+ * Increment this number whenever the serialized inspection payload changes in a
8
+ * breaking way.
9
+ */
10
+ export declare const BLOCK_GENERATION_TOOL_CONTRACT_VERSION: 1;
11
+ /**
12
+ * Staged execution points for the non-mutating inspection workflow.
13
+ *
14
+ * - `"plan"` returns the normalized `BlockSpec` and target metadata.
15
+ * - `"validate"` returns the validated generation stage without rendering.
16
+ * - `"render"` returns the full non-mutating preview, including copied and
17
+ * emitted file snapshots.
18
+ */
19
+ export type BlockGenerationToolStage = "plan" | "validate" | "render";
20
+ /**
21
+ * Input for the staged, non-mutating block generation inspection entrypoint.
22
+ *
23
+ * Extends `PlanBlockInput` with an optional `stopAfter` selector so callers can
24
+ * stop at `plan`, `validate`, or continue through the full `render` preview.
25
+ */
26
+ export interface InspectBlockGenerationInput extends PlanBlockInput {
27
+ stopAfter?: BlockGenerationToolStage;
28
+ }
29
+ export interface BlockGenerationTemplateCopyPreview {
30
+ owner: "template-copy";
31
+ relativePath: string;
32
+ }
33
+ export interface BlockGenerationEmittedFilePreview {
34
+ kind: "generated-source" | "starter-manifest" | "structural";
35
+ owner: "emitter";
36
+ relativePath: string;
37
+ source: string;
38
+ }
39
+ export interface BlockGenerationStarterManifestPreview {
40
+ document: ManifestDocument;
41
+ owner: "emitter";
42
+ relativePath: string;
43
+ source: string;
44
+ }
45
+ export interface BlockGenerationRenderPreview {
46
+ copiedTemplateFiles: BlockGenerationTemplateCopyPreview[];
47
+ emittedFiles: BlockGenerationEmittedFilePreview[];
48
+ postRender: RenderBlockResult["postRender"] & {
49
+ installsDependencies: boolean;
50
+ };
51
+ selectedVariant: null;
52
+ starterManifestFiles: BlockGenerationStarterManifestPreview[];
53
+ template: {
54
+ description: string;
55
+ family: BuiltInTemplateId;
56
+ features: string[];
57
+ format: "wp-typia";
58
+ };
59
+ warnings: string[];
60
+ readmeContent: string;
61
+ gitignoreContent: string;
62
+ }
63
+ interface BlockGenerationInspectionBase {
64
+ contractVersion: typeof BLOCK_GENERATION_TOOL_CONTRACT_VERSION;
65
+ mutatesWorkspace: false;
66
+ stage: BlockGenerationToolStage;
67
+ }
68
+ export interface InspectBlockGenerationPlanResult extends BlockGenerationInspectionBase {
69
+ plan: PlanBlockResult;
70
+ stage: "plan";
71
+ }
72
+ export interface InspectBlockGenerationValidateResult extends BlockGenerationInspectionBase {
73
+ plan: PlanBlockResult;
74
+ stage: "validate";
75
+ validated: ValidateBlockResult;
76
+ }
77
+ export interface InspectBlockGenerationRenderResult extends BlockGenerationInspectionBase {
78
+ plan: PlanBlockResult;
79
+ rendered: BlockGenerationRenderPreview;
80
+ stage: "render";
81
+ validated: ValidateBlockResult;
82
+ }
83
+ export type InspectBlockGenerationResult = InspectBlockGenerationPlanResult | InspectBlockGenerationValidateResult | InspectBlockGenerationRenderResult;
84
+ export declare function inspectBlockGeneration(input: InspectBlockGenerationInput & {
85
+ stopAfter: "plan";
86
+ }, service?: BlockGeneratorService): Promise<InspectBlockGenerationPlanResult>;
87
+ export declare function inspectBlockGeneration(input: InspectBlockGenerationInput & {
88
+ stopAfter: "validate";
89
+ }, service?: BlockGeneratorService): Promise<InspectBlockGenerationValidateResult>;
90
+ export declare function inspectBlockGeneration(input: InspectBlockGenerationInput & {
91
+ stopAfter?: "render" | undefined;
92
+ }, service?: BlockGeneratorService): Promise<InspectBlockGenerationRenderResult>;
93
+ export {};
@@ -0,0 +1,157 @@
1
+ import { buildBuiltInBlockArtifacts, stringifyBuiltInBlockJsonDocument, } from "./built-in-block-artifacts.js";
2
+ import { buildBuiltInCodeArtifacts, } from "./built-in-block-code-artifacts.js";
3
+ import { BlockGeneratorService, } from "./block-generator-service.js";
4
+ import { getStarterManifestFiles, stringifyStarterManifest, } from "./starter-manifests.js";
5
+ import { listInterpolatedDirectoryOutputs } from "./template-render.js";
6
+ /**
7
+ * Semantic version marker for the public block generation tool contract.
8
+ *
9
+ * Increment this number whenever the serialized inspection payload changes in a
10
+ * breaking way.
11
+ */
12
+ export const BLOCK_GENERATION_TOOL_CONTRACT_VERSION = 1;
13
+ function buildStarterManifestPreviews(templateId, variables, artifacts) {
14
+ const starterManifests = getStarterManifestFiles(templateId, variables);
15
+ const artifactManifests = new Map(artifacts.map((artifact) => [
16
+ `${artifact.relativeDir}/typia.manifest.json`,
17
+ artifact.manifestDocument,
18
+ ]));
19
+ return starterManifests
20
+ .map((entry) => {
21
+ const document = artifactManifests.get(entry.relativePath) ?? entry.document;
22
+ return {
23
+ document,
24
+ owner: "emitter",
25
+ relativePath: entry.relativePath,
26
+ source: stringifyStarterManifest(document),
27
+ };
28
+ })
29
+ .sort((left, right) => left.relativePath.localeCompare(right.relativePath));
30
+ }
31
+ function buildStructuralArtifactPreviews(artifacts) {
32
+ return artifacts
33
+ .flatMap((artifact) => [
34
+ {
35
+ kind: "structural",
36
+ owner: "emitter",
37
+ relativePath: `${artifact.relativeDir}/block.json`,
38
+ source: stringifyBuiltInBlockJsonDocument(artifact.blockJsonDocument),
39
+ },
40
+ {
41
+ kind: "structural",
42
+ owner: "emitter",
43
+ relativePath: `${artifact.relativeDir}/types.ts`,
44
+ source: artifact.typesSource,
45
+ },
46
+ ])
47
+ .sort((left, right) => left.relativePath.localeCompare(right.relativePath));
48
+ }
49
+ function buildCodeArtifactPreviews(codeArtifacts) {
50
+ return codeArtifacts
51
+ .map((artifact) => ({
52
+ kind: "generated-source",
53
+ owner: "emitter",
54
+ relativePath: artifact.relativePath,
55
+ source: artifact.source,
56
+ }))
57
+ .sort((left, right) => left.relativePath.localeCompare(right.relativePath));
58
+ }
59
+ async function buildRenderPreview(rendered) {
60
+ const artifacts = buildBuiltInBlockArtifacts({
61
+ templateId: rendered.spec.template.family,
62
+ variables: rendered.variables,
63
+ });
64
+ const codeArtifacts = buildBuiltInCodeArtifacts({
65
+ templateId: rendered.spec.template.family,
66
+ variables: rendered.variables,
67
+ });
68
+ const copiedTemplateFiles = await listInterpolatedDirectoryOutputs(rendered.templateDir, rendered.variables);
69
+ return {
70
+ copiedTemplateFiles: copiedTemplateFiles.map((relativePath) => ({
71
+ owner: "template-copy",
72
+ relativePath,
73
+ })),
74
+ emittedFiles: [
75
+ ...buildStructuralArtifactPreviews(artifacts),
76
+ ...buildCodeArtifactPreviews(codeArtifacts),
77
+ ].sort((left, right) => left.relativePath.localeCompare(right.relativePath)),
78
+ gitignoreContent: rendered.gitignoreContent,
79
+ postRender: {
80
+ ...rendered.postRender,
81
+ installsDependencies: !rendered.target.noInstall,
82
+ },
83
+ readmeContent: rendered.readmeContent,
84
+ selectedVariant: rendered.selectedVariant,
85
+ starterManifestFiles: buildStarterManifestPreviews(rendered.spec.template.family, rendered.variables, artifacts),
86
+ template: {
87
+ description: rendered.spec.template.description,
88
+ family: rendered.spec.template.family,
89
+ features: [...rendered.spec.template.features],
90
+ format: "wp-typia",
91
+ },
92
+ warnings: [...rendered.warnings],
93
+ };
94
+ }
95
+ /**
96
+ * Inspects built-in block generation through the staged generator boundary
97
+ * without mutating the destination workspace.
98
+ *
99
+ * Use `stopAfter` to halt after the `plan`, `validate`, or `render` stage.
100
+ * The render stage includes copied template file paths, emitter-owned source
101
+ * previews, starter manifest previews, and post-render intent metadata. Any
102
+ * temporary render state is cleaned up before the promise resolves.
103
+ *
104
+ * @example
105
+ * ```ts
106
+ * const inspection = await inspectBlockGeneration({
107
+ * answers,
108
+ * noInstall: true,
109
+ * packageManager: "bun",
110
+ * projectDir: "demo-block",
111
+ * templateId: "basic",
112
+ * stopAfter: "render",
113
+ * });
114
+ * ```
115
+ *
116
+ * @param input - Planning input plus an optional `stopAfter` stage selector.
117
+ * @param service - Optional generator service instance. Defaults to a new
118
+ * `BlockGeneratorService`.
119
+ * @returns The serialized inspection result for the reached stage.
120
+ * @throws Propagates planning, validation, or render failures from
121
+ * `BlockGeneratorService`.
122
+ */
123
+ export async function inspectBlockGeneration({ stopAfter = "render", ...planInput }, service = new BlockGeneratorService()) {
124
+ const plan = await service.plan(planInput);
125
+ if (stopAfter === "plan") {
126
+ return {
127
+ contractVersion: BLOCK_GENERATION_TOOL_CONTRACT_VERSION,
128
+ mutatesWorkspace: false,
129
+ plan,
130
+ stage: "plan",
131
+ };
132
+ }
133
+ const validated = await service.validate({ plan });
134
+ if (stopAfter === "validate") {
135
+ return {
136
+ contractVersion: BLOCK_GENERATION_TOOL_CONTRACT_VERSION,
137
+ mutatesWorkspace: false,
138
+ plan,
139
+ stage: "validate",
140
+ validated,
141
+ };
142
+ }
143
+ const rendered = await service.render({ validated });
144
+ try {
145
+ return {
146
+ contractVersion: BLOCK_GENERATION_TOOL_CONTRACT_VERSION,
147
+ mutatesWorkspace: false,
148
+ plan,
149
+ rendered: await buildRenderPreview(rendered),
150
+ stage: "render",
151
+ validated,
152
+ };
153
+ }
154
+ finally {
155
+ await rendered.cleanup?.();
156
+ }
157
+ }