@wp-typia/create 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/README.md +43 -0
  2. package/dist/cli.js +2492 -0
  3. package/dist/runtime/cli-core.js +222 -0
  4. package/dist/runtime/index.js +4 -0
  5. package/dist/runtime/migration-constants.js +14 -0
  6. package/dist/runtime/migration-diff.js +521 -0
  7. package/dist/runtime/migration-fixtures.js +89 -0
  8. package/dist/runtime/migration-manifest.js +129 -0
  9. package/dist/runtime/migration-project.js +167 -0
  10. package/dist/runtime/migration-render.js +267 -0
  11. package/dist/runtime/migration-types.js +1 -0
  12. package/dist/runtime/migration-utils.js +184 -0
  13. package/dist/runtime/migrations.js +232 -0
  14. package/dist/runtime/package-managers.js +135 -0
  15. package/dist/runtime/scaffold.js +334 -0
  16. package/dist/runtime/template-registry.js +75 -0
  17. package/package.json +65 -0
  18. package/templates/advanced/README.md.mustache +150 -0
  19. package/templates/advanced/block.json.mustache +43 -0
  20. package/templates/advanced/index.js +21 -0
  21. package/templates/advanced/package.json.mustache +47 -0
  22. package/templates/advanced/render.php.mustache +83 -0
  23. package/templates/advanced/scripts/lib/typia-metadata-core.ts +1413 -0
  24. package/templates/advanced/scripts/sync-types-to-block-json.ts.mustache +32 -0
  25. package/templates/advanced/src/admin/migration-dashboard.tsx.mustache +315 -0
  26. package/templates/advanced/src/components/ErrorBoundary.tsx.mustache +47 -0
  27. package/templates/advanced/src/deprecated.ts.mustache +2 -0
  28. package/templates/advanced/src/edit.tsx.mustache +97 -0
  29. package/templates/advanced/src/hooks/useDebounce.ts.mustache +20 -0
  30. package/templates/advanced/src/hooks/useLocalStorage.ts.mustache +31 -0
  31. package/templates/advanced/src/hooks.ts.mustache +56 -0
  32. package/templates/advanced/src/index.tsx.mustache +18 -0
  33. package/templates/advanced/src/migration-detector.ts.mustache +9 -0
  34. package/templates/advanced/src/migrations/config.ts.mustache +8 -0
  35. package/templates/advanced/src/migrations/examples/rename-transform-union/README.md.mustache +23 -0
  36. package/templates/advanced/src/migrations/examples/rename-transform-union/fixture.example.json.mustache +36 -0
  37. package/templates/advanced/src/migrations/examples/rename-transform-union/rule.example.ts.mustache +47 -0
  38. package/templates/advanced/src/migrations/fixtures/README.md.mustache +3 -0
  39. package/templates/advanced/src/migrations/generated/deprecated.ts.mustache +3 -0
  40. package/templates/advanced/src/migrations/generated/registry.ts.mustache +9 -0
  41. package/templates/advanced/src/migrations/generated/verify.ts.mustache +1 -0
  42. package/templates/advanced/src/migrations/helpers.ts.mustache +354 -0
  43. package/templates/advanced/src/migrations/index.ts.mustache +616 -0
  44. package/templates/advanced/src/migrations/rules/README.md.mustache +3 -0
  45. package/templates/advanced/src/migrations/versions/README.md.mustache +3 -0
  46. package/templates/advanced/src/save.tsx.mustache +12 -0
  47. package/templates/advanced/src/style.scss.mustache +84 -0
  48. package/templates/advanced/src/types.ts.mustache +46 -0
  49. package/templates/advanced/src/utils/classnames.ts.mustache +51 -0
  50. package/templates/advanced/src/utils/debounce.ts.mustache +37 -0
  51. package/templates/advanced/src/utils/index.ts.mustache +7 -0
  52. package/templates/advanced/src/utils/uuid.ts.mustache +17 -0
  53. package/templates/advanced/src/validators.ts.mustache +39 -0
  54. package/templates/advanced/src/view.ts.mustache +59 -0
  55. package/templates/advanced/tsconfig.json.mustache +20 -0
  56. package/templates/advanced/webpack.config.js.mustache +95 -0
  57. package/templates/basic/package.json.mustache +39 -0
  58. package/templates/basic/scripts/lib/typia-metadata-core.ts +1413 -0
  59. package/templates/basic/scripts/sync-types-to-block-json.ts +25 -0
  60. package/templates/basic/src/block.json +51 -0
  61. package/templates/basic/src/edit.tsx +85 -0
  62. package/templates/basic/src/hooks.ts +75 -0
  63. package/templates/basic/src/index.tsx +37 -0
  64. package/templates/basic/src/save.tsx +27 -0
  65. package/templates/basic/src/style.scss +42 -0
  66. package/templates/basic/src/types.ts +48 -0
  67. package/templates/basic/src/validators.ts +39 -0
  68. package/templates/basic/tsconfig.json +20 -0
  69. package/templates/basic/webpack.config.js +89 -0
  70. package/templates/full/package.json.mustache +40 -0
  71. package/templates/full/scripts/lib/typia-metadata-core.ts +1413 -0
  72. package/templates/full/scripts/sync-types-to-block-json.ts.mustache +32 -0
  73. package/templates/full/src/block.json.mustache +120 -0
  74. package/templates/full/src/edit.tsx.mustache +300 -0
  75. package/templates/full/src/editor.scss.mustache +251 -0
  76. package/templates/full/src/hooks.ts.mustache +141 -0
  77. package/templates/full/src/index.tsx.mustache +27 -0
  78. package/templates/full/src/save.tsx.mustache +39 -0
  79. package/templates/full/src/style.scss.mustache +224 -0
  80. package/templates/full/src/types.ts.mustache +35 -0
  81. package/templates/full/src/validators.ts.mustache +84 -0
  82. package/templates/full/tsconfig.json.mustache +20 -0
  83. package/templates/full/webpack.config.js.mustache +89 -0
  84. package/templates/interactivity/package.json.mustache +41 -0
  85. package/templates/interactivity/scripts/lib/typia-metadata-core.ts +1413 -0
  86. package/templates/interactivity/scripts/sync-types-to-block-json.ts.mustache +32 -0
  87. package/templates/interactivity/src/block.json.mustache +74 -0
  88. package/templates/interactivity/src/edit.tsx.mustache +206 -0
  89. package/templates/interactivity/src/index.tsx.mustache +20 -0
  90. package/templates/interactivity/src/interactivity.ts.mustache +183 -0
  91. package/templates/interactivity/src/save.tsx.mustache +87 -0
  92. package/templates/interactivity/src/style.scss.mustache +60 -0
  93. package/templates/interactivity/src/types.ts.mustache +30 -0
  94. package/templates/interactivity/tsconfig.json.mustache +20 -0
  95. package/templates/interactivity/webpack.config.js.mustache +89 -0
