@metorial/cli-core 0.0.0-dev.6

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.
@@ -0,0 +1,15 @@
1
+ type OutputTransform = (value: string) => string;
2
+ type RunCLIOptions = {
3
+ cwd?: string;
4
+ env?: NodeJS.ProcessEnv;
5
+ version?: string;
6
+ stdoutTransform?: OutputTransform;
7
+ stderrTransform?: OutputTransform;
8
+ };
9
+ declare function resolveCLIPath(version?: string): Promise<string>;
10
+ declare function runCLI(args: string[], options?: RunCLIOptions): Promise<number>;
11
+ declare function runCLIAndExit(args: string[], options?: RunCLIOptions): Promise<void>;
12
+ declare function replaceOutput(searchValue: string, replaceValue: string): OutputTransform;
13
+ declare function npmInstallEnvironment(packageName: string): NodeJS.ProcessEnv;
14
+
15
+ export { type OutputTransform, type RunCLIOptions, npmInstallEnvironment, replaceOutput, resolveCLIPath, runCLI, runCLIAndExit };
package/dist/index.js ADDED
@@ -0,0 +1,314 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ npmInstallEnvironment: () => npmInstallEnvironment,
34
+ replaceOutput: () => replaceOutput,
35
+ resolveCLIPath: () => resolveCLIPath,
36
+ runCLI: () => runCLI,
37
+ runCLIAndExit: () => runCLIAndExit
38
+ });
39
+ module.exports = __toCommonJS(index_exports);
40
+ var import_extract_zip = __toESM(require("extract-zip"));
41
+ var import_node_child_process = require("child_process");
42
+ var import_node_crypto = require("crypto");
43
+ var import_promises = require("fs/promises");
44
+ var import_node_os = __toESM(require("os"));
45
+ var import_node_path = __toESM(require("path"));
46
+ var tar = __toESM(require("tar"));
47
+
48
+ // package.json
49
+ var package_default = {
50
+ name: "@metorial/cli-core",
51
+ version: "0.0.0-dev.6",
52
+ description: "Runtime helpers for downloading and invoking the Metorial CLI",
53
+ license: "MIT",
54
+ main: "./dist/index.js",
55
+ types: "./dist/index.d.ts",
56
+ files: [
57
+ "dist"
58
+ ],
59
+ publishConfig: {
60
+ access: "public"
61
+ },
62
+ scripts: {
63
+ build: "tsup --config tsup.config.ts"
64
+ },
65
+ dependencies: {
66
+ "extract-zip": "^2.0.1",
67
+ tar: "^7.4.3"
68
+ }
69
+ };
70
+
71
+ // src/index.ts
72
+ var DEFAULT_BASE_URL = "https://cli.metorial.com";
73
+ async function resolveCLIPath(version) {
74
+ let tag = normalizeVersion(
75
+ version || process.env.METORIAL_CLI_VERSION || package_default.version
76
+ );
77
+ let spec = getPlatformSpec(tag);
78
+ let installDir = import_node_path.default.join(getCacheRoot(), "versions", tag);
79
+ let binaryPath = import_node_path.default.join(installDir, spec.binaryName);
80
+ if (await exists(binaryPath)) {
81
+ return binaryPath;
82
+ }
83
+ await downloadAndInstall({ tag, spec, installDir, binaryPath });
84
+ return binaryPath;
85
+ }
86
+ async function runCLI(args, options = {}) {
87
+ let binaryPath = await resolveCLIPath(options.version);
88
+ if (!options.stdoutTransform && !options.stderrTransform) {
89
+ return await spawnWithInheritedOutput(binaryPath, args, options);
90
+ }
91
+ return await spawnWithBufferedOutput(binaryPath, args, options);
92
+ }
93
+ async function runCLIAndExit(args, options = {}) {
94
+ let exitCode = await runCLI(args, options);
95
+ process.exit(exitCode);
96
+ }
97
+ function replaceOutput(searchValue, replaceValue) {
98
+ return (value) => value.replaceAll(searchValue, replaceValue);
99
+ }
100
+ function npmInstallEnvironment(packageName) {
101
+ return {
102
+ METORIAL_INSTALL_METHOD: "npm",
103
+ METORIAL_INSTALL_PM: detectNodePackageManager(__filename),
104
+ METORIAL_INSTALL_PACKAGE: packageName
105
+ };
106
+ }
107
+ async function downloadAndInstall(input) {
108
+ let tempDir = await makeTempDir(import_node_path.default.join(getCacheRoot(), "tmp"));
109
+ let archivePath = import_node_path.default.join(tempDir, input.spec.archiveName);
110
+ let checksumsPath = import_node_path.default.join(tempDir, "checksums.txt");
111
+ let extractDir = import_node_path.default.join(tempDir, "extract");
112
+ let baseUrl = `${(process.env.METORIAL_CLI_BASE_URL || DEFAULT_BASE_URL).replace(/\/$/, "")}/metorial-cli/${input.tag}`;
113
+ try {
114
+ await (0, import_promises.mkdir)(extractDir, { recursive: true });
115
+ await downloadFile(`${baseUrl}/${input.spec.archiveName}`, archivePath);
116
+ await downloadFile(`${baseUrl}/checksums.txt`, checksumsPath);
117
+ await verifyChecksum(checksumsPath, input.spec.archiveName, archivePath);
118
+ await extractArchive(archivePath, extractDir);
119
+ let extractedBinary = await findBinary(extractDir, input.spec.binaryName);
120
+ await (0, import_promises.mkdir)(input.installDir, { recursive: true });
121
+ await (0, import_promises.copyFile)(extractedBinary, input.binaryPath);
122
+ if (process.platform !== "win32") {
123
+ await (0, import_promises.chmod)(input.binaryPath, 493);
124
+ }
125
+ } finally {
126
+ await (0, import_promises.rm)(tempDir, { recursive: true, force: true });
127
+ }
128
+ }
129
+ async function spawnWithInheritedOutput(binaryPath, args, options) {
130
+ return await new Promise((resolve, reject) => {
131
+ let child = (0, import_node_child_process.spawn)(binaryPath, args, {
132
+ cwd: options.cwd,
133
+ env: { ...process.env, ...options.env },
134
+ stdio: "inherit"
135
+ });
136
+ child.on("error", reject);
137
+ child.on("close", (code) => resolve(code ?? 1));
138
+ });
139
+ }
140
+ async function spawnWithBufferedOutput(binaryPath, args, options) {
141
+ return await new Promise((resolve, reject) => {
142
+ let stdout = "";
143
+ let stderr = "";
144
+ let child = (0, import_node_child_process.spawn)(binaryPath, args, {
145
+ cwd: options.cwd,
146
+ env: { ...process.env, ...options.env },
147
+ stdio: ["inherit", "pipe", "pipe"]
148
+ });
149
+ child.stdout?.setEncoding("utf8");
150
+ child.stderr?.setEncoding("utf8");
151
+ child.stdout?.on("data", (chunk) => {
152
+ stdout += String(chunk);
153
+ });
154
+ child.stderr?.on("data", (chunk) => {
155
+ stderr += String(chunk);
156
+ });
157
+ child.on("error", reject);
158
+ child.on("close", (code) => {
159
+ process.stdout.write(options.stdoutTransform ? options.stdoutTransform(stdout) : stdout);
160
+ process.stderr.write(options.stderrTransform ? options.stderrTransform(stderr) : stderr);
161
+ resolve(code ?? 1);
162
+ });
163
+ });
164
+ }
165
+ async function downloadFile(url, destinationPath) {
166
+ let response = await fetch(url, {
167
+ headers: {
168
+ "User-Agent": "metorial-cli-npm",
169
+ Accept: "*/*"
170
+ }
171
+ });
172
+ if (!response.ok || !response.body) {
173
+ throw new Error(`Failed to download ${url}: ${response.status} ${response.statusText}`);
174
+ }
175
+ await (0, import_promises.writeFile)(destinationPath, Buffer.from(await response.arrayBuffer()));
176
+ }
177
+ async function verifyChecksum(checksumsPath, archiveName, archivePath) {
178
+ let checksums = await (0, import_promises.readFile)(checksumsPath, "utf8");
179
+ let expected = checksums.split("\n").map((line) => line.trim().split(/\s+/)).find((parts) => parts.length >= 2 && parts[1] === archiveName)?.[0];
180
+ if (!expected) {
181
+ throw new Error(`Checksum for ${archiveName} not found`);
182
+ }
183
+ let archiveBuffer = await (0, import_promises.readFile)(archivePath);
184
+ let actual = (0, import_node_crypto.createHash)("sha256").update(archiveBuffer).digest("hex");
185
+ if (actual !== expected) {
186
+ throw new Error(`Checksum verification failed for ${archiveName}`);
187
+ }
188
+ }
189
+ async function extractArchive(archivePath, extractDir) {
190
+ if (archivePath.endsWith(".zip")) {
191
+ await (0, import_extract_zip.default)(archivePath, { dir: extractDir });
192
+ return;
193
+ }
194
+ await tar.x({
195
+ file: archivePath,
196
+ cwd: extractDir
197
+ });
198
+ }
199
+ async function findBinary(rootDir, binaryName) {
200
+ let directPath = import_node_path.default.join(rootDir, binaryName);
201
+ if (await exists(directPath)) {
202
+ return directPath;
203
+ }
204
+ return await walkForBinary(rootDir, binaryName);
205
+ }
206
+ function getCacheRoot() {
207
+ if (process.env.METORIAL_CLI_CACHE_DIR) {
208
+ return process.env.METORIAL_CLI_CACHE_DIR;
209
+ }
210
+ return import_node_path.default.join(import_node_os.default.homedir(), ".metorial", "cli-npm");
211
+ }
212
+ function getPlatformSpec(tag) {
213
+ let goos = resolveGoOS();
214
+ let goarch = resolveGoArch();
215
+ let extension = goos === "windows" ? "zip" : "tar.gz";
216
+ let binaryName = goos === "windows" ? "metorial.exe" : "metorial";
217
+ return {
218
+ archiveName: `metorial_${tag.replace(/^v/, "")}_${goos}_${goarch}.${extension}`,
219
+ binaryName
220
+ };
221
+ }
222
+ function resolveGoOS() {
223
+ switch (process.platform) {
224
+ case "darwin":
225
+ return "darwin";
226
+ case "linux":
227
+ return "linux";
228
+ case "win32":
229
+ return "windows";
230
+ default:
231
+ throw new Error(`Unsupported platform: ${process.platform}`);
232
+ }
233
+ }
234
+ function resolveGoArch() {
235
+ switch (process.arch) {
236
+ case "x64":
237
+ return "amd64";
238
+ case "arm64":
239
+ return "arm64";
240
+ default:
241
+ throw new Error(`Unsupported architecture: ${process.arch}`);
242
+ }
243
+ }
244
+ function normalizeVersion(value) {
245
+ if (!value.trim()) {
246
+ throw new Error("Unable to resolve a CLI version");
247
+ }
248
+ return value.startsWith("v") ? value : `v${value}`;
249
+ }
250
+ async function makeTempDir(parentDir) {
251
+ let suffix = `${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
252
+ let tempDir = import_node_path.default.join(parentDir, suffix);
253
+ await (0, import_promises.mkdir)(tempDir, { recursive: true });
254
+ return tempDir;
255
+ }
256
+ async function exists(filePath) {
257
+ try {
258
+ await (0, import_promises.access)(filePath);
259
+ return true;
260
+ } catch {
261
+ return false;
262
+ }
263
+ }
264
+ async function walkForBinary(rootDir, binaryName) {
265
+ let entries = await (0, import_promises.readdir)(rootDir, { withFileTypes: true });
266
+ for (let entry of entries) {
267
+ let entryPath = import_node_path.default.join(rootDir, entry.name);
268
+ if (entry.isFile() && entry.name === binaryName) {
269
+ return entryPath;
270
+ }
271
+ if (entry.isDirectory()) {
272
+ try {
273
+ return await walkForBinary(entryPath, binaryName);
274
+ } catch {
275
+ continue;
276
+ }
277
+ }
278
+ }
279
+ throw new Error(`Downloaded archive did not contain ${binaryName}`);
280
+ }
281
+ function detectNodePackageManager(fileName) {
282
+ let userAgent = process.env.npm_config_user_agent || "";
283
+ if (userAgent.startsWith("pnpm/")) {
284
+ return "pnpm";
285
+ }
286
+ if (userAgent.startsWith("yarn/")) {
287
+ return "yarn";
288
+ }
289
+ if (userAgent.startsWith("bun/")) {
290
+ return "bun";
291
+ }
292
+ if (userAgent.startsWith("npm/")) {
293
+ return "npm";
294
+ }
295
+ let normalized = fileName.replaceAll("\\", "/").toLowerCase();
296
+ if (normalized.includes("/.bun/install/global/")) {
297
+ return "bun";
298
+ }
299
+ if (normalized.includes("/pnpm/global/") || normalized.includes("/pnpm-global/")) {
300
+ return "pnpm";
301
+ }
302
+ if (normalized.includes("/.config/yarn/global/") || normalized.includes("/.yarn/global/")) {
303
+ return "yarn";
304
+ }
305
+ return "npm";
306
+ }
307
+ // Annotate the CommonJS export names for ESM import in node:
308
+ 0 && (module.exports = {
309
+ npmInstallEnvironment,
310
+ replaceOutput,
311
+ resolveCLIPath,
312
+ runCLI,
313
+ runCLIAndExit
314
+ });
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "@metorial/cli-core",
3
+ "version": "0.0.0-dev.6",
4
+ "description": "Runtime helpers for downloading and invoking the Metorial CLI",
5
+ "license": "MIT",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "scripts": {
15
+ "build": "tsup --config tsup.config.ts"
16
+ },
17
+ "dependencies": {
18
+ "extract-zip": "^2.0.1",
19
+ "tar": "^7.4.3"
20
+ }
21
+ }