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