@@ -0,0 +1,184 @@
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
+ export function cloneJsonValue(value) {
6
+ return JSON.parse(JSON.stringify(value));
7
+ }
8
+ export function getValueAtPath(input, pathLabel) {
9
+ return String(pathLabel)
10
+ .split(".")
11
+ .reduce((value, segment) => value && typeof value === "object" ? value[segment] : undefined, input);
12
+ }
13
+ export function setValueAtPath(input, pathLabel, value) {
14
+ const segments = String(pathLabel).split(".");
15
+ let target = input;
16
+ while (segments.length > 1) {
17
+ const segment = segments.shift();
18
+ if (!segment) {
19
+ continue;
20
+ }
21
+ if (!target[segment] || typeof target[segment] !== "object" || Array.isArray(target[segment])) {
22
+ target[segment] = {};
23
+ }
24
+ target = target[segment];
25
+ }
26
+ target[segments[0]] = value;
27
+ }
28
+ export function deleteValueAtPath(input, pathLabel) {
29
+ const segments = String(pathLabel).split(".");
30
+ let target = input;
31
+ while (segments.length > 1) {
32
+ const segment = segments.shift();
33
+ if (!segment || !target[segment] || typeof target[segment] !== "object") {
34
+ return;
35
+ }
36
+ target = target[segment];
37
+ }
38
+ delete target[segments[0]];
39
+ }
40
+ export function createFixtureScalarValue(pathLabel) {
41
+ const normalized = String(pathLabel).toLowerCase();
42
+ if (normalized.includes("id")) {
43
+ return "00000000-0000-4000-8000-000000000000";
44
+ }
45
+ if (normalized.includes("count") || normalized.includes("number")) {
46
+ return 1;
47
+ }
48
+ if (normalized.includes("visible") || normalized.startsWith("is")) {
49
+ return true;
50
+ }
51
+ return `legacy:${pathLabel}`;
52
+ }
53
+ export function createTransformFixtureValue(attribute, pathLabel) {
54
+ switch (attribute?.ts?.kind) {
55
+ case "number":
56
+ return "42";
57
+ case "boolean":
58
+ return "1";
59
+ case "union":
60
+ return { kind: "unknown" };
61
+ default:
62
+ return createFixtureScalarValue(pathLabel);
63
+ }
64
+ }
65
+ export function readJson(filePath) {
66
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
67
+ }
68
+ export function renderPhpValue(value, indentLevel) {
69
+ const indent = "\t".repeat(indentLevel);
70
+ const nestedIndent = "\t".repeat(indentLevel + 1);
71
+ if (value === null) {
72
+ return "null";
73
+ }
74
+ if (typeof value === "string") {
75
+ return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
76
+ }
77
+ if (typeof value === "number" || typeof value === "boolean") {
78
+ return String(value);
79
+ }
80
+ if (Array.isArray(value)) {
81
+ if (value.length === 0) {
82
+ return "[]";
83
+ }
84
+ const items = value.map((item) => `${nestedIndent}${renderPhpValue(item, indentLevel + 1)}`);
85
+ return `[\n${items.join(",\n")}\n${indent}]`;
86
+ }
87
+ if (typeof value === "object") {
88
+ const entries = Object.entries(value);
89
+ if (entries.length === 0) {
90
+ return "[]";
91
+ }
92
+ const items = entries.map(([key, item]) => `${nestedIndent}'${String(key).replace(/\\/g, "\\\\").replace(/'/g, "\\'")}' => ${renderPhpValue(item, indentLevel + 1)}`);
93
+ return `[\n${items.join(",\n")}\n${indent}]`;
94
+ }
95
+ throw new Error(`Unable to encode PHP migration registry value for ${String(value)}`);
96
+ }
97
+ export function copyFile(sourcePath, targetPath) {
98
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
99
+ fs.copyFileSync(sourcePath, targetPath);
100
+ }
101
+ export function sanitizeSaveSnapshotSource(source) {
102
+ 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, ": { attributes: any }")
106
+ .replace(/attributes:\s*[A-Za-z0-9_<>{}\[\]|&,\s]+;/g, "attributes: any;")
107
+ .replace(/\(\{\s*attributes\s*\}:\s*\{\s*attributes:\s*any\s*\}\)/g, "({ attributes }: { attributes: any })");
108
+ }
109
+ export function sanitizeSnapshotBlockJson(blockJson) {
110
+ const snapshot = { ...blockJson };
111
+ for (const key of [
112
+ "editorScript",
113
+ "script",
114
+ "scriptModule",
115
+ "viewScript",
116
+ "viewScriptModule",
117
+ "style",
118
+ "editorStyle",
119
+ "render",
120
+ ]) {
121
+ delete snapshot[key];
122
+ }
123
+ return snapshot;
124
+ }
125
+ export function runProjectScriptIfPresent(projectDir, scriptName) {
126
+ const packageJson = readJson(path.join(projectDir, "package.json"));
127
+ if (!packageJson.scripts?.[scriptName]) {
128
+ return;
129
+ }
130
+ const packageManagerId = detectPackageManagerId(projectDir);
131
+ execSync(formatRunScript(packageManagerId, scriptName), {
132
+ cwd: projectDir,
133
+ stdio: "inherit",
134
+ });
135
+ }
136
+ export function detectPackageManagerId(projectDir) {
137
+ const packageJson = readJson(path.join(projectDir, "package.json"));
138
+ const field = String(packageJson.packageManager ?? "");
139
+ if (field.startsWith("bun@"))
140
+ return "bun";
141
+ if (field.startsWith("npm@"))
142
+ return "npm";
143
+ if (field.startsWith("pnpm@"))
144
+ return "pnpm";
145
+ if (field.startsWith("yarn@"))
146
+ return "yarn";
147
+ return "bun";
148
+ }
149
+ export function getLocalTsxBinary(projectDir) {
150
+ const filename = process.platform === "win32" ? "tsx.cmd" : "tsx";
151
+ const binaryPath = path.join(projectDir, "node_modules", ".bin", filename);
152
+ if (!fs.existsSync(binaryPath)) {
153
+ throw new Error("Local tsx binary was not found. Install project dependencies before running migration verification.");
154
+ }
155
+ return binaryPath;
156
+ }
157
+ export function resolveTargetVersion(currentVersion, value) {
158
+ return value === "current" ? currentVersion : value;
159
+ }
160
+ export function assertSemver(value, label) {
161
+ if (!/^\d+\.\d+\.\d+$/.test(value)) {
162
+ throw new Error(`Invalid ${label}: ${value}. Expected x.y.z`);
163
+ }
164
+ }
165
+ export function compareSemver(left, right) {
166
+ const leftParts = left.split(".").map((part) => Number.parseInt(part, 10));
167
+ const rightParts = right.split(".").map((part) => Number.parseInt(part, 10));
168
+ for (let index = 0; index < Math.max(leftParts.length, rightParts.length); index += 1) {
169
+ const delta = (leftParts[index] ?? 0) - (rightParts[index] ?? 0);
170
+ if (delta !== 0) {
171
+ return delta;
172
+ }
173
+ }
174
+ return 0;
175
+ }
176
+ export function escapeForCode(value) {
177
+ return String(value).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
178
+ }
179
+ export function renderObjectKey(key) {
180
+ return JSON.stringify(String(key));
181
+ }
182
+ export function isNumber(value) {
183
+ return typeof value === "number" && Number.isFinite(value);
184
+ }
@@ -0,0 +1,232 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { execSync } from "node:child_process";
4
+ import { GENERATED_DIR, ROOT_BLOCK_JSON, ROOT_MANIFEST, ROOT_PHP_MIGRATION_REGISTRY, ROOT_SAVE_FILE, SNAPSHOT_DIR, } from "./migration-constants.js";
5
+ import { createMigrationDiff } from "./migration-diff.js";
6
+ import { ensureEdgeFixtureFile } from "./migration-fixtures.js";
7
+ import { assertRuleHasNoTodos, discoverMigrationEntries, ensureAdvancedMigrationProject, ensureMigrationDirectories, getProjectPaths, getRuleFilePath, loadMigrationProject, readProjectBlockName, writeInitialMigrationScaffold, writeMigrationConfig, } from "./migration-project.js";
8
+ import { formatDiffReport, renderGeneratedDeprecatedFile, renderMigrationRegistryFile, renderMigrationRuleFile, renderPhpMigrationRegistryFile, renderVerifyFile, } from "./migration-render.js";
9
+ import { assertSemver, compareSemver, copyFile, getLocalTsxBinary, readJson, resolveTargetVersion, runProjectScriptIfPresent, sanitizeSaveSnapshotSource, sanitizeSnapshotBlockJson, } from "./migration-utils.js";
10
+ export function formatMigrationHelpText() {
11
+ return `Usage:
12
+ wp-typia migrations init --current-version <semver>
13
+ wp-typia migrations snapshot --version <semver>
14
+ wp-typia migrations diff --from <semver> [--to current]
15
+ wp-typia migrations scaffold --from <semver> [--to current]
16
+ wp-typia migrations verify [--from <semver>|--all]`;
17
+ }
18
+ export function parseMigrationArgs(argv) {
19
+ const parsed = {
20
+ command: undefined,
21
+ flags: {
22
+ all: false,
23
+ currentVersion: undefined,
24
+ from: undefined,
25
+ to: "current",
26
+ version: undefined,
27
+ },
28
+ };
29
+ if (argv.length === 0) {
30
+ throw new Error(formatMigrationHelpText());
31
+ }
32
+ parsed.command = argv[0];
33
+ for (let index = 1; index < argv.length; index += 1) {
34
+ const arg = argv[index];
35
+ const next = argv[index + 1];
36
+ if (arg === "--")
37
+ continue;
38
+ if (arg === "--all") {
39
+ parsed.flags.all = true;
40
+ continue;
41
+ }
42
+ if (arg === "--current-version") {
43
+ parsed.flags.currentVersion = next;
44
+ index += 1;
45
+ continue;
46
+ }
47
+ if (arg.startsWith("--current-version=")) {
48
+ parsed.flags.currentVersion = arg.split("=", 2)[1];
49
+ continue;
50
+ }
51
+ if (arg === "--from") {
52
+ parsed.flags.from = next;
53
+ index += 1;
54
+ continue;
55
+ }
56
+ if (arg.startsWith("--from=")) {
57
+ parsed.flags.from = arg.split("=", 2)[1];
58
+ continue;
59
+ }
60
+ if (arg === "--to") {
61
+ parsed.flags.to = next;
62
+ index += 1;
63
+ continue;
64
+ }
65
+ if (arg.startsWith("--to=")) {
66
+ parsed.flags.to = arg.split("=", 2)[1];
67
+ continue;
68
+ }
69
+ if (arg === "--version") {
70
+ parsed.flags.version = next;
71
+ index += 1;
72
+ continue;
73
+ }
74
+ if (arg.startsWith("--version=")) {
75
+ parsed.flags.version = arg.split("=", 2)[1];
76
+ continue;
77
+ }
78
+ throw new Error(`Unknown migrations flag: ${arg}`);
79
+ }
80
+ return parsed;
81
+ }
82
+ export { formatDiffReport };
83
+ export function runMigrationCommand(command, cwd, { renderLine = console.log } = {}) {
84
+ switch (command.command) {
85
+ case "init":
86
+ if (!command.flags.currentVersion) {
87
+ throw new Error("`migrations init` requires --current-version <semver>.");
88
+ }
89
+ return initProjectMigrations(cwd, command.flags.currentVersion, { renderLine });
90
+ case "snapshot":
91
+ if (!command.flags.version) {
92
+ throw new Error("`migrations snapshot` requires --version <semver>.");
93
+ }
94
+ return snapshotProjectVersion(cwd, command.flags.version, { renderLine });
95
+ case "diff":
96
+ if (!command.flags.from) {
97
+ throw new Error("`migrations diff` requires --from <semver>.");
98
+ }
99
+ return diffProjectMigrations(cwd, {
100
+ fromVersion: command.flags.from,
101
+ renderLine,
102
+ toVersion: command.flags.to ?? "current",
103
+ });
104
+ case "scaffold":
105
+ if (!command.flags.from) {
106
+ throw new Error("`migrations scaffold` requires --from <semver>.");
107
+ }
108
+ return scaffoldProjectMigrations(cwd, {
109
+ fromVersion: command.flags.from,
110
+ renderLine,
111
+ toVersion: command.flags.to ?? "current",
112
+ });
113
+ case "verify":
114
+ return verifyProjectMigrations(cwd, {
115
+ all: command.flags.all,
116
+ fromVersion: command.flags.from,
117
+ renderLine,
118
+ });
119
+ default:
120
+ throw new Error(formatMigrationHelpText());
121
+ }
122
+ }
123
+ export function initProjectMigrations(projectDir, currentVersion, { renderLine = console.log } = {}) {
124
+ ensureAdvancedMigrationProject(projectDir);
125
+ assertSemver(currentVersion, "current version");
126
+ const blockName = readProjectBlockName(projectDir);
127
+ ensureMigrationDirectories(projectDir);
128
+ writeMigrationConfig(projectDir, {
129
+ blockName,
130
+ currentVersion,
131
+ snapshotDir: SNAPSHOT_DIR.replace(/\\/g, "/"),
132
+ supportedVersions: [currentVersion],
133
+ });
134
+ writeInitialMigrationScaffold(projectDir, currentVersion);
135
+ snapshotProjectVersion(projectDir, currentVersion, { renderLine, skipConfigUpdate: true });
136
+ regenerateGeneratedArtifacts(projectDir);
137
+ renderLine(`Initialized migrations for ${blockName} at version ${currentVersion}`);
138
+ return loadMigrationProject(projectDir);
139
+ }
140
+ export function snapshotProjectVersion(projectDir, version, { renderLine = console.log, skipConfigUpdate = false, } = {}) {
141
+ ensureAdvancedMigrationProject(projectDir);
142
+ assertSemver(version, "snapshot version");
143
+ runProjectScriptIfPresent(projectDir, "sync-types");
144
+ const state = loadMigrationProject(projectDir, { allowMissingConfig: skipConfigUpdate });
145
+ const snapshotRoot = path.join(projectDir, SNAPSHOT_DIR, version);
146
+ fs.mkdirSync(snapshotRoot, { recursive: true });
147
+ fs.writeFileSync(path.join(snapshotRoot, ROOT_BLOCK_JSON), `${JSON.stringify(sanitizeSnapshotBlockJson(readJson(path.join(projectDir, ROOT_BLOCK_JSON))), null, "\t")}\n`, "utf8");
148
+ copyFile(path.join(projectDir, ROOT_MANIFEST), path.join(snapshotRoot, ROOT_MANIFEST));
149
+ fs.writeFileSync(path.join(snapshotRoot, "save.tsx"), sanitizeSaveSnapshotSource(fs.readFileSync(path.join(projectDir, ROOT_SAVE_FILE), "utf8")), "utf8");
150
+ if (!skipConfigUpdate) {
151
+ const nextSupported = [...new Set([...state.config.supportedVersions, version])].sort(compareSemver);
152
+ writeMigrationConfig(projectDir, {
153
+ ...state.config,
154
+ currentVersion: version,
155
+ supportedVersions: nextSupported,
156
+ });
157
+ }
158
+ regenerateGeneratedArtifacts(projectDir);
159
+ renderLine(`Snapshot stored for ${version}`);
160
+ return loadMigrationProject(projectDir);
161
+ }
162
+ export function diffProjectMigrations(projectDir, { fromVersion, toVersion = "current", renderLine = console.log } = {}) {
163
+ if (!fromVersion) {
164
+ throw new Error("`migrations diff` requires --from <semver>.");
165
+ }
166
+ const state = loadMigrationProject(projectDir);
167
+ const targetVersion = resolveTargetVersion(state.config.currentVersion, toVersion);
168
+ const diff = createMigrationDiff(state, fromVersion, targetVersion);
169
+ renderLine(formatDiffReport(diff));
170
+ return diff;
171
+ }
172
+ export function scaffoldProjectMigrations(projectDir, { fromVersion, toVersion = "current", renderLine = console.log } = {}) {
173
+ if (!fromVersion) {
174
+ throw new Error("`migrations scaffold` requires --from <semver>.");
175
+ }
176
+ ensureMigrationDirectories(projectDir);
177
+ const state = loadMigrationProject(projectDir);
178
+ const targetVersion = resolveTargetVersion(state.config.currentVersion, toVersion);
179
+ const diff = createMigrationDiff(state, fromVersion, targetVersion);
180
+ const paths = getProjectPaths(projectDir);
181
+ const rulePath = getRuleFilePath(paths, fromVersion, targetVersion);
182
+ if (!fs.existsSync(rulePath)) {
183
+ fs.writeFileSync(rulePath, renderMigrationRuleFile({
184
+ currentAttributes: state.currentManifest.attributes ?? {},
185
+ currentTypeName: state.currentManifest.sourceType,
186
+ diff,
187
+ fromVersion,
188
+ targetVersion,
189
+ }), "utf8");
190
+ }
191
+ ensureEdgeFixtureFile(projectDir, fromVersion, targetVersion, diff);
192
+ regenerateGeneratedArtifacts(projectDir);
193
+ renderLine(formatDiffReport(diff));
194
+ renderLine(`Scaffolded ${path.relative(projectDir, rulePath)}`);
195
+ return { diff, rulePath };
196
+ }
197
+ export function verifyProjectMigrations(projectDir, { all = false, fromVersion, renderLine = console.log } = {}) {
198
+ const state = loadMigrationProject(projectDir);
199
+ const verifyScriptPath = path.join(projectDir, GENERATED_DIR, "verify.ts");
200
+ if (!fs.existsSync(verifyScriptPath)) {
201
+ throw new Error("Generated verify script is missing. Run `wp-typia migrations scaffold --from <semver>` first.");
202
+ }
203
+ const targetVersions = all
204
+ ? state.config.supportedVersions.filter((version) => version !== state.config.currentVersion)
205
+ : fromVersion
206
+ ? [fromVersion]
207
+ : state.config.supportedVersions.filter((version) => version !== state.config.currentVersion);
208
+ if (targetVersions.length === 0) {
209
+ renderLine("No legacy versions configured for verification.");
210
+ return { verifiedVersions: [] };
211
+ }
212
+ for (const version of targetVersions) {
213
+ assertRuleHasNoTodos(projectDir, version, state.config.currentVersion);
214
+ }
215
+ const tsxBinary = getLocalTsxBinary(projectDir);
216
+ const filteredArgs = all ? ["--all"] : fromVersion ? ["--from", fromVersion] : [];
217
+ execSync(`"${tsxBinary}" "${verifyScriptPath}" ${filteredArgs.join(" ")}`.trim(), {
218
+ cwd: projectDir,
219
+ stdio: "inherit",
220
+ });
221
+ renderLine(`Verified migrations for ${targetVersions.join(", ")}`);
222
+ return { verifiedVersions: targetVersions };
223
+ }
224
+ function regenerateGeneratedArtifacts(projectDir) {
225
+ const state = loadMigrationProject(projectDir);
226
+ const entries = discoverMigrationEntries(state);
227
+ fs.mkdirSync(state.paths.generatedDir, { recursive: true });
228
+ fs.writeFileSync(path.join(state.paths.generatedDir, "registry.ts"), renderMigrationRegistryFile(state, entries), "utf8");
229
+ fs.writeFileSync(path.join(state.paths.generatedDir, "deprecated.ts"), renderGeneratedDeprecatedFile(entries), "utf8");
230
+ fs.writeFileSync(path.join(state.paths.generatedDir, "verify.ts"), renderVerifyFile(state, entries), "utf8");
231
+ fs.writeFileSync(path.join(projectDir, ROOT_PHP_MIGRATION_REGISTRY), renderPhpMigrationRegistryFile(state, entries), "utf8");
232
+ }
@@ -0,0 +1,135 @@
1
+ const PACKAGE_MANAGER_DATA = [
2
+ {
3
+ id: "bun",
4
+ label: "Bun",
5
+ packageManagerField: "bun@1.3.10",
6
+ installCommand: "bun install",
7
+ frozenInstallCommand: "bun install --frozen-lockfile",
8
+ },
9
+ {
10
+ id: "npm",
11
+ label: "npm",
12
+ packageManagerField: "npm@11.6.1",
13
+ installCommand: "npm install",
14
+ frozenInstallCommand: "npm ci",
15
+ },
16
+ {
17
+ id: "pnpm",
18
+ label: "pnpm",
19
+ packageManagerField: "pnpm@8.3.1",
20
+ installCommand: "pnpm install",
21
+ frozenInstallCommand: "pnpm install --frozen-lockfile",
22
+ },
23
+ {
24
+ id: "yarn",
25
+ label: "Yarn",
26
+ packageManagerField: "yarn@3.2.4",
27
+ installCommand: "yarn install",
28
+ frozenInstallCommand: "yarn install --frozen-lockfile",
29
+ },
30
+ ];
31
+ export const PACKAGE_MANAGER_IDS = PACKAGE_MANAGER_DATA.map((manager) => manager.id);
32
+ export const PACKAGE_MANAGERS = Object.freeze(Object.fromEntries(PACKAGE_MANAGER_DATA.map((manager) => [manager.id, manager])));
33
+ const DEV_INSTALL_FLAGS = {
34
+ bun: "add -d",
35
+ npm: "install --save-dev",
36
+ pnpm: "add -D",
37
+ yarn: "add -D",
38
+ };
39
+ const STOP_CHARS = new Set(["\n", "\r", "`", "\"", "'", ")", "]", "}", "!", ",", "."]);
40
+ export function getPackageManager(id) {
41
+ const manager = PACKAGE_MANAGERS[id];
42
+ if (!manager) {
43
+ throw new Error(`Unknown package manager "${id}". Expected one of: ${PACKAGE_MANAGER_IDS.join(", ")}`);
44
+ }
45
+ return manager;
46
+ }
47
+ export function getPackageManagerSelectOptions() {
48
+ return PACKAGE_MANAGER_DATA.map((manager) => ({
49
+ label: manager.label,
50
+ value: manager.id,
51
+ hint: manager.installCommand,
52
+ }));
53
+ }
54
+ export function formatRunScript(packageManagerId, scriptName, extraArgs = "") {
55
+ const args = extraArgs.trim();
56
+ if (packageManagerId === "bun") {
57
+ return args ? `bun run ${scriptName} ${args}` : `bun run ${scriptName}`;
58
+ }
59
+ if (packageManagerId === "npm") {
60
+ return args ? `npm run ${scriptName} -- ${args}` : `npm run ${scriptName}`;
61
+ }
62
+ if (packageManagerId === "pnpm") {
63
+ return args ? `pnpm run ${scriptName} -- ${args}` : `pnpm run ${scriptName}`;
64
+ }
65
+ return args ? `yarn run ${scriptName} ${args}` : `yarn run ${scriptName}`;
66
+ }
67
+ export function formatInstallCommand(packageManagerId) {
68
+ return getPackageManager(packageManagerId).installCommand;
69
+ }
70
+ function consumeCommandArguments(content, startIndex) {
71
+ let cursor = startIndex;
72
+ let args = "";
73
+ while (cursor < content.length) {
74
+ const current = content[cursor];
75
+ if (STOP_CHARS.has(current) ||
76
+ content.startsWith("&&", cursor) ||
77
+ content.startsWith("||", cursor) ||
78
+ current === ";") {
79
+ break;
80
+ }
81
+ args += current;
82
+ cursor += 1;
83
+ }
84
+ return {
85
+ args: args.trim(),
86
+ cursor,
87
+ };
88
+ }
89
+ function replaceBunRunCommands(content, packageManagerId) {
90
+ const marker = "bun run ";
91
+ let result = "";
92
+ let cursor = 0;
93
+ while (cursor < content.length) {
94
+ const index = content.indexOf(marker, cursor);
95
+ if (index === -1) {
96
+ result += content.slice(cursor);
97
+ break;
98
+ }
99
+ if (index > 0 && /[A-Za-z0-9_-]/.test(content[index - 1])) {
100
+ result += content.slice(cursor, index + marker.length);
101
+ cursor = index + marker.length;
102
+ continue;
103
+ }
104
+ result += content.slice(cursor, index);
105
+ const scriptNameStart = index + marker.length;
106
+ const scriptNameMatch = /^[A-Za-z0-9:_-]+/.exec(content.slice(scriptNameStart));
107
+ if (!scriptNameMatch) {
108
+ result += marker;
109
+ cursor = scriptNameStart;
110
+ continue;
111
+ }
112
+ const scriptName = scriptNameMatch[0];
113
+ const argsStart = scriptNameStart + scriptName.length;
114
+ const { args, cursor: nextCursor } = consumeCommandArguments(content, argsStart);
115
+ result += formatRunScript(packageManagerId, scriptName, args);
116
+ cursor = nextCursor;
117
+ }
118
+ return result;
119
+ }
120
+ function replaceDevDependencyInstalls(content, packageManagerId) {
121
+ return content.replace(/\bbun add -d ([^\s&|;`"'()]+)\b/g, (_, packageName) => {
122
+ if (packageManagerId === "bun") {
123
+ return `bun add -d ${packageName}`;
124
+ }
125
+ return `${packageManagerId} ${DEV_INSTALL_FLAGS[packageManagerId]} ${packageName}`;
126
+ });
127
+ }
128
+ export function transformPackageManagerText(content, packageManagerId) {
129
+ const manager = getPackageManager(packageManagerId);
130
+ return replaceDevDependencyInstalls(replaceBunRunCommands(content
131
+ .replace(/\bbun install --frozen-lockfile\b/g, manager.frozenInstallCommand)
132
+ .replace(/\bbun install\b/g, manager.installCommand), packageManagerId), packageManagerId)
133
+ .replace(/\s*&&\s*/g, " && ")
134
+ .replace(/\s*\|\|\s*/g, " || ");
135
+ }