@wp-typia/project-tools 0.16.7 → 0.16.9

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 (54) hide show
  1. package/README.md +26 -1
  2. package/dist/runtime/block-generator-service.d.ts +9 -1
  3. package/dist/runtime/block-generator-service.js +137 -16
  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-artifacts.js +171 -423
  7. package/dist/runtime/built-in-block-code-artifacts.js +96 -46
  8. package/dist/runtime/built-in-block-code-templates.d.ts +36 -0
  9. package/dist/runtime/built-in-block-code-templates.js +2233 -0
  10. package/dist/runtime/cli-add-block.d.ts +2 -1
  11. package/dist/runtime/cli-add-block.js +163 -25
  12. package/dist/runtime/cli-add-shared.d.ts +7 -0
  13. package/dist/runtime/cli-add-shared.js +4 -6
  14. package/dist/runtime/cli-add-workspace.js +56 -17
  15. package/dist/runtime/cli-core.d.ts +4 -0
  16. package/dist/runtime/cli-core.js +3 -0
  17. package/dist/runtime/cli-diagnostics.d.ts +58 -0
  18. package/dist/runtime/cli-diagnostics.js +101 -0
  19. package/dist/runtime/cli-doctor.d.ts +2 -1
  20. package/dist/runtime/cli-doctor.js +16 -5
  21. package/dist/runtime/cli-help.js +4 -4
  22. package/dist/runtime/cli-scaffold.d.ts +5 -1
  23. package/dist/runtime/cli-scaffold.js +138 -111
  24. package/dist/runtime/external-layer-selection.d.ts +14 -0
  25. package/dist/runtime/external-layer-selection.js +35 -0
  26. package/dist/runtime/index.d.ts +4 -2
  27. package/dist/runtime/index.js +2 -1
  28. package/dist/runtime/migration-render.d.ts +23 -1
  29. package/dist/runtime/migration-render.js +58 -10
  30. package/dist/runtime/migration-ui-capability.js +17 -8
  31. package/dist/runtime/migration-utils.d.ts +7 -6
  32. package/dist/runtime/migration-utils.js +76 -73
  33. package/dist/runtime/migrations.js +2 -2
  34. package/dist/runtime/object-utils.d.ts +8 -1
  35. package/dist/runtime/object-utils.js +21 -1
  36. package/dist/runtime/scaffold-apply-utils.d.ts +14 -2
  37. package/dist/runtime/scaffold-apply-utils.js +19 -6
  38. package/dist/runtime/scaffold-repository-reference.d.ts +22 -0
  39. package/dist/runtime/scaffold-repository-reference.js +119 -0
  40. package/dist/runtime/scaffold.d.ts +5 -1
  41. package/dist/runtime/scaffold.js +15 -37
  42. package/dist/runtime/template-builtins.d.ts +11 -1
  43. package/dist/runtime/template-builtins.js +118 -25
  44. package/dist/runtime/template-layers.d.ts +37 -0
  45. package/dist/runtime/template-layers.js +184 -0
  46. package/dist/runtime/template-render.d.ts +28 -2
  47. package/dist/runtime/template-render.js +122 -55
  48. package/dist/runtime/template-source.d.ts +23 -5
  49. package/dist/runtime/template-source.js +296 -217
  50. package/package.json +8 -3
  51. package/templates/_shared/base/src/validator-toolkit.ts.mustache +2 -2
  52. package/templates/_shared/compound/core/scripts/add-compound-child.ts.mustache +58 -12
  53. package/templates/_shared/migration-ui/common/src/migrations/helpers.ts +19 -47
  54. package/templates/_shared/migration-ui/common/src/migrations/index.ts +40 -11
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.
@@ -50,4 +51,28 @@ files and starter `typia.manifest.json` now come from the emitter path, while
50
51
  project bootstrap/package-manager files, sync scripts, shared REST helpers, and
51
52
  the remaining non-block assets still come from Mustache-backed template copy.
52
53
 
