mcproc 0.14.1 → 0.14.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +149 -150
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2631,7 +2631,7 @@ async function unpack(target, opts, deps2) {
|
|
|
2631
2631
|
}
|
|
2632
2632
|
|
|
2633
2633
|
// src/core/leases/view.ts
|
|
2634
|
-
import
|
|
2634
|
+
import fs9 from "fs";
|
|
2635
2635
|
|
|
2636
2636
|
// src/core/memory.ts
|
|
2637
2637
|
var KB = 1024;
|
|
@@ -2688,8 +2688,8 @@ function stateDir(env = process.env) {
|
|
|
2688
2688
|
}
|
|
2689
2689
|
|
|
2690
2690
|
// src/core/leases/ensure.ts
|
|
2691
|
-
import
|
|
2692
|
-
import
|
|
2691
|
+
import fs8 from "fs";
|
|
2692
|
+
import path12 from "path";
|
|
2693
2693
|
|
|
2694
2694
|
// src/core/ecosystem.ts
|
|
2695
2695
|
import path9 from "path";
|
|
@@ -2731,11 +2731,95 @@ function buildEcosystem(m, host) {
|
|
|
2731
2731
|
return { apps };
|
|
2732
2732
|
}
|
|
2733
2733
|
|
|
2734
|
-
// src/core/
|
|
2734
|
+
// src/core/slots.ts
|
|
2735
2735
|
import crypto from "crypto";
|
|
2736
2736
|
import fs6 from "fs";
|
|
2737
|
-
import os5 from "os";
|
|
2738
2737
|
import path10 from "path";
|
|
2738
|
+
function registryKey(baseProject, repo) {
|
|
2739
|
+
return `${baseProject}-${crypto.createHash("sha256").update(realOf(repo)).digest("hex").slice(0, 8)}`;
|
|
2740
|
+
}
|
|
2741
|
+
function registryPath(stateDir2, baseProject, repo) {
|
|
2742
|
+
return path10.join(stateDir2, "slots", `${registryKey(baseProject, repo)}.json`);
|
|
2743
|
+
}
|
|
2744
|
+
function emptyRegistry(repo) {
|
|
2745
|
+
return { repo, slots: {} };
|
|
2746
|
+
}
|
|
2747
|
+
function readRegistry(file) {
|
|
2748
|
+
if (!fs6.existsSync(file)) return null;
|
|
2749
|
+
return JSON.parse(fs6.readFileSync(file, "utf8"));
|
|
2750
|
+
}
|
|
2751
|
+
function writeRegistry(file, r) {
|
|
2752
|
+
fs6.mkdirSync(path10.dirname(file), { recursive: true });
|
|
2753
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
2754
|
+
fs6.writeFileSync(tmp, JSON.stringify(r, null, 2));
|
|
2755
|
+
fs6.renameSync(tmp, file);
|
|
2756
|
+
}
|
|
2757
|
+
function withMain(r, entry) {
|
|
2758
|
+
return { ...r, slots: { ...r.slots, "0": entry } };
|
|
2759
|
+
}
|
|
2760
|
+
function claimSlot(r, opts) {
|
|
2761
|
+
if (opts.want !== void 0) {
|
|
2762
|
+
if (opts.want < 1 || opts.want > opts.max)
|
|
2763
|
+
throw new McprocError(
|
|
2764
|
+
"BAD_ARGUMENT",
|
|
2765
|
+
`slot ${opts.want} is outside 1..${opts.max} (slot 0 is the main checkout)`
|
|
2766
|
+
);
|
|
2767
|
+
if (Object.hasOwn(r.slots, String(opts.want)))
|
|
2768
|
+
throw new McprocError(
|
|
2769
|
+
"SLOT_TAKEN",
|
|
2770
|
+
`slot ${opts.want} is taken by ${r.slots[String(opts.want)].path}`,
|
|
2771
|
+
{ hint: "Pick another slot, or remove that worktree first." }
|
|
2772
|
+
);
|
|
2773
|
+
return {
|
|
2774
|
+
registry: { ...r, slots: { ...r.slots, [opts.want]: opts.entry } },
|
|
2775
|
+
slot: opts.want
|
|
2776
|
+
};
|
|
2777
|
+
}
|
|
2778
|
+
for (let s = 1; s <= opts.max; s++)
|
|
2779
|
+
if (!Object.hasOwn(r.slots, String(s)))
|
|
2780
|
+
return {
|
|
2781
|
+
registry: { ...r, slots: { ...r.slots, [s]: opts.entry } },
|
|
2782
|
+
slot: s
|
|
2783
|
+
};
|
|
2784
|
+
throw new McprocError(
|
|
2785
|
+
"NO_FREE_SLOT",
|
|
2786
|
+
`all ${opts.max} worktree slots of ${r.repo} are taken`,
|
|
2787
|
+
{
|
|
2788
|
+
hint: "Remove a worktree (worktree remove), or raise worktree.slots in the manifest."
|
|
2789
|
+
}
|
|
2790
|
+
);
|
|
2791
|
+
}
|
|
2792
|
+
function releaseSlot(r, slot) {
|
|
2793
|
+
const slots = { ...r.slots };
|
|
2794
|
+
delete slots[String(slot)];
|
|
2795
|
+
return { ...r, slots };
|
|
2796
|
+
}
|
|
2797
|
+
function realOf(p) {
|
|
2798
|
+
try {
|
|
2799
|
+
return fs6.realpathSync(p);
|
|
2800
|
+
} catch {
|
|
2801
|
+
return path10.resolve(p);
|
|
2802
|
+
}
|
|
2803
|
+
}
|
|
2804
|
+
function reclaimGone(r, exists) {
|
|
2805
|
+
const freed = [];
|
|
2806
|
+
let out = r;
|
|
2807
|
+
for (const [k, e] of Object.entries(r.slots)) {
|
|
2808
|
+
if (k === "0" || exists(realOf(e.path))) continue;
|
|
2809
|
+
out = releaseSlot(out, Number(k));
|
|
2810
|
+
freed.push(Number(k));
|
|
2811
|
+
}
|
|
2812
|
+
return { registry: out, freed: freed.sort((a, b) => a - b) };
|
|
2813
|
+
}
|
|
2814
|
+
function siblingsOf(r, root) {
|
|
2815
|
+
return Object.entries(r.slots).filter(([, e]) => realOf(e.path) !== realOf(root)).map(([k, entry]) => ({ slot: Number(k), entry })).sort((a, b) => a.slot - b.slot);
|
|
2816
|
+
}
|
|
2817
|
+
|
|
2818
|
+
// src/core/leases/store.ts
|
|
2819
|
+
import crypto2 from "crypto";
|
|
2820
|
+
import fs7 from "fs";
|
|
2821
|
+
import os5 from "os";
|
|
2822
|
+
import path11 from "path";
|
|
2739
2823
|
var ALLOWED = {
|
|
2740
2824
|
pending: ["starting", "running", "cancelled"],
|
|
2741
2825
|
starting: ["running", "failed", "cancelled"],
|
|
@@ -2776,7 +2860,7 @@ function parseHeld(raw) {
|
|
|
2776
2860
|
}
|
|
2777
2861
|
function readHeld(file) {
|
|
2778
2862
|
try {
|
|
2779
|
-
return parseHeld(
|
|
2863
|
+
return parseHeld(fs7.readFileSync(file, "utf8"));
|
|
2780
2864
|
} catch {
|
|
2781
2865
|
return null;
|
|
2782
2866
|
}
|
|
@@ -2791,13 +2875,13 @@ var LeaseStore = class {
|
|
|
2791
2875
|
* `beforeUnlink` (tests only) runs while a breaker holds the break guard, before the re-read that decides the unlink.
|
|
2792
2876
|
*/
|
|
2793
2877
|
constructor(stateDir2, deps2 = {}) {
|
|
2794
|
-
this.dir =
|
|
2878
|
+
this.dir = path11.join(stateDir2, "leases");
|
|
2795
2879
|
this.now = deps2.now ?? (() => Date.now());
|
|
2796
2880
|
this.pid = deps2.pid ?? process.pid;
|
|
2797
2881
|
this.beforeUnlink = deps2.beforeUnlink;
|
|
2798
2882
|
}
|
|
2799
2883
|
file(l) {
|
|
2800
|
-
return
|
|
2884
|
+
return path11.join(
|
|
2801
2885
|
this.dir,
|
|
2802
2886
|
`${String(l.seq).padStart(6, "0")}-${l.id}.json`
|
|
2803
2887
|
);
|
|
@@ -2806,11 +2890,11 @@ var LeaseStore = class {
|
|
|
2806
2890
|
return new Date(this.now()).toISOString();
|
|
2807
2891
|
}
|
|
2808
2892
|
write(l) {
|
|
2809
|
-
|
|
2893
|
+
fs7.mkdirSync(this.dir, { recursive: true });
|
|
2810
2894
|
const f = this.file(l);
|
|
2811
2895
|
const tmp = `${f}.${this.pid}.tmp`;
|
|
2812
|
-
|
|
2813
|
-
|
|
2896
|
+
fs7.writeFileSync(tmp, JSON.stringify(l, null, 2));
|
|
2897
|
+
fs7.renameSync(tmp, f);
|
|
2814
2898
|
return l;
|
|
2815
2899
|
}
|
|
2816
2900
|
/**
|
|
@@ -2820,13 +2904,13 @@ var LeaseStore = class {
|
|
|
2820
2904
|
* breakIfStale), never by moving it aside, so the lock path is never left free while a live holder has it.
|
|
2821
2905
|
*/
|
|
2822
2906
|
async withLock(fn) {
|
|
2823
|
-
|
|
2824
|
-
const lock =
|
|
2825
|
-
const token = `${this.pid}-${
|
|
2826
|
-
const tmp =
|
|
2907
|
+
fs7.mkdirSync(this.dir, { recursive: true });
|
|
2908
|
+
const lock = path11.join(this.dir, ".lock");
|
|
2909
|
+
const token = `${this.pid}-${crypto2.randomBytes(6).toString("hex")}`;
|
|
2910
|
+
const tmp = path11.join(this.dir, `.lock.${token}.tmp`);
|
|
2827
2911
|
const writeToken = () => {
|
|
2828
|
-
|
|
2829
|
-
|
|
2912
|
+
fs7.mkdirSync(this.dir, { recursive: true });
|
|
2913
|
+
fs7.writeFileSync(
|
|
2830
2914
|
tmp,
|
|
2831
2915
|
JSON.stringify({ pid: this.pid, at: this.now(), token })
|
|
2832
2916
|
);
|
|
@@ -2857,7 +2941,7 @@ var LeaseStore = class {
|
|
|
2857
2941
|
await new Promise((r2) => setTimeout(r2, 50));
|
|
2858
2942
|
}
|
|
2859
2943
|
} finally {
|
|
2860
|
-
|
|
2944
|
+
fs7.rmSync(tmp, { force: true });
|
|
2861
2945
|
}
|
|
2862
2946
|
LIVE_TOKENS.add(token);
|
|
2863
2947
|
try {
|
|
@@ -2883,15 +2967,15 @@ var LeaseStore = class {
|
|
|
2883
2967
|
sweepStrays() {
|
|
2884
2968
|
let names;
|
|
2885
2969
|
try {
|
|
2886
|
-
names =
|
|
2970
|
+
names = fs7.readdirSync(this.dir);
|
|
2887
2971
|
} catch {
|
|
2888
2972
|
return;
|
|
2889
2973
|
}
|
|
2890
2974
|
for (const n of names) {
|
|
2891
2975
|
if (!STRAY.test(n)) continue;
|
|
2892
|
-
const f =
|
|
2976
|
+
const f = path11.join(this.dir, n);
|
|
2893
2977
|
if (this.mtimeAgeMs(f) > LOCK_STALE_MS)
|
|
2894
|
-
|
|
2978
|
+
fs7.rmSync(f, { recursive: true, force: true });
|
|
2895
2979
|
}
|
|
2896
2980
|
}
|
|
2897
2981
|
/**
|
|
@@ -2902,12 +2986,12 @@ var LeaseStore = class {
|
|
|
2902
2986
|
tryLink(tmp, lock, rewrite) {
|
|
2903
2987
|
for (let attempt = 0; ; attempt++) {
|
|
2904
2988
|
try {
|
|
2905
|
-
|
|
2989
|
+
fs7.linkSync(tmp, lock);
|
|
2906
2990
|
return true;
|
|
2907
2991
|
} catch (e) {
|
|
2908
2992
|
const code = e.code;
|
|
2909
2993
|
if (code === "EEXIST") return false;
|
|
2910
|
-
if (code === "ENOENT" && attempt === 0 && !
|
|
2994
|
+
if (code === "ENOENT" && attempt === 0 && !fs7.existsSync(tmp)) {
|
|
2911
2995
|
rewrite();
|
|
2912
2996
|
continue;
|
|
2913
2997
|
}
|
|
@@ -2919,7 +3003,7 @@ var LeaseStore = class {
|
|
|
2919
3003
|
stillStale(lock, judged) {
|
|
2920
3004
|
let raw;
|
|
2921
3005
|
try {
|
|
2922
|
-
raw =
|
|
3006
|
+
raw = fs7.readFileSync(lock, "utf8");
|
|
2923
3007
|
} catch (e) {
|
|
2924
3008
|
if (e.code === "ENOENT") return false;
|
|
2925
3009
|
throw e;
|
|
@@ -2932,7 +3016,7 @@ var LeaseStore = class {
|
|
|
2932
3016
|
* or, with no readable holder, the directory's mtime is older than LOCK_STALE_MS.
|
|
2933
3017
|
*/
|
|
2934
3018
|
guardStale(guard) {
|
|
2935
|
-
const held = readHeld(
|
|
3019
|
+
const held = readHeld(path11.join(guard, "holder"));
|
|
2936
3020
|
if (held)
|
|
2937
3021
|
return this.now() - held.at > LOCK_STALE_MS && !pidAlive(held.pid);
|
|
2938
3022
|
return this.mtimeAgeMs(guard) > LOCK_STALE_MS;
|
|
@@ -2950,7 +3034,7 @@ var LeaseStore = class {
|
|
|
2950
3034
|
breakIfStale(lock, token, take) {
|
|
2951
3035
|
let raw;
|
|
2952
3036
|
try {
|
|
2953
|
-
raw =
|
|
3037
|
+
raw = fs7.readFileSync(lock, "utf8");
|
|
2954
3038
|
} catch (e) {
|
|
2955
3039
|
if (e.code === "ENOENT") return "retry";
|
|
2956
3040
|
throw e;
|
|
@@ -2960,57 +3044,57 @@ var LeaseStore = class {
|
|
|
2960
3044
|
if (!stale) return "wait";
|
|
2961
3045
|
const guard = `${lock}.break`;
|
|
2962
3046
|
try {
|
|
2963
|
-
|
|
3047
|
+
fs7.mkdirSync(guard);
|
|
2964
3048
|
} catch (e) {
|
|
2965
3049
|
if (e.code !== "EEXIST") throw e;
|
|
2966
3050
|
if (!this.guardStale(guard)) return "wait";
|
|
2967
3051
|
const moved = `${guard}.${token}.stale`;
|
|
2968
3052
|
try {
|
|
2969
|
-
|
|
3053
|
+
fs7.renameSync(guard, moved);
|
|
2970
3054
|
} catch (err) {
|
|
2971
3055
|
if (err.code === "ENOENT") return "retry";
|
|
2972
3056
|
throw err;
|
|
2973
3057
|
}
|
|
2974
|
-
|
|
3058
|
+
fs7.rmSync(moved, { recursive: true, force: true });
|
|
2975
3059
|
return "retry";
|
|
2976
3060
|
}
|
|
2977
3061
|
try {
|
|
2978
|
-
|
|
2979
|
-
|
|
3062
|
+
fs7.writeFileSync(
|
|
3063
|
+
path11.join(guard, "holder"),
|
|
2980
3064
|
JSON.stringify({ pid: this.pid, at: this.now(), token })
|
|
2981
3065
|
);
|
|
2982
3066
|
this.beforeUnlink?.();
|
|
2983
3067
|
if (!this.stillStale(lock, held)) return "retry";
|
|
2984
3068
|
try {
|
|
2985
|
-
|
|
3069
|
+
fs7.unlinkSync(lock);
|
|
2986
3070
|
} catch (e) {
|
|
2987
3071
|
if (e.code !== "ENOENT") throw e;
|
|
2988
3072
|
}
|
|
2989
3073
|
return take() ? "taken" : "retry";
|
|
2990
3074
|
} finally {
|
|
2991
|
-
|
|
3075
|
+
fs7.rmSync(guard, { recursive: true, force: true });
|
|
2992
3076
|
}
|
|
2993
3077
|
}
|
|
2994
3078
|
/** Age of a lock file by its modification time on the file system's clock; 0 when it is gone. */
|
|
2995
3079
|
mtimeAgeMs(lock) {
|
|
2996
3080
|
try {
|
|
2997
|
-
return Date.now() -
|
|
3081
|
+
return Date.now() - fs7.statSync(lock).mtimeMs;
|
|
2998
3082
|
} catch {
|
|
2999
3083
|
return 0;
|
|
3000
3084
|
}
|
|
3001
3085
|
}
|
|
3002
3086
|
/** Unlinks the lock only when it still carries this holder's token; a lock it does not own is never removed. */
|
|
3003
3087
|
release(lock, token) {
|
|
3004
|
-
if (readHeld(lock)?.token === token)
|
|
3088
|
+
if (readHeld(lock)?.token === token) fs7.rmSync(lock, { force: true });
|
|
3005
3089
|
}
|
|
3006
3090
|
/** Every readable lease, in sequence order; a half-written file is skipped. */
|
|
3007
3091
|
list() {
|
|
3008
|
-
if (!
|
|
3009
|
-
return
|
|
3092
|
+
if (!fs7.existsSync(this.dir)) return [];
|
|
3093
|
+
return fs7.readdirSync(this.dir).filter((f) => f.endsWith(".json")).sort().flatMap((f) => {
|
|
3010
3094
|
try {
|
|
3011
3095
|
return [
|
|
3012
3096
|
JSON.parse(
|
|
3013
|
-
|
|
3097
|
+
fs7.readFileSync(path11.join(this.dir, f), "utf8")
|
|
3014
3098
|
)
|
|
3015
3099
|
];
|
|
3016
3100
|
} catch {
|
|
@@ -3023,25 +3107,25 @@ var LeaseStore = class {
|
|
|
3023
3107
|
return this.list().find((l) => l.id === id) ?? null;
|
|
3024
3108
|
}
|
|
3025
3109
|
nextSeq() {
|
|
3026
|
-
const f =
|
|
3110
|
+
const f = path11.join(this.dir, ".seq");
|
|
3027
3111
|
const onDisk = Math.max(
|
|
3028
3112
|
0,
|
|
3029
|
-
...
|
|
3113
|
+
...fs7.readdirSync(this.dir).filter((name2) => name2.endsWith(".json")).map((name2) => Number(name2.split("-")[0])).filter(Number.isFinite)
|
|
3030
3114
|
);
|
|
3031
|
-
const parsed =
|
|
3115
|
+
const parsed = fs7.existsSync(f) ? Number(fs7.readFileSync(f, "utf8")) : 0;
|
|
3032
3116
|
const n = Math.max(Number.isFinite(parsed) ? parsed : 0, onDisk);
|
|
3033
3117
|
const tmp = `${f}.${this.pid}.tmp`;
|
|
3034
|
-
|
|
3035
|
-
|
|
3118
|
+
fs7.writeFileSync(tmp, String(n + 1));
|
|
3119
|
+
fs7.renameSync(tmp, f);
|
|
3036
3120
|
return n + 1;
|
|
3037
3121
|
}
|
|
3038
3122
|
/** A new lease, pending (queued) or running (admitted at once), with `child_pid` the process running its start; call it under the lock. */
|
|
3039
3123
|
async create(input, state, opts = {}) {
|
|
3040
|
-
|
|
3124
|
+
fs7.mkdirSync(this.dir, { recursive: true });
|
|
3041
3125
|
const at = this.stamp();
|
|
3042
3126
|
const l = {
|
|
3043
3127
|
...input,
|
|
3044
|
-
id: `l_${
|
|
3128
|
+
id: `l_${crypto2.randomBytes(8).toString("base64url").replace(/[^a-z0-9]/gi, "").toLowerCase().padEnd(10, "0").slice(0, 10)}`,
|
|
3045
3129
|
seq: this.nextSeq(),
|
|
3046
3130
|
state,
|
|
3047
3131
|
child_pid: opts.child_pid ?? null,
|
|
@@ -3119,7 +3203,7 @@ var LeaseStore = class {
|
|
|
3119
3203
|
for (const l of this.list()) {
|
|
3120
3204
|
if (!ENDED.has(l.state) || !l.ended_at) continue;
|
|
3121
3205
|
if (this.now() - Date.parse(l.ended_at) > maxAgeMs) {
|
|
3122
|
-
|
|
3206
|
+
fs7.rmSync(this.file(l), { force: true });
|
|
3123
3207
|
n++;
|
|
3124
3208
|
}
|
|
3125
3209
|
}
|
|
@@ -3137,7 +3221,7 @@ var EXIT_POLL_MS = 250;
|
|
|
3137
3221
|
function exitingAt(dir) {
|
|
3138
3222
|
try {
|
|
3139
3223
|
const v = JSON.parse(
|
|
3140
|
-
|
|
3224
|
+
fs8.readFileSync(path12.join(dir, WATCHER_NOTE), "utf8")
|
|
3141
3225
|
);
|
|
3142
3226
|
const at = typeof v.exiting_at === "string" ? Date.parse(v.exiting_at) : Number.NaN;
|
|
3143
3227
|
return Number.isFinite(at) ? at : null;
|
|
@@ -3146,9 +3230,10 @@ function exitingAt(dir) {
|
|
|
3146
3230
|
}
|
|
3147
3231
|
}
|
|
3148
3232
|
function watcherEntry(env = process.env, argv = process.argv) {
|
|
3149
|
-
if (env.MCPROC_CLI_ENTRY) return
|
|
3233
|
+
if (env.MCPROC_CLI_ENTRY) return path12.resolve(env.MCPROC_CLI_ENTRY);
|
|
3150
3234
|
const own2 = argv[1] ?? "";
|
|
3151
|
-
|
|
3235
|
+
const real = own2 ? realOf(own2) : "";
|
|
3236
|
+
if (real.endsWith("cli.js")) return real;
|
|
3152
3237
|
throw new McprocError(
|
|
3153
3238
|
"NOT_STARTED",
|
|
3154
3239
|
`cannot launch the lease watcher: this mcproc runs from ${own2 || "an unknown entry"}, not from a built cli.js`,
|
|
@@ -3180,10 +3265,10 @@ async function lookAndLaunch(ctx, dir, leaving) {
|
|
|
3180
3265
|
if (await watcherOnline(ctx) && (leaving === null || exitingAt(dir) !== leaving))
|
|
3181
3266
|
return "online";
|
|
3182
3267
|
const entry = watcherEntry();
|
|
3183
|
-
const file =
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3268
|
+
const file = path12.join(dir, "watcher.ecosystem.json");
|
|
3269
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
3270
|
+
fs8.rmSync(path12.join(dir, WATCHER_NOTE), { force: true });
|
|
3271
|
+
fs8.writeFileSync(
|
|
3187
3272
|
file,
|
|
3188
3273
|
JSON.stringify(
|
|
3189
3274
|
{
|
|
@@ -3219,7 +3304,7 @@ async function lookAndLaunch(ctx, dir, leaving) {
|
|
|
3219
3304
|
// src/core/leases/view.ts
|
|
3220
3305
|
async function leasesView(dir = stateDir(), ctx) {
|
|
3221
3306
|
const store = new LeaseStore(dir);
|
|
3222
|
-
if (
|
|
3307
|
+
if (fs9.existsSync(store.dir))
|
|
3223
3308
|
await store.withLock(() => store.prune()).catch(() => {
|
|
3224
3309
|
});
|
|
3225
3310
|
const leases = store.list().map((l) => ({ ...l, position: store.positionOf(l.id) }));
|
|
@@ -3280,11 +3365,11 @@ import fs12 from "fs";
|
|
|
3280
3365
|
import path16 from "path";
|
|
3281
3366
|
|
|
3282
3367
|
// src/core/actions.ts
|
|
3283
|
-
import
|
|
3368
|
+
import path15 from "path";
|
|
3284
3369
|
|
|
3285
3370
|
// src/core/list.ts
|
|
3286
|
-
import
|
|
3287
|
-
import
|
|
3371
|
+
import fs10 from "fs";
|
|
3372
|
+
import path13 from "path";
|
|
3288
3373
|
|
|
3289
3374
|
// src/core/metrics.ts
|
|
3290
3375
|
function cpuPercent(pids, before, after, clkTck, intervalSec) {
|
|
@@ -4345,8 +4430,8 @@ var depView = (d) => ({
|
|
|
4345
4430
|
timeout_s: d.timeoutMs / 1e3
|
|
4346
4431
|
});
|
|
4347
4432
|
function describeProject(m) {
|
|
4348
|
-
const readmePath =
|
|
4349
|
-
const readme =
|
|
4433
|
+
const readmePath = path13.join(m.root, ".mcproc", "README.md");
|
|
4434
|
+
const readme = fs10.existsSync(readmePath) ? fs10.readFileSync(readmePath, "utf8") : null;
|
|
4350
4435
|
return {
|
|
4351
4436
|
project: m.project,
|
|
4352
4437
|
root: m.root,
|
|
@@ -4379,7 +4464,7 @@ function describeProject(m) {
|
|
|
4379
4464
|
|
|
4380
4465
|
// src/core/logfollow.ts
|
|
4381
4466
|
import { open } from "fs/promises";
|
|
4382
|
-
import
|
|
4467
|
+
import path14 from "path";
|
|
4383
4468
|
|
|
4384
4469
|
// src/core/logwatch.ts
|
|
4385
4470
|
import { watch as fsWatch } from "fs";
|
|
@@ -4873,8 +4958,8 @@ var LogFollower = class {
|
|
|
4873
4958
|
}
|
|
4874
4959
|
const byDir = /* @__PURE__ */ new Map();
|
|
4875
4960
|
for (const p of [this.paths.out, this.paths.err]) {
|
|
4876
|
-
const dir =
|
|
4877
|
-
byDir.set(dir, (byDir.get(dir) ?? /* @__PURE__ */ new Set()).add(
|
|
4961
|
+
const dir = path14.dirname(p);
|
|
4962
|
+
byDir.set(dir, (byDir.get(dir) ?? /* @__PURE__ */ new Set()).add(path14.basename(p)));
|
|
4878
4963
|
}
|
|
4879
4964
|
for (const [dir, names] of byDir)
|
|
4880
4965
|
this.unwatch.push(
|
|
@@ -6331,7 +6416,7 @@ function assertNoCollision(ctx, procs, names) {
|
|
|
6331
6416
|
const name2 = pm2Name(ctx.manifest.project, n);
|
|
6332
6417
|
const expectedCwd = appCwd(ctx.host, ctx.manifest.apps[n]);
|
|
6333
6418
|
const other = procs.find(
|
|
6334
|
-
(p) => p.name === name2 && (p.namespace !== ctx.manifest.project || p.cwd !== "" &&
|
|
6419
|
+
(p) => p.name === name2 && (p.namespace !== ctx.manifest.project || p.cwd !== "" && path15.posix.resolve(p.cwd) !== expectedCwd)
|
|
6335
6420
|
);
|
|
6336
6421
|
if (other && other.namespace !== ctx.manifest.project) {
|
|
6337
6422
|
throw new McprocError(
|
|
@@ -6358,7 +6443,7 @@ async function shipEcosystem(ctx) {
|
|
|
6358
6443
|
const eco = buildEcosystem(ctx.manifest, ctx.host);
|
|
6359
6444
|
const file = ecosystemPath(ctx.host);
|
|
6360
6445
|
const dir = generatedDir(ctx.host);
|
|
6361
|
-
const gi =
|
|
6446
|
+
const gi = path15.posix.join(path15.posix.dirname(dir), ".gitignore");
|
|
6362
6447
|
const cmd = `mkdir -p ${shq(dir)} && { [ -e ${shq(gi)} ] || printf '*\\n' > ${shq(gi)}; } && cat > ${shq(file)}`;
|
|
6363
6448
|
const res = ensureOk(
|
|
6364
6449
|
await ctx.transport.exec(cmd, { stdin: JSON.stringify(eco, null, 2) }),
|
|
@@ -6536,92 +6621,6 @@ function admit(opts) {
|
|
|
6536
6621
|
|
|
6537
6622
|
// src/core/leases/siblings.ts
|
|
6538
6623
|
import fs11 from "fs";
|
|
6539
|
-
|
|
6540
|
-
// src/core/slots.ts
|
|
6541
|
-
import crypto2 from "crypto";
|
|
6542
|
-
import fs10 from "fs";
|
|
6543
|
-
import path15 from "path";
|
|
6544
|
-
function registryKey(baseProject, repo) {
|
|
6545
|
-
return `${baseProject}-${crypto2.createHash("sha256").update(realOf(repo)).digest("hex").slice(0, 8)}`;
|
|
6546
|
-
}
|
|
6547
|
-
function registryPath(stateDir2, baseProject, repo) {
|
|
6548
|
-
return path15.join(stateDir2, "slots", `${registryKey(baseProject, repo)}.json`);
|
|
6549
|
-
}
|
|
6550
|
-
function emptyRegistry(repo) {
|
|
6551
|
-
return { repo, slots: {} };
|
|
6552
|
-
}
|
|
6553
|
-
function readRegistry(file) {
|
|
6554
|
-
if (!fs10.existsSync(file)) return null;
|
|
6555
|
-
return JSON.parse(fs10.readFileSync(file, "utf8"));
|
|
6556
|
-
}
|
|
6557
|
-
function writeRegistry(file, r) {
|
|
6558
|
-
fs10.mkdirSync(path15.dirname(file), { recursive: true });
|
|
6559
|
-
const tmp = `${file}.${process.pid}.tmp`;
|
|
6560
|
-
fs10.writeFileSync(tmp, JSON.stringify(r, null, 2));
|
|
6561
|
-
fs10.renameSync(tmp, file);
|
|
6562
|
-
}
|
|
6563
|
-
function withMain(r, entry) {
|
|
6564
|
-
return { ...r, slots: { ...r.slots, "0": entry } };
|
|
6565
|
-
}
|
|
6566
|
-
function claimSlot(r, opts) {
|
|
6567
|
-
if (opts.want !== void 0) {
|
|
6568
|
-
if (opts.want < 1 || opts.want > opts.max)
|
|
6569
|
-
throw new McprocError(
|
|
6570
|
-
"BAD_ARGUMENT",
|
|
6571
|
-
`slot ${opts.want} is outside 1..${opts.max} (slot 0 is the main checkout)`
|
|
6572
|
-
);
|
|
6573
|
-
if (Object.hasOwn(r.slots, String(opts.want)))
|
|
6574
|
-
throw new McprocError(
|
|
6575
|
-
"SLOT_TAKEN",
|
|
6576
|
-
`slot ${opts.want} is taken by ${r.slots[String(opts.want)].path}`,
|
|
6577
|
-
{ hint: "Pick another slot, or remove that worktree first." }
|
|
6578
|
-
);
|
|
6579
|
-
return {
|
|
6580
|
-
registry: { ...r, slots: { ...r.slots, [opts.want]: opts.entry } },
|
|
6581
|
-
slot: opts.want
|
|
6582
|
-
};
|
|
6583
|
-
}
|
|
6584
|
-
for (let s = 1; s <= opts.max; s++)
|
|
6585
|
-
if (!Object.hasOwn(r.slots, String(s)))
|
|
6586
|
-
return {
|
|
6587
|
-
registry: { ...r, slots: { ...r.slots, [s]: opts.entry } },
|
|
6588
|
-
slot: s
|
|
6589
|
-
};
|
|
6590
|
-
throw new McprocError(
|
|
6591
|
-
"NO_FREE_SLOT",
|
|
6592
|
-
`all ${opts.max} worktree slots of ${r.repo} are taken`,
|
|
6593
|
-
{
|
|
6594
|
-
hint: "Remove a worktree (worktree remove), or raise worktree.slots in the manifest."
|
|
6595
|
-
}
|
|
6596
|
-
);
|
|
6597
|
-
}
|
|
6598
|
-
function releaseSlot(r, slot) {
|
|
6599
|
-
const slots = { ...r.slots };
|
|
6600
|
-
delete slots[String(slot)];
|
|
6601
|
-
return { ...r, slots };
|
|
6602
|
-
}
|
|
6603
|
-
function realOf(p) {
|
|
6604
|
-
try {
|
|
6605
|
-
return fs10.realpathSync(p);
|
|
6606
|
-
} catch {
|
|
6607
|
-
return path15.resolve(p);
|
|
6608
|
-
}
|
|
6609
|
-
}
|
|
6610
|
-
function reclaimGone(r, exists) {
|
|
6611
|
-
const freed = [];
|
|
6612
|
-
let out = r;
|
|
6613
|
-
for (const [k, e] of Object.entries(r.slots)) {
|
|
6614
|
-
if (k === "0" || exists(realOf(e.path))) continue;
|
|
6615
|
-
out = releaseSlot(out, Number(k));
|
|
6616
|
-
freed.push(Number(k));
|
|
6617
|
-
}
|
|
6618
|
-
return { registry: out, freed: freed.sort((a, b) => a - b) };
|
|
6619
|
-
}
|
|
6620
|
-
function siblingsOf(r, root) {
|
|
6621
|
-
return Object.entries(r.slots).filter(([, e]) => realOf(e.path) !== realOf(root)).map(([k, entry]) => ({ slot: Number(k), entry })).sort((a, b) => a.slot - b.slot);
|
|
6622
|
-
}
|
|
6623
|
-
|
|
6624
|
-
// src/core/leases/siblings.ts
|
|
6625
6624
|
var MIB = 1024 * 1024;
|
|
6626
6625
|
var repoOf = (m) => m.repo;
|
|
6627
6626
|
function registryOf(m, dir = stateDir()) {
|