@wp-typia/project-tools 0.11.1 → 0.12.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.
@@ -5,6 +5,17 @@ import { execFileSync } from "node:child_process";
5
5
  import { access, constants as fsConstants, rm, writeFile } from "node:fs/promises";
6
6
  import { getBuiltInTemplateLayerDirs } from "./template-builtins.js";
7
7
  import { listTemplates } from "./template-registry.js";
8
+ import { readWorkspaceInventory } from "./workspace-inventory.js";
9
+ import { getInvalidWorkspaceProjectReason, parseWorkspacePackageJson, WORKSPACE_TEMPLATE_PACKAGE, tryResolveWorkspaceProject, } from "./workspace-project.js";
10
+ const WORKSPACE_COLLECTION_IMPORT_LINE = "import '../../collection';";
11
+ const WORKSPACE_COLLECTION_IMPORT_PATTERN = /^\s*import\s+["']\.\.\/\.\.\/collection["']\s*;?\s*$/m;
12
+ const WORKSPACE_GENERATED_BLOCK_ARTIFACTS = [
13
+ "block.json",
14
+ "typia.manifest.json",
15
+ "typia.schema.json",
16
+ "typia-validator.php",
17
+ "typia.openapi.json",
18
+ ];
8
19
  function readCommandVersion(command, args = ["--version"]) {
9
20
  try {
10
21
  return execFileSync(command, args, {
@@ -40,6 +51,132 @@ async function checkTempDirectory() {
40
51
  return false;
41
52
  }
42
53
  }
54
+ function createDoctorCheck(label, status, detail) {
55
+ return { detail, label, status };
56
+ }
57
+ function getWorkspaceBootstrapRelativePath(packageName) {
58
+ const packageBaseName = packageName.split("/").pop() ?? packageName;
59
+ return `${packageBaseName}.php`;
60
+ }
61
+ function checkExistingFiles(projectDir, label, filePaths) {
62
+ const missing = filePaths
63
+ .filter((filePath) => typeof filePath === "string")
64
+ .filter((filePath) => !fs.existsSync(path.join(projectDir, filePath)));
65
+ return createDoctorCheck(label, missing.length === 0 ? "pass" : "fail", missing.length === 0 ? "All referenced files exist" : `Missing: ${missing.join(", ")}`);
66
+ }
67
+ function checkWorkspacePackageMetadata(workspace, packageJson) {
68
+ const issues = [];
69
+ const packageName = packageJson.name;
70
+ const bootstrapRelativePath = getWorkspaceBootstrapRelativePath(typeof packageName === "string" && packageName.length > 0 ? packageName : workspace.packageName);
71
+ const wpTypia = packageJson.wpTypia;
72
+ if (typeof packageName !== "string" || packageName.length === 0) {
73
+ issues.push("package.json must define a string name for workspace bootstrap resolution");
74
+ }
75
+ if (wpTypia?.projectType !== "workspace") {
76
+ issues.push('wpTypia.projectType must be "workspace"');
77
+ }
78
+ if (wpTypia?.templatePackage !== WORKSPACE_TEMPLATE_PACKAGE) {
79
+ issues.push(`wpTypia.templatePackage must be "${WORKSPACE_TEMPLATE_PACKAGE}"`);
80
+ }
81
+ if (wpTypia?.namespace !== workspace.workspace.namespace) {
82
+ issues.push(`wpTypia.namespace must equal "${workspace.workspace.namespace}"`);
83
+ }
84
+ if (wpTypia?.textDomain !== workspace.workspace.textDomain) {
85
+ issues.push(`wpTypia.textDomain must equal "${workspace.workspace.textDomain}"`);
86
+ }
87
+ if (wpTypia?.phpPrefix !== workspace.workspace.phpPrefix) {
88
+ issues.push(`wpTypia.phpPrefix must equal "${workspace.workspace.phpPrefix}"`);
89
+ }
90
+ if (!fs.existsSync(path.join(workspace.projectDir, bootstrapRelativePath))) {
91
+ issues.push(`Missing bootstrap file ${bootstrapRelativePath}`);
92
+ }
93
+ return createDoctorCheck("Workspace package metadata", issues.length === 0 ? "pass" : "fail", issues.length === 0
94
+ ? `package.json metadata aligns with ${workspace.packageName} and ${bootstrapRelativePath}`
95
+ : issues.join("; "));
96
+ }
97
+ function getWorkspaceBlockRequiredFiles(block) {
98
+ const blockDir = path.join("src", "blocks", block.slug);
99
+ return Array.from(new Set([
100
+ block.typesFile,
101
+ block.apiTypesFile,
102
+ block.openApiFile,
103
+ path.join(blockDir, "index.tsx"),
104
+ ...WORKSPACE_GENERATED_BLOCK_ARTIFACTS.map((fileName) => path.join(blockDir, fileName)),
105
+ ].filter((filePath) => typeof filePath === "string")));
106
+ }
107
+ function checkWorkspaceBlockMetadata(projectDir, workspace, block) {
108
+ const blockJsonRelativePath = path.join("src", "blocks", block.slug, "block.json");
109
+ const blockJsonPath = path.join(projectDir, blockJsonRelativePath);
110
+ if (!fs.existsSync(blockJsonPath)) {
111
+ return createDoctorCheck(`Block metadata ${block.slug}`, "fail", `Missing ${blockJsonRelativePath}`);
112
+ }
113
+ let blockJson;
114
+ try {
115
+ blockJson = JSON.parse(fs.readFileSync(blockJsonPath, "utf8"));
116
+ }
117
+ catch (error) {
118
+ return createDoctorCheck(`Block metadata ${block.slug}`, "fail", error instanceof Error ? error.message : String(error));
119
+ }
120
+ const expectedName = `${workspace.workspace.namespace}/${block.slug}`;
121
+ const issues = [];
122
+ if (blockJson.name !== expectedName) {
123
+ issues.push(`block.json name must equal "${expectedName}"`);
124
+ }
125
+ if (blockJson.textdomain !== workspace.workspace.textDomain) {
126
+ issues.push(`block.json textdomain must equal "${workspace.workspace.textDomain}"`);
127
+ }
128
+ return createDoctorCheck(`Block metadata ${block.slug}`, issues.length === 0 ? "pass" : "fail", issues.length === 0
129
+ ? `block.json matches ${expectedName} and ${workspace.workspace.textDomain}`
130
+ : issues.join("; "));
131
+ }
132
+ function checkWorkspaceBlockCollectionImport(projectDir, blockSlug) {
133
+ const entryRelativePath = path.join("src", "blocks", blockSlug, "index.tsx");
134
+ const entryPath = path.join(projectDir, entryRelativePath);
135
+ if (!fs.existsSync(entryPath)) {
136
+ return createDoctorCheck(`Block collection ${blockSlug}`, "fail", `Missing ${entryRelativePath}`);
137
+ }
138
+ const source = fs.readFileSync(entryPath, "utf8");
139
+ const hasCollectionImport = WORKSPACE_COLLECTION_IMPORT_PATTERN.test(source);
140
+ return createDoctorCheck(`Block collection ${blockSlug}`, hasCollectionImport ? "pass" : "fail", hasCollectionImport
141
+ ? "Shared block collection import is present"
142
+ : `Missing a shared collection import like ${WORKSPACE_COLLECTION_IMPORT_LINE}`);
143
+ }
144
+ function checkWorkspacePatternBootstrap(projectDir, packageName) {
145
+ const packageBaseName = packageName.split("/").pop() ?? packageName;
146
+ const bootstrapPath = path.join(projectDir, `${packageBaseName}.php`);
147
+ if (!fs.existsSync(bootstrapPath)) {
148
+ return createDoctorCheck("Pattern bootstrap", "fail", `Missing ${path.basename(bootstrapPath)}`);
149
+ }
150
+ const source = fs.readFileSync(bootstrapPath, "utf8");
151
+ const hasCategoryAnchor = source.includes("register_block_pattern_category");
152
+ const hasPatternGlob = source.includes("/src/patterns/*.php");
153
+ return createDoctorCheck("Pattern bootstrap", hasCategoryAnchor && hasPatternGlob ? "pass" : "fail", hasCategoryAnchor && hasPatternGlob
154
+ ? "Pattern category and loader hooks are present"
155
+ : "Missing pattern category registration or src/patterns loader hook");
156
+ }
157
+ function checkVariationEntrypoint(projectDir, blockSlug) {
158
+ const entryPath = path.join(projectDir, "src", "blocks", blockSlug, "index.tsx");
159
+ if (!fs.existsSync(entryPath)) {
160
+ return createDoctorCheck(`Variation entrypoint ${blockSlug}`, "fail", `Missing ${path.relative(projectDir, entryPath)}`);
161
+ }
162
+ const source = fs.readFileSync(entryPath, "utf8");
163
+ const hasImport = source.includes("./variations");
164
+ const hasCall = source.includes("registerWorkspaceVariations()");
165
+ return createDoctorCheck(`Variation entrypoint ${blockSlug}`, hasImport && hasCall ? "pass" : "fail", hasImport && hasCall
166
+ ? "Variations registration hook is present"
167
+ : "Missing ./variations import or registerWorkspaceVariations() call");
168
+ }
169
+ function checkMigrationWorkspaceHint(workspace, packageJson) {
170
+ const hasMigrationScript = typeof packageJson.scripts?.["migration:doctor"] === "string";
171
+ const migrationConfigRelativePath = path.join("src", "migrations", "config.ts");
172
+ const hasMigrationConfig = fs.existsSync(path.join(workspace.projectDir, migrationConfigRelativePath));
173
+ if (!hasMigrationScript && !hasMigrationConfig) {
174
+ return null;
175
+ }
176
+ return createDoctorCheck("Migration workspace", hasMigrationConfig ? "pass" : "fail", hasMigrationConfig
177
+ ? "Run `wp-typia migrate doctor --all` for migration target, snapshot, fixture, and generated artifact checks"
178
+ : `Missing ${migrationConfigRelativePath} for the configured migration workspace`);
179
+ }
43
180
  /**
44
181
  * Collect all runtime doctor checks for the current environment.
45
182
  *
@@ -109,6 +246,71 @@ export async function getDoctorChecks(cwd) {
109
246
  detail: hasAssets ? layerDirs.join(" + ") : "Missing core template assets",
110
247
  });
111
248
  }
249
+ let workspace = null;
250
+ let invalidWorkspaceReason = null;
251
+ try {
252
+ invalidWorkspaceReason = getInvalidWorkspaceProjectReason(cwd);
253
+ workspace = tryResolveWorkspaceProject(cwd);
254
+ }
255
+ catch (error) {
256
+ checks.push(createDoctorCheck("Workspace package metadata", "fail", error instanceof Error ? error.message : String(error)));
257
+ return checks;
258
+ }
259
+ if (!workspace) {
260
+ if (invalidWorkspaceReason) {
261
+ checks.push(createDoctorCheck("Workspace package metadata", "fail", invalidWorkspaceReason));
262
+ }
263
+ return checks;
264
+ }
265
+ checks.push(createDoctorCheck("Workspace marker", "pass", `Official workspace detected for ${workspace.workspace.namespace}`));
266
+ let workspacePackageJson;
267
+ try {
268
+ workspacePackageJson = parseWorkspacePackageJson(workspace.projectDir);
269
+ }
270
+ catch (error) {
271
+ checks.push(createDoctorCheck("Workspace package metadata", "fail", error instanceof Error ? error.message : String(error)));
272
+ return checks;
273
+ }
274
+ checks.push(checkWorkspacePackageMetadata(workspace, workspacePackageJson));
275
+ try {
276
+ const inventory = readWorkspaceInventory(workspace.projectDir);
277
+ checks.push(createDoctorCheck("Workspace inventory", "pass", `${inventory.blocks.length} block(s), ${inventory.variations.length} variation(s), ${inventory.patterns.length} pattern(s)`));
278
+ for (const block of inventory.blocks) {
279
+ checks.push(checkExistingFiles(workspace.projectDir, `Block ${block.slug}`, [
280
+ ...getWorkspaceBlockRequiredFiles(block),
281
+ ]));
282
+ checks.push(checkWorkspaceBlockMetadata(workspace.projectDir, workspace, block));
283
+ checks.push(checkWorkspaceBlockCollectionImport(workspace.projectDir, block.slug));
284
+ }
285
+ const registeredBlockSlugs = new Set(inventory.blocks.map((block) => block.slug));
286
+ const variationTargetBlocks = new Set();
287
+ for (const variation of inventory.variations) {
288
+ if (!registeredBlockSlugs.has(variation.block)) {
289
+ checks.push(createDoctorCheck(`Variation ${variation.block}/${variation.slug}`, "fail", `Variation references unknown block "${variation.block}"`));
290
+ continue;
291
+ }
292
+ variationTargetBlocks.add(variation.block);
293
+ checks.push(checkExistingFiles(workspace.projectDir, `Variation ${variation.block}/${variation.slug}`, [variation.file]));
294
+ }
295
+ for (const blockSlug of variationTargetBlocks) {
296
+ checks.push(checkVariationEntrypoint(workspace.projectDir, blockSlug));
297
+ }
298
+ const shouldCheckPatternBootstrap = inventory.patterns.length > 0 ||
299
+ fs.existsSync(path.join(workspace.projectDir, "src", "patterns"));
300
+ if (shouldCheckPatternBootstrap) {
301
+ checks.push(checkWorkspacePatternBootstrap(workspace.projectDir, workspace.packageName));
302
+ }
303
+ for (const pattern of inventory.patterns) {
304
+ checks.push(checkExistingFiles(workspace.projectDir, `Pattern ${pattern.slug}`, [pattern.file]));
305
+ }
306
+ const migrationWorkspaceCheck = checkMigrationWorkspaceHint(workspace, workspacePackageJson);
307
+ if (migrationWorkspaceCheck) {
308
+ checks.push(migrationWorkspaceCheck);
309
+ }
310
+ }
311
+ catch (error) {
312
+ checks.push(createDoctorCheck("Workspace inventory", "fail", error instanceof Error ? error.message : String(error)));
313
+ }
112
314
  return checks;
113
315
  }
114
316
  /**
@@ -17,8 +17,8 @@ export function formatHelpText() {
17
17
  wp-typia create <project-dir> [--template compound] [--data-storage <post-meta|custom-table>] [--persistence-policy <authenticated|public>] [--namespace <value>] [--text-domain <value>] [--php-prefix <value>] [--with-migration-ui] [--with-wp-env] [--with-test-preset] [--yes] [--no-install] [--package-manager <id>]
18
18
  wp-typia <project-dir> [create flags...]
19
19
  wp-typia add block <name> --template <basic|interactivity|persistence|compound> [--data-storage <post-meta|custom-table>] [--persistence-policy <authenticated|public>]
20
- wp-typia add variation
21
- wp-typia add pattern
20
+ wp-typia add variation <name> --block <block-slug>
21
+ wp-typia add pattern <name>
22
22
  wp-typia migrate <init|snapshot|diff|scaffold|verify|doctor|fixtures|fuzz> [...]
23
23
  wp-typia templates list
24
24
  wp-typia templates inspect <id>
@@ -32,6 +32,9 @@ Package managers: ${PACKAGE_MANAGER_IDS.join(", ")}
32
32
  Notes:
33
33
  \`wp-typia create\` is the canonical scaffold command.
34
34
  \`wp-typia <project-dir>\` remains a backward-compatible alias to \`create\`.
35
- \`add variation\` and \`add pattern\` are reserved placeholders in this release.
35
+ \`add variation\` uses an existing workspace block from \`scripts/block-config.ts\`.
36
+ \`add pattern\` scaffolds a namespaced PHP pattern shell under \`src/patterns/\`.
37
+ \`wp-typia doctor\` checks environment readiness plus workspace inventory and source-tree drift.
38
+ \`wp-typia migrate doctor --all\` checks migration target alignment, snapshots, fixtures, and generated migration artifacts.
36
39
  \`migrate\` is the canonical migration command; \`migrations\` is no longer supported.`;
37
40
  }
@@ -1,9 +1,20 @@
1
+ /**
2
+ * Public runtime surface for wp-typia project tools.
3
+ *
4
+ * This barrel exposes the stable orchestration APIs that power the `wp-typia`
5
+ * CLI while keeping reusable project logic out of the CLI package itself.
6
+ * Consumers should prefer these exports for scaffold, add, migrate, doctor,
7
+ * and workspace-aware helpers such as `getWorkspaceBlockSelectOptions`,
8
+ * `runAddBlockCommand`, `runAddVariationCommand`, `runAddPatternCommand`, and
9
+ * `runDoctor`.
10
+ */
1
11
  export { scaffoldProject, collectScaffoldAnswers, getDefaultAnswers, getTemplateVariables, resolvePackageManagerId, resolveTemplateId, } from "./scaffold.js";
2
12
  export { formatMigrationHelpText, parseMigrationArgs, runMigrationCommand, } from "./migrations.js";
13
+ export { parseWorkspacePackageManagerId, resolveWorkspaceProject, tryResolveWorkspaceProject, } from "./workspace-project.js";
3
14
  export { manifestAttributeToJsonSchema, projectJsonSchemaDocument, manifestToJsonSchema, manifestToOpenApi, normalizeEndpointAuthDefinition, } from "./schema-core.js";
4
15
  export { buildCompoundChildStarterManifestDocument, getStarterManifestFiles, stringifyStarterManifest, } from "./starter-manifests.js";
5
16
  export type { EndpointAuthIntent, EndpointOpenApiAuthMode, EndpointOpenApiContractDocument, EndpointOpenApiDocumentOptions, EndpointOpenApiEndpointDefinition, EndpointOpenApiMethod, EndpointWordPressAuthDefinition, EndpointWordPressAuthMechanism, JsonSchemaDocument, JsonSchemaProjectionProfile, JsonSchemaObject, NormalizedEndpointAuthDefinition, OpenApiDocument, OpenApiInfo, OpenApiOperation, OpenApiParameter, OpenApiPathItem, OpenApiSecurityScheme, } from "./schema-core.js";
6
17
  export { PACKAGE_MANAGER_IDS, PACKAGE_MANAGERS, formatPackageExecCommand, formatInstallCommand, formatRunScript, getPackageManager, getPackageManagerSelectOptions, transformPackageManagerText, } from "./package-managers.js";
7
18
  export { TEMPLATE_IDS, TEMPLATE_REGISTRY, getTemplateById, getTemplateSelectOptions, listTemplates, } from "./template-registry.js";
8
- export { createAddPlaceholderMessage, formatAddHelpText, formatHelpText, formatTemplateDetails, formatTemplateFeatures, formatTemplateSummary, getDoctorChecks, getNextSteps, getOptionalOnboarding, runAddBlockCommand, runDoctor, runScaffoldFlow, } from "./cli-core.js";
19
+ export { createReadlinePrompt, formatAddHelpText, formatHelpText, formatTemplateDetails, formatTemplateFeatures, formatTemplateSummary, getDoctorChecks, getNextSteps, getOptionalOnboarding, getWorkspaceBlockSelectOptions, runAddBlockCommand, runAddPatternCommand, runDoctor, runAddVariationCommand, runScaffoldFlow, } from "./cli-core.js";
9
20
  export type { DoctorCheck, ReadlinePrompt } from "./cli-core.js";
@@ -1,7 +1,18 @@
1
+ /**
2
+ * Public runtime surface for wp-typia project tools.
3
+ *
4
+ * This barrel exposes the stable orchestration APIs that power the `wp-typia`
5
+ * CLI while keeping reusable project logic out of the CLI package itself.
6
+ * Consumers should prefer these exports for scaffold, add, migrate, doctor,
7
+ * and workspace-aware helpers such as `getWorkspaceBlockSelectOptions`,
8
+ * `runAddBlockCommand`, `runAddVariationCommand`, `runAddPatternCommand`, and
9
+ * `runDoctor`.
10
+ */
1
11
  export { scaffoldProject, collectScaffoldAnswers, getDefaultAnswers, getTemplateVariables, resolvePackageManagerId, resolveTemplateId, } from "./scaffold.js";
2
12
  export { formatMigrationHelpText, parseMigrationArgs, runMigrationCommand, } from "./migrations.js";
13
+ export { parseWorkspacePackageManagerId, resolveWorkspaceProject, tryResolveWorkspaceProject, } from "./workspace-project.js";
3
14
  export { manifestAttributeToJsonSchema, projectJsonSchemaDocument, manifestToJsonSchema, manifestToOpenApi, normalizeEndpointAuthDefinition, } from "./schema-core.js";
4
15
  export { buildCompoundChildStarterManifestDocument, getStarterManifestFiles, stringifyStarterManifest, } from "./starter-manifests.js";
5
16
  export { PACKAGE_MANAGER_IDS, PACKAGE_MANAGERS, formatPackageExecCommand, formatInstallCommand, formatRunScript, getPackageManager, getPackageManagerSelectOptions, transformPackageManagerText, } from "./package-managers.js";
6
17
  export { TEMPLATE_IDS, TEMPLATE_REGISTRY, getTemplateById, getTemplateSelectOptions, listTemplates, } from "./template-registry.js";
7
- export { createAddPlaceholderMessage, formatAddHelpText, formatHelpText, formatTemplateDetails, formatTemplateFeatures, formatTemplateSummary, getDoctorChecks, getNextSteps, getOptionalOnboarding, runAddBlockCommand, runDoctor, runScaffoldFlow, } from "./cli-core.js";
18
+ export { createReadlinePrompt, formatAddHelpText, formatHelpText, formatTemplateDetails, formatTemplateFeatures, formatTemplateSummary, getDoctorChecks, getNextSteps, getOptionalOnboarding, getWorkspaceBlockSelectOptions, runAddBlockCommand, runAddPatternCommand, runDoctor, runAddVariationCommand, runScaffoldFlow, } from "./cli-core.js";
@@ -1,7 +1,7 @@
1
1
  import { createMigrationDiff } from "./migration-diff.js";
2
2
  import { formatDiffReport } from "./migration-render.js";
3
3
  import { createMigrationRiskSummary } from "./migration-risk.js";
4
- import type { MigrationBlockConfig, ParsedMigrationArgs, RenderLine } from "./migration-types.js";
4
+ import type { MigrationBlockConfig, MigrationProjectState, ParsedMigrationArgs, RenderLine } from "./migration-types.js";
5
5
  import type { ReadlinePrompt } from "./cli-prompt.js";
6
6
  type CommandRenderOptions = {
7
7
  prompt?: ReadlinePrompt;
@@ -78,7 +78,7 @@ export { formatDiffReport };
78
78
  * @param options Optional prompt/render hooks for testable and interactive execution.
79
79
  * @returns The command result, or a promise when the selected command is interactive.
80
80
  */
81
- export declare function runMigrationCommand(command: ParsedMigrationArgs, cwd: string, { prompt, renderLine }?: CommandRenderOptions): import("./migration-types.js").MigrationProjectState | import("./migration-types.js").MigrationDiff | MigrationPlanSummary | Promise<MigrationPlanSummary | {
81
+ export declare function runMigrationCommand(command: ParsedMigrationArgs, cwd: string, { prompt, renderLine }?: CommandRenderOptions): MigrationProjectState | import("./migration-types.js").MigrationDiff | MigrationPlanSummary | Promise<MigrationPlanSummary | {
82
82
  cancelled: true;
83
83
  }> | {
84
84
  block: import("./migration-types.js").ResolvedMigrationBlockTarget;
@@ -139,7 +139,7 @@ export declare function wizardProjectMigrations(projectDir: string, { isInteract
139
139
  * @returns The loaded migration project state after the config, snapshots, and generated files are written.
140
140
  * @throws Error When the project layout is unsupported or the migration version label is invalid.
141
141
  */
142
- export declare function initProjectMigrations(projectDir: string, currentMigrationVersion: string, { renderLine }?: CommandRenderOptions): import("./migration-types.js").MigrationProjectState;
142
+ export declare function initProjectMigrations(projectDir: string, currentMigrationVersion: string, { renderLine }?: CommandRenderOptions): MigrationProjectState;
143
143
  /**
144
144
  * Captures the current project state as a named migration snapshot and refreshes generated artifacts.
145
145
  *
@@ -152,7 +152,7 @@ export declare function initProjectMigrations(projectDir: string, currentMigrati
152
152
  export declare function snapshotProjectVersion(projectDir: string, migrationVersion: string, { renderLine, skipConfigUpdate, skipSyncTypes, }?: CommandRenderOptions & {
153
153
  skipConfigUpdate?: boolean;
154
154
  skipSyncTypes?: boolean;
155
- }): import("./migration-types.js").MigrationProjectState;
155
+ }): MigrationProjectState;
156
156
  /**
157
157
  * Computes and renders migration diffs for a selected legacy-to-target edge.
158
158
  *
@@ -246,4 +246,4 @@ export declare function fuzzProjectMigrations(projectDir: string, { all, fromMig
246
246
  * @param options Console rendering options for initialization output.
247
247
  * @returns The loaded migration project state after initialization completes.
248
248
  */
249
- export declare function seedProjectMigrations(projectDir: string, currentMigrationVersion: string, blocks: MigrationBlockConfig[], { renderLine }?: CommandRenderOptions): import("./migration-types.js").MigrationProjectState;
249
+ export declare function seedProjectMigrations(projectDir: string, currentMigrationVersion: string, blocks: MigrationBlockConfig[], { renderLine }?: CommandRenderOptions): MigrationProjectState;
@@ -11,6 +11,8 @@ import { assertRuleHasNoTodos, assertNoLegacySemverMigrationWorkspace, discoverM
11
11
  import { formatDiffReport, renderFuzzFile, renderGeneratedDeprecatedFile, renderGeneratedMigrationIndexFile, renderMigrationRegistryFile, renderMigrationRuleFile, renderPhpMigrationRegistryFile, renderVerifyFile, } from "./migration-render.js";
12
12
  import { createMigrationRiskSummary, formatMigrationRiskSummary } from "./migration-risk.js";
13
13
  import { assertMigrationVersionLabel, compareMigrationVersionLabels, copyFile, detectPackageManagerId, formatLegacyMigrationWorkspaceResetGuidance, getLocalTsxBinary, isInteractiveTerminal, readJson, resolveTargetMigrationVersion, runProjectScriptIfPresent, sanitizeSaveSnapshotSource, sanitizeSnapshotBlockJson, } from "./migration-utils.js";
14
+ import { readWorkspaceInventory } from "./workspace-inventory.js";
15
+ import { getInvalidWorkspaceProjectReason, tryResolveWorkspaceProject } from "./workspace-project.js";
14
16
  /**
15
17
  * Returns the formatted help text for migration CLI commands and flags.
16
18
  *
@@ -561,6 +563,48 @@ export function verifyProjectMigrations(projectDir, { all = false, fromMigration
561
563
  }
562
564
  return { verifiedVersions: targetVersions };
563
565
  }
566
+ function recordWorkspaceMigrationTargetAlignment(projectDir, state, recordCheck) {
567
+ let invalidWorkspaceReason = null;
568
+ let workspace;
569
+ try {
570
+ invalidWorkspaceReason = getInvalidWorkspaceProjectReason(projectDir);
571
+ workspace = tryResolveWorkspaceProject(projectDir);
572
+ }
573
+ catch (error) {
574
+ recordCheck("fail", "Workspace migration targets", error instanceof Error ? error.message : String(error));
575
+ return;
576
+ }
577
+ if (!workspace) {
578
+ if (invalidWorkspaceReason) {
579
+ recordCheck("fail", "Workspace migration targets", invalidWorkspaceReason);
580
+ }
581
+ return;
582
+ }
583
+ try {
584
+ const inventory = readWorkspaceInventory(workspace.projectDir);
585
+ const expectedTargets = inventory.blocks.map((block) => `${workspace.workspace.namespace}/${block.slug}`);
586
+ const configuredTargets = state.blocks.map((block) => block.blockName);
587
+ const expectedTargetSet = new Set(expectedTargets);
588
+ const configuredTargetSet = new Set(configuredTargets);
589
+ const missingTargets = expectedTargets.filter((target) => !configuredTargetSet.has(target));
590
+ const staleTargets = configuredTargets.filter((target) => !expectedTargetSet.has(target));
591
+ recordCheck(missingTargets.length === 0 && staleTargets.length === 0 ? "pass" : "fail", "Workspace migration targets", missingTargets.length === 0 && staleTargets.length === 0
592
+ ? `${expectedTargets.length} workspace block target(s) align with migration config`
593
+ : [
594
+ missingTargets.length > 0
595
+ ? `Missing from migration config: ${missingTargets.join(", ")}`
596
+ : null,
597
+ staleTargets.length > 0
598
+ ? `Not present in scripts/block-config.ts: ${staleTargets.join(", ")}`
599
+ : null,
600
+ ]
601
+ .filter((detail) => typeof detail === "string")
602
+ .join("; "));
603
+ }
604
+ catch (error) {
605
+ recordCheck("fail", "Workspace migration targets", error instanceof Error ? error.message : String(error));
606
+ }
607
+ }
564
608
  /**
565
609
  * Validate the migration workspace without mutating files.
566
610
  *
@@ -591,6 +635,7 @@ export function doctorProjectMigrations(projectDir, { all = false, fromMigration
591
635
  const snapshotVersions = new Set(targetVersions.length > 0
592
636
  ? [state.config.currentMigrationVersion, ...targetVersions]
593
637
  : state.config.supportedMigrationVersions);
638
+ recordWorkspaceMigrationTargetAlignment(projectDir, state, recordCheck);
594
639
  for (const version of snapshotVersions) {
595
640
  for (const block of state.blocks) {
596
641
  const snapshotRoot = getSnapshotRoot(projectDir, block, version);
@@ -2,7 +2,7 @@ const PACKAGE_MANAGER_DATA = [
2
2
  {
3
3
  id: "bun",
4
4
  label: "Bun",
5
- packageManagerField: "bun@1.3.10",
5
+ packageManagerField: "bun@1.3.11",
6
6
  installCommand: "bun install",
7
7
  frozenInstallCommand: "bun install --frozen-lockfile",
8
8
  },
@@ -0,0 +1,84 @@
1
+ export interface WorkspaceBlockInventoryEntry {
2
+ apiTypesFile?: string;
3
+ attributeTypeName?: string;
4
+ openApiFile?: string;
5
+ slug: string;
6
+ typesFile: string;
7
+ }
8
+ export interface WorkspaceVariationInventoryEntry {
9
+ block: string;
10
+ file: string;
11
+ slug: string;
12
+ }
13
+ export interface WorkspacePatternInventoryEntry {
14
+ file: string;
15
+ slug: string;
16
+ }
17
+ export interface WorkspaceInventory {
18
+ blockConfigPath: string;
19
+ blocks: WorkspaceBlockInventoryEntry[];
20
+ hasPatternsSection: boolean;
21
+ hasVariationsSection: boolean;
22
+ patterns: WorkspacePatternInventoryEntry[];
23
+ source: string;
24
+ variations: WorkspaceVariationInventoryEntry[];
25
+ }
26
+ export declare const BLOCK_CONFIG_ENTRY_MARKER = "\t// wp-typia add block entries";
27
+ export declare const VARIATION_CONFIG_ENTRY_MARKER = "\t// wp-typia add variation entries";
28
+ export declare const PATTERN_CONFIG_ENTRY_MARKER = "\t// wp-typia add pattern entries";
29
+ /**
30
+ * Parse workspace inventory entries from the source of `scripts/block-config.ts`.
31
+ *
32
+ * @param source Raw TypeScript source from `scripts/block-config.ts`.
33
+ * @returns Parsed inventory sections without the resolved `blockConfigPath`.
34
+ * @throws {Error} When `BLOCKS` is missing or any inventory entry is malformed.
35
+ */
36
+ export declare function parseWorkspaceInventorySource(source: string): Omit<WorkspaceInventory, "blockConfigPath">;
37
+ /**
38
+ * Read and parse the canonical workspace inventory file.
39
+ *
40
+ * @param projectDir Workspace root directory.
41
+ * @returns Parsed `WorkspaceInventory` including the resolved `blockConfigPath`.
42
+ * @throws {Error} When `scripts/block-config.ts` is missing or invalid.
43
+ */
44
+ export declare function readWorkspaceInventory(projectDir: string): WorkspaceInventory;
45
+ /**
46
+ * Return select options for the current workspace block inventory.
47
+ *
48
+ * The `description` field mirrors `block.typesFile`, while `name` and `value`
49
+ * both map to the block slug for use in interactive add flows.
50
+ *
51
+ * @param projectDir Workspace root directory.
52
+ * @returns Block options for variation-target selection.
53
+ */
54
+ export declare function getWorkspaceBlockSelectOptions(projectDir: string): Array<{
55
+ description: string;
56
+ name: string;
57
+ value: string;
58
+ }>;
59
+ /**
60
+ * Update `scripts/block-config.ts` source text with additional inventory entries.
61
+ *
62
+ * Missing `VARIATIONS` and `PATTERNS` sections are created automatically before
63
+ * new entries are appended at their marker comments. When provided,
64
+ * `transformSource` runs before any entries are inserted.
65
+ *
66
+ * @param source Existing `scripts/block-config.ts` source.
67
+ * @param options Entry lists plus an optional source transformer.
68
+ * @returns Updated source text with all requested inventory entries appended.
69
+ */
70
+ export declare function updateWorkspaceInventorySource(source: string, { blockEntries, patternEntries, variationEntries, transformSource, }?: {
71
+ blockEntries?: string[];
72
+ patternEntries?: string[];
73
+ transformSource?: (source: string) => string;
74
+ variationEntries?: string[];
75
+ }): string;
76
+ /**
77
+ * Append new entries to the canonical workspace inventory file on disk.
78
+ *
79
+ * @param projectDir Workspace root directory.
80
+ * @param options Entry lists and optional source transform passed through to
81
+ * `updateWorkspaceInventorySource`.
82
+ * @returns Resolves once `scripts/block-config.ts` has been updated if needed.
83
+ */
84
+ export declare function appendWorkspaceInventoryEntries(projectDir: string, options: Parameters<typeof updateWorkspaceInventorySource>[1]): Promise<void>;