54
+ Generated projects now consume JSON artifacts through typed wrapper modules
55
+ instead of local casts:
56
+
57
+ - `block-metadata.ts` for `block.json`
58
+ - `manifest-document.ts` for editor and migration consumers of
59
+ `typia.manifest.json`
60
+ - `manifest-defaults-document.ts` for validator/defaults consumers of
61
+ `typia.manifest.json`
62
+
63
+ The higher-level generator architecture record, including the current phase map
64
+ and the non-mutating `plan -> validate -> render -> apply` tool-facing usage
65
+ model, lives in
66
+ [`docs/block-generator-architecture.md`](../../docs/block-generator-architecture.md).
67
+ The public non-mutating controller/tool contract now lives in
68
+ [`docs/block-generator-tool-contract.md`](../../docs/block-generator-tool-contract.md).
69
+
70
+ Reusable external layer packages on top of the built-in shared scaffold model
71
+ are now available through the canonical built-in CLI flags
72
+ `wp-typia create --external-layer-source ... [--external-layer-id ...]`,
73
+ `wp-typia add block --external-layer-source ... [--external-layer-id ...]`,
74
+ and programmatically through `scaffoldProject(...)`, `BlockGeneratorService`,
75
+ and `inspectBlockGeneration(...)`. The layer contract record lives in
76
+ [`docs/external-template-layer-composition.md`](../../docs/external-template-layer-composition.md).
77
+
53
78
  If you need metadata sync, editor helpers, validation helpers, or other generated-project runtime utilities, import them directly from `@wp-typia/block-runtime/*`.
