@meetless/mla 0.1.4
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/LICENSE +201 -0
- package/README.md +81 -0
- package/dist/build-info.json +9 -0
- package/dist/bundles/ask-core.js +396 -0
- package/dist/bundles/mcp.js +16592 -0
- package/dist/bundles/trace-core.js +263 -0
- package/dist/cli.js +828 -0
- package/dist/commands/activate.js +781 -0
- package/dist/commands/adoption.js +130 -0
- package/dist/commands/ask.js +290 -0
- package/dist/commands/context.js +114 -0
- package/dist/commands/debug.js +313 -0
- package/dist/commands/doctor.js +1021 -0
- package/dist/commands/enrich.js +427 -0
- package/dist/commands/evidence.js +229 -0
- package/dist/commands/flush.js +184 -0
- package/dist/commands/graph.js +104 -0
- package/dist/commands/init.js +272 -0
- package/dist/commands/internal-active-review.js +322 -0
- package/dist/commands/internal-auto-index.js +188 -0
- package/dist/commands/internal-capture-decisions.js +320 -0
- package/dist/commands/internal-evidence-correlate.js +239 -0
- package/dist/commands/internal-evidence-hooks.js +240 -0
- package/dist/commands/internal-evidence-inject.js +231 -0
- package/dist/commands/internal-finalize.js +221 -0
- package/dist/commands/internal-pretool-observe.js +225 -0
- package/dist/commands/internal-refresh.js +136 -0
- package/dist/commands/internal-session-nudge.js +120 -0
- package/dist/commands/internal-steer-sync.js +117 -0
- package/dist/commands/internal-turn-recap.js +140 -0
- package/dist/commands/kb.js +375 -0
- package/dist/commands/kb_add.js +681 -0
- package/dist/commands/kb_forget.js +283 -0
- package/dist/commands/kb_move.js +45 -0
- package/dist/commands/kb_pending.js +410 -0
- package/dist/commands/kb_personal.js +149 -0
- package/dist/commands/kb_promote.js +188 -0
- package/dist/commands/kb_purge.js +168 -0
- package/dist/commands/kb_reingest.js +335 -0
- package/dist/commands/kb_retime.js +170 -0
- package/dist/commands/kb_review.js +391 -0
- package/dist/commands/kb_revision.js +179 -0
- package/dist/commands/kb_show.js +385 -0
- package/dist/commands/label.js +226 -0
- package/dist/commands/login.js +295 -0
- package/dist/commands/logout.js +108 -0
- package/dist/commands/mcp-supervisor.js +93 -0
- package/dist/commands/mcp.js +227 -0
- package/dist/commands/queue-prune.js +98 -0
- package/dist/commands/review.js +358 -0
- package/dist/commands/rewire.js +124 -0
- package/dist/commands/rules.js +728 -0
- package/dist/commands/scan-context.js +67 -0
- package/dist/commands/session.js +347 -0
- package/dist/commands/stats.js +479 -0
- package/dist/commands/status.js +61 -0
- package/dist/commands/summary.js +250 -0
- package/dist/commands/turn.js +114 -0
- package/dist/commands/uninstall.js +222 -0
- package/dist/commands/whoami.js +102 -0
- package/dist/commands/workspace.js +130 -0
- package/dist/hooks-template/ce0-post-tool-use.sh +34 -0
- package/dist/hooks-template/ce0-session-start.sh +49 -0
- package/dist/hooks-template/ce0-stop.sh +29 -0
- package/dist/hooks-template/ce0-user-prompt-submit.sh +38 -0
- package/dist/hooks-template/common.sh +934 -0
- package/dist/hooks-template/event-batch-filter.jq +67 -0
- package/dist/hooks-template/flush.sh +503 -0
- package/dist/hooks-template/post-tool-use.sh +423 -0
- package/dist/hooks-template/pre-tool-use.sh +69 -0
- package/dist/hooks-template/session-start.sh +140 -0
- package/dist/hooks-template/stop.sh +308 -0
- package/dist/hooks-template/user-prompt-submit.sh +1162 -0
- package/dist/lib/activation.js +79 -0
- package/dist/lib/active-conflict-cache.js +141 -0
- package/dist/lib/active-memory.js +59 -0
- package/dist/lib/active-review-runner.js +26 -0
- package/dist/lib/agent-decision/index.js +25 -0
- package/dist/lib/agent-decision/keys.js +49 -0
- package/dist/lib/agent-decision/normalize-claude.js +183 -0
- package/dist/lib/agent-decision/types.js +21 -0
- package/dist/lib/agent-decision/validate.js +216 -0
- package/dist/lib/analytics/capture.js +96 -0
- package/dist/lib/analytics/command-event.js +267 -0
- package/dist/lib/analytics/consent.js +58 -0
- package/dist/lib/analytics/coverage-gap.js +96 -0
- package/dist/lib/analytics/envelope.js +236 -0
- package/dist/lib/analytics/event-id.js +86 -0
- package/dist/lib/analytics/evidence.js +150 -0
- package/dist/lib/analytics/followthrough.js +194 -0
- package/dist/lib/analytics/forwarder.js +109 -0
- package/dist/lib/analytics/logs.js +78 -0
- package/dist/lib/analytics/metrics.js +78 -0
- package/dist/lib/analytics/recorder.js +92 -0
- package/dist/lib/analytics/review-analytics.js +75 -0
- package/dist/lib/analytics/sequence.js +77 -0
- package/dist/lib/analytics/store.js +131 -0
- package/dist/lib/analytics/turn-recap.js +279 -0
- package/dist/lib/artifact_id.js +108 -0
- package/dist/lib/auth-breaker.js +161 -0
- package/dist/lib/auto-index.js +112 -0
- package/dist/lib/classifier.js +88 -0
- package/dist/lib/config.js +298 -0
- package/dist/lib/conflict-advisory.js +64 -0
- package/dist/lib/debug-bundle.js +520 -0
- package/dist/lib/enrichment/ingest.js +301 -0
- package/dist/lib/enrichment/plan.js +253 -0
- package/dist/lib/enrichment/protocol.js +359 -0
- package/dist/lib/enrichment/scout-brief.js +176 -0
- package/dist/lib/failure-telemetry.js +444 -0
- package/dist/lib/git.js +200 -0
- package/dist/lib/governance-cache.js +77 -0
- package/dist/lib/governed-path-cache.js +76 -0
- package/dist/lib/http.js +677 -0
- package/dist/lib/identity-envelope.js +23 -0
- package/dist/lib/kb-candidate.js +65 -0
- package/dist/lib/kb_acl.js +98 -0
- package/dist/lib/login.js +353 -0
- package/dist/lib/mcp-fetchers.js +130 -0
- package/dist/lib/mcp-restart.js +47 -0
- package/dist/lib/observability.js +805 -0
- package/dist/lib/open-url.js +33 -0
- package/dist/lib/orphan-guard.js +70 -0
- package/dist/lib/packaged.js +21 -0
- package/dist/lib/reconcile-sessions.js +171 -0
- package/dist/lib/redactor.js +89 -0
- package/dist/lib/relationship-candidate-query.js +27 -0
- package/dist/lib/render.js +611 -0
- package/dist/lib/rules/applicability.js +64 -0
- package/dist/lib/rules/attest-code-rule-version.js +47 -0
- package/dist/lib/rules/attest-notes-location.js +217 -0
- package/dist/lib/rules/attest-rule-version.js +69 -0
- package/dist/lib/rules/canonical-json.js +97 -0
- package/dist/lib/rules/ce0-emit.js +64 -0
- package/dist/lib/rules/ce0-evidence.js +281 -0
- package/dist/lib/rules/ce0-recall-sample.js +82 -0
- package/dist/lib/rules/ce0-rule.js +55 -0
- package/dist/lib/rules/ce0-sampling-bucket.js +15 -0
- package/dist/lib/rules/ce0-store.js +683 -0
- package/dist/lib/rules/ce0-telemetry-project.js +93 -0
- package/dist/lib/rules/ce0-telemetry.js +158 -0
- package/dist/lib/rules/code-rule-registry.js +17 -0
- package/dist/lib/rules/command-match.js +185 -0
- package/dist/lib/rules/consult-evidence-binding.js +27 -0
- package/dist/lib/rules/consultation-capture-adapter.js +193 -0
- package/dist/lib/rules/content-match.js +56 -0
- package/dist/lib/rules/deny-admission.js +99 -0
- package/dist/lib/rules/durable-observation.js +190 -0
- package/dist/lib/rules/enforce-notes-version.js +421 -0
- package/dist/lib/rules/evaluation-input-hash.js +126 -0
- package/dist/lib/rules/evaluator.js +108 -0
- package/dist/lib/rules/inert-rule-families.js +51 -0
- package/dist/lib/rules/input-authority-resolver.js +241 -0
- package/dist/lib/rules/interception-schema.js +170 -0
- package/dist/lib/rules/interception-store.js +267 -0
- package/dist/lib/rules/live-input-authority.js +66 -0
- package/dist/lib/rules/local-matcher.js +108 -0
- package/dist/lib/rules/local-observe.js +79 -0
- package/dist/lib/rules/local-rule-version-repo.js +214 -0
- package/dist/lib/rules/memory-requirement.js +109 -0
- package/dist/lib/rules/notes-observe.js +39 -0
- package/dist/lib/rules/notes-path.js +261 -0
- package/dist/lib/rules/notes-rule.js +75 -0
- package/dist/lib/rules/observe-adapter.js +114 -0
- package/dist/lib/rules/observed-rule-hash.js +119 -0
- package/dist/lib/rules/prompt-submit-adapter.js +132 -0
- package/dist/lib/rules/requirement-subject.js +240 -0
- package/dist/lib/rules/rule-activity.js +67 -0
- package/dist/lib/rules/rule-version-hash.js +151 -0
- package/dist/lib/rules/runtime-scope.js +55 -0
- package/dist/lib/rules/stop-adapter.js +116 -0
- package/dist/lib/rules/stop-response-snapshot.js +174 -0
- package/dist/lib/rules/types.js +10 -0
- package/dist/lib/rules/ulid.js +46 -0
- package/dist/lib/rules/version-evaluation.js +156 -0
- package/dist/lib/scanner/agent-memory.js +99 -0
- package/dist/lib/scanner/bootstrap-summary.js +87 -0
- package/dist/lib/scanner/cache.js +59 -0
- package/dist/lib/scanner/frontmatter.js +42 -0
- package/dist/lib/scanner/parse-directives.js +69 -0
- package/dist/lib/scanner/parse-structured.js +72 -0
- package/dist/lib/scanner/render.js +73 -0
- package/dist/lib/scanner/scan.js +132 -0
- package/dist/lib/scanner/score.js +38 -0
- package/dist/lib/scanner/scout-mission.js +126 -0
- package/dist/lib/scanner/types.js +7 -0
- package/dist/lib/session-scope.js +195 -0
- package/dist/lib/spool.js +355 -0
- package/dist/lib/staleness.js +100 -0
- package/dist/lib/steer-cache.js +87 -0
- package/dist/lib/tagged-reference.js +20 -0
- package/dist/lib/temporal.js +109 -0
- package/dist/lib/turn-recap-emit.js +67 -0
- package/dist/lib/unwire.js +253 -0
- package/dist/lib/update-check.js +469 -0
- package/dist/lib/update-notifier.js +217 -0
- package/dist/lib/upgrade-apply.js +643 -0
- package/dist/lib/wire.js +1087 -0
- package/dist/lib/workspace.js +96 -0
- package/dist/lib/zip.js +154 -0
- package/dist/pretool-entry.js +37 -0
- package/package.json +75 -0
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.RELEASE_TRIPLES = exports.DEFAULT_MANIFEST_URL = exports.DEFAULT_UPDATE_URL = exports.UPDATE_STATE_FILE = exports.UPDATE_CHECK_INTERVAL_MS = void 0;
|
|
37
|
+
exports.parseManifest = parseManifest;
|
|
38
|
+
exports.verifyManifestSignature = verifyManifestSignature;
|
|
39
|
+
exports.currentTriple = currentTriple;
|
|
40
|
+
exports.selectArtifact = selectArtifact;
|
|
41
|
+
exports.isBelowMinVersion = isBelowMinVersion;
|
|
42
|
+
exports.parseVersion = parseVersion;
|
|
43
|
+
exports.isNewerVersion = isNewerVersion;
|
|
44
|
+
exports.isCI = isCI;
|
|
45
|
+
exports.notifierDisabled = notifierDisabled;
|
|
46
|
+
exports.upgradeKillSwitch = upgradeKillSwitch;
|
|
47
|
+
exports.autoUpgradeDisabled = autoUpgradeDisabled;
|
|
48
|
+
exports.resolveAutoApply = resolveAutoApply;
|
|
49
|
+
exports.shouldRunCheck = shouldRunCheck;
|
|
50
|
+
exports.shouldShowNag = shouldShowNag;
|
|
51
|
+
exports.detectInstallMethod = detectInstallMethod;
|
|
52
|
+
exports.upgradeCommandFor = upgradeCommandFor;
|
|
53
|
+
exports.formatUpdateNag = formatUpdateNag;
|
|
54
|
+
exports.planUpgrade = planUpgrade;
|
|
55
|
+
exports.parseState = parseState;
|
|
56
|
+
exports.serializeState = serializeState;
|
|
57
|
+
// Update notifier, copied from gh's model: a background version check on a 24h
|
|
58
|
+
// throttle plus an install-method-aware upgrade nag. Two rules, both deliberate:
|
|
59
|
+
//
|
|
60
|
+
// 1. NEVER auto-apply. We detect how `mla` was installed and print the right
|
|
61
|
+
// upgrade command; we never rename a binary over a brew/npm-managed path
|
|
62
|
+
// (that corrupts their metadata). Detect-and-redirect, not self-replace.
|
|
63
|
+
// 2. NEVER block or spam. The fetch runs in a detached child so the parent
|
|
64
|
+
// exits instantly; the nag shows only on a real TTY, off CI, and honors
|
|
65
|
+
// MLA_NO_UPDATE_NOTIFIER.
|
|
66
|
+
//
|
|
67
|
+
// This file is the PURE core (throttle, version compare, install-method
|
|
68
|
+
// detection, nag text). The IO wrappers (spawn the child, fetch GitHub, read/
|
|
69
|
+
// write the cache) live in update-notifier.ts so this stays unit-testable.
|
|
70
|
+
const path = __importStar(require("path"));
|
|
71
|
+
const os = __importStar(require("os"));
|
|
72
|
+
const crypto = __importStar(require("crypto"));
|
|
73
|
+
exports.UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24h, gh's cadence
|
|
74
|
+
exports.UPDATE_STATE_FILE = "update-check.json";
|
|
75
|
+
// Where the update check learns the latest published version: a plaintext file
|
|
76
|
+
// on the public meetless-public bucket holding the bare version (e.g. "0.4.2"),
|
|
77
|
+
// written by release.yml next to the binaries. We are not open-sourced yet, so
|
|
78
|
+
// there is no public GitHub Releases feed; this keeps the nag and the installer
|
|
79
|
+
// agreeing on the same release host. Override with MLA_UPDATE_URL.
|
|
80
|
+
exports.DEFAULT_UPDATE_URL = "https://storage.googleapis.com/meetless-public/cli/releases/latest/VERSION";
|
|
81
|
+
// --- signed release manifest -------------------------------------------------
|
|
82
|
+
// The signed manifest is the source of truth the upgrade path reads: the latest
|
|
83
|
+
// version, the floor below which a client is forced to upgrade, and a per-triple
|
|
84
|
+
// {url, sha256} so a client can download and verify the exact bytes for its own
|
|
85
|
+
// platform. It is fetched alongside a detached Ed25519 signature (manifest.json
|
|
86
|
+
// .sig) so a tampered manifest is rejected before any byte is trusted.
|
|
87
|
+
//
|
|
88
|
+
// Hosted on the same public bucket as the binaries so the installer, the cask,
|
|
89
|
+
// and the upgrade path all agree on one release host. The bucket can later sit
|
|
90
|
+
// behind the meetless.ai load balancer with zero code change (override the URL).
|
|
91
|
+
exports.DEFAULT_MANIFEST_URL = "https://storage.googleapis.com/meetless-public/cli/releases/latest/manifest.json";
|
|
92
|
+
// The three triples we publish. Hard invariants: install.sh, the Homebrew cask,
|
|
93
|
+
// and this list MUST stay byte-identical or a platform silently loses upgrades.
|
|
94
|
+
exports.RELEASE_TRIPLES = [
|
|
95
|
+
"aarch64-apple-darwin",
|
|
96
|
+
"x86_64-apple-darwin",
|
|
97
|
+
"x86_64-unknown-linux-gnu",
|
|
98
|
+
];
|
|
99
|
+
const SHA256_RE = /^[0-9a-f]{64}$/;
|
|
100
|
+
const BARE_SEMVER_RE = /^\d+\.\d+\.\d+([.-][0-9A-Za-z.-]+)?$/;
|
|
101
|
+
// Allow https anywhere; allow http ONLY for loopback hosts. Loopback can't be
|
|
102
|
+
// MITM'd, which is exactly what the local eval server needs, and a real release
|
|
103
|
+
// URL is always https so production never downgrades the transport.
|
|
104
|
+
function isAllowedArtifactUrl(u) {
|
|
105
|
+
try {
|
|
106
|
+
const url = new URL(u);
|
|
107
|
+
if (url.protocol === "https:")
|
|
108
|
+
return true;
|
|
109
|
+
if (url.protocol === "http:" &&
|
|
110
|
+
(url.hostname === "127.0.0.1" || url.hostname === "::1" || url.hostname === "localhost")) {
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Parse + validate a manifest's JSON text. Returns null on ANY shape violation
|
|
120
|
+
// (wrong schemaVersion, bad semver, non-hex sha, disallowed url, empty artifact
|
|
121
|
+
// set) so a malformed or truncated manifest is treated as "no update", never
|
|
122
|
+
// acted on. Pure: callers verify the signature over the raw bytes first.
|
|
123
|
+
function parseManifest(raw) {
|
|
124
|
+
if (!raw)
|
|
125
|
+
return null;
|
|
126
|
+
let o;
|
|
127
|
+
try {
|
|
128
|
+
o = JSON.parse(raw);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
if (!o || typeof o !== "object")
|
|
134
|
+
return null;
|
|
135
|
+
if (o.schemaVersion !== 1)
|
|
136
|
+
return null;
|
|
137
|
+
if (typeof o.channel !== "string" || !o.channel)
|
|
138
|
+
return null;
|
|
139
|
+
if (typeof o.version !== "string" || !BARE_SEMVER_RE.test(o.version))
|
|
140
|
+
return null;
|
|
141
|
+
if (typeof o.minVersion !== "string" || !BARE_SEMVER_RE.test(o.minVersion))
|
|
142
|
+
return null;
|
|
143
|
+
if (typeof o.releasedAt !== "string" || !o.releasedAt)
|
|
144
|
+
return null;
|
|
145
|
+
if (!o.artifacts || typeof o.artifacts !== "object")
|
|
146
|
+
return null;
|
|
147
|
+
const artifacts = {};
|
|
148
|
+
for (const [triple, a] of Object.entries(o.artifacts)) {
|
|
149
|
+
if (!a || typeof a !== "object")
|
|
150
|
+
return null;
|
|
151
|
+
const url = a.url;
|
|
152
|
+
const sha256 = a.sha256;
|
|
153
|
+
if (typeof url !== "string" || !isAllowedArtifactUrl(url))
|
|
154
|
+
return null;
|
|
155
|
+
if (typeof sha256 !== "string" || !SHA256_RE.test(sha256))
|
|
156
|
+
return null;
|
|
157
|
+
artifacts[triple] = { url, sha256 };
|
|
158
|
+
}
|
|
159
|
+
if (Object.keys(artifacts).length === 0)
|
|
160
|
+
return null;
|
|
161
|
+
const m = {
|
|
162
|
+
schemaVersion: 1,
|
|
163
|
+
channel: o.channel,
|
|
164
|
+
version: o.version,
|
|
165
|
+
minVersion: o.minVersion,
|
|
166
|
+
releasedAt: o.releasedAt,
|
|
167
|
+
artifacts,
|
|
168
|
+
};
|
|
169
|
+
if (typeof o.notes === "string")
|
|
170
|
+
m.notes = o.notes;
|
|
171
|
+
return m;
|
|
172
|
+
}
|
|
173
|
+
// Verify a detached Ed25519 signature over the EXACT manifest bytes against a
|
|
174
|
+
// trust list of SPKI PEM public keys. True iff any key verifies (so the prod key
|
|
175
|
+
// and a rotation key can be trusted at once). Ed25519 takes a null algorithm in
|
|
176
|
+
// Node's crypto (the hash is intrinsic). A malformed key or signature returns
|
|
177
|
+
// false, never throws: an unverifiable manifest is simply not trusted.
|
|
178
|
+
function verifyManifestSignature(manifestBytes, signatureB64, publicKeysPem) {
|
|
179
|
+
if (!signatureB64 || publicKeysPem.length === 0)
|
|
180
|
+
return false;
|
|
181
|
+
let sig;
|
|
182
|
+
try {
|
|
183
|
+
sig = Buffer.from(signatureB64.trim(), "base64");
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
if (sig.length === 0)
|
|
189
|
+
return false;
|
|
190
|
+
for (const pem of publicKeysPem) {
|
|
191
|
+
if (!pem || !pem.trim())
|
|
192
|
+
continue;
|
|
193
|
+
try {
|
|
194
|
+
const key = crypto.createPublicKey(pem);
|
|
195
|
+
if (crypto.verify(null, manifestBytes, key, sig))
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
// a bad key in the trust list is skipped, not fatal
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
// Map this process's platform/arch to a release triple, or null if unsupported
|
|
205
|
+
// (e.g. win32, or linux-arm64 which we do not publish). Mirrors install.sh's
|
|
206
|
+
// detect_target so the upgrade path picks the same artifact the installer would.
|
|
207
|
+
function currentTriple(platform, arch) {
|
|
208
|
+
if (platform === "darwin") {
|
|
209
|
+
if (arch === "arm64")
|
|
210
|
+
return "aarch64-apple-darwin";
|
|
211
|
+
if (arch === "x64")
|
|
212
|
+
return "x86_64-apple-darwin";
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
if (platform === "linux") {
|
|
216
|
+
if (arch === "x64")
|
|
217
|
+
return "x86_64-unknown-linux-gnu";
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
function selectArtifact(manifest, triple) {
|
|
223
|
+
if (!triple)
|
|
224
|
+
return null;
|
|
225
|
+
return manifest.artifacts[triple] ?? null;
|
|
226
|
+
}
|
|
227
|
+
// True iff `current` is strictly below `minVersion` (the forced-upgrade floor).
|
|
228
|
+
// Reuses the dev-build-safe comparison: an unparseable current is never "below".
|
|
229
|
+
function isBelowMinVersion(current, minVersion) {
|
|
230
|
+
return isNewerVersion(minVersion, current);
|
|
231
|
+
}
|
|
232
|
+
// --- version comparison ------------------------------------------------------
|
|
233
|
+
// Parse "1.2.3" / "v1.2.3" / "1.2.3-dirty" / "abc123-dirty" into [major,minor,
|
|
234
|
+
// patch], or null if it has no clean numeric core. Build suffixes (a git sha,
|
|
235
|
+
// "-dirty", "+meta") are ignored: a dev build like "b6a81f7a-dirty" has no
|
|
236
|
+
// semver and must never be compared as if it were behind or ahead.
|
|
237
|
+
function parseVersion(v) {
|
|
238
|
+
if (!v)
|
|
239
|
+
return null;
|
|
240
|
+
const m = /^v?(\d+)\.(\d+)\.(\d+)/.exec(v.trim());
|
|
241
|
+
if (!m)
|
|
242
|
+
return null;
|
|
243
|
+
return [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
244
|
+
}
|
|
245
|
+
// True iff `latest` is strictly newer than `current`. If either side has no
|
|
246
|
+
// parseable semver (e.g. a dev `<sha>-dirty` build), return false: we never nag
|
|
247
|
+
// someone whose version we can't reason about.
|
|
248
|
+
function isNewerVersion(latest, current) {
|
|
249
|
+
const a = parseVersion(latest);
|
|
250
|
+
const b = parseVersion(current);
|
|
251
|
+
if (!a || !b)
|
|
252
|
+
return false;
|
|
253
|
+
for (let i = 0; i < 3; i++) {
|
|
254
|
+
if (a[i] > b[i])
|
|
255
|
+
return true;
|
|
256
|
+
if (a[i] < b[i])
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
// --- environment gating ------------------------------------------------------
|
|
262
|
+
// Standard CI markers. The notifier is pointless (and noisy in logs) on CI.
|
|
263
|
+
function isCI(env) {
|
|
264
|
+
return Boolean(env.CI ||
|
|
265
|
+
env.CONTINUOUS_INTEGRATION ||
|
|
266
|
+
env.GITHUB_ACTIONS ||
|
|
267
|
+
env.GITLAB_CI ||
|
|
268
|
+
env.BUILDKITE ||
|
|
269
|
+
env.CIRCLECI ||
|
|
270
|
+
env.TEAMCITY_VERSION);
|
|
271
|
+
}
|
|
272
|
+
// Shared env-flag truthiness: unset / "" / "0" / "false" is off, anything else
|
|
273
|
+
// (1, yes, true, ...) is on. One definition so every opt-out reads the same way.
|
|
274
|
+
function envTruthy(v) {
|
|
275
|
+
const s = (v || "").trim().toLowerCase();
|
|
276
|
+
return s !== "" && s !== "0" && s !== "false";
|
|
277
|
+
}
|
|
278
|
+
// The user-facing opt-out (gh uses GH_NO_UPDATE_NOTIFIER; we mirror it).
|
|
279
|
+
function notifierDisabled(env) {
|
|
280
|
+
return envTruthy(env.MLA_NO_UPDATE_NOTIFIER);
|
|
281
|
+
}
|
|
282
|
+
// --- upgrade opt-out precedence ----------------------------------------------
|
|
283
|
+
// The total kill switch. When set, the binary does NO self-management at all:
|
|
284
|
+
// no background check, no nag, no staging, no apply-on-launch, and even an
|
|
285
|
+
// explicit `mla upgrade` refuses. For locked-down / managed environments that
|
|
286
|
+
// want the binary to never modify itself or phone home about versions.
|
|
287
|
+
function upgradeKillSwitch(env) {
|
|
288
|
+
return envTruthy(env.MLA_DISABLE_UPGRADE);
|
|
289
|
+
}
|
|
290
|
+
// Disable only the automatic self-replace (background staging + apply-on-launch).
|
|
291
|
+
// The check and the nag still run, and explicit `mla upgrade` still works; the
|
|
292
|
+
// binary just never swaps itself without the user typing the command.
|
|
293
|
+
function autoUpgradeDisabled(env) {
|
|
294
|
+
return envTruthy(env.MLA_DISABLE_AUTO_UPGRADE);
|
|
295
|
+
}
|
|
296
|
+
// Whether the binary may replace itself unattended (stage in the background and
|
|
297
|
+
// promote on launch). Precedence, highest first:
|
|
298
|
+
// MLA_DISABLE_UPGRADE > MLA_DISABLE_AUTO_UPGRADE > MLA_NO_UPDATE_NOTIFIER
|
|
299
|
+
// > config.update.autoApply (default false: nag-only).
|
|
300
|
+
function resolveAutoApply(opts) {
|
|
301
|
+
const { env, configAutoApply } = opts;
|
|
302
|
+
if (upgradeKillSwitch(env))
|
|
303
|
+
return false;
|
|
304
|
+
if (autoUpgradeDisabled(env))
|
|
305
|
+
return false;
|
|
306
|
+
if (notifierDisabled(env))
|
|
307
|
+
return false;
|
|
308
|
+
return configAutoApply === true;
|
|
309
|
+
}
|
|
310
|
+
// --- throttle ----------------------------------------------------------------
|
|
311
|
+
// Should the background fetch run? Only when notifications could ever be shown
|
|
312
|
+
// (not disabled, not CI) AND the throttle window has elapsed. Gating the fetch
|
|
313
|
+
// here means a disabled notifier makes zero network calls.
|
|
314
|
+
function shouldRunCheck(opts) {
|
|
315
|
+
const { state, now, env } = opts;
|
|
316
|
+
if (upgradeKillSwitch(env) || notifierDisabled(env) || isCI(env))
|
|
317
|
+
return false;
|
|
318
|
+
return now - state.lastCheckedAt >= exports.UPDATE_CHECK_INTERVAL_MS;
|
|
319
|
+
}
|
|
320
|
+
// --- nag display gating ------------------------------------------------------
|
|
321
|
+
// Should we print the upgrade nag now? Requires a cached newer version, an
|
|
322
|
+
// interactive session (both stdout and stderr are TTYs, so piping `mla ... |`
|
|
323
|
+
// stays clean), off CI, and not opted out.
|
|
324
|
+
function shouldShowNag(opts) {
|
|
325
|
+
const { state, currentVersion, env, stdoutTTY, stderrTTY } = opts;
|
|
326
|
+
if (notifierDisabled(env) || isCI(env))
|
|
327
|
+
return false;
|
|
328
|
+
if (!stdoutTTY || !stderrTTY)
|
|
329
|
+
return false;
|
|
330
|
+
return isNewerVersion(state.latestVersion, currentVersion);
|
|
331
|
+
}
|
|
332
|
+
// --- install-method detection ------------------------------------------------
|
|
333
|
+
// Detect how this `mla` was installed so the nag prints the command that
|
|
334
|
+
// actually works. We check both the running binary path (pkg build) and the
|
|
335
|
+
// script path (node + cli.js for source/npm), because which one is meaningful
|
|
336
|
+
// depends on the install:
|
|
337
|
+
// - curl|sh -> binary lives under ~/.meetless/bin
|
|
338
|
+
// - homebrew -> path under a brew prefix or a Caskroom dir
|
|
339
|
+
// - npm -> path under a node_modules tree
|
|
340
|
+
// MLA_INSTALL_METHOD overrides everything (an explicit escape hatch + the test seam).
|
|
341
|
+
function detectInstallMethod(opts) {
|
|
342
|
+
const { execPath, scriptPath, env } = opts;
|
|
343
|
+
const override = (env.MLA_INSTALL_METHOD || "").trim().toLowerCase();
|
|
344
|
+
if (override === "homebrew" || override === "curl" || override === "npm" || override === "unknown") {
|
|
345
|
+
return override;
|
|
346
|
+
}
|
|
347
|
+
const home = opts.home ?? os.homedir();
|
|
348
|
+
const brewPrefixes = opts.brewPrefixes ?? ["/opt/homebrew", "/usr/local", "/home/linuxbrew/.linuxbrew"];
|
|
349
|
+
const candidates = [execPath, scriptPath].filter((p) => Boolean(p));
|
|
350
|
+
const within = (child, parent) => {
|
|
351
|
+
const rel = path.relative(parent, child);
|
|
352
|
+
return rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel));
|
|
353
|
+
};
|
|
354
|
+
// curl|sh install dir wins first: it is the most specific and unambiguous.
|
|
355
|
+
const curlDir = path.join(home, ".meetless", "bin");
|
|
356
|
+
if (candidates.some((c) => within(c, curlDir)))
|
|
357
|
+
return "curl";
|
|
358
|
+
// Homebrew: a Caskroom path, or anything under a brew prefix.
|
|
359
|
+
if (candidates.some((c) => c.includes(`${path.sep}Caskroom${path.sep}`)))
|
|
360
|
+
return "homebrew";
|
|
361
|
+
if (candidates.some((c) => brewPrefixes.some((p) => within(c, p))))
|
|
362
|
+
return "homebrew";
|
|
363
|
+
// npm global / npx: served out of a node_modules tree.
|
|
364
|
+
if (candidates.some((c) => c.includes(`${path.sep}node_modules${path.sep}`)))
|
|
365
|
+
return "npm";
|
|
366
|
+
return "unknown";
|
|
367
|
+
}
|
|
368
|
+
// The upgrade command to print for each install method.
|
|
369
|
+
function upgradeCommandFor(method) {
|
|
370
|
+
switch (method) {
|
|
371
|
+
case "homebrew":
|
|
372
|
+
return "brew upgrade --cask mla";
|
|
373
|
+
case "curl":
|
|
374
|
+
return "curl -fsSL https://meetless.ai/install.sh | sh";
|
|
375
|
+
case "npm":
|
|
376
|
+
return "npm i -g @meetless/mla@latest";
|
|
377
|
+
case "unknown":
|
|
378
|
+
default:
|
|
379
|
+
// We genuinely don't know how it was installed; send them to the page
|
|
380
|
+
// rather than guess a command that might corrupt a managed install.
|
|
381
|
+
return "see https://meetless.ai/install";
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
// --- nag text ----------------------------------------------------------------
|
|
385
|
+
// The two-line nag, gh-style. Kept here so it is asserted in tests verbatim.
|
|
386
|
+
function formatUpdateNag(opts) {
|
|
387
|
+
const { current, latest, method, required } = opts;
|
|
388
|
+
const from = current ? `${current} ` : "";
|
|
389
|
+
const lead = required
|
|
390
|
+
? `\nmla ${from}is below the minimum supported version. Please upgrade to ${latest}.\n`
|
|
391
|
+
: `\nA new release of mla is available: ${from}-> ${latest}\n`;
|
|
392
|
+
return lead + `To upgrade, run: ${upgradeCommandFor(method)}\n`;
|
|
393
|
+
}
|
|
394
|
+
// Decide what `mla upgrade` should do given the current version, the verified
|
|
395
|
+
// manifest, this machine's triple, and whether --force was passed. Encapsulates
|
|
396
|
+
// the downgrade guard: we never silently move a user to an OLDER build (a stale
|
|
397
|
+
// "latest" pointer, a rolled-back release) unless they explicitly force it.
|
|
398
|
+
function planUpgrade(opts) {
|
|
399
|
+
const { current, manifest, triple, force } = opts;
|
|
400
|
+
const to = manifest.version;
|
|
401
|
+
if (!triple || !selectArtifact(manifest, triple)) {
|
|
402
|
+
return { action: "no-artifact", from: current, to, triple };
|
|
403
|
+
}
|
|
404
|
+
if (isNewerVersion(to, current)) {
|
|
405
|
+
return { action: "upgrade", from: current, to, triple };
|
|
406
|
+
}
|
|
407
|
+
// Not newer: same version, older, or a current we can't parse.
|
|
408
|
+
if (force) {
|
|
409
|
+
return { action: "upgrade", from: current, to, triple };
|
|
410
|
+
}
|
|
411
|
+
const cur = parseVersion(current);
|
|
412
|
+
const tgt = parseVersion(to); // always parses; manifest.version is validated
|
|
413
|
+
if (!cur) {
|
|
414
|
+
return { action: "unparseable-current", from: current, to, triple };
|
|
415
|
+
}
|
|
416
|
+
if (tgt && cur[0] === tgt[0] && cur[1] === tgt[1] && cur[2] === tgt[2]) {
|
|
417
|
+
return { action: "up-to-date", from: current, to, triple };
|
|
418
|
+
}
|
|
419
|
+
return { action: "downgrade-blocked", from: current, to, triple };
|
|
420
|
+
}
|
|
421
|
+
// --- state (de)serialization -------------------------------------------------
|
|
422
|
+
const EMPTY_STATE = { lastCheckedAt: 0, latestVersion: null };
|
|
423
|
+
// Validate a staged-upgrade pointer read back from disk; null if any field is
|
|
424
|
+
// missing or the wrong type, so a corrupt staged record is ignored, never acted
|
|
425
|
+
// on (apply-on-launch re-verifies the file's sha before promoting regardless).
|
|
426
|
+
function parseStaged(v) {
|
|
427
|
+
if (!v || typeof v !== "object")
|
|
428
|
+
return null;
|
|
429
|
+
const o = v;
|
|
430
|
+
if (typeof o.version !== "string" || typeof o.triple !== "string")
|
|
431
|
+
return null;
|
|
432
|
+
if (typeof o.sha256 !== "string" || typeof o.path !== "string")
|
|
433
|
+
return null;
|
|
434
|
+
if (typeof o.stagedAt !== "number")
|
|
435
|
+
return null;
|
|
436
|
+
return {
|
|
437
|
+
version: o.version,
|
|
438
|
+
triple: o.triple,
|
|
439
|
+
sha256: o.sha256,
|
|
440
|
+
path: o.path,
|
|
441
|
+
stagedAt: o.stagedAt,
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
function parseState(raw) {
|
|
445
|
+
if (!raw)
|
|
446
|
+
return { ...EMPTY_STATE };
|
|
447
|
+
try {
|
|
448
|
+
const o = JSON.parse(raw);
|
|
449
|
+
// Base shape only, so a pre-manifest cache deserializes to exactly the two
|
|
450
|
+
// original fields. Optional fields are attached ONLY when present and valid.
|
|
451
|
+
const state = {
|
|
452
|
+
lastCheckedAt: typeof o.lastCheckedAt === "number" ? o.lastCheckedAt : 0,
|
|
453
|
+
latestVersion: typeof o.latestVersion === "string" ? o.latestVersion : null,
|
|
454
|
+
};
|
|
455
|
+
if (typeof o.minVersion === "string")
|
|
456
|
+
state.minVersion = o.minVersion;
|
|
457
|
+
const staged = parseStaged(o.staged);
|
|
458
|
+
if (staged)
|
|
459
|
+
state.staged = staged;
|
|
460
|
+
return state;
|
|
461
|
+
}
|
|
462
|
+
catch {
|
|
463
|
+
// A corrupt cache is treated as "never checked", never fatal.
|
|
464
|
+
return { ...EMPTY_STATE };
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
function serializeState(state) {
|
|
468
|
+
return JSON.stringify(state);
|
|
469
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.stateFilePath = exports.writeUpdateState = exports.readUpdateState = void 0;
|
|
37
|
+
exports.maybeSpawnBackgroundCheck = maybeSpawnBackgroundCheck;
|
|
38
|
+
exports.maybeShowUpdateNag = maybeShowUpdateNag;
|
|
39
|
+
exports.fetchLatestVersion = fetchLatestVersion;
|
|
40
|
+
exports.runInternalUpdateCheck = runInternalUpdateCheck;
|
|
41
|
+
// IO layer for the update notifier. The decision logic is the pure core in
|
|
42
|
+
// update-check.ts; the heavier upgrade IO (cache read/write, manifest fetch +
|
|
43
|
+
// verify, download, swap, stage) lives in upgrade-apply.ts. This file wires the
|
|
44
|
+
// two together for the two background concerns: spawn the detached check child,
|
|
45
|
+
// and print the nag. Every function here is best-effort and swallows its own
|
|
46
|
+
// errors: the update notifier must never change an exit code or break a command.
|
|
47
|
+
const child_process_1 = require("child_process");
|
|
48
|
+
const update_check_1 = require("./update-check");
|
|
49
|
+
const observability_1 = require("./observability");
|
|
50
|
+
const config_1 = require("./config");
|
|
51
|
+
const upgrade_apply_1 = require("./upgrade-apply");
|
|
52
|
+
const fs = __importStar(require("fs"));
|
|
53
|
+
// Re-export the cache IO from its new home so existing importers keep working.
|
|
54
|
+
var upgrade_apply_2 = require("./upgrade-apply");
|
|
55
|
+
Object.defineProperty(exports, "readUpdateState", { enumerable: true, get: function () { return upgrade_apply_2.readUpdateState; } });
|
|
56
|
+
Object.defineProperty(exports, "writeUpdateState", { enumerable: true, get: function () { return upgrade_apply_2.writeUpdateState; } });
|
|
57
|
+
Object.defineProperty(exports, "stateFilePath", { enumerable: true, get: function () { return upgrade_apply_2.stateFilePath; } });
|
|
58
|
+
// Spawn a detached `mla _internal update-check` that fetches the signed manifest
|
|
59
|
+
// (and stages a binary when auto-apply is on), writes the cache, then returns
|
|
60
|
+
// immediately. The parent never waits on it. Skipped entirely for `_internal`
|
|
61
|
+
// commands (so the check child can't recurse) and whenever the throttle/gating
|
|
62
|
+
// says no (shouldRunCheck).
|
|
63
|
+
function maybeSpawnBackgroundCheck(opts) {
|
|
64
|
+
try {
|
|
65
|
+
const { command, env, now } = opts;
|
|
66
|
+
if (command === "_internal")
|
|
67
|
+
return; // never recurse from the check child
|
|
68
|
+
const entry = process.argv[1];
|
|
69
|
+
if (!entry)
|
|
70
|
+
return; // no re-invokable entry; skip rather than guess
|
|
71
|
+
if (!(0, update_check_1.shouldRunCheck)({ state: (0, upgrade_apply_1.readUpdateState)(), now, env }))
|
|
72
|
+
return;
|
|
73
|
+
const child = (0, child_process_1.spawn)(process.execPath, [entry, "_internal", "update-check"], {
|
|
74
|
+
detached: true,
|
|
75
|
+
stdio: "ignore",
|
|
76
|
+
});
|
|
77
|
+
child.on("error", () => { }); // swallow ENOENT etc; never throw from here
|
|
78
|
+
child.unref();
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// never let the notifier break the real command
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// Print the upgrade nag to stderr if a newer version is cached and the session
|
|
85
|
+
// is interactive (TTY) and not opted out / not CI. stderr keeps it off stdout
|
|
86
|
+
// so `mla ... | jq` stays clean. When the current version is below the manifest
|
|
87
|
+
// floor (minVersion), the stronger "required" wording is shown.
|
|
88
|
+
function maybeShowUpdateNag(opts) {
|
|
89
|
+
try {
|
|
90
|
+
const { currentVersion, env } = opts;
|
|
91
|
+
const state = (0, upgrade_apply_1.readUpdateState)();
|
|
92
|
+
const show = (0, update_check_1.shouldShowNag)({
|
|
93
|
+
state,
|
|
94
|
+
currentVersion,
|
|
95
|
+
env,
|
|
96
|
+
stdoutTTY: Boolean(process.stdout.isTTY),
|
|
97
|
+
stderrTTY: Boolean(process.stderr.isTTY),
|
|
98
|
+
});
|
|
99
|
+
if (!show || !state.latestVersion)
|
|
100
|
+
return;
|
|
101
|
+
const method = (0, update_check_1.detectInstallMethod)({
|
|
102
|
+
execPath: process.execPath,
|
|
103
|
+
scriptPath: process.argv[1],
|
|
104
|
+
env,
|
|
105
|
+
});
|
|
106
|
+
const required = (0, update_check_1.isBelowMinVersion)(currentVersion, state.minVersion ?? null);
|
|
107
|
+
process.stderr.write((0, update_check_1.formatUpdateNag)({ current: currentVersion, latest: state.latestVersion, method, required }));
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// never let the notifier break the real command
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Query the plaintext release feed for the latest version (bare, no leading v).
|
|
114
|
+
// This is the FALLBACK path used only when the signed manifest is unreachable
|
|
115
|
+
// (e.g. during the manifest rollout window): it keeps the nag working but cannot
|
|
116
|
+
// drive a self-upgrade (no per-artifact sha to verify). Returns null on any
|
|
117
|
+
// failure so the caller just leaves the cache untouched.
|
|
118
|
+
async function fetchLatestVersion(opts) {
|
|
119
|
+
const url = opts?.url ?? process.env.MLA_UPDATE_URL ?? update_check_1.DEFAULT_UPDATE_URL;
|
|
120
|
+
const timeoutMs = opts?.timeoutMs ?? 4000;
|
|
121
|
+
const controller = new AbortController();
|
|
122
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
123
|
+
try {
|
|
124
|
+
const res = await fetch(url, {
|
|
125
|
+
headers: { "User-Agent": "mla-update-check" },
|
|
126
|
+
signal: controller.signal,
|
|
127
|
+
});
|
|
128
|
+
if (!res.ok)
|
|
129
|
+
return null;
|
|
130
|
+
// The VERSION file holds a single bare version line (e.g. "0.4.2"). Trim
|
|
131
|
+
// whitespace, take the first line, and drop an optional leading "v". Guard
|
|
132
|
+
// against an unexpected large/HTML body so a misrouted URL can't poison the
|
|
133
|
+
// cache with junk.
|
|
134
|
+
const text = (await res.text()).trim();
|
|
135
|
+
const first = text.split(/\r?\n/, 1)[0]?.trim() ?? "";
|
|
136
|
+
const ver = first.replace(/^v/, "");
|
|
137
|
+
if (!/^\d+\.\d+\.\d+([.-][0-9A-Za-z.-]+)?$/.test(ver))
|
|
138
|
+
return null;
|
|
139
|
+
return ver;
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
finally {
|
|
145
|
+
clearTimeout(timer);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// `mla _internal update-check`: the detached child. Fetch + verify the signed
|
|
149
|
+
// manifest, stamp latestVersion + minVersion, and (when auto-apply is enabled on
|
|
150
|
+
// a curl install) download + verify + stage a newer binary so apply-on-launch is
|
|
151
|
+
// a cheap local swap. Always exits 0 (best-effort); stamps lastCheckedAt even on
|
|
152
|
+
// a failed fetch so a flaky network doesn't hammer the feed every command.
|
|
153
|
+
async function runInternalUpdateCheck() {
|
|
154
|
+
const now = Date.now();
|
|
155
|
+
const env = process.env;
|
|
156
|
+
const buildInfo = (0, observability_1.loadBuildInfo)();
|
|
157
|
+
const prev = (0, upgrade_apply_1.readUpdateState)();
|
|
158
|
+
const verified = await (0, upgrade_apply_1.fetchManifest)({ env, buildInfo });
|
|
159
|
+
let latestVersion = prev.latestVersion;
|
|
160
|
+
let minVersion = prev.minVersion ?? null;
|
|
161
|
+
if (verified) {
|
|
162
|
+
latestVersion = verified.manifest.version;
|
|
163
|
+
minVersion = verified.manifest.minVersion;
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
// Manifest unreachable: fall back to the plaintext VERSION feed so the nag
|
|
167
|
+
// keeps working. Cannot stage from this path (no verified artifact sha).
|
|
168
|
+
const plain = await fetchLatestVersion();
|
|
169
|
+
if (plain)
|
|
170
|
+
latestVersion = plain;
|
|
171
|
+
}
|
|
172
|
+
let staged = prev.staged ?? null;
|
|
173
|
+
if (verified &&
|
|
174
|
+
(0, update_check_1.resolveAutoApply)({ env, configAutoApply: (0, config_1.readUpdateConfig)().autoApply })) {
|
|
175
|
+
const method = (0, update_check_1.detectInstallMethod)({
|
|
176
|
+
execPath: process.execPath,
|
|
177
|
+
scriptPath: process.argv[1],
|
|
178
|
+
env,
|
|
179
|
+
});
|
|
180
|
+
if (method === "curl") {
|
|
181
|
+
const triple = (0, update_check_1.currentTriple)(process.platform, process.arch);
|
|
182
|
+
const plan = (0, update_check_1.planUpgrade)({
|
|
183
|
+
current: buildInfo.version,
|
|
184
|
+
manifest: verified.manifest,
|
|
185
|
+
triple,
|
|
186
|
+
force: false,
|
|
187
|
+
});
|
|
188
|
+
if (plan.action === "upgrade" && triple) {
|
|
189
|
+
const already = staged &&
|
|
190
|
+
staged.version === plan.to &&
|
|
191
|
+
staged.triple === triple &&
|
|
192
|
+
fs.existsSync(staged.path);
|
|
193
|
+
if (!already) {
|
|
194
|
+
const artifact = (0, update_check_1.selectArtifact)(verified.manifest, triple);
|
|
195
|
+
if (artifact) {
|
|
196
|
+
const extracted = await (0, upgrade_apply_1.downloadVerifyExtract)({ artifact });
|
|
197
|
+
if (extracted) {
|
|
198
|
+
try {
|
|
199
|
+
staged = (0, upgrade_apply_1.stageBinary)({
|
|
200
|
+
binaryPath: extracted.binaryPath,
|
|
201
|
+
version: plan.to,
|
|
202
|
+
triple,
|
|
203
|
+
now,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
finally {
|
|
207
|
+
(0, upgrade_apply_1.cleanupDir)(extracted.dir);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
(0, upgrade_apply_1.writeUpdateState)({ lastCheckedAt: now, latestVersion, minVersion, staged });
|
|
216
|
+
return 0;
|
|
217
|
+
}
|