@wp-typia/project-tools 0.16.12 → 0.16.14
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/README.md +0 -1
- package/dist/runtime/block-generator-service-core.d.ts +8 -0
- package/dist/runtime/block-generator-service-core.js +274 -0
- package/dist/runtime/block-generator-service-spec.d.ts +104 -0
- package/dist/runtime/block-generator-service-spec.js +139 -0
- package/dist/runtime/block-generator-service.d.ts +2 -110
- package/dist/runtime/block-generator-service.js +2 -389
- package/dist/runtime/cli-diagnostics.js +76 -4
- package/dist/runtime/cli-doctor-workspace.d.ts +9 -5
- package/dist/runtime/cli-doctor-workspace.js +18 -6
- package/dist/runtime/cli-help.js +1 -1
- package/dist/runtime/cli-prompt.js +78 -19
- package/dist/runtime/cli-scaffold.d.ts +8 -1
- package/dist/runtime/cli-scaffold.js +47 -4
- package/dist/runtime/migration-maintenance-fixtures.d.ts +23 -0
- package/dist/runtime/migration-maintenance-fixtures.js +126 -0
- package/dist/runtime/migration-maintenance-verify.d.ts +26 -0
- package/dist/runtime/migration-maintenance-verify.js +262 -0
- package/dist/runtime/migration-maintenance.d.ts +2 -51
- package/dist/runtime/migration-maintenance.js +2 -380
- package/dist/runtime/migrations.d.ts +0 -3
- package/dist/runtime/scaffold-answer-resolution.d.ts +37 -0
- package/dist/runtime/scaffold-answer-resolution.js +138 -0
- package/dist/runtime/scaffold-apply-utils.d.ts +1 -7
- package/dist/runtime/scaffold-apply-utils.js +4 -105
- package/dist/runtime/scaffold-documents.d.ts +34 -0
- package/dist/runtime/scaffold-documents.js +144 -0
- package/dist/runtime/scaffold-onboarding.d.ts +12 -0
- package/dist/runtime/scaffold-onboarding.js +42 -5
- package/dist/runtime/scaffold-template-variables.d.ts +9 -0
- package/dist/runtime/scaffold-template-variables.js +111 -0
- package/dist/runtime/scaffold.d.ts +11 -9
- package/dist/runtime/scaffold.js +6 -202
- package/package.json +3 -3
|
@@ -18,18 +18,17 @@ export function createReadlinePrompt() {
|
|
|
18
18
|
* retry behavior without stubbing global stdin/stdout.
|
|
19
19
|
*/
|
|
20
20
|
export function createReadlinePromptWithInterface(rl) {
|
|
21
|
+
const askQuestion = (query) => new Promise((resolve) => {
|
|
22
|
+
rl.question(query, resolve);
|
|
23
|
+
});
|
|
21
24
|
return {
|
|
22
25
|
async text(message, defaultValue, validate) {
|
|
23
|
-
const suffix = defaultValue ? ` (${defaultValue})` : "";
|
|
24
26
|
while (true) {
|
|
25
|
-
const
|
|
26
|
-
rl.question(`${message}${suffix}: `, resolve);
|
|
27
|
-
});
|
|
28
|
-
const value = String(answer).trim() || defaultValue;
|
|
27
|
+
const value = normalizePromptAnswer(await askQuestion(formatTextPrompt(message, defaultValue))) || defaultValue;
|
|
29
28
|
if (validate) {
|
|
30
29
|
const result = validate(value);
|
|
31
30
|
if (result !== true) {
|
|
32
|
-
console.error(
|
|
31
|
+
console.error(formatValidationError(message, result, defaultValue));
|
|
33
32
|
continue;
|
|
34
33
|
}
|
|
35
34
|
}
|
|
@@ -40,22 +39,22 @@ export function createReadlinePromptWithInterface(rl) {
|
|
|
40
39
|
if (options.length === 0) {
|
|
41
40
|
throw new Error(`select() requires at least one option for prompt: ${message}`);
|
|
42
41
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const hint = option.hint ? ` - ${option.hint}` : "";
|
|
46
|
-
console.log(` ${index + 1}. ${option.label}${hint}`);
|
|
47
|
-
});
|
|
42
|
+
const resolvedDefaultIndex = getResolvedDefaultIndex(options, defaultValue);
|
|
43
|
+
renderSelectPrompt(message, options, resolvedDefaultIndex);
|
|
48
44
|
while (true) {
|
|
49
|
-
const answer = await
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
45
|
+
const answer = normalizePromptAnswer(await askQuestion(formatChoicePrompt(resolvedDefaultIndex)));
|
|
46
|
+
if (answer.length === 0) {
|
|
47
|
+
return options[resolvedDefaultIndex].value;
|
|
48
|
+
}
|
|
49
|
+
const selection = resolvePromptSelection(options, answer);
|
|
50
|
+
if (selection) {
|
|
51
|
+
return selection.value;
|
|
53
52
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
if (isPromptHelpToken(answer)) {
|
|
54
|
+
renderSelectPrompt(message, options, resolvedDefaultIndex);
|
|
55
|
+
continue;
|
|
57
56
|
}
|
|
58
|
-
console.error(
|
|
57
|
+
console.error(formatInvalidSelectionError(answer, options, resolvedDefaultIndex));
|
|
59
58
|
}
|
|
60
59
|
},
|
|
61
60
|
close() {
|
|
@@ -63,3 +62,63 @@ export function createReadlinePromptWithInterface(rl) {
|
|
|
63
62
|
},
|
|
64
63
|
};
|
|
65
64
|
}
|
|
65
|
+
function normalizePromptAnswer(value) {
|
|
66
|
+
return String(value).trim();
|
|
67
|
+
}
|
|
68
|
+
function normalizePromptToken(value) {
|
|
69
|
+
return value.trim().toLowerCase();
|
|
70
|
+
}
|
|
71
|
+
function getResolvedDefaultIndex(options, defaultValue) {
|
|
72
|
+
const candidateIndex = Number.isInteger(defaultValue) ? defaultValue - 1 : -1;
|
|
73
|
+
return options[candidateIndex] ? candidateIndex : 0;
|
|
74
|
+
}
|
|
75
|
+
function formatTextPrompt(message, defaultValue) {
|
|
76
|
+
const suffix = defaultValue.length > 0 ? ` [default: ${defaultValue}]` : "";
|
|
77
|
+
return `${message}${suffix}: `;
|
|
78
|
+
}
|
|
79
|
+
function formatValidationError(message, result, defaultValue) {
|
|
80
|
+
const detail = typeof result === "string" ? result : "Invalid input";
|
|
81
|
+
const retryHint = defaultValue.length > 0 ? ` Press Enter to keep "${defaultValue}".` : "";
|
|
82
|
+
return `❌ ${message}: ${detail}.${retryHint}`;
|
|
83
|
+
}
|
|
84
|
+
function formatChoicePrompt(defaultIndex) {
|
|
85
|
+
return `Choice [default: ${defaultIndex + 1}, ? for options]: `;
|
|
86
|
+
}
|
|
87
|
+
function renderSelectPrompt(message, options, defaultIndex) {
|
|
88
|
+
console.log(message);
|
|
89
|
+
console.log(" Enter a number, option label, or option value. Press Enter to keep the default, or type ? to list choices again.");
|
|
90
|
+
options.forEach((option, index) => {
|
|
91
|
+
const defaultMarker = index === defaultIndex ? " (default)" : "";
|
|
92
|
+
const valueHint = normalizePromptToken(option.label) === normalizePromptToken(option.value)
|
|
93
|
+
? ""
|
|
94
|
+
: ` [${option.value}]`;
|
|
95
|
+
console.log(` ${index + 1}. ${option.label}${valueHint}${defaultMarker}`);
|
|
96
|
+
if (option.hint) {
|
|
97
|
+
console.log(` ${option.hint}`);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
function isPromptHelpToken(answer) {
|
|
102
|
+
const normalized = normalizePromptToken(answer);
|
|
103
|
+
return normalized === "?" || normalized === "help" || normalized === "list";
|
|
104
|
+
}
|
|
105
|
+
function resolvePromptSelection(options, answer) {
|
|
106
|
+
const numericChoice = Number(answer);
|
|
107
|
+
if (!Number.isNaN(numericChoice) && options[numericChoice - 1]) {
|
|
108
|
+
return options[numericChoice - 1];
|
|
109
|
+
}
|
|
110
|
+
const normalizedAnswer = normalizePromptToken(answer);
|
|
111
|
+
return options.find((option) => {
|
|
112
|
+
const normalizedLabel = normalizePromptToken(option.label);
|
|
113
|
+
const normalizedValue = normalizePromptToken(option.value);
|
|
114
|
+
return normalizedAnswer === normalizedLabel || normalizedAnswer === normalizedValue;
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
function formatInvalidSelectionError(answer, options, defaultIndex) {
|
|
118
|
+
const optionValues = options.map((option) => option.value).join(", ");
|
|
119
|
+
return [
|
|
120
|
+
`❌ Invalid selection: ${answer}.`,
|
|
121
|
+
`Enter 1-${options.length}, one of: ${optionValues},`,
|
|
122
|
+
`or press Enter for "${options[defaultIndex].label}".`,
|
|
123
|
+
].join(" ");
|
|
124
|
+
}
|
|
@@ -78,7 +78,14 @@ export declare function runScaffoldFlow({ projectInput, cwd, templateId, dataSto
|
|
|
78
78
|
projectDir: string;
|
|
79
79
|
projectInput: string;
|
|
80
80
|
packageManager: PackageManagerId;
|
|
81
|
-
result: import("./scaffold.js").ScaffoldProjectResult;
|
|
82
81
|
nextSteps: string[];
|
|
82
|
+
result: {
|
|
83
|
+
warnings: string[];
|
|
84
|
+
packageManager: PackageManagerId;
|
|
85
|
+
projectDir: string;
|
|
86
|
+
selectedVariant: string | null;
|
|
87
|
+
templateId: string;
|
|
88
|
+
variables: import("./scaffold.js").ScaffoldTemplateVariables;
|
|
89
|
+
};
|
|
83
90
|
}>;
|
|
84
91
|
export {};
|
|
@@ -6,6 +6,29 @@ import { getPrimaryDevelopmentScript } from "./local-dev-presets.js";
|
|
|
6
6
|
import { getOptionalOnboardingNote, getOptionalOnboardingSteps, } from "./scaffold-onboarding.js";
|
|
7
7
|
import { OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, isBuiltInTemplateId, } from "./template-registry.js";
|
|
8
8
|
import { resolveOptionalInteractiveExternalLayerId, } from "./external-layer-selection.js";
|
|
9
|
+
function validateCreateProjectInput(projectInput) {
|
|
10
|
+
const normalizedProjectInput = projectInput.trim();
|
|
11
|
+
if (normalizedProjectInput.length === 0) {
|
|
12
|
+
throw new Error("Project directory is required. Usage: wp-typia create <project-dir> (or wp-typia <project-dir> when <project-dir> is the only positional argument).");
|
|
13
|
+
}
|
|
14
|
+
const normalizedProjectPath = path.normalize(normalizedProjectInput).replace(/[\\/]+$/u, "") ||
|
|
15
|
+
path.normalize(normalizedProjectInput);
|
|
16
|
+
if (normalizedProjectPath === "." || normalizedProjectPath === "..") {
|
|
17
|
+
throw new Error("`wp-typia create` requires a new project directory. Use an explicit child directory instead of `.` or `..`.");
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function collectProjectDirectoryWarnings(projectDir) {
|
|
21
|
+
const warnings = [];
|
|
22
|
+
const projectName = path.basename(projectDir);
|
|
23
|
+
if (/\s/u.test(projectName)) {
|
|
24
|
+
warnings.push(`Project directory "${projectName}" contains spaces. The generated next-step commands will be quoted, but a simple kebab-case directory name is usually easier to use with shells and downstream tooling.`);
|
|
25
|
+
}
|
|
26
|
+
const shellSensitiveCharacters = Array.from(new Set(projectName.match(/[^A-Za-z0-9._ -]/gu) ?? []));
|
|
27
|
+
if (shellSensitiveCharacters.length > 0) {
|
|
28
|
+
warnings.push(`Project directory "${projectName}" contains shell-sensitive characters (${shellSensitiveCharacters.join(", ")}). Prefer letters, numbers, ".", "_" and "-" when possible.`);
|
|
29
|
+
}
|
|
30
|
+
return warnings;
|
|
31
|
+
}
|
|
9
32
|
function templateUsesPersistenceSettings(templateId, options) {
|
|
10
33
|
if (templateId === "persistence") {
|
|
11
34
|
return true;
|
|
@@ -15,6 +38,19 @@ function templateUsesPersistenceSettings(templateId, options) {
|
|
|
15
38
|
}
|
|
16
39
|
return Boolean(options.dataStorageMode || options.persistencePolicy);
|
|
17
40
|
}
|
|
41
|
+
function templateSupportsPersistenceFlags(templateId) {
|
|
42
|
+
return templateId === "persistence" || templateId === "compound";
|
|
43
|
+
}
|
|
44
|
+
function validateCreateFlagContract(options) {
|
|
45
|
+
const { dataStorageMode, persistencePolicy, templateId, variant } = options;
|
|
46
|
+
if ((dataStorageMode || persistencePolicy) &&
|
|
47
|
+
!templateSupportsPersistenceFlags(templateId)) {
|
|
48
|
+
throw new Error("`--data-storage` and `--persistence-policy` are supported only for `wp-typia create --template persistence` or `--template compound`.");
|
|
49
|
+
}
|
|
50
|
+
if (variant && isBuiltInTemplateId(templateId)) {
|
|
51
|
+
throw new Error(`--variant is only supported for official external template configs. Received variant "${variant}" for built-in template "${templateId}".`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
18
54
|
function parseSelectableValue(label, value, isValue, allowedValues) {
|
|
19
55
|
if (isValue(value)) {
|
|
20
56
|
return value;
|
|
@@ -107,15 +143,19 @@ export async function runScaffoldFlow({ projectInput, cwd = process.cwd(), templ
|
|
|
107
143
|
externalLayerSource.trim().length > 0
|
|
108
144
|
? externalLayerSource.trim()
|
|
109
145
|
: undefined;
|
|
110
|
-
|
|
111
|
-
throw new Error("Project directory is required. Usage: wp-typia create <project-dir> (or wp-typia <project-dir> when <project-dir> is the only positional argument).");
|
|
112
|
-
}
|
|
146
|
+
validateCreateProjectInput(projectInput);
|
|
113
147
|
const resolvedTemplateId = await resolveTemplateId({
|
|
114
148
|
templateId,
|
|
115
149
|
yes,
|
|
116
150
|
isInteractive,
|
|
117
151
|
selectTemplate,
|
|
118
152
|
});
|
|
153
|
+
validateCreateFlagContract({
|
|
154
|
+
dataStorageMode,
|
|
155
|
+
persistencePolicy,
|
|
156
|
+
templateId: resolvedTemplateId,
|
|
157
|
+
variant,
|
|
158
|
+
});
|
|
119
159
|
const resolvedExternalLayerSelection = isBuiltInTemplateId(resolvedTemplateId) && isInteractive
|
|
120
160
|
? await resolveOptionalInteractiveExternalLayerId({
|
|
121
161
|
callerCwd: cwd,
|
|
@@ -237,7 +277,6 @@ export async function runScaffoldFlow({ projectInput, cwd = process.cwd(), templ
|
|
|
237
277
|
projectDir,
|
|
238
278
|
projectInput,
|
|
239
279
|
packageManager: resolvedPackageManager,
|
|
240
|
-
result,
|
|
241
280
|
nextSteps: getNextSteps({
|
|
242
281
|
projectInput,
|
|
243
282
|
projectDir,
|
|
@@ -245,6 +284,10 @@ export async function runScaffoldFlow({ projectInput, cwd = process.cwd(), templ
|
|
|
245
284
|
noInstall,
|
|
246
285
|
templateId: resolvedTemplateId,
|
|
247
286
|
}),
|
|
287
|
+
result: {
|
|
288
|
+
...result,
|
|
289
|
+
warnings: [...result.warnings, ...collectProjectDirectoryWarnings(projectDir)],
|
|
290
|
+
},
|
|
248
291
|
};
|
|
249
292
|
}
|
|
250
293
|
finally {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { FixturesOptions, FuzzOptions } from './migration-command-surface.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generate or refresh migration fixtures for one or more legacy edges.
|
|
4
|
+
*
|
|
5
|
+
* @param projectDir Absolute or relative project directory containing the migration workspace.
|
|
6
|
+
* @param options Fixture generation scope and refresh options.
|
|
7
|
+
* @returns Generated and skipped legacy versions.
|
|
8
|
+
*/
|
|
9
|
+
export declare function fixturesProjectMigrations(projectDir: string, { all, confirmOverwrite, force, fromMigrationVersion, isInteractive, renderLine, toMigrationVersion, }?: FixturesOptions): {
|
|
10
|
+
generatedVersions: string[];
|
|
11
|
+
skippedVersions: string[];
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Run seeded migration fuzz verification against generated fuzz artifacts.
|
|
15
|
+
*
|
|
16
|
+
* @param projectDir Absolute or relative project directory containing the migration workspace.
|
|
17
|
+
* @param options Fuzz scope, iteration count, seed, and console rendering options.
|
|
18
|
+
* @returns Fuzzed legacy versions and the effective seed.
|
|
19
|
+
*/
|
|
20
|
+
export declare function fuzzProjectMigrations(projectDir: string, { all, fromMigrationVersion, iterations, renderLine, seed, }?: FuzzOptions): {
|
|
21
|
+
fuzzedVersions: string[];
|
|
22
|
+
seed: number | undefined;
|
|
23
|
+
};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { execFileSync } from 'node:child_process';
|
|
4
|
+
import { createMigrationDiff } from './migration-diff.js';
|
|
5
|
+
import { ensureEdgeFixtureFile, } from './migration-fixtures.js';
|
|
6
|
+
import { collectFixtureTargets, formatScaffoldCommand, getSelectedEntriesByBlock, isLegacySingleBlockProject, resolveLegacyVersions, } from './migration-planning.js';
|
|
7
|
+
import { assertRuleHasNoTodos, getGeneratedDirForBlock, loadMigrationProject, } from './migration-project.js';
|
|
8
|
+
import { getLocalTsxBinary, isInteractiveTerminal, resolveTargetMigrationVersion, } from './migration-utils.js';
|
|
9
|
+
/**
|
|
10
|
+
* Generate or refresh migration fixtures for one or more legacy edges.
|
|
11
|
+
*
|
|
12
|
+
* @param projectDir Absolute or relative project directory containing the migration workspace.
|
|
13
|
+
* @param options Fixture generation scope and refresh options.
|
|
14
|
+
* @returns Generated and skipped legacy versions.
|
|
15
|
+
*/
|
|
16
|
+
export function fixturesProjectMigrations(projectDir, { all = false, confirmOverwrite, force = false, fromMigrationVersion, isInteractive = isInteractiveTerminal(), renderLine = console.log, toMigrationVersion = 'current', } = {}) {
|
|
17
|
+
const state = loadMigrationProject(projectDir);
|
|
18
|
+
const targetMigrationVersion = resolveTargetMigrationVersion(state.config.currentMigrationVersion, toMigrationVersion);
|
|
19
|
+
const targetVersions = resolveLegacyVersions(state, { all, fromMigrationVersion });
|
|
20
|
+
if (targetVersions.length === 0) {
|
|
21
|
+
renderLine('No legacy migration versions configured for fixture generation.');
|
|
22
|
+
return { generatedVersions: [], skippedVersions: [] };
|
|
23
|
+
}
|
|
24
|
+
const generatedVersions = [];
|
|
25
|
+
const skippedVersions = [];
|
|
26
|
+
const fixtureTargets = collectFixtureTargets(state, targetVersions, targetMigrationVersion);
|
|
27
|
+
if (force) {
|
|
28
|
+
const overwriteTargets = fixtureTargets.filter(({ fixturePath }) => fs.existsSync(fixturePath));
|
|
29
|
+
if (isInteractive && overwriteTargets.length > 0) {
|
|
30
|
+
const confirmed = confirmOverwrite?.(`About to overwrite ${overwriteTargets.length} existing migration fixture file(s). Continue?`) ??
|
|
31
|
+
promptForConfirmation(`About to overwrite ${overwriteTargets.length} existing migration fixture file(s). Continue?`);
|
|
32
|
+
if (!confirmed) {
|
|
33
|
+
renderLine(`Cancelled fixture refresh. Kept ${overwriteTargets.length} existing fixture file(s).`);
|
|
34
|
+
return {
|
|
35
|
+
generatedVersions,
|
|
36
|
+
skippedVersions: overwriteTargets.map(({ scopedLabel }) => scopedLabel),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
for (const { block, fixturePath, scopedLabel, version } of fixtureTargets) {
|
|
42
|
+
const existed = fs.existsSync(fixturePath);
|
|
43
|
+
const diff = createMigrationDiff(state, block, version, targetMigrationVersion);
|
|
44
|
+
const result = ensureEdgeFixtureFile(projectDir, block, version, targetMigrationVersion, diff, { force });
|
|
45
|
+
if (result.written) {
|
|
46
|
+
generatedVersions.push(scopedLabel);
|
|
47
|
+
renderLine(`${existed ? 'Refreshed' : 'Generated'} fixture ${path.relative(projectDir, fixturePath)}`);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
skippedVersions.push(scopedLabel);
|
|
51
|
+
renderLine(`Preserved existing fixture ${path.relative(projectDir, fixturePath)} (use --force to refresh)`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
generatedVersions,
|
|
56
|
+
skippedVersions,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Run seeded migration fuzz verification against generated fuzz artifacts.
|
|
61
|
+
*
|
|
62
|
+
* @param projectDir Absolute or relative project directory containing the migration workspace.
|
|
63
|
+
* @param options Fuzz scope, iteration count, seed, and console rendering options.
|
|
64
|
+
* @returns Fuzzed legacy versions and the effective seed.
|
|
65
|
+
*/
|
|
66
|
+
export function fuzzProjectMigrations(projectDir, { all = false, fromMigrationVersion, iterations = 25, renderLine = console.log, seed, } = {}) {
|
|
67
|
+
const state = loadMigrationProject(projectDir);
|
|
68
|
+
const targetVersions = resolveLegacyVersions(state, { all, fromMigrationVersion });
|
|
69
|
+
const blockEntries = getSelectedEntriesByBlock(state, targetVersions, 'fuzz');
|
|
70
|
+
const legacySingleBlock = isLegacySingleBlockProject(state);
|
|
71
|
+
if (targetVersions.length === 0) {
|
|
72
|
+
renderLine('No legacy migration versions configured for fuzzing.');
|
|
73
|
+
return { fuzzedVersions: [], seed };
|
|
74
|
+
}
|
|
75
|
+
const tsxBinary = getLocalTsxBinary(projectDir);
|
|
76
|
+
for (const [blockKey, entries] of Object.entries(blockEntries)) {
|
|
77
|
+
const block = state.blocks.find((entry) => entry.key === blockKey);
|
|
78
|
+
if (!block || entries.length === 0) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
for (const entry of entries) {
|
|
82
|
+
assertRuleHasNoTodos(projectDir, block, entry.fromVersion, state.config.currentMigrationVersion);
|
|
83
|
+
}
|
|
84
|
+
const fuzzScriptPath = path.join(getGeneratedDirForBlock(state.paths, block), 'fuzz.ts');
|
|
85
|
+
if (!fs.existsSync(fuzzScriptPath)) {
|
|
86
|
+
const selectedVersionsForBlock = entries.map((entry) => entry.fromVersion);
|
|
87
|
+
throw new Error(`Generated fuzz script is missing for ${block.blockName} (${selectedVersionsForBlock.join(', ')}). ` +
|
|
88
|
+
`Run \`${formatScaffoldCommand(selectedVersionsForBlock)}\` first, then \`wp-typia migrate doctor --all\` if the workspace should already be scaffolded.`);
|
|
89
|
+
}
|
|
90
|
+
const selectedVersionsForBlock = entries.map((entry) => entry.fromVersion);
|
|
91
|
+
const args = [
|
|
92
|
+
fuzzScriptPath,
|
|
93
|
+
...(all ? ['--all'] : ['--from-migration-version', selectedVersionsForBlock[0]]),
|
|
94
|
+
'--iterations',
|
|
95
|
+
String(iterations),
|
|
96
|
+
...(seed === undefined ? [] : ['--seed', String(seed)]),
|
|
97
|
+
];
|
|
98
|
+
execFileSync(tsxBinary, args, {
|
|
99
|
+
cwd: projectDir,
|
|
100
|
+
shell: process.platform === 'win32',
|
|
101
|
+
stdio: 'inherit',
|
|
102
|
+
});
|
|
103
|
+
renderLine(legacySingleBlock
|
|
104
|
+
? `Fuzzed migrations for ${selectedVersionsForBlock.join(', ')}`
|
|
105
|
+
: `Fuzzed ${block.blockName} migrations for ${selectedVersionsForBlock.join(', ')}`);
|
|
106
|
+
}
|
|
107
|
+
return { fuzzedVersions: targetVersions, seed };
|
|
108
|
+
}
|
|
109
|
+
function promptForConfirmation(message) {
|
|
110
|
+
process.stdout.write(`${message} [y/N]: `);
|
|
111
|
+
const buffer = Buffer.alloc(1);
|
|
112
|
+
let answer = '';
|
|
113
|
+
while (true) {
|
|
114
|
+
const bytesRead = fs.readSync(process.stdin.fd, buffer, 0, 1, null);
|
|
115
|
+
if (bytesRead === 0) {
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
const char = buffer.toString('utf8', 0, bytesRead);
|
|
119
|
+
if (char === '\n' || char === '\r') {
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
answer += char;
|
|
123
|
+
}
|
|
124
|
+
const normalized = answer.trim().toLowerCase();
|
|
125
|
+
return normalized === 'y' || normalized === 'yes';
|
|
126
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { VerifyOptions } from './migration-command-surface.js';
|
|
2
|
+
/**
|
|
3
|
+
* Run deterministic migration verification against generated fixtures.
|
|
4
|
+
*
|
|
5
|
+
* @param projectDir Absolute or relative project directory containing the migration workspace.
|
|
6
|
+
* @param options Verification scope and console rendering options.
|
|
7
|
+
* @returns Verified legacy versions.
|
|
8
|
+
*/
|
|
9
|
+
export declare function verifyProjectMigrations(projectDir: string, { all, fromMigrationVersion, renderLine, }?: VerifyOptions): {
|
|
10
|
+
verifiedVersions: string[];
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Validate the migration workspace without mutating files.
|
|
14
|
+
*
|
|
15
|
+
* @param projectDir Absolute or relative project directory containing the migration workspace.
|
|
16
|
+
* @param options Doctor scope and console rendering options.
|
|
17
|
+
* @returns Structured doctor check results for the selected legacy versions.
|
|
18
|
+
*/
|
|
19
|
+
export declare function doctorProjectMigrations(projectDir: string, { all, fromMigrationVersion, renderLine, }?: VerifyOptions): {
|
|
20
|
+
checkedVersions: string[];
|
|
21
|
+
checks: {
|
|
22
|
+
detail: string;
|
|
23
|
+
label: string;
|
|
24
|
+
status: "fail" | "pass";
|
|
25
|
+
}[];
|
|
26
|
+
};
|