@getmikie/cli 1.1.86 → 1.1.87
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/bin/mikie.mjs +156 -0
- package/lib/launcher.mjs +88 -0
- package/lib/update.mjs +2944 -0
- package/package.json +5 -5
package/bin/mikie.mjs
CHANGED
|
@@ -1,12 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { access } from "node:fs/promises";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
4
8
|
|
|
5
9
|
import {
|
|
10
|
+
launchNativeCaptured,
|
|
6
11
|
launchNative,
|
|
7
12
|
resolveNativeBinary,
|
|
13
|
+
selectPlatformPackage,
|
|
8
14
|
translateArguments,
|
|
9
15
|
} from "../lib/launcher.mjs";
|
|
16
|
+
import {
|
|
17
|
+
UpdateTopology,
|
|
18
|
+
createCommandAdapter,
|
|
19
|
+
createFilesystemAdapter,
|
|
20
|
+
createRegistryAdapter,
|
|
21
|
+
detectUpdateTopology,
|
|
22
|
+
externalRecoveryInvocation,
|
|
23
|
+
parseUpdateHandoff,
|
|
24
|
+
runUpdateTransaction,
|
|
25
|
+
} from "../lib/update.mjs";
|
|
10
26
|
|
|
11
27
|
const require = createRequire(import.meta.url);
|
|
12
28
|
const packageMetadata = require("../package.json");
|
|
@@ -23,8 +39,148 @@ Usage:
|
|
|
23
39
|
mikie login
|
|
24
40
|
mikie connect .
|
|
25
41
|
mikie doctor
|
|
42
|
+
mikie update
|
|
26
43
|
|
|
27
44
|
All commands execute the compiled Mikie native engine.`);
|
|
45
|
+
} else if (arguments_.length === 1 && arguments_[0].toLowerCase() === "update") {
|
|
46
|
+
try {
|
|
47
|
+
const commands = createCommandAdapter();
|
|
48
|
+
const npmRoot = await commands.run("npm", ["root", "--global"], {
|
|
49
|
+
shell: false,
|
|
50
|
+
capture: true,
|
|
51
|
+
});
|
|
52
|
+
const npmPrefix = await commands.run("npm", ["prefix", "--global"], {
|
|
53
|
+
shell: false,
|
|
54
|
+
capture: true,
|
|
55
|
+
});
|
|
56
|
+
if (
|
|
57
|
+
npmRoot.code !== 0 ||
|
|
58
|
+
npmRoot.stdout.trim().split(/\r?\n/).length !== 1 ||
|
|
59
|
+
!path.isAbsolute(npmRoot.stdout.trim()) ||
|
|
60
|
+
npmPrefix.code !== 0 ||
|
|
61
|
+
npmPrefix.stdout.trim().split(/\r?\n/).length !== 1 ||
|
|
62
|
+
!path.isAbsolute(npmPrefix.stdout.trim())
|
|
63
|
+
) {
|
|
64
|
+
throw new Error("global npm installation authority is unavailable");
|
|
65
|
+
}
|
|
66
|
+
const topology = detectUpdateTopology({ globalNodeModules: npmRoot.stdout.trim() });
|
|
67
|
+
const customerHome = homedir();
|
|
68
|
+
const transactionRoot = path.join(customerHome, ".mikie", "npm-update-v1");
|
|
69
|
+
const recoveryJournal = path.join(transactionRoot, "journal.json");
|
|
70
|
+
if (topology === UpdateTopology.NpmGlobal) {
|
|
71
|
+
try {
|
|
72
|
+
await access(recoveryJournal);
|
|
73
|
+
const invocation = await externalRecoveryInvocation(transactionRoot);
|
|
74
|
+
const recovered = await commands.run(invocation.executable, invocation.args, {
|
|
75
|
+
shell: false,
|
|
76
|
+
capture: true,
|
|
77
|
+
});
|
|
78
|
+
if (recovered.code !== 0) {
|
|
79
|
+
throw new Error("pending Mikie update recovery failed");
|
|
80
|
+
}
|
|
81
|
+
} catch (error) {
|
|
82
|
+
if (error?.code !== "ENOENT") throw error;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const selected = selectPlatformPackage();
|
|
86
|
+
const platformPackageJson = require.resolve(`${selected.packageName}/package.json`);
|
|
87
|
+
const wrapperPackageJson = fileURLToPath(new URL("../package.json", import.meta.url));
|
|
88
|
+
const binary = await resolveNativeBinary();
|
|
89
|
+
const globalPrefix = npmPrefix.stdout.trim();
|
|
90
|
+
const globalShims =
|
|
91
|
+
process.platform === "win32"
|
|
92
|
+
? ["mikie", "mikie.cmd", "mikie.ps1"].map((name) => path.join(globalPrefix, name))
|
|
93
|
+
: [path.join(globalPrefix, "bin", "mikie")];
|
|
94
|
+
const handoffResult = await launchNativeCaptured(binary, ["update"], { topology });
|
|
95
|
+
if (handoffResult.code !== 0) {
|
|
96
|
+
if (handoffResult.stderr) process.stderr.write(handoffResult.stderr);
|
|
97
|
+
throw new Error(`native update handoff exited with status ${handoffResult.code}`);
|
|
98
|
+
}
|
|
99
|
+
const handoff = parseUpdateHandoff(handoffResult.stdout, topology);
|
|
100
|
+
const registry = createRegistryAdapter({ commands });
|
|
101
|
+
const files = createFilesystemAdapter({
|
|
102
|
+
currentLayout: { wrapperPackageJson, platformPackageJson, binary, shims: globalShims },
|
|
103
|
+
locateCandidate: async ({ stagingPrefix } = {}) => {
|
|
104
|
+
if (stagingPrefix) {
|
|
105
|
+
const nodeModules =
|
|
106
|
+
process.platform === "win32"
|
|
107
|
+
? path.join(stagingPrefix, "node_modules")
|
|
108
|
+
: path.join(stagingPrefix, "lib", "node_modules");
|
|
109
|
+
const stagedWrapper = path.join(nodeModules, "@getmikie", "cli", "package.json");
|
|
110
|
+
const stagedPlatform = path.join(
|
|
111
|
+
nodeModules,
|
|
112
|
+
...selected.packageName.split("/"),
|
|
113
|
+
"package.json",
|
|
114
|
+
);
|
|
115
|
+
return {
|
|
116
|
+
wrapperPackageJson: stagedWrapper,
|
|
117
|
+
platformPackageJson: stagedPlatform,
|
|
118
|
+
binary: path.join(
|
|
119
|
+
path.dirname(stagedPlatform),
|
|
120
|
+
...selected.binaryPath.split("/"),
|
|
121
|
+
),
|
|
122
|
+
shims:
|
|
123
|
+
process.platform === "win32"
|
|
124
|
+
? ["mikie", "mikie.cmd", "mikie.ps1"].map((name) =>
|
|
125
|
+
path.join(stagingPrefix, name),
|
|
126
|
+
)
|
|
127
|
+
: [path.join(stagingPrefix, "bin", "mikie")],
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
wrapperPackageJson,
|
|
132
|
+
platformPackageJson: require.resolve(`${selected.packageName}/package.json`),
|
|
133
|
+
binary: await resolveNativeBinary(),
|
|
134
|
+
shims: globalShims,
|
|
135
|
+
};
|
|
136
|
+
},
|
|
137
|
+
providerPaths: {
|
|
138
|
+
registration: path.join(customerHome, ".claude.json"),
|
|
139
|
+
settings: path.join(customerHome, ".claude", "settings.json"),
|
|
140
|
+
ownership: path.join(customerHome, ".claude", "mikie-evidence-hook-ownership.json"),
|
|
141
|
+
},
|
|
142
|
+
transactionRoot,
|
|
143
|
+
settleProviderRepair: async (phase, snapshot) => {
|
|
144
|
+
const settlementBinary = snapshot?.preparedCandidate?.binary;
|
|
145
|
+
const finalLauncher = snapshot?.preparedCandidate?.finalLauncher;
|
|
146
|
+
if (!settlementBinary || !finalLauncher) {
|
|
147
|
+
throw new Error("prepared candidate recovery authority is unavailable");
|
|
148
|
+
}
|
|
149
|
+
const settlement = await commands.run(settlementBinary, handoff.repair_command, {
|
|
150
|
+
shell: false,
|
|
151
|
+
capture: true,
|
|
152
|
+
environment: {
|
|
153
|
+
MIKIE_DISTRIBUTION_MODE: "npm",
|
|
154
|
+
MIKIE_NPM_UPDATE_TOPOLOGY: UpdateTopology.NpmGlobal,
|
|
155
|
+
MIKIE_NPM_UPDATE_REPAIR_PHASE: phase,
|
|
156
|
+
MIKIE_UPDATE_REPAIR_CANDIDATE: settlementBinary,
|
|
157
|
+
MIKIE_UPDATE_REPAIR_LAUNCHER: finalLauncher,
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
if (settlement.code !== 0) {
|
|
161
|
+
throw new Error("candidate owned-adapter settlement failed");
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
const result = await runUpdateTransaction({
|
|
166
|
+
handoff,
|
|
167
|
+
currentVersion: packageMetadata.version,
|
|
168
|
+
platformPackage: selected.packageName,
|
|
169
|
+
registry,
|
|
170
|
+
commands,
|
|
171
|
+
files,
|
|
172
|
+
});
|
|
173
|
+
if (result.status === "updated") {
|
|
174
|
+
console.log(`Updated Mikie to ${result.version}.`);
|
|
175
|
+
for (const setting of result.foreignSettings) {
|
|
176
|
+
console.log(`Preserved foreign setting: ${setting}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
} catch (error) {
|
|
180
|
+
const message = error instanceof Error ? error.message : "Mikie update failed";
|
|
181
|
+
console.error(`mikie: ${message}`);
|
|
182
|
+
process.exitCode = 1;
|
|
183
|
+
}
|
|
28
184
|
} else {
|
|
29
185
|
try {
|
|
30
186
|
const binary = await resolveNativeBinary();
|
package/lib/launcher.mjs
CHANGED
|
@@ -17,6 +17,8 @@ const SIGNAL_EXIT_CODES = new Map([
|
|
|
17
17
|
["SIGTERM", 143],
|
|
18
18
|
]);
|
|
19
19
|
|
|
20
|
+
const MAX_CAPTURED_OUTPUT_BYTES = 1024 * 1024;
|
|
21
|
+
|
|
20
22
|
function currentRuntimeUsesGlibc() {
|
|
21
23
|
try {
|
|
22
24
|
const report = process.report?.getReport();
|
|
@@ -122,3 +124,89 @@ export function launchNative(
|
|
|
122
124
|
});
|
|
123
125
|
});
|
|
124
126
|
}
|
|
127
|
+
|
|
128
|
+
export function launchNativeCaptured(
|
|
129
|
+
binary,
|
|
130
|
+
arguments_,
|
|
131
|
+
{
|
|
132
|
+
topology,
|
|
133
|
+
spawnProcess = spawn,
|
|
134
|
+
environment = process.env,
|
|
135
|
+
timeoutMs = 30_000,
|
|
136
|
+
scheduleTimeout = setTimeout,
|
|
137
|
+
cancelTimeout = clearTimeout,
|
|
138
|
+
} = {},
|
|
139
|
+
) {
|
|
140
|
+
if (!["npm_global", "npm_ephemeral"].includes(topology)) {
|
|
141
|
+
throw new Error("Mikie npm update topology is invalid");
|
|
142
|
+
}
|
|
143
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs <= 0) {
|
|
144
|
+
throw new Error("Mikie native update timeout is invalid");
|
|
145
|
+
}
|
|
146
|
+
return new Promise((resolve, reject) => {
|
|
147
|
+
let settled = false;
|
|
148
|
+
const stdout = [];
|
|
149
|
+
const stderr = [];
|
|
150
|
+
let capturedBytes = 0;
|
|
151
|
+
const child = spawnProcess(binary, arguments_, {
|
|
152
|
+
shell: false,
|
|
153
|
+
stdio: ["inherit", "pipe", "pipe"],
|
|
154
|
+
windowsHide: true,
|
|
155
|
+
env: {
|
|
156
|
+
...environment,
|
|
157
|
+
MIKIE_DISTRIBUTION_MODE: "npm",
|
|
158
|
+
MIKIE_NPM_UPDATE_TOPOLOGY: topology,
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
const deadline = scheduleTimeout(() => {
|
|
162
|
+
if (settled) return;
|
|
163
|
+
settled = true;
|
|
164
|
+
try {
|
|
165
|
+
child.kill?.("SIGKILL");
|
|
166
|
+
} catch {
|
|
167
|
+
// The child may already have exited.
|
|
168
|
+
}
|
|
169
|
+
reject(new Error("Mikie native update command timed out"));
|
|
170
|
+
}, timeoutMs);
|
|
171
|
+
const finish = (callback) => {
|
|
172
|
+
if (settled) return false;
|
|
173
|
+
settled = true;
|
|
174
|
+
cancelTimeout(deadline);
|
|
175
|
+
callback();
|
|
176
|
+
return true;
|
|
177
|
+
};
|
|
178
|
+
const capture = (target) => (chunk) => {
|
|
179
|
+
if (settled) return;
|
|
180
|
+
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
181
|
+
capturedBytes += bytes.length;
|
|
182
|
+
if (capturedBytes > MAX_CAPTURED_OUTPUT_BYTES) {
|
|
183
|
+
finish(() => {
|
|
184
|
+
try {
|
|
185
|
+
child.kill?.("SIGKILL");
|
|
186
|
+
} catch {
|
|
187
|
+
// The child may already have exited.
|
|
188
|
+
}
|
|
189
|
+
reject(new Error("Mikie native update response exceeded the capture limit"));
|
|
190
|
+
});
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
target.push(bytes);
|
|
194
|
+
};
|
|
195
|
+
child.stdout.on("data", capture(stdout));
|
|
196
|
+
child.stderr.on("data", capture(stderr));
|
|
197
|
+
child.once("error", (error) => {
|
|
198
|
+
finish(() =>
|
|
199
|
+
reject(new Error("Mikie native update command could not be started", { cause: error })),
|
|
200
|
+
);
|
|
201
|
+
});
|
|
202
|
+
child.once("close", (code, signal) => {
|
|
203
|
+
finish(() =>
|
|
204
|
+
resolve({
|
|
205
|
+
code: Number.isInteger(code) ? code : (SIGNAL_EXIT_CODES.get(signal) ?? 1),
|
|
206
|
+
stdout: Buffer.concat(stdout).toString("utf8"),
|
|
207
|
+
stderr: Buffer.concat(stderr).toString("utf8"),
|
|
208
|
+
}),
|
|
209
|
+
);
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
}
|