@dovocode/workstation 0.1.0 → 0.1.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/README.md +17 -1
- package/dist/cli.js +710 -204
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +32 -3
- package/dist/index.js +227 -61
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/cli.js
CHANGED
|
@@ -24,12 +24,12 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
24
24
|
}
|
|
25
25
|
return to;
|
|
26
26
|
};
|
|
27
|
-
var __toESM = (mod, isNodeMode,
|
|
27
|
+
var __toESM = (mod, isNodeMode, target2) => (target2 = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
28
28
|
// If the importer is in node compatibility mode or this is not an ESM
|
|
29
29
|
// file that has been converted to a CommonJS file using a Babel-
|
|
30
30
|
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
31
31
|
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
32
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(
|
|
32
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target2, "default", { value: mod, enumerable: true }) : target2,
|
|
33
33
|
mod
|
|
34
34
|
));
|
|
35
35
|
|
|
@@ -2960,8 +2960,8 @@ var require_jiti = __commonJS({
|
|
|
2960
2960
|
}
|
|
2961
2961
|
const He = /^[/\\]{2}/, ze = /^[/\\](?![/\\])|^[/\\]{2}(?!\.)|^[A-Za-z]:[/\\]/, Je = /^[A-Za-z]:$/, Ye = /.(\.[^./]+|\.)$/, pathe_M_eThtNZ_normalize = function(e3) {
|
|
2962
2962
|
if (0 === e3.length) return ".";
|
|
2963
|
-
const t3 = (e3 = pathe_M_eThtNZ_normalizeWindowsPath(e3)).match(He), i2 =
|
|
2964
|
-
return 0 === (e3 = normalizeString(e3, !i2)).length ? i2 ? "/" : n2 ? "./" : "." : (n2 && (e3 += "/"), Je.test(e3) && (e3 += "/"), t3 ? i2 ? `//${e3}` : `//./${e3}` : i2 && !
|
|
2963
|
+
const t3 = (e3 = pathe_M_eThtNZ_normalizeWindowsPath(e3)).match(He), i2 = isAbsolute4(e3), n2 = "/" === e3[e3.length - 1];
|
|
2964
|
+
return 0 === (e3 = normalizeString(e3, !i2)).length ? i2 ? "/" : n2 ? "./" : "." : (n2 && (e3 += "/"), Je.test(e3) && (e3 += "/"), t3 ? i2 ? `//${e3}` : `//./${e3}` : i2 && !isAbsolute4(e3) ? `/${e3}` : e3);
|
|
2965
2965
|
}, pathe_M_eThtNZ_join = function(...e3) {
|
|
2966
2966
|
let t3 = "";
|
|
2967
2967
|
for (const i2 of e3) if (i2) if (t3.length > 0) {
|
|
@@ -2977,9 +2977,9 @@ var require_jiti = __commonJS({
|
|
|
2977
2977
|
let t3 = "", i2 = false;
|
|
2978
2978
|
for (let n2 = (e3 = e3.map((e4) => pathe_M_eThtNZ_normalizeWindowsPath(e4))).length - 1; n2 >= -1 && !i2; n2--) {
|
|
2979
2979
|
const a2 = n2 >= 0 ? e3[n2] : pathe_M_eThtNZ_cwd();
|
|
2980
|
-
a2 && 0 !== a2.length && (t3 = `${a2}/${t3}`, i2 =
|
|
2980
|
+
a2 && 0 !== a2.length && (t3 = `${a2}/${t3}`, i2 = isAbsolute4(a2));
|
|
2981
2981
|
}
|
|
2982
|
-
return t3 = normalizeString(t3, !i2), i2 && !
|
|
2982
|
+
return t3 = normalizeString(t3, !i2), i2 && !isAbsolute4(t3) ? `/${t3}` : t3.length > 0 ? t3 : ".";
|
|
2983
2983
|
};
|
|
2984
2984
|
function normalizeString(e3, t3) {
|
|
2985
2985
|
let i2 = "", n2 = 0, a2 = -1, c2 = 0, l2 = null;
|
|
@@ -3010,7 +3010,7 @@ var require_jiti = __commonJS({
|
|
|
3010
3010
|
}
|
|
3011
3011
|
return i2;
|
|
3012
3012
|
}
|
|
3013
|
-
const
|
|
3013
|
+
const isAbsolute4 = function(e3) {
|
|
3014
3014
|
return ze.test(e3);
|
|
3015
3015
|
}, extname = function(e3) {
|
|
3016
3016
|
if (".." === e3) return "";
|
|
@@ -3018,8 +3018,8 @@ var require_jiti = __commonJS({
|
|
|
3018
3018
|
return t3 && t3[1] || "";
|
|
3019
3019
|
}, pathe_M_eThtNZ_dirname = function(e3) {
|
|
3020
3020
|
const t3 = pathe_M_eThtNZ_normalizeWindowsPath(e3).replace(/\/$/, "").split("/").slice(0, -1);
|
|
3021
|
-
return 1 === t3.length && Je.test(t3[0]) && (t3[0] += "/"), t3.join("/") || (
|
|
3022
|
-
},
|
|
3021
|
+
return 1 === t3.length && Je.test(t3[0]) && (t3[0] += "/"), t3.join("/") || (isAbsolute4(e3) ? "/" : ".");
|
|
3022
|
+
}, basename2 = function(e3, t3) {
|
|
3023
3023
|
const i2 = pathe_M_eThtNZ_normalizeWindowsPath(e3).split("/");
|
|
3024
3024
|
let n2 = "";
|
|
3025
3025
|
for (let e4 = i2.length - 1; e4 >= 0; e4--) {
|
|
@@ -3494,7 +3494,7 @@ Default "index" lookups for the main are deprecated for ES modules.`, "Deprecati
|
|
|
3494
3494
|
}
|
|
3495
3495
|
if (/(?:node|data|http|https):/.test(e3)) return e3;
|
|
3496
3496
|
if (st.has(e3)) return "node:" + e3;
|
|
3497
|
-
if (e3.startsWith("file://") && (e3 = fileURLToPath(e3)),
|
|
3497
|
+
if (e3.startsWith("file://") && (e3 = fileURLToPath(e3)), isAbsolute4(e3)) try {
|
|
3498
3498
|
if ((0, $e.statSync)(e3).isFile()) return pathToFileURL(e3);
|
|
3499
3499
|
} catch (e4) {
|
|
3500
3500
|
if ("ENOENT" !== e4?.code) throw e4;
|
|
@@ -3732,7 +3732,7 @@ Default "index" lookups for the main are deprecated for ES modules.`, "Deprecati
|
|
|
3732
3732
|
}
|
|
3733
3733
|
function nativeImportOrRequire(e3, t3, i2) {
|
|
3734
3734
|
return i2 && e3.nativeImport ? e3.nativeImport((function(e4) {
|
|
3735
|
-
return ni &&
|
|
3735
|
+
return ni && isAbsolute4(e4) ? pathToFileURL(e4) : e4;
|
|
3736
3736
|
})(t3)).then((t4) => jitiInteropDefault(e3, t4)) : jitiInteropDefault(e3, e3.nativeRequire(t3));
|
|
3737
3737
|
}
|
|
3738
3738
|
const Ti = "9";
|
|
@@ -3740,7 +3740,7 @@ Default "index" lookups for the main are deprecated for ES modules.`, "Deprecati
|
|
|
3740
3740
|
if (!e3.opts.fsCache || !t3.filename) return i2();
|
|
3741
3741
|
const n2 = ` /* v${Ti}-${utils_hash(t3.source, 16)} */
|
|
3742
3742
|
`;
|
|
3743
|
-
let a2 = `${
|
|
3743
|
+
let a2 = `${basename2(pathe_M_eThtNZ_dirname(t3.filename))}-${(function(e4) {
|
|
3744
3744
|
const t4 = e4.split(Wt).pop();
|
|
3745
3745
|
if (!t4) return;
|
|
3746
3746
|
const i3 = t4.lastIndexOf(".");
|
|
@@ -3786,7 +3786,7 @@ Default "index" lookups for the main are deprecated for ES modules.`, "Deprecati
|
|
|
3786
3786
|
return i2.startsWith("#!") && (i2 = "// " + i2), i2;
|
|
3787
3787
|
}
|
|
3788
3788
|
function eval_evalModule(t3, i2, n2 = {}) {
|
|
3789
|
-
const a2 = n2.id || (n2.filename ?
|
|
3789
|
+
const a2 = n2.id || (n2.filename ? basename2(n2.filename) : `_jitiEval.${n2.ext || (n2.async ? "mjs" : "js")}`), c2 = n2.filename || jitiResolve(t3, a2, { async: n2.async }), l2 = n2.ext || extname(c2), y2 = n2.cache || t3.parentCache || {}, E2 = /\.[cm]?tsx?$/.test(l2), w2 = ".mjs" === l2 || ".js" === l2 && "module" === (function(e3) {
|
|
3790
3790
|
for (; e3 && "." !== e3 && "/" !== e3; ) {
|
|
3791
3791
|
e3 = pathe_M_eThtNZ_join(e3, "..");
|
|
3792
3792
|
try {
|
|
@@ -3834,7 +3834,7 @@ Default "index" lookups for the main are deprecated for ES modules.`, "Deprecati
|
|
|
3834
3834
|
(0, $e.mkdirSync)(n4, { recursive: true });
|
|
3835
3835
|
} catch {
|
|
3836
3836
|
}
|
|
3837
|
-
const a4 = pathe_M_eThtNZ_join(n4, `${
|
|
3837
|
+
const a4 = pathe_M_eThtNZ_join(n4, `${basename2(i6, extname(i6))}-${Date.now()}-${Math.random().toString(36).slice(2)}.mjs`);
|
|
3838
3838
|
return (0, $e.writeFileSync)(a4, t5), a4;
|
|
3839
3839
|
})(l3, n3), debug(t4, "[esm]", "[tempfile]", c4), a3(pathToFileURL(c4))), E3 = y3 ? a3(y3).catch((e3) => {
|
|
3840
3840
|
if ("ENAMETOOLONG" !== e3?.code) throw e3;
|
|
@@ -4210,7 +4210,7 @@ Did you specify these with the most recent transformation maps first?`);
|
|
|
4210
4210
|
for (let e5 = 1; e5 < s; e5++) a += "/" + n[e5];
|
|
4211
4211
|
(!a || o && !a.endsWith("/..")) && (a += "/"), e4.path = a;
|
|
4212
4212
|
}
|
|
4213
|
-
function
|
|
4213
|
+
function resolve12(e4, t3) {
|
|
4214
4214
|
if (!e4 && !t3) return "";
|
|
4215
4215
|
const r3 = parseUrl(e4);
|
|
4216
4216
|
let n = r3.type;
|
|
@@ -4247,7 +4247,7 @@ Did you specify these with the most recent transformation maps first?`);
|
|
|
4247
4247
|
return r3.scheme + "//" + r3.user + r3.host + r3.port + r3.path + s;
|
|
4248
4248
|
}
|
|
4249
4249
|
}
|
|
4250
|
-
return
|
|
4250
|
+
return resolve12;
|
|
4251
4251
|
})();
|
|
4252
4252
|
}, "./node_modules/.pnpm/@jridgewell+sourcemap-codec@1.5.5/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.umd.js"(e2, t2, r2) {
|
|
4253
4253
|
var n;
|
|
@@ -7368,7 +7368,7 @@ ${yield* s.optionsAndDescriptors(e3.content)}`;
|
|
|
7368
7368
|
return e3;
|
|
7369
7369
|
}, e3;
|
|
7370
7370
|
}
|
|
7371
|
-
Object.defineProperty(t2, "__esModule", { value: true }), t2.access =
|
|
7371
|
+
Object.defineProperty(t2, "__esModule", { value: true }), t2.access = access3, t2.assertArray = assertArray, t2.assertAssumptions = function(e3, t3) {
|
|
7372
7372
|
if (void 0 === t3) return;
|
|
7373
7373
|
if ("object" != typeof t3 || null === t3) throw new Error(`${msg(e3)} must be an object or undefined.`);
|
|
7374
7374
|
let r3 = e3;
|
|
@@ -7377,7 +7377,7 @@ ${yield* s.optionsAndDescriptors(e3.content)}`;
|
|
|
7377
7377
|
} while ("root" !== r3.type);
|
|
7378
7378
|
const s = "preset" === r3.source;
|
|
7379
7379
|
for (const r4 of Object.keys(t3)) {
|
|
7380
|
-
const i =
|
|
7380
|
+
const i = access3(e3, r4);
|
|
7381
7381
|
if (!n.assumptionsNames.has(r4)) throw new Error(`${msg(i)} is not a supported assumption.`);
|
|
7382
7382
|
if ("boolean" != typeof t3[r4]) throw new Error(`${msg(i)} must be a boolean.`);
|
|
7383
7383
|
if (s && false === t3[r4]) throw new Error(`${msg(i)} cannot be set to 'false' inside presets.`);
|
|
@@ -7386,7 +7386,7 @@ ${yield* s.optionsAndDescriptors(e3.content)}`;
|
|
|
7386
7386
|
}, t2.assertBabelrcSearch = function(e3, t3) {
|
|
7387
7387
|
if (void 0 === t3 || "boolean" == typeof t3) return t3;
|
|
7388
7388
|
if (Array.isArray(t3)) t3.forEach((t4, r3) => {
|
|
7389
|
-
if (!checkValidTest(t4)) throw new Error(`${msg(
|
|
7389
|
+
if (!checkValidTest(t4)) throw new Error(`${msg(access3(e3, r3))} must be a string/Function/RegExp.`);
|
|
7390
7390
|
});
|
|
7391
7391
|
else if (!checkValidTest(t3)) throw new Error(`${msg(e3)} must be a undefined, a boolean, a string/Function/RegExp or an array of those, got ${JSON.stringify(t3)}`);
|
|
7392
7392
|
return t3;
|
|
@@ -7395,7 +7395,7 @@ ${yield* s.optionsAndDescriptors(e3.content)}`;
|
|
|
7395
7395
|
if (r3) {
|
|
7396
7396
|
if ("string" != typeof r3.name) throw new Error(`${msg(e3)} set but does not contain "name" property string`);
|
|
7397
7397
|
for (const t4 of Object.keys(r3)) {
|
|
7398
|
-
const n2 =
|
|
7398
|
+
const n2 = access3(e3, t4), s = r3[t4];
|
|
7399
7399
|
if (null != s && "boolean" != typeof s && "string" != typeof s && "number" != typeof s) throw new Error(`${msg(n2)} must be null, undefined, a boolean, a string, or a number.`);
|
|
7400
7400
|
}
|
|
7401
7401
|
}
|
|
@@ -7406,7 +7406,7 @@ ${yield* s.optionsAndDescriptors(e3.content)}`;
|
|
|
7406
7406
|
}, t2.assertConfigApplicableTest = function(e3, t3) {
|
|
7407
7407
|
if (void 0 === t3) return t3;
|
|
7408
7408
|
if (Array.isArray(t3)) t3.forEach((t4, r3) => {
|
|
7409
|
-
if (!checkValidTest(t4)) throw new Error(`${msg(
|
|
7409
|
+
if (!checkValidTest(t4)) throw new Error(`${msg(access3(e3, r3))} must be a string/Function/RegExp.`);
|
|
7410
7410
|
});
|
|
7411
7411
|
else if (!checkValidTest(t3)) throw new Error(`${msg(e3)} must be a string/Function/RegExp, or an array of those`);
|
|
7412
7412
|
return t3;
|
|
@@ -7421,7 +7421,7 @@ ${yield* s.optionsAndDescriptors(e3.content)}`;
|
|
|
7421
7421
|
return null == r3 || r3.forEach((t4, r4) => (function(e4, t5) {
|
|
7422
7422
|
if ("string" != typeof t5 && "function" != typeof t5 && !(t5 instanceof RegExp)) throw new Error(`${msg(e4)} must be an array of string/Function/RegExp values, or undefined`);
|
|
7423
7423
|
return t5;
|
|
7424
|
-
})(
|
|
7424
|
+
})(access3(e3, r4), t4)), r3;
|
|
7425
7425
|
}, t2.assertInputSourceMap = function(e3, t3) {
|
|
7426
7426
|
if (void 0 !== t3 && "boolean" != typeof t3 && ("object" != typeof t3 || !t3)) throw new Error(`${msg(e3)} must be a boolean, object, or undefined`);
|
|
7427
7427
|
return t3;
|
|
@@ -7431,17 +7431,17 @@ ${yield* s.optionsAndDescriptors(e3.content)}`;
|
|
|
7431
7431
|
if (Array.isArray(t5)) {
|
|
7432
7432
|
if (0 === t5.length) throw new Error(`${msg(e4)} must include an object`);
|
|
7433
7433
|
if (t5.length > 3) throw new Error(`${msg(e4)} may only be a two-tuple or three-tuple`);
|
|
7434
|
-
if (assertPluginTarget(
|
|
7434
|
+
if (assertPluginTarget(access3(e4, 0), t5[0]), t5.length > 1) {
|
|
7435
7435
|
const r5 = t5[1];
|
|
7436
|
-
if (void 0 !== r5 && false !== r5 && ("object" != typeof r5 || Array.isArray(r5) || null === r5)) throw new Error(`${msg(
|
|
7436
|
+
if (void 0 !== r5 && false !== r5 && ("object" != typeof r5 || Array.isArray(r5) || null === r5)) throw new Error(`${msg(access3(e4, 1))} must be an object, false, or undefined`);
|
|
7437
7437
|
}
|
|
7438
7438
|
if (3 === t5.length) {
|
|
7439
7439
|
const r5 = t5[2];
|
|
7440
|
-
if (void 0 !== r5 && "string" != typeof r5) throw new Error(`${msg(
|
|
7440
|
+
if (void 0 !== r5 && "string" != typeof r5) throw new Error(`${msg(access3(e4, 2))} must be a string, or undefined`);
|
|
7441
7441
|
}
|
|
7442
7442
|
} else assertPluginTarget(e4, t5);
|
|
7443
7443
|
return t5;
|
|
7444
|
-
})(
|
|
7444
|
+
})(access3(e3, r4), t4));
|
|
7445
7445
|
return r3;
|
|
7446
7446
|
}, t2.assertRootMode = function(e3, t3) {
|
|
7447
7447
|
if (void 0 !== t3 && "root" !== t3 && "upward" !== t3 && "upward-optional" !== t3) throw new Error(`${msg(e3)} must be a "root", "upward", "upward-optional" or undefined`);
|
|
@@ -7458,10 +7458,10 @@ ${yield* s.optionsAndDescriptors(e3.content)}`;
|
|
|
7458
7458
|
}, t2.assertTargets = function(e3, t3) {
|
|
7459
7459
|
if ((0, _helperCompilationTargets().isBrowsersQueryValid)(t3)) return t3;
|
|
7460
7460
|
if ("object" != typeof t3 || !t3 || Array.isArray(t3)) throw new Error(`${msg(e3)} must be a string, an array of strings or an object`);
|
|
7461
|
-
const r3 =
|
|
7461
|
+
const r3 = access3(e3, "browsers"), n2 = access3(e3, "esmodules");
|
|
7462
7462
|
assertBrowsersList(r3, t3.browsers), assertBoolean(n2, t3.esmodules);
|
|
7463
7463
|
for (const r4 of Object.keys(t3)) {
|
|
7464
|
-
const n3 = t3[r4], s =
|
|
7464
|
+
const n3 = t3[r4], s = access3(e3, r4);
|
|
7465
7465
|
if ("esmodules" === r4) assertBoolean(s, n3);
|
|
7466
7466
|
else if ("browsers" === r4) assertBrowsersList(s, n3);
|
|
7467
7467
|
else {
|
|
@@ -7491,7 +7491,7 @@ ${yield* s.optionsAndDescriptors(e3.content)}`;
|
|
|
7491
7491
|
throw new Error(`Assertion failure: Unknown type ${e3.type}`);
|
|
7492
7492
|
}
|
|
7493
7493
|
}
|
|
7494
|
-
function
|
|
7494
|
+
function access3(e3, t3) {
|
|
7495
7495
|
return { type: "access", name: t3, parent: e3 };
|
|
7496
7496
|
}
|
|
7497
7497
|
function assertBoolean(e3, t3) {
|
|
@@ -39080,12 +39080,12 @@ var require_parser = __commonJS({
|
|
|
39080
39080
|
}
|
|
39081
39081
|
return prev.splice(i, prev.length);
|
|
39082
39082
|
}
|
|
39083
|
-
function arrayPushArray(
|
|
39083
|
+
function arrayPushArray(target2, source) {
|
|
39084
39084
|
if (source.length < 1e5)
|
|
39085
|
-
Array.prototype.push.apply(
|
|
39085
|
+
Array.prototype.push.apply(target2, source);
|
|
39086
39086
|
else
|
|
39087
39087
|
for (let i = 0; i < source.length; ++i)
|
|
39088
|
-
|
|
39088
|
+
target2.push(source[i]);
|
|
39089
39089
|
}
|
|
39090
39090
|
function fixFlowSeqItems(fc) {
|
|
39091
39091
|
if (fc.start.type === "flow-seq-start") {
|
|
@@ -40034,14 +40034,15 @@ var require_dist = __commonJS({
|
|
|
40034
40034
|
|
|
40035
40035
|
// src/config/load.ts
|
|
40036
40036
|
import { hostname as readHostname, homedir, platform as readPlatform } from "os";
|
|
40037
|
-
import { dirname, isAbsolute as isAbsolute2, resolve as
|
|
40038
|
-
import { access } from "fs/promises";
|
|
40037
|
+
import { dirname, isAbsolute as isAbsolute2, resolve as resolve4 } from "path";
|
|
40038
|
+
import { access as access2 } from "fs/promises";
|
|
40039
40039
|
|
|
40040
40040
|
// src/config/identity.ts
|
|
40041
40041
|
import { createHash } from "crypto";
|
|
40042
40042
|
function resourceId(resource) {
|
|
40043
40043
|
switch (resource.kind) {
|
|
40044
40044
|
case "package":
|
|
40045
|
+
if (resource.manager === "flatpak") return `package:flatpak:${resource.flatpak?.scope ?? "user"}:${resource.name}:${resource.flatpak?.branch ?? "stable"}`;
|
|
40045
40046
|
return `package:${resource.manager}:${resource.name}`;
|
|
40046
40047
|
case "symlink":
|
|
40047
40048
|
return `file:${resource.target}`;
|
|
@@ -40096,6 +40097,20 @@ async function addPathToHash(hash, path, relativePath) {
|
|
|
40096
40097
|
}
|
|
40097
40098
|
}
|
|
40098
40099
|
|
|
40100
|
+
// src/config/package-options.ts
|
|
40101
|
+
function isFlatpakOptions(value2) {
|
|
40102
|
+
if (typeof value2 !== "object" || value2 === null || Array.isArray(value2)) return false;
|
|
40103
|
+
const options = value2;
|
|
40104
|
+
return Object.keys(options).every((key) => ["scope", "remote", "branch"].includes(key)) && (options.scope === void 0 || options.scope === "user" || options.scope === "system") && [options.remote, options.branch].every((field) => field === void 0 || typeof field === "string" && /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(field));
|
|
40105
|
+
}
|
|
40106
|
+
function isPackageName(manager, name) {
|
|
40107
|
+
if (typeof name !== "string" || name.length === 0) return false;
|
|
40108
|
+
if (manager === "mas") return /^[1-9]\d*$/.test(name);
|
|
40109
|
+
if (manager === "flatpak") return /^[A-Za-z_][A-Za-z0-9_-]*(?:\.[A-Za-z_][A-Za-z0-9_-]*){2,}$/.test(name);
|
|
40110
|
+
if (manager === "pacman") return /^[A-Za-z0-9@_+][A-Za-z0-9@._+-]*$/.test(name);
|
|
40111
|
+
return true;
|
|
40112
|
+
}
|
|
40113
|
+
|
|
40099
40114
|
// src/config/validation.ts
|
|
40100
40115
|
function validateResource(value2) {
|
|
40101
40116
|
if (typeof value2 !== "object" || value2 === null || Array.isArray(value2)) {
|
|
@@ -40104,7 +40119,7 @@ function validateResource(value2) {
|
|
|
40104
40119
|
const candidate = value2;
|
|
40105
40120
|
switch (candidate.kind) {
|
|
40106
40121
|
case "package":
|
|
40107
|
-
if (!["mise", "brew", "brew-cask", "apt", "system"].includes(String(candidate.manager)) ||
|
|
40122
|
+
if (!["mise", "brew", "brew-cask", "apt", "dnf", "yum", "pacman", "flatpak", "mas", "system"].includes(String(candidate.manager)) || !isPackageName(candidate.manager, candidate.name) || candidate.flatpak !== void 0 && (!["flatpak", "system"].includes(String(candidate.manager)) || !isFlatpakOptions(candidate.flatpak)) || candidate.version !== void 0 && typeof candidate.version !== "string" || !isBrewCaskUpgradeOptions(candidate.upgrade)) {
|
|
40108
40123
|
throw new Error("Invalid package resource");
|
|
40109
40124
|
}
|
|
40110
40125
|
return;
|
|
@@ -40191,11 +40206,11 @@ function resolveTasks(definition, context) {
|
|
|
40191
40206
|
const cwd = value2.cwd?.replace(/^~(?=\/|$)/, context.home) ?? context.configDir;
|
|
40192
40207
|
tasks[name] = { ...value2, cwd: isAbsolute(cwd) ? cwd : resolve2(context.configDir, cwd) };
|
|
40193
40208
|
}
|
|
40194
|
-
for (const [name,
|
|
40209
|
+
for (const [name, target2] of Object.entries(definition.aliases ?? {})) {
|
|
40195
40210
|
validateName(name);
|
|
40196
40211
|
if (Object.hasOwn(tasks, name)) throw new Error(`Task and alias share a name: ${name}`);
|
|
40197
|
-
if (typeof
|
|
40198
|
-
aliases[name] =
|
|
40212
|
+
if (typeof target2 !== "string") throw new Error(`Invalid alias target: ${name}`);
|
|
40213
|
+
aliases[name] = target2;
|
|
40199
40214
|
}
|
|
40200
40215
|
for (const name of Object.keys(aliases)) resolveTaskName(name, tasks, aliases);
|
|
40201
40216
|
return { tasks, aliases };
|
|
@@ -40206,9 +40221,9 @@ function resolveTaskName(name, tasks, aliases) {
|
|
|
40206
40221
|
while (Object.hasOwn(aliases, current)) {
|
|
40207
40222
|
if (visited.has(current)) throw new Error(`Task alias cycle: ${[...visited, current].join(" -> ")}`);
|
|
40208
40223
|
visited.add(current);
|
|
40209
|
-
const
|
|
40210
|
-
if (
|
|
40211
|
-
current =
|
|
40224
|
+
const target2 = aliases[current];
|
|
40225
|
+
if (target2 === void 0) throw new Error(`Invalid alias: ${current}`);
|
|
40226
|
+
current = target2;
|
|
40212
40227
|
}
|
|
40213
40228
|
if (!Object.hasOwn(tasks, current)) throw new Error(`Unknown task: ${current}`);
|
|
40214
40229
|
return current;
|
|
@@ -40217,13 +40232,32 @@ function validateName(name) {
|
|
|
40217
40232
|
if (!/^[a-zA-Z0-9][a-zA-Z0-9:_-]*$/.test(name) || name === "help" || name === "init") throw new Error(`Invalid or reserved task name: ${name}`);
|
|
40218
40233
|
}
|
|
40219
40234
|
|
|
40235
|
+
// src/config/system-manager.ts
|
|
40236
|
+
import { access, stat } from "fs/promises";
|
|
40237
|
+
import { constants } from "fs";
|
|
40238
|
+
import { delimiter, resolve as resolve3 } from "path";
|
|
40239
|
+
async function detectLinuxManager(searchPath = process.env.PATH ?? "") {
|
|
40240
|
+
for (const [manager, command] of [["apt", "apt-get"], ["dnf", "dnf"], ["yum", "yum"], ["pacman", "pacman"]]) {
|
|
40241
|
+
for (const directory of searchPath.split(delimiter).filter(Boolean)) {
|
|
40242
|
+
const path = resolve3(directory, command);
|
|
40243
|
+
try {
|
|
40244
|
+
await access(path, constants.X_OK);
|
|
40245
|
+
if ((await stat(path)).isFile()) return manager;
|
|
40246
|
+
} catch (error) {
|
|
40247
|
+
if (!(error instanceof Error && "code" in error && ["ENOENT", "ENOTDIR", "EACCES"].includes(String(error.code)))) throw error;
|
|
40248
|
+
}
|
|
40249
|
+
}
|
|
40250
|
+
}
|
|
40251
|
+
throw new Error("No supported Linux package manager found on PATH (apt-get, dnf, yum, pacman). Install one or set managers.linux explicitly.");
|
|
40252
|
+
}
|
|
40253
|
+
|
|
40220
40254
|
// src/config/load.ts
|
|
40221
40255
|
var DEFAULT_CONFIG_FILE = "workstation.config.ts";
|
|
40222
40256
|
async function findConfig(explicit) {
|
|
40223
|
-
if (explicit) return
|
|
40224
|
-
const path =
|
|
40257
|
+
if (explicit) return resolve4(explicit);
|
|
40258
|
+
const path = resolve4(DEFAULT_CONFIG_FILE);
|
|
40225
40259
|
try {
|
|
40226
|
-
await
|
|
40260
|
+
await access2(path);
|
|
40227
40261
|
return path;
|
|
40228
40262
|
} catch {
|
|
40229
40263
|
throw new Error(`No configuration found (${DEFAULT_CONFIG_FILE})`);
|
|
@@ -40269,7 +40303,11 @@ async function loadConfig(configPath, machineOverride) {
|
|
|
40269
40303
|
}
|
|
40270
40304
|
async function resolveResource(resource, definition, context) {
|
|
40271
40305
|
if (resource.kind === "package") {
|
|
40272
|
-
const manager = resolveManager(resource.manager, definition, context);
|
|
40306
|
+
const manager = await resolveManager(resource.manager, definition, context);
|
|
40307
|
+
if (!isPackageName(manager, resource.name)) throw new Error(`Invalid ${manager} package ID: ${resource.name}`);
|
|
40308
|
+
if ((manager === "pacman" || manager === "flatpak") && context.platform !== "linux") throw new Error(`${manager} requires Linux`);
|
|
40309
|
+
if (manager === "mas" && context.platform !== "darwin") throw new Error("mas requires macOS");
|
|
40310
|
+
if (resource.flatpak !== void 0 && manager !== "flatpak") throw new Error(`Flatpak options require the flatpak backend (${resource.name})`);
|
|
40273
40311
|
if (resource.upgrade !== void 0 && manager !== "brew-cask") {
|
|
40274
40312
|
throw new Error(`Upgrade options are only supported for Homebrew casks (${resource.name})`);
|
|
40275
40313
|
}
|
|
@@ -40279,7 +40317,11 @@ async function resolveResource(resource, definition, context) {
|
|
|
40279
40317
|
if (resource.version !== void 0) {
|
|
40280
40318
|
throw new Error(`${manager} package ${resource.name} cannot declare a version`);
|
|
40281
40319
|
}
|
|
40282
|
-
return { ...resource, manager
|
|
40320
|
+
return { ...resource, manager, ...manager === "flatpak" ? { flatpak: {
|
|
40321
|
+
scope: resource.flatpak?.scope ?? "user",
|
|
40322
|
+
remote: resource.flatpak?.remote ?? "flathub",
|
|
40323
|
+
branch: resource.flatpak?.branch ?? "stable"
|
|
40324
|
+
} } : {} };
|
|
40283
40325
|
}
|
|
40284
40326
|
if (resource.kind === "symlink") {
|
|
40285
40327
|
return {
|
|
@@ -40303,11 +40345,11 @@ async function resolveResource(resource, definition, context) {
|
|
|
40303
40345
|
}
|
|
40304
40346
|
if (resource.kind === "custom-tool") {
|
|
40305
40347
|
const source = expandPath(resource.source, context, true);
|
|
40306
|
-
const
|
|
40348
|
+
const target2 = expandPath(resource.target, context, false);
|
|
40307
40349
|
return {
|
|
40308
40350
|
...resource,
|
|
40309
40351
|
source,
|
|
40310
|
-
target,
|
|
40352
|
+
target: target2,
|
|
40311
40353
|
sourceHash: await hashSource(source),
|
|
40312
40354
|
build: {
|
|
40313
40355
|
...resource.build,
|
|
@@ -40325,14 +40367,16 @@ async function resolveResource(resource, definition, context) {
|
|
|
40325
40367
|
...resource.stderrPath ? { stderrPath: expandPath(resource.stderrPath, context, false) } : {}
|
|
40326
40368
|
};
|
|
40327
40369
|
}
|
|
40328
|
-
function resolveManager(manager, definition, context) {
|
|
40370
|
+
async function resolveManager(manager, definition, context) {
|
|
40329
40371
|
if (manager !== "system") return manager;
|
|
40330
|
-
|
|
40372
|
+
const configured = definition.managers?.[context.platform];
|
|
40373
|
+
if (configured !== void 0) return configured;
|
|
40374
|
+
return context.platform === "darwin" ? "brew" : await detectLinuxManager();
|
|
40331
40375
|
}
|
|
40332
40376
|
function expandPath(path, context, relativeToConfig) {
|
|
40333
40377
|
const expanded = path === "~" ? context.home : path.replace(/^~\//, `${context.home}/`);
|
|
40334
|
-
if (isAbsolute2(expanded)) return
|
|
40335
|
-
return
|
|
40378
|
+
if (isAbsolute2(expanded)) return resolve4(expanded);
|
|
40379
|
+
return resolve4(relativeToConfig ? context.configDir : context.home, expanded);
|
|
40336
40380
|
}
|
|
40337
40381
|
function flatten(input) {
|
|
40338
40382
|
if (!input) return [];
|
|
@@ -40370,7 +40414,7 @@ function mergeDefinitions(definitions) {
|
|
|
40370
40414
|
|
|
40371
40415
|
// src/persistence/manifest.ts
|
|
40372
40416
|
import { mkdir, readFile as readFile2, rename, writeFile } from "fs/promises";
|
|
40373
|
-
import { dirname as dirname2, resolve as
|
|
40417
|
+
import { dirname as dirname2, resolve as resolve5 } from "path";
|
|
40374
40418
|
|
|
40375
40419
|
// node_modules/.pnpm/smol-toml@1.8.0/node_modules/smol-toml/dist/date.js
|
|
40376
40420
|
var DATE_TIME_RE = /^(\d{4}-\d{2}-\d{2})?[T ]?(?:(\d{2}):\d{2}(?::\d{2}(?:\.\d+)?)?)?(Z|[-+]\d{2}:\d{2})?$/i;
|
|
@@ -41169,7 +41213,7 @@ function stringify(obj, { maxDepth = 1e3, numbersAsFloat = false } = {}) {
|
|
|
41169
41213
|
// src/persistence/manifest.ts
|
|
41170
41214
|
var MANIFEST_VERSION = 1;
|
|
41171
41215
|
function manifestPath(config) {
|
|
41172
|
-
return
|
|
41216
|
+
return resolve5(dirname2(config.stateFile), "config.toml");
|
|
41173
41217
|
}
|
|
41174
41218
|
async function writeManifest(path, config) {
|
|
41175
41219
|
const document2 = {
|
|
@@ -41211,6 +41255,11 @@ function toTomlResource(resource) {
|
|
|
41211
41255
|
name: resource.name,
|
|
41212
41256
|
...resource.version ? { version: resource.version } : {},
|
|
41213
41257
|
...resource.lockedVersion ? { locked_version: resource.lockedVersion } : {},
|
|
41258
|
+
...resource.flatpak ? { flatpak: {
|
|
41259
|
+
...resource.flatpak.scope ? { scope: resource.flatpak.scope } : {},
|
|
41260
|
+
...resource.flatpak.remote ? { remote: resource.flatpak.remote } : {},
|
|
41261
|
+
...resource.flatpak.branch ? { branch: resource.flatpak.branch } : {}
|
|
41262
|
+
} } : {},
|
|
41214
41263
|
...resource.upgrade ? {
|
|
41215
41264
|
upgrade: {
|
|
41216
41265
|
...resource.upgrade.greedy !== void 0 ? { greedy: resource.upgrade.greedy } : {},
|
|
@@ -41361,6 +41410,9 @@ function parsePackage(value2) {
|
|
|
41361
41410
|
if (!isPackageManager(manager)) {
|
|
41362
41411
|
throw new Error(`Invalid manifest package manager: ${manager}`);
|
|
41363
41412
|
}
|
|
41413
|
+
if (!isPackageName(manager, value2.name)) throw new Error(`Invalid ${manager} package ID`);
|
|
41414
|
+
if (value2.flatpak !== void 0 && (manager !== "flatpak" || !isFlatpakOptions(value2.flatpak))) throw new Error("Invalid Flatpak options");
|
|
41415
|
+
if (manager === "mas" && value2.locked_version !== void 0) throw new Error("mas does not support version pins");
|
|
41364
41416
|
const upgrade = value2.upgrade === void 0 ? void 0 : requireTable(value2.upgrade, "package.upgrade");
|
|
41365
41417
|
if (value2.version !== void 0 && manager !== "mise") {
|
|
41366
41418
|
throw new Error(`Manifest ${manager} package cannot declare a version`);
|
|
@@ -41372,6 +41424,7 @@ function parsePackage(value2) {
|
|
|
41372
41424
|
kind: "package",
|
|
41373
41425
|
manager,
|
|
41374
41426
|
name: requireString(value2.name, "package.name"),
|
|
41427
|
+
...isFlatpakOptions(value2.flatpak) ? { flatpak: value2.flatpak } : {},
|
|
41375
41428
|
...value2.version !== void 0 ? { version: requireString(value2.version, "package.version") } : {},
|
|
41376
41429
|
...value2.locked_version !== void 0 ? { lockedVersion: requireString(value2.locked_version, "package.locked_version") } : {},
|
|
41377
41430
|
...upgrade ? {
|
|
@@ -41391,13 +41444,13 @@ function parseSymlink(value2) {
|
|
|
41391
41444
|
}
|
|
41392
41445
|
function parseLaunchAgent(value2) {
|
|
41393
41446
|
const args = value2.args === void 0 ? void 0 : requireStringArray(value2.args, "launch-agent.args");
|
|
41394
|
-
const
|
|
41447
|
+
const environment3 = value2.environment === void 0 ? void 0 : requireStringTable(value2.environment, "launch-agent.environment");
|
|
41395
41448
|
return {
|
|
41396
41449
|
kind: "launch-agent",
|
|
41397
41450
|
label: requireString(value2.label, "launch-agent.label"),
|
|
41398
41451
|
program: requireString(value2.program, "launch-agent.program"),
|
|
41399
41452
|
...args ? { args } : {},
|
|
41400
|
-
...
|
|
41453
|
+
...environment3 ? { environment: environment3 } : {},
|
|
41401
41454
|
...value2.run_at_load !== void 0 ? { runAtLoad: requireBoolean(value2.run_at_load, "launch-agent.run_at_load") } : {},
|
|
41402
41455
|
...value2.keep_alive !== void 0 ? { keepAlive: requireBoolean(value2.keep_alive, "launch-agent.keep_alive") } : {},
|
|
41403
41456
|
...value2.stdout_path !== void 0 ? { stdoutPath: requireString(value2.stdout_path, "launch-agent.stdout_path") } : {},
|
|
@@ -41434,7 +41487,7 @@ function requireStringTable(value2, field) {
|
|
|
41434
41487
|
return result;
|
|
41435
41488
|
}
|
|
41436
41489
|
function isPackageManager(value2) {
|
|
41437
|
-
return value2 === "mise" || value2 === "brew" || value2 === "brew-cask" || value2 === "apt";
|
|
41490
|
+
return value2 === "mise" || value2 === "brew" || value2 === "brew-cask" || value2 === "apt" || value2 === "dnf" || value2 === "yum" || value2 === "pacman" || value2 === "flatpak" || value2 === "mas";
|
|
41438
41491
|
}
|
|
41439
41492
|
function isConfigValue2(value2) {
|
|
41440
41493
|
if (value2 === null || typeof value2 === "string" || typeof value2 === "boolean" || typeof value2 === "number" && Number.isFinite(value2)) {
|
|
@@ -41445,7 +41498,7 @@ function isConfigValue2(value2) {
|
|
|
41445
41498
|
}
|
|
41446
41499
|
|
|
41447
41500
|
// src/reconciliation/plan.ts
|
|
41448
|
-
import { lstat as
|
|
41501
|
+
import { lstat as lstat6 } from "fs/promises";
|
|
41449
41502
|
|
|
41450
41503
|
// src/resources/generated-file.ts
|
|
41451
41504
|
import { lstat as lstat2, mkdir as mkdir2, readFile as readFile3, readlink as readlink2, rename as rename3, symlink, unlink } from "fs/promises";
|
|
@@ -41637,29 +41690,325 @@ function isRecord2(value2) {
|
|
|
41637
41690
|
return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
|
|
41638
41691
|
}
|
|
41639
41692
|
|
|
41640
|
-
// src/resources/
|
|
41641
|
-
|
|
41642
|
-
|
|
41643
|
-
|
|
41644
|
-
|
|
41645
|
-
|
|
41646
|
-
|
|
41647
|
-
|
|
41693
|
+
// src/resources/mise-cask.ts
|
|
41694
|
+
import { lstat as lstat3, mkdtemp, readFile as readFile4, readdir as readdir2, realpath, rename as rename4, readlink as readlink3, symlink as symlink2 } from "fs/promises";
|
|
41695
|
+
import { basename, dirname as dirname4, isAbsolute as isAbsolute3, join, resolve as resolve6 } from "path";
|
|
41696
|
+
async function inspectMiseCask(resource, runner) {
|
|
41697
|
+
const result = await requireSuccess(runner, "brew", ["--caskroom", resource.name]);
|
|
41698
|
+
const path = result.stdout.trim();
|
|
41699
|
+
if (!isAbsolute3(path) || basename(path) !== resource.name.split("/").at(-1) || dirname4(path) === path) {
|
|
41700
|
+
throw new Error(`Homebrew returned an invalid cask directory for ${resource.name}`);
|
|
41701
|
+
}
|
|
41702
|
+
try {
|
|
41703
|
+
const stat2 = await lstat3(path);
|
|
41704
|
+
if (!stat2.isDirectory() || stat2.isSymbolicLink()) return void 0;
|
|
41705
|
+
const entries = await readdir2(path, { withFileTypes: true });
|
|
41706
|
+
if (entries.length !== 1 || !entries[0]?.isDirectory()) return void 0;
|
|
41707
|
+
const version = entries[0].name;
|
|
41708
|
+
const receiptPath = join(path, version, ".mise-cask.toml");
|
|
41709
|
+
if (!(await lstat3(receiptPath)).isFile()) return void 0;
|
|
41710
|
+
const receipt = parse(await readFile4(receiptPath, "utf8"));
|
|
41711
|
+
if (receipt.schema_version !== 3 || receipt.version !== version || !Array.isArray(receipt.apps) || !Array.isArray(receipt.fonts) || !Array.isArray(receipt.binaries) || receipt.apps.length + receipt.fonts.length + receipt.binaries.length === 0 || !["flight_directories", "generic", "pkg_ids"].every((key) => Array.isArray(receipt[key]) && receipt[key].length === 0)) return void 0;
|
|
41712
|
+
const apps = [];
|
|
41713
|
+
for (const app of receipt.apps) {
|
|
41714
|
+
if (typeof app !== "string" || !isAbsolute3(app) || !app.endsWith(".app") || resolve6(app) !== app) return void 0;
|
|
41715
|
+
const staged = join(path, version, basename(app));
|
|
41716
|
+
if (!(await lstat3(staged)).isSymbolicLink() || await realpath(staged) !== await realpath(app) || !(await lstat3(app)).isDirectory() || app.startsWith(`${dirname4(path)}/`)) return void 0;
|
|
41717
|
+
apps.push(app);
|
|
41718
|
+
}
|
|
41719
|
+
const fonts = [];
|
|
41720
|
+
for (const font of receipt.fonts) {
|
|
41721
|
+
if (typeof font !== "string" || !isAbsolute3(font) || resolve6(font) !== font || !/\.(ttf|otf|ttc|dfont)$/i.test(font) || !dirname4(font).endsWith("/Library/Fonts") || font.startsWith(`${dirname4(path)}/`) || !(await lstat3(font)).isFile()) return void 0;
|
|
41722
|
+
const staged = join(path, version, basename(font));
|
|
41723
|
+
if (!(await lstat3(staged)).isFile() || !(await readFile4(staged)).equals(await readFile4(font))) return void 0;
|
|
41724
|
+
fonts.push(font);
|
|
41648
41725
|
}
|
|
41649
|
-
|
|
41650
|
-
const
|
|
41651
|
-
if (
|
|
41652
|
-
|
|
41653
|
-
|
|
41654
|
-
await
|
|
41726
|
+
const links = [];
|
|
41727
|
+
const linkRoots = [await realpath(join(path, version)), ...await Promise.all(apps.map((app) => realpath(app)))];
|
|
41728
|
+
if (!Array.isArray(receipt.completions)) return void 0;
|
|
41729
|
+
for (const completion of [...receipt.binaries, ...receipt.completions]) {
|
|
41730
|
+
if (typeof completion !== "string" || !isAbsolute3(completion) || resolve6(completion) !== completion || completion.startsWith(`${path}/`) || !(await lstat3(completion)).isSymbolicLink()) return void 0;
|
|
41731
|
+
const target2 = await readlink3(completion);
|
|
41732
|
+
const destination = await realpath(completion);
|
|
41733
|
+
if (!resolve6(dirname4(completion), target2).startsWith(`${path}/${version}/`) || !linkRoots.some((root) => destination.startsWith(`${root}/`))) return void 0;
|
|
41734
|
+
links.push({ path: completion, target: target2 });
|
|
41655
41735
|
}
|
|
41736
|
+
return { path, version, apps: [...new Set(apps)], fonts: [...new Set(fonts)], links };
|
|
41737
|
+
} catch (error) {
|
|
41738
|
+
if (isMissingFile(error)) return void 0;
|
|
41739
|
+
throw error;
|
|
41740
|
+
}
|
|
41741
|
+
}
|
|
41742
|
+
async function migrateMiseCask(resource, mise, runner) {
|
|
41743
|
+
const backup = await mkdtemp(join(dirname4(dirname4(mise.path)), `.workstation-mise-${basename(mise.path)}-`));
|
|
41744
|
+
runner.report?.(`Migrating ${resource.name} from mise to Homebrew; backups: ${backup}`);
|
|
41745
|
+
for (const [index, app] of mise.apps.entries()) {
|
|
41746
|
+
await requireSuccess(runner, "/usr/bin/ditto", [app, join(backup, `app-${index}.app`)], { streamOutput: true });
|
|
41747
|
+
}
|
|
41748
|
+
for (const [index, font] of mise.fonts.entries()) {
|
|
41749
|
+
await requireSuccess(runner, "/usr/bin/ditto", [font, join(backup, `font-${index}`)], { streamOutput: true });
|
|
41750
|
+
}
|
|
41751
|
+
await rename4(mise.path, join(backup, "original-cask"));
|
|
41752
|
+
try {
|
|
41753
|
+
await requireSuccess(runner, "brew", [
|
|
41754
|
+
"install",
|
|
41755
|
+
"--cask",
|
|
41756
|
+
resource.lockedVersion === mise.version ? "--adopt" : "--force",
|
|
41757
|
+
resource.name
|
|
41758
|
+
], {
|
|
41759
|
+
streamOutput: true,
|
|
41760
|
+
environment: { HOMEBREW_NO_AUTO_UPDATE: "1" }
|
|
41761
|
+
});
|
|
41762
|
+
const installed = await readInstalledBrewVersion(resource, runner);
|
|
41763
|
+
if (!installed || resource.lockedVersion !== void 0 && installed !== resource.lockedVersion) {
|
|
41764
|
+
throw new Error(`Homebrew migration expected ${resource.lockedVersion ?? "an installed version"}; reported ${installed ?? "no installed version"}`);
|
|
41765
|
+
}
|
|
41766
|
+
} catch (error) {
|
|
41767
|
+
try {
|
|
41768
|
+
try {
|
|
41769
|
+
await rename4(mise.path, join(backup, "failed-cask"));
|
|
41770
|
+
} catch (moveError) {
|
|
41771
|
+
if (!isMissingFile(moveError)) throw moveError;
|
|
41772
|
+
}
|
|
41773
|
+
await rename4(join(backup, "original-cask"), mise.path);
|
|
41774
|
+
for (const [index, link] of mise.links.entries()) {
|
|
41775
|
+
try {
|
|
41776
|
+
const stat2 = await lstat3(link.path);
|
|
41777
|
+
if (stat2.isSymbolicLink()) {
|
|
41778
|
+
const current = await readlink3(link.path);
|
|
41779
|
+
const destination = resolve6(dirname4(link.path), current);
|
|
41780
|
+
if (current !== link.target && [mise.path, ...mise.apps].some((root) => destination.startsWith(`${root}/`))) {
|
|
41781
|
+
await rename4(link.path, join(backup, `failed-link-${index}`));
|
|
41782
|
+
await symlink2(link.target, link.path);
|
|
41783
|
+
}
|
|
41784
|
+
}
|
|
41785
|
+
} catch (linkError) {
|
|
41786
|
+
if (!isMissingFile(linkError)) throw linkError;
|
|
41787
|
+
await symlink2(link.target, link.path);
|
|
41788
|
+
}
|
|
41789
|
+
}
|
|
41790
|
+
for (const [index, app] of mise.apps.entries()) {
|
|
41791
|
+
try {
|
|
41792
|
+
await lstat3(app);
|
|
41793
|
+
} catch (appError) {
|
|
41794
|
+
if (!isMissingFile(appError)) throw appError;
|
|
41795
|
+
await requireSuccess(runner, "/usr/bin/ditto", [join(backup, `app-${index}.app`), app], { streamOutput: true });
|
|
41796
|
+
}
|
|
41797
|
+
}
|
|
41798
|
+
for (const [index, font] of mise.fonts.entries()) {
|
|
41799
|
+
try {
|
|
41800
|
+
await lstat3(font);
|
|
41801
|
+
} catch (fontError) {
|
|
41802
|
+
if (!isMissingFile(fontError)) throw fontError;
|
|
41803
|
+
await requireSuccess(runner, "/usr/bin/ditto", [join(backup, `font-${index}`), font], { streamOutput: true });
|
|
41804
|
+
}
|
|
41805
|
+
}
|
|
41806
|
+
} catch (restoreError) {
|
|
41807
|
+
throw new AggregateError([error, restoreError], `Migration and recovery failed for ${resource.name}. Backups: ${backup}`, { cause: restoreError });
|
|
41808
|
+
}
|
|
41809
|
+
throw new Error(`Migration failed for ${resource.name}: ${error instanceof Error ? error.message : String(error)}. Mise staging restored; artifact backups: ${backup}`, { cause: error });
|
|
41810
|
+
}
|
|
41811
|
+
runner.report?.(`Migrated ${resource.name} from mise to Homebrew. Backups: ${backup}. Remove brew-cask:${resource.name} from your mise configuration; do not run mise uninstall for this cask.`);
|
|
41812
|
+
}
|
|
41813
|
+
|
|
41814
|
+
// src/resources/package-command.ts
|
|
41815
|
+
async function runPackageCommand(mutation, runner) {
|
|
41816
|
+
if (!mutation) return;
|
|
41817
|
+
await requireSuccess(runner, mutation.command, [...mutation.args, ...mutation.targets], { ...mutation.options, streamOutput: true });
|
|
41818
|
+
}
|
|
41819
|
+
function packageCommandKey(mutation) {
|
|
41820
|
+
return mutation.single ? void 0 : JSON.stringify([mutation.command, mutation.args, mutation.options]);
|
|
41821
|
+
}
|
|
41822
|
+
|
|
41823
|
+
// src/resources/rpm.ts
|
|
41824
|
+
var environment = { LC_ALL: "C" };
|
|
41825
|
+
var versionPattern = /^\d+:[A-Za-z0-9._+~^]+-[A-Za-z0-9._+~^]+\.[A-Za-z0-9_]+$/;
|
|
41826
|
+
async function resolveRpmVersion(resource, runner) {
|
|
41827
|
+
const architecture = (await requireSuccess(runner, "rpm", ["--eval", "%{_arch}"], { environment })).stdout.trim();
|
|
41828
|
+
if (!/^[A-Za-z0-9_]+$/.test(architecture)) throw new Error("RPM did not report a valid native architecture");
|
|
41829
|
+
const result = await requireSuccess(runner, resource.manager, ["--quiet", "--color=never", "list", resource.name], { environment });
|
|
41830
|
+
const candidates = [];
|
|
41831
|
+
let available = false;
|
|
41832
|
+
const lines = result.stdout.replace(/^(\S+\.\S+)\n[ \t]+(?=\S+\s+\S+)/gm, "$1 ").split("\n");
|
|
41833
|
+
for (const line of lines) {
|
|
41834
|
+
if (/^Available packages\s*$/i.test(line.trim())) {
|
|
41835
|
+
available = true;
|
|
41836
|
+
continue;
|
|
41837
|
+
}
|
|
41838
|
+
if (/^Installed packages\s*$/i.test(line.trim())) {
|
|
41839
|
+
available = false;
|
|
41840
|
+
continue;
|
|
41841
|
+
}
|
|
41842
|
+
const [nameArch, evr, repository] = line.trim().split(/\s+/);
|
|
41843
|
+
if (!nameArch || !evr || !repository) continue;
|
|
41844
|
+
const dot = nameArch.lastIndexOf(".");
|
|
41845
|
+
const name = nameArch.slice(0, dot);
|
|
41846
|
+
const arch = nameArch.slice(dot + 1);
|
|
41847
|
+
if (nameArch !== resource.name && (name !== resource.name || arch !== architecture && arch !== "noarch")) continue;
|
|
41848
|
+
const version = `${evr.includes(":") ? evr : `0:${evr}`}.${arch}`;
|
|
41849
|
+
if (!versionPattern.test(version)) throw new Error(`${resource.manager} reported an invalid RPM version for ${resource.name}: ${version}`);
|
|
41850
|
+
candidates.push({ version, native: arch === architecture, available });
|
|
41851
|
+
}
|
|
41852
|
+
candidates.sort((left, right) => Number(right.available) - Number(left.available) || Number(right.native) - Number(left.native));
|
|
41853
|
+
const candidate = candidates[0];
|
|
41854
|
+
if (!candidate) throw new Error(`${resource.manager} has no installation candidate for ${resource.name}`);
|
|
41855
|
+
return candidate.version;
|
|
41856
|
+
}
|
|
41857
|
+
async function inspectRpmPackage(resource, runner) {
|
|
41858
|
+
const result = await runner.run("rpm", ["-q", "--qf", "%{EPOCHNUM}:%{VERSION}-%{RELEASE}.%{ARCH}\n", resource.name], { environment });
|
|
41859
|
+
if (result.exitCode !== 0) {
|
|
41860
|
+
if (result.exitCode === 1 && result.stdout.trim() === `package ${resource.name} is not installed` && !result.stderr.trim()) {
|
|
41861
|
+
return { present: false, matches: false };
|
|
41862
|
+
}
|
|
41863
|
+
throw new Error(`rpm query failed for ${resource.name} (${result.exitCode}): ${(result.stderr || result.stdout).trim()}`);
|
|
41864
|
+
}
|
|
41865
|
+
const versions = result.stdout.trim().split("\n");
|
|
41866
|
+
if (!versions.every((version) => versionPattern.test(version))) throw new Error(`RPM returned invalid installed versions for ${resource.name}`);
|
|
41867
|
+
const requestedArch = resource.lockedVersion?.slice(resource.lockedVersion.lastIndexOf(".") + 1);
|
|
41868
|
+
const installedVersion = versions.find((version) => version === resource.lockedVersion) ?? versions.find((version) => requestedArch !== void 0 && version.endsWith(`.${requestedArch}`)) ?? versions[0];
|
|
41869
|
+
if (!installedVersion) throw new Error(`RPM reported no installed version for ${resource.name}`);
|
|
41870
|
+
return { present: true, matches: resource.lockedVersion === void 0 || installedVersion === resource.lockedVersion, installedVersion };
|
|
41871
|
+
}
|
|
41872
|
+
async function prepareRpmPackage(resource, runner, inspection) {
|
|
41873
|
+
const before = inspection ?? await inspectRpmPackage(resource, runner);
|
|
41874
|
+
if (before.matches) return;
|
|
41875
|
+
let operation = "install";
|
|
41876
|
+
let spec = resource.name;
|
|
41877
|
+
if (resource.lockedVersion !== void 0) {
|
|
41878
|
+
const desired = resource.lockedVersion;
|
|
41879
|
+
if (!versionPattern.test(desired)) throw new Error(`Invalid RPM lock version for ${resource.name}: ${desired}`);
|
|
41880
|
+
const arch = desired.slice(desired.lastIndexOf(".") + 1);
|
|
41881
|
+
const name = resource.name.endsWith(`.${arch}`) ? resource.name.slice(0, -arch.length - 1) : resource.name;
|
|
41882
|
+
spec = `${name}-${desired}`;
|
|
41883
|
+
if (before.installedVersion?.endsWith(`.${arch}`)) {
|
|
41884
|
+
const current = before.installedVersion.slice(0, before.installedVersion.lastIndexOf("."));
|
|
41885
|
+
const target2 = desired.slice(0, desired.lastIndexOf("."));
|
|
41886
|
+
const comparison = (await requireSuccess(runner, "rpm", ["--eval", `%{lua:print(rpm.vercmp("${current}", "${target2}"))}`], { environment })).stdout.trim();
|
|
41887
|
+
if (!["-1", "0", "1"].includes(comparison)) throw new Error(`RPM could not compare versions for ${resource.name}`);
|
|
41888
|
+
if (comparison === "1") operation = "downgrade";
|
|
41889
|
+
}
|
|
41890
|
+
}
|
|
41891
|
+
return { command: "sudo", args: [resource.manager, operation, "-y"], targets: [spec] };
|
|
41892
|
+
}
|
|
41893
|
+
|
|
41894
|
+
// src/resources/pacman.ts
|
|
41895
|
+
var environment2 = { LC_ALL: "C" };
|
|
41896
|
+
async function resolvePacmanVersion(resource, runner) {
|
|
41897
|
+
const result = await requireSuccess(runner, "pacman", ["-Sp", "--print-format", "%n %v", resource.name], { environment: environment2 });
|
|
41898
|
+
const versions = result.stdout.trim().split("\n").flatMap((line) => {
|
|
41899
|
+
const [name, version, extra] = line.trim().split(/\s+/);
|
|
41900
|
+
return name === resource.name && version && !extra ? [version] : [];
|
|
41901
|
+
});
|
|
41902
|
+
if (versions.length !== 1 || !versions[0]) throw new Error(`pacman did not report a unique version for ${resource.name}`);
|
|
41903
|
+
return versions[0];
|
|
41904
|
+
}
|
|
41905
|
+
async function inspectPacman(resource, runner) {
|
|
41906
|
+
const result = await runner.run("pacman", ["-Q", resource.name], { environment: environment2 });
|
|
41907
|
+
if (result.exitCode !== 0) {
|
|
41908
|
+
if (result.exitCode === 1 && !result.stdout.trim() && result.stderr.trim() === `error: package '${resource.name}' was not found`) {
|
|
41909
|
+
return { present: false, matches: false };
|
|
41910
|
+
}
|
|
41911
|
+
throw new Error(`pacman query failed for ${resource.name} (${result.exitCode}): ${(result.stderr || result.stdout).trim()}`);
|
|
41912
|
+
}
|
|
41913
|
+
const [name, installedVersion, extra] = result.stdout.trim().split(/\s+/);
|
|
41914
|
+
if (name !== resource.name || !installedVersion || extra) throw new Error(`pacman returned invalid installed information for ${resource.name}`);
|
|
41915
|
+
return { present: true, matches: resource.lockedVersion === void 0 || resource.lockedVersion === installedVersion, installedVersion };
|
|
41916
|
+
}
|
|
41917
|
+
async function preparePacman(resource, runner, inspection) {
|
|
41918
|
+
if ((inspection ?? await inspectPacman(resource, runner)).matches) return;
|
|
41919
|
+
const available = await resolvePacmanVersion(resource, runner);
|
|
41920
|
+
if (resource.lockedVersion !== void 0 && available !== resource.lockedVersion) {
|
|
41921
|
+
throw new Error(`pacman offers ${resource.name} ${available}, but workstation.lock pins ${resource.lockedVersion}. Refresh the pin after your normal full system upgrade; Workstation does not fetch archived packages.`);
|
|
41922
|
+
}
|
|
41923
|
+
return { command: "sudo", args: ["pacman", "-S", "--needed", "--noconfirm"], targets: [resource.name] };
|
|
41924
|
+
}
|
|
41925
|
+
|
|
41926
|
+
// src/resources/flatpak.ts
|
|
41927
|
+
function target(resource) {
|
|
41928
|
+
const scope = resource.flatpak?.scope ?? "user";
|
|
41929
|
+
const branch = resource.flatpak?.branch ?? "stable";
|
|
41930
|
+
return { scope, branch, flag: `--${scope}`, remote: resource.flatpak?.remote ?? "flathub", ref: `app/${resource.name}//${branch}` };
|
|
41931
|
+
}
|
|
41932
|
+
function commit(value2) {
|
|
41933
|
+
const hash = value2.trim();
|
|
41934
|
+
if (!/^[a-f0-9]{64}$/.test(hash)) throw new Error(`Flatpak did not report a full commit ID: ${hash}`);
|
|
41935
|
+
return hash;
|
|
41936
|
+
}
|
|
41937
|
+
async function resolveFlatpakVersion(resource, runner) {
|
|
41938
|
+
const { flag, remote, ref } = target(resource);
|
|
41939
|
+
return commit((await requireSuccess(runner, "flatpak", ["remote-info", flag, "--show-commit", remote, ref])).stdout);
|
|
41940
|
+
}
|
|
41941
|
+
async function inspectFlatpak(resource, runner) {
|
|
41942
|
+
const { flag, remote, ref, branch } = target(resource);
|
|
41943
|
+
const result = await requireSuccess(runner, "flatpak", ["list", flag, "--app", "--columns=application,branch,origin"]);
|
|
41944
|
+
const rows = result.stdout.trim() === "" ? [] : result.stdout.trim().split("\n").map((line) => line.split(" "));
|
|
41945
|
+
if (rows.some((row) => row.length !== 3 || row.some((value2) => value2 === ""))) throw new Error("Flatpak returned an invalid application list");
|
|
41946
|
+
const installed = rows.find(([id, installedBranch]) => id === resource.name && installedBranch === branch);
|
|
41947
|
+
if (!installed) return { present: false, matches: false };
|
|
41948
|
+
if (installed[2] !== remote) return { present: true, matches: false, conflict: `${resource.name} is installed from ${installed[2]}, but the configuration requests ${remote}. Migrate its remote explicitly.` };
|
|
41949
|
+
const installedVersion = commit((await requireSuccess(runner, "flatpak", ["info", flag, "--show-commit", ref])).stdout);
|
|
41950
|
+
return { present: true, matches: resource.lockedVersion === void 0 || installedVersion === resource.lockedVersion, installedVersion };
|
|
41951
|
+
}
|
|
41952
|
+
async function prepareFlatpak(resource, runner, inspection) {
|
|
41953
|
+
const before = inspection ?? await inspectFlatpak(resource, runner);
|
|
41954
|
+
if (before.conflict) throw new Error(before.conflict);
|
|
41955
|
+
if (before.matches) return;
|
|
41956
|
+
const { flag, scope, remote, ref } = target(resource);
|
|
41957
|
+
if (resource.lockedVersion !== void 0) commit(resource.lockedVersion);
|
|
41958
|
+
if (!before.present && resource.lockedVersion !== void 0 && await resolveFlatpakVersion(resource, runner) !== resource.lockedVersion) {
|
|
41959
|
+
throw new Error(`Flatpak's current ${resource.name} commit differs from workstation.lock. Refresh its pin before a fresh install; existing installations can update to retained historical commits.`);
|
|
41960
|
+
}
|
|
41961
|
+
const args = before.present ? ["update", flag, "--noninteractive", "--assumeyes", ...resource.lockedVersion ? [`--commit=${resource.lockedVersion}`] : []] : ["install", flag, "--noninteractive", "--assumeyes", "--app", remote];
|
|
41962
|
+
return {
|
|
41963
|
+
command: scope === "system" ? "sudo" : "flatpak",
|
|
41964
|
+
args: scope === "system" ? ["flatpak", ...args] : args,
|
|
41965
|
+
targets: [ref],
|
|
41966
|
+
...before.present && resource.lockedVersion ? { single: true } : {}
|
|
41967
|
+
};
|
|
41968
|
+
}
|
|
41969
|
+
function prepareFlatpakRemoval(resource) {
|
|
41970
|
+
const { flag, scope, ref } = target(resource);
|
|
41971
|
+
const args = ["uninstall", flag, "--noninteractive", "--assumeyes", "--app"];
|
|
41972
|
+
return { command: scope === "system" ? "sudo" : "flatpak", args: scope === "system" ? ["flatpak", ...args] : args, targets: [ref] };
|
|
41973
|
+
}
|
|
41974
|
+
|
|
41975
|
+
// src/resources/mas.ts
|
|
41976
|
+
function appRows(output) {
|
|
41977
|
+
return output.trim() === "" ? [] : output.trim().split("\n").map((line) => {
|
|
41978
|
+
const match = /^\s*(\d+)\s+.+\s+\(([^()]+)\)\s*$/.exec(line);
|
|
41979
|
+
if (!match?.[1] || !match[2]) throw new Error(`mas returned an unrecognized app row: ${line}`);
|
|
41980
|
+
return { id: match[1], version: match[2] };
|
|
41981
|
+
});
|
|
41982
|
+
}
|
|
41983
|
+
async function inspectMas(resource, runner) {
|
|
41984
|
+
if (resource.lockedVersion !== void 0) throw new Error("mas does not support version pins");
|
|
41985
|
+
const installed = appRows((await requireSuccess(runner, "mas", ["list"])).stdout).find((app) => app.id === resource.name);
|
|
41986
|
+
if (!installed) return { present: false, matches: false };
|
|
41987
|
+
const outdated = appRows((await requireSuccess(runner, "mas", ["outdated"])).stdout);
|
|
41988
|
+
return { present: true, matches: !outdated.some((app) => app.id === resource.name), installedVersion: installed.version };
|
|
41989
|
+
}
|
|
41990
|
+
async function prepareMas(resource, runner, inspection) {
|
|
41991
|
+
const before = inspection ?? await inspectMas(resource, runner);
|
|
41992
|
+
if (before.matches) return;
|
|
41993
|
+
return { command: "mas", args: [before.present ? "upgrade" : "install"], targets: [resource.name] };
|
|
41994
|
+
}
|
|
41995
|
+
|
|
41996
|
+
// src/resources/package.ts
|
|
41997
|
+
async function reconcilePackage(resource, runner) {
|
|
41998
|
+
const before = await inspectPackage(resource, runner);
|
|
41999
|
+
if (before.conflict) throw new Error(before.conflict);
|
|
42000
|
+
if (before.migration === "mise-cask") {
|
|
42001
|
+
await assertBrewVersionAvailable(resource, runner);
|
|
42002
|
+
const mise = await inspectMiseCask(resource, runner);
|
|
42003
|
+
if (!mise) throw new Error(`Mise installation changed during inspection: ${resource.name}`);
|
|
42004
|
+
await migrateMiseCask(resource, mise, runner);
|
|
41656
42005
|
} else {
|
|
41657
|
-
await
|
|
42006
|
+
await runPackageCommand(await preparePackageInstallation(resource, runner, before), runner);
|
|
41658
42007
|
}
|
|
41659
42008
|
const inspection = await inspectPackage(resource, runner);
|
|
41660
42009
|
if (!inspection.matches) {
|
|
41661
42010
|
throw new Error(
|
|
41662
|
-
inspection.conflict ?? `${resource.manager} reported success but ${resource.name}
|
|
42011
|
+
inspection.conflict ?? `${resource.manager} reported success but ${resource.name} did not converge: expected ${resource.lockedVersion ?? resource.version ?? "no available upgrades"}; installed ${inspection.installedVersion ?? (inspection.present ? "version unknown" : "not present")}. Check the package manager output above for skipped upgrades or warnings.`
|
|
41663
42012
|
);
|
|
41664
42013
|
}
|
|
41665
42014
|
return inspection;
|
|
@@ -41685,10 +42034,17 @@ async function inspectPackage(resource, runner) {
|
|
|
41685
42034
|
const installed = result.exitCode === 0;
|
|
41686
42035
|
if (installed && resource.lockedVersion !== void 0) {
|
|
41687
42036
|
const installedVersion = await readInstalledBrewVersion(resource, runner);
|
|
42037
|
+
if (installedVersion === void 0 && resource.manager === "brew-cask") {
|
|
42038
|
+
const mise = await inspectMiseCask(resource, runner);
|
|
42039
|
+
if (mise) return { present: true, matches: false, installedVersion: mise.version, migration: "mise-cask" };
|
|
42040
|
+
}
|
|
41688
42041
|
return {
|
|
41689
42042
|
present: true,
|
|
41690
42043
|
matches: installedVersion === resource.lockedVersion,
|
|
41691
|
-
...installedVersion ? { installedVersion } : {}
|
|
42044
|
+
...installedVersion ? { installedVersion } : {},
|
|
42045
|
+
...installedVersion === void 0 ? {
|
|
42046
|
+
conflict: `Homebrew lists ${resource.name} but reports no installed version. Its installation metadata may be incomplete; inspect brew info --json=v2 ${flag} ${resource.name} and repair the Homebrew installation before retrying.`
|
|
42047
|
+
} : {}
|
|
41692
42048
|
};
|
|
41693
42049
|
}
|
|
41694
42050
|
if (!installed || resource.manager !== "brew-cask" || resource.upgrade === void 0) {
|
|
@@ -41716,52 +42072,54 @@ async function inspectPackage(resource, runner) {
|
|
|
41716
42072
|
...installedVersion ? { installedVersion } : {}
|
|
41717
42073
|
};
|
|
41718
42074
|
}
|
|
42075
|
+
case "dnf":
|
|
42076
|
+
case "yum":
|
|
42077
|
+
return await inspectRpmPackage(resource, runner);
|
|
42078
|
+
case "pacman":
|
|
42079
|
+
return await inspectPacman(resource, runner);
|
|
42080
|
+
case "flatpak":
|
|
42081
|
+
return await inspectFlatpak(resource, runner);
|
|
42082
|
+
case "mas":
|
|
42083
|
+
return await inspectMas(resource, runner);
|
|
41719
42084
|
case "system":
|
|
41720
42085
|
throw new Error("System package manager must be resolved before inspection");
|
|
41721
42086
|
}
|
|
41722
42087
|
}
|
|
41723
|
-
async function
|
|
41724
|
-
if (
|
|
41725
|
-
|
|
41726
|
-
|
|
41727
|
-
await assertBrewVersionAvailable(resource, runner);
|
|
41728
|
-
const args = ["upgrade", "--cask", "--no-ask"];
|
|
41729
|
-
if (resource.upgrade.greedy) args.push("--greedy");
|
|
41730
|
-
if (resource.upgrade.force) args.push("--force");
|
|
41731
|
-
args.push(resource.name);
|
|
41732
|
-
await requireSuccess(runner, "brew", args);
|
|
41733
|
-
}
|
|
41734
|
-
async function upgradeLockedBrewPackage(resource, runner) {
|
|
41735
|
-
if (resource.manager !== "brew" && resource.manager !== "brew-cask") {
|
|
41736
|
-
throw new Error(`Cannot upgrade non-Homebrew package ${resource.name}`);
|
|
41737
|
-
}
|
|
41738
|
-
await assertBrewVersionAvailable(resource, runner);
|
|
41739
|
-
const args = ["upgrade"];
|
|
41740
|
-
if (resource.manager === "brew-cask") {
|
|
41741
|
-
args.push("--cask", "--no-ask");
|
|
41742
|
-
if (resource.upgrade?.greedy) args.push("--greedy");
|
|
41743
|
-
if (resource.upgrade?.force) args.push("--force");
|
|
41744
|
-
}
|
|
41745
|
-
args.push(resource.name);
|
|
41746
|
-
await requireSuccess(runner, "brew", args);
|
|
41747
|
-
}
|
|
41748
|
-
async function installPackage(resource, runner) {
|
|
42088
|
+
async function preparePackageInstallation(resource, runner, before) {
|
|
42089
|
+
if (before.conflict) throw new Error(before.conflict);
|
|
42090
|
+
if (before.migration) throw new Error(`Cask migration must run separately: ${resource.name}`);
|
|
42091
|
+
if (before.matches) return;
|
|
41749
42092
|
let command;
|
|
41750
42093
|
let args;
|
|
42094
|
+
let spec = resource.name;
|
|
41751
42095
|
switch (resource.manager) {
|
|
41752
42096
|
case "mise":
|
|
41753
42097
|
command = "mise";
|
|
41754
|
-
args = ["install"
|
|
42098
|
+
args = ["install"];
|
|
42099
|
+
spec = `${resource.name}@${resource.lockedVersion ?? resource.version ?? "latest"}`;
|
|
41755
42100
|
break;
|
|
42101
|
+
case "dnf":
|
|
42102
|
+
case "yum":
|
|
42103
|
+
return await prepareRpmPackage(resource, runner, before);
|
|
42104
|
+
case "pacman":
|
|
42105
|
+
return await preparePacman(resource, runner, before);
|
|
42106
|
+
case "flatpak":
|
|
42107
|
+
return await prepareFlatpak(resource, runner, before);
|
|
42108
|
+
case "mas":
|
|
42109
|
+
return await prepareMas(resource, runner, before);
|
|
41756
42110
|
case "brew":
|
|
41757
|
-
await assertBrewVersionAvailable(resource, runner);
|
|
41758
|
-
command = "brew";
|
|
41759
|
-
args = ["install", resource.name];
|
|
41760
|
-
break;
|
|
41761
42111
|
case "brew-cask":
|
|
41762
42112
|
await assertBrewVersionAvailable(resource, runner);
|
|
41763
42113
|
command = "brew";
|
|
41764
|
-
args = ["
|
|
42114
|
+
args = [before.present ? "upgrade" : "install"];
|
|
42115
|
+
if (resource.manager === "brew-cask") {
|
|
42116
|
+
args.push("--cask");
|
|
42117
|
+
if (before.present) {
|
|
42118
|
+
args.push("--no-ask");
|
|
42119
|
+
if (resource.lockedVersion || resource.upgrade?.greedy) args.push("--greedy");
|
|
42120
|
+
if (resource.upgrade?.force) args.push("--force");
|
|
42121
|
+
}
|
|
42122
|
+
}
|
|
41765
42123
|
break;
|
|
41766
42124
|
case "apt":
|
|
41767
42125
|
command = "sudo";
|
|
@@ -41769,14 +42127,20 @@ async function installPackage(resource, runner) {
|
|
|
41769
42127
|
"apt-get",
|
|
41770
42128
|
"install",
|
|
41771
42129
|
"-y",
|
|
41772
|
-
...resource.lockedVersion ? ["--allow-downgrades"] : []
|
|
41773
|
-
resource.lockedVersion ? `${resource.name}=${resource.lockedVersion}` : resource.name
|
|
42130
|
+
...resource.lockedVersion ? ["--allow-downgrades"] : []
|
|
41774
42131
|
];
|
|
42132
|
+
spec = resource.lockedVersion ? `${resource.name}=${resource.lockedVersion}` : resource.name;
|
|
41775
42133
|
break;
|
|
41776
42134
|
case "system":
|
|
41777
42135
|
throw new Error("System package manager must be resolved before installation");
|
|
41778
42136
|
}
|
|
41779
|
-
|
|
42137
|
+
return { command, args, targets: [spec], options: brewMutationOptions(resource) };
|
|
42138
|
+
}
|
|
42139
|
+
function brewMutationOptions(resource) {
|
|
42140
|
+
return {
|
|
42141
|
+
streamOutput: true,
|
|
42142
|
+
...(resource.manager === "brew" || resource.manager === "brew-cask") && resource.lockedVersion ? { environment: { HOMEBREW_NO_AUTO_UPDATE: "1" } } : {}
|
|
42143
|
+
};
|
|
41780
42144
|
}
|
|
41781
42145
|
async function assertBrewVersionAvailable(resource, runner) {
|
|
41782
42146
|
if (!resource.lockedVersion) return;
|
|
@@ -41788,41 +42152,60 @@ async function assertBrewVersionAvailable(resource, runner) {
|
|
|
41788
42152
|
}
|
|
41789
42153
|
}
|
|
41790
42154
|
async function removePackage(resource, runner, installedVersion) {
|
|
42155
|
+
await runPackageCommand(preparePackageRemoval(resource, installedVersion), runner);
|
|
42156
|
+
}
|
|
42157
|
+
function preparePackageRemoval(resource, installedVersion) {
|
|
41791
42158
|
let command;
|
|
41792
42159
|
let args;
|
|
42160
|
+
let spec = resource.name;
|
|
41793
42161
|
switch (resource.manager) {
|
|
41794
42162
|
case "mise":
|
|
41795
42163
|
command = "mise";
|
|
41796
42164
|
args = [
|
|
41797
42165
|
"uninstall",
|
|
41798
|
-
"--yes"
|
|
41799
|
-
`${resource.name}@${installedVersion ?? resource.lockedVersion ?? resource.version ?? "latest"}`
|
|
42166
|
+
"--yes"
|
|
41800
42167
|
];
|
|
42168
|
+
spec = `${resource.name}@${installedVersion ?? resource.lockedVersion ?? resource.version ?? "latest"}`;
|
|
41801
42169
|
break;
|
|
41802
42170
|
case "brew":
|
|
41803
42171
|
command = "brew";
|
|
41804
|
-
args = ["uninstall"
|
|
42172
|
+
args = ["uninstall"];
|
|
41805
42173
|
break;
|
|
41806
42174
|
case "brew-cask":
|
|
41807
42175
|
command = "brew";
|
|
41808
|
-
args = ["uninstall", "--cask"
|
|
42176
|
+
args = ["uninstall", "--cask"];
|
|
41809
42177
|
break;
|
|
41810
42178
|
case "apt":
|
|
41811
42179
|
command = "sudo";
|
|
41812
|
-
args = ["apt-get", "remove", "-y"
|
|
42180
|
+
args = ["apt-get", "remove", "-y"];
|
|
42181
|
+
break;
|
|
42182
|
+
case "dnf":
|
|
42183
|
+
case "yum":
|
|
42184
|
+
command = "sudo";
|
|
42185
|
+
args = [resource.manager, "remove", "-y"];
|
|
42186
|
+
break;
|
|
42187
|
+
case "pacman":
|
|
42188
|
+
command = "sudo";
|
|
42189
|
+
args = ["pacman", "-R", "--noconfirm"];
|
|
42190
|
+
break;
|
|
42191
|
+
case "flatpak":
|
|
42192
|
+
return prepareFlatpakRemoval(resource);
|
|
42193
|
+
case "mas":
|
|
42194
|
+
command = "mas";
|
|
42195
|
+
args = ["uninstall"];
|
|
41813
42196
|
break;
|
|
41814
42197
|
case "system":
|
|
41815
42198
|
throw new Error("System package manager must be resolved before removal");
|
|
41816
42199
|
}
|
|
41817
|
-
|
|
42200
|
+
return { command, args, targets: [spec] };
|
|
41818
42201
|
}
|
|
41819
42202
|
|
|
41820
42203
|
// src/resources/symlink.ts
|
|
41821
|
-
import { lstat as
|
|
41822
|
-
import { dirname as
|
|
42204
|
+
import { lstat as lstat4, mkdir as mkdir3, readlink as readlink4, symlink as symlink3, unlink as unlink2 } from "fs/promises";
|
|
42205
|
+
import { dirname as dirname5, resolve as resolve7 } from "path";
|
|
41823
42206
|
async function inspectSymlink(resource) {
|
|
41824
42207
|
try {
|
|
41825
|
-
const stats = await
|
|
42208
|
+
const stats = await lstat4(resource.target);
|
|
41826
42209
|
if (!stats.isSymbolicLink()) {
|
|
41827
42210
|
return {
|
|
41828
42211
|
present: true,
|
|
@@ -41830,8 +42213,8 @@ async function inspectSymlink(resource) {
|
|
|
41830
42213
|
conflict: `${resource.target} exists and is not a symbolic link`
|
|
41831
42214
|
};
|
|
41832
42215
|
}
|
|
41833
|
-
const
|
|
41834
|
-
const actual =
|
|
42216
|
+
const target2 = await readlink4(resource.target);
|
|
42217
|
+
const actual = resolve7(dirname5(resource.target), target2);
|
|
41835
42218
|
const matches = actual === resource.source;
|
|
41836
42219
|
return {
|
|
41837
42220
|
present: true,
|
|
@@ -41847,12 +42230,12 @@ async function installSymlink(resource) {
|
|
|
41847
42230
|
const inspection = await inspectSymlink(resource);
|
|
41848
42231
|
if (inspection.conflict) throw new Error(inspection.conflict);
|
|
41849
42232
|
if (inspection.matches) return;
|
|
41850
|
-
await
|
|
42233
|
+
await lstat4(resource.source).catch((error) => {
|
|
41851
42234
|
if (isMissingFile(error)) throw new Error(`Symlink source does not exist: ${resource.source}`);
|
|
41852
42235
|
throw error;
|
|
41853
42236
|
});
|
|
41854
|
-
await mkdir3(
|
|
41855
|
-
await
|
|
42237
|
+
await mkdir3(dirname5(resource.target), { recursive: true });
|
|
42238
|
+
await symlink3(resource.source, resource.target);
|
|
41856
42239
|
}
|
|
41857
42240
|
async function removeSymlink(resource) {
|
|
41858
42241
|
const inspection = await inspectSymlink(resource);
|
|
@@ -41864,13 +42247,13 @@ async function removeSymlink(resource) {
|
|
|
41864
42247
|
}
|
|
41865
42248
|
|
|
41866
42249
|
// src/resources/launch-agent.ts
|
|
41867
|
-
import { mkdir as mkdir4, readFile as
|
|
41868
|
-
import { dirname as
|
|
42250
|
+
import { mkdir as mkdir4, readFile as readFile5, unlink as unlink3 } from "fs/promises";
|
|
42251
|
+
import { dirname as dirname6, resolve as resolve8 } from "path";
|
|
41869
42252
|
|
|
41870
42253
|
// src/rendering/plist.ts
|
|
41871
42254
|
function renderLaunchAgent(resource) {
|
|
41872
42255
|
const args = resource.args ?? [];
|
|
41873
|
-
const
|
|
42256
|
+
const environment3 = Object.entries(resource.environment ?? {}).sort(
|
|
41874
42257
|
([left], [right]) => left.localeCompare(right)
|
|
41875
42258
|
);
|
|
41876
42259
|
const lines = [
|
|
@@ -41888,9 +42271,9 @@ function renderLaunchAgent(resource) {
|
|
|
41888
42271
|
];
|
|
41889
42272
|
if (resource.runAtLoad ?? true) lines.push(" <key>RunAtLoad</key>", " <true/>");
|
|
41890
42273
|
if (resource.keepAlive ?? false) lines.push(" <key>KeepAlive</key>", " <true/>");
|
|
41891
|
-
if (
|
|
42274
|
+
if (environment3.length > 0) {
|
|
41892
42275
|
lines.push(" <key>EnvironmentVariables</key>", " <dict>");
|
|
41893
|
-
for (const [key, value2] of
|
|
42276
|
+
for (const [key, value2] of environment3) {
|
|
41894
42277
|
lines.push(` <key>${escapeXml(key)}</key>`, ` <string>${escapeXml(value2)}</string>`);
|
|
41895
42278
|
}
|
|
41896
42279
|
lines.push(" </dict>");
|
|
@@ -41918,7 +42301,7 @@ function escapeXml(value2) {
|
|
|
41918
42301
|
async function inspectLaunchAgent(resource) {
|
|
41919
42302
|
const path = launchAgentPath(resource);
|
|
41920
42303
|
try {
|
|
41921
|
-
const current = await
|
|
42304
|
+
const current = await readFile5(path, "utf8");
|
|
41922
42305
|
const matches = current === renderLaunchAgent(resource);
|
|
41923
42306
|
return {
|
|
41924
42307
|
present: true,
|
|
@@ -41935,7 +42318,7 @@ async function installLaunchAgent(resource, runner) {
|
|
|
41935
42318
|
if (inspection.present && !inspection.matches) throw new Error(inspection.conflict);
|
|
41936
42319
|
if (!inspection.present) {
|
|
41937
42320
|
const path = launchAgentPath(resource);
|
|
41938
|
-
await mkdir4(
|
|
42321
|
+
await mkdir4(dirname6(path), { recursive: true });
|
|
41939
42322
|
await atomicWrite(path, renderLaunchAgent(resource));
|
|
41940
42323
|
}
|
|
41941
42324
|
const domain = `gui/${process.getuid?.() ?? 0}`;
|
|
@@ -41959,13 +42342,13 @@ async function removeLaunchAgent(resource, runner) {
|
|
|
41959
42342
|
function launchAgentPath(resource) {
|
|
41960
42343
|
const home = process.env.HOME;
|
|
41961
42344
|
if (!home) throw new Error("HOME is required to manage LaunchAgents");
|
|
41962
|
-
return
|
|
42345
|
+
return resolve8(home, "Library", "LaunchAgents", `${resource.label}.plist`);
|
|
41963
42346
|
}
|
|
41964
42347
|
|
|
41965
42348
|
// src/resources/systemd-service.ts
|
|
41966
|
-
import { mkdir as mkdir5, readFile as
|
|
42349
|
+
import { mkdir as mkdir5, readFile as readFile6, unlink as unlink4, writeFile as writeFile3 } from "fs/promises";
|
|
41967
42350
|
import { tmpdir } from "os";
|
|
41968
|
-
import { dirname as
|
|
42351
|
+
import { dirname as dirname7, resolve as resolve9 } from "path";
|
|
41969
42352
|
|
|
41970
42353
|
// src/rendering/systemd.ts
|
|
41971
42354
|
function renderSystemdService(resource) {
|
|
@@ -41978,7 +42361,7 @@ function renderSystemdService(resource) {
|
|
|
41978
42361
|
if (!/^[A-Za-z0-9_.@-]+\.(?:target|service)$/.test(wantedBy)) {
|
|
41979
42362
|
throw new Error(`Invalid systemd WantedBy unit: ${wantedBy}`);
|
|
41980
42363
|
}
|
|
41981
|
-
const
|
|
42364
|
+
const environment3 = Object.entries(resource.environment ?? {}).map(
|
|
41982
42365
|
([key, value2]) => {
|
|
41983
42366
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
|
|
41984
42367
|
throw new Error(`Invalid systemd environment variable: ${key}`);
|
|
@@ -41992,7 +42375,7 @@ function renderSystemdService(resource) {
|
|
|
41992
42375
|
"",
|
|
41993
42376
|
"[Service]",
|
|
41994
42377
|
`ExecStart=${[resource.program, ...resource.args ?? []].map(quote).join(" ")}`,
|
|
41995
|
-
...
|
|
42378
|
+
...environment3,
|
|
41996
42379
|
`Restart=${restart}`,
|
|
41997
42380
|
"",
|
|
41998
42381
|
"[Install]",
|
|
@@ -42008,7 +42391,7 @@ function quote(value2) {
|
|
|
42008
42391
|
async function inspectSystemdService(resource) {
|
|
42009
42392
|
const path = systemdServicePath(resource);
|
|
42010
42393
|
try {
|
|
42011
|
-
const current = await
|
|
42394
|
+
const current = await readFile6(path, "utf8");
|
|
42012
42395
|
const matches = current === renderSystemdService(resource);
|
|
42013
42396
|
return {
|
|
42014
42397
|
present: true,
|
|
@@ -42046,11 +42429,11 @@ async function writeSystemdService(resource, runner) {
|
|
|
42046
42429
|
const path = systemdServicePath(resource);
|
|
42047
42430
|
const content = renderSystemdService(resource);
|
|
42048
42431
|
if (resource.scope === "user") {
|
|
42049
|
-
await mkdir5(
|
|
42432
|
+
await mkdir5(dirname7(path), { recursive: true });
|
|
42050
42433
|
await atomicWrite(path, content, 420);
|
|
42051
42434
|
return;
|
|
42052
42435
|
}
|
|
42053
|
-
const temporary =
|
|
42436
|
+
const temporary = resolve9(tmpdir(), `workstation-${process.pid}-${resource.name}`);
|
|
42054
42437
|
await writeFile3(temporary, content, { mode: 384 });
|
|
42055
42438
|
try {
|
|
42056
42439
|
await requireSuccess(runner, "sudo", ["install", "-m", "0644", temporary, path]);
|
|
@@ -42066,26 +42449,26 @@ async function systemctl(resource, runner, args) {
|
|
|
42066
42449
|
}
|
|
42067
42450
|
}
|
|
42068
42451
|
function systemdServicePath(resource) {
|
|
42069
|
-
if (resource.scope === "system") return
|
|
42452
|
+
if (resource.scope === "system") return resolve9("/etc/systemd/system", resource.name);
|
|
42070
42453
|
const home = process.env.HOME;
|
|
42071
42454
|
if (!home) throw new Error("HOME is required to manage user systemd services");
|
|
42072
|
-
return
|
|
42455
|
+
return resolve9(home, ".config", "systemd", "user", resource.name);
|
|
42073
42456
|
}
|
|
42074
42457
|
|
|
42075
42458
|
// src/resources/custom-tool.ts
|
|
42076
|
-
import { chmod as chmod2, lstat as
|
|
42077
|
-
import { dirname as
|
|
42459
|
+
import { chmod as chmod2, lstat as lstat5, mkdir as mkdir6, readFile as readFile7, rename as rename5, unlink as unlink5 } from "fs/promises";
|
|
42460
|
+
import { dirname as dirname8 } from "path";
|
|
42078
42461
|
import { createHash as createHash3 } from "crypto";
|
|
42079
42462
|
async function inspectCustomTool(resource) {
|
|
42080
42463
|
try {
|
|
42081
|
-
const stats = await
|
|
42464
|
+
const stats = await lstat5(resource.target);
|
|
42082
42465
|
if (!stats.isFile()) {
|
|
42083
42466
|
return { present: true, matches: false, conflict: `${resource.target} is not a regular file` };
|
|
42084
42467
|
}
|
|
42085
42468
|
return {
|
|
42086
42469
|
present: true,
|
|
42087
42470
|
matches: true,
|
|
42088
|
-
installedHash: createHash3("sha256").update(await
|
|
42471
|
+
installedHash: createHash3("sha256").update(await readFile7(resource.target)).digest("hex")
|
|
42089
42472
|
};
|
|
42090
42473
|
} catch (error) {
|
|
42091
42474
|
if (isMissingFile(error)) return { present: false, matches: false };
|
|
@@ -42093,7 +42476,7 @@ async function inspectCustomTool(resource) {
|
|
|
42093
42476
|
}
|
|
42094
42477
|
}
|
|
42095
42478
|
async function installCustomTool(resource, runner) {
|
|
42096
|
-
await mkdir6(
|
|
42479
|
+
await mkdir6(dirname8(resource.target), { recursive: true });
|
|
42097
42480
|
const output = `${resource.target}.${process.pid}.workstation.tmp`;
|
|
42098
42481
|
await unlink5(output).catch((error) => {
|
|
42099
42482
|
if (!isMissingFile(error)) throw error;
|
|
@@ -42106,11 +42489,12 @@ async function installCustomTool(resource, runner) {
|
|
|
42106
42489
|
resource.build.command,
|
|
42107
42490
|
(resource.build.args ?? []).map(replace),
|
|
42108
42491
|
{
|
|
42492
|
+
streamOutput: true,
|
|
42109
42493
|
...resource.build.cwd ? { cwd: replace(resource.build.cwd) } : {},
|
|
42110
42494
|
...resource.build.environment ? { environment: Object.fromEntries(Object.entries(resource.build.environment).map(([key, value2]) => [key, replace(value2)])) } : {}
|
|
42111
42495
|
}
|
|
42112
42496
|
);
|
|
42113
|
-
const stats = await
|
|
42497
|
+
const stats = await lstat5(output).catch((error) => {
|
|
42114
42498
|
if (isMissingFile(error)) {
|
|
42115
42499
|
throw new Error(`Custom tool build did not create {output}: ${resource.name}`);
|
|
42116
42500
|
}
|
|
@@ -42118,7 +42502,7 @@ async function installCustomTool(resource, runner) {
|
|
|
42118
42502
|
});
|
|
42119
42503
|
if (!stats.isFile()) throw new Error(`Custom tool output is not a regular file: ${output}`);
|
|
42120
42504
|
await chmod2(output, 493);
|
|
42121
|
-
await
|
|
42505
|
+
await rename5(output, resource.target);
|
|
42122
42506
|
} finally {
|
|
42123
42507
|
await unlink5(output).catch(() => void 0);
|
|
42124
42508
|
}
|
|
@@ -42200,11 +42584,11 @@ async function removeResource(resource, runner, installedVersion, originalFile,
|
|
|
42200
42584
|
}
|
|
42201
42585
|
|
|
42202
42586
|
// src/persistence/state.ts
|
|
42203
|
-
import { mkdir as mkdir7, readFile as
|
|
42204
|
-
import { dirname as
|
|
42587
|
+
import { mkdir as mkdir7, readFile as readFile8, rename as rename6, writeFile as writeFile4 } from "fs/promises";
|
|
42588
|
+
import { dirname as dirname9 } from "path";
|
|
42205
42589
|
async function readState(path, machine) {
|
|
42206
42590
|
try {
|
|
42207
|
-
const parsed = JSON.parse(await
|
|
42591
|
+
const parsed = JSON.parse(await readFile8(path, "utf8"));
|
|
42208
42592
|
if (!isState(parsed)) {
|
|
42209
42593
|
throw new Error(`Unsupported or invalid state file: ${path}`);
|
|
42210
42594
|
}
|
|
@@ -42222,11 +42606,11 @@ async function readState(path, machine) {
|
|
|
42222
42606
|
}
|
|
42223
42607
|
}
|
|
42224
42608
|
async function writeState(path, state) {
|
|
42225
|
-
await mkdir7(
|
|
42609
|
+
await mkdir7(dirname9(path), { recursive: true, mode: 448 });
|
|
42226
42610
|
const temporary = `${path}.${process.pid}.tmp`;
|
|
42227
42611
|
await writeFile4(temporary, `${JSON.stringify(state, null, 2)}
|
|
42228
42612
|
`, { mode: 384 });
|
|
42229
|
-
await
|
|
42613
|
+
await rename6(temporary, path);
|
|
42230
42614
|
}
|
|
42231
42615
|
function isMissingFile2(error) {
|
|
42232
42616
|
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
@@ -42252,7 +42636,7 @@ function isResolvedResource(value2) {
|
|
|
42252
42636
|
const candidate = value2;
|
|
42253
42637
|
switch (candidate.kind) {
|
|
42254
42638
|
case "package":
|
|
42255
|
-
return ["mise", "brew", "brew-cask", "apt"].includes(String(candidate.manager)) &&
|
|
42639
|
+
return ["mise", "brew", "brew-cask", "apt", "dnf", "yum", "pacman", "flatpak", "mas"].includes(String(candidate.manager)) && isPackageName(candidate.manager, candidate.name) && (candidate.flatpak === void 0 || candidate.manager === "flatpak" && isFlatpakOptions(candidate.flatpak)) && (candidate.manager !== "mas" || candidate.lockedVersion === void 0) && (candidate.version === void 0 || typeof candidate.version === "string") && (candidate.lockedVersion === void 0 || typeof candidate.lockedVersion === "string") && (candidate.upgrade === void 0 || candidate.manager === "brew-cask" && isBrewCaskUpgradeOptions2(candidate.upgrade));
|
|
42256
42640
|
case "symlink":
|
|
42257
42641
|
return typeof candidate.source === "string" && typeof candidate.target === "string";
|
|
42258
42642
|
case "launch-agent":
|
|
@@ -42295,24 +42679,25 @@ function isBrewCaskUpgradeOptions2(value2) {
|
|
|
42295
42679
|
}
|
|
42296
42680
|
|
|
42297
42681
|
// src/reconciliation/plan.ts
|
|
42298
|
-
async function createPlan(config, runner) {
|
|
42682
|
+
async function createPlan(config, runner, onProgress) {
|
|
42299
42683
|
const state = await readState(config.stateFile, config.context.machine);
|
|
42300
42684
|
const desired = new Map(config.resources.map((resource) => [resourceId(resource), resource]));
|
|
42301
42685
|
const actions = [];
|
|
42302
42686
|
for (const [id, resource] of desired) {
|
|
42687
|
+
onProgress?.(` Inspect: ${id}`);
|
|
42303
42688
|
const previous = state.resources[id];
|
|
42304
42689
|
const inspection = await inspectResource(resource, runner);
|
|
42305
42690
|
if (resource.kind === "custom-tool" && inspection.present && previous?.installedHash !== void 0 && inspection.installedHash !== previous.installedHash) {
|
|
42306
42691
|
throw new Error(`Managed custom tool ${id} was changed outside Workstation`);
|
|
42307
42692
|
}
|
|
42308
|
-
if (resource.kind === "symlink" && !inspection.matches) await
|
|
42693
|
+
if (resource.kind === "symlink" && !inspection.matches) await lstat6(resource.source);
|
|
42309
42694
|
if (!previous) {
|
|
42310
42695
|
if (inspection.matches) {
|
|
42311
42696
|
actions.push({ type: "adopt", id, resource, reason: "already present" });
|
|
42312
42697
|
} else if (inspection.conflict) {
|
|
42313
42698
|
throw new Error(`Cannot manage ${id}: ${inspection.conflict}`);
|
|
42314
42699
|
} else if (inspection.present) {
|
|
42315
|
-
actions.push({ type: "update", id, resource, reason: "upgrade available" });
|
|
42700
|
+
actions.push({ type: "update", id, resource, reason: inspection.migration ? "migrate from mise to Homebrew" : "upgrade available" });
|
|
42316
42701
|
} else {
|
|
42317
42702
|
actions.push({ type: "create", id, resource, reason: "not present" });
|
|
42318
42703
|
}
|
|
@@ -42335,12 +42720,13 @@ async function createPlan(config, runner) {
|
|
|
42335
42720
|
id,
|
|
42336
42721
|
resource,
|
|
42337
42722
|
previous,
|
|
42338
|
-
reason: inspection.present ? "upgrade available" : "managed resource is missing"
|
|
42723
|
+
reason: inspection.migration ? "migrate from mise to Homebrew" : inspection.present ? "upgrade available" : "managed resource is missing"
|
|
42339
42724
|
});
|
|
42340
42725
|
}
|
|
42341
42726
|
}
|
|
42342
42727
|
for (const [id, previous] of Object.entries(state.resources)) {
|
|
42343
42728
|
if (desired.has(id)) continue;
|
|
42729
|
+
onProgress?.(` Inspect removed declaration: ${id}`);
|
|
42344
42730
|
const removable = previous.owned || previous.originalFile !== void 0;
|
|
42345
42731
|
if (!removable) {
|
|
42346
42732
|
actions.push({
|
|
@@ -42372,7 +42758,7 @@ async function createPlan(config, runner) {
|
|
|
42372
42758
|
);
|
|
42373
42759
|
}
|
|
42374
42760
|
function canUpdateAdoptedInPlace(resource) {
|
|
42375
|
-
return resource.kind === "package" &&
|
|
42761
|
+
return resource.kind === "package" && ["brew-cask", "flatpak", "mas", "pacman"].includes(resource.manager) || resource.kind === "generated-file" && resource.ifExists === "overwrite" || resource.kind === "custom-tool";
|
|
42376
42762
|
}
|
|
42377
42763
|
function canRepairManagedFile(resource, previous) {
|
|
42378
42764
|
return resource.kind === "generated-file" && (previous.owned || previous.originalFile !== void 0 && resource.ifExists === "overwrite");
|
|
@@ -42390,11 +42776,66 @@ function actionOrder(action) {
|
|
|
42390
42776
|
return 6;
|
|
42391
42777
|
}
|
|
42392
42778
|
|
|
42779
|
+
// src/reconciliation/package-batch.ts
|
|
42780
|
+
function isPackageMutation(action) {
|
|
42781
|
+
return action.resource.kind === "package" && ["create", "update", "remove"].includes(action.type);
|
|
42782
|
+
}
|
|
42783
|
+
async function applyPackageBatch(actions, runner, checkpoint, onAction, onProgress) {
|
|
42784
|
+
const groups = [];
|
|
42785
|
+
const byCommand = /* @__PURE__ */ new Map();
|
|
42786
|
+
for (const action of actions) {
|
|
42787
|
+
const before = await inspectPackage(action.resource, runner);
|
|
42788
|
+
if (before.conflict) throw new Error(`Cannot manage ${action.id}: ${before.conflict}`);
|
|
42789
|
+
const mutation = action.type === "remove" ? before.present ? preparePackageRemoval(action.resource, action.previous?.installedVersion) : void 0 : before.migration ? void 0 : await preparePackageInstallation(action.resource, runner, before);
|
|
42790
|
+
const prepared = { action, before, ...mutation ? { mutation } : {} };
|
|
42791
|
+
const key = mutation ? packageCommandKey(mutation) : void 0;
|
|
42792
|
+
const group = key === void 0 ? void 0 : byCommand.get(key);
|
|
42793
|
+
if (group) group.push(prepared);
|
|
42794
|
+
else {
|
|
42795
|
+
const next = [prepared];
|
|
42796
|
+
groups.push(next);
|
|
42797
|
+
if (key !== void 0) byCommand.set(key, next);
|
|
42798
|
+
}
|
|
42799
|
+
}
|
|
42800
|
+
for (const group of groups) {
|
|
42801
|
+
const first = group[0];
|
|
42802
|
+
if (!first) continue;
|
|
42803
|
+
for (const item of group) onAction?.(item.action);
|
|
42804
|
+
onProgress?.(` ${group.length > 1 ? "Batch" : "Package"}: ${first.action.resource.manager} ${first.mutation?.args.join(" ") ?? first.action.type} (${group.length} package(s))`);
|
|
42805
|
+
const errors = [];
|
|
42806
|
+
try {
|
|
42807
|
+
if (first.before.migration && first.action.type !== "remove") {
|
|
42808
|
+
await reconcilePackage(first.action.resource, runner);
|
|
42809
|
+
} else if (first.mutation) {
|
|
42810
|
+
await runPackageCommand({ ...first.mutation, targets: group.flatMap((item) => item.mutation?.targets ?? []) }, runner);
|
|
42811
|
+
}
|
|
42812
|
+
} catch (error) {
|
|
42813
|
+
errors.push(error);
|
|
42814
|
+
}
|
|
42815
|
+
for (const { action, before } of group) {
|
|
42816
|
+
try {
|
|
42817
|
+
const after = await inspectPackage(action.resource, runner);
|
|
42818
|
+
const converged = action.type === "remove" ? !after.present : after.matches;
|
|
42819
|
+
if (converged || action.type !== "remove" && after.present && !after.conflict && !after.migration) {
|
|
42820
|
+
await checkpoint(action, before, after);
|
|
42821
|
+
onProgress?.(` ${converged ? "Done" : "Partial"}: ${action.type} ${action.id} (state saved)`);
|
|
42822
|
+
}
|
|
42823
|
+
if (!converged) errors.push(new Error(`${action.id} did not converge: expected ${action.type === "remove" ? "absent" : action.resource.lockedVersion ?? "no available upgrades"}; installed ${after.installedVersion ?? (after.present ? "version unknown" : "not present")}${after.conflict ? `; ${after.conflict}` : ""}`));
|
|
42824
|
+
} catch (error) {
|
|
42825
|
+
errors.push(error);
|
|
42826
|
+
}
|
|
42827
|
+
}
|
|
42828
|
+
if (errors.length > 0) {
|
|
42829
|
+
throw new AggregateError(errors, `Package batch failed; verified results were saved. ${errors.map((error) => error instanceof Error ? error.message : String(error)).join("; ")}`);
|
|
42830
|
+
}
|
|
42831
|
+
}
|
|
42832
|
+
}
|
|
42833
|
+
|
|
42393
42834
|
// src/reconciliation/apply.ts
|
|
42394
42835
|
import { mkdir as mkdir8, rmdir } from "fs/promises";
|
|
42395
|
-
import { dirname as
|
|
42396
|
-
async function applyPlan(config, runner, onAction) {
|
|
42397
|
-
await mkdir8(
|
|
42836
|
+
import { dirname as dirname10 } from "path";
|
|
42837
|
+
async function applyPlan(config, runner, onAction, onProgress) {
|
|
42838
|
+
await mkdir8(dirname10(config.stateFile), { recursive: true, mode: 448 });
|
|
42398
42839
|
const guard = `${config.stateFile}.lock`;
|
|
42399
42840
|
try {
|
|
42400
42841
|
await mkdir8(guard, { mode: 448 });
|
|
@@ -42405,15 +42846,42 @@ async function applyPlan(config, runner, onAction) {
|
|
|
42405
42846
|
throw error;
|
|
42406
42847
|
}
|
|
42407
42848
|
try {
|
|
42408
|
-
return await reconcile(config, runner, onAction);
|
|
42849
|
+
return await reconcile(config, runner, onAction, onProgress);
|
|
42409
42850
|
} finally {
|
|
42410
42851
|
await rmdir(guard);
|
|
42411
42852
|
}
|
|
42412
42853
|
}
|
|
42413
|
-
async function reconcile(config, runner, onAction) {
|
|
42414
|
-
|
|
42854
|
+
async function reconcile(config, runner, onAction, onProgress) {
|
|
42855
|
+
onProgress?.("Inspecting resources...");
|
|
42856
|
+
const actions = await createPlan(config, runner, onProgress);
|
|
42857
|
+
onProgress?.(`Plan: ${actions.length} action(s).`);
|
|
42415
42858
|
let state = await readState(config.stateFile, config.context.machine);
|
|
42416
|
-
for (
|
|
42859
|
+
for (let index = 0; index < actions.length; index++) {
|
|
42860
|
+
const action = actions[index];
|
|
42861
|
+
if (!action) continue;
|
|
42862
|
+
if (isPackageMutation(action)) {
|
|
42863
|
+
const batch = [action];
|
|
42864
|
+
while (index + 1 < actions.length) {
|
|
42865
|
+
const next = actions[index + 1];
|
|
42866
|
+
if (!next || !isPackageMutation(next) || next.type === "remove" !== (action.type === "remove")) break;
|
|
42867
|
+
batch.push(next);
|
|
42868
|
+
index++;
|
|
42869
|
+
}
|
|
42870
|
+
await applyPackageBatch(batch, runner, async (completed, before, after) => {
|
|
42871
|
+
if (completed.type === "update" && after.matches) await removeReplacedMiseVersion(completed, runner);
|
|
42872
|
+
const resources2 = { ...state.resources };
|
|
42873
|
+
if (completed.type === "remove") delete resources2[completed.id];
|
|
42874
|
+
else {
|
|
42875
|
+
resources2[completed.id] = entry(completed, {
|
|
42876
|
+
owned: completed.previous?.owned === true || !before.present,
|
|
42877
|
+
inspection: after
|
|
42878
|
+
});
|
|
42879
|
+
}
|
|
42880
|
+
state = { ...state, resources: resources2 };
|
|
42881
|
+
await writeState(config.stateFile, state);
|
|
42882
|
+
}, onAction, onProgress);
|
|
42883
|
+
continue;
|
|
42884
|
+
}
|
|
42417
42885
|
onAction?.(action);
|
|
42418
42886
|
const resources = { ...state.resources };
|
|
42419
42887
|
switch (action.type) {
|
|
@@ -42486,6 +42954,7 @@ async function reconcile(config, runner, onAction) {
|
|
|
42486
42954
|
}
|
|
42487
42955
|
state = { ...state, resources };
|
|
42488
42956
|
await writeState(config.stateFile, state);
|
|
42957
|
+
onProgress?.(` Done: ${action.type} ${action.id} (state saved)`);
|
|
42489
42958
|
}
|
|
42490
42959
|
return actions;
|
|
42491
42960
|
}
|
|
@@ -42522,9 +42991,24 @@ function entry(action, options) {
|
|
|
42522
42991
|
// src/resources/runner.ts
|
|
42523
42992
|
import { spawn } from "child_process";
|
|
42524
42993
|
var ProcessRunner = class {
|
|
42994
|
+
/** Opt into command progress and streaming; verbose also prints captured query output. */
|
|
42995
|
+
constructor(logging = {}) {
|
|
42996
|
+
this.logging = logging;
|
|
42997
|
+
}
|
|
42998
|
+
/** Print resource progress only when logging is enabled. */
|
|
42999
|
+
report(message) {
|
|
43000
|
+
if (this.logging.progress || this.logging.verbose) process.stderr.write(` ${message}
|
|
43001
|
+
`);
|
|
43002
|
+
}
|
|
42525
43003
|
/** Execute a command directly and return captured output and its exit code; spawn failures reject. */
|
|
42526
43004
|
async run(command, args, options) {
|
|
42527
|
-
|
|
43005
|
+
const started = Date.now();
|
|
43006
|
+
if (this.logging.progress || this.logging.verbose) {
|
|
43007
|
+
process.stderr.write(` $ ${[command, ...args].map((value2) => /^[\w./:@=+-]+$/.test(value2) ? value2 : JSON.stringify(value2)).join(" ")}${options?.cwd ? ` (in ${options.cwd})` : ""}
|
|
43008
|
+
`);
|
|
43009
|
+
}
|
|
43010
|
+
const stream = this.logging.verbose || this.logging.progress && options?.streamOutput;
|
|
43011
|
+
return await new Promise((resolve12, reject) => {
|
|
42528
43012
|
const child = spawn(command, [...args], {
|
|
42529
43013
|
cwd: options?.cwd,
|
|
42530
43014
|
env: { ...process.env, ...options?.environment },
|
|
@@ -42534,20 +43018,29 @@ var ProcessRunner = class {
|
|
|
42534
43018
|
let stderr = "";
|
|
42535
43019
|
child.stdout.setEncoding("utf8");
|
|
42536
43020
|
child.stderr.setEncoding("utf8");
|
|
42537
|
-
child.stdout.on("data", (chunk) =>
|
|
42538
|
-
|
|
43021
|
+
child.stdout.on("data", (chunk) => {
|
|
43022
|
+
stdout += chunk;
|
|
43023
|
+
if (stream) process.stdout.write(chunk);
|
|
43024
|
+
});
|
|
43025
|
+
child.stderr.on("data", (chunk) => {
|
|
43026
|
+
stderr += chunk;
|
|
43027
|
+
if (stream || this.logging.progress) process.stderr.write(chunk);
|
|
43028
|
+
});
|
|
42539
43029
|
child.once("error", reject);
|
|
42540
|
-
child.once(
|
|
42541
|
-
|
|
42542
|
-
|
|
42543
|
-
|
|
43030
|
+
child.once("close", (exitCode) => {
|
|
43031
|
+
if (this.logging.progress || this.logging.verbose) {
|
|
43032
|
+
process.stderr.write(` -> exit ${exitCode ?? 1} (${((Date.now() - started) / 1e3).toFixed(1)}s)
|
|
43033
|
+
`);
|
|
43034
|
+
}
|
|
43035
|
+
resolve12({ exitCode: exitCode ?? 1, stdout, stderr });
|
|
43036
|
+
});
|
|
42544
43037
|
});
|
|
42545
43038
|
}
|
|
42546
43039
|
};
|
|
42547
43040
|
|
|
42548
43041
|
// src/persistence/lock.ts
|
|
42549
|
-
import { readFile as
|
|
42550
|
-
import { dirname as
|
|
43042
|
+
import { readFile as readFile9, rename as rename7, writeFile as writeFile5 } from "fs/promises";
|
|
43043
|
+
import { dirname as dirname11, resolve as resolve10 } from "path";
|
|
42551
43044
|
|
|
42552
43045
|
// src/resources/package-version.ts
|
|
42553
43046
|
async function resolvePackageVersion(resource, runner) {
|
|
@@ -42573,22 +43066,31 @@ async function resolvePackageVersion(resource, runner) {
|
|
|
42573
43066
|
const version = await readAvailableBrewVersion(resource, runner);
|
|
42574
43067
|
return version === "latest" ? void 0 : version;
|
|
42575
43068
|
}
|
|
43069
|
+
case "dnf":
|
|
43070
|
+
case "yum":
|
|
43071
|
+
return await resolveRpmVersion(resource, runner);
|
|
42576
43072
|
case "system":
|
|
42577
43073
|
throw new Error("System package manager must be resolved before locking");
|
|
43074
|
+
case "pacman":
|
|
43075
|
+
return await resolvePacmanVersion(resource, runner);
|
|
43076
|
+
case "flatpak":
|
|
43077
|
+
return await resolveFlatpakVersion(resource, runner);
|
|
43078
|
+
case "mas":
|
|
43079
|
+
return void 0;
|
|
42578
43080
|
}
|
|
42579
43081
|
}
|
|
42580
43082
|
|
|
42581
43083
|
// src/persistence/lock.ts
|
|
42582
43084
|
var LOCK_VERSION = 1;
|
|
42583
43085
|
function lockPath(configPath) {
|
|
42584
|
-
return
|
|
43086
|
+
return resolve10(dirname11(configPath), "workstation.lock");
|
|
42585
43087
|
}
|
|
42586
43088
|
async function lockConfig(configPath, config, runner) {
|
|
42587
43089
|
const path = lockPath(configPath);
|
|
42588
43090
|
const previousText = await readOptional(path);
|
|
42589
43091
|
const previous = previousText === void 0 ? emptyLock() : parseLock(previousText, path);
|
|
42590
|
-
const
|
|
42591
|
-
const existing = new Map(
|
|
43092
|
+
const target2 = previous.targets.find(({ machine }) => machine === config.context.machine);
|
|
43093
|
+
const existing = new Map(target2?.resources.map((entry2) => [entry2.id, entry2]));
|
|
42592
43094
|
const resources = [];
|
|
42593
43095
|
const entries = [];
|
|
42594
43096
|
for (const resource of config.resources) {
|
|
@@ -42632,10 +43134,10 @@ function emptyLock() {
|
|
|
42632
43134
|
function stringifyLock(lock) {
|
|
42633
43135
|
return stringify({
|
|
42634
43136
|
version: lock.version,
|
|
42635
|
-
targets: lock.targets.map((
|
|
42636
|
-
machine:
|
|
42637
|
-
platform:
|
|
42638
|
-
resources:
|
|
43137
|
+
targets: lock.targets.map((target2) => ({
|
|
43138
|
+
machine: target2.machine,
|
|
43139
|
+
platform: target2.platform,
|
|
43140
|
+
resources: target2.resources.map((entry2) => ({
|
|
42639
43141
|
id: entry2.id,
|
|
42640
43142
|
fingerprint: entry2.fingerprint,
|
|
42641
43143
|
...entry2.lockedVersion ? { locked_version: entry2.lockedVersion } : {}
|
|
@@ -42651,19 +43153,19 @@ function parseLock(text, path) {
|
|
|
42651
43153
|
if (!Array.isArray(document2.targets)) throw new Error(`Lock targets must be an array in ${path}`);
|
|
42652
43154
|
const machines = /* @__PURE__ */ new Set();
|
|
42653
43155
|
const targets = document2.targets.map((value2, targetIndex) => {
|
|
42654
|
-
const
|
|
42655
|
-
const machine = requireString2(
|
|
43156
|
+
const target2 = requireTable2(value2, `targets[${targetIndex}]`);
|
|
43157
|
+
const machine = requireString2(target2.machine, `targets[${targetIndex}].machine`);
|
|
42656
43158
|
if (machines.has(machine)) throw new Error(`Duplicate machine ${machine} in ${path}`);
|
|
42657
43159
|
machines.add(machine);
|
|
42658
|
-
const platform = requireString2(
|
|
43160
|
+
const platform = requireString2(target2.platform, `targets[${targetIndex}].platform`);
|
|
42659
43161
|
if (platform !== "darwin" && platform !== "linux") {
|
|
42660
43162
|
throw new Error(`Invalid platform for ${machine} in ${path}`);
|
|
42661
43163
|
}
|
|
42662
|
-
if (!Array.isArray(
|
|
43164
|
+
if (!Array.isArray(target2.resources)) {
|
|
42663
43165
|
throw new Error(`Lock resources for ${machine} must be an array in ${path}`);
|
|
42664
43166
|
}
|
|
42665
43167
|
const ids = /* @__PURE__ */ new Set();
|
|
42666
|
-
const resources =
|
|
43168
|
+
const resources = target2.resources.map((value3, resourceIndex) => {
|
|
42667
43169
|
const entry2 = requireTable2(value3, `targets[${targetIndex}].resources[${resourceIndex}]`);
|
|
42668
43170
|
const id = requireString2(entry2.id, "lock resource id");
|
|
42669
43171
|
if (ids.has(id)) throw new Error(`Duplicate resource ${id} for ${machine} in ${path}`);
|
|
@@ -42690,7 +43192,7 @@ function requireString2(value2, field) {
|
|
|
42690
43192
|
}
|
|
42691
43193
|
async function readOptional(path) {
|
|
42692
43194
|
try {
|
|
42693
|
-
return await
|
|
43195
|
+
return await readFile9(path, "utf8");
|
|
42694
43196
|
} catch (error) {
|
|
42695
43197
|
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
|
|
42696
43198
|
return void 0;
|
|
@@ -42701,7 +43203,7 @@ async function readOptional(path) {
|
|
|
42701
43203
|
async function atomicWrite2(path, contents) {
|
|
42702
43204
|
const temporary = `${path}.${process.pid}.tmp`;
|
|
42703
43205
|
await writeFile5(temporary, contents, { mode: 420 });
|
|
42704
|
-
await
|
|
43206
|
+
await rename7(temporary, path);
|
|
42705
43207
|
}
|
|
42706
43208
|
|
|
42707
43209
|
// src/resources/tasks.ts
|
|
@@ -42718,7 +43220,7 @@ async function runTask(config, name, args, runner) {
|
|
|
42718
43220
|
|
|
42719
43221
|
// src/config/init.ts
|
|
42720
43222
|
import { mkdir as mkdir9, writeFile as writeFile6 } from "fs/promises";
|
|
42721
|
-
import { dirname as
|
|
43223
|
+
import { dirname as dirname12, resolve as resolve11 } from "path";
|
|
42722
43224
|
var starter = `import type { ConfigDefinition } from "@dovocode/workstation";
|
|
42723
43225
|
|
|
42724
43226
|
export default {
|
|
@@ -42739,17 +43241,17 @@ export default {
|
|
|
42739
43241
|
} satisfies ConfigDefinition;
|
|
42740
43242
|
`;
|
|
42741
43243
|
async function initConfig(path = "workstation.config.ts") {
|
|
42742
|
-
const
|
|
42743
|
-
await mkdir9(
|
|
43244
|
+
const target2 = resolve11(path);
|
|
43245
|
+
await mkdir9(dirname12(target2), { recursive: true });
|
|
42744
43246
|
try {
|
|
42745
|
-
await writeFile6(
|
|
43247
|
+
await writeFile6(target2, starter, { encoding: "utf8", flag: "wx" });
|
|
42746
43248
|
} catch (error) {
|
|
42747
43249
|
if (error instanceof Error && "code" in error && error.code === "EEXIST") {
|
|
42748
|
-
throw new Error(`Configuration already exists: ${
|
|
43250
|
+
throw new Error(`Configuration already exists: ${target2}. No changes made.`, { cause: error });
|
|
42749
43251
|
}
|
|
42750
43252
|
throw error;
|
|
42751
43253
|
}
|
|
42752
|
-
return
|
|
43254
|
+
return target2;
|
|
42753
43255
|
}
|
|
42754
43256
|
|
|
42755
43257
|
// src/cli.ts
|
|
@@ -42763,27 +43265,27 @@ async function main() {
|
|
|
42763
43265
|
return;
|
|
42764
43266
|
}
|
|
42765
43267
|
const sourcePath = await findConfig(options.config);
|
|
43268
|
+
if (!options.listTasks && !options.task) console.log(`Loading configuration: ${sourcePath}`);
|
|
42766
43269
|
const sourceConfig = await loadConfig(sourcePath, options.machine);
|
|
42767
|
-
const runner = new ProcessRunner();
|
|
43270
|
+
const runner = new ProcessRunner({ progress: !options.listTasks, verbose: options.verbose });
|
|
42768
43271
|
if (options.listTasks) {
|
|
42769
43272
|
for (const [name, task] of Object.entries(sourceConfig.tasks ?? {})) console.log(`${name}${task.description ? ` ${task.description}` : ""}`);
|
|
42770
|
-
for (const [name,
|
|
43273
|
+
for (const [name, target2] of Object.entries(sourceConfig.aliases ?? {})) console.log(`${name} -> ${target2}`);
|
|
42771
43274
|
return;
|
|
42772
43275
|
}
|
|
42773
43276
|
if (options.task) {
|
|
42774
|
-
const result = await runTask(sourceConfig, options.task, options.taskArgs,
|
|
42775
|
-
process.stdout.write(result.stdout);
|
|
42776
|
-
process.stderr.write(result.stderr);
|
|
43277
|
+
const result = await runTask(sourceConfig, options.task, options.taskArgs, new ProcessRunner({ progress: true, verbose: true }));
|
|
42777
43278
|
process.exitCode = result.exitCode;
|
|
42778
43279
|
return;
|
|
42779
43280
|
}
|
|
43281
|
+
console.log(`Resolving package versions for ${sourceConfig.context.machine} (${sourceConfig.resources.length} resources)...`);
|
|
42780
43282
|
const locked = await lockConfig(sourcePath, sourceConfig, runner);
|
|
43283
|
+
console.log(`Lock: ${locked.path}${locked.changed ? " (updated)" : " (unchanged)"}`);
|
|
42781
43284
|
const outputPath = manifestPath(locked.config);
|
|
42782
43285
|
await writeManifest(outputPath, locked.config);
|
|
42783
|
-
const config = await readManifest(outputPath);
|
|
42784
|
-
const actions = await applyPlan(config, runner, printAction);
|
|
42785
|
-
console.log(`Lock: ${locked.path}${locked.changed ? " (updated)" : ""}`);
|
|
42786
43286
|
console.log(`Manifest: ${outputPath}`);
|
|
43287
|
+
const config = await readManifest(outputPath);
|
|
43288
|
+
const actions = await applyPlan(config, runner, printAction, (message) => console.log(message));
|
|
42787
43289
|
console.log(
|
|
42788
43290
|
actions.length === 0 ? "Workstation is already converged." : `Reconciled ${actions.length} action(s).`
|
|
42789
43291
|
);
|
|
@@ -42795,11 +43297,14 @@ function parseArguments(args) {
|
|
|
42795
43297
|
let taskArgs = [];
|
|
42796
43298
|
let listTasks = false;
|
|
42797
43299
|
let init = false;
|
|
43300
|
+
let verbose = false;
|
|
42798
43301
|
for (let index = 0; index < args.length; index += 1) {
|
|
42799
43302
|
const argument = args[index];
|
|
42800
43303
|
if (argument === "init") {
|
|
42801
43304
|
if (init) throw new Error("init may only be specified once");
|
|
42802
43305
|
init = true;
|
|
43306
|
+
} else if (argument === "--verbose" || argument === "-v") {
|
|
43307
|
+
verbose = true;
|
|
42803
43308
|
} else if (argument === "--list-tasks") {
|
|
42804
43309
|
listTasks = true;
|
|
42805
43310
|
} else if (argument && !argument.startsWith("-") && argument !== "help") {
|
|
@@ -42820,7 +43325,7 @@ function parseArguments(args) {
|
|
|
42820
43325
|
}
|
|
42821
43326
|
if (listTasks && task) throw new Error("--list-tasks cannot be combined with a task name");
|
|
42822
43327
|
if (init && (task || listTasks || machine)) throw new Error("init supports only --config PATH and --help");
|
|
42823
|
-
return { init, taskArgs, listTasks, ...task ? { task } : {}, ...config ? { config } : {}, ...machine ? { machine } : {} };
|
|
43328
|
+
return { init, verbose, taskArgs, listTasks, ...task ? { task } : {}, ...config ? { config } : {}, ...machine ? { machine } : {} };
|
|
42824
43329
|
}
|
|
42825
43330
|
function requireValue(args, index, option) {
|
|
42826
43331
|
const value2 = args[index];
|
|
@@ -42843,6 +43348,7 @@ Options:
|
|
|
42843
43348
|
--config PATH Configuration entry point (default: workstation.config.ts)
|
|
42844
43349
|
--machine NAME Override the short hostname
|
|
42845
43350
|
--list-tasks List configured tasks and aliases
|
|
43351
|
+
-v, --verbose Also stream inspection and version-query output
|
|
42846
43352
|
-h, --help Show this help`);
|
|
42847
43353
|
}
|
|
42848
43354
|
main().catch((error) => {
|