@m8t-stack/cli 0.2.90 → 0.2.92
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +414 -163
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -921,8 +921,8 @@ async function mirrorBrainYaml(args) {
|
|
|
921
921
|
Accept: "application/vnd.github+json",
|
|
922
922
|
"X-GitHub-Api-Version": "2022-11-28"
|
|
923
923
|
};
|
|
924
|
-
const
|
|
925
|
-
for (let attempt = 1; attempt <=
|
|
924
|
+
const MAX_ATTEMPTS3 = 3;
|
|
925
|
+
for (let attempt = 1; attempt <= MAX_ATTEMPTS3; attempt++) {
|
|
926
926
|
const getRes = await fetch(url, { headers });
|
|
927
927
|
let sha;
|
|
928
928
|
let existing;
|
|
@@ -953,8 +953,8 @@ async function mirrorBrainYaml(args) {
|
|
|
953
953
|
if (putRes.ok) return;
|
|
954
954
|
const text = await putRes.text();
|
|
955
955
|
const retryable = putRes.status === 409 || putRes.status === 422;
|
|
956
|
-
if (retryable && attempt <
|
|
957
|
-
const suffix = retryable ? ` after ${
|
|
956
|
+
if (retryable && attempt < MAX_ATTEMPTS3) continue;
|
|
957
|
+
const suffix = retryable ? ` after ${MAX_ATTEMPTS3.toString()} attempts` : "";
|
|
958
958
|
throw new LocalCliError({
|
|
959
959
|
code: "BRAIN_YAML_MIRROR_FAILED",
|
|
960
960
|
message: `PUT .m8t/brain.yaml: HTTP ${putRes.status.toString()}${suffix}
|
|
@@ -1487,7 +1487,7 @@ function installFoundryDnsShim() {
|
|
|
1487
1487
|
}
|
|
1488
1488
|
|
|
1489
1489
|
// src/lib/package-version.ts
|
|
1490
|
-
var CLI_VERSION = "0.2.
|
|
1490
|
+
var CLI_VERSION = "0.2.92";
|
|
1491
1491
|
|
|
1492
1492
|
// src/lib/render-error.ts
|
|
1493
1493
|
init_errors();
|
|
@@ -24829,7 +24829,7 @@ function reportWhatIf(r, opts) {
|
|
|
24829
24829
|
const exitCode = r.unexpected.length === 0 ? 0 : 1;
|
|
24830
24830
|
if (opts.redact) {
|
|
24831
24831
|
const output = opts.json ? JSON.stringify({ summary: redactForReport(r), exitCode }, null, 2) : redactForLog(r);
|
|
24832
|
-
return { output, exitCode };
|
|
24832
|
+
return { output, exitCode, ...opts.json ? { diagnostic: redactForLog(r) } : {} };
|
|
24833
24833
|
}
|
|
24834
24834
|
if (opts.json) {
|
|
24835
24835
|
return { output: JSON.stringify({ summary: { noise: r.noise.length, pending: r.pending.length, unexpected: r.unexpected.length }, pending: r.pending, unexpected: r.unexpected, exitCode }, null, 2), exitCode };
|
|
@@ -25096,8 +25096,9 @@ var DeployCommand = class extends M8tCommand {
|
|
|
25096
25096
|
});
|
|
25097
25097
|
const changes = await runWhatIf({ resourceGroup: this.resourceGroup, repoRoot: repoRoot2, params: params2 });
|
|
25098
25098
|
const classified = classifyWhatIf(changes);
|
|
25099
|
-
const { output, exitCode } = reportWhatIf(classified, { json: mode === "json", redact: this.redact });
|
|
25099
|
+
const { output, exitCode, diagnostic } = reportWhatIf(classified, { json: mode === "json", redact: this.redact });
|
|
25100
25100
|
this.context.stdout.write(output + "\n");
|
|
25101
|
+
if (diagnostic) this.context.stderr.write(diagnostic + "\n");
|
|
25101
25102
|
return exitCode;
|
|
25102
25103
|
} catch (e) {
|
|
25103
25104
|
const { output, exitCode } = reportWhatIfFailure(e, { json: mode === "json", redact: this.redact });
|
|
@@ -31792,6 +31793,7 @@ async function getAciState(opts) {
|
|
|
31792
31793
|
}
|
|
31793
31794
|
|
|
31794
31795
|
// src/lib/bootstrap-finalize.ts
|
|
31796
|
+
init_errors();
|
|
31795
31797
|
import * as fs39 from "fs/promises";
|
|
31796
31798
|
import * as os20 from "os";
|
|
31797
31799
|
import * as path44 from "path";
|
|
@@ -32708,10 +32710,10 @@ var DEFAULT_WORKERS = [
|
|
|
32708
32710
|
{ name: "ezra", label: "Ezra - Azure Expert", brain: "ezra-brain" }
|
|
32709
32711
|
];
|
|
32710
32712
|
function renderInstallSummary(args) {
|
|
32711
|
-
const
|
|
32713
|
+
const open4 = args.webappUrl ? `${args.webappUrl} (or run: m8t open)` : "run: m8t open";
|
|
32712
32714
|
const lines = [
|
|
32713
32715
|
"\u2705 Your m8t platform is ready to use.",
|
|
32714
|
-
` Open it: ${
|
|
32716
|
+
` Open it: ${open4}`,
|
|
32715
32717
|
""
|
|
32716
32718
|
];
|
|
32717
32719
|
if (args.brainOrg) {
|
|
@@ -32735,10 +32737,10 @@ function renderInstallSummary(args) {
|
|
|
32735
32737
|
|
|
32736
32738
|
// src/lib/companion-install.ts
|
|
32737
32739
|
import { constants as constants2 } from "fs";
|
|
32738
|
-
import * as
|
|
32739
|
-
import * as
|
|
32740
|
+
import * as fs38 from "fs/promises";
|
|
32741
|
+
import * as path42 from "path";
|
|
32740
32742
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
32741
|
-
import { execFile, spawn as spawn7 } from "child_process";
|
|
32743
|
+
import { execFile as execFile2, spawn as spawn7 } from "child_process";
|
|
32742
32744
|
|
|
32743
32745
|
// src/lib/companion-artifact.ts
|
|
32744
32746
|
import { createHash as createHash7 } from "crypto";
|
|
@@ -33016,6 +33018,265 @@ async function copyArtifactPayload(sourceRoot, targetRoot, manifest) {
|
|
|
33016
33018
|
await verifyArtifactPayload(targetRoot, manifest);
|
|
33017
33019
|
}
|
|
33018
33020
|
|
|
33021
|
+
// src/lib/companion-download.ts
|
|
33022
|
+
import { createHash as createHash8 } from "crypto";
|
|
33023
|
+
import { execFile } from "child_process";
|
|
33024
|
+
import { createReadStream } from "fs";
|
|
33025
|
+
import * as fs37 from "fs/promises";
|
|
33026
|
+
import * as os18 from "os";
|
|
33027
|
+
import * as path41 from "path";
|
|
33028
|
+
init_errors();
|
|
33029
|
+
var MAX_ASSET_BYTES = 512 * 1024 * 1024;
|
|
33030
|
+
var MAX_ATTEMPTS2 = 3;
|
|
33031
|
+
var BACKOFF_MS = [1e3, 3e3];
|
|
33032
|
+
function megabytes(bytes) {
|
|
33033
|
+
return `${Math.round(bytes / 1e6).toString()} MB`;
|
|
33034
|
+
}
|
|
33035
|
+
function extractArchive(archive, into) {
|
|
33036
|
+
return new Promise((resolve6, reject) => {
|
|
33037
|
+
execFile("tar", ["-xzf", archive, "-C", into], { windowsHide: true }, (error) => {
|
|
33038
|
+
if (error) reject(new Error(`Could not unpack the companion build: ${error.message}`));
|
|
33039
|
+
else resolve6();
|
|
33040
|
+
});
|
|
33041
|
+
});
|
|
33042
|
+
}
|
|
33043
|
+
function companionDownloadDirectory(homeDirectory) {
|
|
33044
|
+
return path41.join(homeDirectory, ".m8t", "companion", "downloads");
|
|
33045
|
+
}
|
|
33046
|
+
function companionAssetFor(component, platform = process.platform, architecture = process.arch) {
|
|
33047
|
+
const target = companionTargetFor(platform, architecture);
|
|
33048
|
+
if (target === null) return null;
|
|
33049
|
+
const pinned = component.targets[target];
|
|
33050
|
+
return pinned ? { target, asset: pinned.asset, sha256: pinned.sha256 } : null;
|
|
33051
|
+
}
|
|
33052
|
+
function tooLarge(asset) {
|
|
33053
|
+
return new LocalCliError({
|
|
33054
|
+
code: "COMPANION_ASSET_TOO_LARGE",
|
|
33055
|
+
message: `${asset} is larger than this command will accept.`
|
|
33056
|
+
});
|
|
33057
|
+
}
|
|
33058
|
+
function parseContentRange(value) {
|
|
33059
|
+
const matched = /^bytes (\d+)-(\d+)\/(\d+|\*)$/u.exec((value ?? "").trim());
|
|
33060
|
+
if (!matched?.[1] || !matched[3]) return null;
|
|
33061
|
+
return {
|
|
33062
|
+
start: Number(matched[1]),
|
|
33063
|
+
total: matched[3] === "*" ? null : Number(matched[3])
|
|
33064
|
+
};
|
|
33065
|
+
}
|
|
33066
|
+
function declaredLength(response) {
|
|
33067
|
+
const raw = response.headers.get("content-length");
|
|
33068
|
+
if (raw === null) return null;
|
|
33069
|
+
const value = Number(raw);
|
|
33070
|
+
return Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
33071
|
+
}
|
|
33072
|
+
async function usableBytes(partial) {
|
|
33073
|
+
const status = await fs37.lstat(partial).catch(() => null);
|
|
33074
|
+
if (status === null) return 0;
|
|
33075
|
+
if (!status.isFile()) {
|
|
33076
|
+
await fs37.rm(partial, { recursive: true, force: true }).catch(() => void 0);
|
|
33077
|
+
if (await fs37.lstat(partial).then(() => true, () => false)) {
|
|
33078
|
+
throw new LocalCliError({
|
|
33079
|
+
code: "COMPANION_DOWNLOAD_PATH_OCCUPIED",
|
|
33080
|
+
message: `${partial} is not a file and could not be cleared.`,
|
|
33081
|
+
hint: "Something that is not a download is sitting where the companion build goes. Remove it, then run this again."
|
|
33082
|
+
});
|
|
33083
|
+
}
|
|
33084
|
+
return 0;
|
|
33085
|
+
}
|
|
33086
|
+
return status.size;
|
|
33087
|
+
}
|
|
33088
|
+
async function sweepForeignPartials(directory, keep) {
|
|
33089
|
+
const entries = await fs37.readdir(directory).catch(() => []);
|
|
33090
|
+
await Promise.all(
|
|
33091
|
+
entries.filter((name) => name.endsWith(".part") && name !== keep).map((name) => fs37.rm(path41.join(directory, name), { recursive: true, force: true }))
|
|
33092
|
+
);
|
|
33093
|
+
}
|
|
33094
|
+
function sha256File3(filePath) {
|
|
33095
|
+
return new Promise((resolve6, reject) => {
|
|
33096
|
+
const hash = createHash8("sha256");
|
|
33097
|
+
const stream = createReadStream(filePath);
|
|
33098
|
+
stream.on("error", reject);
|
|
33099
|
+
stream.on("data", (chunk) => hash.update(chunk));
|
|
33100
|
+
stream.on("end", () => {
|
|
33101
|
+
resolve6(hash.digest("hex"));
|
|
33102
|
+
});
|
|
33103
|
+
});
|
|
33104
|
+
}
|
|
33105
|
+
async function transferToDisk(input) {
|
|
33106
|
+
let lastError;
|
|
33107
|
+
for (let attempt = 0; attempt < MAX_ATTEMPTS2; attempt++) {
|
|
33108
|
+
let existing = await usableBytes(input.partial);
|
|
33109
|
+
if (existing > input.maxAssetBytes) {
|
|
33110
|
+
await fs37.rm(input.partial, { force: true }).catch(() => void 0);
|
|
33111
|
+
existing = 0;
|
|
33112
|
+
}
|
|
33113
|
+
let received = existing;
|
|
33114
|
+
let answered = false;
|
|
33115
|
+
try {
|
|
33116
|
+
const controller = new AbortController();
|
|
33117
|
+
const response = await input.fetchImpl(input.url, {
|
|
33118
|
+
signal: controller.signal,
|
|
33119
|
+
...existing > 0 ? { headers: { Range: `bytes=${existing.toString()}-` } } : {}
|
|
33120
|
+
});
|
|
33121
|
+
answered = true;
|
|
33122
|
+
if (response.status === 416) {
|
|
33123
|
+
await response.body?.cancel().catch(() => void 0);
|
|
33124
|
+
if (existing > 0 && await sha256File3(input.partial).catch(() => null) === input.expectedDigest) {
|
|
33125
|
+
return;
|
|
33126
|
+
}
|
|
33127
|
+
await fs37.rm(input.partial, { force: true }).catch(() => void 0);
|
|
33128
|
+
lastError = new Error("the interrupted download no longer fits the published asset");
|
|
33129
|
+
continue;
|
|
33130
|
+
}
|
|
33131
|
+
if (!response.ok) {
|
|
33132
|
+
await response.body?.cancel().catch(() => void 0);
|
|
33133
|
+
const failed = new LocalCliError({
|
|
33134
|
+
code: "COMPANION_ASSET_FETCH_FAILED",
|
|
33135
|
+
message: `GET ${input.url} returned HTTP ${response.status.toString()}.`,
|
|
33136
|
+
hint: "The release the platform is pinned to should carry this file. Try again, or run 'm8t companion repair' once the network is back."
|
|
33137
|
+
});
|
|
33138
|
+
if (response.status < 500) throw failed;
|
|
33139
|
+
lastError = failed;
|
|
33140
|
+
if (attempt + 1 < MAX_ATTEMPTS2) {
|
|
33141
|
+
input.onEvent({ kind: "retrying", received: existing, attempt: attempt + 1, cause: "server-error" });
|
|
33142
|
+
await input.delay(BACKOFF_MS[Math.min(attempt, BACKOFF_MS.length - 1)]);
|
|
33143
|
+
}
|
|
33144
|
+
continue;
|
|
33145
|
+
}
|
|
33146
|
+
let offset;
|
|
33147
|
+
let total;
|
|
33148
|
+
if (response.status === 206) {
|
|
33149
|
+
const range = parseContentRange(response.headers.get("content-range"));
|
|
33150
|
+
if (range?.start !== existing) {
|
|
33151
|
+
await response.body?.cancel().catch(() => void 0);
|
|
33152
|
+
await fs37.rm(input.partial, { force: true }).catch(() => void 0);
|
|
33153
|
+
lastError = new Error("the server answered a different range than the one requested");
|
|
33154
|
+
continue;
|
|
33155
|
+
}
|
|
33156
|
+
offset = existing;
|
|
33157
|
+
total = range.total;
|
|
33158
|
+
} else {
|
|
33159
|
+
offset = 0;
|
|
33160
|
+
total = declaredLength(response);
|
|
33161
|
+
}
|
|
33162
|
+
const declaredBody = declaredLength(response);
|
|
33163
|
+
if (total !== null && total > input.maxAssetBytes || declaredBody !== null && declaredBody > input.maxAssetBytes) {
|
|
33164
|
+
await response.body?.cancel().catch(() => void 0);
|
|
33165
|
+
await fs37.rm(input.partial, { force: true }).catch(() => void 0);
|
|
33166
|
+
throw tooLarge(input.asset);
|
|
33167
|
+
}
|
|
33168
|
+
if (offset > 0) input.onEvent({ kind: "resumed", received: offset, total });
|
|
33169
|
+
received = offset;
|
|
33170
|
+
const body = response.body;
|
|
33171
|
+
if (body === null) throw new Error("the response carried no body");
|
|
33172
|
+
const handle = await fs37.open(input.partial, offset > 0 ? "a" : "w", 384);
|
|
33173
|
+
try {
|
|
33174
|
+
for await (const chunk of body) {
|
|
33175
|
+
received += chunk.byteLength;
|
|
33176
|
+
if (received > input.maxAssetBytes) {
|
|
33177
|
+
controller.abort();
|
|
33178
|
+
throw tooLarge(input.asset);
|
|
33179
|
+
}
|
|
33180
|
+
await handle.write(chunk);
|
|
33181
|
+
input.onEvent({ kind: "progress", received, total });
|
|
33182
|
+
}
|
|
33183
|
+
} finally {
|
|
33184
|
+
await handle.close();
|
|
33185
|
+
}
|
|
33186
|
+
if (total !== null && received < total) {
|
|
33187
|
+
throw new Error(
|
|
33188
|
+
`the transfer stopped at ${received.toString()} of ${total.toString()} bytes`
|
|
33189
|
+
);
|
|
33190
|
+
}
|
|
33191
|
+
return;
|
|
33192
|
+
} catch (error) {
|
|
33193
|
+
if (error instanceof LocalCliError) {
|
|
33194
|
+
if (error.code === "COMPANION_ASSET_TOO_LARGE") {
|
|
33195
|
+
await fs37.rm(input.partial, { force: true }).catch(() => void 0);
|
|
33196
|
+
}
|
|
33197
|
+
throw error;
|
|
33198
|
+
}
|
|
33199
|
+
lastError = error;
|
|
33200
|
+
if (attempt + 1 < MAX_ATTEMPTS2) {
|
|
33201
|
+
input.onEvent({
|
|
33202
|
+
kind: "retrying",
|
|
33203
|
+
received,
|
|
33204
|
+
attempt: attempt + 1,
|
|
33205
|
+
cause: answered ? "dropped" : "not-connected"
|
|
33206
|
+
});
|
|
33207
|
+
await input.delay(BACKOFF_MS[Math.min(attempt, BACKOFF_MS.length - 1)]);
|
|
33208
|
+
}
|
|
33209
|
+
}
|
|
33210
|
+
}
|
|
33211
|
+
const kept = await fs37.lstat(input.partial).then(
|
|
33212
|
+
(status) => status.isFile() ? status.size : 0,
|
|
33213
|
+
() => 0
|
|
33214
|
+
);
|
|
33215
|
+
const keptLabel = megabytes(kept);
|
|
33216
|
+
throw new LocalCliError({
|
|
33217
|
+
code: "COMPANION_DOWNLOAD_INCOMPLETE",
|
|
33218
|
+
message: "The companion build could not be downloaded.",
|
|
33219
|
+
...keptLabel === "0 MB" ? {} : { hint: `The ${keptLabel} already downloaded is kept. Run it again to carry on from there.` },
|
|
33220
|
+
cause: lastError
|
|
33221
|
+
});
|
|
33222
|
+
}
|
|
33223
|
+
async function downloadCompanionArtifact(component, deps = {}) {
|
|
33224
|
+
const platform = deps.platform ?? process.platform;
|
|
33225
|
+
const architecture = deps.architecture ?? process.arch;
|
|
33226
|
+
const pinned = companionAssetFor(component, platform, architecture);
|
|
33227
|
+
if (pinned === null) {
|
|
33228
|
+
throw new LocalCliError({
|
|
33229
|
+
code: "COMPANION_NO_BUILD_FOR_HOST",
|
|
33230
|
+
message: `Release ${component.version} carries no desktop companion for ${platform}-${architecture}.`,
|
|
33231
|
+
hint: "The companions are built for macOS and Windows on arm64 and x64."
|
|
33232
|
+
});
|
|
33233
|
+
}
|
|
33234
|
+
const url = releaseAssetUrl(component.tag, pinned.asset);
|
|
33235
|
+
const directory = deps.downloadDirectory ?? companionDownloadDirectory(os18.homedir());
|
|
33236
|
+
const partialName = `${pinned.sha256}.part`;
|
|
33237
|
+
const partial = path41.join(directory, partialName);
|
|
33238
|
+
await fs37.mkdir(directory, { recursive: true, mode: 448 });
|
|
33239
|
+
await sweepForeignPartials(directory, partialName);
|
|
33240
|
+
await transferToDisk({
|
|
33241
|
+
url,
|
|
33242
|
+
asset: pinned.asset,
|
|
33243
|
+
partial,
|
|
33244
|
+
fetchImpl: deps.fetchImpl ?? fetch,
|
|
33245
|
+
onEvent: deps.onEvent ?? (() => void 0),
|
|
33246
|
+
delay: deps.delay ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms))),
|
|
33247
|
+
maxAssetBytes: deps.maxAssetBytes ?? MAX_ASSET_BYTES,
|
|
33248
|
+
expectedDigest: pinned.sha256
|
|
33249
|
+
});
|
|
33250
|
+
const digest = await sha256File3(partial);
|
|
33251
|
+
if (digest !== pinned.sha256) {
|
|
33252
|
+
await fs37.rm(partial, { force: true }).catch(() => void 0);
|
|
33253
|
+
throw new LocalCliError({
|
|
33254
|
+
code: "COMPANION_ASSET_DIGEST_MISMATCH",
|
|
33255
|
+
message: `${pinned.asset} does not match the digest the release pins for it.`,
|
|
33256
|
+
hint: "Nothing was unpacked. This is what a corrupted download or a substituted file looks like \u2014 retry, and report it if it persists."
|
|
33257
|
+
});
|
|
33258
|
+
}
|
|
33259
|
+
const root = await (deps.makeTemporaryDirectory ?? (() => fs37.mkdtemp(path41.join(os18.tmpdir(), "m8t-companion-"))))();
|
|
33260
|
+
const dispose = async () => {
|
|
33261
|
+
await fs37.rm(root, { recursive: true, force: true }).catch(() => void 0);
|
|
33262
|
+
};
|
|
33263
|
+
try {
|
|
33264
|
+
const unpacked = path41.join(root, "unpacked");
|
|
33265
|
+
await fs37.mkdir(unpacked, { recursive: false, mode: 448 });
|
|
33266
|
+
await (deps.extract ?? extractArchive)(partial, unpacked);
|
|
33267
|
+
return {
|
|
33268
|
+
version: component.version,
|
|
33269
|
+
artifactManifestPath: path41.join(unpacked, "artifact-v1.json"),
|
|
33270
|
+
dispose
|
|
33271
|
+
};
|
|
33272
|
+
} catch (error) {
|
|
33273
|
+
await dispose();
|
|
33274
|
+
throw error;
|
|
33275
|
+
} finally {
|
|
33276
|
+
await fs37.rm(partial, { force: true }).catch(() => void 0);
|
|
33277
|
+
}
|
|
33278
|
+
}
|
|
33279
|
+
|
|
33019
33280
|
// src/lib/companion-install.ts
|
|
33020
33281
|
function xmlEscape(value) {
|
|
33021
33282
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
@@ -33028,7 +33289,7 @@ function commandError(error, message) {
|
|
|
33028
33289
|
}
|
|
33029
33290
|
function execFileAsync(file2, args) {
|
|
33030
33291
|
return new Promise((resolve6, reject) => {
|
|
33031
|
-
|
|
33292
|
+
execFile2(file2, [...args], { windowsHide: true }, (error) => {
|
|
33032
33293
|
if (error) reject(commandError(error, "Login-item command failed"));
|
|
33033
33294
|
else resolve6();
|
|
33034
33295
|
});
|
|
@@ -33036,7 +33297,7 @@ function execFileAsync(file2, args) {
|
|
|
33036
33297
|
}
|
|
33037
33298
|
function execFileOutput(file2, args) {
|
|
33038
33299
|
return new Promise((resolve6, reject) => {
|
|
33039
|
-
|
|
33300
|
+
execFile2(
|
|
33040
33301
|
file2,
|
|
33041
33302
|
[...args],
|
|
33042
33303
|
{ windowsHide: true },
|
|
@@ -33049,17 +33310,17 @@ function execFileOutput(file2, args) {
|
|
|
33049
33310
|
}
|
|
33050
33311
|
async function setCompanionStartAtLogin(input) {
|
|
33051
33312
|
if (input.platform === "darwin") {
|
|
33052
|
-
const launchAgents =
|
|
33053
|
-
const registration =
|
|
33313
|
+
const launchAgents = path42.join(input.homeDirectory, "Library", "LaunchAgents");
|
|
33314
|
+
const registration = path42.join(
|
|
33054
33315
|
launchAgents,
|
|
33055
33316
|
"com.m8t.companion.plist"
|
|
33056
33317
|
);
|
|
33057
33318
|
await assertNotSymlink(registration, "Start-at-login registration");
|
|
33058
33319
|
if (!input.enabled) {
|
|
33059
|
-
await
|
|
33320
|
+
await fs38.rm(registration, { force: true });
|
|
33060
33321
|
return;
|
|
33061
33322
|
}
|
|
33062
|
-
await
|
|
33323
|
+
await fs38.mkdir(launchAgents, { recursive: true, mode: 448 });
|
|
33063
33324
|
const plist = '<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0"><dict><key>Label</key><string>com.m8t.companion</string><key>ProgramArguments</key><array><string>' + xmlEscape(input.executable) + "</string></array><key>RunAtLoad</key><true/></dict></plist>\n";
|
|
33064
33325
|
await atomicWriteText(registration, plist, 384);
|
|
33065
33326
|
return;
|
|
@@ -33111,7 +33372,7 @@ async function setCompanionStartAtLogin(input) {
|
|
|
33111
33372
|
}
|
|
33112
33373
|
async function getCompanionStartAtLogin(input) {
|
|
33113
33374
|
if (input.platform === "darwin") {
|
|
33114
|
-
const registration =
|
|
33375
|
+
const registration = path42.join(
|
|
33115
33376
|
input.homeDirectory,
|
|
33116
33377
|
"Library",
|
|
33117
33378
|
"LaunchAgents",
|
|
@@ -33173,26 +33434,29 @@ function assertSupported(options) {
|
|
|
33173
33434
|
}
|
|
33174
33435
|
function companionInstallPaths(options) {
|
|
33175
33436
|
assertSupported(options);
|
|
33176
|
-
const companionState =
|
|
33437
|
+
const companionState = path42.join(
|
|
33177
33438
|
options.homeDirectory,
|
|
33178
33439
|
".m8t",
|
|
33179
33440
|
"companion"
|
|
33180
33441
|
);
|
|
33181
|
-
const targetRoot = options.platform === "darwin" ?
|
|
33442
|
+
const targetRoot = options.platform === "darwin" ? path42.join(
|
|
33182
33443
|
options.homeDirectory,
|
|
33183
33444
|
"Applications",
|
|
33184
33445
|
"m8t Companion.app"
|
|
33185
|
-
) :
|
|
33186
|
-
options.localAppData ??
|
|
33446
|
+
) : path42.join(
|
|
33447
|
+
options.localAppData ?? path42.join(options.homeDirectory, "AppData", "Local"),
|
|
33187
33448
|
"m8t",
|
|
33188
33449
|
"companion",
|
|
33189
33450
|
"app"
|
|
33190
33451
|
);
|
|
33191
33452
|
return {
|
|
33192
33453
|
targetRoot,
|
|
33193
|
-
installManifest:
|
|
33194
|
-
runtimeBinding:
|
|
33195
|
-
preferences:
|
|
33454
|
+
installManifest: path42.join(companionState, "install-v1.json"),
|
|
33455
|
+
runtimeBinding: path42.join(companionState, "runtime-v1.json"),
|
|
33456
|
+
preferences: path42.join(companionState, "preferences-v1.json"),
|
|
33457
|
+
// Asked of the downloader rather than rebuilt here. Two modules computing
|
|
33458
|
+
// one path is how an uninstall comes to sweep a directory nothing writes.
|
|
33459
|
+
downloads: companionDownloadDirectory(options.homeDirectory)
|
|
33196
33460
|
};
|
|
33197
33461
|
}
|
|
33198
33462
|
function exactKeys2(value, keys) {
|
|
@@ -33203,7 +33467,7 @@ function parseInstallManifest(value) {
|
|
|
33203
33467
|
throw new Error("Install manifest schema is invalid");
|
|
33204
33468
|
}
|
|
33205
33469
|
const record = value;
|
|
33206
|
-
if (!exactKeys2(record, INSTALL_KEYS) || record.schemaVersion !== 1 || typeof record.version !== "string" || record.platform !== "darwin" && record.platform !== "win32" || record.architecture !== "arm64" && record.architecture !== "x64" || typeof record.entryRelativePath !== "string" || typeof record.artifactTreeSha256 !== "string" || !/^[a-f0-9]{64}$/u.test(record.artifactTreeSha256) || typeof record.ownedTargetRoot !== "string" || !
|
|
33470
|
+
if (!exactKeys2(record, INSTALL_KEYS) || record.schemaVersion !== 1 || typeof record.version !== "string" || record.platform !== "darwin" && record.platform !== "win32" || record.architecture !== "arm64" && record.architecture !== "x64" || typeof record.entryRelativePath !== "string" || typeof record.artifactTreeSha256 !== "string" || !/^[a-f0-9]{64}$/u.test(record.artifactTreeSha256) || typeof record.ownedTargetRoot !== "string" || !path42.isAbsolute(record.ownedTargetRoot)) {
|
|
33207
33471
|
throw new Error("Install manifest schema is invalid");
|
|
33208
33472
|
}
|
|
33209
33473
|
return record;
|
|
@@ -33213,7 +33477,7 @@ function parseRuntimeBinding(value, platform) {
|
|
|
33213
33477
|
throw new Error("Runtime binding schema is invalid");
|
|
33214
33478
|
}
|
|
33215
33479
|
const record = value;
|
|
33216
|
-
const paths = platform === "win32" ?
|
|
33480
|
+
const paths = platform === "win32" ? path42.win32 : path42.posix;
|
|
33217
33481
|
if (!exactKeys2(record, RUNTIME_KEYS) || record.schemaVersion !== 1 || typeof record.nodeExecutable !== "string" || !paths.isAbsolute(record.nodeExecutable) || typeof record.cliEntry !== "string" || !paths.isAbsolute(record.cliEntry) || typeof record.gatewayOrigin !== "string") {
|
|
33218
33482
|
throw new Error("Runtime binding schema is invalid");
|
|
33219
33483
|
}
|
|
@@ -33238,7 +33502,7 @@ function validateGatewayOrigin(value) {
|
|
|
33238
33502
|
}
|
|
33239
33503
|
async function assertNotSymlink(filePath, kind) {
|
|
33240
33504
|
try {
|
|
33241
|
-
if ((await
|
|
33505
|
+
if ((await fs38.lstat(filePath)).isSymbolicLink()) {
|
|
33242
33506
|
throw new Error(`${kind} is a symbolic link`);
|
|
33243
33507
|
}
|
|
33244
33508
|
} catch (error) {
|
|
@@ -33247,20 +33511,20 @@ async function assertNotSymlink(filePath, kind) {
|
|
|
33247
33511
|
}
|
|
33248
33512
|
}
|
|
33249
33513
|
async function assertOwnedDirectoryChain(anchor, targetDirectory) {
|
|
33250
|
-
const relative4 =
|
|
33251
|
-
if (relative4 === ".." || relative4.startsWith(`..${
|
|
33514
|
+
const relative4 = path42.relative(anchor, targetDirectory);
|
|
33515
|
+
if (relative4 === ".." || relative4.startsWith(`..${path42.sep}`) || path42.isAbsolute(relative4)) {
|
|
33252
33516
|
throw new Error("Companion owned directory escapes its trusted anchor");
|
|
33253
33517
|
}
|
|
33254
|
-
const segments = relative4.split(
|
|
33518
|
+
const segments = relative4.split(path42.sep).filter(Boolean);
|
|
33255
33519
|
let current = anchor;
|
|
33256
|
-
const anchorStatus = await
|
|
33520
|
+
const anchorStatus = await fs38.lstat(anchor);
|
|
33257
33521
|
if (anchorStatus.isSymbolicLink() || !anchorStatus.isDirectory()) {
|
|
33258
33522
|
throw new Error("Companion owned directory anchor is unsafe");
|
|
33259
33523
|
}
|
|
33260
33524
|
for (const segment of segments) {
|
|
33261
|
-
current =
|
|
33525
|
+
current = path42.join(current, segment);
|
|
33262
33526
|
try {
|
|
33263
|
-
const status = await
|
|
33527
|
+
const status = await fs38.lstat(current);
|
|
33264
33528
|
if (status.isSymbolicLink() || !status.isDirectory()) {
|
|
33265
33529
|
throw new Error("Companion owned directory contains a symbolic link");
|
|
33266
33530
|
}
|
|
@@ -33273,19 +33537,19 @@ async function assertOwnedDirectoryChain(anchor, targetDirectory) {
|
|
|
33273
33537
|
async function assertOwnedParents(options, paths) {
|
|
33274
33538
|
await assertOwnedDirectoryChain(
|
|
33275
33539
|
options.homeDirectory,
|
|
33276
|
-
|
|
33540
|
+
path42.dirname(paths.installManifest)
|
|
33277
33541
|
);
|
|
33278
|
-
const targetAnchor = options.platform === "win32" ? options.localAppData ??
|
|
33279
|
-
await assertOwnedDirectoryChain(targetAnchor,
|
|
33542
|
+
const targetAnchor = options.platform === "win32" ? options.localAppData ?? path42.join(options.homeDirectory, "AppData", "Local") : options.homeDirectory;
|
|
33543
|
+
await assertOwnedDirectoryChain(targetAnchor, path42.dirname(paths.targetRoot));
|
|
33280
33544
|
}
|
|
33281
33545
|
async function readRegularText(filePath, maxBytes) {
|
|
33282
|
-
const before = await
|
|
33546
|
+
const before = await fs38.lstat(filePath);
|
|
33283
33547
|
if (before.isSymbolicLink() || !before.isFile()) {
|
|
33284
33548
|
throw new Error("Companion state file is not a regular file");
|
|
33285
33549
|
}
|
|
33286
33550
|
let handle;
|
|
33287
33551
|
try {
|
|
33288
|
-
handle = await
|
|
33552
|
+
handle = await fs38.open(
|
|
33289
33553
|
filePath,
|
|
33290
33554
|
constants2.O_RDONLY | constants2.O_NOFOLLOW
|
|
33291
33555
|
);
|
|
@@ -33308,27 +33572,27 @@ async function atomicWriteJson(filePath, value) {
|
|
|
33308
33572
|
}
|
|
33309
33573
|
async function atomicWriteText(filePath, contents, mode) {
|
|
33310
33574
|
await assertNotSymlink(filePath, "Companion state file");
|
|
33311
|
-
await
|
|
33575
|
+
await fs38.mkdir(path42.dirname(filePath), {
|
|
33312
33576
|
recursive: true,
|
|
33313
33577
|
mode: 448
|
|
33314
33578
|
});
|
|
33315
33579
|
const temporary = `${filePath}.${randomUUID3()}.tmp`;
|
|
33316
33580
|
try {
|
|
33317
|
-
await
|
|
33581
|
+
await fs38.writeFile(temporary, contents, {
|
|
33318
33582
|
mode,
|
|
33319
33583
|
flag: "wx"
|
|
33320
33584
|
});
|
|
33321
|
-
await
|
|
33322
|
-
await
|
|
33585
|
+
await fs38.rename(temporary, filePath);
|
|
33586
|
+
await fs38.chmod(filePath, mode).catch(() => void 0);
|
|
33323
33587
|
} finally {
|
|
33324
|
-
await
|
|
33588
|
+
await fs38.rm(temporary, { force: true }).catch(() => void 0);
|
|
33325
33589
|
}
|
|
33326
33590
|
}
|
|
33327
33591
|
async function realRegularFile(filePath, executable) {
|
|
33328
|
-
const real = await
|
|
33329
|
-
const stat5 = await
|
|
33592
|
+
const real = await fs38.realpath(filePath);
|
|
33593
|
+
const stat5 = await fs38.stat(real);
|
|
33330
33594
|
if (!stat5.isFile()) throw new Error("Companion launch target is not a file");
|
|
33331
|
-
await
|
|
33595
|
+
await fs38.access(real, executable ? constants2.X_OK : constants2.R_OK);
|
|
33332
33596
|
return real;
|
|
33333
33597
|
}
|
|
33334
33598
|
function defaultLaunch(executable) {
|
|
@@ -33344,7 +33608,7 @@ async function companionIsRunning(platform, executable) {
|
|
|
33344
33608
|
if (platform !== "win32") return false;
|
|
33345
33609
|
let handle;
|
|
33346
33610
|
try {
|
|
33347
|
-
handle = await
|
|
33611
|
+
handle = await fs38.open(executable, "r+");
|
|
33348
33612
|
} catch (error) {
|
|
33349
33613
|
const code = error.code;
|
|
33350
33614
|
if (code === "ENOENT") return false;
|
|
@@ -33425,7 +33689,7 @@ async function statusCompanion(options) {
|
|
|
33425
33689
|
realRegularFile(runtime.nodeExecutable, options.platform !== "win32"),
|
|
33426
33690
|
realRegularFile(runtime.cliEntry, false)
|
|
33427
33691
|
]);
|
|
33428
|
-
const executable =
|
|
33692
|
+
const executable = path42.join(
|
|
33429
33693
|
paths.targetRoot,
|
|
33430
33694
|
...install.entryRelativePath.split("/")
|
|
33431
33695
|
);
|
|
@@ -33458,7 +33722,7 @@ async function snapshotFile(filePath) {
|
|
|
33458
33722
|
}
|
|
33459
33723
|
async function restoreFile(filePath, bytes) {
|
|
33460
33724
|
if (bytes === null) {
|
|
33461
|
-
await
|
|
33725
|
+
await fs38.rm(filePath, { force: true });
|
|
33462
33726
|
} else {
|
|
33463
33727
|
await atomicWriteJson(filePath, JSON.parse(bytes.toString("utf8")));
|
|
33464
33728
|
}
|
|
@@ -33473,8 +33737,8 @@ async function converge(options, force) {
|
|
|
33473
33737
|
if (artifactManifest.platform !== options.platform || artifactManifest.architecture !== options.architecture) {
|
|
33474
33738
|
throw new Error("Companion artifact does not match this OS and architecture");
|
|
33475
33739
|
}
|
|
33476
|
-
const payloadRoot =
|
|
33477
|
-
|
|
33740
|
+
const payloadRoot = path42.join(
|
|
33741
|
+
path42.dirname(options.artifactManifestPath),
|
|
33478
33742
|
"payload"
|
|
33479
33743
|
);
|
|
33480
33744
|
await verifyArtifactSource(payloadRoot, artifactManifest);
|
|
@@ -33494,7 +33758,7 @@ async function converge(options, force) {
|
|
|
33494
33758
|
if (priorInstalled) {
|
|
33495
33759
|
await assertCompanionNotRunning(
|
|
33496
33760
|
options,
|
|
33497
|
-
|
|
33761
|
+
path42.join(
|
|
33498
33762
|
priorInstalled.paths.targetRoot,
|
|
33499
33763
|
...priorInstalled.install.entryRelativePath.split("/")
|
|
33500
33764
|
)
|
|
@@ -33502,7 +33766,7 @@ async function converge(options, force) {
|
|
|
33502
33766
|
}
|
|
33503
33767
|
if (current.state === "not-installed") {
|
|
33504
33768
|
try {
|
|
33505
|
-
await
|
|
33769
|
+
await fs38.lstat(paths.targetRoot);
|
|
33506
33770
|
throw new Error(
|
|
33507
33771
|
"The fixed companion target exists without an owned install manifest"
|
|
33508
33772
|
);
|
|
@@ -33513,7 +33777,7 @@ async function converge(options, force) {
|
|
|
33513
33777
|
const stage = `${paths.targetRoot}.m8t-stage-${randomUUID3()}`;
|
|
33514
33778
|
const backup = `${paths.targetRoot}.m8t-backup-${randomUUID3()}`;
|
|
33515
33779
|
const copy = options.copyPayload ?? copyArtifactPayload;
|
|
33516
|
-
await
|
|
33780
|
+
await fs38.mkdir(path42.dirname(paths.targetRoot), {
|
|
33517
33781
|
recursive: true,
|
|
33518
33782
|
mode: 448
|
|
33519
33783
|
});
|
|
@@ -33530,12 +33794,12 @@ async function converge(options, force) {
|
|
|
33530
33794
|
try {
|
|
33531
33795
|
await copy(payloadRoot, stage, artifactManifest);
|
|
33532
33796
|
try {
|
|
33533
|
-
await
|
|
33797
|
+
await fs38.rename(paths.targetRoot, backup);
|
|
33534
33798
|
movedPrior = true;
|
|
33535
33799
|
} catch (error) {
|
|
33536
33800
|
if (error.code !== "ENOENT") throw error;
|
|
33537
33801
|
}
|
|
33538
|
-
await
|
|
33802
|
+
await fs38.rename(stage, paths.targetRoot);
|
|
33539
33803
|
installedStage = true;
|
|
33540
33804
|
const nodeExecutable = await realRegularFile(
|
|
33541
33805
|
options.nodeExecutable,
|
|
@@ -33577,7 +33841,7 @@ async function converge(options, force) {
|
|
|
33577
33841
|
await readClosedJson(paths.runtimeBinding),
|
|
33578
33842
|
options.platform
|
|
33579
33843
|
);
|
|
33580
|
-
const executable =
|
|
33844
|
+
const executable = path42.join(
|
|
33581
33845
|
paths.targetRoot,
|
|
33582
33846
|
...artifactManifest.entryRelativePath.split("/")
|
|
33583
33847
|
);
|
|
@@ -33587,7 +33851,7 @@ async function converge(options, force) {
|
|
|
33587
33851
|
platform: options.platform,
|
|
33588
33852
|
executable: ownedExecutable
|
|
33589
33853
|
}));
|
|
33590
|
-
priorLoginExecutable = priorInstalled ?
|
|
33854
|
+
priorLoginExecutable = priorInstalled ? path42.join(
|
|
33591
33855
|
priorInstalled.paths.targetRoot,
|
|
33592
33856
|
...priorInstalled.install.entryRelativePath.split("/")
|
|
33593
33857
|
) : executable;
|
|
@@ -33598,7 +33862,7 @@ async function converge(options, force) {
|
|
|
33598
33862
|
);
|
|
33599
33863
|
loginChanged = true;
|
|
33600
33864
|
await (options.launch ?? defaultLaunch)(executable);
|
|
33601
|
-
await
|
|
33865
|
+
await fs38.rm(backup, { recursive: true, force: true });
|
|
33602
33866
|
return {
|
|
33603
33867
|
state: "installed",
|
|
33604
33868
|
version: artifactManifest.version,
|
|
@@ -33612,15 +33876,15 @@ async function converge(options, force) {
|
|
|
33612
33876
|
() => void 0
|
|
33613
33877
|
);
|
|
33614
33878
|
}
|
|
33615
|
-
await
|
|
33879
|
+
await fs38.rm(stage, { recursive: true, force: true }).catch(() => void 0);
|
|
33616
33880
|
if (installedStage) {
|
|
33617
|
-
await
|
|
33881
|
+
await fs38.rm(paths.targetRoot, {
|
|
33618
33882
|
recursive: true,
|
|
33619
33883
|
force: true
|
|
33620
33884
|
}).catch(() => void 0);
|
|
33621
33885
|
}
|
|
33622
33886
|
if (movedPrior) {
|
|
33623
|
-
await
|
|
33887
|
+
await fs38.rename(backup, paths.targetRoot).catch(() => void 0);
|
|
33624
33888
|
}
|
|
33625
33889
|
await Promise.all([
|
|
33626
33890
|
restoreFile(paths.installManifest, snapshots[0]),
|
|
@@ -33640,25 +33904,29 @@ async function uninstallCompanion(options) {
|
|
|
33640
33904
|
assertSupported(options);
|
|
33641
33905
|
const expectedPaths = companionInstallPaths(options);
|
|
33642
33906
|
await assertOwnedParents(options, expectedPaths);
|
|
33907
|
+
const remove = options.removeOwnedPath ?? (async (ownedPath, recursive) => {
|
|
33908
|
+
await fs38.rm(ownedPath, { recursive, force: true });
|
|
33909
|
+
});
|
|
33643
33910
|
const installed2 = await readInstalled(options);
|
|
33644
|
-
if (!installed2)
|
|
33911
|
+
if (!installed2) {
|
|
33912
|
+
await remove(expectedPaths.downloads, true);
|
|
33913
|
+
return { state: "not-installed" };
|
|
33914
|
+
}
|
|
33645
33915
|
const { paths, install } = installed2;
|
|
33646
33916
|
if (install.ownedTargetRoot !== paths.targetRoot) {
|
|
33647
33917
|
throw new Error("Refusing to remove a non-owned companion target");
|
|
33648
33918
|
}
|
|
33649
|
-
const executable =
|
|
33919
|
+
const executable = path42.join(
|
|
33650
33920
|
paths.targetRoot,
|
|
33651
33921
|
...install.entryRelativePath.split("/")
|
|
33652
33922
|
);
|
|
33653
33923
|
await assertCompanionNotRunning(options, executable);
|
|
33654
33924
|
await (options.setStartAtLogin ?? (() => Promise.resolve()))(executable, false);
|
|
33655
|
-
const remove = options.removeOwnedPath ?? (async (ownedPath, recursive) => {
|
|
33656
|
-
await fs37.rm(ownedPath, { recursive, force: true });
|
|
33657
|
-
});
|
|
33658
33925
|
await remove(paths.targetRoot, true);
|
|
33659
33926
|
await remove(paths.installManifest, false);
|
|
33660
33927
|
await remove(paths.runtimeBinding, false);
|
|
33661
33928
|
await remove(paths.preferences, false);
|
|
33929
|
+
await remove(paths.downloads, true);
|
|
33662
33930
|
return { state: "not-installed" };
|
|
33663
33931
|
}
|
|
33664
33932
|
|
|
@@ -33679,84 +33947,55 @@ async function readCompanionRelease(source = { url: CHANNEL_LATEST_URL }, deps =
|
|
|
33679
33947
|
};
|
|
33680
33948
|
}
|
|
33681
33949
|
|
|
33682
|
-
// src/lib/companion-download.ts
|
|
33683
|
-
|
|
33684
|
-
|
|
33685
|
-
|
|
33686
|
-
|
|
33687
|
-
|
|
33688
|
-
|
|
33689
|
-
|
|
33690
|
-
|
|
33691
|
-
|
|
33692
|
-
|
|
33693
|
-
|
|
33694
|
-
|
|
33695
|
-
|
|
33696
|
-
|
|
33697
|
-
}
|
|
33698
|
-
|
|
33699
|
-
|
|
33700
|
-
|
|
33701
|
-
|
|
33702
|
-
|
|
33703
|
-
|
|
33704
|
-
|
|
33705
|
-
|
|
33706
|
-
|
|
33707
|
-
|
|
33708
|
-
|
|
33709
|
-
|
|
33710
|
-
|
|
33711
|
-
|
|
33712
|
-
|
|
33713
|
-
|
|
33714
|
-
|
|
33715
|
-
|
|
33716
|
-
|
|
33717
|
-
|
|
33718
|
-
|
|
33719
|
-
|
|
33720
|
-
|
|
33721
|
-
|
|
33722
|
-
|
|
33723
|
-
|
|
33724
|
-
|
|
33725
|
-
|
|
33726
|
-
|
|
33727
|
-
|
|
33728
|
-
|
|
33729
|
-
|
|
33730
|
-
});
|
|
33731
|
-
}
|
|
33732
|
-
const digest = createHash8("sha256").update(bytes).digest("hex");
|
|
33733
|
-
if (digest !== pinned.sha256) {
|
|
33734
|
-
throw new LocalCliError({
|
|
33735
|
-
code: "COMPANION_ASSET_DIGEST_MISMATCH",
|
|
33736
|
-
message: `${pinned.asset} does not match the digest the release pins for it.`,
|
|
33737
|
-
hint: "Nothing was unpacked. This is what a corrupted download or a substituted file looks like \u2014 retry, and report it if it persists."
|
|
33738
|
-
});
|
|
33739
|
-
}
|
|
33740
|
-
const root = await (deps.makeTemporaryDirectory ?? (() => fs38.mkdtemp(path42.join(os18.tmpdir(), "m8t-companion-"))))();
|
|
33741
|
-
const dispose = async () => {
|
|
33742
|
-
await fs38.rm(root, { recursive: true, force: true }).catch(() => void 0);
|
|
33950
|
+
// src/lib/companion-download-progress.ts
|
|
33951
|
+
var STEP_FRACTION = 0.25;
|
|
33952
|
+
var INTERVAL_MS = 3e4;
|
|
33953
|
+
function downloadProgressLines(options) {
|
|
33954
|
+
const now = options.now ?? Date.now;
|
|
33955
|
+
let lastStep = 0;
|
|
33956
|
+
let lastReceived = 0;
|
|
33957
|
+
let lastEmitAt = null;
|
|
33958
|
+
const emit = (line2) => {
|
|
33959
|
+
lastEmitAt = now();
|
|
33960
|
+
options.write(line2);
|
|
33961
|
+
};
|
|
33962
|
+
return (event) => {
|
|
33963
|
+
if (event.kind === "retrying") {
|
|
33964
|
+
emit(
|
|
33965
|
+
event.cause === "not-connected" ? " Couldn't connect \u2014 retrying.\n" : event.cause === "server-error" ? " The download service answered with an error \u2014 retrying.\n" : ` The connection dropped at ${megabytes(event.received)} \u2014 retrying.
|
|
33966
|
+
`
|
|
33967
|
+
);
|
|
33968
|
+
return;
|
|
33969
|
+
}
|
|
33970
|
+
if (event.kind === "resumed") {
|
|
33971
|
+
if (event.total !== null && event.total > 0) {
|
|
33972
|
+
lastStep = Math.floor(event.received / event.total / STEP_FRACTION);
|
|
33973
|
+
}
|
|
33974
|
+
lastReceived = event.received;
|
|
33975
|
+
emit(` Resuming an interrupted download from ${megabytes(event.received)}.
|
|
33976
|
+
`);
|
|
33977
|
+
return;
|
|
33978
|
+
}
|
|
33979
|
+
lastEmitAt ??= now();
|
|
33980
|
+
if (event.received < lastReceived) lastStep = 0;
|
|
33981
|
+
lastReceived = event.received;
|
|
33982
|
+
const total = event.total !== null && event.total > 0 ? event.total : null;
|
|
33983
|
+
if (total === null) {
|
|
33984
|
+
if (now() - lastEmitAt < INTERVAL_MS) return;
|
|
33985
|
+
emit(` ${megabytes(event.received)} downloaded.
|
|
33986
|
+
`);
|
|
33987
|
+
return;
|
|
33988
|
+
}
|
|
33989
|
+
const fraction = event.received / total;
|
|
33990
|
+
if (fraction >= 1) return;
|
|
33991
|
+
const step = Math.floor(fraction / STEP_FRACTION);
|
|
33992
|
+
if (step <= lastStep && now() - lastEmitAt < INTERVAL_MS) return;
|
|
33993
|
+
lastStep = step;
|
|
33994
|
+
emit(
|
|
33995
|
+
` ${Math.floor(fraction * 100).toString()}% \xB7 ${megabytes(event.received)} of ${megabytes(total)}
|
|
33996
|
+
`
|
|
33997
|
+
);
|
|
33743
33998
|
};
|
|
33744
|
-
try {
|
|
33745
|
-
const archive = path42.join(root, pinned.asset);
|
|
33746
|
-
await fs38.writeFile(archive, bytes, { mode: 384 });
|
|
33747
|
-
const unpacked = path42.join(root, "unpacked");
|
|
33748
|
-
await fs38.mkdir(unpacked, { recursive: false, mode: 448 });
|
|
33749
|
-
await (deps.extract ?? extractArchive)(archive, unpacked);
|
|
33750
|
-
await fs38.rm(archive, { force: true });
|
|
33751
|
-
return {
|
|
33752
|
-
version: component.version,
|
|
33753
|
-
artifactManifestPath: path42.join(unpacked, "artifact-v1.json"),
|
|
33754
|
-
dispose
|
|
33755
|
-
};
|
|
33756
|
-
} catch (error) {
|
|
33757
|
-
await dispose();
|
|
33758
|
-
throw error;
|
|
33759
|
-
}
|
|
33760
33999
|
}
|
|
33761
34000
|
|
|
33762
34001
|
// src/commands/companion/install.ts
|
|
@@ -33777,7 +34016,7 @@ function defaultLocalCompanionInstallOptions() {
|
|
|
33777
34016
|
};
|
|
33778
34017
|
}
|
|
33779
34018
|
async function convergeCompanionFromChannel(converge2, options = {}) {
|
|
33780
|
-
const { from: stagedDirectory, resourceGroup, platformVersion } = options;
|
|
34019
|
+
const { from: stagedDirectory, resourceGroup, platformVersion, onDownloadEvent } = options;
|
|
33781
34020
|
let artifactManifestPath;
|
|
33782
34021
|
let dispose = () => Promise.resolve();
|
|
33783
34022
|
if (stagedDirectory !== void 0) {
|
|
@@ -33787,7 +34026,9 @@ async function convergeCompanionFromChannel(converge2, options = {}) {
|
|
|
33787
34026
|
platformVersion !== void 0 ? { channel: true, version: platformTag(platformVersion) } : { url: CHANNEL_LATEST_URL }
|
|
33788
34027
|
);
|
|
33789
34028
|
if (release === null) return { state: "not-released" };
|
|
33790
|
-
const build = await downloadCompanionArtifact(release.component
|
|
34029
|
+
const build = await downloadCompanionArtifact(release.component, {
|
|
34030
|
+
...onDownloadEvent !== void 0 ? { onEvent: onDownloadEvent } : {}
|
|
34031
|
+
});
|
|
33791
34032
|
artifactManifestPath = build.artifactManifestPath;
|
|
33792
34033
|
dispose = build.dispose;
|
|
33793
34034
|
}
|
|
@@ -33840,11 +34081,13 @@ var CompanionInstallCommand = class extends M8tCommand {
|
|
|
33840
34081
|
description: "Which deployment to bind to, when the subscription holds more than one."
|
|
33841
34082
|
});
|
|
33842
34083
|
async executeCommand() {
|
|
34084
|
+
const stdout = this.context.stdout;
|
|
33843
34085
|
return runCompanionInstallCommand(
|
|
33844
|
-
|
|
34086
|
+
stdout,
|
|
33845
34087
|
() => convergeCompanionFromChannel(installCompanion, {
|
|
33846
34088
|
...this.from !== void 0 ? { from: this.from } : {},
|
|
33847
|
-
...this.resourceGroup !== void 0 ? { resourceGroup: this.resourceGroup } : {}
|
|
34089
|
+
...this.resourceGroup !== void 0 ? { resourceGroup: this.resourceGroup } : {},
|
|
34090
|
+
onDownloadEvent: downloadProgressLines({ write: (line2) => stdout.write(line2) })
|
|
33848
34091
|
})
|
|
33849
34092
|
);
|
|
33850
34093
|
}
|
|
@@ -33864,7 +34107,7 @@ async function looksLikeCheckout(dir2) {
|
|
|
33864
34107
|
return false;
|
|
33865
34108
|
}
|
|
33866
34109
|
}
|
|
33867
|
-
var
|
|
34110
|
+
var defaultFinalizeDeps = Object.freeze({
|
|
33868
34111
|
discoverGateway,
|
|
33869
34112
|
writeConfig,
|
|
33870
34113
|
ensureGatewayRedirectUri,
|
|
@@ -33872,12 +34115,13 @@ var defaultDeps3 = {
|
|
|
33872
34115
|
reactiveSeed: reactiveSeedOnInstallComplete,
|
|
33873
34116
|
companionsSupportHost,
|
|
33874
34117
|
companionStatus: () => statusCompanion(defaultLocalCompanionInstallOptions()),
|
|
33875
|
-
convergeCompanion: (platformVersion) => convergeCompanionFromChannel(installCompanion, {
|
|
33876
|
-
...platformVersion !== void 0 ? { platformVersion } : {}
|
|
34118
|
+
convergeCompanion: (platformVersion, onDownloadEvent) => convergeCompanionFromChannel(installCompanion, {
|
|
34119
|
+
...platformVersion !== void 0 ? { platformVersion } : {},
|
|
34120
|
+
...onDownloadEvent !== void 0 ? { onDownloadEvent } : {}
|
|
33877
34121
|
}),
|
|
33878
34122
|
homedir: () => os20.homedir()
|
|
33879
|
-
};
|
|
33880
|
-
async function finalizeInstall(args, deps =
|
|
34123
|
+
});
|
|
34124
|
+
async function finalizeInstall(args, deps = defaultFinalizeDeps) {
|
|
33881
34125
|
const markerDir = path44.join(deps.homedir(), ".m8t");
|
|
33882
34126
|
const markerPath = path44.join(markerDir, "repo-root");
|
|
33883
34127
|
const cwd = process.cwd();
|
|
@@ -34015,7 +34259,10 @@ ${colors.field("Then open a brand new chat/session")} \u2014 new skills + MCP se
|
|
|
34015
34259
|
`
|
|
34016
34260
|
);
|
|
34017
34261
|
try {
|
|
34018
|
-
const companion = await deps.convergeCompanion(
|
|
34262
|
+
const companion = await deps.convergeCompanion(
|
|
34263
|
+
pinned,
|
|
34264
|
+
downloadProgressLines({ write: args.stdout })
|
|
34265
|
+
);
|
|
34019
34266
|
if (companion.state === "installed") {
|
|
34020
34267
|
args.stdout("Desktop companions installed - they are at the edge of your screen.\n\n");
|
|
34021
34268
|
} else if (companion.state === "not-released") {
|
|
@@ -34031,7 +34278,7 @@ They arrive with a platform release \u2014 run: m8t platform update
|
|
|
34031
34278
|
);
|
|
34032
34279
|
}
|
|
34033
34280
|
} catch (e) {
|
|
34034
|
-
const reason = e instanceof Error ? e.message : String(e);
|
|
34281
|
+
const reason = e instanceof LocalCliError && e.code === "COMPANION_DOWNLOAD_INCOMPLETE" && e.hint !== void 0 ? e.hint : e instanceof Error ? e.message : String(e);
|
|
34035
34282
|
args.stdout(
|
|
34036
34283
|
`Platform installation succeeded, but the desktop companions need repair.
|
|
34037
34284
|
Run: m8t companion repair
|
|
@@ -34680,9 +34927,9 @@ async function openChatInvite(deps = {}) {
|
|
|
34680
34927
|
if (!invite.ok) return CHAT_UNAVAILABLE_LINE;
|
|
34681
34928
|
const print = deps.print === true;
|
|
34682
34929
|
if (!print) {
|
|
34683
|
-
const
|
|
34930
|
+
const open4 = deps.open ?? tryOpenUrl;
|
|
34684
34931
|
try {
|
|
34685
|
-
await
|
|
34932
|
+
await open4(invite.url);
|
|
34686
34933
|
} catch {
|
|
34687
34934
|
}
|
|
34688
34935
|
}
|
|
@@ -36449,7 +36696,7 @@ async function runCompanionBridgeServe(stdin, stdout, stderr, deps = {}) {
|
|
|
36449
36696
|
}
|
|
36450
36697
|
|
|
36451
36698
|
// src/commands/companion/bridge.ts
|
|
36452
|
-
var
|
|
36699
|
+
var defaultDeps3 = {
|
|
36453
36700
|
roster: rosterCompanions,
|
|
36454
36701
|
send: sendCompanionMessage,
|
|
36455
36702
|
converse: converseCompanionMessage,
|
|
@@ -36487,7 +36734,7 @@ function exitFor(terminal) {
|
|
|
36487
36734
|
}
|
|
36488
36735
|
return 2;
|
|
36489
36736
|
}
|
|
36490
|
-
async function runCompanionBridge(stdin, stdout, stderr, deps =
|
|
36737
|
+
async function runCompanionBridge(stdin, stdout, stderr, deps = defaultDeps3) {
|
|
36491
36738
|
let request;
|
|
36492
36739
|
try {
|
|
36493
36740
|
request = parseRequestLine(await readSingleRequest(stdin));
|
|
@@ -36659,10 +36906,14 @@ var CompanionRepairCommand = class extends M8tCommand {
|
|
|
36659
36906
|
description: "Which deployment to bind to, when the subscription holds more than one."
|
|
36660
36907
|
});
|
|
36661
36908
|
async executeCommand() {
|
|
36909
|
+
const stdout = this.context.stdout;
|
|
36662
36910
|
return runCompanionRepairCommand(
|
|
36663
|
-
|
|
36911
|
+
stdout,
|
|
36664
36912
|
() => convergeCompanionFromChannel(repairCompanion, {
|
|
36665
|
-
...this.resourceGroup !== void 0 ? { resourceGroup: this.resourceGroup } : {}
|
|
36913
|
+
...this.resourceGroup !== void 0 ? { resourceGroup: this.resourceGroup } : {},
|
|
36914
|
+
// Repair is the command a founder reaches for after an interrupted
|
|
36915
|
+
// install, so it is the one most likely to pick up a partial download.
|
|
36916
|
+
onDownloadEvent: downloadProgressLines({ write: (line2) => stdout.write(line2) })
|
|
36666
36917
|
})
|
|
36667
36918
|
);
|
|
36668
36919
|
}
|