@roulabs/mx 1.2.2 → 1.7.1
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/bin/mx.js +256 -102
- package/package.json +1 -1
package/bin/mx.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/main.ts
|
|
4
4
|
import { readFileSync as readFileSync2 } from "fs";
|
|
5
|
-
import * as
|
|
5
|
+
import * as path8 from "path";
|
|
6
6
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7
7
|
|
|
8
8
|
// ../../packages/core/src/errors.ts
|
|
@@ -133,6 +133,11 @@ function writeWork(root, work) {
|
|
|
133
133
|
function findWorktree(work, repo) {
|
|
134
134
|
return (work.worktrees ?? []).find((w) => w.repo === repo) ?? null;
|
|
135
135
|
}
|
|
136
|
+
function countSessions(root, workName) {
|
|
137
|
+
const dir = path3.join(workDir(root, workName), "sessions");
|
|
138
|
+
if (!exists(dir)) return 0;
|
|
139
|
+
return fs4.readdirSync(dir).filter((n) => n.endsWith(".md")).length;
|
|
140
|
+
}
|
|
136
141
|
function inferContext(root) {
|
|
137
142
|
const cwd = realpath(process.cwd());
|
|
138
143
|
const segmentsUnder = (base) => {
|
|
@@ -328,19 +333,13 @@ function workNew(root, name, description = "") {
|
|
|
328
333
|
return { ...work, path: dir };
|
|
329
334
|
}
|
|
330
335
|
function listWorksInfo(root, opts = {}) {
|
|
331
|
-
return listWorkNames(root).map((name) => {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
worktrees: (w.worktrees ?? []).length,
|
|
337
|
-
isArchived: w.isArchived === true,
|
|
338
|
-
archived_at: w.archived_at ?? null
|
|
339
|
-
};
|
|
340
|
-
}).filter((s) => {
|
|
341
|
-
if (opts.onlyArchived) return s.isArchived;
|
|
336
|
+
return listWorkNames(root).map((name) => ({
|
|
337
|
+
...readWork(root, name),
|
|
338
|
+
sessions: countSessions(root, name)
|
|
339
|
+
})).filter((w) => {
|
|
340
|
+
if (opts.onlyArchived) return w.isArchived === true;
|
|
342
341
|
if (opts.includeArchived) return true;
|
|
343
|
-
return
|
|
342
|
+
return w.isArchived !== true;
|
|
344
343
|
});
|
|
345
344
|
}
|
|
346
345
|
function workInfo(root, name) {
|
|
@@ -567,10 +566,24 @@ function portList(root, name) {
|
|
|
567
566
|
}
|
|
568
567
|
|
|
569
568
|
// ../../packages/core/src/status.ts
|
|
569
|
+
import * as path5 from "path";
|
|
570
|
+
function countContextEntries(root) {
|
|
571
|
+
const indexPath = path5.join(root, "context", "INDEX.json");
|
|
572
|
+
if (!exists(indexPath)) return 0;
|
|
573
|
+
try {
|
|
574
|
+
const data = readJson(indexPath);
|
|
575
|
+
return Array.isArray(data) ? data.length : 0;
|
|
576
|
+
} catch {
|
|
577
|
+
return 0;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
570
580
|
function statusRuntime(root) {
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
581
|
+
return {
|
|
582
|
+
runtime: root,
|
|
583
|
+
context: { entries: countContextEntries(root) },
|
|
584
|
+
repos: listReposInfo(root),
|
|
585
|
+
works: listWorksInfo(root, { includeArchived: true })
|
|
586
|
+
};
|
|
574
587
|
}
|
|
575
588
|
|
|
576
589
|
// src/args.ts
|
|
@@ -589,7 +602,6 @@ function parseArgs(argv) {
|
|
|
589
602
|
help: false,
|
|
590
603
|
version: false,
|
|
591
604
|
force: false,
|
|
592
|
-
all: false,
|
|
593
605
|
archived: false
|
|
594
606
|
};
|
|
595
607
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -602,8 +614,6 @@ function parseArgs(argv) {
|
|
|
602
614
|
flags.version = true;
|
|
603
615
|
} else if (a === "--force") {
|
|
604
616
|
flags.force = true;
|
|
605
|
-
} else if (a === "--all") {
|
|
606
|
-
flags.all = true;
|
|
607
617
|
} else if (a === "--archived") {
|
|
608
618
|
flags.archived = true;
|
|
609
619
|
} else if (a.startsWith("--") && a.includes("=")) {
|
|
@@ -621,6 +631,22 @@ function parseArgs(argv) {
|
|
|
621
631
|
}
|
|
622
632
|
|
|
623
633
|
// src/output.ts
|
|
634
|
+
var USE_STYLE = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
635
|
+
function wrap(s, code) {
|
|
636
|
+
return USE_STYLE ? `\x1B[${code}m${s}\x1B[0m` : s;
|
|
637
|
+
}
|
|
638
|
+
function dim(s) {
|
|
639
|
+
return wrap(s, 2);
|
|
640
|
+
}
|
|
641
|
+
function bold(s) {
|
|
642
|
+
return wrap(s, 1);
|
|
643
|
+
}
|
|
644
|
+
function check() {
|
|
645
|
+
return "\u2713";
|
|
646
|
+
}
|
|
647
|
+
function warn() {
|
|
648
|
+
return "\u26A0";
|
|
649
|
+
}
|
|
624
650
|
var porcelain = false;
|
|
625
651
|
function setPorcelain(value) {
|
|
626
652
|
porcelain = value;
|
|
@@ -640,7 +666,9 @@ function fail(err) {
|
|
|
640
666
|
if (porcelain) {
|
|
641
667
|
process.stdout.write(JSON.stringify({ error: message, code }, null, 2) + "\n");
|
|
642
668
|
} else {
|
|
643
|
-
process.stderr.
|
|
669
|
+
const useStyle = process.stderr.isTTY && !process.env.NO_COLOR;
|
|
670
|
+
const prefix = useStyle ? `\x1B[1mmx:\x1B[0m` : "mx:";
|
|
671
|
+
process.stderr.write(`${prefix} ${message}
|
|
644
672
|
`);
|
|
645
673
|
}
|
|
646
674
|
process.exit(1);
|
|
@@ -651,8 +679,8 @@ var HELP = `mx \u2014 control panel for the mx runtime
|
|
|
651
679
|
|
|
652
680
|
Global:
|
|
653
681
|
mx init [path] scaffold/adopt a runtime (default ~/mx)
|
|
654
|
-
mx status [--porcelain] show runtime, repos, works, ports
|
|
655
|
-
mx update re-
|
|
682
|
+
mx status [--porcelain] show runtime, repos, works, ports (aliases: mx s, mx st)
|
|
683
|
+
mx update re-sync runtime from current templates + backfill structural scaffolding
|
|
656
684
|
mx help | version
|
|
657
685
|
|
|
658
686
|
Repos (pristine clones):
|
|
@@ -664,7 +692,7 @@ Repos (pristine clones):
|
|
|
664
692
|
|
|
665
693
|
Works (features):
|
|
666
694
|
mx work new <name> [--description <t>] creates folder + empty work.json + sessions/
|
|
667
|
-
mx work ls [--
|
|
695
|
+
mx work ls [--archived] [--porcelain] default: all works (archived marked); --archived filters to archived only
|
|
668
696
|
mx work -n <name> info [--porcelain]
|
|
669
697
|
mx work -n <name> path print the work folder path (cd "$(mx work -n <name> path)")
|
|
670
698
|
mx work -n <name> describe <text>
|
|
@@ -686,33 +714,33 @@ Runtime discovery: --runtime <path> -> $MX_RUNTIME -> default ~/mx.
|
|
|
686
714
|
`;
|
|
687
715
|
|
|
688
716
|
// src/commands/global.ts
|
|
689
|
-
import * as
|
|
717
|
+
import * as path7 from "path";
|
|
690
718
|
|
|
691
719
|
// src/paths.ts
|
|
692
|
-
import * as
|
|
720
|
+
import * as path6 from "path";
|
|
693
721
|
import { fileURLToPath } from "url";
|
|
694
722
|
function templatesDir() {
|
|
695
723
|
if (process.env.MX_TEMPLATES_DIR) return process.env.MX_TEMPLATES_DIR;
|
|
696
|
-
const here =
|
|
697
|
-
return
|
|
724
|
+
const here = path6.dirname(fileURLToPath(import.meta.url));
|
|
725
|
+
return path6.join(here, "..", "templates");
|
|
698
726
|
}
|
|
699
727
|
|
|
700
728
|
// src/commands/global.ts
|
|
701
729
|
function runtimeEnvHint(runtime) {
|
|
702
|
-
const envRuntime = process.env.MX_RUNTIME ?
|
|
730
|
+
const envRuntime = process.env.MX_RUNTIME ? path7.resolve(process.env.MX_RUNTIME) : null;
|
|
703
731
|
if (envRuntime === runtime) {
|
|
704
|
-
return ["", `$MX_RUNTIME already points here \u2014 you're set
|
|
732
|
+
return ["", `${dim("$MX_RUNTIME already points here \u2014 you're set.")}`];
|
|
705
733
|
}
|
|
706
734
|
if (runtime === defaultRuntime() && !envRuntime) {
|
|
707
|
-
return ["",
|
|
735
|
+
return ["", `${dim("This is the default mx runtime (~/mx) \u2014 no MX_RUNTIME setup needed.")}`];
|
|
708
736
|
}
|
|
709
737
|
return [
|
|
710
738
|
"",
|
|
711
|
-
`Point mx at this runtime by adding to your shell config (~/.zshrc, ~/.bashrc):`,
|
|
739
|
+
`Point mx at this runtime by adding to your shell config ${dim("(~/.zshrc, ~/.bashrc)")}:`,
|
|
712
740
|
"",
|
|
713
|
-
` export MX_RUNTIME="${runtime}"`,
|
|
741
|
+
` ${bold(`export MX_RUNTIME="${runtime}"`)}`,
|
|
714
742
|
"",
|
|
715
|
-
|
|
743
|
+
dim("Without it, future `mx` commands fall back to the default ~/mx.")
|
|
716
744
|
];
|
|
717
745
|
}
|
|
718
746
|
function runGlobal(positionals, flags) {
|
|
@@ -721,8 +749,8 @@ function runGlobal(positionals, flags) {
|
|
|
721
749
|
const target = positionals[1] || discoverRuntime({ runtime: flags.runtime });
|
|
722
750
|
const res = initRuntime(target, templatesDir());
|
|
723
751
|
emit(() => {
|
|
724
|
-
console.log(
|
|
725
|
-
for (const c of res.created) console.log(`
|
|
752
|
+
console.log(`${check()} Runtime ready at ${bold(res.runtime)}`);
|
|
753
|
+
for (const c of res.created) console.log(` ${dim(`+ ${c}`)}`);
|
|
726
754
|
for (const line of runtimeEnvHint(res.runtime)) console.log(line);
|
|
727
755
|
}, res);
|
|
728
756
|
return;
|
|
@@ -730,31 +758,15 @@ function runGlobal(positionals, flags) {
|
|
|
730
758
|
case "status": {
|
|
731
759
|
const root = requireRuntime({ runtime: flags.runtime });
|
|
732
760
|
const data = statusRuntime(root);
|
|
733
|
-
emit(() =>
|
|
734
|
-
console.log(`runtime: ${data.runtime}
|
|
735
|
-
`);
|
|
736
|
-
console.log(`repos (${data.repos.length}):`);
|
|
737
|
-
for (const r of data.repos) {
|
|
738
|
-
console.log(` ${r.name} [${r.branch}] ${r.remote ?? "(no remote)"}`);
|
|
739
|
-
}
|
|
740
|
-
console.log(`
|
|
741
|
-
works (${data.works.length}):`);
|
|
742
|
-
for (const w of data.works) {
|
|
743
|
-
console.log(` ${w.name}${w.description ? ` \u2014 ${w.description}` : ""}`);
|
|
744
|
-
for (const wt of w.worktrees) {
|
|
745
|
-
const ports = Object.entries(wt.ports).map(([s, p]) => `${s}:${p}`).join(", ");
|
|
746
|
-
console.log(` ${wt.repo} [${wt.branch}]${ports ? ` (${ports})` : ""}`);
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
}, data);
|
|
761
|
+
emit(() => renderStatus(data), data);
|
|
750
762
|
return;
|
|
751
763
|
}
|
|
752
764
|
case "update": {
|
|
753
765
|
const root = requireRuntime({ runtime: flags.runtime });
|
|
754
766
|
const res = updateRuntime(root, templatesDir());
|
|
755
767
|
emit(() => {
|
|
756
|
-
console.log(
|
|
757
|
-
for (const p of res.updated) console.log(`
|
|
768
|
+
console.log(`${check()} Updated runtime at ${bold(res.runtime)}`);
|
|
769
|
+
for (const p of res.updated) console.log(` ${dim(`+ ${p}`)}`);
|
|
758
770
|
}, res);
|
|
759
771
|
return;
|
|
760
772
|
}
|
|
@@ -762,6 +774,61 @@ works (${data.works.length}):`);
|
|
|
762
774
|
throw new MxError(`unknown command: ${positionals[0]}`, "BAD_ARGS");
|
|
763
775
|
}
|
|
764
776
|
}
|
|
777
|
+
function renderStatus(data) {
|
|
778
|
+
console.log();
|
|
779
|
+
console.log(` ${bold("mx")} ${dim("\xB7")} ${data.runtime}`);
|
|
780
|
+
console.log();
|
|
781
|
+
console.log(` ${bold("context")} ${dim(`(${data.context.entries})`)}`);
|
|
782
|
+
console.log();
|
|
783
|
+
console.log(` ${bold("repos")}`);
|
|
784
|
+
if (data.repos.length === 0) {
|
|
785
|
+
console.log(` ${dim("none yet \u2014 `mx repo add <git-url>`")}`);
|
|
786
|
+
} else {
|
|
787
|
+
const nameW = Math.max(...data.repos.map((r) => r.name.length));
|
|
788
|
+
const branchW = Math.max(...data.repos.map((r) => r.branch.length));
|
|
789
|
+
for (const r of data.repos) {
|
|
790
|
+
const name = r.name.padEnd(nameW);
|
|
791
|
+
const branch = dim(r.branch.padEnd(branchW));
|
|
792
|
+
const remote = dim(r.remote ?? "(no remote)");
|
|
793
|
+
console.log(` \u2022 ${name} ${branch} ${remote}`);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
console.log();
|
|
797
|
+
const active = data.works.filter((w) => w.isArchived !== true);
|
|
798
|
+
const archived = data.works.filter((w) => w.isArchived === true);
|
|
799
|
+
const worksCount = archived.length > 0 ? dim(`(${active.length} active, ${archived.length} archived)`) : dim(`(${data.works.length})`);
|
|
800
|
+
console.log(` ${bold("works")} ${worksCount}`);
|
|
801
|
+
if (data.works.length === 0) {
|
|
802
|
+
console.log(` ${dim("none yet \u2014 `mx work new <name>`")}`);
|
|
803
|
+
console.log();
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
const ordered = [...active, ...archived];
|
|
807
|
+
const wtRepoW = Math.max(
|
|
808
|
+
0,
|
|
809
|
+
...ordered.flatMap((w) => (w.worktrees ?? []).map((wt) => wt.repo.length))
|
|
810
|
+
);
|
|
811
|
+
for (let i = 0; i < ordered.length; i++) {
|
|
812
|
+
if (i > 0) console.log();
|
|
813
|
+
const w = ordered[i];
|
|
814
|
+
const wts = w.worktrees ?? [];
|
|
815
|
+
const chip = w.isArchived === true ? ` ${dim(`[archived ${(w.archived_at ?? "").slice(0, 10)}]`)}` : "";
|
|
816
|
+
const styledName = w.isArchived === true ? dim(w.name) : bold(w.name);
|
|
817
|
+
console.log(` \u2022 ${styledName}${chip}`);
|
|
818
|
+
if (wts.length === 0) {
|
|
819
|
+
console.log(` ${dim("(no worktrees)")}`);
|
|
820
|
+
continue;
|
|
821
|
+
}
|
|
822
|
+
for (const t of wts) {
|
|
823
|
+
const repo = dim(t.repo.padEnd(wtRepoW));
|
|
824
|
+
const branch = dim(`[${t.branch}]`);
|
|
825
|
+
const ports = Object.entries(t.ports ?? {}).map(([s, p]) => dim(`${s}:${p}`)).join(" ");
|
|
826
|
+
const portsCol = ports ? ` ${ports}` : "";
|
|
827
|
+
console.log(` ${repo} ${branch}${portsCol}`);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
console.log();
|
|
831
|
+
}
|
|
765
832
|
|
|
766
833
|
// src/commands/repo.ts
|
|
767
834
|
function need(v, msg) {
|
|
@@ -776,13 +843,24 @@ function dispatchRepo(positionals, flags) {
|
|
|
776
843
|
case "add": {
|
|
777
844
|
const url = need(positionals[2], "usage: mx repo add <git-url> [--name <n>]");
|
|
778
845
|
const res = repoAdd(root, url, flags.name);
|
|
779
|
-
emit(() => console.log(
|
|
846
|
+
emit(() => console.log(`${check()} cloned ${bold(res.name)} ${dim(`\u2192 ${res.path}`)}`), res);
|
|
780
847
|
return;
|
|
781
848
|
}
|
|
782
849
|
case "ls": {
|
|
783
850
|
const repos = listReposInfo(root);
|
|
784
851
|
emit(() => {
|
|
785
|
-
|
|
852
|
+
if (repos.length === 0) {
|
|
853
|
+
console.log(dim("no repos yet \u2014 `mx repo add <git-url>`"));
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
const nameW = Math.max(...repos.map((r) => r.name.length));
|
|
857
|
+
const branchW = Math.max(...repos.map((r) => r.branch.length));
|
|
858
|
+
for (const r of repos) {
|
|
859
|
+
const name = r.name.padEnd(nameW);
|
|
860
|
+
const branch = dim(r.branch.padEnd(branchW));
|
|
861
|
+
const remote = dim(r.remote ?? "(no remote)");
|
|
862
|
+
console.log(`\u2022 ${name} ${branch} ${remote}`);
|
|
863
|
+
}
|
|
786
864
|
}, repos);
|
|
787
865
|
return;
|
|
788
866
|
}
|
|
@@ -794,7 +872,7 @@ function dispatchRepo(positionals, flags) {
|
|
|
794
872
|
const res = repoFetch(root, name);
|
|
795
873
|
emit(
|
|
796
874
|
() => console.log(
|
|
797
|
-
|
|
875
|
+
`${check()} fetched ${bold(res.name)} ${dim(`\u2014 ${res.remoteBranches.length} branch(es) on origin, now on ${res.branch}`)}`
|
|
798
876
|
),
|
|
799
877
|
res
|
|
800
878
|
);
|
|
@@ -807,15 +885,12 @@ function dispatchRepo(positionals, flags) {
|
|
|
807
885
|
);
|
|
808
886
|
const res = repoInfo(root, name);
|
|
809
887
|
emit(() => {
|
|
810
|
-
console.log(
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
);
|
|
816
|
-
console.log(
|
|
817
|
-
` used by works: ${res.worktreesInWorks.length ? res.worktreesInWorks.join(", ") : "(none)"}`
|
|
818
|
-
);
|
|
888
|
+
console.log(bold(res.name));
|
|
889
|
+
console.log(` ${dim("path ")} ${dim(res.path)}`);
|
|
890
|
+
console.log(` ${dim("branch")} ${dim(res.branch)}`);
|
|
891
|
+
console.log(` ${dim("remote")} ${dim(res.remote ?? "(none)")}`);
|
|
892
|
+
const usedBy = res.worktreesInWorks.length ? res.worktreesInWorks.join(", ") : "(none)";
|
|
893
|
+
console.log(` ${dim("used ")} ${dim(usedBy)}`);
|
|
819
894
|
}, res);
|
|
820
895
|
return;
|
|
821
896
|
}
|
|
@@ -825,7 +900,7 @@ function dispatchRepo(positionals, flags) {
|
|
|
825
900
|
"which repo? pass -n <name> or run inside a repo (mx repo -n <name> rm)"
|
|
826
901
|
);
|
|
827
902
|
const res = repoRemove(root, name);
|
|
828
|
-
emit(() => console.log(
|
|
903
|
+
emit(() => console.log(`${check()} removed repo ${bold(res.name)}`), res);
|
|
829
904
|
return;
|
|
830
905
|
}
|
|
831
906
|
default:
|
|
@@ -845,23 +920,48 @@ function dispatchWork(positionals, flags) {
|
|
|
845
920
|
const name2 = need2(positionals[2], "usage: mx work new <name> [--description <text>]");
|
|
846
921
|
const res = workNew(root2, name2, flags.description ?? "");
|
|
847
922
|
emit(() => {
|
|
848
|
-
console.log(
|
|
849
|
-
console.log(` ${res.path}`);
|
|
923
|
+
console.log(`${check()} created work ${bold(res.name)}`);
|
|
924
|
+
console.log(` ${dim(res.path)}`);
|
|
850
925
|
}, res);
|
|
851
926
|
return;
|
|
852
927
|
}
|
|
853
928
|
if (action === "ls") {
|
|
854
929
|
const root2 = requireRuntime({ runtime: flags.runtime });
|
|
855
930
|
const works = listWorksInfo(root2, {
|
|
856
|
-
includeArchived:
|
|
931
|
+
includeArchived: true,
|
|
857
932
|
onlyArchived: flags.archived
|
|
858
933
|
});
|
|
859
934
|
emit(() => {
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
935
|
+
const ordered = [
|
|
936
|
+
...works.filter((w) => w.isArchived !== true),
|
|
937
|
+
...works.filter((w) => w.isArchived === true)
|
|
938
|
+
];
|
|
939
|
+
if (ordered.length === 0) {
|
|
940
|
+
console.log(dim("no works yet \u2014 `mx work new <name>`"));
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
for (let i = 0; i < ordered.length; i++) {
|
|
944
|
+
if (i > 0) console.log();
|
|
945
|
+
const w = ordered[i];
|
|
946
|
+
const wts = w.worktrees ?? [];
|
|
947
|
+
const chip = w.isArchived === true ? ` ${dim(`[archived ${(w.archived_at ?? "").slice(0, 10)}]`)}` : "";
|
|
948
|
+
const styledName = w.isArchived === true ? dim(w.name) : bold(w.name);
|
|
949
|
+
console.log(`\u2022 ${styledName}${chip}`);
|
|
950
|
+
if (w.description) {
|
|
951
|
+
console.log(` ${dim(`\u2014 ${w.description}`)}`);
|
|
952
|
+
}
|
|
953
|
+
if (wts.length === 0) {
|
|
954
|
+
console.log(` ${dim("(no worktrees)")}`);
|
|
955
|
+
} else {
|
|
956
|
+
const repoW = Math.max(...wts.map((t) => t.repo.length));
|
|
957
|
+
for (const t of wts) {
|
|
958
|
+
const repo = dim(t.repo.padEnd(repoW));
|
|
959
|
+
const branch = dim(`[${t.branch}]`);
|
|
960
|
+
const ports = Object.entries(t.ports ?? {}).map(([s, p]) => `${dim(`${s}:${p}`)}`).join(" ");
|
|
961
|
+
const portsCol = ports ? ` ${ports}` : "";
|
|
962
|
+
console.log(` ${repo} ${branch}${portsCol}`);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
865
965
|
}
|
|
866
966
|
}, works);
|
|
867
967
|
return;
|
|
@@ -874,7 +974,19 @@ function dispatchWork(positionals, flags) {
|
|
|
874
974
|
switch (action) {
|
|
875
975
|
case "info": {
|
|
876
976
|
const work = workInfo(root, name);
|
|
877
|
-
emit(() =>
|
|
977
|
+
emit(() => {
|
|
978
|
+
const archivedChip = work.isArchived === true ? ` ${dim(`[archived ${(work.archived_at ?? "").slice(0, 10)}]`)}` : "";
|
|
979
|
+
const styledName = work.isArchived === true ? dim(work.name) : bold(work.name);
|
|
980
|
+
console.log(`${styledName}${archivedChip}`);
|
|
981
|
+
if (work.description) console.log(` ${dim("description")} ${dim(work.description)}`);
|
|
982
|
+
const wts = work.worktrees ?? [];
|
|
983
|
+
console.log(` ${dim("worktrees ")} ${dim(`${wts.length}`)}`);
|
|
984
|
+
for (const wt of wts) {
|
|
985
|
+
const ports = Object.entries(wt.ports ?? {}).map(([s, p]) => `${dim(`${s}:`)}${dim(String(p))}`).join(" ");
|
|
986
|
+
const portsCol = ports ? ` ${ports}` : "";
|
|
987
|
+
console.log(` ${dim(wt.repo)} ${dim(`[${wt.branch}]`)}${portsCol}`);
|
|
988
|
+
}
|
|
989
|
+
}, work);
|
|
878
990
|
return;
|
|
879
991
|
}
|
|
880
992
|
case "path": {
|
|
@@ -885,7 +997,7 @@ function dispatchWork(positionals, flags) {
|
|
|
885
997
|
case "describe": {
|
|
886
998
|
const text = need2(positionals[2], "usage: mx work -n <name> describe <text>");
|
|
887
999
|
const work = workDescribe(root, name, text);
|
|
888
|
-
emit(() => console.log(
|
|
1000
|
+
emit(() => console.log(`${check()} updated description of ${bold(name)}`), work);
|
|
889
1001
|
return;
|
|
890
1002
|
}
|
|
891
1003
|
case "worktree":
|
|
@@ -895,33 +1007,32 @@ function dispatchWork(positionals, flags) {
|
|
|
895
1007
|
case "destroy": {
|
|
896
1008
|
if (flags.force && !flags.porcelain) {
|
|
897
1009
|
process.stderr.write(
|
|
898
|
-
|
|
1010
|
+
`${warn()} ${dim(`permanently removing work "${name}" \u2014 folder and any session summaries will be deleted (branches kept). This cannot be undone.`)}
|
|
899
1011
|
`
|
|
900
1012
|
);
|
|
901
1013
|
}
|
|
902
1014
|
const res = workDestroy(root, name, { force: flags.force });
|
|
903
|
-
emit(
|
|
904
|
-
()
|
|
905
|
-
|
|
906
|
-
)
|
|
907
|
-
|
|
908
|
-
);
|
|
1015
|
+
emit(() => {
|
|
1016
|
+
const removed = res.removedWorktrees.join(", ") || "none";
|
|
1017
|
+
console.log(`${check()} destroyed work ${bold(name)}`);
|
|
1018
|
+
console.log(` ${dim(`worktrees removed: ${removed}; branches kept`)}`);
|
|
1019
|
+
}, res);
|
|
909
1020
|
return;
|
|
910
1021
|
}
|
|
911
1022
|
case "archive": {
|
|
912
1023
|
if (!flags.porcelain) {
|
|
913
1024
|
process.stderr.write(
|
|
914
|
-
`Reminder: write any pending session summary into works/${name}/sessions/ before archiving
|
|
1025
|
+
`${warn()} ${dim(`Reminder: write any pending session summary into works/${name}/sessions/ before archiving.`)}
|
|
915
1026
|
`
|
|
916
1027
|
);
|
|
917
1028
|
}
|
|
918
1029
|
const res = archiveWork(root, name);
|
|
919
|
-
emit(
|
|
920
|
-
()
|
|
921
|
-
|
|
922
|
-
)
|
|
923
|
-
|
|
924
|
-
);
|
|
1030
|
+
emit(() => {
|
|
1031
|
+
const removed = res.removedWorktrees.join(", ") || "none";
|
|
1032
|
+
console.log(`${check()} archived work ${bold(name)}`);
|
|
1033
|
+
console.log(` ${dim(`at ${res.archived_at}`)}`);
|
|
1034
|
+
console.log(` ${dim(`worktrees removed: ${removed}; branches kept`)}`);
|
|
1035
|
+
}, res);
|
|
925
1036
|
return;
|
|
926
1037
|
}
|
|
927
1038
|
case "unarchive": {
|
|
@@ -938,8 +1049,10 @@ function dispatchWork(positionals, flags) {
|
|
|
938
1049
|
}
|
|
939
1050
|
const res = unarchiveWork(root, name, overrides);
|
|
940
1051
|
emit(() => {
|
|
941
|
-
console.log(
|
|
942
|
-
for (const r of res.restored)
|
|
1052
|
+
console.log(`${check()} unarchived work ${bold(name)}`);
|
|
1053
|
+
for (const r of res.restored) {
|
|
1054
|
+
console.log(` ${r.repo} ${dim(`[${r.branch}]`)} ${dim(`\u2192 ${r.path}`)}`);
|
|
1055
|
+
}
|
|
943
1056
|
}, res);
|
|
944
1057
|
return;
|
|
945
1058
|
}
|
|
@@ -956,15 +1069,28 @@ function workWorktree(root, name, positionals, flags) {
|
|
|
956
1069
|
"usage: mx work -n <name> worktree add <repo> [--branch <b>] [--base <ref>]"
|
|
957
1070
|
);
|
|
958
1071
|
const res = worktreeAdd(root, name, repo, { branch: flags.branch, base: flags.base });
|
|
959
|
-
emit(
|
|
1072
|
+
emit(
|
|
1073
|
+
() => console.log(
|
|
1074
|
+
`${check()} added worktree ${bold(res.repo)} ${dim(`[${res.branch}]`)} ${dim(`\u2192 ${res.path}`)}`
|
|
1075
|
+
),
|
|
1076
|
+
res
|
|
1077
|
+
);
|
|
960
1078
|
return;
|
|
961
1079
|
}
|
|
962
1080
|
case "ls": {
|
|
963
1081
|
const list = worktreeList(root, name);
|
|
964
1082
|
emit(() => {
|
|
1083
|
+
if (list.length === 0) {
|
|
1084
|
+
console.log(dim("no worktrees yet \u2014 `mx work -n <name> worktree add <repo>`"));
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
1087
|
+
const repoW = Math.max(...list.map((wt) => wt.repo.length));
|
|
965
1088
|
for (const wt of list) {
|
|
966
|
-
const
|
|
967
|
-
|
|
1089
|
+
const repo = dim(wt.repo.padEnd(repoW));
|
|
1090
|
+
const branch = dim(`[${wt.branch}]`);
|
|
1091
|
+
const ports = Object.entries(wt.ports ?? {}).map(([s, p]) => `${dim(`${s}:`)}${dim(String(p))}`).join(" ");
|
|
1092
|
+
const portsCol = ports ? ` ${ports}` : "";
|
|
1093
|
+
console.log(`${repo} ${branch}${portsCol}`);
|
|
968
1094
|
}
|
|
969
1095
|
}, list);
|
|
970
1096
|
return;
|
|
@@ -972,7 +1098,12 @@ function workWorktree(root, name, positionals, flags) {
|
|
|
972
1098
|
case "rm": {
|
|
973
1099
|
const repo = need2(positionals[3], "usage: mx work -n <name> worktree rm <repo>");
|
|
974
1100
|
const res = worktreeRemove(root, name, repo);
|
|
975
|
-
emit(
|
|
1101
|
+
emit(
|
|
1102
|
+
() => console.log(
|
|
1103
|
+
`${check()} removed worktree ${bold(res.repo)} ${dim(`from ${name} (branch ${res.branch} kept)`)}`
|
|
1104
|
+
),
|
|
1105
|
+
res
|
|
1106
|
+
);
|
|
976
1107
|
return;
|
|
977
1108
|
}
|
|
978
1109
|
default:
|
|
@@ -993,7 +1124,12 @@ function workPort(root, name, positionals) {
|
|
|
993
1124
|
if (!Number.isInteger(port)) throw new MxError(`invalid port: ${portArg}`, "BAD_ARGS");
|
|
994
1125
|
}
|
|
995
1126
|
const res = portSet(root, name, repo, service, port);
|
|
996
|
-
emit(
|
|
1127
|
+
emit(
|
|
1128
|
+
() => console.log(
|
|
1129
|
+
`${check()} ${res.repo}${dim(".")}${res.service} ${dim("\u2192")} ${dim(String(res.port))}`
|
|
1130
|
+
),
|
|
1131
|
+
res
|
|
1132
|
+
);
|
|
997
1133
|
return;
|
|
998
1134
|
}
|
|
999
1135
|
case "unset": {
|
|
@@ -1001,17 +1137,34 @@ function workPort(root, name, positionals) {
|
|
|
1001
1137
|
const repo = need2(positionals[3], usage);
|
|
1002
1138
|
const service = need2(positionals[4], usage);
|
|
1003
1139
|
const res = portUnset(root, name, repo, service);
|
|
1004
|
-
emit(
|
|
1140
|
+
emit(
|
|
1141
|
+
() => console.log(
|
|
1142
|
+
`${check()} unset ${res.repo}${dim(".")}${res.service} ${dim(`(was ${res.released})`)}`
|
|
1143
|
+
),
|
|
1144
|
+
res
|
|
1145
|
+
);
|
|
1005
1146
|
return;
|
|
1006
1147
|
}
|
|
1007
1148
|
case "ls": {
|
|
1008
1149
|
const map = portList(root, name);
|
|
1009
1150
|
emit(() => {
|
|
1151
|
+
const entries = [];
|
|
1010
1152
|
for (const [repo, ports] of Object.entries(map)) {
|
|
1011
1153
|
for (const [service, port] of Object.entries(ports)) {
|
|
1012
|
-
|
|
1154
|
+
entries.push({ repo, service, port });
|
|
1013
1155
|
}
|
|
1014
1156
|
}
|
|
1157
|
+
if (entries.length === 0) {
|
|
1158
|
+
console.log(dim("no ports allocated yet \u2014 `mx work -n <name> port set <repo> <service>`"));
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
const lhsW = Math.max(...entries.map((e) => `${e.repo}.${e.service}`.length));
|
|
1162
|
+
for (const e of entries) {
|
|
1163
|
+
const lhs = `${e.repo}${dim(".")}${e.service}`;
|
|
1164
|
+
const plain = `${e.repo}.${e.service}`;
|
|
1165
|
+
const pad = " ".repeat(lhsW - plain.length);
|
|
1166
|
+
console.log(`${lhs}${pad} ${dim("\u2192")} ${dim(String(e.port))}`);
|
|
1167
|
+
}
|
|
1015
1168
|
}, map);
|
|
1016
1169
|
return;
|
|
1017
1170
|
}
|
|
@@ -1022,13 +1175,14 @@ function workPort(root, name, positionals) {
|
|
|
1022
1175
|
|
|
1023
1176
|
// src/main.ts
|
|
1024
1177
|
var VERSION = (() => {
|
|
1025
|
-
const here =
|
|
1026
|
-
const pkg = JSON.parse(readFileSync2(
|
|
1178
|
+
const here = path8.dirname(fileURLToPath2(import.meta.url));
|
|
1179
|
+
const pkg = JSON.parse(readFileSync2(path8.join(here, "..", "package.json"), "utf8"));
|
|
1027
1180
|
return pkg.version;
|
|
1028
1181
|
})();
|
|
1029
1182
|
function main() {
|
|
1030
1183
|
const { positionals, flags } = parseArgs(process.argv.slice(2));
|
|
1031
1184
|
setPorcelain(flags.porcelain);
|
|
1185
|
+
if (positionals[0] === "s" || positionals[0] === "st") positionals[0] = "status";
|
|
1032
1186
|
try {
|
|
1033
1187
|
if (flags.version || positionals[0] === "version") {
|
|
1034
1188
|
emit(() => console.log(`mx ${VERSION}`), { version: VERSION });
|