@dualkit/convert 0.4.0-beta.0 → 0.5.0-beta.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.
- package/dist/index.cjs +241 -32
- package/dist/index.js +238 -29
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -177,8 +177,8 @@ function detectProject(root) {
|
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
// src/convert.ts
|
|
180
|
-
var
|
|
181
|
-
var
|
|
180
|
+
var import_lint13 = require("@dualkit/lint");
|
|
181
|
+
var import_node_path18 = require("path");
|
|
182
182
|
|
|
183
183
|
// src/apply.ts
|
|
184
184
|
var import_lint2 = require("@dualkit/lint");
|
|
@@ -274,11 +274,11 @@ var RefusedError = class extends Error {
|
|
|
274
274
|
};
|
|
275
275
|
var NO_GIT = "git is not installed or not on PATH";
|
|
276
276
|
function git(cwd, args) {
|
|
277
|
-
return new Promise((
|
|
277
|
+
return new Promise((resolve4, reject) => {
|
|
278
278
|
(0, import_node_child_process.execFile)("git", ["--literal-pathspecs", ...args], { cwd, maxBuffer: 64 * 1024 * 1024 }, (error, stdout, stderr) => {
|
|
279
279
|
if (error && error.code === "ENOENT") return reject(new Error((0, import_node_fs3.existsSync)(cwd) ? NO_GIT : `The folder ${cwd} does not exist.`));
|
|
280
280
|
const code = error ? typeof error.code === "number" ? error.code : 1 : 0;
|
|
281
|
-
|
|
281
|
+
resolve4({ code, out: `${stdout}${stderr}`.trim(), stdout });
|
|
282
282
|
});
|
|
283
283
|
});
|
|
284
284
|
}
|
|
@@ -644,15 +644,15 @@ var ServerError = class extends Error {
|
|
|
644
644
|
var OUTPUT_LINES = 20;
|
|
645
645
|
var POLL_MS = 250;
|
|
646
646
|
var KILL_GRACE_MS = 5e3;
|
|
647
|
-
var sleep = (ms) => new Promise((
|
|
647
|
+
var sleep = (ms) => new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
648
648
|
function portTaken(port) {
|
|
649
|
-
return new Promise((
|
|
649
|
+
return new Promise((resolve4) => {
|
|
650
650
|
const socket = (0, import_node_net.connect)({ port, host: "localhost" });
|
|
651
651
|
socket.once("connect", () => {
|
|
652
652
|
socket.destroy();
|
|
653
|
-
|
|
653
|
+
resolve4(true);
|
|
654
654
|
});
|
|
655
|
-
socket.once("error", () =>
|
|
655
|
+
socket.once("error", () => resolve4(false));
|
|
656
656
|
});
|
|
657
657
|
}
|
|
658
658
|
async function answers(url) {
|
|
@@ -692,7 +692,7 @@ async function startServer(cmd, port, cwd, timeoutMs = 12e4) {
|
|
|
692
692
|
});
|
|
693
693
|
const pid = child.pid;
|
|
694
694
|
if (pid === void 0) {
|
|
695
|
-
await new Promise((
|
|
695
|
+
await new Promise((resolve4) => setImmediate(resolve4));
|
|
696
696
|
throw new ServerError(`Could not start "${cmd}": ${spawnError?.message ?? "no process id"}`);
|
|
697
697
|
}
|
|
698
698
|
const collect = (chunk) => {
|
|
@@ -1655,12 +1655,12 @@ function routeMoves(appDir, paths) {
|
|
|
1655
1655
|
}
|
|
1656
1656
|
return { moves, keptPages };
|
|
1657
1657
|
}
|
|
1658
|
-
function eachNode(node,
|
|
1659
|
-
if (Array.isArray(node)) return node.forEach((child) => eachNode(child,
|
|
1658
|
+
function eachNode(node, visit2) {
|
|
1659
|
+
if (Array.isArray(node)) return node.forEach((child) => eachNode(child, visit2));
|
|
1660
1660
|
if (!node || typeof node !== "object") return;
|
|
1661
1661
|
const n = node;
|
|
1662
|
-
if (typeof n.type === "string")
|
|
1663
|
-
for (const [key, child] of Object.entries(n)) if (!SKIP3.has(key)) eachNode(child,
|
|
1662
|
+
if (typeof n.type === "string") visit2(n);
|
|
1663
|
+
for (const [key, child] of Object.entries(n)) if (!SKIP3.has(key)) eachNode(child, visit2);
|
|
1664
1664
|
}
|
|
1665
1665
|
var RELATIVE = /^\.\.?\//;
|
|
1666
1666
|
var stringLiteral = (node) => node?.type === "StringLiteral" ? node : null;
|
|
@@ -2962,9 +2962,208 @@ function runPasses(path, text, abs, ctx) {
|
|
|
2962
2962
|
return { kind: "done", text: file.text, edits: edits2, byKind, flags, entries };
|
|
2963
2963
|
}
|
|
2964
2964
|
|
|
2965
|
+
// src/transforms/data.ts
|
|
2966
|
+
var import_node_path16 = require("path");
|
|
2967
|
+
var import_lint12 = require("@dualkit/lint");
|
|
2968
|
+
var SKIP8 = /* @__PURE__ */ new Set(["loc", "leadingComments", "trailingComments", "innerComments", "extra"]);
|
|
2969
|
+
var TEXT_KEYS = /* @__PURE__ */ new Set(["title", "label", "description", "placeholder", "subtitle", "message", "text", "alt", "heading", "caption"]);
|
|
2970
|
+
var NAV_KEYS = /* @__PURE__ */ new Set(["href", "url", "path", "to", "icon"]);
|
|
2971
|
+
var RESERVED = /* @__PURE__ */ new Set(["metadata", "viewport", "config", "routing"]);
|
|
2972
|
+
var PROSE2 = /^\p{Lu}\p{Ll}/u;
|
|
2973
|
+
var NOT_PROSE2 = /[/\\_=<>{}@#|]/;
|
|
2974
|
+
var EXTENSIONS3 = ["", ".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js", "/index.jsx"];
|
|
2975
|
+
var ALIASES = /^[@~]\//;
|
|
2976
|
+
var unwrap = (node) => node.type === "TSAsExpression" || node.type === "TSSatisfiesExpression" ? unwrap(node.expression) : node;
|
|
2977
|
+
var children = (node) => Object.entries(node).filter(([key]) => !SKIP8.has(key));
|
|
2978
|
+
function visit(node, ancestors, fn) {
|
|
2979
|
+
if (Array.isArray(node)) return node.forEach((child) => visit(child, ancestors, fn));
|
|
2980
|
+
if (!node || typeof node !== "object") return;
|
|
2981
|
+
const n = node;
|
|
2982
|
+
if (typeof n.type === "string") fn(n, ancestors);
|
|
2983
|
+
const next = typeof n.type === "string" ? [...ancestors, n] : ancestors;
|
|
2984
|
+
for (const [, child] of children(n)) visit(child, next, fn);
|
|
2985
|
+
}
|
|
2986
|
+
var keyName = (property) => {
|
|
2987
|
+
const key = property.key;
|
|
2988
|
+
if (property.computed) return null;
|
|
2989
|
+
return key.type === "Identifier" ? String(key.name) : key.type === "StringLiteral" ? String(key.value) : null;
|
|
2990
|
+
};
|
|
2991
|
+
var isProse = (value) => PROSE2.test(value) && !NOT_PROSE2.test(value);
|
|
2992
|
+
function textProperty(property, parent) {
|
|
2993
|
+
const key = keyName(property);
|
|
2994
|
+
if (key === null) return false;
|
|
2995
|
+
if (TEXT_KEYS.has(key)) return true;
|
|
2996
|
+
if (key !== "name" || parent?.type !== "ObjectExpression") return false;
|
|
2997
|
+
return (parent.properties ?? []).some((p) => p.type === "ObjectProperty" && NAV_KEYS.has(keyName(p) ?? ""));
|
|
2998
|
+
}
|
|
2999
|
+
function textsIn(init, reserved) {
|
|
3000
|
+
const strings = [];
|
|
3001
|
+
let clash = false;
|
|
3002
|
+
visit(init, [], (node, above) => {
|
|
3003
|
+
if (FUNCTIONS.has(node.type) && scopeNames(node).includes("t")) clash = true;
|
|
3004
|
+
if (node.type !== "ObjectProperty" || above.some((a) => FUNCTIONS.has(a.type) || a.type?.startsWith("JSX"))) return;
|
|
3005
|
+
const value = node.value;
|
|
3006
|
+
if (value.type === "StringLiteral" && isProse(String(value.value)) && !reserved.has(String(value.value)) && textProperty(node, above[above.length - 1])) strings.push(value);
|
|
3007
|
+
});
|
|
3008
|
+
return clash ? null : strings;
|
|
3009
|
+
}
|
|
3010
|
+
function dataIn(module2, reserved) {
|
|
3011
|
+
const found = [];
|
|
3012
|
+
for (const statement of module2.program.body ?? []) {
|
|
3013
|
+
const declaration = statement.type === "ExportNamedDeclaration" ? statement.declaration : statement;
|
|
3014
|
+
if (declaration?.type !== "VariableDeclaration" || declaration.kind !== "const") continue;
|
|
3015
|
+
for (const declarator of declaration.declarations ?? []) {
|
|
3016
|
+
const id = declarator.id;
|
|
3017
|
+
const init = declarator.init;
|
|
3018
|
+
if (id.type !== "Identifier" || !init || RESERVED.has(String(id.name))) continue;
|
|
3019
|
+
if (!["ArrayExpression", "ObjectExpression"].includes(unwrap(init).type)) continue;
|
|
3020
|
+
const strings = textsIn(init, reserved);
|
|
3021
|
+
if (strings?.length) found.push({ module: module2, name: String(id.name), declarator, init, strings });
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
3024
|
+
return found;
|
|
3025
|
+
}
|
|
3026
|
+
function literalTypes(modules) {
|
|
3027
|
+
const found = /* @__PURE__ */ new Set();
|
|
3028
|
+
for (const module2 of modules) {
|
|
3029
|
+
visit(module2.program, [], (node) => {
|
|
3030
|
+
const literal = node.type === "TSLiteralType" ? node.literal : null;
|
|
3031
|
+
if (literal?.type === "StringLiteral") found.add(String(literal.value));
|
|
3032
|
+
});
|
|
3033
|
+
}
|
|
3034
|
+
return found;
|
|
3035
|
+
}
|
|
3036
|
+
function resolve3(from, source, known) {
|
|
3037
|
+
const bases = source.startsWith(".") ? [(0, import_node_path16.join)((0, import_node_path16.dirname)(from), source)] : ALIASES.test(source) ? [source.slice(2), (0, import_node_path16.join)("src", source.slice(2))] : [];
|
|
3038
|
+
for (const base of bases) {
|
|
3039
|
+
for (const extension of EXTENSIONS3) {
|
|
3040
|
+
const path = (0, import_node_path16.normalize)(`${base}${extension}`);
|
|
3041
|
+
if (known.has(path)) return path;
|
|
3042
|
+
}
|
|
3043
|
+
}
|
|
3044
|
+
return null;
|
|
3045
|
+
}
|
|
3046
|
+
function opaque(module2, known) {
|
|
3047
|
+
const paths = [];
|
|
3048
|
+
for (const statement of module2.program.body ?? []) {
|
|
3049
|
+
const source = statement.source;
|
|
3050
|
+
if (!source) continue;
|
|
3051
|
+
const path = resolve3(module2.path, String(source.value), known);
|
|
3052
|
+
if (!path) continue;
|
|
3053
|
+
const star = (statement.specifiers ?? []).some((s) => s.type === "ImportNamespaceSpecifier");
|
|
3054
|
+
if (statement.type !== "ImportDeclaration" || star) paths.push(path);
|
|
3055
|
+
}
|
|
3056
|
+
return paths;
|
|
3057
|
+
}
|
|
3058
|
+
function usesOf(module2, local, skip, lib) {
|
|
3059
|
+
const uses = [];
|
|
3060
|
+
let blocked = false;
|
|
3061
|
+
visit(module2.program, [], (node, above) => {
|
|
3062
|
+
if (blocked || node.type !== "Identifier" || node.name !== local || node === skip) return;
|
|
3063
|
+
const parent = above[above.length - 1];
|
|
3064
|
+
if (parent.type?.startsWith("Import") || parent.type === "TSTypeReference" || parent.type === "TSQualifiedName") return;
|
|
3065
|
+
if (parent.type === "MemberExpression" && parent.property === node && !parent.computed) return;
|
|
3066
|
+
if (parent.type === "OptionalMemberExpression" && parent.property === node && !parent.computed) return;
|
|
3067
|
+
if (parent.type === "ObjectProperty" && parent.key === node && !parent.computed && !parent.shorthand) return;
|
|
3068
|
+
const inner = above.slice(1);
|
|
3069
|
+
const shadowed = inner.some((a) => scopeNames(a).includes(local));
|
|
3070
|
+
const component = componentOf(above);
|
|
3071
|
+
const stuck = parent.type === "TSTypeQuery" || parent.type?.startsWith("Export") || Boolean(parent.shorthand);
|
|
3072
|
+
const inParams = component !== null && node.start < component.body.start;
|
|
3073
|
+
if (shadowed || stuck || inParams || !component || tInScope(inner) || lib === "react-i18next" && component.async) blocked = true;
|
|
3074
|
+
else uses.push({ module: module2, node, local, component });
|
|
3075
|
+
});
|
|
3076
|
+
return blocked ? null : uses;
|
|
3077
|
+
}
|
|
3078
|
+
function allUses(data, modules, known, lib) {
|
|
3079
|
+
const own = usesOf(data.module, data.name, data.declarator.id, lib);
|
|
3080
|
+
if (!own) return null;
|
|
3081
|
+
const uses = [...own];
|
|
3082
|
+
for (const module2 of modules) {
|
|
3083
|
+
if (module2 === data.module) continue;
|
|
3084
|
+
for (const statement of module2.program.body ?? []) {
|
|
3085
|
+
if (statement.type !== "ImportDeclaration" || resolve3(module2.path, String(statement.source.value), known) !== data.module.path) continue;
|
|
3086
|
+
const specifier2 = (statement.specifiers ?? []).find((s) => s.type === "ImportSpecifier" && s.imported.name === data.name);
|
|
3087
|
+
if (!specifier2) continue;
|
|
3088
|
+
const found = usesOf(module2, String(specifier2.local.name), null, lib);
|
|
3089
|
+
if (!found) return null;
|
|
3090
|
+
uses.push(...found);
|
|
3091
|
+
}
|
|
3092
|
+
}
|
|
3093
|
+
return uses.length ? uses : null;
|
|
3094
|
+
}
|
|
3095
|
+
function parseModule({ path, text }) {
|
|
3096
|
+
if (text.includes("use server")) return null;
|
|
3097
|
+
const outcome = (0, import_lint12.parseSourceOutcome)(path, text, path);
|
|
3098
|
+
const block = "file" in outcome ? outcome.file.js[0] : void 0;
|
|
3099
|
+
return block ? { path, text, base: block.offset, program: block.ast.program } : null;
|
|
3100
|
+
}
|
|
3101
|
+
function keyFor2(keys, stem, en) {
|
|
3102
|
+
const known = keys.byText.get(`${stem}
|
|
3103
|
+
${en}`);
|
|
3104
|
+
if (known) return { key: known, isNew: false };
|
|
3105
|
+
const base = `${stem}.${keyWords(en)}`;
|
|
3106
|
+
let key = base;
|
|
3107
|
+
for (let n = 2; keys.used.has(key); n++) key = `${base}-${n}`;
|
|
3108
|
+
keys.used.add(key);
|
|
3109
|
+
keys.byText.set(`${stem}
|
|
3110
|
+
${en}`, key);
|
|
3111
|
+
return { key, isNew: true };
|
|
3112
|
+
}
|
|
3113
|
+
function dataEdits(data, keys, entries) {
|
|
3114
|
+
const { module: module2, init, declarator, strings } = data;
|
|
3115
|
+
const { base, text, path } = module2;
|
|
3116
|
+
const annotation = declarator.id.typeAnnotation;
|
|
3117
|
+
const returns = annotation ? text.slice(base + annotation.start, base + annotation.end) : "";
|
|
3118
|
+
const edits2 = [
|
|
3119
|
+
{ start: base + init.start, end: base + init.start, text: `(t: (key: string) => string)${returns} => (` },
|
|
3120
|
+
{ start: base + init.end, end: base + init.end, text: ")" },
|
|
3121
|
+
...annotation ? [{ start: base + annotation.start, end: base + annotation.end, text: "" }] : []
|
|
3122
|
+
];
|
|
3123
|
+
const stem = stemOf(path);
|
|
3124
|
+
for (const literal of strings) {
|
|
3125
|
+
const en = String(literal.value);
|
|
3126
|
+
const { key, isNew } = keyFor2(keys, stem, en);
|
|
3127
|
+
if (isNew) entries.push({ key, en, ar: "", status: "pending", file: path, line: lineOf(text, base + literal.start) });
|
|
3128
|
+
edits2.push({ start: base + literal.start, end: base + literal.end, text: `t('${key}')` });
|
|
3129
|
+
}
|
|
3130
|
+
return edits2;
|
|
3131
|
+
}
|
|
3132
|
+
function liftData(inputs, lib, taken) {
|
|
3133
|
+
const modules = inputs.map(parseModule).filter((m) => m !== null);
|
|
3134
|
+
const known = new Set(inputs.map((i) => (0, import_node_path16.normalize)(i.path)));
|
|
3135
|
+
const barred = new Set(modules.flatMap((m) => opaque(m, known)));
|
|
3136
|
+
const reserved = literalTypes(modules);
|
|
3137
|
+
const edits2 = /* @__PURE__ */ new Map();
|
|
3138
|
+
const components = /* @__PURE__ */ new Map();
|
|
3139
|
+
const entries = [];
|
|
3140
|
+
const keys = { used: new Set(taken), byText: /* @__PURE__ */ new Map() };
|
|
3141
|
+
const push = (module2, more) => {
|
|
3142
|
+
edits2.set(module2, [...edits2.get(module2) ?? [], ...more]);
|
|
3143
|
+
};
|
|
3144
|
+
let moved = 0;
|
|
3145
|
+
for (const data of modules.filter((m) => !barred.has((0, import_node_path16.normalize)(m.path))).flatMap((m) => dataIn(m, reserved))) {
|
|
3146
|
+
const uses = allUses(data, modules, known, lib);
|
|
3147
|
+
if (!uses) continue;
|
|
3148
|
+
push(data.module, dataEdits(data, keys, entries));
|
|
3149
|
+
moved += data.strings.length;
|
|
3150
|
+
for (const use of uses) {
|
|
3151
|
+
push(use.module, [{ start: use.module.base + use.node.end, end: use.module.base + use.node.end, text: "(t)" }]);
|
|
3152
|
+
components.set(use.module, (components.get(use.module) ?? /* @__PURE__ */ new Set()).add(use.component));
|
|
3153
|
+
}
|
|
3154
|
+
}
|
|
3155
|
+
for (const [module2, found] of components) {
|
|
3156
|
+
const style = styleOf(module2.program, module2.text, module2.base);
|
|
3157
|
+
const hooks = [...found].map((fn) => hookEdits(fn, lib, module2.text, module2.base, style));
|
|
3158
|
+
push(module2, [...hooks.flatMap((h) => h.edits), ...importEdits(module2.program, hooks.flatMap((h) => h.imports), module2.base, style)]);
|
|
3159
|
+
}
|
|
3160
|
+
const texts = new Map([...edits2].map(([module2, list]) => [module2.path, applyEdits(module2.text, list)]));
|
|
3161
|
+
return { texts, entries, moved };
|
|
3162
|
+
}
|
|
3163
|
+
|
|
2965
3164
|
// src/rollback.ts
|
|
2966
3165
|
var import_node_fs9 = require("fs");
|
|
2967
|
-
var
|
|
3166
|
+
var import_node_path17 = require("path");
|
|
2968
3167
|
var ConvertFailedError = class extends Error {
|
|
2969
3168
|
constructor(message, cause) {
|
|
2970
3169
|
super(message);
|
|
@@ -2983,7 +3182,7 @@ async function rollBack(target, step, error) {
|
|
|
2983
3182
|
const failed = `convert failed at ${step}: ${reasonOf(error)}.`;
|
|
2984
3183
|
try {
|
|
2985
3184
|
await resetHard(target.root);
|
|
2986
|
-
for (const path of [...target.created].reverse()) (0, import_node_fs9.rmSync)((0,
|
|
3185
|
+
for (const path of [...target.created].reverse()) (0, import_node_fs9.rmSync)((0, import_node_path17.join)(target.root, path), { recursive: true, force: true });
|
|
2987
3186
|
if (target.madeBranch) {
|
|
2988
3187
|
await checkout(target.root, target.base);
|
|
2989
3188
|
await deleteBranch(target.root, target.branch);
|
|
@@ -3063,7 +3262,7 @@ var branchFor = (mode) => `dualkit/${mode}`;
|
|
|
3063
3262
|
var problems = (flags) => flags.filter((f) => f.kind !== "info");
|
|
3064
3263
|
function transformAll(project, generated, taken) {
|
|
3065
3264
|
const { root } = project;
|
|
3066
|
-
const walked = (0,
|
|
3265
|
+
const walked = (0, import_lint13.walk)(["."], root);
|
|
3067
3266
|
const used = new Set(taken);
|
|
3068
3267
|
const ctx = {
|
|
3069
3268
|
tailwind: project.tailwind,
|
|
@@ -3072,18 +3271,21 @@ function transformAll(project, generated, taken) {
|
|
|
3072
3271
|
css: physicalContext(root)
|
|
3073
3272
|
};
|
|
3074
3273
|
const out = { texts: /* @__PURE__ */ new Map(), edits: 0, byKind: { tailwind: 0, css: 0, markup: 0, strings: 0, formats: 0 }, perFile: /* @__PURE__ */ new Map(), flags: [], entries: [], skipped: [...walked.skipped] };
|
|
3274
|
+
const lifted = lift(project, walked.files.filter((path) => !generated.has(path)), ctx.lib, used);
|
|
3275
|
+
out.entries.push(...lifted.entries);
|
|
3276
|
+
for (const entry of lifted.entries) used.add(entry.key);
|
|
3075
3277
|
for (const path of walked.files) {
|
|
3076
3278
|
if (generated.has(path)) continue;
|
|
3077
|
-
const raw = readText(root, path);
|
|
3279
|
+
const raw = lifted.texts.get(path) ?? readText(root, path);
|
|
3078
3280
|
if (raw === null) {
|
|
3079
3281
|
out.skipped.push({ file: path, reason: "unreadable" });
|
|
3080
3282
|
continue;
|
|
3081
3283
|
}
|
|
3082
|
-
if ((0,
|
|
3284
|
+
if ((0, import_lint13.isMinified)(raw)) {
|
|
3083
3285
|
out.skipped.push({ file: path, reason: "minified" });
|
|
3084
3286
|
continue;
|
|
3085
3287
|
}
|
|
3086
|
-
const result = runPasses(path, raw, (0,
|
|
3288
|
+
const result = runPasses(path, raw, (0, import_node_path18.join)(root, path), { ...ctx, taken: used });
|
|
3087
3289
|
if (result.kind === "skipped") {
|
|
3088
3290
|
out.skipped.push({ file: path, reason: result.reason });
|
|
3089
3291
|
continue;
|
|
@@ -3091,7 +3293,7 @@ function transformAll(project, generated, taken) {
|
|
|
3091
3293
|
out.flags.push(...result.flags);
|
|
3092
3294
|
out.entries.push(...result.entries);
|
|
3093
3295
|
for (const entry of result.entries) used.add(entry.key);
|
|
3094
|
-
if (result.edits === 0) continue;
|
|
3296
|
+
if (result.edits === 0 && !lifted.texts.has(path)) continue;
|
|
3095
3297
|
out.edits += result.edits;
|
|
3096
3298
|
for (const kind of Object.keys(out.byKind)) out.byKind[kind] += result.byKind[kind];
|
|
3097
3299
|
out.perFile.set(path, result.edits);
|
|
@@ -3099,6 +3301,13 @@ function transformAll(project, generated, taken) {
|
|
|
3099
3301
|
}
|
|
3100
3302
|
return out;
|
|
3101
3303
|
}
|
|
3304
|
+
function lift(project, paths, lib, taken) {
|
|
3305
|
+
const inputs = paths.flatMap((path) => {
|
|
3306
|
+
const text = /\.[jt]sx?$/.test(path) ? readText(project.root, path) : null;
|
|
3307
|
+
return text !== null && !text.startsWith(BOM) && !(0, import_lint13.isMinified)(text) ? [{ path, text }] : [];
|
|
3308
|
+
});
|
|
3309
|
+
return liftData(inputs, lib, taken);
|
|
3310
|
+
}
|
|
3102
3311
|
function changedFiles(perFile, moved, wiring) {
|
|
3103
3312
|
const total = /* @__PURE__ */ new Map();
|
|
3104
3313
|
const add = (file, n) => {
|
|
@@ -3188,7 +3397,7 @@ async function convert(options, ioIn) {
|
|
|
3188
3397
|
|
|
3189
3398
|
// src/finish.ts
|
|
3190
3399
|
var import_node_fs11 = require("fs");
|
|
3191
|
-
var
|
|
3400
|
+
var import_node_path20 = require("path");
|
|
3192
3401
|
|
|
3193
3402
|
// src/match.ts
|
|
3194
3403
|
var import_pngjs = require("pngjs");
|
|
@@ -3219,7 +3428,7 @@ function compare(a, b) {
|
|
|
3219
3428
|
|
|
3220
3429
|
// src/finish-undo.ts
|
|
3221
3430
|
var import_node_fs10 = require("fs");
|
|
3222
|
-
var
|
|
3431
|
+
var import_node_path19 = require("path");
|
|
3223
3432
|
async function snapshot(root, paths) {
|
|
3224
3433
|
const clean = new Set(await cleanAtHead(root, paths));
|
|
3225
3434
|
return paths.map((path) => ({ path, clean: clean.has(path), text: clean.has(path) ? null : readText(root, path) }));
|
|
@@ -3228,8 +3437,8 @@ async function undo(root, saved, staged) {
|
|
|
3228
3437
|
await restoreFromHead(root, saved.filter((s) => s.clean).map((s) => s.path));
|
|
3229
3438
|
const dirty = saved.filter((s) => !s.clean);
|
|
3230
3439
|
for (const { path, text } of dirty) {
|
|
3231
|
-
if (text === null) (0, import_node_fs10.rmSync)((0,
|
|
3232
|
-
else (0, import_node_fs10.writeFileSync)((0,
|
|
3440
|
+
if (text === null) (0, import_node_fs10.rmSync)((0, import_node_path19.join)(root, path), { force: true });
|
|
3441
|
+
else (0, import_node_fs10.writeFileSync)((0, import_node_path19.join)(root, path), text);
|
|
3233
3442
|
}
|
|
3234
3443
|
await unstage(root, [.../* @__PURE__ */ new Set([...dirty.map((s) => s.path), ...staged])]);
|
|
3235
3444
|
}
|
|
@@ -3408,7 +3617,7 @@ function writeMessages(root, ledger) {
|
|
|
3408
3617
|
for (const [path, text] of Object.entries(messagesFiles(ledger))) writeText(disk, path, mergedMessages(root, path, text));
|
|
3409
3618
|
}
|
|
3410
3619
|
function mergedMessages(root, path, generated) {
|
|
3411
|
-
const full = (0,
|
|
3620
|
+
const full = (0, import_node_path20.join)(root, path);
|
|
3412
3621
|
if (!(0, import_node_fs11.existsSync)(full)) return generated;
|
|
3413
3622
|
try {
|
|
3414
3623
|
const existing = JSON.parse((0, import_node_fs11.readFileSync)(full, "utf8"));
|
|
@@ -3423,7 +3632,7 @@ async function englishChecks(root, base, routes, shoot) {
|
|
|
3423
3632
|
const noBaseline = [];
|
|
3424
3633
|
for (const route of routes) {
|
|
3425
3634
|
for (const width of WIDTHS) {
|
|
3426
|
-
const file = (0,
|
|
3635
|
+
const file = (0, import_node_path20.join)(root, baselinePath(route, width));
|
|
3427
3636
|
if (!(0, import_node_fs11.existsSync)(file)) {
|
|
3428
3637
|
noBaseline.push({ route, width });
|
|
3429
3638
|
continue;
|
|
@@ -3433,8 +3642,8 @@ async function englishChecks(root, base, routes, shoot) {
|
|
|
3433
3642
|
let diffPath;
|
|
3434
3643
|
if (ratio >= MAX_DIFF_RATIO) {
|
|
3435
3644
|
diffPath = `${DIFF_DIR}/${routeSlug(route)}-${width}.png`;
|
|
3436
|
-
(0, import_node_fs11.mkdirSync)((0,
|
|
3437
|
-
(0, import_node_fs11.writeFileSync)((0,
|
|
3645
|
+
(0, import_node_fs11.mkdirSync)((0, import_node_path20.join)(root, DIFF_DIR), { recursive: true });
|
|
3646
|
+
(0, import_node_fs11.writeFileSync)((0, import_node_path20.join)(root, diffPath), diff);
|
|
3438
3647
|
}
|
|
3439
3648
|
english2.push({ route, width, ratio, ...diffPath ? { diffPath } : {} });
|
|
3440
3649
|
}
|
|
@@ -3455,7 +3664,7 @@ async function arabicChecks(project, state, base, routes, audit) {
|
|
|
3455
3664
|
return arabic2;
|
|
3456
3665
|
}
|
|
3457
3666
|
function closedAtBaseline(root) {
|
|
3458
|
-
const path = (0,
|
|
3667
|
+
const path = (0, import_node_path20.join)(root, CLOSED_FILE);
|
|
3459
3668
|
if (!(0, import_node_fs11.existsSync)(path)) return /* @__PURE__ */ new Set();
|
|
3460
3669
|
try {
|
|
3461
3670
|
const routes = JSON.parse((0, import_node_fs11.readFileSync)(path, "utf8"));
|
|
@@ -3525,17 +3734,17 @@ async function run(ready, step) {
|
|
|
3525
3734
|
remainingLint,
|
|
3526
3735
|
...tests2 ? { tests: { code: tests2.code, tail: tail(tests2.out) } } : {}
|
|
3527
3736
|
};
|
|
3528
|
-
(0, import_node_fs11.writeFileSync)((0,
|
|
3737
|
+
(0, import_node_fs11.writeFileSync)((0, import_node_path20.join)(root, REPORT_FILE), renderConvertReport(data));
|
|
3529
3738
|
step.name = "saving the state";
|
|
3530
3739
|
saveState(root, { ...state, phase: "finished" });
|
|
3531
3740
|
step.name = "saving your answers";
|
|
3532
3741
|
if (options.configAnswers) writeConfigAnswers({ root, created: [] }, options.configAnswers, configFile);
|
|
3533
3742
|
step.name = "committing";
|
|
3534
|
-
await stage(root, own.filter((p) => (p !== configFile || options.configAnswers) && (0, import_node_fs11.existsSync)((0,
|
|
3743
|
+
await stage(root, own.filter((p) => (p !== configFile || options.configAnswers) && (0, import_node_fs11.existsSync)((0, import_node_path20.join)(root, p))));
|
|
3535
3744
|
if ((await stagedFiles(root)).length) await commit(root, `dualkit: finish ${state.mode} conversion`);
|
|
3536
3745
|
const checked = checks.arabic.length > 0;
|
|
3537
3746
|
const passed = checked && checks.english.every((p) => p.ratio < MAX_DIFF_RATIO) && checks.arabic.every((p) => p.errors === 0) && (tests2?.code ?? 0) === 0;
|
|
3538
|
-
return { reportPath: (0,
|
|
3747
|
+
return { reportPath: (0, import_node_path20.join)(root, REPORT_FILE), passed };
|
|
3539
3748
|
}
|
|
3540
3749
|
async function finish(options, ioIn) {
|
|
3541
3750
|
const ready = await prepare(options, ioIn);
|
package/dist/index.js
CHANGED
|
@@ -143,7 +143,7 @@ function detectProject(root) {
|
|
|
143
143
|
|
|
144
144
|
// src/convert.ts
|
|
145
145
|
import { isMinified, walk as walk4 } from "@dualkit/lint";
|
|
146
|
-
import { join as
|
|
146
|
+
import { join as join10 } from "path";
|
|
147
147
|
|
|
148
148
|
// src/apply.ts
|
|
149
149
|
import { parseSourceOutcome } from "@dualkit/lint";
|
|
@@ -239,11 +239,11 @@ var RefusedError = class extends Error {
|
|
|
239
239
|
};
|
|
240
240
|
var NO_GIT = "git is not installed or not on PATH";
|
|
241
241
|
function git(cwd, args) {
|
|
242
|
-
return new Promise((
|
|
242
|
+
return new Promise((resolve4, reject) => {
|
|
243
243
|
execFile("git", ["--literal-pathspecs", ...args], { cwd, maxBuffer: 64 * 1024 * 1024 }, (error, stdout, stderr) => {
|
|
244
244
|
if (error && error.code === "ENOENT") return reject(new Error(existsSync3(cwd) ? NO_GIT : `The folder ${cwd} does not exist.`));
|
|
245
245
|
const code = error ? typeof error.code === "number" ? error.code : 1 : 0;
|
|
246
|
-
|
|
246
|
+
resolve4({ code, out: `${stdout}${stderr}`.trim(), stdout });
|
|
247
247
|
});
|
|
248
248
|
});
|
|
249
249
|
}
|
|
@@ -609,15 +609,15 @@ var ServerError = class extends Error {
|
|
|
609
609
|
var OUTPUT_LINES = 20;
|
|
610
610
|
var POLL_MS = 250;
|
|
611
611
|
var KILL_GRACE_MS = 5e3;
|
|
612
|
-
var sleep = (ms) => new Promise((
|
|
612
|
+
var sleep = (ms) => new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
613
613
|
function portTaken(port) {
|
|
614
|
-
return new Promise((
|
|
614
|
+
return new Promise((resolve4) => {
|
|
615
615
|
const socket = connect({ port, host: "localhost" });
|
|
616
616
|
socket.once("connect", () => {
|
|
617
617
|
socket.destroy();
|
|
618
|
-
|
|
618
|
+
resolve4(true);
|
|
619
619
|
});
|
|
620
|
-
socket.once("error", () =>
|
|
620
|
+
socket.once("error", () => resolve4(false));
|
|
621
621
|
});
|
|
622
622
|
}
|
|
623
623
|
async function answers(url) {
|
|
@@ -657,7 +657,7 @@ async function startServer(cmd, port, cwd, timeoutMs = 12e4) {
|
|
|
657
657
|
});
|
|
658
658
|
const pid = child.pid;
|
|
659
659
|
if (pid === void 0) {
|
|
660
|
-
await new Promise((
|
|
660
|
+
await new Promise((resolve4) => setImmediate(resolve4));
|
|
661
661
|
throw new ServerError(`Could not start "${cmd}": ${spawnError?.message ?? "no process id"}`);
|
|
662
662
|
}
|
|
663
663
|
const collect = (chunk) => {
|
|
@@ -1620,12 +1620,12 @@ function routeMoves(appDir, paths) {
|
|
|
1620
1620
|
}
|
|
1621
1621
|
return { moves, keptPages };
|
|
1622
1622
|
}
|
|
1623
|
-
function eachNode(node,
|
|
1624
|
-
if (Array.isArray(node)) return node.forEach((child) => eachNode(child,
|
|
1623
|
+
function eachNode(node, visit2) {
|
|
1624
|
+
if (Array.isArray(node)) return node.forEach((child) => eachNode(child, visit2));
|
|
1625
1625
|
if (!node || typeof node !== "object") return;
|
|
1626
1626
|
const n = node;
|
|
1627
|
-
if (typeof n.type === "string")
|
|
1628
|
-
for (const [key, child] of Object.entries(n)) if (!SKIP3.has(key)) eachNode(child,
|
|
1627
|
+
if (typeof n.type === "string") visit2(n);
|
|
1628
|
+
for (const [key, child] of Object.entries(n)) if (!SKIP3.has(key)) eachNode(child, visit2);
|
|
1629
1629
|
}
|
|
1630
1630
|
var RELATIVE = /^\.\.?\//;
|
|
1631
1631
|
var stringLiteral = (node) => node?.type === "StringLiteral" ? node : null;
|
|
@@ -2927,9 +2927,208 @@ function runPasses(path, text, abs, ctx) {
|
|
|
2927
2927
|
return { kind: "done", text: file.text, edits: edits2, byKind, flags, entries };
|
|
2928
2928
|
}
|
|
2929
2929
|
|
|
2930
|
+
// src/transforms/data.ts
|
|
2931
|
+
import { dirname as dirname4, join as join8, normalize } from "path";
|
|
2932
|
+
import { parseSourceOutcome as parseSourceOutcome7 } from "@dualkit/lint";
|
|
2933
|
+
var SKIP8 = /* @__PURE__ */ new Set(["loc", "leadingComments", "trailingComments", "innerComments", "extra"]);
|
|
2934
|
+
var TEXT_KEYS = /* @__PURE__ */ new Set(["title", "label", "description", "placeholder", "subtitle", "message", "text", "alt", "heading", "caption"]);
|
|
2935
|
+
var NAV_KEYS = /* @__PURE__ */ new Set(["href", "url", "path", "to", "icon"]);
|
|
2936
|
+
var RESERVED = /* @__PURE__ */ new Set(["metadata", "viewport", "config", "routing"]);
|
|
2937
|
+
var PROSE2 = /^\p{Lu}\p{Ll}/u;
|
|
2938
|
+
var NOT_PROSE2 = /[/\\_=<>{}@#|]/;
|
|
2939
|
+
var EXTENSIONS3 = ["", ".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js", "/index.jsx"];
|
|
2940
|
+
var ALIASES = /^[@~]\//;
|
|
2941
|
+
var unwrap = (node) => node.type === "TSAsExpression" || node.type === "TSSatisfiesExpression" ? unwrap(node.expression) : node;
|
|
2942
|
+
var children = (node) => Object.entries(node).filter(([key]) => !SKIP8.has(key));
|
|
2943
|
+
function visit(node, ancestors, fn) {
|
|
2944
|
+
if (Array.isArray(node)) return node.forEach((child) => visit(child, ancestors, fn));
|
|
2945
|
+
if (!node || typeof node !== "object") return;
|
|
2946
|
+
const n = node;
|
|
2947
|
+
if (typeof n.type === "string") fn(n, ancestors);
|
|
2948
|
+
const next = typeof n.type === "string" ? [...ancestors, n] : ancestors;
|
|
2949
|
+
for (const [, child] of children(n)) visit(child, next, fn);
|
|
2950
|
+
}
|
|
2951
|
+
var keyName = (property) => {
|
|
2952
|
+
const key = property.key;
|
|
2953
|
+
if (property.computed) return null;
|
|
2954
|
+
return key.type === "Identifier" ? String(key.name) : key.type === "StringLiteral" ? String(key.value) : null;
|
|
2955
|
+
};
|
|
2956
|
+
var isProse = (value) => PROSE2.test(value) && !NOT_PROSE2.test(value);
|
|
2957
|
+
function textProperty(property, parent) {
|
|
2958
|
+
const key = keyName(property);
|
|
2959
|
+
if (key === null) return false;
|
|
2960
|
+
if (TEXT_KEYS.has(key)) return true;
|
|
2961
|
+
if (key !== "name" || parent?.type !== "ObjectExpression") return false;
|
|
2962
|
+
return (parent.properties ?? []).some((p) => p.type === "ObjectProperty" && NAV_KEYS.has(keyName(p) ?? ""));
|
|
2963
|
+
}
|
|
2964
|
+
function textsIn(init, reserved) {
|
|
2965
|
+
const strings = [];
|
|
2966
|
+
let clash = false;
|
|
2967
|
+
visit(init, [], (node, above) => {
|
|
2968
|
+
if (FUNCTIONS.has(node.type) && scopeNames(node).includes("t")) clash = true;
|
|
2969
|
+
if (node.type !== "ObjectProperty" || above.some((a) => FUNCTIONS.has(a.type) || a.type?.startsWith("JSX"))) return;
|
|
2970
|
+
const value = node.value;
|
|
2971
|
+
if (value.type === "StringLiteral" && isProse(String(value.value)) && !reserved.has(String(value.value)) && textProperty(node, above[above.length - 1])) strings.push(value);
|
|
2972
|
+
});
|
|
2973
|
+
return clash ? null : strings;
|
|
2974
|
+
}
|
|
2975
|
+
function dataIn(module, reserved) {
|
|
2976
|
+
const found = [];
|
|
2977
|
+
for (const statement of module.program.body ?? []) {
|
|
2978
|
+
const declaration = statement.type === "ExportNamedDeclaration" ? statement.declaration : statement;
|
|
2979
|
+
if (declaration?.type !== "VariableDeclaration" || declaration.kind !== "const") continue;
|
|
2980
|
+
for (const declarator of declaration.declarations ?? []) {
|
|
2981
|
+
const id = declarator.id;
|
|
2982
|
+
const init = declarator.init;
|
|
2983
|
+
if (id.type !== "Identifier" || !init || RESERVED.has(String(id.name))) continue;
|
|
2984
|
+
if (!["ArrayExpression", "ObjectExpression"].includes(unwrap(init).type)) continue;
|
|
2985
|
+
const strings = textsIn(init, reserved);
|
|
2986
|
+
if (strings?.length) found.push({ module, name: String(id.name), declarator, init, strings });
|
|
2987
|
+
}
|
|
2988
|
+
}
|
|
2989
|
+
return found;
|
|
2990
|
+
}
|
|
2991
|
+
function literalTypes(modules) {
|
|
2992
|
+
const found = /* @__PURE__ */ new Set();
|
|
2993
|
+
for (const module of modules) {
|
|
2994
|
+
visit(module.program, [], (node) => {
|
|
2995
|
+
const literal = node.type === "TSLiteralType" ? node.literal : null;
|
|
2996
|
+
if (literal?.type === "StringLiteral") found.add(String(literal.value));
|
|
2997
|
+
});
|
|
2998
|
+
}
|
|
2999
|
+
return found;
|
|
3000
|
+
}
|
|
3001
|
+
function resolve3(from, source, known) {
|
|
3002
|
+
const bases = source.startsWith(".") ? [join8(dirname4(from), source)] : ALIASES.test(source) ? [source.slice(2), join8("src", source.slice(2))] : [];
|
|
3003
|
+
for (const base of bases) {
|
|
3004
|
+
for (const extension of EXTENSIONS3) {
|
|
3005
|
+
const path = normalize(`${base}${extension}`);
|
|
3006
|
+
if (known.has(path)) return path;
|
|
3007
|
+
}
|
|
3008
|
+
}
|
|
3009
|
+
return null;
|
|
3010
|
+
}
|
|
3011
|
+
function opaque(module, known) {
|
|
3012
|
+
const paths = [];
|
|
3013
|
+
for (const statement of module.program.body ?? []) {
|
|
3014
|
+
const source = statement.source;
|
|
3015
|
+
if (!source) continue;
|
|
3016
|
+
const path = resolve3(module.path, String(source.value), known);
|
|
3017
|
+
if (!path) continue;
|
|
3018
|
+
const star = (statement.specifiers ?? []).some((s) => s.type === "ImportNamespaceSpecifier");
|
|
3019
|
+
if (statement.type !== "ImportDeclaration" || star) paths.push(path);
|
|
3020
|
+
}
|
|
3021
|
+
return paths;
|
|
3022
|
+
}
|
|
3023
|
+
function usesOf(module, local, skip, lib) {
|
|
3024
|
+
const uses = [];
|
|
3025
|
+
let blocked = false;
|
|
3026
|
+
visit(module.program, [], (node, above) => {
|
|
3027
|
+
if (blocked || node.type !== "Identifier" || node.name !== local || node === skip) return;
|
|
3028
|
+
const parent = above[above.length - 1];
|
|
3029
|
+
if (parent.type?.startsWith("Import") || parent.type === "TSTypeReference" || parent.type === "TSQualifiedName") return;
|
|
3030
|
+
if (parent.type === "MemberExpression" && parent.property === node && !parent.computed) return;
|
|
3031
|
+
if (parent.type === "OptionalMemberExpression" && parent.property === node && !parent.computed) return;
|
|
3032
|
+
if (parent.type === "ObjectProperty" && parent.key === node && !parent.computed && !parent.shorthand) return;
|
|
3033
|
+
const inner = above.slice(1);
|
|
3034
|
+
const shadowed = inner.some((a) => scopeNames(a).includes(local));
|
|
3035
|
+
const component = componentOf(above);
|
|
3036
|
+
const stuck = parent.type === "TSTypeQuery" || parent.type?.startsWith("Export") || Boolean(parent.shorthand);
|
|
3037
|
+
const inParams = component !== null && node.start < component.body.start;
|
|
3038
|
+
if (shadowed || stuck || inParams || !component || tInScope(inner) || lib === "react-i18next" && component.async) blocked = true;
|
|
3039
|
+
else uses.push({ module, node, local, component });
|
|
3040
|
+
});
|
|
3041
|
+
return blocked ? null : uses;
|
|
3042
|
+
}
|
|
3043
|
+
function allUses(data, modules, known, lib) {
|
|
3044
|
+
const own = usesOf(data.module, data.name, data.declarator.id, lib);
|
|
3045
|
+
if (!own) return null;
|
|
3046
|
+
const uses = [...own];
|
|
3047
|
+
for (const module of modules) {
|
|
3048
|
+
if (module === data.module) continue;
|
|
3049
|
+
for (const statement of module.program.body ?? []) {
|
|
3050
|
+
if (statement.type !== "ImportDeclaration" || resolve3(module.path, String(statement.source.value), known) !== data.module.path) continue;
|
|
3051
|
+
const specifier2 = (statement.specifiers ?? []).find((s) => s.type === "ImportSpecifier" && s.imported.name === data.name);
|
|
3052
|
+
if (!specifier2) continue;
|
|
3053
|
+
const found = usesOf(module, String(specifier2.local.name), null, lib);
|
|
3054
|
+
if (!found) return null;
|
|
3055
|
+
uses.push(...found);
|
|
3056
|
+
}
|
|
3057
|
+
}
|
|
3058
|
+
return uses.length ? uses : null;
|
|
3059
|
+
}
|
|
3060
|
+
function parseModule({ path, text }) {
|
|
3061
|
+
if (text.includes("use server")) return null;
|
|
3062
|
+
const outcome = parseSourceOutcome7(path, text, path);
|
|
3063
|
+
const block = "file" in outcome ? outcome.file.js[0] : void 0;
|
|
3064
|
+
return block ? { path, text, base: block.offset, program: block.ast.program } : null;
|
|
3065
|
+
}
|
|
3066
|
+
function keyFor2(keys, stem, en) {
|
|
3067
|
+
const known = keys.byText.get(`${stem}
|
|
3068
|
+
${en}`);
|
|
3069
|
+
if (known) return { key: known, isNew: false };
|
|
3070
|
+
const base = `${stem}.${keyWords(en)}`;
|
|
3071
|
+
let key = base;
|
|
3072
|
+
for (let n = 2; keys.used.has(key); n++) key = `${base}-${n}`;
|
|
3073
|
+
keys.used.add(key);
|
|
3074
|
+
keys.byText.set(`${stem}
|
|
3075
|
+
${en}`, key);
|
|
3076
|
+
return { key, isNew: true };
|
|
3077
|
+
}
|
|
3078
|
+
function dataEdits(data, keys, entries) {
|
|
3079
|
+
const { module, init, declarator, strings } = data;
|
|
3080
|
+
const { base, text, path } = module;
|
|
3081
|
+
const annotation = declarator.id.typeAnnotation;
|
|
3082
|
+
const returns = annotation ? text.slice(base + annotation.start, base + annotation.end) : "";
|
|
3083
|
+
const edits2 = [
|
|
3084
|
+
{ start: base + init.start, end: base + init.start, text: `(t: (key: string) => string)${returns} => (` },
|
|
3085
|
+
{ start: base + init.end, end: base + init.end, text: ")" },
|
|
3086
|
+
...annotation ? [{ start: base + annotation.start, end: base + annotation.end, text: "" }] : []
|
|
3087
|
+
];
|
|
3088
|
+
const stem = stemOf(path);
|
|
3089
|
+
for (const literal of strings) {
|
|
3090
|
+
const en = String(literal.value);
|
|
3091
|
+
const { key, isNew } = keyFor2(keys, stem, en);
|
|
3092
|
+
if (isNew) entries.push({ key, en, ar: "", status: "pending", file: path, line: lineOf(text, base + literal.start) });
|
|
3093
|
+
edits2.push({ start: base + literal.start, end: base + literal.end, text: `t('${key}')` });
|
|
3094
|
+
}
|
|
3095
|
+
return edits2;
|
|
3096
|
+
}
|
|
3097
|
+
function liftData(inputs, lib, taken) {
|
|
3098
|
+
const modules = inputs.map(parseModule).filter((m) => m !== null);
|
|
3099
|
+
const known = new Set(inputs.map((i) => normalize(i.path)));
|
|
3100
|
+
const barred = new Set(modules.flatMap((m) => opaque(m, known)));
|
|
3101
|
+
const reserved = literalTypes(modules);
|
|
3102
|
+
const edits2 = /* @__PURE__ */ new Map();
|
|
3103
|
+
const components = /* @__PURE__ */ new Map();
|
|
3104
|
+
const entries = [];
|
|
3105
|
+
const keys = { used: new Set(taken), byText: /* @__PURE__ */ new Map() };
|
|
3106
|
+
const push = (module, more) => {
|
|
3107
|
+
edits2.set(module, [...edits2.get(module) ?? [], ...more]);
|
|
3108
|
+
};
|
|
3109
|
+
let moved = 0;
|
|
3110
|
+
for (const data of modules.filter((m) => !barred.has(normalize(m.path))).flatMap((m) => dataIn(m, reserved))) {
|
|
3111
|
+
const uses = allUses(data, modules, known, lib);
|
|
3112
|
+
if (!uses) continue;
|
|
3113
|
+
push(data.module, dataEdits(data, keys, entries));
|
|
3114
|
+
moved += data.strings.length;
|
|
3115
|
+
for (const use of uses) {
|
|
3116
|
+
push(use.module, [{ start: use.module.base + use.node.end, end: use.module.base + use.node.end, text: "(t)" }]);
|
|
3117
|
+
components.set(use.module, (components.get(use.module) ?? /* @__PURE__ */ new Set()).add(use.component));
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
3120
|
+
for (const [module, found] of components) {
|
|
3121
|
+
const style = styleOf(module.program, module.text, module.base);
|
|
3122
|
+
const hooks = [...found].map((fn) => hookEdits(fn, lib, module.text, module.base, style));
|
|
3123
|
+
push(module, [...hooks.flatMap((h) => h.edits), ...importEdits(module.program, hooks.flatMap((h) => h.imports), module.base, style)]);
|
|
3124
|
+
}
|
|
3125
|
+
const texts = new Map([...edits2].map(([module, list]) => [module.path, applyEdits(module.text, list)]));
|
|
3126
|
+
return { texts, entries, moved };
|
|
3127
|
+
}
|
|
3128
|
+
|
|
2930
3129
|
// src/rollback.ts
|
|
2931
3130
|
import { rmSync } from "fs";
|
|
2932
|
-
import { join as
|
|
3131
|
+
import { join as join9 } from "path";
|
|
2933
3132
|
var ConvertFailedError = class extends Error {
|
|
2934
3133
|
constructor(message, cause) {
|
|
2935
3134
|
super(message);
|
|
@@ -2948,7 +3147,7 @@ async function rollBack(target, step, error) {
|
|
|
2948
3147
|
const failed = `convert failed at ${step}: ${reasonOf(error)}.`;
|
|
2949
3148
|
try {
|
|
2950
3149
|
await resetHard(target.root);
|
|
2951
|
-
for (const path of [...target.created].reverse()) rmSync(
|
|
3150
|
+
for (const path of [...target.created].reverse()) rmSync(join9(target.root, path), { recursive: true, force: true });
|
|
2952
3151
|
if (target.madeBranch) {
|
|
2953
3152
|
await checkout(target.root, target.base);
|
|
2954
3153
|
await deleteBranch(target.root, target.branch);
|
|
@@ -3037,9 +3236,12 @@ function transformAll(project, generated, taken) {
|
|
|
3037
3236
|
css: physicalContext(root)
|
|
3038
3237
|
};
|
|
3039
3238
|
const out = { texts: /* @__PURE__ */ new Map(), edits: 0, byKind: { tailwind: 0, css: 0, markup: 0, strings: 0, formats: 0 }, perFile: /* @__PURE__ */ new Map(), flags: [], entries: [], skipped: [...walked.skipped] };
|
|
3239
|
+
const lifted = lift(project, walked.files.filter((path) => !generated.has(path)), ctx.lib, used);
|
|
3240
|
+
out.entries.push(...lifted.entries);
|
|
3241
|
+
for (const entry of lifted.entries) used.add(entry.key);
|
|
3040
3242
|
for (const path of walked.files) {
|
|
3041
3243
|
if (generated.has(path)) continue;
|
|
3042
|
-
const raw = readText(root, path);
|
|
3244
|
+
const raw = lifted.texts.get(path) ?? readText(root, path);
|
|
3043
3245
|
if (raw === null) {
|
|
3044
3246
|
out.skipped.push({ file: path, reason: "unreadable" });
|
|
3045
3247
|
continue;
|
|
@@ -3048,7 +3250,7 @@ function transformAll(project, generated, taken) {
|
|
|
3048
3250
|
out.skipped.push({ file: path, reason: "minified" });
|
|
3049
3251
|
continue;
|
|
3050
3252
|
}
|
|
3051
|
-
const result = runPasses(path, raw,
|
|
3253
|
+
const result = runPasses(path, raw, join10(root, path), { ...ctx, taken: used });
|
|
3052
3254
|
if (result.kind === "skipped") {
|
|
3053
3255
|
out.skipped.push({ file: path, reason: result.reason });
|
|
3054
3256
|
continue;
|
|
@@ -3056,7 +3258,7 @@ function transformAll(project, generated, taken) {
|
|
|
3056
3258
|
out.flags.push(...result.flags);
|
|
3057
3259
|
out.entries.push(...result.entries);
|
|
3058
3260
|
for (const entry of result.entries) used.add(entry.key);
|
|
3059
|
-
if (result.edits === 0) continue;
|
|
3261
|
+
if (result.edits === 0 && !lifted.texts.has(path)) continue;
|
|
3060
3262
|
out.edits += result.edits;
|
|
3061
3263
|
for (const kind of Object.keys(out.byKind)) out.byKind[kind] += result.byKind[kind];
|
|
3062
3264
|
out.perFile.set(path, result.edits);
|
|
@@ -3064,6 +3266,13 @@ function transformAll(project, generated, taken) {
|
|
|
3064
3266
|
}
|
|
3065
3267
|
return out;
|
|
3066
3268
|
}
|
|
3269
|
+
function lift(project, paths, lib, taken) {
|
|
3270
|
+
const inputs = paths.flatMap((path) => {
|
|
3271
|
+
const text = /\.[jt]sx?$/.test(path) ? readText(project.root, path) : null;
|
|
3272
|
+
return text !== null && !text.startsWith(BOM) && !isMinified(text) ? [{ path, text }] : [];
|
|
3273
|
+
});
|
|
3274
|
+
return liftData(inputs, lib, taken);
|
|
3275
|
+
}
|
|
3067
3276
|
function changedFiles(perFile, moved, wiring) {
|
|
3068
3277
|
const total = /* @__PURE__ */ new Map();
|
|
3069
3278
|
const add = (file, n) => {
|
|
@@ -3153,7 +3362,7 @@ async function convert(options, ioIn) {
|
|
|
3153
3362
|
|
|
3154
3363
|
// src/finish.ts
|
|
3155
3364
|
import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync7 } from "fs";
|
|
3156
|
-
import { join as
|
|
3365
|
+
import { join as join12 } from "path";
|
|
3157
3366
|
|
|
3158
3367
|
// src/match.ts
|
|
3159
3368
|
import { PNG } from "pngjs";
|
|
@@ -3184,7 +3393,7 @@ function compare(a, b) {
|
|
|
3184
3393
|
|
|
3185
3394
|
// src/finish-undo.ts
|
|
3186
3395
|
import { rmSync as rmSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
3187
|
-
import { join as
|
|
3396
|
+
import { join as join11 } from "path";
|
|
3188
3397
|
async function snapshot(root, paths) {
|
|
3189
3398
|
const clean = new Set(await cleanAtHead(root, paths));
|
|
3190
3399
|
return paths.map((path) => ({ path, clean: clean.has(path), text: clean.has(path) ? null : readText(root, path) }));
|
|
@@ -3193,8 +3402,8 @@ async function undo(root, saved, staged) {
|
|
|
3193
3402
|
await restoreFromHead(root, saved.filter((s) => s.clean).map((s) => s.path));
|
|
3194
3403
|
const dirty = saved.filter((s) => !s.clean);
|
|
3195
3404
|
for (const { path, text } of dirty) {
|
|
3196
|
-
if (text === null) rmSync2(
|
|
3197
|
-
else writeFileSync6(
|
|
3405
|
+
if (text === null) rmSync2(join11(root, path), { force: true });
|
|
3406
|
+
else writeFileSync6(join11(root, path), text);
|
|
3198
3407
|
}
|
|
3199
3408
|
await unstage(root, [.../* @__PURE__ */ new Set([...dirty.map((s) => s.path), ...staged])]);
|
|
3200
3409
|
}
|
|
@@ -3373,7 +3582,7 @@ function writeMessages(root, ledger) {
|
|
|
3373
3582
|
for (const [path, text] of Object.entries(messagesFiles(ledger))) writeText(disk, path, mergedMessages(root, path, text));
|
|
3374
3583
|
}
|
|
3375
3584
|
function mergedMessages(root, path, generated) {
|
|
3376
|
-
const full =
|
|
3585
|
+
const full = join12(root, path);
|
|
3377
3586
|
if (!existsSync8(full)) return generated;
|
|
3378
3587
|
try {
|
|
3379
3588
|
const existing = JSON.parse(readFileSync7(full, "utf8"));
|
|
@@ -3388,7 +3597,7 @@ async function englishChecks(root, base, routes, shoot) {
|
|
|
3388
3597
|
const noBaseline = [];
|
|
3389
3598
|
for (const route of routes) {
|
|
3390
3599
|
for (const width of WIDTHS) {
|
|
3391
|
-
const file =
|
|
3600
|
+
const file = join12(root, baselinePath(route, width));
|
|
3392
3601
|
if (!existsSync8(file)) {
|
|
3393
3602
|
noBaseline.push({ route, width });
|
|
3394
3603
|
continue;
|
|
@@ -3398,8 +3607,8 @@ async function englishChecks(root, base, routes, shoot) {
|
|
|
3398
3607
|
let diffPath;
|
|
3399
3608
|
if (ratio >= MAX_DIFF_RATIO) {
|
|
3400
3609
|
diffPath = `${DIFF_DIR}/${routeSlug(route)}-${width}.png`;
|
|
3401
|
-
mkdirSync5(
|
|
3402
|
-
writeFileSync7(
|
|
3610
|
+
mkdirSync5(join12(root, DIFF_DIR), { recursive: true });
|
|
3611
|
+
writeFileSync7(join12(root, diffPath), diff);
|
|
3403
3612
|
}
|
|
3404
3613
|
english2.push({ route, width, ratio, ...diffPath ? { diffPath } : {} });
|
|
3405
3614
|
}
|
|
@@ -3420,7 +3629,7 @@ async function arabicChecks(project, state, base, routes, audit) {
|
|
|
3420
3629
|
return arabic2;
|
|
3421
3630
|
}
|
|
3422
3631
|
function closedAtBaseline(root) {
|
|
3423
|
-
const path =
|
|
3632
|
+
const path = join12(root, CLOSED_FILE);
|
|
3424
3633
|
if (!existsSync8(path)) return /* @__PURE__ */ new Set();
|
|
3425
3634
|
try {
|
|
3426
3635
|
const routes = JSON.parse(readFileSync7(path, "utf8"));
|
|
@@ -3490,17 +3699,17 @@ async function run(ready, step) {
|
|
|
3490
3699
|
remainingLint,
|
|
3491
3700
|
...tests2 ? { tests: { code: tests2.code, tail: tail(tests2.out) } } : {}
|
|
3492
3701
|
};
|
|
3493
|
-
writeFileSync7(
|
|
3702
|
+
writeFileSync7(join12(root, REPORT_FILE), renderConvertReport(data));
|
|
3494
3703
|
step.name = "saving the state";
|
|
3495
3704
|
saveState(root, { ...state, phase: "finished" });
|
|
3496
3705
|
step.name = "saving your answers";
|
|
3497
3706
|
if (options.configAnswers) writeConfigAnswers({ root, created: [] }, options.configAnswers, configFile);
|
|
3498
3707
|
step.name = "committing";
|
|
3499
|
-
await stage(root, own.filter((p) => (p !== configFile || options.configAnswers) && existsSync8(
|
|
3708
|
+
await stage(root, own.filter((p) => (p !== configFile || options.configAnswers) && existsSync8(join12(root, p))));
|
|
3500
3709
|
if ((await stagedFiles(root)).length) await commit(root, `dualkit: finish ${state.mode} conversion`);
|
|
3501
3710
|
const checked = checks.arabic.length > 0;
|
|
3502
3711
|
const passed = checked && checks.english.every((p) => p.ratio < MAX_DIFF_RATIO) && checks.arabic.every((p) => p.errors === 0) && (tests2?.code ?? 0) === 0;
|
|
3503
|
-
return { reportPath:
|
|
3712
|
+
return { reportPath: join12(root, REPORT_FILE), passed };
|
|
3504
3713
|
}
|
|
3505
3714
|
async function finish(options, ioIn) {
|
|
3506
3715
|
const ready = await prepare(options, ioIn);
|