@mercury-fw/utils 0.25.0
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/CHANGELOG.md +3 -0
- package/README.md +5 -0
- package/cli-binary.ts +109 -0
- package/dist/cli-binary.d.ts +36 -0
- package/dist/index.d.ts +6 -0
- package/index.ts +13 -0
- package/package.json +35 -0
package/CHANGELOG.md
ADDED
package/README.md
ADDED
package/cli-binary.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helper for provisioning a plugin's pinned CLI binary. Every plugin
|
|
3
|
+
* that owns a CLI binary downloads it the same way — the only thing that
|
|
4
|
+
* differs is the pin (repo/crate/version), which each plugin declares as data
|
|
5
|
+
* in its own package.json (`mercury.cliBinary`). This module is that common
|
|
6
|
+
* mechanism, factored out when the second plugin (Bitbucket) would otherwise
|
|
7
|
+
* have duplicated it verbatim.
|
|
8
|
+
*
|
|
9
|
+
* The pure parts (platform resolution, URL construction, reading the pin) are
|
|
10
|
+
* unit-tested; `downloadPinnedBinary` composes them with Bun's `fetch` and a
|
|
11
|
+
* file write and is the body of each plugin's postinstall script.
|
|
12
|
+
*
|
|
13
|
+
* The binary version is pinned as data rather than resolved to "latest" at
|
|
14
|
+
* install time: two builds weeks apart must bake the identical binary, so the
|
|
15
|
+
* pin is bumped deliberately. That reproducibility is why binary provisioning
|
|
16
|
+
* moved off the build-time `install-clis.sh` "latest per crate" resolution and
|
|
17
|
+
* onto the versioned plugin packages.
|
|
18
|
+
*/
|
|
19
|
+
import { chmod, mkdir } from "node:fs/promises";
|
|
20
|
+
|
|
21
|
+
/** The three platform triples the CLI monorepo publishes an asset for — there
|
|
22
|
+
* is deliberately no macos-x86_64 (Intel Mac) build. */
|
|
23
|
+
export type Platform = "linux-x86_64" | "linux-arm64" | "macos-arm64";
|
|
24
|
+
|
|
25
|
+
/** The pinned binary coordinates, read from a package.json's `mercury.cliBinary`. */
|
|
26
|
+
export type PinnedBinary = { repo: string; crate: string; version: string };
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Maps a Node `process.platform` / `process.arch` pair to the published asset
|
|
30
|
+
* platform triple, throwing for any combination with no asset (an Intel Mac,
|
|
31
|
+
* an unknown OS or architecture) rather than building a URL that 404s.
|
|
32
|
+
*/
|
|
33
|
+
export function resolvePlatform(platform: string, arch: string): Platform {
|
|
34
|
+
const os = platform === "linux" ? "linux" : platform === "darwin" ? "macos" : undefined;
|
|
35
|
+
const cpu = arch === "x64" ? "x86_64" : arch === "arm64" ? "arm64" : undefined;
|
|
36
|
+
if (!os || !cpu) {
|
|
37
|
+
throw new Error(`unsupported platform for CLI binary: ${platform}/${arch}`);
|
|
38
|
+
}
|
|
39
|
+
const triple = `${os}-${cpu}`;
|
|
40
|
+
// macos-x86_64 resolves above but has no published asset — reject it here so
|
|
41
|
+
// the failure is a clear "unsupported", not a download 404.
|
|
42
|
+
if (triple !== "linux-x86_64" && triple !== "linux-arm64" && triple !== "macos-arm64") {
|
|
43
|
+
throw new Error(`no published CLI asset for platform ${triple}`);
|
|
44
|
+
}
|
|
45
|
+
return triple;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Builds the GitHub release download URL for a pinned binary and a resolved
|
|
50
|
+
* platform. Releases are tagged per crate as `<crate>-v<version>` and each
|
|
51
|
+
* carries one asset per platform named `<crate>-<platform>`.
|
|
52
|
+
*/
|
|
53
|
+
export function binaryAssetUrl(pin: PinnedBinary, platform: Platform): string {
|
|
54
|
+
return `https://github.com/${pin.repo}/releases/download/${pin.crate}-v${pin.version}/${pin.crate}-${platform}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Reads and validates the `mercury.cliBinary` pin out of a package.json-shaped
|
|
59
|
+
* object, throwing a clear error when it's missing or malformed — a typo in the
|
|
60
|
+
* pin must fail the install loudly, not silently fetch the wrong thing.
|
|
61
|
+
*/
|
|
62
|
+
export function readPinnedBinary(pkg: unknown): PinnedBinary {
|
|
63
|
+
// Hand-validated rather than with zod: this helper runs from a plugin's
|
|
64
|
+
// postinstall during `bun install`, and a build-time helper must not depend
|
|
65
|
+
// on an external package that may not be linked yet at that point.
|
|
66
|
+
const cliBinary = (pkg as { mercury?: { cliBinary?: unknown } })?.mercury?.cliBinary;
|
|
67
|
+
const isNonEmptyString = (v: unknown): v is string => typeof v === "string" && v.length > 0;
|
|
68
|
+
if (
|
|
69
|
+
typeof cliBinary !== "object" ||
|
|
70
|
+
cliBinary === null ||
|
|
71
|
+
!isNonEmptyString((cliBinary as Record<string, unknown>).repo) ||
|
|
72
|
+
!isNonEmptyString((cliBinary as Record<string, unknown>).crate) ||
|
|
73
|
+
!isNonEmptyString((cliBinary as Record<string, unknown>).version)
|
|
74
|
+
) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
"invalid or missing mercury.cliBinary pin in package.json: expected { repo, crate, version } as non-empty strings",
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
const { repo, crate, version } = cliBinary as { repo: string; crate: string; version: string };
|
|
80
|
+
return { repo, crate, version };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Downloads a plugin's pinned CLI binary into its `bin/` directory. Called from
|
|
85
|
+
* a plugin's postinstall as `downloadPinnedBinary(pkg, import.meta.url)`, where
|
|
86
|
+
* `pkg` is the plugin's own imported package.json and the script lives in the
|
|
87
|
+
* plugin's `scripts/` — so `../bin/` resolves to the plugin's `bin/`. Uses
|
|
88
|
+
* Bun's `fetch` (no curl/jq). Throws on a failed download, failing the install
|
|
89
|
+
* loudly rather than producing an image without the binary it asked for.
|
|
90
|
+
*/
|
|
91
|
+
export async function downloadPinnedBinary(pkg: unknown, scriptUrl: string | URL): Promise<void> {
|
|
92
|
+
const pin = readPinnedBinary(pkg);
|
|
93
|
+
const platform = resolvePlatform(process.platform, process.arch);
|
|
94
|
+
const url = binaryAssetUrl(pin, platform);
|
|
95
|
+
|
|
96
|
+
const binDir = new URL("../bin/", scriptUrl);
|
|
97
|
+
const binPath = new URL(`./${pin.crate}`, binDir);
|
|
98
|
+
|
|
99
|
+
const response = await fetch(url);
|
|
100
|
+
if (!response.ok) {
|
|
101
|
+
throw new Error(`failed to download ${pin.crate} CLI from ${url}: HTTP ${response.status} ${response.statusText}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
await mkdir(binDir, { recursive: true });
|
|
105
|
+
await Bun.write(binPath, response);
|
|
106
|
+
await chmod(binPath, 0o755);
|
|
107
|
+
|
|
108
|
+
console.log(`[${pin.crate}] installed ${pin.crate} ${pin.version} (${platform}) from ${url}`);
|
|
109
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** The three platform triples the CLI monorepo publishes an asset for — there
|
|
2
|
+
* is deliberately no macos-x86_64 (Intel Mac) build. */
|
|
3
|
+
export type Platform = "linux-x86_64" | "linux-arm64" | "macos-arm64";
|
|
4
|
+
/** The pinned binary coordinates, read from a package.json's `mercury.cliBinary`. */
|
|
5
|
+
export type PinnedBinary = {
|
|
6
|
+
repo: string;
|
|
7
|
+
crate: string;
|
|
8
|
+
version: string;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Maps a Node `process.platform` / `process.arch` pair to the published asset
|
|
12
|
+
* platform triple, throwing for any combination with no asset (an Intel Mac,
|
|
13
|
+
* an unknown OS or architecture) rather than building a URL that 404s.
|
|
14
|
+
*/
|
|
15
|
+
export declare function resolvePlatform(platform: string, arch: string): Platform;
|
|
16
|
+
/**
|
|
17
|
+
* Builds the GitHub release download URL for a pinned binary and a resolved
|
|
18
|
+
* platform. Releases are tagged per crate as `<crate>-v<version>` and each
|
|
19
|
+
* carries one asset per platform named `<crate>-<platform>`.
|
|
20
|
+
*/
|
|
21
|
+
export declare function binaryAssetUrl(pin: PinnedBinary, platform: Platform): string;
|
|
22
|
+
/**
|
|
23
|
+
* Reads and validates the `mercury.cliBinary` pin out of a package.json-shaped
|
|
24
|
+
* object, throwing a clear error when it's missing or malformed — a typo in the
|
|
25
|
+
* pin must fail the install loudly, not silently fetch the wrong thing.
|
|
26
|
+
*/
|
|
27
|
+
export declare function readPinnedBinary(pkg: unknown): PinnedBinary;
|
|
28
|
+
/**
|
|
29
|
+
* Downloads a plugin's pinned CLI binary into its `bin/` directory. Called from
|
|
30
|
+
* a plugin's postinstall as `downloadPinnedBinary(pkg, import.meta.url)`, where
|
|
31
|
+
* `pkg` is the plugin's own imported package.json and the script lives in the
|
|
32
|
+
* plugin's `scripts/` — so `../bin/` resolves to the plugin's `bin/`. Uses
|
|
33
|
+
* Bun's `fetch` (no curl/jq). Throws on a failed download, failing the install
|
|
34
|
+
* loudly rather than producing an image without the binary it asked for.
|
|
35
|
+
*/
|
|
36
|
+
export declare function downloadPinnedBinary(pkg: unknown, scriptUrl: string | URL): Promise<void>;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@mercury-fw/utils` — shared, plugin-agnostic helpers used across the monorepo.
|
|
3
|
+
* Currently the CLI-binary provisioning used by each plugin's postinstall;
|
|
4
|
+
* more shared utilities land here as they're factored out.
|
|
5
|
+
*/
|
|
6
|
+
export { downloadPinnedBinary, resolvePlatform, binaryAssetUrl, readPinnedBinary, type Platform, type PinnedBinary, } from "./cli-binary.ts";
|
package/index.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@mercury-fw/utils` — shared, plugin-agnostic helpers used across the monorepo.
|
|
3
|
+
* Currently the CLI-binary provisioning used by each plugin's postinstall;
|
|
4
|
+
* more shared utilities land here as they're factored out.
|
|
5
|
+
*/
|
|
6
|
+
export {
|
|
7
|
+
downloadPinnedBinary,
|
|
8
|
+
resolvePlatform,
|
|
9
|
+
binaryAssetUrl,
|
|
10
|
+
readPinnedBinary,
|
|
11
|
+
type Platform,
|
|
12
|
+
type PinnedBinary,
|
|
13
|
+
} from "./cli-binary.ts";
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mercury-fw/utils",
|
|
3
|
+
"version": "0.25.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/lucabro81/mercury-fw.git",
|
|
8
|
+
"directory": "packages/libs/utils"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"*.ts",
|
|
12
|
+
"dist",
|
|
13
|
+
"CHANGELOG.md",
|
|
14
|
+
"!**/*.test.ts"
|
|
15
|
+
],
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"mercury-fw-source": "./index.ts",
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"default": "./index.ts"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"test": "bun test",
|
|
28
|
+
"typecheck": "tsc --noEmit"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@mercury-fw/typescript-config": "*",
|
|
32
|
+
"@types/bun": "^1.4.0",
|
|
33
|
+
"typescript": "^6.0.3"
|
|
34
|
+
}
|
|
35
|
+
}
|