@wp-typia/project-tools 0.16.7 → 0.16.9
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 +26 -1
- package/dist/runtime/block-generator-service.d.ts +9 -1
- package/dist/runtime/block-generator-service.js +137 -16
- package/dist/runtime/block-generator-tool-contract.d.ts +93 -0
- package/dist/runtime/block-generator-tool-contract.js +157 -0
- package/dist/runtime/built-in-block-artifacts.js +171 -423
- package/dist/runtime/built-in-block-code-artifacts.js +96 -46
- package/dist/runtime/built-in-block-code-templates.d.ts +36 -0
- package/dist/runtime/built-in-block-code-templates.js +2233 -0
- package/dist/runtime/cli-add-block.d.ts +2 -1
- package/dist/runtime/cli-add-block.js +163 -25
- package/dist/runtime/cli-add-shared.d.ts +7 -0
- package/dist/runtime/cli-add-shared.js +4 -6
- package/dist/runtime/cli-add-workspace.js +56 -17
- package/dist/runtime/cli-core.d.ts +4 -0
- package/dist/runtime/cli-core.js +3 -0
- package/dist/runtime/cli-diagnostics.d.ts +58 -0
- package/dist/runtime/cli-diagnostics.js +101 -0
- package/dist/runtime/cli-doctor.d.ts +2 -1
- package/dist/runtime/cli-doctor.js +16 -5
- package/dist/runtime/cli-help.js +4 -4
- package/dist/runtime/cli-scaffold.d.ts +5 -1
- package/dist/runtime/cli-scaffold.js +138 -111
- package/dist/runtime/external-layer-selection.d.ts +14 -0
- package/dist/runtime/external-layer-selection.js +35 -0
- package/dist/runtime/index.d.ts +4 -2
- package/dist/runtime/index.js +2 -1
- package/dist/runtime/migration-render.d.ts +23 -1
- package/dist/runtime/migration-render.js +58 -10
- package/dist/runtime/migration-ui-capability.js +17 -8
- package/dist/runtime/migration-utils.d.ts +7 -6
- package/dist/runtime/migration-utils.js +76 -73
- package/dist/runtime/migrations.js +2 -2
- package/dist/runtime/object-utils.d.ts +8 -1
- package/dist/runtime/object-utils.js +21 -1
- package/dist/runtime/scaffold-apply-utils.d.ts +14 -2
- package/dist/runtime/scaffold-apply-utils.js +19 -6
- package/dist/runtime/scaffold-repository-reference.d.ts +22 -0
- package/dist/runtime/scaffold-repository-reference.js +119 -0
- package/dist/runtime/scaffold.d.ts +5 -1
- package/dist/runtime/scaffold.js +15 -37
- package/dist/runtime/template-builtins.d.ts +11 -1
- package/dist/runtime/template-builtins.js +118 -25
- package/dist/runtime/template-layers.d.ts +37 -0
- package/dist/runtime/template-layers.js +184 -0
- package/dist/runtime/template-render.d.ts +28 -2
- package/dist/runtime/template-render.js +122 -55
- package/dist/runtime/template-source.d.ts +23 -5
- package/dist/runtime/template-source.js +296 -217
- package/package.json +8 -3
- package/templates/_shared/base/src/validator-toolkit.ts.mustache +2 -2
- package/templates/_shared/compound/core/scripts/add-compound-child.ts.mustache +58 -12
- package/templates/_shared/migration-ui/common/src/migrations/helpers.ts +19 -47
- package/templates/_shared/migration-ui/common/src/migrations/index.ts +40 -11
|
@@ -120,14 +120,34 @@ export function renderMigrationRuleFile({ block, currentAttributes, currentTypeN
|
|
|
120
120
|
lines.push("");
|
|
121
121
|
return `${lines.join("\n")}\n`;
|
|
122
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Renders the generated migration registry module for a block target.
|
|
125
|
+
*
|
|
126
|
+
* Prefers manifest wrapper modules when they are available in the project,
|
|
127
|
+
* while still validating the imported manifest before the registry consumes it.
|
|
128
|
+
*
|
|
129
|
+
* @param state The resolved migration project state.
|
|
130
|
+
* @param blockKey The stable key for the block whose registry is being generated.
|
|
131
|
+
* @param entries The generated migration entries to include in the registry.
|
|
132
|
+
* @returns The generated TypeScript source code for the migration registry file.
|
|
133
|
+
*/
|
|
123
134
|
export function renderMigrationRegistryFile(state, blockKey, entries) {
|
|
124
135
|
const block = state.blocks.find((entry) => entry.key === blockKey);
|
|
125
136
|
if (!block) {
|
|
126
137
|
throw new Error(`Unknown migration block target: ${blockKey}`);
|
|
127
138
|
}
|
|
139
|
+
const generatedDir = getGeneratedDir(block, state);
|
|
140
|
+
const currentManifestWrapperCandidates = [
|
|
141
|
+
block.manifestFile.replace(/typia\.manifest\.json$/u, "manifest-document.ts"),
|
|
142
|
+
path.join(path.dirname(block.typesFile), "manifest-document.ts"),
|
|
143
|
+
].filter((candidate, index, allCandidates) => candidate !== block.manifestFile && allCandidates.indexOf(candidate) === index);
|
|
144
|
+
const currentManifestWrapperFile = currentManifestWrapperCandidates.find((candidate) => fs.existsSync(path.join(state.projectDir, candidate))) ?? null;
|
|
145
|
+
const currentManifestSourceFile = currentManifestWrapperFile ?? block.manifestFile;
|
|
146
|
+
const currentManifestImport = normalizeImportPath(path.relative(generatedDir, path.join(state.projectDir, currentManifestSourceFile)), currentManifestWrapperFile !== null);
|
|
128
147
|
const imports = [
|
|
129
|
-
`import
|
|
148
|
+
`import rawCurrentManifest from "${currentManifestImport}";`,
|
|
130
149
|
`import type { ManifestDocument, MigrationRiskSummary } from "${normalizeImportPath(path.relative(getGeneratedDir(block, state), path.join(state.projectDir, "src", "migrations", "helpers.ts")), true)}";`,
|
|
150
|
+
`import { parseManifestDocument } from "@wp-typia/block-runtime/editor";`,
|
|
131
151
|
];
|
|
132
152
|
const body = [];
|
|
133
153
|
entries.forEach(({ entry, riskSummary }, index) => {
|
|
@@ -135,7 +155,7 @@ export function renderMigrationRegistryFile(state, blockKey, entries) {
|
|
|
135
155
|
imports.push(`import * as rule_${index} from "${entry.ruleImport}";`);
|
|
136
156
|
body.push(`\t{`);
|
|
137
157
|
body.push(`\t\tfromMigrationVersion: "${entry.fromVersion}",`);
|
|
138
|
-
body.push(`\t\tmanifest: manifest_${index},`);
|
|
158
|
+
body.push(`\t\tmanifest: parseManifestDocument<ManifestDocument>(manifest_${index}),`);
|
|
139
159
|
body.push(`\t\triskSummary: ${JSON.stringify(riskSummary, null, "\t").replace(/\n/g, "\n\t\t")},`);
|
|
140
160
|
body.push(`\t\trule: rule_${index},`);
|
|
141
161
|
body.push(`\t},`);
|
|
@@ -159,7 +179,7 @@ export const migrationRegistry: {
|
|
|
159
179
|
entries: MigrationRegistryEntry[];
|
|
160
180
|
} = {
|
|
161
181
|
currentMigrationVersion: "${state.config.currentMigrationVersion}",
|
|
162
|
-
currentManifest:
|
|
182
|
+
currentManifest: parseManifestDocument<ManifestDocument>(rawCurrentManifest),
|
|
163
183
|
entries: [
|
|
164
184
|
${body.join("\n")}
|
|
165
185
|
],
|
|
@@ -168,23 +188,51 @@ ${body.join("\n")}
|
|
|
168
188
|
export default migrationRegistry;
|
|
169
189
|
`;
|
|
170
190
|
}
|
|
171
|
-
|
|
191
|
+
/**
|
|
192
|
+
* Renders the generated deprecated module for a block target.
|
|
193
|
+
*
|
|
194
|
+
* The emitted module exposes the ordered deprecation array consumed by block
|
|
195
|
+
* registration and migration helpers.
|
|
196
|
+
*
|
|
197
|
+
* @param state The resolved migration project state.
|
|
198
|
+
* @param blockKey The stable key for the block whose deprecated entries are being generated.
|
|
199
|
+
* @param entries The migration entries that define deprecated manifest versions.
|
|
200
|
+
* @returns The generated TypeScript source code for the deprecated module.
|
|
201
|
+
*/
|
|
202
|
+
export function renderGeneratedDeprecatedFile(state, blockKey, entries) {
|
|
203
|
+
const block = state.blocks.find((entry) => entry.key === blockKey);
|
|
204
|
+
if (!block) {
|
|
205
|
+
throw new Error(`Unknown migration block target: ${blockKey}`);
|
|
206
|
+
}
|
|
207
|
+
const currentTypeName = typeof block.currentManifest.sourceType === "string" &&
|
|
208
|
+
block.currentManifest.sourceType.length > 0
|
|
209
|
+
? block.currentManifest.sourceType
|
|
210
|
+
: "Record<string, unknown>";
|
|
211
|
+
const hasNamedCurrentType = currentTypeName !== "Record<string, unknown>";
|
|
212
|
+
const generatedDir = getGeneratedDir(block, state);
|
|
213
|
+
const typesImport = normalizeImportPath(path.relative(generatedDir, path.join(state.projectDir, block.typesFile)), true);
|
|
172
214
|
if (entries.length === 0) {
|
|
173
215
|
return `/* eslint-disable prettier/prettier */
|
|
174
|
-
import type {
|
|
216
|
+
import type { BlockDeprecationList } from "@wp-typia/block-types/blocks/registration";
|
|
217
|
+
${hasNamedCurrentType ? `import type { ${currentTypeName} } from "${typesImport}";\n` : ""}
|
|
175
218
|
|
|
176
|
-
export const deprecated:
|
|
219
|
+
export const deprecated: BlockDeprecationList<${currentTypeName}> = [];
|
|
177
220
|
`;
|
|
178
221
|
}
|
|
179
|
-
const imports = [
|
|
222
|
+
const imports = [
|
|
223
|
+
`import type { BlockConfiguration, BlockDeprecationList } from "@wp-typia/block-types/blocks/registration";`,
|
|
224
|
+
...(hasNamedCurrentType
|
|
225
|
+
? [`import type { ${currentTypeName} } from "${typesImport}";`]
|
|
226
|
+
: []),
|
|
227
|
+
];
|
|
180
228
|
const definitions = [];
|
|
181
229
|
const arrayEntries = [];
|
|
182
230
|
entries.forEach((entry, index) => {
|
|
183
231
|
imports.push(`import block_${index} from "${entry.blockJsonImport}";`);
|
|
184
232
|
imports.push(`import save_${index} from "${entry.saveImport}";`);
|
|
185
233
|
imports.push(`import * as rule_${index} from "${entry.ruleImport}";`);
|
|
186
|
-
definitions.push(`const deprecated_${index}:
|
|
187
|
-
definitions.push(`\tattributes: (block_${index}.attributes ?? {}) as
|
|
234
|
+
definitions.push(`const deprecated_${index}: BlockDeprecationList<${currentTypeName}>[number] = {`);
|
|
235
|
+
definitions.push(`\tattributes: (block_${index}.attributes ?? {}) as BlockConfiguration["attributes"],`);
|
|
188
236
|
definitions.push(`\tsave: save_${index} as BlockConfiguration["save"],`);
|
|
189
237
|
definitions.push(`\tmigrate(attributes: Record<string, unknown>) {`);
|
|
190
238
|
definitions.push(`\t\treturn rule_${index}.migrate(attributes);`);
|
|
@@ -197,7 +245,7 @@ ${imports.join("\n")}
|
|
|
197
245
|
|
|
198
246
|
${definitions.join("\n\n")}
|
|
199
247
|
|
|
200
|
-
export const deprecated:
|
|
248
|
+
export const deprecated: BlockDeprecationList<${currentTypeName}> = [${arrayEntries.join(", ")}];
|
|
201
249
|
`;
|
|
202
250
|
}
|
|
203
251
|
export function renderGeneratedMigrationIndexFile(state, entries) {
|
|
@@ -6,6 +6,8 @@ import { seedProjectMigrations } from "./migrations.js";
|
|
|
6
6
|
import { copyInterpolatedDirectory } from "./template-render.js";
|
|
7
7
|
import { SHARED_MIGRATION_UI_TEMPLATE_ROOT, } from "./template-registry.js";
|
|
8
8
|
const INITIAL_MIGRATION_VERSION = "v1";
|
|
9
|
+
const BLOCK_METADATA_IMPORT_LINE = "import metadata from './block-metadata';";
|
|
10
|
+
const LEGACY_BLOCK_JSON_IMPORT_LINE = "import metadata from './block.json';";
|
|
9
11
|
async function mutatePackageJson(projectDir, mutate) {
|
|
10
12
|
const packageJsonPath = path.join(projectDir, "package.json");
|
|
11
13
|
const packageJson = JSON.parse(await fsp.readFile(packageJsonPath, "utf8"));
|
|
@@ -38,6 +40,13 @@ function injectBefore(source, needle, insertion) {
|
|
|
38
40
|
}
|
|
39
41
|
return source.replace(needle, `${insertion}\n${needle}`);
|
|
40
42
|
}
|
|
43
|
+
function injectAfterBlockMetadataImport(source, insertion) {
|
|
44
|
+
let nextSource = injectAfter(source, BLOCK_METADATA_IMPORT_LINE, insertion);
|
|
45
|
+
if (nextSource !== source) {
|
|
46
|
+
return nextSource;
|
|
47
|
+
}
|
|
48
|
+
return injectAfter(source, LEGACY_BLOCK_JSON_IMPORT_LINE, insertion);
|
|
49
|
+
}
|
|
41
50
|
function buildMigrationBlocks(templateId, variables) {
|
|
42
51
|
if (templateId === "compound") {
|
|
43
52
|
return [
|
|
@@ -74,11 +83,11 @@ async function applySingleBlockPatches(projectDir, variables) {
|
|
|
74
83
|
const editPath = path.join(projectDir, "src", "edit.tsx");
|
|
75
84
|
const indexPath = path.join(projectDir, "src", "index.tsx");
|
|
76
85
|
const deprecatedImport = `import { deprecated } from './migrations/generated/${variables.slugKebabCase}/deprecated';`;
|
|
77
|
-
const deprecatedLine =
|
|
86
|
+
const deprecatedLine = " deprecated,";
|
|
78
87
|
const dashboardImport = `import { MigrationDashboard } from './admin/migration-dashboard';`;
|
|
79
88
|
const migrationPanel = `\n <PanelBody title={__('Migration Manager', '${variables.textDomain}')}>\n <MigrationDashboard />\n </PanelBody>\n </InspectorControls>`;
|
|
80
89
|
await patchFile(indexPath, (source) => {
|
|
81
|
-
let nextSource =
|
|
90
|
+
let nextSource = injectAfterBlockMetadataImport(source, deprecatedImport);
|
|
82
91
|
nextSource = injectBefore(nextSource, " edit: Edit,", deprecatedLine);
|
|
83
92
|
return nextSource;
|
|
84
93
|
});
|
|
@@ -94,13 +103,13 @@ async function applyCompoundPatches(projectDir, variables) {
|
|
|
94
103
|
const childIndexPath = path.join(projectDir, "src", "blocks", `${variables.slugKebabCase}-item`, "index.tsx");
|
|
95
104
|
const addChildScriptPath = path.join(projectDir, "scripts", "add-compound-child.ts");
|
|
96
105
|
await patchFile(parentIndexPath, (source) => {
|
|
97
|
-
let nextSource =
|
|
98
|
-
nextSource = injectBefore(nextSource, "\tedit: Edit,",
|
|
106
|
+
let nextSource = injectAfterBlockMetadataImport(source, `import { deprecated } from '../../migrations/generated/${variables.slugKebabCase}/deprecated';`);
|
|
107
|
+
nextSource = injectBefore(nextSource, "\tedit: Edit,", "\tdeprecated,");
|
|
99
108
|
return nextSource;
|
|
100
109
|
});
|
|
101
110
|
await patchFile(childIndexPath, (source) => {
|
|
102
|
-
let nextSource =
|
|
103
|
-
nextSource = injectBefore(nextSource, "\tedit: Edit,",
|
|
111
|
+
let nextSource = injectAfterBlockMetadataImport(source, `import { deprecated } from '../../migrations/generated/${variables.slugKebabCase}-item/deprecated';`);
|
|
112
|
+
nextSource = injectBefore(nextSource, "\tedit: Edit,", "\tdeprecated,");
|
|
104
113
|
return nextSource;
|
|
105
114
|
});
|
|
106
115
|
await patchFile(parentEditPath, (source) => {
|
|
@@ -110,8 +119,8 @@ async function applyCompoundPatches(projectDir, variables) {
|
|
|
110
119
|
});
|
|
111
120
|
await patchFile(addChildScriptPath, (source) => {
|
|
112
121
|
let nextSource = injectAfter(source, "const PROJECT_ROOT = process.cwd();", "const MIGRATION_CONFIG_FILE = path.join( PROJECT_ROOT, 'src', 'migrations', 'config.ts' );");
|
|
113
|
-
nextSource = nextSource
|
|
114
|
-
nextSource = nextSource.replace("
|
|
122
|
+
nextSource = injectAfterBlockMetadataImport(nextSource, "import { deprecated } from '../../migrations/generated/${ childFolderSlug }/deprecated';");
|
|
123
|
+
nextSource = nextSource.replace("\\t\\tedit: Edit,\n\\t\\tsave: Save,", "\\t\\tdeprecated,\n\\t\\tedit: Edit,\n\\t\\tsave: Save,");
|
|
115
124
|
nextSource = injectAfter(nextSource, "function insertBeforeMarker( filePath: string, marker: string, insertionLines: string[] ) {", "");
|
|
116
125
|
if (!nextSource.includes("function appendMigrationBlockConfig")) {
|
|
117
126
|
const appendMigrationHelper = [
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type { JsonValue, ManifestAttribute, JsonObject } from
|
|
2
|
-
|
|
3
|
-
export
|
|
4
|
-
export declare function
|
|
5
|
-
export declare function
|
|
1
|
+
import type { JsonValue, ManifestAttribute, JsonObject } from './migration-types.js';
|
|
2
|
+
import { type UnknownRecord } from './object-utils.js';
|
|
3
|
+
export { cloneJsonValue } from './json-utils.js';
|
|
4
|
+
export declare function getValueAtPath<TRecord extends UnknownRecord>(input: TRecord, pathLabel: string): unknown;
|
|
5
|
+
export declare function setValueAtPath<TRecord extends UnknownRecord>(input: TRecord, pathLabel: string, value: unknown): TRecord;
|
|
6
|
+
export declare function deleteValueAtPath<TRecord extends UnknownRecord>(input: TRecord, pathLabel: string): TRecord;
|
|
6
7
|
export declare function createFixtureScalarValue(pathLabel: string): JsonValue;
|
|
7
8
|
export declare function createTransformFixtureValue(attribute: ManifestAttribute | null | undefined, pathLabel: string): JsonValue;
|
|
8
9
|
export declare function readJson<T = unknown>(filePath: string): T;
|
|
@@ -11,7 +12,7 @@ export declare function copyFile(sourcePath: string, targetPath: string): void;
|
|
|
11
12
|
export declare function sanitizeSaveSnapshotSource(source: string): string;
|
|
12
13
|
export declare function sanitizeSnapshotBlockJson(blockJson: JsonObject): JsonObject;
|
|
13
14
|
export declare function runProjectScriptIfPresent(projectDir: string, scriptName: string): void;
|
|
14
|
-
export declare function detectPackageManagerId(projectDir: string):
|
|
15
|
+
export declare function detectPackageManagerId(projectDir: string): 'bun' | 'npm' | 'pnpm' | 'yarn';
|
|
15
16
|
export declare function getLocalTsxBinary(projectDir: string): string;
|
|
16
17
|
/**
|
|
17
18
|
* Returns whether isInteractiveTerminal() is running with both stdin and stdout
|
|
@@ -1,96 +1,99 @@
|
|
|
1
|
-
import fs from
|
|
2
|
-
import path from
|
|
3
|
-
import { execSync } from
|
|
4
|
-
import { formatRunScript } from
|
|
5
|
-
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { execSync } from 'node:child_process';
|
|
4
|
+
import { formatRunScript } from './package-managers.js';
|
|
5
|
+
import { isPlainObject } from './object-utils.js';
|
|
6
|
+
export { cloneJsonValue } from './json-utils.js';
|
|
6
7
|
const MIGRATION_VERSION_LABEL_PATTERN = /^v([1-9]\d*)$/;
|
|
7
8
|
const LEGACY_SEMVER_MIGRATION_VERSION_PATTERN = /^\d+\.\d+\.\d+$/;
|
|
8
9
|
export function getValueAtPath(input, pathLabel) {
|
|
9
10
|
return String(pathLabel)
|
|
10
|
-
.split(
|
|
11
|
-
.reduce((value, segment) => value
|
|
11
|
+
.split('.')
|
|
12
|
+
.reduce((value, segment) => (isPlainObject(value) ? value[segment] : undefined), input);
|
|
12
13
|
}
|
|
13
14
|
export function setValueAtPath(input, pathLabel, value) {
|
|
14
|
-
const segments = String(pathLabel).split(
|
|
15
|
+
const segments = String(pathLabel).split('.');
|
|
15
16
|
let target = input;
|
|
16
17
|
while (segments.length > 1) {
|
|
17
18
|
const segment = segments.shift();
|
|
18
19
|
if (!segment) {
|
|
19
20
|
continue;
|
|
20
21
|
}
|
|
21
|
-
if (!
|
|
22
|
+
if (!isPlainObject(target[segment])) {
|
|
22
23
|
target[segment] = {};
|
|
23
24
|
}
|
|
24
25
|
target = target[segment];
|
|
25
26
|
}
|
|
26
27
|
target[segments[0]] = value;
|
|
28
|
+
return input;
|
|
27
29
|
}
|
|
28
30
|
export function deleteValueAtPath(input, pathLabel) {
|
|
29
|
-
const segments = String(pathLabel).split(
|
|
31
|
+
const segments = String(pathLabel).split('.');
|
|
30
32
|
let target = input;
|
|
31
33
|
while (segments.length > 1) {
|
|
32
34
|
const segment = segments.shift();
|
|
33
|
-
if (!segment || !target[segment]
|
|
34
|
-
return;
|
|
35
|
+
if (!segment || !isPlainObject(target[segment])) {
|
|
36
|
+
return input;
|
|
35
37
|
}
|
|
36
38
|
target = target[segment];
|
|
37
39
|
}
|
|
38
40
|
delete target[segments[0]];
|
|
41
|
+
return input;
|
|
39
42
|
}
|
|
40
43
|
export function createFixtureScalarValue(pathLabel) {
|
|
41
44
|
const normalized = String(pathLabel).toLowerCase();
|
|
42
|
-
if (normalized.includes(
|
|
43
|
-
return
|
|
45
|
+
if (normalized.includes('id')) {
|
|
46
|
+
return '00000000-0000-4000-8000-000000000000';
|
|
44
47
|
}
|
|
45
|
-
if (normalized.includes(
|
|
48
|
+
if (normalized.includes('count') || normalized.includes('number')) {
|
|
46
49
|
return 1;
|
|
47
50
|
}
|
|
48
|
-
if (normalized.includes(
|
|
51
|
+
if (normalized.includes('visible') || normalized.startsWith('is')) {
|
|
49
52
|
return true;
|
|
50
53
|
}
|
|
51
54
|
return `legacy:${pathLabel}`;
|
|
52
55
|
}
|
|
53
56
|
export function createTransformFixtureValue(attribute, pathLabel) {
|
|
54
57
|
switch (attribute?.ts?.kind) {
|
|
55
|
-
case
|
|
56
|
-
return
|
|
57
|
-
case
|
|
58
|
-
return
|
|
59
|
-
case
|
|
60
|
-
return { kind:
|
|
58
|
+
case 'number':
|
|
59
|
+
return '42';
|
|
60
|
+
case 'boolean':
|
|
61
|
+
return '1';
|
|
62
|
+
case 'union':
|
|
63
|
+
return { kind: 'unknown' };
|
|
61
64
|
default:
|
|
62
65
|
return createFixtureScalarValue(pathLabel);
|
|
63
66
|
}
|
|
64
67
|
}
|
|
65
68
|
export function readJson(filePath) {
|
|
66
|
-
return JSON.parse(fs.readFileSync(filePath,
|
|
69
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
67
70
|
}
|
|
68
71
|
export function renderPhpValue(value, indentLevel) {
|
|
69
|
-
const indent =
|
|
70
|
-
const nestedIndent =
|
|
72
|
+
const indent = '\t'.repeat(indentLevel);
|
|
73
|
+
const nestedIndent = '\t'.repeat(indentLevel + 1);
|
|
71
74
|
if (value === null) {
|
|
72
|
-
return
|
|
75
|
+
return 'null';
|
|
73
76
|
}
|
|
74
|
-
if (typeof value ===
|
|
75
|
-
return `'${value.replace(/\\/g,
|
|
77
|
+
if (typeof value === 'string') {
|
|
78
|
+
return `'${value.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
|
|
76
79
|
}
|
|
77
|
-
if (typeof value ===
|
|
80
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
78
81
|
return String(value);
|
|
79
82
|
}
|
|
80
83
|
if (Array.isArray(value)) {
|
|
81
84
|
if (value.length === 0) {
|
|
82
|
-
return
|
|
85
|
+
return '[]';
|
|
83
86
|
}
|
|
84
87
|
const items = value.map((item) => `${nestedIndent}${renderPhpValue(item, indentLevel + 1)}`);
|
|
85
|
-
return `[\n${items.join(
|
|
88
|
+
return `[\n${items.join(',\n')}\n${indent}]`;
|
|
86
89
|
}
|
|
87
|
-
if (typeof value ===
|
|
90
|
+
if (typeof value === 'object') {
|
|
88
91
|
const entries = Object.entries(value);
|
|
89
92
|
if (entries.length === 0) {
|
|
90
|
-
return
|
|
93
|
+
return '[]';
|
|
91
94
|
}
|
|
92
|
-
const items = entries.map(([key, item]) => `${nestedIndent}'${String(key).replace(/\\/g,
|
|
93
|
-
return `[\n${items.join(
|
|
95
|
+
const items = entries.map(([key, item]) => `${nestedIndent}'${String(key).replace(/\\/g, '\\\\').replace(/'/g, "\\'")}' => ${renderPhpValue(item, indentLevel + 1)}`);
|
|
96
|
+
return `[\n${items.join(',\n')}\n${indent}]`;
|
|
94
97
|
}
|
|
95
98
|
throw new Error(`Unable to encode PHP migration registry value for ${String(value)}`);
|
|
96
99
|
}
|
|
@@ -100,60 +103,60 @@ export function copyFile(sourcePath, targetPath) {
|
|
|
100
103
|
}
|
|
101
104
|
export function sanitizeSaveSnapshotSource(source) {
|
|
102
105
|
return source
|
|
103
|
-
.replace(/^import\s+\{[^}]+\}\s+from\s+['"]\.\/types['"];?\n?/gm,
|
|
104
|
-
.replace(/^interface\s+SaveProps\s*\{[\s\S]*?\}\n?/m,
|
|
105
|
-
.replace(/: SaveProps/g,
|
|
106
|
-
.replace(/attributes:\s*[A-Za-z0-9_<>{}\[\]|&,\s]+;/g,
|
|
107
|
-
.replace(/\(\{\s*attributes\s*\}:\s*\{\s*attributes:\s*any\s*\}\)/g,
|
|
108
|
-
.replace(/\n{3,}/g,
|
|
106
|
+
.replace(/^import\s+\{[^}]+\}\s+from\s+['"]\.\/types['"];?\n?/gm, '')
|
|
107
|
+
.replace(/^interface\s+SaveProps\s*\{[\s\S]*?\}\n?/m, '')
|
|
108
|
+
.replace(/: SaveProps/g, ': { attributes: any }')
|
|
109
|
+
.replace(/attributes:\s*[A-Za-z0-9_<>{}\[\]|&,\s]+;/g, 'attributes: any;')
|
|
110
|
+
.replace(/\(\{\s*attributes\s*\}:\s*\{\s*attributes:\s*any\s*\}\)/g, '({ attributes }: { attributes: any })')
|
|
111
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
109
112
|
.trimEnd()
|
|
110
|
-
.concat(
|
|
113
|
+
.concat('\n');
|
|
111
114
|
}
|
|
112
115
|
export function sanitizeSnapshotBlockJson(blockJson) {
|
|
113
116
|
const snapshot = { ...blockJson };
|
|
114
117
|
for (const key of [
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
118
|
+
'editorScript',
|
|
119
|
+
'script',
|
|
120
|
+
'scriptModule',
|
|
121
|
+
'viewScript',
|
|
122
|
+
'viewScriptModule',
|
|
123
|
+
'style',
|
|
124
|
+
'editorStyle',
|
|
125
|
+
'render',
|
|
123
126
|
]) {
|
|
124
127
|
delete snapshot[key];
|
|
125
128
|
}
|
|
126
129
|
return snapshot;
|
|
127
130
|
}
|
|
128
131
|
export function runProjectScriptIfPresent(projectDir, scriptName) {
|
|
129
|
-
const packageJson = readJson(path.join(projectDir,
|
|
132
|
+
const packageJson = readJson(path.join(projectDir, 'package.json'));
|
|
130
133
|
if (!packageJson.scripts?.[scriptName]) {
|
|
131
134
|
return;
|
|
132
135
|
}
|
|
133
136
|
const packageManagerId = detectPackageManagerId(projectDir);
|
|
134
137
|
execSync(formatRunScript(packageManagerId, scriptName), {
|
|
135
138
|
cwd: projectDir,
|
|
136
|
-
stdio:
|
|
139
|
+
stdio: 'inherit',
|
|
137
140
|
});
|
|
138
141
|
}
|
|
139
142
|
export function detectPackageManagerId(projectDir) {
|
|
140
|
-
const packageJson = readJson(path.join(projectDir,
|
|
141
|
-
const field = String(packageJson.packageManager ??
|
|
142
|
-
if (field.startsWith(
|
|
143
|
-
return
|
|
144
|
-
if (field.startsWith(
|
|
145
|
-
return
|
|
146
|
-
if (field.startsWith(
|
|
147
|
-
return
|
|
148
|
-
if (field.startsWith(
|
|
149
|
-
return
|
|
150
|
-
return
|
|
143
|
+
const packageJson = readJson(path.join(projectDir, 'package.json'));
|
|
144
|
+
const field = String(packageJson.packageManager ?? '');
|
|
145
|
+
if (field.startsWith('bun@'))
|
|
146
|
+
return 'bun';
|
|
147
|
+
if (field.startsWith('npm@'))
|
|
148
|
+
return 'npm';
|
|
149
|
+
if (field.startsWith('pnpm@'))
|
|
150
|
+
return 'pnpm';
|
|
151
|
+
if (field.startsWith('yarn@'))
|
|
152
|
+
return 'yarn';
|
|
153
|
+
return 'bun';
|
|
151
154
|
}
|
|
152
155
|
export function getLocalTsxBinary(projectDir) {
|
|
153
|
-
const filename = process.platform ===
|
|
154
|
-
const binaryPath = path.join(projectDir,
|
|
156
|
+
const filename = process.platform === 'win32' ? 'tsx.cmd' : 'tsx';
|
|
157
|
+
const binaryPath = path.join(projectDir, 'node_modules', '.bin', filename);
|
|
155
158
|
if (!fs.existsSync(binaryPath)) {
|
|
156
|
-
throw new Error(
|
|
159
|
+
throw new Error('Local tsx binary was not found. Install project dependencies before running migration verification.');
|
|
157
160
|
}
|
|
158
161
|
return binaryPath;
|
|
159
162
|
}
|
|
@@ -172,7 +175,7 @@ export function isInteractiveTerminal() {
|
|
|
172
175
|
* @returns The concrete migration version label that should be used.
|
|
173
176
|
*/
|
|
174
177
|
export function resolveTargetMigrationVersion(currentMigrationVersion, value) {
|
|
175
|
-
return value ===
|
|
178
|
+
return value === 'current' ? currentMigrationVersion : value;
|
|
176
179
|
}
|
|
177
180
|
/**
|
|
178
181
|
* Returns whether a value matches the canonical `vN` migration label format.
|
|
@@ -230,17 +233,17 @@ export function compareMigrationVersionLabels(left, right) {
|
|
|
230
233
|
*/
|
|
231
234
|
export function formatLegacyMigrationWorkspaceResetGuidance(reason) {
|
|
232
235
|
return [
|
|
233
|
-
reason ? `${reason} ` :
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
].join(
|
|
236
|
+
reason ? `${reason} ` : '',
|
|
237
|
+
'Back up `src/migrations/` if needed, remove or reset the existing migration workspace, ',
|
|
238
|
+
'then rerun `wp-typia migrate init --current-migration-version v1`.',
|
|
239
|
+
].join('');
|
|
237
240
|
}
|
|
238
241
|
export function escapeForCode(value) {
|
|
239
|
-
return String(value).replace(/\\/g,
|
|
242
|
+
return String(value).replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
240
243
|
}
|
|
241
244
|
export function renderObjectKey(key) {
|
|
242
245
|
return JSON.stringify(String(key));
|
|
243
246
|
}
|
|
244
247
|
export function isNumber(value) {
|
|
245
|
-
return typeof value ===
|
|
248
|
+
return typeof value === 'number' && Number.isFinite(value);
|
|
246
249
|
}
|
|
@@ -667,7 +667,7 @@ export function doctorProjectMigrations(projectDir, { all = false, fromMigration
|
|
|
667
667
|
const entries = blockGeneratedEntries.map(({ entry }) => entry);
|
|
668
668
|
const generatedDir = getGeneratedDirForBlock(state.paths, block);
|
|
669
669
|
expectedGeneratedFiles.set(path.join(generatedDir, "registry.ts"), renderMigrationRegistryFile(state, block.key, blockGeneratedEntries));
|
|
670
|
-
expectedGeneratedFiles.set(path.join(generatedDir, "deprecated.ts"), renderGeneratedDeprecatedFile(entries));
|
|
670
|
+
expectedGeneratedFiles.set(path.join(generatedDir, "deprecated.ts"), renderGeneratedDeprecatedFile(state, block.key, entries));
|
|
671
671
|
expectedGeneratedFiles.set(path.join(generatedDir, "verify.ts"), renderVerifyFile(state, block.key, entries));
|
|
672
672
|
expectedGeneratedFiles.set(path.join(generatedDir, "fuzz.ts"), renderFuzzFile(state, block.key, blockGeneratedEntries));
|
|
673
673
|
}
|
|
@@ -942,7 +942,7 @@ function regenerateGeneratedArtifacts(projectDir) {
|
|
|
942
942
|
const generatedDir = getGeneratedDirForBlock(state.paths, block);
|
|
943
943
|
fs.mkdirSync(generatedDir, { recursive: true });
|
|
944
944
|
fs.writeFileSync(path.join(generatedDir, "registry.ts"), renderMigrationRegistryFile(state, block.key, blockGeneratedEntries), "utf8");
|
|
945
|
-
fs.writeFileSync(path.join(generatedDir, "deprecated.ts"), renderGeneratedDeprecatedFile(entries), "utf8");
|
|
945
|
+
fs.writeFileSync(path.join(generatedDir, "deprecated.ts"), renderGeneratedDeprecatedFile(state, block.key, entries), "utf8");
|
|
946
946
|
fs.writeFileSync(path.join(generatedDir, "verify.ts"), renderVerifyFile(state, block.key, entries), "utf8");
|
|
947
947
|
fs.writeFileSync(path.join(generatedDir, "fuzz.ts"), renderFuzzFile(state, block.key, blockGeneratedEntries), "utf8");
|
|
948
948
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Generic record type used for JSON-like plain-object inspection.
|
|
3
3
|
*/
|
|
4
|
-
export type UnknownRecord = Record<string,
|
|
4
|
+
export type UnknownRecord<TValue = unknown> = Record<string, TValue>;
|
|
5
5
|
/**
|
|
6
6
|
* Delegate plain-object detection to the shared runtime primitive owner.
|
|
7
7
|
*
|
|
@@ -10,3 +10,10 @@ export type UnknownRecord = Record<string, unknown>;
|
|
|
10
10
|
* `Object.prototype` or `null` prototype.
|
|
11
11
|
*/
|
|
12
12
|
export declare function isPlainObject(value: unknown): value is UnknownRecord;
|
|
13
|
+
/**
|
|
14
|
+
* Serialize JSON-like values with deterministic plain-object key ordering.
|
|
15
|
+
*
|
|
16
|
+
* Arrays preserve their declared order, while plain objects are normalized by
|
|
17
|
+
* sorting keys recursively before calling `JSON.stringify`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function stableJsonStringify(value: unknown): string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isPlainObject as isSharedPlainObject } from
|
|
1
|
+
import { isPlainObject as isSharedPlainObject } from '@wp-typia/api-client/runtime-primitives';
|
|
2
2
|
/**
|
|
3
3
|
* Delegate plain-object detection to the shared runtime primitive owner.
|
|
4
4
|
*
|
|
@@ -9,3 +9,23 @@ import { isPlainObject as isSharedPlainObject } from "@wp-typia/api-client/runti
|
|
|
9
9
|
export function isPlainObject(value) {
|
|
10
10
|
return isSharedPlainObject(value);
|
|
11
11
|
}
|
|
12
|
+
function toStableJsonValue(value) {
|
|
13
|
+
if (Array.isArray(value)) {
|
|
14
|
+
return value.map((entry) => toStableJsonValue(entry));
|
|
15
|
+
}
|
|
16
|
+
if (isPlainObject(value)) {
|
|
17
|
+
return Object.fromEntries(Object.keys(value)
|
|
18
|
+
.sort((left, right) => left < right ? -1 : left > right ? 1 : 0)
|
|
19
|
+
.map((key) => [key, toStableJsonValue(value[key])]));
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Serialize JSON-like values with deterministic plain-object key ordering.
|
|
25
|
+
*
|
|
26
|
+
* Arrays preserve their declared order, while plain objects are normalized by
|
|
27
|
+
* sorting keys recursively before calling `JSON.stringify`.
|
|
28
|
+
*/
|
|
29
|
+
export function stableJsonStringify(value) {
|
|
30
|
+
return JSON.stringify(toStableJsonValue(value));
|
|
31
|
+
}
|
|
@@ -24,11 +24,22 @@ export declare function seedBuiltInPersistenceArtifacts(targetDir: string, templ
|
|
|
24
24
|
export declare function normalizePackageManagerFiles(targetDir: string, packageManagerId: PackageManagerId): Promise<void>;
|
|
25
25
|
export declare function normalizePackageJson(targetDir: string, packageManagerId: PackageManagerId): Promise<void>;
|
|
26
26
|
export declare function removeUnexpectedLockfiles(targetDir: string, packageManagerId: PackageManagerId): Promise<void>;
|
|
27
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Recursively normalizes generated text files for the selected package manager
|
|
29
|
+
* and repository reference.
|
|
30
|
+
*/
|
|
31
|
+
export declare function replaceTextRecursively(targetDir: string, packageManagerId: PackageManagerId, { repositoryManifestPaths, repositoryReference, }?: {
|
|
32
|
+
repositoryManifestPaths?: readonly string[];
|
|
33
|
+
repositoryReference?: string;
|
|
34
|
+
}): Promise<void>;
|
|
28
35
|
export declare function defaultInstallDependencies({ projectDir, packageManager, }: InstallDependenciesOptions): Promise<void>;
|
|
29
36
|
export declare function isOfficialWorkspaceProject(projectDir: string): boolean;
|
|
30
37
|
export declare function applyWorkspaceMigrationCapability(projectDir: string, packageManager: PackageManagerId): Promise<void>;
|
|
31
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Applies a built-in scaffold into the target directory, including generated
|
|
40
|
+
* code artifacts, starter manifests, preset files, and placeholder rewrites.
|
|
41
|
+
*/
|
|
42
|
+
export declare function applyBuiltInScaffoldProjectFiles({ projectDir, templateDir, templateId, variables, artifacts, codeArtifacts, readmeContent, gitignoreContent, allowExistingDir, packageManager, withMigrationUi, withTestPreset, withWpEnv, noInstall, installDependencies, repositoryReference, }: {
|
|
32
43
|
projectDir: string;
|
|
33
44
|
templateDir: string;
|
|
34
45
|
templateId: BuiltInTemplateId;
|
|
@@ -44,4 +55,5 @@ export declare function applyBuiltInScaffoldProjectFiles({ projectDir, templateD
|
|
|
44
55
|
withWpEnv?: boolean;
|
|
45
56
|
noInstall?: boolean;
|
|
46
57
|
installDependencies?: ((options: InstallDependenciesOptions) => Promise<void>) | undefined;
|
|
58
|
+
repositoryReference?: string;
|
|
47
59
|
}): Promise<void>;
|