outfitter 0.2.2 → 0.2.3
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 +215 -116
- package/dist/actions.d.ts +2 -0
- package/dist/actions.js +34 -0
- package/dist/cli.js +3 -1
- package/dist/commands/add.d.ts +54 -0
- package/dist/commands/add.js +16 -0
- package/dist/commands/check.d.ts +91 -0
- package/dist/commands/check.js +14 -0
- package/dist/commands/demo.d.ts +21 -0
- package/dist/commands/demo.js +8 -0
- package/dist/commands/docs-module-loader.d.ts +2 -0
- package/dist/commands/docs-module-loader.js +8 -0
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.js +13 -0
- package/dist/commands/init.d.ts +7 -0
- package/dist/commands/init.js +31 -0
- package/dist/commands/migrate-kit.d.ts +2 -0
- package/dist/commands/migrate-kit.js +15 -0
- package/dist/commands/repo.d.ts +3 -0
- package/dist/commands/repo.js +9 -0
- package/dist/commands/scaffold.d.ts +4 -0
- package/dist/commands/scaffold.js +31 -0
- package/dist/commands/shared-deps.d.ts +36 -0
- package/dist/commands/shared-deps.js +10 -0
- package/dist/commands/update-planner.d.ts +58 -0
- package/dist/commands/update-planner.js +8 -0
- package/dist/commands/update-workspace.d.ts +76 -0
- package/dist/commands/update-workspace.js +16 -0
- package/dist/commands/update.d.ts +113 -0
- package/dist/commands/update.js +21 -0
- package/dist/create/index.d.ts +5 -0
- package/dist/create/index.js +29 -0
- package/dist/create/planner.d.ts +3 -0
- package/dist/create/planner.js +21 -0
- package/dist/create/presets.d.ts +3 -0
- package/dist/create/presets.js +12 -0
- package/dist/create/types.d.ts +2 -0
- package/dist/create/types.js +1 -0
- package/dist/engine/blocks.d.ts +3 -0
- package/dist/engine/blocks.js +12 -0
- package/dist/engine/collector.d.ts +2 -0
- package/dist/engine/collector.js +8 -0
- package/dist/engine/config.d.ts +3 -0
- package/dist/engine/config.js +12 -0
- package/dist/engine/executor.d.ts +3 -0
- package/dist/engine/executor.js +16 -0
- package/dist/engine/index.d.ts +8 -0
- package/dist/engine/index.js +59 -0
- package/dist/engine/names.d.ts +2 -0
- package/dist/engine/names.js +16 -0
- package/dist/engine/post-scaffold.d.ts +3 -0
- package/dist/engine/post-scaffold.js +8 -0
- package/dist/engine/render-plan.d.ts +7 -0
- package/dist/engine/render-plan.js +9 -0
- package/dist/engine/template.d.ts +3 -0
- package/dist/engine/template.js +17 -0
- package/dist/engine/types.d.ts +2 -0
- package/dist/engine/types.js +8 -0
- package/dist/engine/workspace.d.ts +3 -0
- package/dist/engine/workspace.js +13 -0
- package/dist/index.d.ts +228 -152
- package/dist/index.js +144 -14
- package/dist/manifest.d.ts +71 -0
- package/dist/manifest.js +16 -0
- package/dist/output-mode.d.ts +2 -0
- package/dist/output-mode.js +10 -0
- package/dist/shared/chunk-b0y0cwkr.js +5533 -0
- package/dist/shared/outfitter-193jvzg4.d.ts +5 -0
- package/dist/shared/outfitter-1dd0k853.js +194 -0
- package/dist/shared/outfitter-1h7k8xxt.js +29 -0
- package/dist/shared/outfitter-1qwpjt6w.js +125 -0
- package/dist/shared/outfitter-2ngep1h2.d.ts +5 -0
- package/dist/shared/outfitter-2np85etz.js +95 -0
- package/dist/shared/outfitter-33w361tc.d.ts +18 -0
- package/dist/shared/outfitter-344t1r38.js +1 -0
- package/dist/shared/outfitter-3weh61w7.d.ts +25 -0
- package/dist/shared/outfitter-4s9meh3j.js +221 -0
- package/dist/shared/outfitter-6a4bq054.js +322 -0
- package/dist/shared/outfitter-6bkqjk86.d.ts +3 -0
- package/dist/shared/outfitter-6gc3g5wk.js +98 -0
- package/dist/shared/outfitter-7cv5fg1m.js +61 -0
- package/dist/shared/outfitter-7ha7p61k.d.ts +6 -0
- package/dist/shared/outfitter-7r12fj7f.js +30 -0
- package/dist/shared/outfitter-8y2dfx6n.js +11 -0
- package/dist/shared/outfitter-9c8edfsn.js +715 -0
- package/dist/shared/outfitter-9x1brcmq.js +184 -0
- package/dist/shared/outfitter-a79xrm12.d.ts +17 -0
- package/dist/shared/outfitter-amc4jbs1.d.ts +50 -0
- package/dist/shared/outfitter-ara3djt0.js +73 -0
- package/dist/shared/outfitter-avhm5z6w.js +82 -0
- package/dist/shared/outfitter-b5nd42y4.d.ts +45 -0
- package/dist/shared/outfitter-dd0btgec.d.ts +40 -0
- package/dist/shared/outfitter-e2zz5wv7.d.ts +51 -0
- package/dist/shared/outfitter-ehp18x1n.js +1 -0
- package/dist/shared/outfitter-fnsmx3xg.js +750 -0
- package/dist/shared/outfitter-gdvm5c0b.d.ts +4 -0
- package/dist/shared/outfitter-gp4v5gkf.js +322 -0
- package/dist/shared/outfitter-h1mnzzd1.d.ts +14 -0
- package/dist/shared/outfitter-hpymx4m9.js +184 -0
- package/dist/shared/outfitter-hvsaxgcp.js +1 -0
- package/dist/shared/outfitter-j8yc7294.d.ts +22 -0
- package/dist/shared/outfitter-jyxwznk1.js +404 -0
- package/dist/shared/outfitter-k112c427.js +21 -0
- package/dist/shared/outfitter-k56rmt24.d.ts +30 -0
- package/dist/shared/outfitter-ksa1pp4t.d.ts +4 -0
- package/dist/shared/outfitter-mdt37hqm.js +4 -0
- package/dist/shared/outfitter-mtbpabf3.js +91 -0
- package/dist/shared/outfitter-nm4m0v6x.d.ts +131 -0
- package/dist/shared/outfitter-nmeecf1b.js +531 -0
- package/dist/shared/outfitter-npyfbdmc.d.ts +6 -0
- package/dist/shared/outfitter-pxt58tsq.js +582 -0
- package/dist/shared/outfitter-q9agarmb.js +42 -0
- package/dist/shared/outfitter-qfgj5xpq.js +70 -0
- package/dist/shared/outfitter-qfh36ddg.d.ts +66 -0
- package/dist/shared/outfitter-s6k8y2p4.js +269 -0
- package/dist/shared/outfitter-sftf1s26.js +199 -0
- package/dist/shared/outfitter-sg7ncy4a.d.ts +51 -0
- package/dist/shared/outfitter-sgtq57qr.d.ts +5 -0
- package/dist/shared/outfitter-txre6cdn.d.ts +60 -0
- package/dist/shared/outfitter-vh4xgb93.js +35 -0
- package/dist/shared/outfitter-ya44h1km.js +191 -0
- package/dist/shared/outfitter-zwyvewr1.js +36 -0
- package/dist/targets/index.d.ts +4 -0
- package/dist/targets/index.js +29 -0
- package/dist/targets/registry.d.ts +3 -0
- package/dist/targets/registry.js +28 -0
- package/dist/targets/types.d.ts +2 -0
- package/dist/targets/types.js +1 -0
- package/package.json +154 -37
- package/templates/minimal/.gitignore.template +30 -0
- package/templates/minimal/.lefthook.yml.template +26 -0
- package/templates/minimal/package.json.template +46 -0
- package/templates/minimal/src/index.ts.template +26 -0
- package/templates/minimal/tsconfig.json.template +34 -0
- package/dist/shared/chunk-sak1tt33.js +0 -3457
|
@@ -0,0 +1,715 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
resolveStructuredOutputMode
|
|
4
|
+
} from "./outfitter-7r12fj7f.js";
|
|
5
|
+
|
|
6
|
+
// apps/outfitter/src/commands/migrate-kit.ts
|
|
7
|
+
import {
|
|
8
|
+
existsSync,
|
|
9
|
+
readdirSync,
|
|
10
|
+
readFileSync,
|
|
11
|
+
statSync,
|
|
12
|
+
writeFileSync
|
|
13
|
+
} from "fs";
|
|
14
|
+
import { basename, dirname, join, relative, resolve } from "path";
|
|
15
|
+
import { output } from "@outfitter/cli/output";
|
|
16
|
+
import { Result } from "@outfitter/contracts";
|
|
17
|
+
import ts from "typescript";
|
|
18
|
+
var FOUNDATION_IMPORT_MAP = {
|
|
19
|
+
"@outfitter/contracts": "@outfitter/kit/foundation/contracts",
|
|
20
|
+
"@outfitter/types": "@outfitter/kit/foundation/types"
|
|
21
|
+
};
|
|
22
|
+
var FOUNDATION_PACKAGES = [
|
|
23
|
+
"@outfitter/contracts",
|
|
24
|
+
"@outfitter/types"
|
|
25
|
+
];
|
|
26
|
+
var DEPENDENCY_SECTIONS = [
|
|
27
|
+
"dependencies",
|
|
28
|
+
"devDependencies",
|
|
29
|
+
"peerDependencies",
|
|
30
|
+
"optionalDependencies"
|
|
31
|
+
];
|
|
32
|
+
var SOURCE_EXTENSIONS = new Set([
|
|
33
|
+
".ts",
|
|
34
|
+
".tsx",
|
|
35
|
+
".mts",
|
|
36
|
+
".cts",
|
|
37
|
+
".js",
|
|
38
|
+
".jsx",
|
|
39
|
+
".mjs",
|
|
40
|
+
".cjs"
|
|
41
|
+
]);
|
|
42
|
+
var IGNORED_DIRECTORIES = new Set([
|
|
43
|
+
".git",
|
|
44
|
+
".next",
|
|
45
|
+
".turbo",
|
|
46
|
+
"build",
|
|
47
|
+
"coverage",
|
|
48
|
+
"dist",
|
|
49
|
+
"node_modules",
|
|
50
|
+
"out"
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
class MigrateKitError extends Error {
|
|
54
|
+
_tag = "MigrateKitError";
|
|
55
|
+
constructor(message) {
|
|
56
|
+
super(message);
|
|
57
|
+
this.name = "MigrateKitError";
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function normalizePath(filePath) {
|
|
61
|
+
return filePath.replaceAll("\\", "/");
|
|
62
|
+
}
|
|
63
|
+
function parseManifest(filePath) {
|
|
64
|
+
try {
|
|
65
|
+
const raw = readFileSync(filePath, "utf-8");
|
|
66
|
+
const parsed = JSON.parse(raw);
|
|
67
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
68
|
+
return Result.err(new MigrateKitError(`Invalid package.json at ${filePath}`));
|
|
69
|
+
}
|
|
70
|
+
return Result.ok(parsed);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
73
|
+
return Result.err(new MigrateKitError(`Failed to read package.json at ${filePath}: ${message}`));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function resolveWorkspacePatterns(manifest) {
|
|
77
|
+
const workspaces = manifest["workspaces"];
|
|
78
|
+
if (Array.isArray(workspaces)) {
|
|
79
|
+
return workspaces.filter((value) => typeof value === "string");
|
|
80
|
+
}
|
|
81
|
+
if (!workspaces || typeof workspaces !== "object" || Array.isArray(workspaces)) {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
const packages = workspaces.packages;
|
|
85
|
+
if (!Array.isArray(packages)) {
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
return packages.filter((value) => typeof value === "string");
|
|
89
|
+
}
|
|
90
|
+
function normalizeWorkspacePattern(pattern) {
|
|
91
|
+
let value = pattern.trim().replaceAll("\\", "/");
|
|
92
|
+
if (value.length === 0)
|
|
93
|
+
return value;
|
|
94
|
+
if (value.endsWith("/")) {
|
|
95
|
+
value = value.slice(0, -1);
|
|
96
|
+
}
|
|
97
|
+
if (value.endsWith("package.json")) {
|
|
98
|
+
return value;
|
|
99
|
+
}
|
|
100
|
+
return `${value}/package.json`;
|
|
101
|
+
}
|
|
102
|
+
function collectPackageJsonFiles(rootDir) {
|
|
103
|
+
const rootPackageJson = join(rootDir, "package.json");
|
|
104
|
+
if (!existsSync(rootPackageJson)) {
|
|
105
|
+
return Result.err(new MigrateKitError(`No package.json found at ${rootPackageJson}`));
|
|
106
|
+
}
|
|
107
|
+
const manifestResult = parseManifest(rootPackageJson);
|
|
108
|
+
if (manifestResult.isErr()) {
|
|
109
|
+
return manifestResult;
|
|
110
|
+
}
|
|
111
|
+
const workspacePatterns = resolveWorkspacePatterns(manifestResult.value);
|
|
112
|
+
const files = new Set([rootPackageJson]);
|
|
113
|
+
for (const pattern of workspacePatterns) {
|
|
114
|
+
const normalized = normalizeWorkspacePattern(pattern);
|
|
115
|
+
if (normalized.length === 0) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const glob = new Bun.Glob(normalized);
|
|
119
|
+
for (const entry of glob.scanSync({ cwd: rootDir })) {
|
|
120
|
+
const absolute = resolve(rootDir, entry);
|
|
121
|
+
if (existsSync(absolute) && basename(absolute) === "package.json") {
|
|
122
|
+
files.add(absolute);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return Result.ok(Array.from(files).sort((a, b) => a.localeCompare(b)));
|
|
127
|
+
}
|
|
128
|
+
function isSourceFile(filename) {
|
|
129
|
+
for (const extension of SOURCE_EXTENSIONS) {
|
|
130
|
+
if (filename.endsWith(extension)) {
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
function collectSourceFiles(packageDir) {
|
|
137
|
+
const files = [];
|
|
138
|
+
const stack = [packageDir];
|
|
139
|
+
while (stack.length > 0) {
|
|
140
|
+
const currentDir = stack.pop();
|
|
141
|
+
if (!currentDir)
|
|
142
|
+
continue;
|
|
143
|
+
let entries;
|
|
144
|
+
try {
|
|
145
|
+
entries = readdirSync(currentDir).sort((a, b) => a.localeCompare(b));
|
|
146
|
+
} catch (error) {
|
|
147
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
148
|
+
return Result.err(new MigrateKitError(`Failed to read source directory ${currentDir}: ${message}`));
|
|
149
|
+
}
|
|
150
|
+
for (const entry of entries) {
|
|
151
|
+
const fullPath = join(currentDir, entry);
|
|
152
|
+
let stat;
|
|
153
|
+
try {
|
|
154
|
+
stat = statSync(fullPath);
|
|
155
|
+
} catch (error) {
|
|
156
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
157
|
+
return Result.err(new MigrateKitError(`Failed to read source tree entry ${fullPath}: ${message}`));
|
|
158
|
+
}
|
|
159
|
+
if (stat.isDirectory()) {
|
|
160
|
+
if (IGNORED_DIRECTORIES.has(entry)) {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (existsSync(join(fullPath, "package.json"))) {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
stack.push(fullPath);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (!stat.isFile()) {
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
if (!isSourceFile(entry)) {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
files.push(fullPath);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return Result.ok(files.sort((a, b) => a.localeCompare(b)));
|
|
179
|
+
}
|
|
180
|
+
function getScriptKind(filePath) {
|
|
181
|
+
if (filePath.endsWith(".tsx"))
|
|
182
|
+
return ts.ScriptKind.TSX;
|
|
183
|
+
if (filePath.endsWith(".ts"))
|
|
184
|
+
return ts.ScriptKind.TS;
|
|
185
|
+
if (filePath.endsWith(".mts"))
|
|
186
|
+
return ts.ScriptKind.TS;
|
|
187
|
+
if (filePath.endsWith(".cts"))
|
|
188
|
+
return ts.ScriptKind.TS;
|
|
189
|
+
if (filePath.endsWith(".jsx"))
|
|
190
|
+
return ts.ScriptKind.JSX;
|
|
191
|
+
if (filePath.endsWith(".js"))
|
|
192
|
+
return ts.ScriptKind.JS;
|
|
193
|
+
if (filePath.endsWith(".mjs"))
|
|
194
|
+
return ts.ScriptKind.JS;
|
|
195
|
+
if (filePath.endsWith(".cjs"))
|
|
196
|
+
return ts.ScriptKind.JS;
|
|
197
|
+
return ts.ScriptKind.Unknown;
|
|
198
|
+
}
|
|
199
|
+
function mapFoundationSpecifier(specifier) {
|
|
200
|
+
return FOUNDATION_IMPORT_MAP[specifier];
|
|
201
|
+
}
|
|
202
|
+
function escapeRegex(value) {
|
|
203
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
204
|
+
}
|
|
205
|
+
function collectRemainingFoundationImports(content) {
|
|
206
|
+
const remaining = new Set;
|
|
207
|
+
for (const foundationPackage of FOUNDATION_PACKAGES) {
|
|
208
|
+
const pattern = new RegExp(`["']${escapeRegex(foundationPackage)}(?:\\/[^"'\\s]+)?["']`);
|
|
209
|
+
if (pattern.test(content)) {
|
|
210
|
+
remaining.add(foundationPackage);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return remaining;
|
|
214
|
+
}
|
|
215
|
+
function mergeFoundationImportsForPackage(byPackage, packageDir, imports) {
|
|
216
|
+
if (imports.size === 0) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const existing = byPackage.get(packageDir);
|
|
220
|
+
if (existing) {
|
|
221
|
+
for (const importPath of imports) {
|
|
222
|
+
existing.add(importPath);
|
|
223
|
+
}
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
byPackage.set(packageDir, new Set(imports));
|
|
227
|
+
}
|
|
228
|
+
function findOwningPackageDir(filePath, packageDirs) {
|
|
229
|
+
const normalizedFilePath = normalizePath(filePath);
|
|
230
|
+
for (const packageDir of packageDirs) {
|
|
231
|
+
const normalizedPackageDir = normalizePath(packageDir);
|
|
232
|
+
if (normalizedFilePath === normalizedPackageDir || normalizedFilePath.startsWith(`${normalizedPackageDir}/`)) {
|
|
233
|
+
return packageDir;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
function quoteForLiteral(literalText) {
|
|
239
|
+
return literalText.startsWith("'") ? "'" : '"';
|
|
240
|
+
}
|
|
241
|
+
function enqueueModuleSpecifierReplacement(sourceFile, literal, replacements) {
|
|
242
|
+
const mapped = mapFoundationSpecifier(literal.text);
|
|
243
|
+
if (!mapped) {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
const quote = quoteForLiteral(literal.getText(sourceFile));
|
|
247
|
+
replacements.push({
|
|
248
|
+
start: literal.getStart(sourceFile),
|
|
249
|
+
end: literal.getEnd(),
|
|
250
|
+
text: `${quote}${mapped}${quote}`
|
|
251
|
+
});
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
function enqueueImportTypeReplacement(sourceFile, node, replacements) {
|
|
255
|
+
const argument = node.argument;
|
|
256
|
+
if (!ts.isLiteralTypeNode(argument)) {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
if (!ts.isStringLiteral(argument.literal)) {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
return enqueueModuleSpecifierReplacement(sourceFile, argument.literal, replacements);
|
|
263
|
+
}
|
|
264
|
+
function enqueueCallExpressionReplacement(sourceFile, node, replacements) {
|
|
265
|
+
const [firstArg] = node.arguments;
|
|
266
|
+
if (!(firstArg && ts.isStringLiteral(firstArg))) {
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
const isDynamicImport = node.expression.kind === ts.SyntaxKind.ImportKeyword;
|
|
270
|
+
const isRequireCall = ts.isIdentifier(node.expression) && node.expression.text === "require";
|
|
271
|
+
if (!(isDynamicImport || isRequireCall)) {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
return enqueueModuleSpecifierReplacement(sourceFile, firstArg, replacements);
|
|
275
|
+
}
|
|
276
|
+
function applyReplacements(content, replacements) {
|
|
277
|
+
if (replacements.length === 0) {
|
|
278
|
+
return content;
|
|
279
|
+
}
|
|
280
|
+
let next = content;
|
|
281
|
+
const ordered = [...replacements].sort((a, b) => b.start - a.start);
|
|
282
|
+
for (const replacement of ordered) {
|
|
283
|
+
next = next.slice(0, replacement.start) + replacement.text + next.slice(replacement.end);
|
|
284
|
+
}
|
|
285
|
+
return next;
|
|
286
|
+
}
|
|
287
|
+
function rewriteFoundationImports(filePath) {
|
|
288
|
+
let content;
|
|
289
|
+
try {
|
|
290
|
+
content = readFileSync(filePath, "utf-8");
|
|
291
|
+
} catch (error) {
|
|
292
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
293
|
+
return Result.err(new MigrateKitError(`Failed to read source file ${filePath}: ${message}`));
|
|
294
|
+
}
|
|
295
|
+
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true, getScriptKind(filePath));
|
|
296
|
+
const replacements = [];
|
|
297
|
+
let rewrites = 0;
|
|
298
|
+
function visit(node) {
|
|
299
|
+
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
300
|
+
if (enqueueModuleSpecifierReplacement(sourceFile, node.moduleSpecifier, replacements)) {
|
|
301
|
+
rewrites += 1;
|
|
302
|
+
}
|
|
303
|
+
} else if (ts.isExportDeclaration(node) && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
304
|
+
if (enqueueModuleSpecifierReplacement(sourceFile, node.moduleSpecifier, replacements)) {
|
|
305
|
+
rewrites += 1;
|
|
306
|
+
}
|
|
307
|
+
} else if (ts.isImportTypeNode(node)) {
|
|
308
|
+
if (enqueueImportTypeReplacement(sourceFile, node, replacements)) {
|
|
309
|
+
rewrites += 1;
|
|
310
|
+
}
|
|
311
|
+
} else if (ts.isCallExpression(node) && enqueueCallExpressionReplacement(sourceFile, node, replacements)) {
|
|
312
|
+
rewrites += 1;
|
|
313
|
+
}
|
|
314
|
+
ts.forEachChild(node, visit);
|
|
315
|
+
}
|
|
316
|
+
visit(sourceFile);
|
|
317
|
+
if (replacements.length === 0) {
|
|
318
|
+
return Result.ok({ content, rewrites: 0 });
|
|
319
|
+
}
|
|
320
|
+
return Result.ok({
|
|
321
|
+
content: applyReplacements(content, replacements),
|
|
322
|
+
rewrites
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
function sortRecord(value) {
|
|
326
|
+
if (!value)
|
|
327
|
+
return value;
|
|
328
|
+
return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left.localeCompare(right)));
|
|
329
|
+
}
|
|
330
|
+
function rewriteManifestDependencies(content, options) {
|
|
331
|
+
let manifest;
|
|
332
|
+
try {
|
|
333
|
+
const parsed = JSON.parse(content);
|
|
334
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
335
|
+
return Result.err(new MigrateKitError("package.json must contain an object"));
|
|
336
|
+
}
|
|
337
|
+
manifest = parsed;
|
|
338
|
+
} catch (error) {
|
|
339
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
340
|
+
return Result.err(new MigrateKitError(`Invalid package.json JSON: ${message}`));
|
|
341
|
+
}
|
|
342
|
+
const keepFoundationPackages = options?.keepFoundationPackages;
|
|
343
|
+
const addKitDependency = options?.addKitDependency ?? false;
|
|
344
|
+
let changed = false;
|
|
345
|
+
let kitVersionCandidate;
|
|
346
|
+
let kitSectionCandidate;
|
|
347
|
+
for (const section of DEPENDENCY_SECTIONS) {
|
|
348
|
+
const current = manifest[section];
|
|
349
|
+
if (!current || typeof current !== "object" || Array.isArray(current)) {
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
const deps = { ...current };
|
|
353
|
+
for (const foundationName of FOUNDATION_PACKAGES) {
|
|
354
|
+
const version = deps[foundationName];
|
|
355
|
+
if (typeof version !== "string") {
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
if (!kitVersionCandidate) {
|
|
359
|
+
kitVersionCandidate = version;
|
|
360
|
+
kitSectionCandidate = section;
|
|
361
|
+
}
|
|
362
|
+
if (keepFoundationPackages?.has(foundationName)) {
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
delete deps[foundationName];
|
|
366
|
+
changed = true;
|
|
367
|
+
}
|
|
368
|
+
manifest[section] = sortRecord(deps);
|
|
369
|
+
}
|
|
370
|
+
let hasKitDependency = false;
|
|
371
|
+
for (const section of DEPENDENCY_SECTIONS) {
|
|
372
|
+
const current = manifest[section];
|
|
373
|
+
if (!current || typeof current !== "object" || Array.isArray(current)) {
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
const deps = current;
|
|
377
|
+
if (typeof deps["@outfitter/kit"] === "string") {
|
|
378
|
+
hasKitDependency = true;
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
const shouldAddKit = addKitDependency || changed;
|
|
383
|
+
if (kitVersionCandidate && shouldAddKit && !hasKitDependency) {
|
|
384
|
+
const targetSection = kitSectionCandidate ?? "dependencies";
|
|
385
|
+
const existingSection = manifest[targetSection];
|
|
386
|
+
const deps = existingSection && typeof existingSection === "object" && !Array.isArray(existingSection) ? { ...existingSection } : {};
|
|
387
|
+
deps["@outfitter/kit"] = kitVersionCandidate;
|
|
388
|
+
manifest[targetSection] = sortRecord(deps);
|
|
389
|
+
changed = true;
|
|
390
|
+
}
|
|
391
|
+
if (!changed) {
|
|
392
|
+
return Result.ok({ content, changed: false });
|
|
393
|
+
}
|
|
394
|
+
return Result.ok({
|
|
395
|
+
content: `${JSON.stringify(manifest, null, 2)}
|
|
396
|
+
`,
|
|
397
|
+
changed: true
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
function splitLines(value) {
|
|
401
|
+
return value.replace(/\r\n/g, `
|
|
402
|
+
`).split(`
|
|
403
|
+
`);
|
|
404
|
+
}
|
|
405
|
+
function createLineDiff(before, after) {
|
|
406
|
+
const left = splitLines(before);
|
|
407
|
+
const right = splitLines(after);
|
|
408
|
+
const rows = left.length + 1;
|
|
409
|
+
const cols = right.length + 1;
|
|
410
|
+
const lcs = Array.from({ length: rows }, () => new Array(cols).fill(0));
|
|
411
|
+
for (let i2 = left.length - 1;i2 >= 0; i2 -= 1) {
|
|
412
|
+
const row = lcs[i2];
|
|
413
|
+
if (!row) {
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
for (let j2 = right.length - 1;j2 >= 0; j2 -= 1) {
|
|
417
|
+
const leftLine = left[i2];
|
|
418
|
+
const rightLine = right[j2];
|
|
419
|
+
if (leftLine === rightLine) {
|
|
420
|
+
row[j2] = 1 + (lcs[i2 + 1]?.[j2 + 1] ?? 0);
|
|
421
|
+
} else {
|
|
422
|
+
row[j2] = Math.max(lcs[i2 + 1]?.[j2] ?? 0, lcs[i2]?.[j2 + 1] ?? 0);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
const ops = [];
|
|
427
|
+
let i = 0;
|
|
428
|
+
let j = 0;
|
|
429
|
+
while (i < left.length && j < right.length) {
|
|
430
|
+
const leftLine = left[i];
|
|
431
|
+
const rightLine = right[j];
|
|
432
|
+
if (leftLine === undefined || rightLine === undefined) {
|
|
433
|
+
break;
|
|
434
|
+
}
|
|
435
|
+
if (leftLine === rightLine) {
|
|
436
|
+
ops.push({ type: "equal", line: leftLine });
|
|
437
|
+
i += 1;
|
|
438
|
+
j += 1;
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
if ((lcs[i + 1]?.[j] ?? 0) >= (lcs[i]?.[j + 1] ?? 0)) {
|
|
442
|
+
ops.push({ type: "remove", line: leftLine });
|
|
443
|
+
i += 1;
|
|
444
|
+
} else {
|
|
445
|
+
ops.push({ type: "add", line: rightLine });
|
|
446
|
+
j += 1;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
while (i < left.length) {
|
|
450
|
+
const line = left[i];
|
|
451
|
+
if (line === undefined)
|
|
452
|
+
break;
|
|
453
|
+
ops.push({ type: "remove", line });
|
|
454
|
+
i += 1;
|
|
455
|
+
}
|
|
456
|
+
while (j < right.length) {
|
|
457
|
+
const line = right[j];
|
|
458
|
+
if (line === undefined)
|
|
459
|
+
break;
|
|
460
|
+
ops.push({ type: "add", line });
|
|
461
|
+
j += 1;
|
|
462
|
+
}
|
|
463
|
+
return ops;
|
|
464
|
+
}
|
|
465
|
+
function createUnifiedDiff(filePath, before, after) {
|
|
466
|
+
if (before === after) {
|
|
467
|
+
return "";
|
|
468
|
+
}
|
|
469
|
+
const ops = createLineDiff(before, after);
|
|
470
|
+
const context = 3;
|
|
471
|
+
const changedIndexes = ops.map((op, index) => op.type === "equal" ? -1 : index).filter((index) => index >= 0);
|
|
472
|
+
if (changedIndexes.length === 0) {
|
|
473
|
+
return "";
|
|
474
|
+
}
|
|
475
|
+
const firstChanged = changedIndexes[0];
|
|
476
|
+
if (firstChanged === undefined) {
|
|
477
|
+
return "";
|
|
478
|
+
}
|
|
479
|
+
const hunks = [];
|
|
480
|
+
let groupStart = firstChanged;
|
|
481
|
+
let previous = firstChanged;
|
|
482
|
+
for (let index = 1;index < changedIndexes.length; index += 1) {
|
|
483
|
+
const current = changedIndexes[index];
|
|
484
|
+
if (current === undefined) {
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
if (current - previous <= context * 2) {
|
|
488
|
+
previous = current;
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
hunks.push({
|
|
492
|
+
start: Math.max(0, groupStart - context),
|
|
493
|
+
end: Math.min(ops.length, previous + context + 1)
|
|
494
|
+
});
|
|
495
|
+
groupStart = current;
|
|
496
|
+
previous = current;
|
|
497
|
+
}
|
|
498
|
+
hunks.push({
|
|
499
|
+
start: Math.max(0, groupStart - context),
|
|
500
|
+
end: Math.min(ops.length, previous + context + 1)
|
|
501
|
+
});
|
|
502
|
+
const oldLineAt = [];
|
|
503
|
+
const newLineAt = [];
|
|
504
|
+
let oldLine = 1;
|
|
505
|
+
let newLine = 1;
|
|
506
|
+
for (const op of ops) {
|
|
507
|
+
oldLineAt.push(oldLine);
|
|
508
|
+
newLineAt.push(newLine);
|
|
509
|
+
if (op.type !== "add") {
|
|
510
|
+
oldLine += 1;
|
|
511
|
+
}
|
|
512
|
+
if (op.type !== "remove") {
|
|
513
|
+
newLine += 1;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
const lines = [`--- a/${filePath}`, `+++ b/${filePath}`];
|
|
517
|
+
for (const hunk of hunks) {
|
|
518
|
+
const oldStart = oldLineAt[hunk.start] ?? 1;
|
|
519
|
+
const newStart = newLineAt[hunk.start] ?? 1;
|
|
520
|
+
let oldCount = 0;
|
|
521
|
+
let newCount = 0;
|
|
522
|
+
for (let index = hunk.start;index < hunk.end; index += 1) {
|
|
523
|
+
const op = ops[index];
|
|
524
|
+
if (!op) {
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
if (op.type !== "add") {
|
|
528
|
+
oldCount += 1;
|
|
529
|
+
}
|
|
530
|
+
if (op.type !== "remove") {
|
|
531
|
+
newCount += 1;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
lines.push(`@@ -${oldStart},${oldCount} +${newStart},${newCount} @@`);
|
|
535
|
+
for (let index = hunk.start;index < hunk.end; index += 1) {
|
|
536
|
+
const op = ops[index];
|
|
537
|
+
if (!op) {
|
|
538
|
+
continue;
|
|
539
|
+
}
|
|
540
|
+
if (op.type === "equal") {
|
|
541
|
+
lines.push(` ${op.line}`);
|
|
542
|
+
} else if (op.type === "remove") {
|
|
543
|
+
lines.push(`-${op.line}`);
|
|
544
|
+
} else {
|
|
545
|
+
lines.push(`+${op.line}`);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
return `${lines.join(`
|
|
550
|
+
`)}
|
|
551
|
+
`;
|
|
552
|
+
}
|
|
553
|
+
function writeChanges(changes, dryRun) {
|
|
554
|
+
if (dryRun) {
|
|
555
|
+
return Result.ok(undefined);
|
|
556
|
+
}
|
|
557
|
+
for (const change of changes) {
|
|
558
|
+
try {
|
|
559
|
+
writeFileSync(change.path, change.after, "utf-8");
|
|
560
|
+
} catch (error) {
|
|
561
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
562
|
+
return Result.err(new MigrateKitError(`Failed to write ${change.path}: ${message}`));
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
return Result.ok(undefined);
|
|
566
|
+
}
|
|
567
|
+
async function runMigrateKit(options) {
|
|
568
|
+
const targetDir = resolve(options.targetDir ?? process.cwd());
|
|
569
|
+
const dryRun = options.dryRun ?? false;
|
|
570
|
+
const packageJsonResult = collectPackageJsonFiles(targetDir);
|
|
571
|
+
if (packageJsonResult.isErr()) {
|
|
572
|
+
return packageJsonResult;
|
|
573
|
+
}
|
|
574
|
+
const packageJsonFiles = packageJsonResult.value;
|
|
575
|
+
const packageDirectories = Array.from(new Set(packageJsonFiles.map((path) => resolve(dirname(path))))).sort((left, right) => right.length - left.length);
|
|
576
|
+
const sourceSet = new Set;
|
|
577
|
+
for (const packageDir of packageDirectories) {
|
|
578
|
+
const sourceFilesResult = collectSourceFiles(packageDir);
|
|
579
|
+
if (sourceFilesResult.isErr()) {
|
|
580
|
+
return sourceFilesResult;
|
|
581
|
+
}
|
|
582
|
+
for (const file of sourceFilesResult.value) {
|
|
583
|
+
sourceSet.add(file);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
const sourceFiles = Array.from(sourceSet).sort((a, b) => a.localeCompare(b));
|
|
587
|
+
const remainingFoundationImportsByPackage = new Map;
|
|
588
|
+
const rewritesByPackage = new Map;
|
|
589
|
+
const changes = [];
|
|
590
|
+
const diffs = [];
|
|
591
|
+
let importRewrites = 0;
|
|
592
|
+
let manifestUpdates = 0;
|
|
593
|
+
for (const sourceFile of sourceFiles) {
|
|
594
|
+
const rewriteResult = rewriteFoundationImports(sourceFile);
|
|
595
|
+
if (rewriteResult.isErr()) {
|
|
596
|
+
return rewriteResult;
|
|
597
|
+
}
|
|
598
|
+
const packageDir = findOwningPackageDir(sourceFile, packageDirectories);
|
|
599
|
+
if (packageDir) {
|
|
600
|
+
const remainingImports = collectRemainingFoundationImports(rewriteResult.value.content);
|
|
601
|
+
mergeFoundationImportsForPackage(remainingFoundationImportsByPackage, packageDir, remainingImports);
|
|
602
|
+
if (rewriteResult.value.rewrites > 0) {
|
|
603
|
+
rewritesByPackage.set(packageDir, (rewritesByPackage.get(packageDir) ?? 0) + rewriteResult.value.rewrites);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
if (rewriteResult.value.rewrites === 0) {
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
const before = readFileSync(sourceFile, "utf-8");
|
|
610
|
+
const after = rewriteResult.value.content;
|
|
611
|
+
if (before === after) {
|
|
612
|
+
continue;
|
|
613
|
+
}
|
|
614
|
+
importRewrites += rewriteResult.value.rewrites;
|
|
615
|
+
const path = normalizePath(relative(targetDir, sourceFile));
|
|
616
|
+
changes.push({ path: sourceFile, before, after });
|
|
617
|
+
diffs.push({ path, preview: createUnifiedDiff(path, before, after) });
|
|
618
|
+
}
|
|
619
|
+
for (const packageJsonPath of packageJsonFiles) {
|
|
620
|
+
let before;
|
|
621
|
+
try {
|
|
622
|
+
before = readFileSync(packageJsonPath, "utf-8");
|
|
623
|
+
} catch (error) {
|
|
624
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
625
|
+
return Result.err(new MigrateKitError(`Failed to read ${packageJsonPath}: ${message}`));
|
|
626
|
+
}
|
|
627
|
+
const packageDir = resolve(dirname(packageJsonPath));
|
|
628
|
+
const keepFoundationPackages = remainingFoundationImportsByPackage.get(packageDir);
|
|
629
|
+
const rewriteResult = rewriteManifestDependencies(before, {
|
|
630
|
+
...keepFoundationPackages ? { keepFoundationPackages } : {},
|
|
631
|
+
addKitDependency: (rewritesByPackage.get(packageDir) ?? 0) > 0
|
|
632
|
+
});
|
|
633
|
+
if (rewriteResult.isErr()) {
|
|
634
|
+
return rewriteResult;
|
|
635
|
+
}
|
|
636
|
+
if (!rewriteResult.value.changed || rewriteResult.value.content === before) {
|
|
637
|
+
continue;
|
|
638
|
+
}
|
|
639
|
+
manifestUpdates += 1;
|
|
640
|
+
const path = normalizePath(relative(targetDir, packageJsonPath));
|
|
641
|
+
changes.push({
|
|
642
|
+
path: packageJsonPath,
|
|
643
|
+
before,
|
|
644
|
+
after: rewriteResult.value.content
|
|
645
|
+
});
|
|
646
|
+
diffs.push({
|
|
647
|
+
path,
|
|
648
|
+
preview: createUnifiedDiff(path, before, rewriteResult.value.content)
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
const writeResult = writeChanges(changes, dryRun);
|
|
652
|
+
if (writeResult.isErr()) {
|
|
653
|
+
return writeResult;
|
|
654
|
+
}
|
|
655
|
+
const changedFiles = changes.map((change) => normalizePath(relative(targetDir, change.path))).sort((a, b) => a.localeCompare(b));
|
|
656
|
+
return Result.ok({
|
|
657
|
+
targetDir,
|
|
658
|
+
dryRun,
|
|
659
|
+
packageJsonFiles: packageJsonFiles.length,
|
|
660
|
+
sourceFiles: sourceFiles.length,
|
|
661
|
+
manifestUpdates,
|
|
662
|
+
importRewrites,
|
|
663
|
+
changedFiles,
|
|
664
|
+
diffs: diffs.sort((left, right) => left.path.localeCompare(right.path))
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
async function printMigrateKitResults(result, options) {
|
|
668
|
+
const structuredMode = resolveStructuredOutputMode(options?.mode);
|
|
669
|
+
if (structuredMode) {
|
|
670
|
+
await output(result, { mode: structuredMode });
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
const lines = [];
|
|
674
|
+
if (result.changedFiles.length === 0) {
|
|
675
|
+
lines.push("No kit migration changes needed.");
|
|
676
|
+
await output(lines, { mode: "human" });
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
lines.push(result.dryRun ? "Dry run complete. No files were written." : "Migration complete.", "");
|
|
680
|
+
lines.push(`Changed files: ${result.changedFiles.length}`);
|
|
681
|
+
lines.push(`Import rewrites: ${result.importRewrites}`);
|
|
682
|
+
lines.push(`Manifest updates: ${result.manifestUpdates}`, "");
|
|
683
|
+
for (const file of result.changedFiles) {
|
|
684
|
+
lines.push(` - ${file}`);
|
|
685
|
+
}
|
|
686
|
+
if (result.dryRun && result.diffs.length > 0) {
|
|
687
|
+
lines.push("", "Diff preview:", "");
|
|
688
|
+
for (const diff of result.diffs) {
|
|
689
|
+
lines.push(diff.preview.trimEnd(), "");
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
await output(lines, { mode: "human" });
|
|
693
|
+
}
|
|
694
|
+
function migrateKitCommand(program) {
|
|
695
|
+
const existingMigrate = program.commands.find((command) => command.name() === "migrate");
|
|
696
|
+
const migrate = existingMigrate ?? program.command("migrate").description("Migration commands");
|
|
697
|
+
if (migrate.commands.some((command) => command.name() === "kit")) {
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
migrate.command("kit [directory]").description("Migrate foundation imports and dependencies to @outfitter/kit").option("--dry-run", "Preview changes without writing files", false).option("--json", "Output result as JSON", false).action(async (directory, flags) => {
|
|
701
|
+
if (flags.json) {
|
|
702
|
+
process.env["OUTFITTER_JSON"] = "1";
|
|
703
|
+
}
|
|
704
|
+
const result = await runMigrateKit({
|
|
705
|
+
...directory ? { targetDir: directory } : {},
|
|
706
|
+
dryRun: Boolean(flags.dryRun)
|
|
707
|
+
});
|
|
708
|
+
if (result.isErr()) {
|
|
709
|
+
throw result.error;
|
|
710
|
+
}
|
|
711
|
+
await printMigrateKitResults(result.value, flags.json ? { mode: "json" } : undefined);
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
export { MigrateKitError, runMigrateKit, printMigrateKitResults, migrateKitCommand };
|