wp-typia 0.20.5 → 0.22.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.
@@ -0,0 +1,844 @@
1
+ // @bun
2
+ import {
3
+ getPackageVersions
4
+ } from "./cli-39er8888.js";
5
+ import {
6
+ discoverMigrationInitLayout
7
+ } from "./cli-gcbre1zs.js";
8
+ import {
9
+ formatAddDevDependenciesCommand,
10
+ formatPackageExecCommand,
11
+ formatRunScript,
12
+ getPackageManager,
13
+ transformPackageManagerText
14
+ } from "./cli-sj5mtyzj.js";
15
+ import"./cli-tke8twkn.js";
16
+ import {
17
+ quoteTsString,
18
+ require_typescript,
19
+ rollbackWorkspaceMutation,
20
+ snapshotWorkspaceFiles,
21
+ toPascalCase,
22
+ updateWorkspaceInventorySource
23
+ } from "./cli-j180bk07.js";
24
+ import {
25
+ CLI_DIAGNOSTIC_CODES,
26
+ createCliDiagnosticCodeError
27
+ } from "./cli-p95wr1q8.js";
28
+ import {
29
+ parseWorkspacePackageManagerId,
30
+ tryResolveWorkspaceProject
31
+ } from "./cli-pd5pqgre.js";
32
+ import {
33
+ __toESM
34
+ } from "./cli-xnn9xjcy.js";
35
+
36
+ // ../wp-typia-project-tools/src/runtime/cli-init.ts
37
+ var import_typescript = __toESM(require_typescript(), 1);
38
+ import fs from "fs";
39
+ import { promises as fsp } from "fs";
40
+ import path from "path";
41
+ import { analyzeSourceTypes } from "@wp-typia/block-runtime/metadata-parser";
42
+ var SUPPORTED_RETROFIT_LAYOUT_NOTE = "Supported retrofit layouts currently mirror the migration bootstrap detector: `src/block.json` + `src/types.ts` + `src/save.tsx`, legacy root `block.json` + `src/types.ts` + `src/save.tsx`, or multi-block `src/blocks/*/block.json` workspaces.";
43
+ var RETROFIT_APPLY_PREVIEW_NOTE = "If you rerun with `wp-typia init --apply`, package.json and generated helper files are snapshotted and rolled back automatically if a write fails.";
44
+ var RETROFIT_ROLLBACK_NOTE = "Apply mode writes package.json and generated helper files with rollback-on-failure protection.";
45
+ var BASE_RETROFIT_SCRIPTS = {
46
+ sync: "tsx scripts/sync-project.ts",
47
+ "sync-types": "tsx scripts/sync-types-to-block-json.ts",
48
+ typecheck: "bun run sync --check && tsc --noEmit"
49
+ };
50
+ var BASE_RETROFIT_DEV_DEPENDENCIES = [
51
+ "@typia/unplugin",
52
+ "@wp-typia/block-runtime",
53
+ "@wp-typia/block-types",
54
+ "tsx",
55
+ "typescript",
56
+ "typia"
57
+ ];
58
+ function normalizeRelativePath(value) {
59
+ return value.replace(/\\/gu, "/");
60
+ }
61
+ function readProjectPackageJson(projectDir) {
62
+ const packageJsonPath = path.join(projectDir, "package.json");
63
+ if (!fs.existsSync(packageJsonPath)) {
64
+ return null;
65
+ }
66
+ const source = fs.readFileSync(packageJsonPath, "utf8");
67
+ try {
68
+ return JSON.parse(source);
69
+ } catch (error) {
70
+ const message = error instanceof Error ? error.message : String(error);
71
+ throw createCliDiagnosticCodeError(CLI_DIAGNOSTIC_CODES.INVALID_ARGUMENT, `Unable to parse ${packageJsonPath}: ${message}`, error instanceof Error ? { cause: error } : undefined);
72
+ }
73
+ }
74
+ function inferInitPackageManager(projectDir, packageJson) {
75
+ if (packageJson?.packageManager) {
76
+ return parseWorkspacePackageManagerId(packageJson.packageManager);
77
+ }
78
+ if (fs.existsSync(path.join(projectDir, "bun.lock")) || fs.existsSync(path.join(projectDir, "bun.lockb"))) {
79
+ return "bun";
80
+ }
81
+ if (fs.existsSync(path.join(projectDir, "pnpm-lock.yaml"))) {
82
+ return "pnpm";
83
+ }
84
+ if (fs.existsSync(path.join(projectDir, "yarn.lock")) || fs.existsSync(path.join(projectDir, ".yarnrc.yml"))) {
85
+ return "yarn";
86
+ }
87
+ return "npm";
88
+ }
89
+ function resolveInitPackageManager(projectDir, packageJson, override) {
90
+ if (!override) {
91
+ return inferInitPackageManager(projectDir, packageJson);
92
+ }
93
+ if (override !== "bun" && override !== "npm" && override !== "pnpm" && override !== "yarn") {
94
+ throw createCliDiagnosticCodeError(CLI_DIAGNOSTIC_CODES.INVALID_ARGUMENT, `Unknown package manager "${override}". Expected one of: bun, npm, pnpm, yarn.`);
95
+ }
96
+ return override;
97
+ }
98
+ function getWpTypiaCliSpecifier() {
99
+ const versions = getPackageVersions();
100
+ return versions.wpTypiaPackageExactVersion === "0.0.0" ? "wp-typia" : `wp-typia@${versions.wpTypiaPackageExactVersion}`;
101
+ }
102
+ function buildRequiredDevDependencyMap() {
103
+ const versions = getPackageVersions();
104
+ return {
105
+ "@typia/unplugin": versions.typiaUnpluginPackageVersion,
106
+ "@wp-typia/block-runtime": versions.blockRuntimePackageVersion,
107
+ "@wp-typia/block-types": versions.blockTypesPackageVersion,
108
+ tsx: versions.tsxPackageVersion,
109
+ typescript: versions.typescriptPackageVersion,
110
+ typia: versions.typiaPackageVersion
111
+ };
112
+ }
113
+ function getExistingDependencyVersion(packageJson, name) {
114
+ return packageJson?.devDependencies?.[name] ?? packageJson?.dependencies?.[name];
115
+ }
116
+ function buildDependencyChanges(packageJson) {
117
+ const requiredDependencies = buildRequiredDevDependencyMap();
118
+ return BASE_RETROFIT_DEV_DEPENDENCIES.flatMap((name) => {
119
+ const requiredValue = requiredDependencies[name];
120
+ const currentValue = getExistingDependencyVersion(packageJson, name);
121
+ if (currentValue === requiredValue) {
122
+ return [];
123
+ }
124
+ return [
125
+ {
126
+ action: currentValue ? "update" : "add",
127
+ ...currentValue ? { currentValue } : {},
128
+ name,
129
+ requiredValue
130
+ }
131
+ ];
132
+ });
133
+ }
134
+ function buildScriptChanges(packageJson, packageManager) {
135
+ const scripts = packageJson?.scripts ?? {};
136
+ return Object.entries(BASE_RETROFIT_SCRIPTS).flatMap(([name, commandSource]) => {
137
+ const requiredValue = transformPackageManagerText(commandSource, packageManager);
138
+ const currentValue = scripts[name];
139
+ if (currentValue === requiredValue) {
140
+ return [];
141
+ }
142
+ return [
143
+ {
144
+ action: typeof currentValue === "string" ? "update" : "add",
145
+ ...typeof currentValue === "string" ? { currentValue } : {},
146
+ name,
147
+ requiredValue
148
+ }
149
+ ];
150
+ });
151
+ }
152
+ function buildPackageManagerFieldChange(packageJson, packageManager, options = {}) {
153
+ if (!options.persistExplicitOverride && packageManager === "npm") {
154
+ return;
155
+ }
156
+ const requiredValue = getPackageManager(packageManager).packageManagerField;
157
+ const currentValue = packageJson?.packageManager;
158
+ if (currentValue === requiredValue) {
159
+ return;
160
+ }
161
+ return {
162
+ action: typeof currentValue === "string" ? "update" : "add",
163
+ ...typeof currentValue === "string" ? { currentValue } : {},
164
+ requiredValue
165
+ };
166
+ }
167
+ function buildGeneratedArtifactPaths(blockJsonFile, manifestFile) {
168
+ const manifestDir = path.dirname(manifestFile);
169
+ const artifactPaths = [
170
+ blockJsonFile,
171
+ manifestFile,
172
+ path.join(manifestDir, "typia.schema.json"),
173
+ path.join(manifestDir, "typia-validator.php"),
174
+ path.join(manifestDir, "typia.openapi.json")
175
+ ];
176
+ return Array.from(new Set(artifactPaths.map((filePath) => normalizeRelativePath(filePath))));
177
+ }
178
+ function collectNamedSourceTypeCandidates(typesSource) {
179
+ const sourceFile = import_typescript.default.createSourceFile("types.ts", typesSource, import_typescript.default.ScriptTarget.Latest, true, import_typescript.default.ScriptKind.TS);
180
+ return sourceFile.statements.flatMap((statement) => {
181
+ if (import_typescript.default.isInterfaceDeclaration(statement) || import_typescript.default.isTypeAliasDeclaration(statement)) {
182
+ return [statement.name.text];
183
+ }
184
+ return [];
185
+ });
186
+ }
187
+ function isObjectLikeSourceType(projectDir, typesFile, sourceTypeName) {
188
+ const analyzedTypes = analyzeSourceTypes({
189
+ projectRoot: projectDir,
190
+ typesFile
191
+ }, [sourceTypeName]);
192
+ return analyzedTypes[sourceTypeName]?.kind === "object";
193
+ }
194
+ function inferRetrofitAttributeTypeName(projectDir, block) {
195
+ const typesPath = path.join(projectDir, block.typesFile);
196
+ const typesSource = fs.readFileSync(typesPath, "utf8");
197
+ const blockNameSegments = block.blockName.split("/");
198
+ const slug = blockNameSegments[blockNameSegments.length - 1] ?? block.key;
199
+ const candidateNames = collectNamedSourceTypeCandidates(typesSource);
200
+ const validCandidates = candidateNames.filter((candidateName) => isObjectLikeSourceType(projectDir, block.typesFile, candidateName));
201
+ const preferredName = `${toPascalCase(slug)}Attributes`;
202
+ if (validCandidates.includes(preferredName)) {
203
+ return preferredName;
204
+ }
205
+ const attributeCandidates = validCandidates.filter((candidateName) => candidateName.endsWith("Attributes"));
206
+ if (attributeCandidates.length === 1) {
207
+ return attributeCandidates[0];
208
+ }
209
+ if (validCandidates.length === 1) {
210
+ return validCandidates[0];
211
+ }
212
+ if (validCandidates.length === 0) {
213
+ throw new Error(`Unable to infer an object-like source type from ${block.typesFile}. Add one interface or type alias such as ${preferredName} before rerunning \`wp-typia init\`.`);
214
+ }
215
+ throw new Error(`Unable to infer a unique source type from ${block.typesFile}. Candidate object-like exports: ${validCandidates.join(", ")}. Rename one to ${preferredName} or leave a single object-like attributes type before rerunning \`wp-typia init\`.`);
216
+ }
217
+ function buildRetrofitBlockTarget(projectDir, block) {
218
+ const blockNameSegments = block.blockName.split("/");
219
+ const slug = blockNameSegments[blockNameSegments.length - 1] ?? block.key;
220
+ return {
221
+ attributeTypeName: inferRetrofitAttributeTypeName(projectDir, block),
222
+ blockJsonFile: block.blockJsonFile,
223
+ blockName: block.blockName,
224
+ manifestFile: block.manifestFile,
225
+ saveFile: block.saveFile,
226
+ slug,
227
+ typesFile: block.typesFile
228
+ };
229
+ }
230
+ function buildRetrofitBlockConfigEntry(target) {
231
+ return [
232
+ "\t{",
233
+ ` slug: ${quoteTsString(target.slug)},`,
234
+ ` attributeTypeName: ${quoteTsString(target.attributeTypeName)},`,
235
+ ` blockJsonFile: ${quoteTsString(target.blockJsonFile)},`,
236
+ ` manifestFile: ${quoteTsString(target.manifestFile)},`,
237
+ ` typesFile: ${quoteTsString(target.typesFile)},`,
238
+ "\t},"
239
+ ].join(`
240
+ `);
241
+ }
242
+ function buildRetrofitBlockConfigSource(targets) {
243
+ const blockEntries = targets.map(buildRetrofitBlockConfigEntry).join(`
244
+ `);
245
+ const baseSource = `export interface WorkspaceBlockConfig {
246
+ attributeTypeName: string;
247
+ apiTypesFile?: string;
248
+ blockJsonFile?: string;
249
+ manifestFile?: string;
250
+ openApiFile?: string;
251
+ restManifest?: ReturnType<
252
+ typeof import( '@wp-typia/block-runtime/metadata-core' ).defineEndpointManifest
253
+ >;
254
+ slug: string;
255
+ typesFile: string;
256
+ }
257
+
258
+ export const BLOCKS: WorkspaceBlockConfig[] = [
259
+ ${blockEntries}
260
+ ];
261
+ `;
262
+ return `${updateWorkspaceInventorySource(baseSource)}
263
+ `;
264
+ }
265
+ function buildRetrofitSyncTypesScriptSource() {
266
+ return `/* eslint-disable no-console */
267
+ import path from 'node:path';
268
+
269
+ import { syncBlockMetadata } from '@wp-typia/block-runtime/metadata-core';
270
+
271
+ import { BLOCKS } from './block-config';
272
+
273
+ function parseCliOptions( argv: string[] ) {
274
+ const options = {
275
+ check: false,
276
+ };
277
+
278
+ for ( const argument of argv ) {
279
+ if ( argument === '--check' ) {
280
+ options.check = true;
281
+ continue;
282
+ }
283
+
284
+ throw new Error( \`Unknown sync-types flag: \${ argument }\` );
285
+ }
286
+
287
+ return options;
288
+ }
289
+
290
+ async function main() {
291
+ const options = parseCliOptions( process.argv.slice( 2 ) );
292
+
293
+ if ( BLOCKS.length === 0 ) {
294
+ console.log(
295
+ options.check
296
+ ? '\u2139\uFE0F No retrofit blocks are registered yet. \`sync-types --check\` is already clean.'
297
+ : '\u2139\uFE0F No retrofit blocks are registered yet. Add one block target to scripts/block-config.ts before rerunning sync-types.'
298
+ );
299
+ return;
300
+ }
301
+
302
+ for ( const block of BLOCKS ) {
303
+ const blockDir = path.dirname( block.typesFile );
304
+ const blockJsonFile =
305
+ block.blockJsonFile ?? path.join( blockDir, 'block.json' );
306
+ const manifestFile =
307
+ block.manifestFile ?? path.join( blockDir, 'typia.manifest.json' );
308
+ const manifestDir = path.dirname( manifestFile );
309
+ const result = await syncBlockMetadata(
310
+ {
311
+ blockJsonFile,
312
+ jsonSchemaFile: path.join( manifestDir, 'typia.schema.json' ),
313
+ manifestFile,
314
+ openApiFile: path.join( manifestDir, 'typia.openapi.json' ),
315
+ sourceTypeName: block.attributeTypeName,
316
+ typesFile: block.typesFile,
317
+ },
318
+ {
319
+ check: options.check,
320
+ }
321
+ );
322
+ for ( const warning of result.lossyProjectionWarnings ) {
323
+ console.warn( \`\u26A0\uFE0F \${ block.slug }: \${ warning }\` );
324
+ }
325
+ for ( const warning of result.phpGenerationWarnings ) {
326
+ console.warn( \`\u26A0\uFE0F \${ block.slug }: \${ warning }\` );
327
+ }
328
+
329
+ console.log(
330
+ options.check
331
+ ? \`\u2705 \${ block.slug }: block.json, typia.manifest.json, typia-validator.php, typia.schema.json, and typia.openapi.json are already up to date with the TypeScript types!\`
332
+ : \`\u2705 \${ block.slug }: block.json, typia.manifest.json, typia-validator.php, typia.schema.json, and typia.openapi.json were generated from TypeScript types!\`
333
+ );
334
+ console.log( '\uD83D\uDCDD Generated attributes:', result.attributeNames );
335
+ }
336
+ }
337
+
338
+ main().catch( ( error ) => {
339
+ console.error( '\u274C Type sync failed:', error );
340
+ process.exit( 1 );
341
+ } );
342
+ `;
343
+ }
344
+ function buildRetrofitSyncProjectScriptSource() {
345
+ return `/* eslint-disable no-console */
346
+ import { spawnSync } from 'node:child_process';
347
+ import fs from 'node:fs';
348
+ import path from 'node:path';
349
+
350
+ interface SyncCliOptions {
351
+ check: boolean;
352
+ }
353
+
354
+ function parseCliOptions( argv: string[] ): SyncCliOptions {
355
+ const options: SyncCliOptions = {
356
+ check: false,
357
+ };
358
+
359
+ for ( const argument of argv ) {
360
+ if ( argument === '--check' ) {
361
+ options.check = true;
362
+ continue;
363
+ }
364
+
365
+ throw new Error( \`Unknown sync flag: \${ argument }\` );
366
+ }
367
+
368
+ return options;
369
+ }
370
+
371
+ function getSyncScriptEnv() {
372
+ const binaryDirectory = path.join( process.cwd(), 'node_modules', '.bin' );
373
+ const inheritedPath =
374
+ process.env.PATH ??
375
+ process.env.Path ??
376
+ Object.entries( process.env ).find(
377
+ ( [ key ] ) => key.toLowerCase() === 'path'
378
+ )?.[ 1 ] ??
379
+ '';
380
+ const nextPath = fs.existsSync( binaryDirectory )
381
+ ? \`\${ binaryDirectory }\${ path.delimiter }\${ inheritedPath }\`
382
+ : inheritedPath;
383
+ const env: NodeJS.ProcessEnv = {
384
+ ...process.env,
385
+ };
386
+
387
+ for ( const key of Object.keys( env ) ) {
388
+ if ( key.toLowerCase() === 'path' ) {
389
+ delete env[ key ];
390
+ }
391
+ }
392
+
393
+ env.PATH = nextPath;
394
+
395
+ return env;
396
+ }
397
+
398
+ function runSyncScript( scriptPath: string, options: SyncCliOptions ) {
399
+ const args = [ scriptPath ];
400
+ if ( options.check ) {
401
+ args.push( '--check' );
402
+ }
403
+
404
+ const result = spawnSync( 'tsx', args, {
405
+ cwd: process.cwd(),
406
+ env: getSyncScriptEnv(),
407
+ shell: process.platform === 'win32',
408
+ stdio: 'inherit',
409
+ } );
410
+
411
+ if ( result.error ) {
412
+ if ( ( result.error as NodeJS.ErrnoException ).code === 'ENOENT' ) {
413
+ throw new Error(
414
+ 'Unable to resolve \`tsx\` for project sync. Install project dependencies or rerun the command through your package manager.'
415
+ );
416
+ }
417
+
418
+ throw result.error;
419
+ }
420
+
421
+ if ( result.status !== 0 ) {
422
+ throw new Error( \`Sync script failed: \${ scriptPath }\` );
423
+ }
424
+ }
425
+
426
+ async function main() {
427
+ const options = parseCliOptions( process.argv.slice( 2 ) );
428
+ const syncTypesScriptPath = path.join( 'scripts', 'sync-types-to-block-json.ts' );
429
+
430
+ runSyncScript( syncTypesScriptPath, options );
431
+
432
+ console.log(
433
+ options.check
434
+ ? '\u2705 Generated project metadata is already synchronized.'
435
+ : '\u2705 Generated project metadata was synchronized.'
436
+ );
437
+ }
438
+
439
+ main().catch( ( error ) => {
440
+ console.error( '\u274C Project sync failed:', error );
441
+ process.exit( 1 );
442
+ } );
443
+ `;
444
+ }
445
+ function buildLayoutDetails(projectDir) {
446
+ try {
447
+ const discoveredLayout = discoverMigrationInitLayout(projectDir);
448
+ const discoveredBlocks = discoveredLayout.mode === "multi" ? discoveredLayout.blocks : [discoveredLayout.block];
449
+ let blockTargets;
450
+ try {
451
+ blockTargets = discoveredBlocks.map((block) => buildRetrofitBlockTarget(projectDir, block));
452
+ } catch (error) {
453
+ const message = error instanceof Error ? error.message : String(error);
454
+ return {
455
+ blockNames: discoveredBlocks.map((block) => block.blockName),
456
+ blockTargets: [],
457
+ description: "Detected supported block files, but could not infer retrofit block-config metadata automatically yet.",
458
+ generatedArtifacts: [],
459
+ kind: "unsupported",
460
+ notes: [message, SUPPORTED_RETROFIT_LAYOUT_NOTE]
461
+ };
462
+ }
463
+ if (discoveredLayout.mode === "multi") {
464
+ return {
465
+ blockNames: discoveredBlocks.map((block) => block.blockName),
466
+ blockTargets,
467
+ description: `Detected a supported multi-block retrofit candidate (${discoveredBlocks.length} targets).`,
468
+ generatedArtifacts: discoveredBlocks.flatMap((block) => buildGeneratedArtifactPaths(block.blockJsonFile, block.manifestFile)),
469
+ kind: "multi-block",
470
+ notes: [
471
+ "Migration bootstrap can stay optional. Add it later with `wp-typia migrate init --current-migration-version v1` once the typed sync surface is in place."
472
+ ]
473
+ };
474
+ }
475
+ return {
476
+ blockNames: [discoveredLayout.block.blockName],
477
+ blockTargets,
478
+ description: "Detected a supported single-block retrofit candidate.",
479
+ generatedArtifacts: buildGeneratedArtifactPaths(discoveredLayout.block.blockJsonFile, discoveredLayout.block.manifestFile),
480
+ kind: "single-block",
481
+ notes: discoveredLayout.block.blockJsonFile === "block.json" ? [
482
+ "Legacy root `block.json` layouts are still supported for retrofit planning, but newer scaffolds keep generated block metadata under `src/`."
483
+ ] : []
484
+ };
485
+ } catch (error) {
486
+ const message = error instanceof Error ? error.message : String(error);
487
+ return {
488
+ blockNames: [],
489
+ blockTargets: [],
490
+ description: "No supported retrofit layout was auto-detected yet.",
491
+ generatedArtifacts: [],
492
+ kind: "unsupported",
493
+ notes: [message, SUPPORTED_RETROFIT_LAYOUT_NOTE]
494
+ };
495
+ }
496
+ }
497
+ function hasExistingWpTypiaProjectSurface(projectDir, packageJson) {
498
+ const scripts = packageJson?.scripts ?? {};
499
+ const hasSyncSurface = typeof scripts.sync === "string" || typeof scripts["sync-types"] === "string";
500
+ const hasHelperFiles = [
501
+ path.join("scripts", "block-config.ts"),
502
+ path.join("scripts", "sync-project.ts"),
503
+ path.join("scripts", "sync-types-to-block-json.ts")
504
+ ].every((relativePath) => fs.existsSync(path.join(projectDir, relativePath)));
505
+ const hasRuntimeDeps = typeof getExistingDependencyVersion(packageJson, "@wp-typia/block-runtime") === "string" && typeof getExistingDependencyVersion(packageJson, "@wp-typia/block-types") === "string";
506
+ return hasSyncSurface && hasHelperFiles && hasRuntimeDeps;
507
+ }
508
+ function buildPlannedFiles(projectDir, layoutKind) {
509
+ if (layoutKind === "unsupported") {
510
+ return [];
511
+ }
512
+ return [
513
+ {
514
+ action: fs.existsSync(path.join(projectDir, "scripts", "block-config.ts")) ? "update" : "add",
515
+ path: "scripts/block-config.ts",
516
+ purpose: "Declare the current retrofit block targets so sync-types can regenerate metadata from the existing TypeScript source of truth."
517
+ },
518
+ {
519
+ action: fs.existsSync(path.join(projectDir, "scripts", "sync-types-to-block-json.ts")) ? "update" : "add",
520
+ path: "scripts/sync-types-to-block-json.ts",
521
+ purpose: "Generate block.json and Typia metadata artifacts from the current TypeScript source of truth."
522
+ },
523
+ {
524
+ action: fs.existsSync(path.join(projectDir, "scripts", "sync-project.ts")) ? "update" : "add",
525
+ path: "scripts/sync-project.ts",
526
+ purpose: "Provide one shared sync entrypoint that can grow into sync-rest or workspace-aware refresh steps later."
527
+ }
528
+ ];
529
+ }
530
+ function buildChangeSummary(changes, options) {
531
+ const lines = [];
532
+ for (const dependencyChange of changes.packageChanges.addDevDependencies) {
533
+ lines.push(`devDependency ${dependencyChange.action} ${dependencyChange.name} -> ${dependencyChange.requiredValue}`);
534
+ }
535
+ if (changes.packageChanges.packageManagerField) {
536
+ lines.push(`packageManager ${changes.packageChanges.packageManagerField.action} -> ${changes.packageChanges.packageManagerField.requiredValue}`);
537
+ }
538
+ for (const scriptChange of changes.packageChanges.scripts) {
539
+ lines.push(`script ${scriptChange.action} ${scriptChange.name} -> ${scriptChange.requiredValue}`);
540
+ }
541
+ for (const filePlan of changes.plannedFiles) {
542
+ lines.push(`file ${filePlan.action} ${filePlan.path} (${filePlan.purpose})`);
543
+ }
544
+ if (options.includeGeneratedArtifacts) {
545
+ for (const artifactPath of changes.generatedArtifacts) {
546
+ lines.push(`generated artifact ${artifactPath}`);
547
+ }
548
+ }
549
+ return lines;
550
+ }
551
+ function buildNextSteps(options) {
552
+ const cliSpecifier = getWpTypiaCliSpecifier();
553
+ const syncTypesRun = formatRunScript(options.packageManager, "sync-types");
554
+ const syncRun = formatRunScript(options.packageManager, "sync");
555
+ const doctorRun = formatPackageExecCommand(options.packageManager, cliSpecifier, "doctor");
556
+ const migrationInitRun = formatPackageExecCommand(options.packageManager, cliSpecifier, "migrate init --current-migration-version v1");
557
+ const dependencyInstallCommand = formatAddDevDependenciesCommand(options.packageManager, buildRequiredDevDependencyMapEntries());
558
+ if (options.layoutKind === "unsupported") {
559
+ return [
560
+ "Align the project to one of the supported retrofit layouts listed below, then rerun `wp-typia init`.",
561
+ dependencyInstallCommand,
562
+ syncTypesRun,
563
+ doctorRun
564
+ ];
565
+ }
566
+ if (options.commandMode === "apply") {
567
+ return [
568
+ ...options.dependencyChangeCount > 0 ? [
569
+ "Install or reinstall project dependencies so the retrofit sync scripts and metadata generators are available locally.",
570
+ dependencyInstallCommand
571
+ ] : [],
572
+ syncRun,
573
+ doctorRun,
574
+ `Optional migration bootstrap: ${migrationInitRun}`
575
+ ];
576
+ }
577
+ const steps = [
578
+ ...options.hasPlannedChanges ? [
579
+ "Re-run `wp-typia init --apply` to write the planned package.json changes and helper files automatically.",
580
+ ...options.dependencyChangeCount > 0 ? [dependencyInstallCommand] : []
581
+ ] : [],
582
+ syncRun,
583
+ doctorRun,
584
+ `Optional migration bootstrap: ${migrationInitRun}`
585
+ ];
586
+ return steps;
587
+ }
588
+ function buildRequiredDevDependencyMapEntries() {
589
+ return Object.entries(buildRequiredDevDependencyMap()).map(([name, version]) => `${name}@${version.replace(/^workspace:/u, "")}`);
590
+ }
591
+ function buildRetrofitPlanSummary(options) {
592
+ if (options.status === "already-initialized") {
593
+ return options.commandMode === "apply" ? "This project already exposes the minimum wp-typia retrofit surface. No files were changed." : "This project already exposes the minimum wp-typia retrofit surface.";
594
+ }
595
+ if (options.commandMode === "apply") {
596
+ return "Applied the minimum wp-typia retrofit surface so package.json and helper scripts are ready for the next install and sync run.";
597
+ }
598
+ return "This command previews the minimum wp-typia adoption layer for the current project without rewriting it into a full scaffold.";
599
+ }
600
+ function createRetrofitPlan(options) {
601
+ const includeGeneratedArtifacts = options.commandMode === "preview-only";
602
+ const plannedChanges = buildChangeSummary({
603
+ generatedArtifacts: options.generatedArtifacts,
604
+ packageChanges: options.packageChanges,
605
+ plannedFiles: options.plannedFiles
606
+ }, {
607
+ includeGeneratedArtifacts
608
+ });
609
+ return {
610
+ blockTargets: options.blockTargets,
611
+ commandMode: options.commandMode,
612
+ detectedLayout: options.detectedLayout,
613
+ generatedArtifacts: options.generatedArtifacts,
614
+ nextSteps: options.nextSteps ?? buildNextSteps({
615
+ commandMode: options.commandMode,
616
+ dependencyChangeCount: options.packageChanges.addDevDependencies.length,
617
+ hasPlannedChanges: plannedChanges.length > 0,
618
+ layoutKind: options.detectedLayout.kind,
619
+ packageManager: options.packageManager
620
+ }),
621
+ notes: options.notes,
622
+ packageChanges: options.packageChanges,
623
+ plannedFiles: options.plannedFiles,
624
+ packageManager: options.packageManager,
625
+ projectDir: options.projectDir,
626
+ projectName: options.projectName,
627
+ status: options.status,
628
+ summary: buildRetrofitPlanSummary({
629
+ commandMode: options.commandMode,
630
+ status: options.status
631
+ })
632
+ };
633
+ }
634
+ function setDependencyVersion(packageJson, name, requiredValue) {
635
+ if (packageJson.devDependencies?.[name] !== undefined) {
636
+ packageJson.devDependencies[name] = requiredValue;
637
+ return;
638
+ }
639
+ if (packageJson.dependencies?.[name] !== undefined) {
640
+ packageJson.dependencies[name] = requiredValue;
641
+ return;
642
+ }
643
+ packageJson.devDependencies ??= {};
644
+ packageJson.devDependencies[name] = requiredValue;
645
+ }
646
+ function buildNextProjectPackageJson(options) {
647
+ const nextPackageJson = options.packageJson ? JSON.parse(JSON.stringify(options.packageJson)) : {
648
+ name: options.projectName,
649
+ private: true
650
+ };
651
+ nextPackageJson.devDependencies ??= {};
652
+ nextPackageJson.scripts ??= {};
653
+ for (const dependencyChange of options.packageChanges.addDevDependencies) {
654
+ setDependencyVersion(nextPackageJson, dependencyChange.name, dependencyChange.requiredValue);
655
+ }
656
+ if (options.packageChanges.packageManagerField) {
657
+ nextPackageJson.packageManager = options.packageChanges.packageManagerField.requiredValue;
658
+ } else if (!nextPackageJson.packageManager && options.packageManager !== "npm") {
659
+ nextPackageJson.packageManager = getPackageManager(options.packageManager).packageManagerField;
660
+ }
661
+ for (const scriptChange of options.packageChanges.scripts) {
662
+ nextPackageJson.scripts[scriptChange.name] = scriptChange.requiredValue;
663
+ }
664
+ return nextPackageJson;
665
+ }
666
+ function buildProjectPackageJsonSource(packageJson) {
667
+ return `${JSON.stringify(packageJson, null, 2)}
668
+ `;
669
+ }
670
+ function buildRetrofitHelperFiles(blockTargets) {
671
+ return {
672
+ [path.join("scripts", "block-config.ts")]: buildRetrofitBlockConfigSource(blockTargets),
673
+ [path.join("scripts", "sync-project.ts")]: buildRetrofitSyncProjectScriptSource(),
674
+ [path.join("scripts", "sync-types-to-block-json.ts")]: buildRetrofitSyncTypesScriptSource()
675
+ };
676
+ }
677
+ async function createRetrofitMutationSnapshot(projectDir, filePaths) {
678
+ const scriptsDir = path.join(projectDir, "scripts");
679
+ const scriptsDirExisted = fs.existsSync(scriptsDir);
680
+ const fileSources = await snapshotWorkspaceFiles(filePaths);
681
+ const targetPaths = fileSources.filter((entry) => entry.source === null).map((entry) => entry.filePath);
682
+ if (!scriptsDirExisted) {
683
+ targetPaths.push(scriptsDir);
684
+ }
685
+ return {
686
+ fileSources,
687
+ snapshotDirs: [],
688
+ targetPaths
689
+ };
690
+ }
691
+ async function writeRetrofitFiles(options) {
692
+ const helperFiles = buildRetrofitHelperFiles(options.blockTargets);
693
+ const scriptsDir = path.join(options.projectDir, "scripts");
694
+ await fsp.mkdir(scriptsDir, { recursive: true });
695
+ await fsp.writeFile(path.join(options.projectDir, "package.json"), buildProjectPackageJsonSource(options.packageJson), "utf8");
696
+ for (const [relativePath, source] of Object.entries(helperFiles)) {
697
+ await fsp.writeFile(path.join(options.projectDir, relativePath), source, "utf8");
698
+ }
699
+ }
700
+ function buildApplyFailureError(error) {
701
+ const message = error instanceof Error ? error.message : String(error);
702
+ return createCliDiagnosticCodeError(CLI_DIAGNOSTIC_CODES.INVALID_ARGUMENT, `Unable to apply the retrofit init plan safely. The command restored the previous package.json/helper-file snapshot. ${message}`, error instanceof Error ? { cause: error } : undefined);
703
+ }
704
+ function getInitPlan(projectDir, options = {}) {
705
+ const resolvedProjectDir = path.resolve(projectDir);
706
+ const packageJson = readProjectPackageJson(resolvedProjectDir);
707
+ const packageManager = resolveInitPackageManager(resolvedProjectDir, packageJson, options.packageManager);
708
+ const workspace = tryResolveWorkspaceProject(resolvedProjectDir);
709
+ if (workspace) {
710
+ const workspacePackageJson = readProjectPackageJson(workspace.projectDir);
711
+ const workspacePackageManager = resolveInitPackageManager(workspace.projectDir, workspacePackageJson, options.packageManager);
712
+ const cliSpecifier = getWpTypiaCliSpecifier();
713
+ return createRetrofitPlan({
714
+ blockTargets: [],
715
+ commandMode: "preview-only",
716
+ detectedLayout: {
717
+ blockNames: [],
718
+ description: "Already an official wp-typia workspace.",
719
+ kind: "official-workspace"
720
+ },
721
+ generatedArtifacts: [],
722
+ nextSteps: [
723
+ "Use `wp-typia add <kind> <name>` to extend the official workspace instead of rerunning init.",
724
+ formatRunScript(workspacePackageManager, "sync"),
725
+ formatPackageExecCommand(workspacePackageManager, cliSpecifier, "doctor")
726
+ ],
727
+ notes: [
728
+ "The official workspace template already owns inventory, doctor, and add-command workflows."
729
+ ],
730
+ packageChanges: {
731
+ addDevDependencies: [],
732
+ scripts: []
733
+ },
734
+ packageManager: workspacePackageManager,
735
+ plannedFiles: [],
736
+ projectDir: workspace.projectDir,
737
+ projectName: workspace.packageName,
738
+ status: "already-initialized"
739
+ });
740
+ }
741
+ const projectName = typeof packageJson?.name === "string" && packageJson.name.length > 0 ? packageJson.name : path.basename(resolvedProjectDir);
742
+ const layout = buildLayoutDetails(resolvedProjectDir);
743
+ const dependencyChanges = buildDependencyChanges(packageJson);
744
+ const scriptChanges = buildScriptChanges(packageJson, packageManager);
745
+ const packageManagerFieldChange = buildPackageManagerFieldChange(packageJson, packageManager, {
746
+ persistExplicitOverride: typeof options.packageManager === "string"
747
+ });
748
+ const rawPlannedFiles = layout.kind === "generated-project" || layout.kind === "official-workspace" ? [] : buildPlannedFiles(resolvedProjectDir, layout.kind);
749
+ const hasExistingSurface = hasExistingWpTypiaProjectSurface(resolvedProjectDir, packageJson);
750
+ const status = hasExistingSurface && dependencyChanges.length === 0 && scriptChanges.length === 0 && packageManagerFieldChange === undefined ? "already-initialized" : "preview";
751
+ const plannedFiles = status === "already-initialized" ? [] : rawPlannedFiles;
752
+ const detectedLayout = status === "already-initialized" && hasExistingSurface ? {
753
+ blockNames: layout.blockNames,
754
+ description: layout.kind === "unsupported" ? "Already exposes the minimum wp-typia sync surface." : `Already exposes the minimum wp-typia sync surface for ${layout.kind === "multi-block" ? "a multi-block project" : "a single-block project"}.`,
755
+ kind: "generated-project"
756
+ } : {
757
+ blockNames: layout.blockNames,
758
+ description: layout.description,
759
+ kind: layout.kind
760
+ };
761
+ return createRetrofitPlan({
762
+ blockTargets: layout.blockTargets,
763
+ commandMode: "preview-only",
764
+ detectedLayout,
765
+ generatedArtifacts: status === "already-initialized" && detectedLayout.kind === "generated-project" ? [] : layout.generatedArtifacts,
766
+ notes: Array.from(new Set([
767
+ "Preview only: `wp-typia init` does not write files yet.",
768
+ RETROFIT_APPLY_PREVIEW_NOTE,
769
+ ...layout.notes
770
+ ])),
771
+ packageChanges: {
772
+ addDevDependencies: dependencyChanges,
773
+ ...packageManagerFieldChange ? { packageManagerField: packageManagerFieldChange } : {},
774
+ scripts: scriptChanges
775
+ },
776
+ packageManager,
777
+ plannedFiles,
778
+ projectDir: resolvedProjectDir,
779
+ projectName,
780
+ status
781
+ });
782
+ }
783
+ async function applyInitPlan(projectDir, options = {}) {
784
+ const previewPlan = getInitPlan(projectDir, options);
785
+ if (previewPlan.detectedLayout.kind === "unsupported") {
786
+ throw createCliDiagnosticCodeError(CLI_DIAGNOSTIC_CODES.INVALID_ARGUMENT, "`wp-typia init --apply` requires a supported retrofit layout. Run `wp-typia init` first to inspect the preview plan and any blocking notes.");
787
+ }
788
+ if (previewPlan.status === "already-initialized") {
789
+ return createRetrofitPlan({
790
+ ...previewPlan,
791
+ commandMode: "apply",
792
+ notes: Array.from(new Set([
793
+ ...previewPlan.notes.filter((note) => note !== "Preview only: `wp-typia init` does not write files yet." && note !== RETROFIT_APPLY_PREVIEW_NOTE),
794
+ RETROFIT_ROLLBACK_NOTE
795
+ ])),
796
+ status: "already-initialized"
797
+ });
798
+ }
799
+ const nextPackageJson = buildNextProjectPackageJson({
800
+ packageChanges: previewPlan.packageChanges,
801
+ packageJson: readProjectPackageJson(previewPlan.projectDir),
802
+ packageManager: previewPlan.packageManager,
803
+ projectName: previewPlan.projectName
804
+ });
805
+ const helperFiles = buildRetrofitHelperFiles(previewPlan.blockTargets);
806
+ const filePaths = [
807
+ path.join(previewPlan.projectDir, "package.json"),
808
+ ...Object.keys(helperFiles).map((relativePath) => path.join(previewPlan.projectDir, relativePath))
809
+ ];
810
+ const mutationSnapshot = await createRetrofitMutationSnapshot(previewPlan.projectDir, filePaths);
811
+ try {
812
+ await writeRetrofitFiles({
813
+ blockTargets: previewPlan.blockTargets,
814
+ packageJson: nextPackageJson,
815
+ projectDir: previewPlan.projectDir
816
+ });
817
+ } catch (error) {
818
+ await rollbackWorkspaceMutation(mutationSnapshot);
819
+ throw buildApplyFailureError(error);
820
+ }
821
+ return createRetrofitPlan({
822
+ ...previewPlan,
823
+ commandMode: "apply",
824
+ notes: Array.from(new Set([
825
+ ...previewPlan.notes.filter((note) => note !== "Preview only: `wp-typia init` does not write files yet." && note !== RETROFIT_APPLY_PREVIEW_NOTE),
826
+ RETROFIT_ROLLBACK_NOTE
827
+ ])),
828
+ status: "applied"
829
+ });
830
+ }
831
+ async function runInitCommand(options) {
832
+ return options.apply ? applyInitPlan(options.projectDir, {
833
+ packageManager: options.packageManager
834
+ }) : getInitPlan(options.projectDir, {
835
+ packageManager: options.packageManager
836
+ });
837
+ }
838
+ export {
839
+ runInitCommand,
840
+ getInitPlan,
841
+ applyInitPlan
842
+ };
843
+
844
+ //# debugId=5F125D72345735E864756E2164756E21