@wp-typia/project-tools 0.22.5 → 0.22.7

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 (69) hide show
  1. package/dist/runtime/ai-feature-capability.js +20 -0
  2. package/dist/runtime/cli-add-block-json.d.ts +2 -2
  3. package/dist/runtime/cli-add-block-json.js +5 -4
  4. package/dist/runtime/cli-add-block.js +16 -11
  5. package/dist/runtime/cli-add-collision.js +213 -136
  6. package/dist/runtime/cli-add-help.js +1 -1
  7. package/dist/runtime/cli-add-kind-ids.d.ts +11 -0
  8. package/dist/runtime/cli-add-kind-ids.js +20 -0
  9. package/dist/runtime/cli-add-types.d.ts +2 -8
  10. package/dist/runtime/cli-add-types.js +1 -17
  11. package/dist/runtime/cli-add-workspace-ability-scaffold.d.ts +3 -1
  12. package/dist/runtime/cli-add-workspace-ability-scaffold.js +22 -5
  13. package/dist/runtime/cli-add-workspace-ability.d.ts +4 -0
  14. package/dist/runtime/cli-add-workspace-ability.js +5 -1
  15. package/dist/runtime/cli-add-workspace-admin-view-source.d.ts +7 -0
  16. package/dist/runtime/cli-add-workspace-admin-view-source.js +9 -10
  17. package/dist/runtime/cli-add-workspace-admin-view-types.d.ts +0 -2
  18. package/dist/runtime/cli-add-workspace-admin-view-types.js +0 -3
  19. package/dist/runtime/cli-add-workspace-ai-scaffold.js +14 -6
  20. package/dist/runtime/cli-add-workspace.js +8 -11
  21. package/dist/runtime/cli-doctor-workspace-bindings.js +2 -3
  22. package/dist/runtime/cli-doctor-workspace-blocks.js +2 -3
  23. package/dist/runtime/cli-doctor-workspace-features.js +6 -11
  24. package/dist/runtime/cli-doctor-workspace-shared.d.ts +8 -0
  25. package/dist/runtime/cli-doctor-workspace-shared.js +10 -0
  26. package/dist/runtime/cli-help.js +1 -1
  27. package/dist/runtime/cli-init-apply.d.ts +15 -0
  28. package/dist/runtime/cli-init-apply.js +99 -0
  29. package/dist/runtime/cli-init-package-json.d.ts +19 -0
  30. package/dist/runtime/cli-init-package-json.js +191 -0
  31. package/dist/runtime/cli-init-plan-presentation.d.ts +16 -0
  32. package/dist/runtime/cli-init-plan-presentation.js +74 -0
  33. package/dist/runtime/cli-init-plan.d.ts +39 -0
  34. package/dist/runtime/cli-init-plan.js +303 -0
  35. package/dist/runtime/cli-init-templates.d.ts +27 -0
  36. package/dist/runtime/cli-init-templates.js +244 -0
  37. package/dist/runtime/cli-init-types.d.ts +84 -0
  38. package/dist/runtime/cli-init-types.js +3 -0
  39. package/dist/runtime/cli-init.d.ts +4 -100
  40. package/dist/runtime/cli-init.js +6 -878
  41. package/dist/runtime/fs-async.d.ts +28 -0
  42. package/dist/runtime/fs-async.js +53 -0
  43. package/dist/runtime/package-managers.js +1 -1
  44. package/dist/runtime/package-versions.d.ts +1 -1
  45. package/dist/runtime/package-versions.js +1 -1
  46. package/dist/runtime/php-utils.d.ts +16 -0
  47. package/dist/runtime/php-utils.js +258 -1
  48. package/dist/runtime/scaffold-apply-utils.js +10 -20
  49. package/dist/runtime/scaffold-bootstrap.js +6 -8
  50. package/dist/runtime/scaffold-compatibility.d.ts +15 -3
  51. package/dist/runtime/scaffold-compatibility.js +42 -11
  52. package/dist/runtime/scaffold-documents.js +12 -0
  53. package/dist/runtime/scaffold-package-manager-files.js +4 -3
  54. package/dist/runtime/string-case.d.ts +5 -0
  55. package/dist/runtime/string-case.js +54 -2
  56. package/dist/runtime/template-source-cache.d.ts +19 -0
  57. package/dist/runtime/template-source-cache.js +164 -28
  58. package/dist/runtime/template-source-external.d.ts +7 -0
  59. package/dist/runtime/template-source-external.js +22 -5
  60. package/dist/runtime/template-source-normalization.d.ts +1 -1
  61. package/dist/runtime/template-source-normalization.js +12 -12
  62. package/dist/runtime/template-source-remote.d.ts +14 -0
  63. package/dist/runtime/template-source-remote.js +91 -15
  64. package/dist/runtime/template-source.js +35 -25
  65. package/dist/runtime/typia-llm.js +7 -0
  66. package/dist/runtime/version-floor.js +8 -2
  67. package/dist/runtime/workspace-inventory.d.ts +16 -14
  68. package/dist/runtime/workspace-inventory.js +58 -14
  69. package/package.json +6 -1
