@goondocks/myco 1.1.2 → 1.2.0-beta.2
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/ui/index.html
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>Myco</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-DUb7343F.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/assets/index-B7gU3-ZJ.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goondocks/myco",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0-beta.2",
|
|
4
4
|
"description": "Collective agent intelligence — Claude Code plugin",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -73,11 +73,11 @@
|
|
|
73
73
|
"zod": "^4.4.3"
|
|
74
74
|
},
|
|
75
75
|
"optionalDependencies": {
|
|
76
|
-
"@goondocks/myco-darwin-arm64": "1.
|
|
77
|
-
"@goondocks/myco-darwin-x64": "1.
|
|
78
|
-
"@goondocks/myco-linux-arm64": "1.
|
|
79
|
-
"@goondocks/myco-linux-x64": "1.
|
|
80
|
-
"@goondocks/myco-windows-x64": "1.
|
|
76
|
+
"@goondocks/myco-darwin-arm64": "1.2.0-beta.2",
|
|
77
|
+
"@goondocks/myco-darwin-x64": "1.2.0-beta.2",
|
|
78
|
+
"@goondocks/myco-linux-arm64": "1.2.0-beta.2",
|
|
79
|
+
"@goondocks/myco-linux-x64": "1.2.0-beta.2",
|
|
80
|
+
"@goondocks/myco-windows-x64": "1.2.0-beta.2"
|
|
81
81
|
},
|
|
82
82
|
"devDependencies": {
|
|
83
83
|
"@goondocks/myco-shared": "*",
|
|
@@ -1,23 +1,59 @@
|
|
|
1
|
-
// Postinstall:
|
|
2
|
-
// installed as an optionalDependency, mark it executable, and write
|
|
3
|
-
// `vendor/resolved.json` so `bin/myco` can dispatch without re-resolving on
|
|
4
|
-
// every invocation.
|
|
1
|
+
// Postinstall: bootstrap npm into the single self-updating managed binary.
|
|
5
2
|
//
|
|
6
3
|
// Each supported platform has its own published package
|
|
7
4
|
// (`@goondocks/myco-<target>`) whose `package.json` carries the matching
|
|
8
5
|
// `os` and `cpu` filters. npm installs only the matching one; the rest are
|
|
9
6
|
// skipped. This script uses `require.resolve` to find the binary inside the
|
|
10
|
-
// installed platform package,
|
|
11
|
-
//
|
|
12
|
-
//
|
|
7
|
+
// installed platform package, writes `vendor/resolved.json` (so a fallback
|
|
8
|
+
// `bin/myco` dispatch can find it), and then CONVERGES: it copies the
|
|
9
|
+
// selected binary into the canonical managed location (`~/.myco/bin/myco`),
|
|
10
|
+
// reconciles the `runtime.command` pin so every CLI invocation re-execs the
|
|
11
|
+
// managed binary, and writes the install marker. The daemon heals the OS
|
|
12
|
+
// service unit on its next startup via `ensureSelfInstalledAsService`, so
|
|
13
|
+
// the postinstall does NOT need to call any service-install logic — doing so
|
|
14
|
+
// would require `dist/src/` modules that are never emitted in the published
|
|
15
|
+
// tarball (the build only produces a bun binary + `dist/ui/`).
|
|
16
|
+
//
|
|
17
|
+
// Path layout helpers are inlined here (not imported from `dist/src/`) for
|
|
18
|
+
// the same reason: this script is the only .mjs in the published package and
|
|
19
|
+
// must be self-contained. The layout it produces MUST match what
|
|
20
|
+
// `src/install/managed-binary.ts` computes (verified by the pack smoke test).
|
|
21
|
+
//
|
|
22
|
+
// This module exports `convergeNpmInstall` so the convergence mechanics are
|
|
23
|
+
// unit-testable in isolation. The postinstall side effects run only when the
|
|
24
|
+
// file is executed as the main module (the is-main guard at the bottom), so
|
|
25
|
+
// importing it for a test is side-effect free.
|
|
13
26
|
|
|
14
27
|
import fs from 'node:fs';
|
|
15
28
|
import path from 'node:path';
|
|
29
|
+
import os from 'node:os';
|
|
16
30
|
import { createRequire } from 'node:module';
|
|
17
|
-
import { fileURLToPath } from 'node:url';
|
|
31
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Inlined path helpers — must match src/install/managed-binary.ts exactly.
|
|
35
|
+
// We cannot import dist/src/ because it is never emitted in the published
|
|
36
|
+
// tarball (the build only produces a bun binary + dist/ui/).
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
function managedBinDir(home, platform, localAppData) {
|
|
40
|
+
if (platform === 'win32') {
|
|
41
|
+
const appDataLocal = localAppData ?? path.win32.join(home, 'AppData', 'Local');
|
|
42
|
+
return path.win32.join(appDataLocal, 'Myco', 'bin');
|
|
43
|
+
}
|
|
44
|
+
return path.posix.join(home, '.myco', 'bin');
|
|
45
|
+
}
|
|
18
46
|
|
|
19
|
-
|
|
20
|
-
const
|
|
47
|
+
function managedBinaryPath(home, platform, localAppData) {
|
|
48
|
+
const binaryName = platform === 'win32' ? 'myco.exe' : 'myco';
|
|
49
|
+
return (platform === 'win32' ? path.win32 : path.posix).join(managedBinDir(home, platform, localAppData), binaryName);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function versionBinaryPath(home, platform, version, localAppData) {
|
|
53
|
+
const binaryName = platform === 'win32' ? 'myco.exe' : 'myco';
|
|
54
|
+
const p = platform === 'win32' ? path.win32 : path.posix;
|
|
55
|
+
return p.join(managedBinDir(home, platform, localAppData), 'versions', version, binaryName);
|
|
56
|
+
}
|
|
21
57
|
|
|
22
58
|
function detectTarget() {
|
|
23
59
|
const { platform, arch } = process;
|
|
@@ -27,79 +63,264 @@ function detectTarget() {
|
|
|
27
63
|
return null;
|
|
28
64
|
}
|
|
29
65
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Converge the npm install onto the single managed binary. Pure-ish: all fs
|
|
68
|
+
* I/O is confined to `home` / `dest`. Every step is wrapped so a failure logs
|
|
69
|
+
* to stderr and is NON-FATAL — npm postinstall must never fail because the
|
|
70
|
+
* daemon's lazy-spawn path and `myco doctor` still recover the gap.
|
|
71
|
+
*
|
|
72
|
+
* `dest` and `versionedDest` are INJECTED so tests can supply arbitrary paths
|
|
73
|
+
* without touching the real home directory.
|
|
74
|
+
*
|
|
75
|
+
* Layout produced (mirrors install.sh / the daemon helpers):
|
|
76
|
+
* `versionedDest` → <bindir>/versions/<bare-semver>/myco[.exe]
|
|
77
|
+
* `dest` → <bindir>/myco[.exe] (stable, current slot)
|
|
78
|
+
*
|
|
79
|
+
* Sequence: place at versioned slot via atomic temp+rename, then copy from
|
|
80
|
+
* the versioned slot to the stable path via a second atomic temp+rename. A
|
|
81
|
+
* partial copy can never leave a broken stable binary.
|
|
82
|
+
*
|
|
83
|
+
* Returns `{ dest, copied, pinAction }` for callers/tests to assert.
|
|
84
|
+
*
|
|
85
|
+
* @param {{ home: string, platform: string, resolvedBinary: string, dest: string, channel: string, version?: string, versionedDest?: string, writeMarker?: Function }} args
|
|
86
|
+
*/
|
|
87
|
+
export function convergeNpmInstall({ home, platform, resolvedBinary, dest, channel, version, versionedDest, writeMarker }) {
|
|
88
|
+
const log = (msg) => process.stderr.write(`[myco] ${msg}\n`);
|
|
89
|
+
const mycoHome = path.join(home, '.myco');
|
|
90
|
+
let copied = false;
|
|
91
|
+
let pinAction = 'skipped';
|
|
92
|
+
|
|
93
|
+
// --- Step 1: Atomic placement into the versioned slot -------------------
|
|
94
|
+
// When `versionedDest` is provided, place the binary at the versioned path
|
|
95
|
+
// first. This is the canonical layout: <bindir>/versions/<semver>/myco[.exe].
|
|
96
|
+
// Uses the same temp+rename pattern as the stable copy below.
|
|
97
|
+
let sourceForStable = resolvedBinary;
|
|
98
|
+
if (versionedDest) {
|
|
99
|
+
try {
|
|
100
|
+
fs.mkdirSync(path.dirname(versionedDest), { recursive: true });
|
|
101
|
+
const tmpV = `${versionedDest}.tmp-${process.pid}`;
|
|
102
|
+
try {
|
|
103
|
+
fs.copyFileSync(resolvedBinary, tmpV);
|
|
104
|
+
if (platform !== 'win32') {
|
|
105
|
+
try { fs.chmodSync(tmpV, 0o755); } catch { /* best effort */ }
|
|
106
|
+
}
|
|
107
|
+
fs.renameSync(tmpV, versionedDest);
|
|
108
|
+
// Stable copy reads from the versioned slot — ensures both paths
|
|
109
|
+
// hold identical bytes from the same verified source.
|
|
110
|
+
sourceForStable = versionedDest;
|
|
111
|
+
} catch (err) {
|
|
112
|
+
try { fs.rmSync(tmpV, { force: true }); } catch { /* best effort */ }
|
|
113
|
+
throw err;
|
|
114
|
+
}
|
|
115
|
+
} catch (err) {
|
|
116
|
+
log(`versioned binary placement skipped: ${err?.message ?? err}`);
|
|
117
|
+
// Fall back to copying directly from the resolved source binary.
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// --- Step 2: Atomic copy to the stable dest ----------------------------
|
|
122
|
+
// Write to a pid-suffixed temp file in the same directory, then rename onto
|
|
123
|
+
// `dest`. A reader either sees the old binary or the new one, never a
|
|
124
|
+
// half-written file.
|
|
125
|
+
try {
|
|
126
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
127
|
+
const tmp = `${dest}.tmp-${process.pid}`;
|
|
128
|
+
try {
|
|
129
|
+
fs.copyFileSync(sourceForStable, tmp);
|
|
130
|
+
if (platform !== 'win32') {
|
|
131
|
+
try { fs.chmodSync(tmp, 0o755); } catch { /* best effort */ }
|
|
132
|
+
}
|
|
133
|
+
fs.renameSync(tmp, dest);
|
|
134
|
+
copied = true;
|
|
135
|
+
} catch (err) {
|
|
136
|
+
// Clean up the temp file on any failure.
|
|
137
|
+
try { fs.rmSync(tmp, { force: true }); } catch { /* best effort */ }
|
|
138
|
+
// On win32 the existing managed binary may be running (the daemon /
|
|
139
|
+
// the launcher), so the rename fails EBUSY/EPERM. Task 9 handles the
|
|
140
|
+
// win32 in-place swap; here we skip and continue, non-fatal.
|
|
141
|
+
if (platform === 'win32' && (err?.code === 'EBUSY' || err?.code === 'EPERM')) {
|
|
142
|
+
log('managed binary in use; skipped (win32 swap deferred to update path)');
|
|
143
|
+
} else {
|
|
144
|
+
throw err;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
} catch (err) {
|
|
148
|
+
log(`managed binary copy skipped: ${err?.message ?? err}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// --- Pin reconciliation -------------------------------------------------
|
|
152
|
+
// `runtime.command` is the machine pin every CLI shim re-execs. We may
|
|
153
|
+
// safely point it at the managed binary, but must NOT clobber an active
|
|
154
|
+
// beta managed-runtime pin or a deliberate external/dev pin.
|
|
155
|
+
try {
|
|
156
|
+
const pinPath = path.join(mycoHome, 'runtime.command');
|
|
157
|
+
let pin = '';
|
|
158
|
+
try { pin = fs.readFileSync(pinPath, 'utf8').trim(); } catch { /* absent */ }
|
|
159
|
+
|
|
160
|
+
// Preserve a legacy managed-runtime pin (a path under ~/.myco/runtime/node_modules/)
|
|
161
|
+
// so a pre-native-installer setup isn't stranded.
|
|
162
|
+
const normalize = (p) => p.split(path.sep).join('/');
|
|
163
|
+
const managedPrefix = `${normalize(path.join(mycoHome, 'runtime'))}/node_modules/`;
|
|
164
|
+
const isManagedRuntimePin = pin !== '' && normalize(pin).startsWith(managedPrefix);
|
|
165
|
+
|
|
166
|
+
const shouldWrite =
|
|
167
|
+
pin === '' ||
|
|
168
|
+
pin === dest ||
|
|
169
|
+
(pin.includes('/node_modules/') && !isManagedRuntimePin);
|
|
170
|
+
|
|
171
|
+
if (shouldWrite) {
|
|
172
|
+
fs.mkdirSync(mycoHome, { recursive: true });
|
|
173
|
+
fs.writeFileSync(pinPath, `${dest}\n`, { mode: 0o644 });
|
|
174
|
+
// `mode` in writeFileSync is masked by umask; chmod to be certain the
|
|
175
|
+
// pin is never group/other-writable (runtime-redirect.cjs refuses it).
|
|
176
|
+
try { fs.chmodSync(pinPath, 0o644); } catch { /* best effort */ }
|
|
177
|
+
pinAction = 'wrote';
|
|
178
|
+
} else if (isManagedRuntimePin) {
|
|
179
|
+
log('legacy managed-runtime pin detected; not re-pointing runtime.command');
|
|
180
|
+
pinAction = 'preserved-managed';
|
|
181
|
+
} else {
|
|
182
|
+
log('external runtime.command pin preserved; not re-pointing');
|
|
183
|
+
pinAction = 'preserved-external';
|
|
184
|
+
}
|
|
185
|
+
} catch (err) {
|
|
186
|
+
log(`runtime.command reconcile skipped: ${err?.message ?? err}`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// --- Install marker -----------------------------------------------------
|
|
190
|
+
// `writeMarker` is an optional injection seam for tests. Production callers
|
|
191
|
+
// omit it; the inline fallback writes the same JSON shape.
|
|
192
|
+
try {
|
|
193
|
+
if (writeMarker) {
|
|
194
|
+
writeMarker(mycoHome, { channel, source: 'npm', bin: dest });
|
|
195
|
+
} else {
|
|
196
|
+
fs.mkdirSync(mycoHome, { recursive: true });
|
|
197
|
+
fs.writeFileSync(
|
|
198
|
+
path.join(mycoHome, 'install.json'),
|
|
199
|
+
JSON.stringify({ channel, source: 'npm', bin: dest }, null, 2),
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
} catch (err) {
|
|
203
|
+
log(`install marker skipped: ${err?.message ?? err}`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return { dest, copied, pinAction };
|
|
37
207
|
}
|
|
38
208
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
209
|
+
/**
|
|
210
|
+
* Derive the release channel from this package's own version: a semver
|
|
211
|
+
* prerelease component (`-beta`, `-alpha`, `-rc`, …) => 'beta', else 'stable'.
|
|
212
|
+
* Any error defaults to 'stable'.
|
|
213
|
+
*/
|
|
214
|
+
function deriveChannel(pkgRoot) {
|
|
215
|
+
try {
|
|
216
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(pkgRoot, 'package.json'), 'utf8'));
|
|
217
|
+
return /-(?:beta|alpha|rc|next|canary|dev)\b/.test(String(pkg.version)) ? 'beta' : 'stable';
|
|
218
|
+
} catch {
|
|
219
|
+
return 'stable';
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function main() {
|
|
224
|
+
const pkgRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
225
|
+
const require = createRequire(import.meta.url);
|
|
226
|
+
|
|
227
|
+
const target = detectTarget();
|
|
228
|
+
if (!target) {
|
|
229
|
+
process.stderr.write(
|
|
230
|
+
`[myco] Unsupported platform: ${process.platform}-${process.arch}. ` +
|
|
231
|
+
`Supported: darwin-{arm64,x64}, linux-{x64,arm64}, windows-x64.\n`,
|
|
232
|
+
);
|
|
233
|
+
process.exit(1);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const platformPkg = `@goondocks/myco-${target}`;
|
|
237
|
+
const binaryName = process.platform === 'win32' ? 'myco.exe' : 'myco';
|
|
238
|
+
|
|
239
|
+
// Source-checkout escape hatch: during local dev (`npm ci` in the monorepo)
|
|
240
|
+
// the platform package's `bin/` directory is empty until `make dev-link` (or
|
|
241
|
+
// an explicit `npm run build:binary`) compiles the host-target binary into
|
|
242
|
+
// it. Detect that state via the presence of `src/`, and exit 0 with a hint
|
|
243
|
+
// so monorepo installs don't trip postinstall.
|
|
244
|
+
const isSourceCheckout = fs.existsSync(path.join(pkgRoot, 'src'));
|
|
245
|
+
|
|
246
|
+
let binaryPath;
|
|
247
|
+
try {
|
|
248
|
+
binaryPath = require.resolve(`${platformPkg}/bin/${binaryName}`);
|
|
249
|
+
} catch (err) {
|
|
250
|
+
if (isSourceCheckout) {
|
|
251
|
+
process.stderr.write(
|
|
252
|
+
`[myco] No platform binary found in ${platformPkg}/bin/${binaryName}. ` +
|
|
253
|
+
`Skipping postinstall in source checkout (expected before \`make dev-link\`).\n`,
|
|
254
|
+
);
|
|
255
|
+
process.exit(0);
|
|
256
|
+
}
|
|
54
257
|
process.stderr.write(
|
|
55
|
-
`[myco]
|
|
56
|
-
`
|
|
258
|
+
`[myco] Platform binary package ${platformPkg} is not installed. ` +
|
|
259
|
+
`npm should have installed it as an optionalDependency of @goondocks/myco. ` +
|
|
260
|
+
`Try: npm install --include=optional -g @goondocks/myco\n` +
|
|
261
|
+
`(reason: ${err.message})\n`,
|
|
57
262
|
);
|
|
58
|
-
process.exit(
|
|
263
|
+
process.exit(1);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (process.platform !== 'win32') {
|
|
267
|
+
try { fs.chmodSync(binaryPath, 0o755); } catch { /* best effort */ }
|
|
59
268
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
269
|
+
|
|
270
|
+
const vendorDir = path.join(pkgRoot, 'vendor');
|
|
271
|
+
fs.mkdirSync(vendorDir, { recursive: true });
|
|
272
|
+
const resolvedPath = path.join(vendorDir, 'resolved.json');
|
|
273
|
+
fs.writeFileSync(
|
|
274
|
+
resolvedPath,
|
|
275
|
+
JSON.stringify({ target, binaryPath }, null, 2) + '\n',
|
|
276
|
+
'utf-8',
|
|
65
277
|
);
|
|
66
|
-
process.exit(1);
|
|
67
|
-
}
|
|
68
278
|
|
|
69
|
-
|
|
70
|
-
try { fs.chmodSync(binaryPath, 0o755); } catch { /* best effort */ }
|
|
71
|
-
}
|
|
279
|
+
process.stdout.write(`[myco] Selected platform binary: ${binaryPath}\n`);
|
|
72
280
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
// Self-install as a managed OS service so launchd / systemd starts the
|
|
85
|
-
// daemon at every login from the moment Myco is installed. Skipped in
|
|
86
|
-
// source checkouts (no published dist/), skipped silently on failure
|
|
87
|
-
// (the daemon's lazy-spawn path still works; doctor will surface the
|
|
88
|
-
// gap). Plan reference: Decision 13 / Step 12.
|
|
89
|
-
if (!isSourceCheckout) {
|
|
90
|
-
const distSelfInstall = path.join(pkgRoot, 'dist/src/service/self-install.js');
|
|
91
|
-
if (fs.existsSync(distSelfInstall)) {
|
|
281
|
+
// Converge npm into the single managed binary. Skipped in source checkouts
|
|
282
|
+
// (no platform binary present before `make dev-link`). Wrapped so a failure
|
|
283
|
+
// logs to stderr and is NON-FATAL — the daemon's lazy-spawn path still works
|
|
284
|
+
// and `myco doctor` surfaces any gap. Plan reference: Decision 13 / Step 12.
|
|
285
|
+
//
|
|
286
|
+
// Service install is intentionally NOT performed here. The daemon calls
|
|
287
|
+
// `ensureSelfInstalledAsService` on every startup (daemon/main.ts), which is
|
|
288
|
+
// idempotent and handles the service unit. Attempting it from the postinstall
|
|
289
|
+
// would require dist/src/service/self-install.js, which is never emitted in
|
|
290
|
+
// the published tarball.
|
|
291
|
+
if (!isSourceCheckout) {
|
|
92
292
|
try {
|
|
93
|
-
const
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
293
|
+
const home = os.homedir();
|
|
294
|
+
const platform = process.platform;
|
|
295
|
+
// `pkg.version` is the bare semver (e.g. "1.2.3") — npm packages never
|
|
296
|
+
// carry the "myco/v" tag prefix that curl installers use. This is the
|
|
297
|
+
// exact version string the daemon's versionBinaryPath() expects.
|
|
298
|
+
let pkg = { version: null };
|
|
299
|
+
try {
|
|
300
|
+
pkg = JSON.parse(fs.readFileSync(path.join(pkgRoot, 'package.json'), 'utf8'));
|
|
301
|
+
} catch { /* version stays null; versionedDest skipped */ }
|
|
302
|
+
const version = pkg.version ?? null;
|
|
303
|
+
const dest = managedBinaryPath(home, platform, process.env.LOCALAPPDATA);
|
|
304
|
+
const versionedDest = version
|
|
305
|
+
? versionBinaryPath(home, platform, version, process.env.LOCALAPPDATA)
|
|
306
|
+
: null;
|
|
307
|
+
const channel = deriveChannel(pkgRoot);
|
|
308
|
+
convergeNpmInstall({
|
|
309
|
+
home,
|
|
310
|
+
platform,
|
|
311
|
+
resolvedBinary: binaryPath,
|
|
312
|
+
dest,
|
|
313
|
+
channel,
|
|
314
|
+
version,
|
|
315
|
+
versionedDest,
|
|
316
|
+
});
|
|
101
317
|
} catch (err) {
|
|
102
|
-
process.stderr.write(`[myco]
|
|
318
|
+
process.stderr.write(`[myco] Convergence skipped: ${err?.message ?? err}\n`);
|
|
103
319
|
}
|
|
104
320
|
}
|
|
105
321
|
}
|
|
322
|
+
|
|
323
|
+
const isMain = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
324
|
+
if (isMain) {
|
|
325
|
+
await main();
|
|
326
|
+
}
|