@@ -41,9 +41,13 @@ export interface BlockSpec {
41
41
  export interface BlockGenerationTarget {
42
42
  allowExistingDir: boolean;
43
43
  cwd: string;
44
+ externalLayerId?: string;
45
+ externalLayerSource?: string;
46
+ externalLayerSourceLabel?: string;
44
47
  noInstall: boolean;
45
48
  packageManager: PackageManagerId;
46
49
  projectDir: string;
50
+ repositoryReference?: string;
47
51
  variant?: string;
48
52
  }
49
53
  export interface PlanBlockInput {
@@ -51,10 +55,14 @@ export interface PlanBlockInput {
51
55
  answers: ScaffoldAnswers;
52
56
  cwd?: string;
53
57
  dataStorageMode?: DataStorageMode;
58
+ externalLayerId?: string;
59
+ externalLayerSource?: string;
60
+ externalLayerSourceLabel?: string;
54
61
  noInstall?: boolean;
55
62
  packageManager: PackageManagerId;
56
63
  persistencePolicy?: PersistencePolicy;
57
64
  projectDir: string;
65
+ repositoryReference?: string;
58
66
  templateId: BuiltInTemplateId;
59
67
  variant?: string;
60
68
  withMigrationUi?: boolean;
@@ -95,7 +103,7 @@ export interface ApplyBlockInput {
95
103
  export declare function createBuiltInBlockSpec({ answers, dataStorageMode, persistencePolicy, templateId, withMigrationUi, withTestPreset, withWpEnv, }: Omit<PlanBlockInput, "allowExistingDir" | "cwd" | "noInstall" | "packageManager" | "projectDir" | "variant">): BlockSpec;
96
104
  export declare function buildTemplateVariablesFromBlockSpec(spec: BlockSpec): ScaffoldTemplateVariables;
97
105
  export declare class BlockGeneratorService {
98
- plan({ allowExistingDir, answers, cwd, dataStorageMode, noInstall, packageManager, persistencePolicy, projectDir, templateId, variant, withMigrationUi, withTestPreset, withWpEnv, }: PlanBlockInput): Promise<PlanBlockResult>;
106
+ plan({ allowExistingDir, answers, cwd, dataStorageMode, externalLayerId, externalLayerSource, externalLayerSourceLabel, noInstall, packageManager, persistencePolicy, projectDir, repositoryReference, templateId, variant, withMigrationUi, withTestPreset, withWpEnv, }: PlanBlockInput): Promise<PlanBlockResult>;
99
107
  validate({ plan }: ValidateBlockInput): Promise<ValidateBlockResult>;
100
108
  render({ validated }: RenderBlockInput): Promise<RenderBlockResult>;
101
109
  apply({ rendered, installDependencies, }: ApplyBlockInput): Promise<ScaffoldProjectResult>;
@@ -7,9 +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 { stableJsonStringify } from "./object-utils.js";
11
+ import { getStarterManifestFiles } from "./starter-manifests.js";
12
+ import { resolveTemplateSeed, parseTemplateLocator } from "./template-source.js";
13
+ import { assertExternalTemplateLayersDoNotWriteProtectedOutputs, resolveExternalTemplateLayers, } from "./template-layers.js";
14
+ import { getBuiltInTemplateOverlayDir, getBuiltInTemplateSharedLayerDirs, resolveBuiltInTemplateSourceFromLayerDirs, } from "./template-builtins.js";
10
15
  const renderedArtifactCache = new WeakMap();
11
16
  function createVariablesFingerprint(variables) {
12
- return JSON.stringify(variables);
17
+ return stableJsonStringify(variables);
18
+ }
19
+ function buildProtectedTemplateOutputPaths({ codeArtifacts, spec, variables, artifacts, }) {
20
+ const protectedOutputs = new Set([
21
+ ".gitignore",
22
+ "package.json",
23
+ "scripts/add-compound-child.ts",
24
+ "scripts/block-config.ts",
25
+ "scripts/sync-project.ts",
26
+ "scripts/sync-rest-contracts.ts",
27
+ "scripts/sync-types-to-block-json.ts",
28
+ "tsconfig.json",
29
+ "webpack.config.js",
30
+ `${variables.slugKebabCase}.php`,
31
+ ]);
32
+ for (const artifact of codeArtifacts) {
33
+ protectedOutputs.add(artifact.relativePath);
34
+ }
35
+ for (const artifact of artifacts) {
36
+ protectedOutputs.add(`${artifact.relativeDir}/block.json`);
37
+ protectedOutputs.add(`${artifact.relativeDir}/types.ts`);
38
+ }
39
+ for (const manifest of getStarterManifestFiles(spec.template.family, variables)) {
40
+ protectedOutputs.add(manifest.relativePath);
41
+ }
42
+ return protectedOutputs;
43
+ }
44
+ function buildCombinedTemplateLayerDirs({ baseLayerDirs, externalEntries, templateId, }) {
45
+ const orderedLayerDirs = [];
46
+ const seenLayerDirs = new Set();
47
+ for (const layerDir of baseLayerDirs) {
48
+ if (seenLayerDirs.has(layerDir)) {
49
+ continue;
50
+ }
51
+ orderedLayerDirs.push(layerDir);
52
+ seenLayerDirs.add(layerDir);
53
+ }
54
+ for (const entry of externalEntries) {
55
+ if (seenLayerDirs.has(entry.dir)) {
56
+ continue;
57
+ }
58
+ orderedLayerDirs.push(entry.dir);
59
+ seenLayerDirs.add(entry.dir);
60
+ }
61
+ const overlayDir = getBuiltInTemplateOverlayDir(templateId);
62
+ if (!seenLayerDirs.has(overlayDir)) {
63
+ orderedLayerDirs.push(overlayDir);
64
+ }
65
+ return orderedLayerDirs;
13
66
  }
14
67
  function getBuiltInPersistenceSpec({ templateId, dataStorageMode, persistencePolicy, }) {
15
68
  if (templateId === "persistence") {
@@ -146,7 +199,7 @@ export function buildTemplateVariablesFromBlockSpec(spec) {
146
199
  };
147
200
  }
148
201
  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, }) {
202
+ async plan({ allowExistingDir = false, answers, cwd = process.cwd(), dataStorageMode, externalLayerId, externalLayerSource, externalLayerSourceLabel, noInstall = false, packageManager, persistencePolicy, projectDir, repositoryReference, templateId, variant, withMigrationUi = false, withTestPreset = false, withWpEnv = false, }) {
150
203
  return {
151
204
  spec: createBuiltInBlockSpec({
152
205
  answers,
@@ -160,29 +213,102 @@ export class BlockGeneratorService {
160
213
  target: {
161
214
  allowExistingDir,
162
215
  cwd,
216
+ externalLayerId,
217
+ externalLayerSource,
218
+ externalLayerSourceLabel,
163
219
  noInstall,
164
220
  packageManager,
165
221
  projectDir,
222
+ repositoryReference,
166
223
  variant,
167
224
  },
168
225
  };
169
226
  }
170
227
  async validate({ plan }) {
228
+ if (plan.target.externalLayerId && !plan.target.externalLayerSource) {
229
+ throw new Error("externalLayerId requires externalLayerSource when composing built-in template layers.");
230
+ }
171
231
  if (plan.target.variant) {
172
232
  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
233
  }
174
234
  return plan;
175
235
  }
176
236
  async render({ validated }) {
177
- const templateSource = await resolveBuiltInTemplateSource(validated.spec.template.family, {
178
- persistenceEnabled: validated.spec.persistence.enabled,
237
+ const variables = buildTemplateVariablesFromBlockSpec(validated.spec);
238
+ const persistenceEnabled = validated.spec.persistence.enabled;
239
+ const artifacts = buildBuiltInBlockArtifacts({
240
+ templateId: validated.spec.template.family,
241
+ variables,
242
+ });
243
+ const codeArtifacts = buildBuiltInCodeArtifacts({
244
+ templateId: validated.spec.template.family,
245
+ variables,
246
+ });
247
+ const templateVariantOptions = {
248
+ persistenceEnabled,
179
249
  persistencePolicy: validated.spec.persistence.enabled &&
180
250
  validated.spec.persistence.persistencePolicy === "public"
181
251
  ? "public"
182
252
  : "authenticated",
183
- });
184
- const variables = buildTemplateVariablesFromBlockSpec(validated.spec);
185
- const persistenceEnabled = validated.spec.persistence.enabled;
253
+ };
254
+ let templateSource = await resolveBuiltInTemplateSource(validated.spec.template.family, templateVariantOptions);
255
+ const warnings = [...(templateSource.warnings ?? [])];
256
+ if (validated.target.externalLayerSource) {
257
+ let layerSeed;
258
+ try {
259
+ layerSeed = await resolveTemplateSeed(parseTemplateLocator(validated.target.externalLayerSource), validated.target.cwd);
260
+ const resolvedLayers = await resolveExternalTemplateLayers({
261
+ externalLayerId: validated.target.externalLayerId,
262
+ sourceRoot: layerSeed.rootDir,
263
+ });
264
+ const baseLayerDirs = getBuiltInTemplateSharedLayerDirs(validated.spec.template.family, templateVariantOptions);
265
+ await assertExternalTemplateLayersDoNotWriteProtectedOutputs({
266
+ externalEntries: resolvedLayers.entries,
267
+ protectedOutputPaths: buildProtectedTemplateOutputPaths({
268
+ artifacts,
269
+ codeArtifacts,
270
+ spec: validated.spec,
271
+ variables,
272
+ }),
273
+ view: variables,
274
+ });
275
+ await templateSource.cleanup?.();
276
+ templateSource = await resolveBuiltInTemplateSourceFromLayerDirs(validated.spec.template.family, buildCombinedTemplateLayerDirs({
277
+ baseLayerDirs,
278
+ externalEntries: resolvedLayers.entries,
279
+ templateId: validated.spec.template.family,
280
+ }));
281
+ const layerSourceCleanup = layerSeed.cleanup;
282
+ const templateCleanup = templateSource.cleanup;
283
+ templateSource.cleanup = async () => {
284
+ const cleanupErrors = [];
285
+ try {
286
+ await templateCleanup?.();
287
+ }
288
+ catch (error) {
289
+ cleanupErrors.push(error instanceof Error ? error : new Error(String(error)));
290
+ }
291
+ try {
292
+ await layerSourceCleanup?.();
293
+ }
294
+ catch (error) {
295
+ cleanupErrors.push(error instanceof Error ? error : new Error(String(error)));
296
+ }
297
+ if (cleanupErrors.length > 0) {
298
+ throw new Error([
299
+ "Failed to cleanup composed template sources.",
300
+ ...cleanupErrors.map((error) => `- ${error.message}`),
301
+ ].join("\n"));
302
+ }
303
+ };
304
+ warnings.push(`Applied external layer "${resolvedLayers.selectedLayerId}" from "${validated.target.externalLayerSourceLabel ?? validated.target.externalLayerSource}".`);
305
+ }
306
+ catch (error) {
307
+ await templateSource.cleanup?.();
308
+ await layerSeed?.cleanup?.();
309
+ throw error;
310
+ }
311
+ }
186
312
  const rendered = {
187
313
  ...validated,
188
314
  cleanup: templateSource.cleanup,
@@ -202,17 +328,11 @@ export class BlockGeneratorService {
202
328
  selectedVariant: null,
203
329
  templateDir: templateSource.templateDir,
204
330
  variables,
205
- warnings: templateSource.warnings ?? [],
331
+ warnings,
206
332
  };
207
333
  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
- }),
334
+ artifacts,
335
+ codeArtifacts,
216
336
  variablesFingerprint: createVariablesFingerprint(variables),
217
337
  });
218
338
  return rendered;
@@ -243,6 +363,7 @@ export class BlockGeneratorService {
243
363
  noInstall: rendered.target.noInstall,
244
364
  packageManager: rendered.target.packageManager,
245
365
  projectDir: rendered.target.projectDir,
366
+ repositoryReference: rendered.target.repositoryReference,
246
367
  gitignoreContent: rendered.gitignoreContent,
247
368
  readmeContent: rendered.readmeContent,
248
369
  templateDir: rendered.templateDir,
@@ -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
+ }