@@ -0,0 +1,191 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { CLI_DIAGNOSTIC_CODES, createCliDiagnosticCodeError, } from "./cli-diagnostics.js";
4
+ import { getPackageManager, transformPackageManagerText, } from "./package-managers.js";
5
+ import { getPackageVersions } from "./package-versions.js";
6
+ import { parseWorkspacePackageManagerId } from "./workspace-project.js";
7
+ const BASE_RETROFIT_SCRIPTS = {
8
+ sync: "tsx scripts/sync-project.ts",
9
+ "sync-types": "tsx scripts/sync-types-to-block-json.ts",
10
+ typecheck: "bun run sync --check && tsc --noEmit",
11
+ };
12
+ const BASE_RETROFIT_DEV_DEPENDENCIES = [
13
+ "@typia/unplugin",
14
+ "@wp-typia/block-runtime",
15
+ "@wp-typia/block-types",
16
+ "tsx",
17
+ "typescript",
18
+ "typia",
19
+ ];
20
+ export function readProjectPackageJson(projectDir) {
21
+ const packageJsonPath = path.join(projectDir, "package.json");
22
+ if (!fs.existsSync(packageJsonPath)) {
23
+ return null;
24
+ }
25
+ const source = fs.readFileSync(packageJsonPath, "utf8");
26
+ try {
27
+ return JSON.parse(source);
28
+ }
29
+ catch (error) {
30
+ const message = error instanceof Error ? error.message : String(error);
31
+ throw createCliDiagnosticCodeError(CLI_DIAGNOSTIC_CODES.INVALID_ARGUMENT, `Unable to parse ${packageJsonPath}: ${message}`, error instanceof Error ? { cause: error } : undefined);
32
+ }
33
+ }
34
+ function inferInitPackageManager(projectDir, packageJson) {
35
+ if (packageJson?.packageManager) {
36
+ return parseWorkspacePackageManagerId(packageJson.packageManager);
37
+ }
38
+ if (fs.existsSync(path.join(projectDir, "bun.lock")) ||
39
+ fs.existsSync(path.join(projectDir, "bun.lockb"))) {
40
+ return "bun";
41
+ }
42
+ if (fs.existsSync(path.join(projectDir, "pnpm-lock.yaml"))) {
43
+ return "pnpm";
44
+ }
45
+ if (fs.existsSync(path.join(projectDir, "yarn.lock")) ||
46
+ fs.existsSync(path.join(projectDir, ".yarnrc.yml"))) {
47
+ return "yarn";
48
+ }
49
+ return "npm";
50
+ }
51
+ export function resolveInitPackageManager(projectDir, packageJson, override) {
52
+ if (!override) {
53
+ return inferInitPackageManager(projectDir, packageJson);
54
+ }
55
+ if (override !== "bun" &&
56
+ override !== "npm" &&
57
+ override !== "pnpm" &&
58
+ override !== "yarn") {
59
+ throw createCliDiagnosticCodeError(CLI_DIAGNOSTIC_CODES.INVALID_ARGUMENT, `Unknown package manager "${override}". Expected one of: bun, npm, pnpm, yarn.`);
60
+ }
61
+ return override;
62
+ }
63
+ export function getWpTypiaCliSpecifier() {
64
+ const versions = getPackageVersions();
65
+ return versions.wpTypiaPackageExactVersion === "0.0.0"
66
+ ? "wp-typia"
67
+ : `wp-typia@${versions.wpTypiaPackageExactVersion}`;
68
+ }
69
+ function buildRequiredDevDependencyMap() {
70
+ const versions = getPackageVersions();
71
+ return {
72
+ "@typia/unplugin": versions.typiaUnpluginPackageVersion,
73
+ "@wp-typia/block-runtime": versions.blockRuntimePackageVersion,
74
+ "@wp-typia/block-types": versions.blockTypesPackageVersion,
75
+ tsx: versions.tsxPackageVersion,
76
+ typescript: versions.typescriptPackageVersion,
77
+ typia: versions.typiaPackageVersion,
78
+ };
79
+ }
80
+ function getExistingDependencyVersion(packageJson, name) {
81
+ return packageJson?.devDependencies?.[name] ?? packageJson?.dependencies?.[name];
82
+ }
83
+ export function buildDependencyChanges(packageJson) {
84
+ const requiredDependencies = buildRequiredDevDependencyMap();
85
+ return BASE_RETROFIT_DEV_DEPENDENCIES.flatMap((name) => {
86
+ const requiredValue = requiredDependencies[name];
87
+ const currentValue = getExistingDependencyVersion(packageJson, name);
88
+ if (currentValue === requiredValue) {
89
+ return [];
90
+ }
91
+ return [
92
+ {
93
+ action: currentValue ? "update" : "add",
94
+ ...(currentValue ? { currentValue } : {}),
95
+ name,
96
+ requiredValue,
97
+ },
98
+ ];
99
+ });
100
+ }
101
+ export function buildScriptChanges(packageJson, packageManager) {
102
+ const scripts = packageJson?.scripts ?? {};
103
+ return Object.entries(BASE_RETROFIT_SCRIPTS).flatMap(([name, commandSource]) => {
104
+ const requiredValue = transformPackageManagerText(commandSource, packageManager);
105
+ const currentValue = scripts[name];
106
+ if (currentValue === requiredValue) {
107
+ return [];
108
+ }
109
+ return [
110
+ {
111
+ action: typeof currentValue === "string" ? "update" : "add",
112
+ ...(typeof currentValue === "string" ? { currentValue } : {}),
113
+ name,
114
+ requiredValue,
115
+ },
116
+ ];
117
+ });
118
+ }
119
+ export function buildPackageManagerFieldChange(packageJson, packageManager, options = {}) {
120
+ if (!options.persistExplicitOverride && packageManager === "npm") {
121
+ return undefined;
122
+ }
123
+ const requiredValue = getPackageManager(packageManager).packageManagerField;
124
+ const currentValue = packageJson?.packageManager;
125
+ if (currentValue === requiredValue) {
126
+ return undefined;
127
+ }
128
+ return {
129
+ action: typeof currentValue === "string" ? "update" : "add",
130
+ ...(typeof currentValue === "string" ? { currentValue } : {}),
131
+ requiredValue,
132
+ };
133
+ }
134
+ export function hasExistingWpTypiaProjectSurface(projectDir, packageJson) {
135
+ const scripts = packageJson?.scripts ?? {};
136
+ const hasSyncSurface = typeof scripts.sync === "string" || typeof scripts["sync-types"] === "string";
137
+ const hasHelperFiles = [
138
+ path.join("scripts", "block-config.ts"),
139
+ path.join("scripts", "sync-project.ts"),
140
+ path.join("scripts", "sync-types-to-block-json.ts"),
141
+ ].every((relativePath) => fs.existsSync(path.join(projectDir, relativePath)));
142
+ const hasRuntimeDeps = typeof getExistingDependencyVersion(packageJson, "@wp-typia/block-runtime") ===
143
+ "string" &&
144
+ typeof getExistingDependencyVersion(packageJson, "@wp-typia/block-types") ===
145
+ "string";
146
+ return hasSyncSurface && hasHelperFiles && hasRuntimeDeps;
147
+ }
148
+ export function buildRequiredDevDependencyMapEntries() {
149
+ return Object.entries(buildRequiredDevDependencyMap()).map(([name, version]) => `${name}@${version.replace(/^workspace:/u, "")}`);
150
+ }
151
+ function setDependencyVersion(packageJson, name, requiredValue) {
152
+ if (packageJson.devDependencies?.[name] !== undefined) {
153
+ packageJson.devDependencies[name] = requiredValue;
154
+ return;
155
+ }
156
+ if (packageJson.dependencies?.[name] !== undefined) {
157
+ packageJson.dependencies[name] = requiredValue;
158
+ return;
159
+ }
160
+ packageJson.devDependencies ?? (packageJson.devDependencies = {});
161
+ packageJson.devDependencies[name] = requiredValue;
162
+ }
163
+ export function buildNextProjectPackageJson(options) {
164
+ const nextPackageJson = options.packageJson
165
+ ? JSON.parse(JSON.stringify(options.packageJson))
166
+ : {
167
+ name: options.projectName,
168
+ private: true,
169
+ };
170
+ nextPackageJson.devDependencies ?? (nextPackageJson.devDependencies = {});
171
+ nextPackageJson.scripts ?? (nextPackageJson.scripts = {});
172
+ for (const dependencyChange of options.packageChanges.addDevDependencies) {
173
+ setDependencyVersion(nextPackageJson, dependencyChange.name, dependencyChange.requiredValue);
174
+ }
175
+ if (options.packageChanges.packageManagerField) {
176
+ nextPackageJson.packageManager =
177
+ options.packageChanges.packageManagerField.requiredValue;
178
+ }
179
+ else if (!nextPackageJson.packageManager &&
180
+ options.packageManager !== "npm") {
181
+ nextPackageJson.packageManager =
182
+ getPackageManager(options.packageManager).packageManagerField;
183
+ }
184
+ for (const scriptChange of options.packageChanges.scripts) {
185
+ nextPackageJson.scripts[scriptChange.name] = scriptChange.requiredValue;
186
+ }
187
+ return nextPackageJson;
188
+ }
189
+ export function buildProjectPackageJsonSource(packageJson) {
190
+ return `${JSON.stringify(packageJson, null, 2)}\n`;
191
+ }
@@ -0,0 +1,16 @@
1
+ import { type PackageManagerId } from "./package-managers.js";
2
+ import type { InitCommandMode, InitPlanLayoutKind, InitPlanStatus, RetrofitInitPlan } from "./cli-init-types.js";
3
+ export declare function buildInitPlanChangeSummary(changes: Pick<RetrofitInitPlan, "generatedArtifacts" | "packageChanges" | "plannedFiles">, options: {
4
+ includeGeneratedArtifacts: boolean;
5
+ }): string[];
6
+ export declare function buildInitPlanNextSteps(options: {
7
+ commandMode: InitCommandMode;
8
+ dependencyChangeCount: number;
9
+ hasPlannedChanges: boolean;
10
+ layoutKind: InitPlanLayoutKind;
11
+ packageManager: PackageManagerId;
12
+ }): string[];
13
+ export declare function buildRetrofitPlanSummary(options: {
14
+ commandMode: InitCommandMode;
15
+ status: InitPlanStatus;
16
+ }): string;
@@ -0,0 +1,74 @@
1
+ import { formatAddDevDependenciesCommand, formatPackageExecCommand, formatRunScript, } from "./package-managers.js";
2
+ import { buildRequiredDevDependencyMapEntries, getWpTypiaCliSpecifier, } from "./cli-init-package-json.js";
3
+ export function buildInitPlanChangeSummary(changes, options) {
4
+ const lines = [];
5
+ for (const dependencyChange of changes.packageChanges.addDevDependencies) {
6
+ lines.push(`devDependency ${dependencyChange.action} ${dependencyChange.name} -> ${dependencyChange.requiredValue}`);
7
+ }
8
+ if (changes.packageChanges.packageManagerField) {
9
+ lines.push(`packageManager ${changes.packageChanges.packageManagerField.action} -> ${changes.packageChanges.packageManagerField.requiredValue}`);
10
+ }
11
+ for (const scriptChange of changes.packageChanges.scripts) {
12
+ lines.push(`script ${scriptChange.action} ${scriptChange.name} -> ${scriptChange.requiredValue}`);
13
+ }
14
+ for (const filePlan of changes.plannedFiles) {
15
+ lines.push(`file ${filePlan.action} ${filePlan.path} (${filePlan.purpose})`);
16
+ }
17
+ if (options.includeGeneratedArtifacts) {
18
+ for (const artifactPath of changes.generatedArtifacts) {
19
+ lines.push(`generated artifact ${artifactPath}`);
20
+ }
21
+ }
22
+ return lines;
23
+ }
24
+ export function buildInitPlanNextSteps(options) {
25
+ const cliSpecifier = getWpTypiaCliSpecifier();
26
+ const syncTypesRun = formatRunScript(options.packageManager, "sync-types");
27
+ const syncRun = formatRunScript(options.packageManager, "sync");
28
+ const doctorRun = formatPackageExecCommand(options.packageManager, cliSpecifier, "doctor");
29
+ const migrationInitRun = formatPackageExecCommand(options.packageManager, cliSpecifier, "migrate init --current-migration-version v1");
30
+ const dependencyInstallCommand = formatAddDevDependenciesCommand(options.packageManager, buildRequiredDevDependencyMapEntries());
31
+ if (options.layoutKind === "unsupported") {
32
+ return [
33
+ "Align the project to one of the supported retrofit layouts listed below, then rerun `wp-typia init`.",
34
+ dependencyInstallCommand,
35
+ syncTypesRun,
36
+ doctorRun,
37
+ ];
38
+ }
39
+ if (options.commandMode === "apply") {
40
+ return [
41
+ ...(options.dependencyChangeCount > 0
42
+ ? [
43
+ "Install or reinstall project dependencies so the retrofit sync scripts and metadata generators are available locally.",
44
+ dependencyInstallCommand,
45
+ ]
46
+ : []),
47
+ syncRun,
48
+ doctorRun,
49
+ `Optional migration bootstrap: ${migrationInitRun}`,
50
+ ];
51
+ }
52
+ return [
53
+ ...(options.hasPlannedChanges
54
+ ? [
55
+ "Re-run `wp-typia init --apply` to write the planned package.json changes and helper files automatically.",
56
+ ...(options.dependencyChangeCount > 0 ? [dependencyInstallCommand] : []),
57
+ ]
58
+ : []),
59
+ syncRun,
60
+ doctorRun,
61
+ `Optional migration bootstrap: ${migrationInitRun}`,
62
+ ];
63
+ }
64
+ export function buildRetrofitPlanSummary(options) {
65
+ if (options.status === "already-initialized") {
66
+ return options.commandMode === "apply"
67
+ ? "This project already exposes the minimum wp-typia retrofit surface. No files were changed."
68
+ : "This project already exposes the minimum wp-typia retrofit surface.";
69
+ }
70
+ if (options.commandMode === "apply") {
71
+ return "Applied the minimum wp-typia retrofit surface so package.json and helper scripts are ready for the next install and sync run.";
72
+ }
73
+ return "This command previews the minimum wp-typia adoption layer for the current project without rewriting it into a full scaffold.";
74
+ }
@@ -0,0 +1,39 @@
1
+ import { type PackageManagerId } from "./package-managers.js";
2
+ import { type InitCommandMode, type InitFilePlan, type InitPlanLayoutKind, type InitPlanStatus, type RetrofitInitBlockTarget, type RetrofitInitPlan } from "./cli-init-types.js";
3
+ export declare function buildInitLayoutDetails(projectDir: string): {
4
+ blockNames: string[];
5
+ blockTargets: RetrofitInitBlockTarget[];
6
+ description: string;
7
+ generatedArtifacts: string[];
8
+ kind: InitPlanLayoutKind;
9
+ notes: string[];
10
+ };
11
+ export declare function createRetrofitPlan(options: {
12
+ commandMode: InitCommandMode;
13
+ detectedLayout: {
14
+ blockNames: string[];
15
+ description: string;
16
+ kind: InitPlanLayoutKind;
17
+ };
18
+ blockTargets: RetrofitInitBlockTarget[];
19
+ generatedArtifacts: string[];
20
+ nextSteps?: string[];
21
+ notes: string[];
22
+ packageChanges: RetrofitInitPlan["packageChanges"];
23
+ packageManager: PackageManagerId;
24
+ plannedFiles: InitFilePlan[];
25
+ projectDir: string;
26
+ projectName: string;
27
+ status: InitPlanStatus;
28
+ }): RetrofitInitPlan;
29
+ /**
30
+ * Inspect one project directory and return the current retrofit init plan.
31
+ *
32
+ * @param projectDir Project root or nested path that should be analyzed.
33
+ * @param options Optional package-manager override used for emitted scripts and
34
+ * follow-up guidance.
35
+ * @returns The preview-only retrofit init plan for the resolved project.
36
+ */
37
+ export declare function getInitPlan(projectDir: string, options?: {
38
+ packageManager?: string;
39
+ }): RetrofitInitPlan;
@@ -0,0 +1,303 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { analyzeSourceTypes } from "@wp-typia/block-runtime/metadata-parser";
4
+ import ts from "typescript";
5
+ import { discoverMigrationInitLayout } from "./migration-project.js";
6
+ import { formatPackageExecCommand, formatRunScript } from "./package-managers.js";
7
+ import { toPascalCase } from "./string-case.js";
8
+ import { buildDependencyChanges, buildPackageManagerFieldChange, buildScriptChanges, getWpTypiaCliSpecifier, hasExistingWpTypiaProjectSurface, readProjectPackageJson, resolveInitPackageManager, } from "./cli-init-package-json.js";
9
+ import { buildInitPlanChangeSummary, buildInitPlanNextSteps, buildRetrofitPlanSummary, } from "./cli-init-plan-presentation.js";
10
+ import { RETROFIT_APPLY_PREVIEW_NOTE, SUPPORTED_RETROFIT_LAYOUT_NOTE, } from "./cli-init-types.js";
11
+ import { tryResolveWorkspaceProject } from "./workspace-project.js";
12
+ function normalizeRelativePath(value) {
13
+ return value.replace(/\\/gu, "/");
14
+ }
15
+ function buildGeneratedArtifactPaths(blockJsonFile, manifestFile) {
16
+ const manifestDir = path.dirname(manifestFile);
17
+ const artifactPaths = [
18
+ blockJsonFile,
19
+ manifestFile,
20
+ path.join(manifestDir, "typia.schema.json"),
21
+ path.join(manifestDir, "typia-validator.php"),
22
+ path.join(manifestDir, "typia.openapi.json"),
23
+ ];
24
+ return Array.from(new Set(artifactPaths.map((filePath) => normalizeRelativePath(filePath))));
25
+ }
26
+ function collectNamedSourceTypeCandidates(typesSource) {
27
+ const sourceFile = ts.createSourceFile("types.ts", typesSource, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
28
+ return sourceFile.statements.flatMap((statement) => {
29
+ if (ts.isInterfaceDeclaration(statement) ||
30
+ ts.isTypeAliasDeclaration(statement)) {
31
+ return [statement.name.text];
32
+ }
33
+ return [];
34
+ });
35
+ }
36
+ function isObjectLikeSourceType(projectDir, typesFile, sourceTypeName) {
37
+ const analyzedTypes = analyzeSourceTypes({
38
+ projectRoot: projectDir,
39
+ typesFile,
40
+ }, [sourceTypeName]);
41
+ return analyzedTypes[sourceTypeName]?.kind === "object";
42
+ }
43
+ function inferRetrofitAttributeTypeName(projectDir, block) {
44
+ const typesPath = path.join(projectDir, block.typesFile);
45
+ const typesSource = fs.readFileSync(typesPath, "utf8");
46
+ const blockNameSegments = block.blockName.split("/");
47
+ const slug = blockNameSegments[blockNameSegments.length - 1] ?? block.key;
48
+ const candidateNames = collectNamedSourceTypeCandidates(typesSource);
49
+ const validCandidates = candidateNames.filter((candidateName) => isObjectLikeSourceType(projectDir, block.typesFile, candidateName));
50
+ const preferredName = `${toPascalCase(slug)}Attributes`;
51
+ if (validCandidates.includes(preferredName)) {
52
+ return preferredName;
53
+ }
54
+ const attributeCandidates = validCandidates.filter((candidateName) => candidateName.endsWith("Attributes"));
55
+ if (attributeCandidates.length === 1) {
56
+ return attributeCandidates[0];
57
+ }
58
+ if (validCandidates.length === 1) {
59
+ return validCandidates[0];
60
+ }
61
+ if (validCandidates.length === 0) {
62
+ 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\`.`);
63
+ }
64
+ 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\`.`);
65
+ }
66
+ function buildRetrofitBlockTarget(projectDir, block) {
67
+ const blockNameSegments = block.blockName.split("/");
68
+ const slug = blockNameSegments[blockNameSegments.length - 1] ?? block.key;
69
+ return {
70
+ attributeTypeName: inferRetrofitAttributeTypeName(projectDir, block),
71
+ blockJsonFile: block.blockJsonFile,
72
+ blockName: block.blockName,
73
+ manifestFile: block.manifestFile,
74
+ saveFile: block.saveFile,
75
+ slug,
76
+ typesFile: block.typesFile,
77
+ };
78
+ }
79
+ export function buildInitLayoutDetails(projectDir) {
80
+ try {
81
+ const discoveredLayout = discoverMigrationInitLayout(projectDir);
82
+ const discoveredBlocks = discoveredLayout.mode === "multi"
83
+ ? discoveredLayout.blocks
84
+ : [discoveredLayout.block];
85
+ let blockTargets;
86
+ try {
87
+ blockTargets = discoveredBlocks.map((block) => buildRetrofitBlockTarget(projectDir, block));
88
+ }
89
+ catch (error) {
90
+ const message = error instanceof Error ? error.message : String(error);
91
+ return {
92
+ blockNames: discoveredBlocks.map((block) => block.blockName),
93
+ blockTargets: [],
94
+ description: "Detected supported block files, but could not infer retrofit block-config metadata automatically yet.",
95
+ generatedArtifacts: [],
96
+ kind: "unsupported",
97
+ notes: [message, SUPPORTED_RETROFIT_LAYOUT_NOTE],
98
+ };
99
+ }
100
+ if (discoveredLayout.mode === "multi") {
101
+ return {
102
+ blockNames: discoveredBlocks.map((block) => block.blockName),
103
+ blockTargets,
104
+ description: `Detected a supported multi-block retrofit candidate (${discoveredBlocks.length} targets).`,
105
+ generatedArtifacts: discoveredBlocks.flatMap((block) => buildGeneratedArtifactPaths(block.blockJsonFile, block.manifestFile)),
106
+ kind: "multi-block",
107
+ notes: [
108
+ "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.",
109
+ ],
110
+ };
111
+ }
112
+ return {
113
+ blockNames: [discoveredLayout.block.blockName],
114
+ blockTargets,
115
+ description: "Detected a supported single-block retrofit candidate.",
116
+ generatedArtifacts: buildGeneratedArtifactPaths(discoveredLayout.block.blockJsonFile, discoveredLayout.block.manifestFile),
117
+ kind: "single-block",
118
+ notes: discoveredLayout.block.blockJsonFile === "block.json"
119
+ ? [
120
+ "Legacy root `block.json` layouts are still supported for retrofit planning, but newer scaffolds keep generated block metadata under `src/`.",
121
+ ]
122
+ : [],
123
+ };
124
+ }
125
+ catch (error) {
126
+ const message = error instanceof Error ? error.message : String(error);
127
+ return {
128
+ blockNames: [],
129
+ blockTargets: [],
130
+ description: "No supported retrofit layout was auto-detected yet.",
131
+ generatedArtifacts: [],
132
+ kind: "unsupported",
133
+ notes: [message, SUPPORTED_RETROFIT_LAYOUT_NOTE],
134
+ };
135
+ }
136
+ }
137
+ function buildPlannedFiles(projectDir, layoutKind) {
138
+ if (layoutKind === "unsupported") {
139
+ return [];
140
+ }
141
+ return [
142
+ {
143
+ action: fs.existsSync(path.join(projectDir, "scripts", "block-config.ts"))
144
+ ? "update"
145
+ : "add",
146
+ path: "scripts/block-config.ts",
147
+ purpose: "Declare the current retrofit block targets so sync-types can regenerate metadata from the existing TypeScript source of truth.",
148
+ },
149
+ {
150
+ action: fs.existsSync(path.join(projectDir, "scripts", "sync-types-to-block-json.ts"))
151
+ ? "update"
152
+ : "add",
153
+ path: "scripts/sync-types-to-block-json.ts",
154
+ purpose: "Generate block.json and Typia metadata artifacts from the current TypeScript source of truth.",
155
+ },
156
+ {
157
+ action: fs.existsSync(path.join(projectDir, "scripts", "sync-project.ts"))
158
+ ? "update"
159
+ : "add",
160
+ path: "scripts/sync-project.ts",
161
+ purpose: "Provide one shared sync entrypoint that can grow into sync-rest or workspace-aware refresh steps later.",
162
+ },
163
+ ];
164
+ }
165
+ export function createRetrofitPlan(options) {
166
+ const includeGeneratedArtifacts = options.commandMode === "preview-only";
167
+ const plannedChanges = buildInitPlanChangeSummary({
168
+ generatedArtifacts: options.generatedArtifacts,
169
+ packageChanges: options.packageChanges,
170
+ plannedFiles: options.plannedFiles,
171
+ }, {
172
+ includeGeneratedArtifacts,
173
+ });
174
+ return {
175
+ blockTargets: options.blockTargets,
176
+ commandMode: options.commandMode,
177
+ detectedLayout: options.detectedLayout,
178
+ generatedArtifacts: options.generatedArtifacts,
179
+ nextSteps: options.nextSteps ??
180
+ buildInitPlanNextSteps({
181
+ commandMode: options.commandMode,
182
+ dependencyChangeCount: options.packageChanges.addDevDependencies.length,
183
+ hasPlannedChanges: plannedChanges.length > 0,
184
+ layoutKind: options.detectedLayout.kind,
185
+ packageManager: options.packageManager,
186
+ }),
187
+ notes: options.notes,
188
+ packageChanges: options.packageChanges,
189
+ plannedFiles: options.plannedFiles,
190
+ packageManager: options.packageManager,
191
+ projectDir: options.projectDir,
192
+ projectName: options.projectName,
193
+ status: options.status,
194
+ summary: buildRetrofitPlanSummary({
195
+ commandMode: options.commandMode,
196
+ status: options.status,
197
+ }),
198
+ };
199
+ }
200
+ /**
201
+ * Inspect one project directory and return the current retrofit init plan.
202
+ *
203
+ * @param projectDir Project root or nested path that should be analyzed.
204
+ * @param options Optional package-manager override used for emitted scripts and
205
+ * follow-up guidance.
206
+ * @returns The preview-only retrofit init plan for the resolved project.
207
+ */
208
+ export function getInitPlan(projectDir, options = {}) {
209
+ const resolvedProjectDir = path.resolve(projectDir);
210
+ const packageJson = readProjectPackageJson(resolvedProjectDir);
211
+ const packageManager = resolveInitPackageManager(resolvedProjectDir, packageJson, options.packageManager);
212
+ const workspace = tryResolveWorkspaceProject(resolvedProjectDir);
213
+ if (workspace) {
214
+ const workspacePackageJson = readProjectPackageJson(workspace.projectDir);
215
+ const workspacePackageManager = resolveInitPackageManager(workspace.projectDir, workspacePackageJson, options.packageManager);
216
+ const cliSpecifier = getWpTypiaCliSpecifier();
217
+ return createRetrofitPlan({
218
+ blockTargets: [],
219
+ commandMode: "preview-only",
220
+ detectedLayout: {
221
+ blockNames: [],
222
+ description: "Already an official wp-typia workspace.",
223
+ kind: "official-workspace",
224
+ },
225
+ generatedArtifacts: [],
226
+ nextSteps: [
227
+ "Use `wp-typia add <kind> <name>` to extend the official workspace instead of rerunning init.",
228
+ formatRunScript(workspacePackageManager, "sync"),
229
+ formatPackageExecCommand(workspacePackageManager, cliSpecifier, "doctor"),
230
+ ],
231
+ notes: [
232
+ "The official workspace template already owns inventory, doctor, and add-command workflows.",
233
+ ],
234
+ packageChanges: {
235
+ addDevDependencies: [],
236
+ scripts: [],
237
+ },
238
+ packageManager: workspacePackageManager,
239
+ plannedFiles: [],
240
+ projectDir: workspace.projectDir,
241
+ projectName: workspace.packageName,
242
+ status: "already-initialized",
243
+ });
244
+ }
245
+ const projectName = typeof packageJson?.name === "string" && packageJson.name.length > 0
246
+ ? packageJson.name
247
+ : path.basename(resolvedProjectDir);
248
+ const layout = buildInitLayoutDetails(resolvedProjectDir);
249
+ const dependencyChanges = buildDependencyChanges(packageJson);
250
+ const scriptChanges = buildScriptChanges(packageJson, packageManager);
251
+ const packageManagerFieldChange = buildPackageManagerFieldChange(packageJson, packageManager, {
252
+ persistExplicitOverride: typeof options.packageManager === "string",
253
+ });
254
+ const rawPlannedFiles = layout.kind === "generated-project" || layout.kind === "official-workspace"
255
+ ? []
256
+ : buildPlannedFiles(resolvedProjectDir, layout.kind);
257
+ const hasExistingSurface = hasExistingWpTypiaProjectSurface(resolvedProjectDir, packageJson);
258
+ const status = hasExistingSurface &&
259
+ dependencyChanges.length === 0 &&
260
+ scriptChanges.length === 0 &&
261
+ packageManagerFieldChange === undefined
262
+ ? "already-initialized"
263
+ : "preview";
264
+ const plannedFiles = status === "already-initialized" ? [] : rawPlannedFiles;
265
+ const detectedLayout = status === "already-initialized" && hasExistingSurface
266
+ ? {
267
+ blockNames: layout.blockNames,
268
+ description: layout.kind === "unsupported"
269
+ ? "Already exposes the minimum wp-typia sync surface."
270
+ : `Already exposes the minimum wp-typia sync surface for ${layout.kind === "multi-block" ? "a multi-block project" : "a single-block project"}.`,
271
+ kind: "generated-project",
272
+ }
273
+ : {
274
+ blockNames: layout.blockNames,
275
+ description: layout.description,
276
+ kind: layout.kind,
277
+ };
278
+ return createRetrofitPlan({
279
+ blockTargets: layout.blockTargets,
280
+ commandMode: "preview-only",
281
+ detectedLayout,
282
+ generatedArtifacts: status === "already-initialized" && detectedLayout.kind === "generated-project"
283
+ ? []
284
+ : layout.generatedArtifacts,
285
+ notes: Array.from(new Set([
286
+ "Preview only: `wp-typia init` does not write files yet.",
287
+ RETROFIT_APPLY_PREVIEW_NOTE,
288
+ ...layout.notes,
289
+ ])),
290
+ packageChanges: {
291
+ addDevDependencies: dependencyChanges,
292
+ ...(packageManagerFieldChange
293
+ ? { packageManagerField: packageManagerFieldChange }
294
+ : {}),
295
+ scripts: scriptChanges,
296
+ },
297
+ packageManager,
298
+ plannedFiles,
299
+ projectDir: resolvedProjectDir,
300
+ projectName,
301
+ status,
302
+ });
303
+ }
@@ -0,0 +1,27 @@
1
+ import type { RetrofitInitBlockTarget } from "./cli-init-types.js";
2
+ /**
3
+ * Generate the `scripts/block-config.ts` source for retrofit block targets.
4
+ *
5
+ * @param targets Existing block targets detected by the init plan.
6
+ * @returns Complete TypeScript source for the generated block config helper.
7
+ */
8
+ export declare function buildRetrofitBlockConfigSource(targets: RetrofitInitBlockTarget[]): string;
9
+ /**
10
+ * Generate the `scripts/sync-types-to-block-json.ts` helper source.
11
+ *
12
+ * @returns Complete TypeScript source for the metadata sync helper.
13
+ */
14
+ export declare function buildRetrofitSyncTypesScriptSource(): string;
15
+ /**
16
+ * Generate the `scripts/sync-project.ts` orchestration helper source.
17
+ *
18
+ * @returns Complete TypeScript source for the project sync entrypoint.
19
+ */
20
+ export declare function buildRetrofitSyncProjectScriptSource(): string;
21
+ /**
22
+ * Build the helper file source map written by `wp-typia init --apply`.
23
+ *
24
+ * @param blockTargets Existing block targets detected by the init plan.
25
+ * @returns Relative helper file paths mapped to their generated source.
26
+ */
27
+ export declare function buildRetrofitHelperFiles(blockTargets: RetrofitInitBlockTarget[]): Record<string, string>;