isotone 0.4.0 → 0.4.2
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/CHANGELOG.md +22 -0
- package/dist/chunk-A4DX2V4C.js +7 -0
- package/dist/chunk-A4DX2V4C.js.map +1 -0
- package/dist/{chunk-DD767R7E.js → chunk-AP7O357L.js} +6 -2
- package/dist/chunk-AP7O357L.js.map +1 -0
- package/dist/{chunk-7WGLOXRQ.js → chunk-DD6BKC3R.js} +4 -46
- package/dist/chunk-DD6BKC3R.js.map +1 -0
- package/dist/{chunk-QT27DAPQ.js → chunk-LHCQL5PA.js} +2 -3
- package/dist/{chunk-QT27DAPQ.js.map → chunk-LHCQL5PA.js.map} +1 -1
- package/dist/{chunk-SNGBNVH2.js → chunk-OSETWKGE.js} +1 -27
- package/dist/{chunk-SNGBNVH2.js.map → chunk-OSETWKGE.js.map} +1 -1
- package/dist/{chunk-GDVZ3B6N.js → chunk-UYMMI2IH.js} +43 -6
- package/dist/chunk-UYMMI2IH.js.map +1 -0
- package/dist/cli.js +11 -310
- package/dist/cli.js.map +1 -1
- package/dist/core.d.ts +1 -0
- package/dist/core.js +3 -2
- package/dist/index.js +14 -6
- package/dist/index.js.map +1 -1
- package/dist/isotone.global.js +18 -18
- package/dist/serve.js +3 -2
- package/dist/serve.js.map +1 -1
- package/dist/server.js +4 -3
- package/dist/server.js.map +1 -1
- package/dist/vite-plugin.js +5 -4
- package/dist/vite-plugin.js.map +1 -1
- package/docs/migration.md +33 -83
- package/docs/updates.md +3 -3
- package/package.json +1 -1
- package/dist/chunk-7WGLOXRQ.js.map +0 -1
- package/dist/chunk-DD767R7E.js.map +0 -1
- package/dist/chunk-GDVZ3B6N.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -13,26 +13,26 @@ import {
|
|
|
13
13
|
syncVariant,
|
|
14
14
|
variantDir,
|
|
15
15
|
writeActive
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-DD6BKC3R.js";
|
|
17
17
|
import {
|
|
18
18
|
overlayGroups,
|
|
19
19
|
variantAppliesTo
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-OSETWKGE.js";
|
|
21
21
|
import {
|
|
22
|
-
checkUpdate
|
|
23
|
-
|
|
24
|
-
} from "./chunk-QT27DAPQ.js";
|
|
22
|
+
checkUpdate
|
|
23
|
+
} from "./chunk-LHCQL5PA.js";
|
|
25
24
|
import {
|
|
26
25
|
readNotes,
|
|
27
26
|
updateNotes
|
|
28
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-AP7O357L.js";
|
|
28
|
+
import "./chunk-A4DX2V4C.js";
|
|
29
29
|
|
|
30
30
|
// src/cli/index.ts
|
|
31
31
|
import { realpathSync } from "fs";
|
|
32
32
|
import { fileURLToPath } from "url";
|
|
33
33
|
|
|
34
34
|
// src/cli/run.ts
|
|
35
|
-
import { resolve as
|
|
35
|
+
import { resolve as resolve2 } from "path";
|
|
36
36
|
|
|
37
37
|
// src/cli/init.ts
|
|
38
38
|
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "fs";
|
|
@@ -124,7 +124,7 @@ function runInit(args, io) {
|
|
|
124
124
|
}
|
|
125
125
|
const { value: approachArg, rest: r1 } = extractFlag(args, "--approach");
|
|
126
126
|
const { present: yes } = extractBoolFlag(r1, "--yes");
|
|
127
|
-
const
|
|
127
|
+
const json = args.includes("--json");
|
|
128
128
|
if (help) {
|
|
129
129
|
out(io, "Usage: isotone init look at this repo and recommend an approach");
|
|
130
130
|
out(io, " isotone init --approach <name> print the setup for overlay, host or serve");
|
|
@@ -142,7 +142,7 @@ function runInit(args, io) {
|
|
|
142
142
|
errOut(io, `Unknown approach "${approach}". Use host, overlay or serve.`);
|
|
143
143
|
return 1;
|
|
144
144
|
}
|
|
145
|
-
if (
|
|
145
|
+
if (json) {
|
|
146
146
|
const instructions = [];
|
|
147
147
|
printApproach({ ...io, stdout: { write: (line) => instructions.push(line) } }, approach, host);
|
|
148
148
|
out(io, JSON.stringify({
|
|
@@ -757,308 +757,10 @@ async function runCheckUpdate(args, io) {
|
|
|
757
757
|
return result.status === "unavailable" ? 1 : 0;
|
|
758
758
|
}
|
|
759
759
|
|
|
760
|
-
// src/cli/migrate.ts
|
|
761
|
-
import { execFileSync } from "child_process";
|
|
762
|
-
import { createHash, randomUUID } from "crypto";
|
|
763
|
-
import { existsSync as existsSync3, lstatSync, readFileSync as readFileSync2, readdirSync as readdirSync2, renameSync, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
764
|
-
import { basename, dirname, extname, join as join3, relative as relative2, resolve as resolve2 } from "path";
|
|
765
|
-
var OLD = "iso-iterate";
|
|
766
|
-
var NEW = "isotone";
|
|
767
|
-
var TEXT = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts", ".json", ".jsonc", ".md", ".mdx", ".html", ".css", ".scss", ".sass", ".less", ".vue", ".svelte", ".yaml", ".yml", ".toml", ".sh"]);
|
|
768
|
-
var SKIP = /* @__PURE__ */ new Set([".git", "node_modules", "dist", "build", ".next", ".nuxt", ".output", "coverage", ".turbo"]);
|
|
769
|
-
var LOCKS = /* @__PURE__ */ new Set(["package-lock.json", "npm-shrinkwrap.json", "pnpm-lock.yaml", "yarn.lock", "bun.lock", "bun.lockb"]);
|
|
770
|
-
function git(cwd, args, input) {
|
|
771
|
-
return execFileSync("git", args, { cwd, input, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], maxBuffer: 32 * 1024 * 1024 }).trimEnd();
|
|
772
|
-
}
|
|
773
|
-
function renamed(text) {
|
|
774
|
-
const keys = [];
|
|
775
|
-
const protect = (match, prefix, quote, value) => {
|
|
776
|
-
if (value === "iso-iterate-feedback" || value === "iso-iterate:knobs") return match;
|
|
777
|
-
const index = keys.push(value) - 1;
|
|
778
|
-
return `${prefix}${quote}\0KEY${index}\0${quote}`;
|
|
779
|
-
};
|
|
780
|
-
const protectedText = text.replace(/(\b(?:key|storageKey)\s*:\s*|["'](?:key|storageKey)["']\s*:\s*)(["'`])([\s\S]*?)\2/g, protect).replace(/(\b(?:localStorage|sessionStorage)\.(?:getItem|setItem|removeItem)\(\s*)(["'`])([\s\S]*?)\2/g, protect);
|
|
781
|
-
let result = protectedText.replace(/\biso-iterate@\d+\.\d+\.\d+(?:-[\da-zA-Z.-]+)?(?:\+[\da-zA-Z.-]+)?/g, `${NEW}@${version}`).replaceAll(OLD, NEW).replaceAll("iso-iter-", "isotone-").replaceAll("isoIterate", "isotone").replaceAll("IsoIterate", "Isotone").replaceAll("ISO_ITERATE", "ISOTONE").replaceAll("ISO_VARIANT", "ISOTONE_VARIANT");
|
|
782
|
-
keys.forEach((value, index) => {
|
|
783
|
-
result = result.replaceAll(`\0KEY${index}\0`, value);
|
|
784
|
-
});
|
|
785
|
-
return result;
|
|
786
|
-
}
|
|
787
|
-
function present(path) {
|
|
788
|
-
try {
|
|
789
|
-
lstatSync(path);
|
|
790
|
-
return true;
|
|
791
|
-
} catch {
|
|
792
|
-
return false;
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
function textContents(buffer) {
|
|
796
|
-
if (buffer.includes(0)) return null;
|
|
797
|
-
const text = buffer.toString("utf8");
|
|
798
|
-
return Buffer.from(text).compare(buffer) === 0 && !feedback(text) ? text : null;
|
|
799
|
-
}
|
|
800
|
-
function safeFile(root, path) {
|
|
801
|
-
const parts = relative2(root, path).split(/[\\/]/);
|
|
802
|
-
if (parts.includes("..") || parts.some((part) => SKIP.has(part))) return false;
|
|
803
|
-
let current = root;
|
|
804
|
-
for (const part of parts) {
|
|
805
|
-
current = join3(current, part);
|
|
806
|
-
try {
|
|
807
|
-
if (lstatSync(current).isSymbolicLink()) return false;
|
|
808
|
-
} catch {
|
|
809
|
-
return false;
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
return lstatSync(path).isFile();
|
|
813
|
-
}
|
|
814
|
-
function walk(directory, visit, legacy, skipped) {
|
|
815
|
-
for (const entry of readdirSync2(directory, { withFileTypes: true })) {
|
|
816
|
-
const path = join3(directory, entry.name);
|
|
817
|
-
if (entry.isSymbolicLink()) {
|
|
818
|
-
if (entry.name === `.${OLD}`) legacy(path);
|
|
819
|
-
continue;
|
|
820
|
-
}
|
|
821
|
-
if (entry.isDirectory()) {
|
|
822
|
-
if (SKIP.has(entry.name)) continue;
|
|
823
|
-
if (present(join3(path, ".git"))) {
|
|
824
|
-
skipped?.(path);
|
|
825
|
-
continue;
|
|
826
|
-
}
|
|
827
|
-
if (entry.name === `.${OLD}`) legacy(path);
|
|
828
|
-
else if (entry.name !== ".isotone") walk(path, visit, legacy, skipped);
|
|
829
|
-
} else if (entry.isFile()) visit(path);
|
|
830
|
-
}
|
|
831
|
-
}
|
|
832
|
-
function json(text) {
|
|
833
|
-
try {
|
|
834
|
-
const data = JSON.parse(text);
|
|
835
|
-
return data && typeof data === "object" && !Array.isArray(data) ? data : null;
|
|
836
|
-
} catch {
|
|
837
|
-
return null;
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
function feedback(text) {
|
|
841
|
-
try {
|
|
842
|
-
const data = JSON.parse(text);
|
|
843
|
-
return Array.isArray(data) && data.some((note) => note && typeof note === "object" && typeof note.feedback === "string" && typeof note.id === "string");
|
|
844
|
-
} catch {
|
|
845
|
-
return false;
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
function packageText(text, path, conflicts) {
|
|
849
|
-
const pkg = json(text);
|
|
850
|
-
if (!pkg) {
|
|
851
|
-
conflicts.push(`Invalid package.json: ${path}`);
|
|
852
|
-
return text;
|
|
853
|
-
}
|
|
854
|
-
for (const key of ["overrides", "resolutions", "pnpm"]) {
|
|
855
|
-
if (JSON.stringify(pkg[key])?.includes(OLD)) conflicts.push(`Legacy package in ${path} (${key}). Review custom overrides/resolutions before migrating.`);
|
|
856
|
-
}
|
|
857
|
-
let changed = false;
|
|
858
|
-
const sections = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"];
|
|
859
|
-
if (sections.some((key) => Object.hasOwn(Object(pkg[key]), OLD)) && sections.some((key) => Object.hasOwn(Object(pkg[key]), NEW))) {
|
|
860
|
-
conflicts.push(`Both package names exist in ${path}. Resolve their dependency declarations before migrating.`);
|
|
861
|
-
return text;
|
|
862
|
-
}
|
|
863
|
-
for (const key of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
|
|
864
|
-
const deps = pkg[key];
|
|
865
|
-
if (!deps || typeof deps !== "object" || Array.isArray(deps) || !Object.hasOwn(deps, OLD)) continue;
|
|
866
|
-
const values = deps;
|
|
867
|
-
values[NEW] = version;
|
|
868
|
-
delete values[OLD];
|
|
869
|
-
changed = true;
|
|
870
|
-
}
|
|
871
|
-
if (!changed) return renamed(text);
|
|
872
|
-
const indent = /\n([\t ]+)"/.exec(text)?.[1] ?? " ";
|
|
873
|
-
return renamed(JSON.stringify(pkg, null, indent)) + (text.endsWith("\n") ? "\n" : "");
|
|
874
|
-
}
|
|
875
|
-
function installStep(root, override) {
|
|
876
|
-
let directory = root;
|
|
877
|
-
let manager = override;
|
|
878
|
-
while (true) {
|
|
879
|
-
const pkg = existsSync3(join3(directory, "package.json")) ? json(readFileSync2(join3(directory, "package.json"), "utf8")) : null;
|
|
880
|
-
const declared = typeof pkg?.packageManager === "string" ? /^(npm|pnpm|yarn|bun)@/.exec(pkg.packageManager)?.[1] : void 0;
|
|
881
|
-
const found = /* @__PURE__ */ new Set();
|
|
882
|
-
if (existsSync3(join3(directory, "package-lock.json")) || existsSync3(join3(directory, "npm-shrinkwrap.json"))) found.add("npm");
|
|
883
|
-
if (existsSync3(join3(directory, "pnpm-lock.yaml"))) found.add("pnpm");
|
|
884
|
-
if (existsSync3(join3(directory, "yarn.lock"))) found.add("yarn");
|
|
885
|
-
if (existsSync3(join3(directory, "bun.lock")) || existsSync3(join3(directory, "bun.lockb"))) found.add("bun");
|
|
886
|
-
if (declared || found.size || dirname(directory) === directory || existsSync3(join3(directory, ".git"))) {
|
|
887
|
-
if (!manager && typeof pkg?.packageManager === "string" && !declared) return { install: null, warning: "Unsupported packageManager; run its install command to regenerate the lockfile." };
|
|
888
|
-
if (!manager && !declared && found.size > 1) return { install: null, warning: "Multiple lockfiles: pass --manager npm, pnpm, yarn or bun for an install command." };
|
|
889
|
-
manager ??= declared ?? [...found][0] ?? "npm";
|
|
890
|
-
const command = `${manager} install`;
|
|
891
|
-
return { install: { cwd: pkg || found.size ? directory : root, command } };
|
|
892
|
-
}
|
|
893
|
-
directory = dirname(directory);
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
function planMigration(cwd, manager) {
|
|
897
|
-
let root = resolve2(cwd);
|
|
898
|
-
let tracked = null;
|
|
899
|
-
try {
|
|
900
|
-
root = git(root, ["rev-parse", "--show-toplevel"]);
|
|
901
|
-
tracked = git(root, ["ls-files", "-z"]).split("\0").filter(Boolean);
|
|
902
|
-
} catch {
|
|
903
|
-
}
|
|
904
|
-
const plan = { root, edits: [], moves: [], blobs: /* @__PURE__ */ new Map(), conflicts: [], install: null, installs: [], warnings: [] };
|
|
905
|
-
const candidates = /* @__PURE__ */ new Set();
|
|
906
|
-
const legacyRoots = /* @__PURE__ */ new Set();
|
|
907
|
-
const addLegacy = (path) => {
|
|
908
|
-
if (legacyRoots.has(path)) return;
|
|
909
|
-
legacyRoots.add(path);
|
|
910
|
-
if (lstatSync(path).isSymbolicLink()) {
|
|
911
|
-
plan.conflicts.push(`Legacy directory is a symlink: ${relative2(root, path)}. Migrate its target separately.`);
|
|
912
|
-
return;
|
|
913
|
-
}
|
|
914
|
-
const destination = join3(dirname(path), ".isotone");
|
|
915
|
-
if (present(destination)) plan.conflicts.push(`Both ${relative2(root, path)} and ${relative2(root, destination)} exist; nothing will be overwritten.`);
|
|
916
|
-
plan.moves.push({ from: path, to: destination });
|
|
917
|
-
walk(path, (file) => candidates.add(file), (nested) => plan.conflicts.push(`Nested legacy directory: ${relative2(root, nested)}`));
|
|
918
|
-
};
|
|
919
|
-
if (tracked) for (const file of tracked) candidates.add(join3(root, file));
|
|
920
|
-
walk(root, (path) => {
|
|
921
|
-
const name = basename(path);
|
|
922
|
-
if (!tracked || dirname(path) === root && (["AGENTS.md", "package.json", ".gitignore", ".npmignore"].includes(name) || /^(?:vite|next|react-router|svelte|astro|nuxt)\.config\./.test(name))) candidates.add(path);
|
|
923
|
-
}, addLegacy, (path) => plan.warnings.push(`Separate Git checkout skipped: ${relative2(root, path)}. Migrate it separately.`));
|
|
924
|
-
if (tracked) {
|
|
925
|
-
const exclude = resolve2(root, git(root, ["rev-parse", "--git-path", "info/exclude"]));
|
|
926
|
-
if (existsSync3(exclude) && lstatSync(exclude).isFile() && !lstatSync(exclude).isSymbolicLink()) {
|
|
927
|
-
const before = readFileSync2(exclude, "utf8");
|
|
928
|
-
const after = renamed(before);
|
|
929
|
-
if (before !== after) plan.edits.push({ path: exclude, before, after, mode: lstatSync(exclude).mode, gitExclude: true });
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
for (const file of candidates) {
|
|
933
|
-
const name = basename(file);
|
|
934
|
-
if (LOCKS.has(name) || name === ".iteration-feedback.json" || !safeFile(root, file)) continue;
|
|
935
|
-
if (relative2(root, file).split(/[\\/]/).some((part) => part !== `.${OLD}` && renamed(part) !== part)) {
|
|
936
|
-
plan.conflicts.push(`Legacy name in a file path: ${relative2(root, file)}. Rename that file or directory and its local references before migrating.`);
|
|
937
|
-
continue;
|
|
938
|
-
}
|
|
939
|
-
if (!TEXT.has(extname(file)) && ![".gitignore", ".npmignore"].includes(name)) continue;
|
|
940
|
-
const before = textContents(readFileSync2(file));
|
|
941
|
-
if (before === null) continue;
|
|
942
|
-
for (const match of before.matchAll(/\biso-iterate@([^\s"'`;,)}\]]+)/g)) {
|
|
943
|
-
if (!/^(?:latest|\d+\.\d+\.\d+(?:-[\da-zA-Z.-]+)?(?:\+[\da-zA-Z.-]+)?)$/.test(match[1])) {
|
|
944
|
-
plan.conflicts.push(`Custom package version constraint in ${relative2(root, file)}. Replace it with the desired Isotone version before migrating.`);
|
|
945
|
-
}
|
|
946
|
-
}
|
|
947
|
-
const after = name === "package.json" ? packageText(before, relative2(root, file), plan.conflicts) : renamed(before);
|
|
948
|
-
if (after !== before) plan.edits.push({ path: file, before, after, mode: lstatSync(file).mode });
|
|
949
|
-
}
|
|
950
|
-
if (tracked) for (const file of candidates) {
|
|
951
|
-
if (basename(file) !== "variant.json" || !safeFile(root, file) || ![...legacyRoots].some((path) => file.startsWith(`${path}/`))) continue;
|
|
952
|
-
const before = readFileSync2(file, "utf8");
|
|
953
|
-
const existing = plan.edits.find((edit) => edit.path === file);
|
|
954
|
-
const data = json(existing?.after ?? before);
|
|
955
|
-
if (!data?.files || typeof data.files !== "object") continue;
|
|
956
|
-
let changed = false;
|
|
957
|
-
for (const [key, entry] of Object.entries(data.files)) {
|
|
958
|
-
if (!TEXT.has(extname(key)) && ![".gitignore", ".npmignore"].includes(basename(key))) continue;
|
|
959
|
-
if (!entry || typeof entry !== "object" || typeof entry.base !== "string" || !/^[a-f0-9]{40}(?:[a-f0-9]{24})?$/.test(entry.base)) continue;
|
|
960
|
-
let base;
|
|
961
|
-
try {
|
|
962
|
-
base = textContents(execFileSync("git", ["cat-file", "blob", entry.base], { cwd: root, stdio: ["pipe", "pipe", "pipe"], maxBuffer: 32 * 1024 * 1024 }));
|
|
963
|
-
} catch {
|
|
964
|
-
continue;
|
|
965
|
-
}
|
|
966
|
-
if (base === null || basename(key) === ".iteration-feedback.json") continue;
|
|
967
|
-
const after = basename(key) === "package.json" ? packageText(base, key, []) : renamed(base);
|
|
968
|
-
if (after === base) continue;
|
|
969
|
-
const hash = createHash(entry.base.length === 64 ? "sha256" : "sha1").update(`blob ${Buffer.byteLength(after)}\0`).update(after).digest("hex");
|
|
970
|
-
entry.base = hash;
|
|
971
|
-
plan.blobs.set(hash, after);
|
|
972
|
-
changed = true;
|
|
973
|
-
}
|
|
974
|
-
if (changed) {
|
|
975
|
-
const after = `${JSON.stringify(data, null, 2)}
|
|
976
|
-
`;
|
|
977
|
-
if (existing) existing.after = after;
|
|
978
|
-
else plan.edits.push({ path: file, before, after, mode: lstatSync(file).mode });
|
|
979
|
-
}
|
|
980
|
-
}
|
|
981
|
-
const packages = plan.edits.filter((edit) => basename(edit.path) === "package.json" && edit.before.includes(`"${OLD}"`));
|
|
982
|
-
for (const edit of packages) {
|
|
983
|
-
const step = installStep(dirname(edit.path), manager);
|
|
984
|
-
if (step.install && !plan.installs.some((existing) => existing.cwd === step.install?.cwd && existing.command === step.install.command)) plan.installs.push(step.install);
|
|
985
|
-
if (step.warning) plan.warnings.push(`${relative2(root, edit.path)}: ${step.warning}`);
|
|
986
|
-
}
|
|
987
|
-
plan.install = plan.installs.length === 1 ? plan.installs[0] : null;
|
|
988
|
-
if (!tracked) plan.warnings.push("No Git repository: review the changed text files before installing.");
|
|
989
|
-
return plan;
|
|
990
|
-
}
|
|
991
|
-
function atomicWrite(path, text, mode) {
|
|
992
|
-
const temporary = `${path}.${randomUUID()}.migrate`;
|
|
993
|
-
try {
|
|
994
|
-
writeFileSync2(temporary, text, { flag: "wx", mode });
|
|
995
|
-
renameSync(temporary, path);
|
|
996
|
-
} finally {
|
|
997
|
-
if (existsSync3(temporary)) unlinkSync(temporary);
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
|
-
function apply(plan) {
|
|
1001
|
-
for (const edit of plan.edits) if (!(edit.gitExclude ? lstatSync(edit.path).isFile() && !lstatSync(edit.path).isSymbolicLink() : safeFile(plan.root, edit.path)) || readFileSync2(edit.path, "utf8") !== edit.before) throw new Error(`File changed during migration: ${relative2(plan.root, edit.path)}. Run the preview again.`);
|
|
1002
|
-
for (const move of plan.moves) if (present(move.to) || lstatSync(move.from).isSymbolicLink()) throw new Error(`Directory changed during migration: ${relative2(plan.root, move.from)}.`);
|
|
1003
|
-
const written = [], moved = [];
|
|
1004
|
-
try {
|
|
1005
|
-
for (const [hash, contents] of plan.blobs) if (git(plan.root, ["hash-object", "--no-filters", "-w", "--stdin"], contents) !== hash) throw new Error("Could not preserve a variant merge base.");
|
|
1006
|
-
for (const edit of plan.edits) {
|
|
1007
|
-
atomicWrite(edit.path, edit.after, edit.mode);
|
|
1008
|
-
written.push(edit);
|
|
1009
|
-
}
|
|
1010
|
-
for (const move of plan.moves) {
|
|
1011
|
-
renameSync(move.from, move.to);
|
|
1012
|
-
moved.push(move);
|
|
1013
|
-
}
|
|
1014
|
-
} catch (error) {
|
|
1015
|
-
for (const move of moved.reverse()) renameSync(move.to, move.from);
|
|
1016
|
-
for (const edit of written.reverse()) atomicWrite(edit.path, edit.before, edit.mode);
|
|
1017
|
-
throw error;
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
function runMigrate(args, io) {
|
|
1021
|
-
if (args.some((arg) => arg === "--help" || arg === "-h")) {
|
|
1022
|
-
out(io, "Usage: isotone migrate [--write] [--json] [--manager npm|pnpm|yarn|bun]");
|
|
1023
|
-
out(io, "Preview migration from iso-iterate. --write applies it; installing remains an explicit next step.");
|
|
1024
|
-
return 0;
|
|
1025
|
-
}
|
|
1026
|
-
if (parseArgs(args, ["--manager"], ["--write", "--json"]).length) throw new Error("migrate does not take positional arguments.");
|
|
1027
|
-
const manager = valueFor(args, "--manager");
|
|
1028
|
-
if (manager && !["npm", "pnpm", "yarn", "bun"].includes(manager)) throw new Error("--manager must be npm, pnpm, yarn or bun.");
|
|
1029
|
-
const plan = planMigration(io.cwd, manager);
|
|
1030
|
-
const write = args.includes("--write");
|
|
1031
|
-
const changed = plan.edits.length > 0 || plan.moves.length > 0;
|
|
1032
|
-
if (write && !plan.conflicts.length) apply(plan);
|
|
1033
|
-
const summary = {
|
|
1034
|
-
root: plan.root,
|
|
1035
|
-
version,
|
|
1036
|
-
applied: write && !plan.conflicts.length && changed,
|
|
1037
|
-
edits: plan.edits.map((edit) => relative2(plan.root, edit.path)),
|
|
1038
|
-
moves: plan.moves.map((move) => ({ from: relative2(plan.root, move.from), to: relative2(plan.root, move.to) })),
|
|
1039
|
-
conflicts: plan.conflicts,
|
|
1040
|
-
warnings: plan.warnings,
|
|
1041
|
-
install: changed ? plan.install : null,
|
|
1042
|
-
installs: changed ? plan.installs : []
|
|
1043
|
-
};
|
|
1044
|
-
if (args.includes("--json")) out(io, JSON.stringify(summary, null, 2));
|
|
1045
|
-
else {
|
|
1046
|
-
out(io, `${plan.conflicts.length ? "Blocked" : write ? "Applied" : "Preview"}: ${plan.root}`);
|
|
1047
|
-
for (const file of summary.edits) out(io, ` edit ${file}`);
|
|
1048
|
-
for (const move of summary.moves) out(io, ` move ${move.from} \u2192 ${move.to}`);
|
|
1049
|
-
for (const message of [...plan.conflicts, ...plan.warnings]) out(io, message);
|
|
1050
|
-
if (!changed && !plan.conflicts.length) out(io, "No migration changes needed.");
|
|
1051
|
-
else if (!write && !plan.conflicts.length) out(io, "Review this plan, then run isotone migrate --write.");
|
|
1052
|
-
if (!plan.conflicts.length) for (const step of summary.installs) out(io, `After applying, run ${step.command} from ${step.cwd}, then restart the dev server. Lockfiles and feedback are unchanged by migration.`);
|
|
1053
|
-
}
|
|
1054
|
-
return plan.conflicts.length ? 1 : 0;
|
|
1055
|
-
}
|
|
1056
|
-
|
|
1057
760
|
// src/cli/run.ts
|
|
1058
761
|
async function runCli(argv, ioInput = {}) {
|
|
1059
762
|
const io = makeIo(ioInput);
|
|
1060
763
|
try {
|
|
1061
|
-
if (argv[0] === "migrate") return runMigrate(argv.slice(1), io);
|
|
1062
764
|
if (argv[0] === "check-update") return await runCheckUpdate(argv.slice(1), io);
|
|
1063
765
|
if (argv[0] === "serve") return await runServe(argv.slice(1), io);
|
|
1064
766
|
if (argv[0] === "variant") return await runVariant(argv.slice(1), io);
|
|
@@ -1100,7 +802,6 @@ function printHelp(io) {
|
|
|
1100
802
|
out(io);
|
|
1101
803
|
out(io, "Setting a repo up");
|
|
1102
804
|
out(io, " isotone init [--approach overlay|host|serve] [--yes] [--json]");
|
|
1103
|
-
out(io, " isotone migrate [--write] [--json] [--manager npm|pnpm|yarn|bun]");
|
|
1104
805
|
}
|
|
1105
806
|
async function runServe(args, io) {
|
|
1106
807
|
const { serveIteration, DEFAULT_PORT } = await import("./serve.js");
|
|
@@ -1116,7 +817,7 @@ async function runServe(args, io) {
|
|
|
1116
817
|
errOut(io, error.message);
|
|
1117
818
|
return 1;
|
|
1118
819
|
}
|
|
1119
|
-
const file =
|
|
820
|
+
const file = resolve2(io.cwd, valueFor(args, "--file") ?? ".iteration-feedback.json");
|
|
1120
821
|
const port = Number(valueFor(args, "--port") ?? DEFAULT_PORT);
|
|
1121
822
|
const host = valueFor(args, "--host");
|
|
1122
823
|
const theme = valueFor(args, "--theme") ?? "auto";
|
|
@@ -1170,7 +871,7 @@ function runNotes(argv, io) {
|
|
|
1170
871
|
errOut(io, "--days must be a positive number.");
|
|
1171
872
|
return 1;
|
|
1172
873
|
}
|
|
1173
|
-
const file =
|
|
874
|
+
const file = resolve2(io.cwd, valueFor(argv, "--file") ?? ".iteration-feedback.json");
|
|
1174
875
|
const all = has(argv, "--all");
|
|
1175
876
|
const doneIdx = argv.indexOf("--done");
|
|
1176
877
|
const undoneIdx = argv.indexOf("--undone");
|