@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
|
@@ -0,0 +1,375 @@
|
|
|
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 { formatAddDevDependenciesCommand, formatPackageExecCommand, formatRunScript, } from "./package-managers.js";
|
|
7
|
+
import { toPascalCase } from "./string-case.js";
|
|
8
|
+
import { buildDependencyChanges, buildPackageManagerFieldChange, buildRequiredDevDependencyMapEntries, buildScriptChanges, getWpTypiaCliSpecifier, hasExistingWpTypiaProjectSurface, readProjectPackageJson, resolveInitPackageManager, } from "./cli-init-package-json.js";
|
|
9
|
+
import { RETROFIT_APPLY_PREVIEW_NOTE, SUPPORTED_RETROFIT_LAYOUT_NOTE, } from "./cli-init-types.js";
|
|
10
|
+
import { tryResolveWorkspaceProject } from "./workspace-project.js";
|
|
11
|
+
function normalizeRelativePath(value) {
|
|
12
|
+
return value.replace(/\\/gu, "/");
|
|
13
|
+
}
|
|
14
|
+
function buildGeneratedArtifactPaths(blockJsonFile, manifestFile) {
|
|
15
|
+
const manifestDir = path.dirname(manifestFile);
|
|
16
|
+
const artifactPaths = [
|
|
17
|
+
blockJsonFile,
|
|
18
|
+
manifestFile,
|
|
19
|
+
path.join(manifestDir, "typia.schema.json"),
|
|
20
|
+
path.join(manifestDir, "typia-validator.php"),
|
|
21
|
+
path.join(manifestDir, "typia.openapi.json"),
|
|
22
|
+
];
|
|
23
|
+
return Array.from(new Set(artifactPaths.map((filePath) => normalizeRelativePath(filePath))));
|
|
24
|
+
}
|
|
25
|
+
function collectNamedSourceTypeCandidates(typesSource) {
|
|
26
|
+
const sourceFile = ts.createSourceFile("types.ts", typesSource, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
27
|
+
return sourceFile.statements.flatMap((statement) => {
|
|
28
|
+
if (ts.isInterfaceDeclaration(statement) ||
|
|
29
|
+
ts.isTypeAliasDeclaration(statement)) {
|
|
30
|
+
return [statement.name.text];
|
|
31
|
+
}
|
|
32
|
+
return [];
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function isObjectLikeSourceType(projectDir, typesFile, sourceTypeName) {
|
|
36
|
+
const analyzedTypes = analyzeSourceTypes({
|
|
37
|
+
projectRoot: projectDir,
|
|
38
|
+
typesFile,
|
|
39
|
+
}, [sourceTypeName]);
|
|
40
|
+
return analyzedTypes[sourceTypeName]?.kind === "object";
|
|
41
|
+
}
|
|
42
|
+
function inferRetrofitAttributeTypeName(projectDir, block) {
|
|
43
|
+
const typesPath = path.join(projectDir, block.typesFile);
|
|
44
|
+
const typesSource = fs.readFileSync(typesPath, "utf8");
|
|
45
|
+
const blockNameSegments = block.blockName.split("/");
|
|
46
|
+
const slug = blockNameSegments[blockNameSegments.length - 1] ?? block.key;
|
|
47
|
+
const candidateNames = collectNamedSourceTypeCandidates(typesSource);
|
|
48
|
+
const validCandidates = candidateNames.filter((candidateName) => isObjectLikeSourceType(projectDir, block.typesFile, candidateName));
|
|
49
|
+
const preferredName = `${toPascalCase(slug)}Attributes`;
|
|
50
|
+
if (validCandidates.includes(preferredName)) {
|
|
51
|
+
return preferredName;
|
|
52
|
+
}
|
|
53
|
+
const attributeCandidates = validCandidates.filter((candidateName) => candidateName.endsWith("Attributes"));
|
|
54
|
+
if (attributeCandidates.length === 1) {
|
|
55
|
+
return attributeCandidates[0];
|
|
56
|
+
}
|
|
57
|
+
if (validCandidates.length === 1) {
|
|
58
|
+
return validCandidates[0];
|
|
59
|
+
}
|
|
60
|
+
if (validCandidates.length === 0) {
|
|
61
|
+
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\`.`);
|
|
62
|
+
}
|
|
63
|
+
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\`.`);
|
|
64
|
+
}
|
|
65
|
+
function buildRetrofitBlockTarget(projectDir, block) {
|
|
66
|
+
const blockNameSegments = block.blockName.split("/");
|
|
67
|
+
const slug = blockNameSegments[blockNameSegments.length - 1] ?? block.key;
|
|
68
|
+
return {
|
|
69
|
+
attributeTypeName: inferRetrofitAttributeTypeName(projectDir, block),
|
|
70
|
+
blockJsonFile: block.blockJsonFile,
|
|
71
|
+
blockName: block.blockName,
|
|
72
|
+
manifestFile: block.manifestFile,
|
|
73
|
+
saveFile: block.saveFile,
|
|
74
|
+
slug,
|
|
75
|
+
typesFile: block.typesFile,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
export function buildInitLayoutDetails(projectDir) {
|
|
79
|
+
try {
|
|
80
|
+
const discoveredLayout = discoverMigrationInitLayout(projectDir);
|
|
81
|
+
const discoveredBlocks = discoveredLayout.mode === "multi"
|
|
82
|
+
? discoveredLayout.blocks
|
|
83
|
+
: [discoveredLayout.block];
|
|
84
|
+
let blockTargets;
|
|
85
|
+
try {
|
|
86
|
+
blockTargets = discoveredBlocks.map((block) => buildRetrofitBlockTarget(projectDir, block));
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
90
|
+
return {
|
|
91
|
+
blockNames: discoveredBlocks.map((block) => block.blockName),
|
|
92
|
+
blockTargets: [],
|
|
93
|
+
description: "Detected supported block files, but could not infer retrofit block-config metadata automatically yet.",
|
|
94
|
+
generatedArtifacts: [],
|
|
95
|
+
kind: "unsupported",
|
|
96
|
+
notes: [message, SUPPORTED_RETROFIT_LAYOUT_NOTE],
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
if (discoveredLayout.mode === "multi") {
|
|
100
|
+
return {
|
|
101
|
+
blockNames: discoveredBlocks.map((block) => block.blockName),
|
|
102
|
+
blockTargets,
|
|
103
|
+
description: `Detected a supported multi-block retrofit candidate (${discoveredBlocks.length} targets).`,
|
|
104
|
+
generatedArtifacts: discoveredBlocks.flatMap((block) => buildGeneratedArtifactPaths(block.blockJsonFile, block.manifestFile)),
|
|
105
|
+
kind: "multi-block",
|
|
106
|
+
notes: [
|
|
107
|
+
"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.",
|
|
108
|
+
],
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
blockNames: [discoveredLayout.block.blockName],
|
|
113
|
+
blockTargets,
|
|
114
|
+
description: "Detected a supported single-block retrofit candidate.",
|
|
115
|
+
generatedArtifacts: buildGeneratedArtifactPaths(discoveredLayout.block.blockJsonFile, discoveredLayout.block.manifestFile),
|
|
116
|
+
kind: "single-block",
|
|
117
|
+
notes: discoveredLayout.block.blockJsonFile === "block.json"
|
|
118
|
+
? [
|
|
119
|
+
"Legacy root `block.json` layouts are still supported for retrofit planning, but newer scaffolds keep generated block metadata under `src/`.",
|
|
120
|
+
]
|
|
121
|
+
: [],
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
126
|
+
return {
|
|
127
|
+
blockNames: [],
|
|
128
|
+
blockTargets: [],
|
|
129
|
+
description: "No supported retrofit layout was auto-detected yet.",
|
|
130
|
+
generatedArtifacts: [],
|
|
131
|
+
kind: "unsupported",
|
|
132
|
+
notes: [message, SUPPORTED_RETROFIT_LAYOUT_NOTE],
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function buildPlannedFiles(projectDir, layoutKind) {
|
|
137
|
+
if (layoutKind === "unsupported") {
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
return [
|
|
141
|
+
{
|
|
142
|
+
action: fs.existsSync(path.join(projectDir, "scripts", "block-config.ts"))
|
|
143
|
+
? "update"
|
|
144
|
+
: "add",
|
|
145
|
+
path: "scripts/block-config.ts",
|
|
146
|
+
purpose: "Declare the current retrofit block targets so sync-types can regenerate metadata from the existing TypeScript source of truth.",
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
action: fs.existsSync(path.join(projectDir, "scripts", "sync-types-to-block-json.ts"))
|
|
150
|
+
? "update"
|
|
151
|
+
: "add",
|
|
152
|
+
path: "scripts/sync-types-to-block-json.ts",
|
|
153
|
+
purpose: "Generate block.json and Typia metadata artifacts from the current TypeScript source of truth.",
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
action: fs.existsSync(path.join(projectDir, "scripts", "sync-project.ts"))
|
|
157
|
+
? "update"
|
|
158
|
+
: "add",
|
|
159
|
+
path: "scripts/sync-project.ts",
|
|
160
|
+
purpose: "Provide one shared sync entrypoint that can grow into sync-rest or workspace-aware refresh steps later.",
|
|
161
|
+
},
|
|
162
|
+
];
|
|
163
|
+
}
|
|
164
|
+
function buildChangeSummary(changes, options) {
|
|
165
|
+
const lines = [];
|
|
166
|
+
for (const dependencyChange of changes.packageChanges.addDevDependencies) {
|
|
167
|
+
lines.push(`devDependency ${dependencyChange.action} ${dependencyChange.name} -> ${dependencyChange.requiredValue}`);
|
|
168
|
+
}
|
|
169
|
+
if (changes.packageChanges.packageManagerField) {
|
|
170
|
+
lines.push(`packageManager ${changes.packageChanges.packageManagerField.action} -> ${changes.packageChanges.packageManagerField.requiredValue}`);
|
|
171
|
+
}
|
|
172
|
+
for (const scriptChange of changes.packageChanges.scripts) {
|
|
173
|
+
lines.push(`script ${scriptChange.action} ${scriptChange.name} -> ${scriptChange.requiredValue}`);
|
|
174
|
+
}
|
|
175
|
+
for (const filePlan of changes.plannedFiles) {
|
|
176
|
+
lines.push(`file ${filePlan.action} ${filePlan.path} (${filePlan.purpose})`);
|
|
177
|
+
}
|
|
178
|
+
if (options.includeGeneratedArtifacts) {
|
|
179
|
+
for (const artifactPath of changes.generatedArtifacts) {
|
|
180
|
+
lines.push(`generated artifact ${artifactPath}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return lines;
|
|
184
|
+
}
|
|
185
|
+
function buildNextSteps(options) {
|
|
186
|
+
const cliSpecifier = getWpTypiaCliSpecifier();
|
|
187
|
+
const syncTypesRun = formatRunScript(options.packageManager, "sync-types");
|
|
188
|
+
const syncRun = formatRunScript(options.packageManager, "sync");
|
|
189
|
+
const doctorRun = formatPackageExecCommand(options.packageManager, cliSpecifier, "doctor");
|
|
190
|
+
const migrationInitRun = formatPackageExecCommand(options.packageManager, cliSpecifier, "migrate init --current-migration-version v1");
|
|
191
|
+
const dependencyInstallCommand = formatAddDevDependenciesCommand(options.packageManager, buildRequiredDevDependencyMapEntries());
|
|
192
|
+
if (options.layoutKind === "unsupported") {
|
|
193
|
+
return [
|
|
194
|
+
"Align the project to one of the supported retrofit layouts listed below, then rerun `wp-typia init`.",
|
|
195
|
+
dependencyInstallCommand,
|
|
196
|
+
syncTypesRun,
|
|
197
|
+
doctorRun,
|
|
198
|
+
];
|
|
199
|
+
}
|
|
200
|
+
if (options.commandMode === "apply") {
|
|
201
|
+
return [
|
|
202
|
+
...(options.dependencyChangeCount > 0
|
|
203
|
+
? [
|
|
204
|
+
"Install or reinstall project dependencies so the retrofit sync scripts and metadata generators are available locally.",
|
|
205
|
+
dependencyInstallCommand,
|
|
206
|
+
]
|
|
207
|
+
: []),
|
|
208
|
+
syncRun,
|
|
209
|
+
doctorRun,
|
|
210
|
+
`Optional migration bootstrap: ${migrationInitRun}`,
|
|
211
|
+
];
|
|
212
|
+
}
|
|
213
|
+
const steps = [
|
|
214
|
+
...(options.hasPlannedChanges
|
|
215
|
+
? [
|
|
216
|
+
"Re-run `wp-typia init --apply` to write the planned package.json changes and helper files automatically.",
|
|
217
|
+
...(options.dependencyChangeCount > 0 ? [dependencyInstallCommand] : []),
|
|
218
|
+
]
|
|
219
|
+
: []),
|
|
220
|
+
syncRun,
|
|
221
|
+
doctorRun,
|
|
222
|
+
`Optional migration bootstrap: ${migrationInitRun}`,
|
|
223
|
+
];
|
|
224
|
+
return steps;
|
|
225
|
+
}
|
|
226
|
+
function buildRetrofitPlanSummary(options) {
|
|
227
|
+
if (options.status === "already-initialized") {
|
|
228
|
+
return options.commandMode === "apply"
|
|
229
|
+
? "This project already exposes the minimum wp-typia retrofit surface. No files were changed."
|
|
230
|
+
: "This project already exposes the minimum wp-typia retrofit surface.";
|
|
231
|
+
}
|
|
232
|
+
if (options.commandMode === "apply") {
|
|
233
|
+
return "Applied the minimum wp-typia retrofit surface so package.json and helper scripts are ready for the next install and sync run.";
|
|
234
|
+
}
|
|
235
|
+
return "This command previews the minimum wp-typia adoption layer for the current project without rewriting it into a full scaffold.";
|
|
236
|
+
}
|
|
237
|
+
export function createRetrofitPlan(options) {
|
|
238
|
+
const includeGeneratedArtifacts = options.commandMode === "preview-only";
|
|
239
|
+
const plannedChanges = buildChangeSummary({
|
|
240
|
+
generatedArtifacts: options.generatedArtifacts,
|
|
241
|
+
packageChanges: options.packageChanges,
|
|
242
|
+
plannedFiles: options.plannedFiles,
|
|
243
|
+
}, {
|
|
244
|
+
includeGeneratedArtifacts,
|
|
245
|
+
});
|
|
246
|
+
return {
|
|
247
|
+
blockTargets: options.blockTargets,
|
|
248
|
+
commandMode: options.commandMode,
|
|
249
|
+
detectedLayout: options.detectedLayout,
|
|
250
|
+
generatedArtifacts: options.generatedArtifacts,
|
|
251
|
+
nextSteps: options.nextSteps ??
|
|
252
|
+
buildNextSteps({
|
|
253
|
+
commandMode: options.commandMode,
|
|
254
|
+
dependencyChangeCount: options.packageChanges.addDevDependencies.length,
|
|
255
|
+
hasPlannedChanges: plannedChanges.length > 0,
|
|
256
|
+
layoutKind: options.detectedLayout.kind,
|
|
257
|
+
packageManager: options.packageManager,
|
|
258
|
+
}),
|
|
259
|
+
notes: options.notes,
|
|
260
|
+
packageChanges: options.packageChanges,
|
|
261
|
+
plannedFiles: options.plannedFiles,
|
|
262
|
+
packageManager: options.packageManager,
|
|
263
|
+
projectDir: options.projectDir,
|
|
264
|
+
projectName: options.projectName,
|
|
265
|
+
status: options.status,
|
|
266
|
+
summary: buildRetrofitPlanSummary({
|
|
267
|
+
commandMode: options.commandMode,
|
|
268
|
+
status: options.status,
|
|
269
|
+
}),
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Inspect one project directory and return the current retrofit init plan.
|
|
274
|
+
*
|
|
275
|
+
* @param projectDir Project root or nested path that should be analyzed.
|
|
276
|
+
* @param options Optional package-manager override used for emitted scripts and
|
|
277
|
+
* follow-up guidance.
|
|
278
|
+
* @returns The preview-only retrofit init plan for the resolved project.
|
|
279
|
+
*/
|
|
280
|
+
export function getInitPlan(projectDir, options = {}) {
|
|
281
|
+
const resolvedProjectDir = path.resolve(projectDir);
|
|
282
|
+
const packageJson = readProjectPackageJson(resolvedProjectDir);
|
|
283
|
+
const packageManager = resolveInitPackageManager(resolvedProjectDir, packageJson, options.packageManager);
|
|
284
|
+
const workspace = tryResolveWorkspaceProject(resolvedProjectDir);
|
|
285
|
+
if (workspace) {
|
|
286
|
+
const workspacePackageJson = readProjectPackageJson(workspace.projectDir);
|
|
287
|
+
const workspacePackageManager = resolveInitPackageManager(workspace.projectDir, workspacePackageJson, options.packageManager);
|
|
288
|
+
const cliSpecifier = getWpTypiaCliSpecifier();
|
|
289
|
+
return createRetrofitPlan({
|
|
290
|
+
blockTargets: [],
|
|
291
|
+
commandMode: "preview-only",
|
|
292
|
+
detectedLayout: {
|
|
293
|
+
blockNames: [],
|
|
294
|
+
description: "Already an official wp-typia workspace.",
|
|
295
|
+
kind: "official-workspace",
|
|
296
|
+
},
|
|
297
|
+
generatedArtifacts: [],
|
|
298
|
+
nextSteps: [
|
|
299
|
+
"Use `wp-typia add <kind> <name>` to extend the official workspace instead of rerunning init.",
|
|
300
|
+
formatRunScript(workspacePackageManager, "sync"),
|
|
301
|
+
formatPackageExecCommand(workspacePackageManager, cliSpecifier, "doctor"),
|
|
302
|
+
],
|
|
303
|
+
notes: [
|
|
304
|
+
"The official workspace template already owns inventory, doctor, and add-command workflows.",
|
|
305
|
+
],
|
|
306
|
+
packageChanges: {
|
|
307
|
+
addDevDependencies: [],
|
|
308
|
+
scripts: [],
|
|
309
|
+
},
|
|
310
|
+
packageManager: workspacePackageManager,
|
|
311
|
+
plannedFiles: [],
|
|
312
|
+
projectDir: workspace.projectDir,
|
|
313
|
+
projectName: workspace.packageName,
|
|
314
|
+
status: "already-initialized",
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
const projectName = typeof packageJson?.name === "string" && packageJson.name.length > 0
|
|
318
|
+
? packageJson.name
|
|
319
|
+
: path.basename(resolvedProjectDir);
|
|
320
|
+
const layout = buildInitLayoutDetails(resolvedProjectDir);
|
|
321
|
+
const dependencyChanges = buildDependencyChanges(packageJson);
|
|
322
|
+
const scriptChanges = buildScriptChanges(packageJson, packageManager);
|
|
323
|
+
const packageManagerFieldChange = buildPackageManagerFieldChange(packageJson, packageManager, {
|
|
324
|
+
persistExplicitOverride: typeof options.packageManager === "string",
|
|
325
|
+
});
|
|
326
|
+
const rawPlannedFiles = layout.kind === "generated-project" || layout.kind === "official-workspace"
|
|
327
|
+
? []
|
|
328
|
+
: buildPlannedFiles(resolvedProjectDir, layout.kind);
|
|
329
|
+
const hasExistingSurface = hasExistingWpTypiaProjectSurface(resolvedProjectDir, packageJson);
|
|
330
|
+
const status = hasExistingSurface &&
|
|
331
|
+
dependencyChanges.length === 0 &&
|
|
332
|
+
scriptChanges.length === 0 &&
|
|
333
|
+
packageManagerFieldChange === undefined
|
|
334
|
+
? "already-initialized"
|
|
335
|
+
: "preview";
|
|
336
|
+
const plannedFiles = status === "already-initialized" ? [] : rawPlannedFiles;
|
|
337
|
+
const detectedLayout = status === "already-initialized" && hasExistingSurface
|
|
338
|
+
? {
|
|
339
|
+
blockNames: layout.blockNames,
|
|
340
|
+
description: layout.kind === "unsupported"
|
|
341
|
+
? "Already exposes the minimum wp-typia sync surface."
|
|
342
|
+
: `Already exposes the minimum wp-typia sync surface for ${layout.kind === "multi-block" ? "a multi-block project" : "a single-block project"}.`,
|
|
343
|
+
kind: "generated-project",
|
|
344
|
+
}
|
|
345
|
+
: {
|
|
346
|
+
blockNames: layout.blockNames,
|
|
347
|
+
description: layout.description,
|
|
348
|
+
kind: layout.kind,
|
|
349
|
+
};
|
|
350
|
+
return createRetrofitPlan({
|
|
351
|
+
blockTargets: layout.blockTargets,
|
|
352
|
+
commandMode: "preview-only",
|
|
353
|
+
detectedLayout,
|
|
354
|
+
generatedArtifacts: status === "already-initialized" && detectedLayout.kind === "generated-project"
|
|
355
|
+
? []
|
|
356
|
+
: layout.generatedArtifacts,
|
|
357
|
+
notes: Array.from(new Set([
|
|
358
|
+
"Preview only: `wp-typia init` does not write files yet.",
|
|
359
|
+
RETROFIT_APPLY_PREVIEW_NOTE,
|
|
360
|
+
...layout.notes,
|
|
361
|
+
])),
|
|
362
|
+
packageChanges: {
|
|
363
|
+
addDevDependencies: dependencyChanges,
|
|
364
|
+
...(packageManagerFieldChange
|
|
365
|
+
? { packageManagerField: packageManagerFieldChange }
|
|
366
|
+
: {}),
|
|
367
|
+
scripts: scriptChanges,
|
|
368
|
+
},
|
|
369
|
+
packageManager,
|
|
370
|
+
plannedFiles,
|
|
371
|
+
projectDir: resolvedProjectDir,
|
|
372
|
+
projectName,
|
|
373
|
+
status,
|
|
374
|
+
});
|
|
375
|
+
}
|
|
@@ -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>;
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { quoteTsString } from "./cli-add-shared.js";
|
|
3
|
+
import { updateWorkspaceInventorySource } from "./workspace-inventory.js";
|
|
4
|
+
function buildRetrofitBlockConfigEntry(target) {
|
|
5
|
+
return [
|
|
6
|
+
"\t{",
|
|
7
|
+
`\t\tslug: ${quoteTsString(target.slug)},`,
|
|
8
|
+
`\t\tattributeTypeName: ${quoteTsString(target.attributeTypeName)},`,
|
|
9
|
+
`\t\tblockJsonFile: ${quoteTsString(target.blockJsonFile)},`,
|
|
10
|
+
`\t\tmanifestFile: ${quoteTsString(target.manifestFile)},`,
|
|
11
|
+
`\t\ttypesFile: ${quoteTsString(target.typesFile)},`,
|
|
12
|
+
"\t},",
|
|
13
|
+
].join("\n");
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Generate the `scripts/block-config.ts` source for retrofit block targets.
|
|
17
|
+
*
|
|
18
|
+
* @param targets Existing block targets detected by the init plan.
|
|
19
|
+
* @returns Complete TypeScript source for the generated block config helper.
|
|
20
|
+
*/
|
|
21
|
+
export function buildRetrofitBlockConfigSource(targets) {
|
|
22
|
+
const blockEntries = targets.map(buildRetrofitBlockConfigEntry).join("\n");
|
|
23
|
+
const baseSource = `export interface WorkspaceBlockConfig {
|
|
24
|
+
\tattributeTypeName: string;
|
|
25
|
+
\tapiTypesFile?: string;
|
|
26
|
+
\tblockJsonFile?: string;
|
|
27
|
+
\tmanifestFile?: string;
|
|
28
|
+
\topenApiFile?: string;
|
|
29
|
+
\trestManifest?: ReturnType<
|
|
30
|
+
\t\ttypeof import( '@wp-typia/block-runtime/metadata-core' ).defineEndpointManifest
|
|
31
|
+
\t>;
|
|
32
|
+
\tslug: string;
|
|
33
|
+
\ttypesFile: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const BLOCKS: WorkspaceBlockConfig[] = [
|
|
37
|
+
${blockEntries}
|
|
38
|
+
];
|
|
39
|
+
`;
|
|
40
|
+
return `${updateWorkspaceInventorySource(baseSource)}\n`;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Generate the `scripts/sync-types-to-block-json.ts` helper source.
|
|
44
|
+
*
|
|
45
|
+
* @returns Complete TypeScript source for the metadata sync helper.
|
|
46
|
+
*/
|
|
47
|
+
export function buildRetrofitSyncTypesScriptSource() {
|
|
48
|
+
return `/* eslint-disable no-console */
|
|
49
|
+
import path from 'node:path';
|
|
50
|
+
|
|
51
|
+
import { syncBlockMetadata } from '@wp-typia/block-runtime/metadata-core';
|
|
52
|
+
|
|
53
|
+
import { BLOCKS } from './block-config';
|
|
54
|
+
|
|
55
|
+
function parseCliOptions( argv: string[] ) {
|
|
56
|
+
\tconst options = {
|
|
57
|
+
\t\tcheck: false,
|
|
58
|
+
\t};
|
|
59
|
+
|
|
60
|
+
\tfor ( const argument of argv ) {
|
|
61
|
+
\t\tif ( argument === '--check' ) {
|
|
62
|
+
\t\t\toptions.check = true;
|
|
63
|
+
\t\t\tcontinue;
|
|
64
|
+
\t\t}
|
|
65
|
+
|
|
66
|
+
\t\tthrow new Error( \`Unknown sync-types flag: \${ argument }\` );
|
|
67
|
+
\t}
|
|
68
|
+
|
|
69
|
+
\treturn options;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function main() {
|
|
73
|
+
\tconst options = parseCliOptions( process.argv.slice( 2 ) );
|
|
74
|
+
|
|
75
|
+
\tif ( BLOCKS.length === 0 ) {
|
|
76
|
+
\t\tconsole.log(
|
|
77
|
+
\t\t\toptions.check
|
|
78
|
+
\t\t\t\t? 'ℹ️ No retrofit blocks are registered yet. \`sync-types --check\` is already clean.'
|
|
79
|
+
\t\t\t\t: 'ℹ️ No retrofit blocks are registered yet. Add one block target to scripts/block-config.ts before rerunning sync-types.'
|
|
80
|
+
\t\t);
|
|
81
|
+
\t\treturn;
|
|
82
|
+
\t}
|
|
83
|
+
|
|
84
|
+
\tfor ( const block of BLOCKS ) {
|
|
85
|
+
\t\tconst blockDir = path.dirname( block.typesFile );
|
|
86
|
+
\t\tconst blockJsonFile =
|
|
87
|
+
\t\t\tblock.blockJsonFile ?? path.join( blockDir, 'block.json' );
|
|
88
|
+
\t\tconst manifestFile =
|
|
89
|
+
\t\t\tblock.manifestFile ?? path.join( blockDir, 'typia.manifest.json' );
|
|
90
|
+
\t\tconst manifestDir = path.dirname( manifestFile );
|
|
91
|
+
\t\tconst result = await syncBlockMetadata(
|
|
92
|
+
\t\t\t{
|
|
93
|
+
\t\t\t\tblockJsonFile,
|
|
94
|
+
\t\t\t\tjsonSchemaFile: path.join( manifestDir, 'typia.schema.json' ),
|
|
95
|
+
\t\t\t\tmanifestFile,
|
|
96
|
+
\t\t\t\topenApiFile: path.join( manifestDir, 'typia.openapi.json' ),
|
|
97
|
+
\t\t\t\tsourceTypeName: block.attributeTypeName,
|
|
98
|
+
\t\t\t\ttypesFile: block.typesFile,
|
|
99
|
+
\t\t\t},
|
|
100
|
+
\t\t\t{
|
|
101
|
+
\t\t\t\tcheck: options.check,
|
|
102
|
+
\t\t\t}
|
|
103
|
+
\t\t);
|
|
104
|
+
\t\tfor ( const warning of result.lossyProjectionWarnings ) {
|
|
105
|
+
\t\t\tconsole.warn( \`⚠️ \${ block.slug }: \${ warning }\` );
|
|
106
|
+
\t\t}
|
|
107
|
+
\t\tfor ( const warning of result.phpGenerationWarnings ) {
|
|
108
|
+
\t\t\tconsole.warn( \`⚠️ \${ block.slug }: \${ warning }\` );
|
|
109
|
+
\t\t}
|
|
110
|
+
|
|
111
|
+
\t\tconsole.log(
|
|
112
|
+
\t\t\toptions.check
|
|
113
|
+
\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!\`
|
|
114
|
+
\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!\`
|
|
115
|
+
\t\t);
|
|
116
|
+
\t\tconsole.log( '📝 Generated attributes:', result.attributeNames );
|
|
117
|
+
\t}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
main().catch( ( error ) => {
|
|
121
|
+
\tconsole.error( '❌ Type sync failed:', error );
|
|
122
|
+
\tprocess.exit( 1 );
|
|
123
|
+
} );
|
|
124
|
+
`;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Generate the `scripts/sync-project.ts` orchestration helper source.
|
|
128
|
+
*
|
|
129
|
+
* @returns Complete TypeScript source for the project sync entrypoint.
|
|
130
|
+
*/
|
|
131
|
+
export function buildRetrofitSyncProjectScriptSource() {
|
|
132
|
+
return `/* eslint-disable no-console */
|
|
133
|
+
import { spawnSync } from 'node:child_process';
|
|
134
|
+
import fs from 'node:fs';
|
|
135
|
+
import path from 'node:path';
|
|
136
|
+
|
|
137
|
+
interface SyncCliOptions {
|
|
138
|
+
\tcheck: boolean;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function parseCliOptions( argv: string[] ): SyncCliOptions {
|
|
142
|
+
\tconst options: SyncCliOptions = {
|
|
143
|
+
\t\tcheck: false,
|
|
144
|
+
\t};
|
|
145
|
+
|
|
146
|
+
\tfor ( const argument of argv ) {
|
|
147
|
+
\t\tif ( argument === '--check' ) {
|
|
148
|
+
\t\t\toptions.check = true;
|
|
149
|
+
\t\t\tcontinue;
|
|
150
|
+
\t\t}
|
|
151
|
+
|
|
152
|
+
\t\tthrow new Error( \`Unknown sync flag: \${ argument }\` );
|
|
153
|
+
\t}
|
|
154
|
+
|
|
155
|
+
\treturn options;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function getSyncScriptEnv() {
|
|
159
|
+
\tconst binaryDirectory = path.join( process.cwd(), 'node_modules', '.bin' );
|
|
160
|
+
\tconst inheritedPath =
|
|
161
|
+
\t\tprocess.env.PATH ??
|
|
162
|
+
\t\tprocess.env.Path ??
|
|
163
|
+
\t\tObject.entries( process.env ).find(
|
|
164
|
+
\t\t\t( [ key ] ) => key.toLowerCase() === 'path'
|
|
165
|
+
\t\t)?.[ 1 ] ??
|
|
166
|
+
\t\t'';
|
|
167
|
+
\tconst nextPath = fs.existsSync( binaryDirectory )
|
|
168
|
+
\t\t? \`\${ binaryDirectory }\${ path.delimiter }\${ inheritedPath }\`
|
|
169
|
+
\t\t: inheritedPath;
|
|
170
|
+
\tconst env: NodeJS.ProcessEnv = {
|
|
171
|
+
\t\t...process.env,
|
|
172
|
+
\t};
|
|
173
|
+
|
|
174
|
+
\tfor ( const key of Object.keys( env ) ) {
|
|
175
|
+
\t\tif ( key.toLowerCase() === 'path' ) {
|
|
176
|
+
\t\t\tdelete env[ key ];
|
|
177
|
+
\t\t}
|
|
178
|
+
\t}
|
|
179
|
+
|
|
180
|
+
\tenv.PATH = nextPath;
|
|
181
|
+
|
|
182
|
+
\treturn env;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function runSyncScript( scriptPath: string, options: SyncCliOptions ) {
|
|
186
|
+
\tconst args = [ scriptPath ];
|
|
187
|
+
\tif ( options.check ) {
|
|
188
|
+
\t\targs.push( '--check' );
|
|
189
|
+
\t}
|
|
190
|
+
|
|
191
|
+
\tconst result = spawnSync( 'tsx', args, {
|
|
192
|
+
\t\tcwd: process.cwd(),
|
|
193
|
+
\t\tenv: getSyncScriptEnv(),
|
|
194
|
+
\t\tshell: process.platform === 'win32',
|
|
195
|
+
\t\tstdio: 'inherit',
|
|
196
|
+
\t} );
|
|
197
|
+
|
|
198
|
+
\tif ( result.error ) {
|
|
199
|
+
\t\tif ( ( result.error as NodeJS.ErrnoException ).code === 'ENOENT' ) {
|
|
200
|
+
\t\t\tthrow new Error(
|
|
201
|
+
\t\t\t\t'Unable to resolve \`tsx\` for project sync. Install project dependencies or rerun the command through your package manager.'
|
|
202
|
+
\t\t\t);
|
|
203
|
+
\t\t}
|
|
204
|
+
|
|
205
|
+
\t\tthrow result.error;
|
|
206
|
+
\t}
|
|
207
|
+
|
|
208
|
+
\tif ( result.status !== 0 ) {
|
|
209
|
+
\t\tthrow new Error( \`Sync script failed: \${ scriptPath }\` );
|
|
210
|
+
\t}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async function main() {
|
|
214
|
+
\tconst options = parseCliOptions( process.argv.slice( 2 ) );
|
|
215
|
+
\tconst syncTypesScriptPath = path.join( 'scripts', 'sync-types-to-block-json.ts' );
|
|
216
|
+
|
|
217
|
+
\trunSyncScript( syncTypesScriptPath, options );
|
|
218
|
+
|
|
219
|
+
\tconsole.log(
|
|
220
|
+
\t\toptions.check
|
|
221
|
+
\t\t\t? '✅ Generated project metadata is already synchronized.'
|
|
222
|
+
\t\t\t: '✅ Generated project metadata was synchronized.'
|
|
223
|
+
\t);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
main().catch( ( error ) => {
|
|
227
|
+
\tconsole.error( '❌ Project sync failed:', error );
|
|
228
|
+
\tprocess.exit( 1 );
|
|
229
|
+
} );
|
|
230
|
+
`;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Build the helper file source map written by `wp-typia init --apply`.
|
|
234
|
+
*
|
|
235
|
+
* @param blockTargets Existing block targets detected by the init plan.
|
|
236
|
+
* @returns Relative helper file paths mapped to their generated source.
|
|
237
|
+
*/
|
|
238
|
+
export function buildRetrofitHelperFiles(blockTargets) {
|
|
239
|
+
return {
|
|
240
|
+
[path.join("scripts", "block-config.ts")]: buildRetrofitBlockConfigSource(blockTargets),
|
|
241
|
+
[path.join("scripts", "sync-project.ts")]: buildRetrofitSyncProjectScriptSource(),
|
|
242
|
+
[path.join("scripts", "sync-types-to-block-json.ts")]: buildRetrofitSyncTypesScriptSource(),
|
|
243
|
+
};
|
|
244
|
+
}
|