@servelink/serve 0.7.0 → 0.8.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/README.md +15 -9
- package/bin/serve.js +69 -18
- package/package.json +10 -5
- package/install.js +0 -184
package/README.md
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
# @servelink/serve
|
|
2
2
|
|
|
3
|
-
Public tunnel for hosted serve accounts. This package is a thin wrapper
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
is
|
|
3
|
+
Public tunnel for hosted serve accounts. This package is a thin wrapper that
|
|
4
|
+
launches the native `serve` CLI binary for your platform. The binary is bundled
|
|
5
|
+
in a dedicated platform package (`@servelink/serve-darwin-arm64`,
|
|
6
|
+
`@servelink/serve-darwin-x64`, `@servelink/serve-linux-arm64`,
|
|
7
|
+
`@servelink/serve-linux-x64`, `@servelink/serve-win32-x64`) that is installed as
|
|
8
|
+
an **optionalDependency** of this package. There is no postinstall and no
|
|
9
|
+
download at install time, so `npm` never runs or prompts about lifecycle
|
|
10
|
+
scripts. The binary that runs is exactly the CI-published GoReleaser artifact —
|
|
11
|
+
no Go code is compiled or downloaded here.
|
|
9
12
|
|
|
10
13
|
## Install
|
|
11
14
|
|
|
@@ -15,6 +18,11 @@ npm i -g @servelink/serve
|
|
|
15
18
|
npx @servelink/serve
|
|
16
19
|
```
|
|
17
20
|
|
|
21
|
+
npm automatically installs the platform package that matches your OS/arch. If
|
|
22
|
+
your platform is unsupported by npm, install via the
|
|
23
|
+
[Homebrew tap](https://github.com/servelink-swyftlabs/homebrew-tap) or a release
|
|
24
|
+
archive from [serve-dist](https://github.com/servelink-swyftlabs/serve-dist).
|
|
25
|
+
|
|
18
26
|
## Use
|
|
19
27
|
|
|
20
28
|
```sh
|
|
@@ -27,6 +35,4 @@ The primary interface is **MCP**: with a bare `serve` registered in your AI
|
|
|
27
35
|
coding agent, the agent can "serve me this" — a file, a directory, or an
|
|
28
36
|
already-running dev server — and hand back a public HTTPS URL.
|
|
29
37
|
|
|
30
|
-
See the README at the repository root for full onboarding.
|
|
31
|
-
unsupported by npm, install via the [Homebrew tap](https://github.com/servelink-swyftlabs/homebrew-tap)
|
|
32
|
-
or a release archive from serve-dist.
|
|
38
|
+
See the README at the repository root for full onboarding.
|
package/bin/serve.js
CHANGED
|
@@ -1,29 +1,80 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
|
-
// @servelink/serve — CLI entrypoint.
|
|
5
|
-
//
|
|
6
|
-
//
|
|
4
|
+
// @servelink/serve — CLI entrypoint.
|
|
5
|
+
//
|
|
6
|
+
// Since v0.8.0 this package ships no install-time download: the native `serve`
|
|
7
|
+
// CLI binary for the current platform/arch is bundled in a dedicated platform
|
|
8
|
+
// package that is installed as an **optionalDependency** (the esbuild/SWC/sharp
|
|
9
|
+
// model). This launcher only resolves that matching package and spawns its
|
|
10
|
+
// binary, forwarding args and inheriting stdio — there are no lifecycle
|
|
11
|
+
// scripts anywhere, so npm never prompts on any version.
|
|
7
12
|
|
|
8
13
|
const { spawnSync } = require("node:child_process");
|
|
9
|
-
const
|
|
14
|
+
const { createRequire } = require("node:module");
|
|
10
15
|
const path = require("node:path");
|
|
11
16
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
17
|
+
// Mapping from "<process.platform>-<process.arch>" to the platform package
|
|
18
|
+
// that ships the matching native binary. Mirrors .goreleaser.yaml: serve builds
|
|
19
|
+
// for darwin/linux (amd64 + arm64) and windows/amd64 only.
|
|
20
|
+
const PLATFORM_PACKAGES = {
|
|
21
|
+
"darwin-arm64": "@servelink/serve-darwin-arm64",
|
|
22
|
+
"darwin-x64": "@servelink/serve-darwin-x64",
|
|
23
|
+
"linux-arm64": "@servelink/serve-linux-arm64",
|
|
24
|
+
"linux-x64": "@servelink/serve-linux-x64",
|
|
25
|
+
"win32-x64": "@servelink/serve-win32-x64",
|
|
26
|
+
};
|
|
15
27
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
);
|
|
21
|
-
process.exit(1);
|
|
28
|
+
// Resolves the platform package name for a platform/arch pair, or null when
|
|
29
|
+
// the pair has no published binary. Exported for tests.
|
|
30
|
+
function resolvePlatformPackage(platform, arch) {
|
|
31
|
+
return PLATFORM_PACKAGES[platform + "-" + arch] || null;
|
|
22
32
|
}
|
|
23
33
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
34
|
+
// Resolves the absolute path to the native binary inside the given platform
|
|
35
|
+
// package. `fromPath` defaults to this file's directory and is overridable so
|
|
36
|
+
// tests can resolve against a temp fixtures tree. Exported for tests.
|
|
37
|
+
function resolveBin(pkgName, platform, fromPath) {
|
|
38
|
+
const req = createRequire(path.join(fromPath || __dirname, "__launcher__.js"));
|
|
39
|
+
// The platform package has no main/index.js; resolve its package.json instead.
|
|
40
|
+
const pkgRoot = path.dirname(req.resolve(pkgName + "/package.json"));
|
|
41
|
+
const binName = platform === "win32" ? "serve.exe" : "serve";
|
|
42
|
+
return path.join(pkgRoot, "bin", binName);
|
|
28
43
|
}
|
|
29
|
-
|
|
44
|
+
|
|
45
|
+
function run() {
|
|
46
|
+
const pkgName = resolvePlatformPackage(process.platform, process.arch);
|
|
47
|
+
if (!pkgName) {
|
|
48
|
+
console.error(
|
|
49
|
+
"serve: unsupported platform " + process.platform + "/" + process.arch + ".\n" +
|
|
50
|
+
"Install the native CLI via the Homebrew tap " +
|
|
51
|
+
"(https://github.com/servelink-swyftlabs/homebrew-tap) or a release " +
|
|
52
|
+
"archive from https://github.com/servelink-swyftlabs/serve-dist/releases"
|
|
53
|
+
);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let binPath;
|
|
58
|
+
try {
|
|
59
|
+
binPath = resolveBin(pkgName, process.platform);
|
|
60
|
+
} catch (err) {
|
|
61
|
+
console.error(
|
|
62
|
+
"serve: platform package " + pkgName + " is not installed.\n" +
|
|
63
|
+
"Reinstall the CLI with: npm i -g @servelink/serve"
|
|
64
|
+
);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const res = spawnSync(binPath, process.argv.slice(2), { stdio: "inherit" });
|
|
69
|
+
if (res.error) {
|
|
70
|
+
console.error("serve: failed to launch native binary: " + res.error.message);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
process.exit(res.status === null ? 1 : res.status);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (require.main === module) {
|
|
77
|
+
run();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = { resolvePlatformPackage, resolveBin, PLATFORM_PACKAGES };
|
package/package.json
CHANGED
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@servelink/serve",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Public tunnel for hosted serve accounts. Thin wrapper that
|
|
3
|
+
"version": "0.8.0",
|
|
4
|
+
"description": "Public tunnel for hosted serve accounts. Thin wrapper that launches the platform-native serve CLI shipped as an optional dependency.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"serve": "bin/serve.js"
|
|
7
7
|
},
|
|
8
8
|
"scripts": {
|
|
9
|
-
"
|
|
10
|
-
"test": "node --test test/install.test.js"
|
|
9
|
+
"test": "node --test test/*.test.js"
|
|
11
10
|
},
|
|
12
11
|
"files": [
|
|
13
12
|
"bin/",
|
|
14
|
-
"install.js",
|
|
15
13
|
"README.md"
|
|
16
14
|
],
|
|
15
|
+
"optionalDependencies": {
|
|
16
|
+
"@servelink/serve-darwin-arm64": "0.8.0",
|
|
17
|
+
"@servelink/serve-darwin-x64": "0.8.0",
|
|
18
|
+
"@servelink/serve-linux-arm64": "0.8.0",
|
|
19
|
+
"@servelink/serve-linux-x64": "0.8.0",
|
|
20
|
+
"@servelink/serve-win32-x64": "0.8.0"
|
|
21
|
+
},
|
|
17
22
|
"engines": {
|
|
18
23
|
"node": ">=18"
|
|
19
24
|
},
|
package/install.js
DELETED
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
|
|
4
|
-
// @servelink/serve — postinstall.
|
|
5
|
-
// Downloads the matching-platform `serve` client binary from the public
|
|
6
|
-
// servelink-swyftlabs/serve-dist releases repo, verifies it against checksums.txt, and
|
|
7
|
-
// extracts it into ./native so bin/serve.js can invoke it. The binary that runs
|
|
8
|
-
// is exactly the GoReleaser artifact — this package contains no Go code.
|
|
9
|
-
|
|
10
|
-
const crypto = require("node:crypto");
|
|
11
|
-
const fs = require("node:fs");
|
|
12
|
-
const https = require("node:https");
|
|
13
|
-
const path = require("node:path");
|
|
14
|
-
const zlib = require("node:zlib");
|
|
15
|
-
|
|
16
|
-
const REPO = "servelink-swyftlabs/serve-dist";
|
|
17
|
-
const DIST = "https://github.com/" + REPO + "/releases/download";
|
|
18
|
-
|
|
19
|
-
function resolveVersion() {
|
|
20
|
-
const pkg = require("./package.json");
|
|
21
|
-
const v = pkg.version;
|
|
22
|
-
if (!v || v === "0.0.0") {
|
|
23
|
-
throw new Error(
|
|
24
|
-
"@servelink/serve: package version must be set before publish. " +
|
|
25
|
-
"CI publishes with the GoReleaser tag as the npm version (see release.yml)."
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
return v;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Nothing is ever silently skipped: an unsupported platform fails loudly and
|
|
32
|
-
// points at the manual install paths.
|
|
33
|
-
function resolvePlatform() {
|
|
34
|
-
const osMap = { darwin: "darwin", linux: "linux", win32: "windows" };
|
|
35
|
-
const archMap = { x64: "amd64", arm64: "arm64" };
|
|
36
|
-
const os = osMap[process.platform];
|
|
37
|
-
const arch = archMap[process.arch];
|
|
38
|
-
if (!os || !arch) {
|
|
39
|
-
throw new Error(
|
|
40
|
-
"@servelink/serve: unsupported platform " + process.platform + "/" + process.arch + ". " +
|
|
41
|
-
"Install via the Homebrew tap or the release archive from " +
|
|
42
|
-
"https://github.com/" + REPO + "/releases"
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
// GoReleaser serves client archives for darwin/linux (amd64+arm64) and
|
|
46
|
-
// windows/amd64. Anything else has no published artifact.
|
|
47
|
-
if (os === "windows" && arch !== "amd64") {
|
|
48
|
-
throw new Error(
|
|
49
|
-
"@servelink/serve: no published binary for windows/" + arch + ". " +
|
|
50
|
-
"Install via the release archive from https://github.com/" + REPO + "/releases"
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
return { os, arch };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Parses a GoReleaser checksums.txt (one "<sha256> <filename>" per line) and
|
|
57
|
-
// returns the expected sha256 hex for filename, or null if absent.
|
|
58
|
-
function checksumFor(filename, checksumsText) {
|
|
59
|
-
for (const raw of checksumsText.split(/\r?\n/)) {
|
|
60
|
-
const line = raw.trim();
|
|
61
|
-
if (!line) continue;
|
|
62
|
-
const m = /^([0-9a-fA-F]{64})\s+(\S+)\s*$/.exec(line);
|
|
63
|
-
if (m && m[2] === filename) return m[1].toLowerCase();
|
|
64
|
-
}
|
|
65
|
-
return null;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function verifySha256(data, expectedHex) {
|
|
69
|
-
return crypto.createHash("sha256").update(data).digest("hex") === expectedHex;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function download(url, redirects) {
|
|
73
|
-
redirects = redirects || 0;
|
|
74
|
-
return new Promise((resolve, reject) => {
|
|
75
|
-
const req = https.get(url, { headers: { "User-Agent": "@servelink/serve-installer" } }, (res) => {
|
|
76
|
-
const status = res.statusCode;
|
|
77
|
-
if (status >= 300 && status < 400 && res.headers.location && redirects < 5) {
|
|
78
|
-
res.resume();
|
|
79
|
-
download(new URL(res.headers.location, url).toString(), redirects + 1).then(resolve, reject);
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
if (status !== 200) {
|
|
83
|
-
res.resume();
|
|
84
|
-
reject(new Error("HTTP " + status + " while downloading " + url));
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
const chunks = [];
|
|
88
|
-
res.on("data", (c) => chunks.push(c));
|
|
89
|
-
res.on("end", () => resolve(Buffer.concat(chunks)));
|
|
90
|
-
res.on("error", reject);
|
|
91
|
-
});
|
|
92
|
-
req.on("error", reject);
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Minimal ustar tar reader over an already-gunzipped buffer. Only regular-file
|
|
97
|
-
// entries are kept. GoReleaser archives use short top-level names (serve,
|
|
98
|
-
// LICENSE, README.md), so PAX/GNU long-name extensions are not needed.
|
|
99
|
-
function readTarFiles(buf) {
|
|
100
|
-
const files = new Map();
|
|
101
|
-
let offset = 0;
|
|
102
|
-
const readStr = (o, len) => {
|
|
103
|
-
let end = o;
|
|
104
|
-
while (end < o + len && buf[end] !== 0) end++;
|
|
105
|
-
return buf.toString("utf8", o, end);
|
|
106
|
-
};
|
|
107
|
-
while (offset + 512 <= buf.length) {
|
|
108
|
-
const base = offset; // absolute offset of the current 512-byte header
|
|
109
|
-
const name = readStr(base, 100);
|
|
110
|
-
if (name.length === 0) break; // end-of-archive marker (zero block)
|
|
111
|
-
const size = parseInt(readStr(base + 124, 12).trim(), 8) || 0;
|
|
112
|
-
const typeflag = String.fromCharCode(buf[base + 156]);
|
|
113
|
-
const dataStart = base + 512;
|
|
114
|
-
offset = dataStart + Math.ceil(size / 512) * 512;
|
|
115
|
-
if (typeflag === "0" || typeflag === "" || typeflag === "\u0000") {
|
|
116
|
-
files.set(name, buf.subarray(dataStart, dataStart + size));
|
|
117
|
-
}
|
|
118
|
-
// Directories, symlinks, PAX headers, etc. are intentionally ignored.
|
|
119
|
-
}
|
|
120
|
-
return files;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
async function run() {
|
|
124
|
-
const version = resolveVersion();
|
|
125
|
-
const { os, arch } = resolvePlatform();
|
|
126
|
-
|
|
127
|
-
const archiveName = "serve_" + version + "_" + os + "_" + arch + ".tar.gz";
|
|
128
|
-
const distDir = path.join(__dirname, "native");
|
|
129
|
-
fs.mkdirSync(distDir, { recursive: true });
|
|
130
|
-
|
|
131
|
-
// The GitHub release tag on serve-dist is `v<version>` (e.g. v0.4.0), while
|
|
132
|
-
// the GoReleaser archive filename uses {{ .Version }} (the tag with the leading
|
|
133
|
-
// "v" stripped, e.g. serve_0.4.0_darwin_amd64.tar.gz). The npm package version
|
|
134
|
-
// is set to the stripped tag, so the archive name matches directly, but the
|
|
135
|
-
// download URL must re-add the "v" prefix to hit the actual release tag.
|
|
136
|
-
const releaseTag = "v" + version;
|
|
137
|
-
const archiveUrl = DIST + "/" + releaseTag + "/" + archiveName;
|
|
138
|
-
const checksumsUrl = DIST + "/" + releaseTag + "/checksums.txt";
|
|
139
|
-
|
|
140
|
-
const [archive, checksumsText] = await Promise.all([
|
|
141
|
-
download(archiveUrl),
|
|
142
|
-
download(checksumsUrl).then((b) => b.toString("utf8")),
|
|
143
|
-
]);
|
|
144
|
-
|
|
145
|
-
const expected = checksumFor(archiveName, checksumsText);
|
|
146
|
-
if (!expected) {
|
|
147
|
-
throw new Error("@servelink/serve: " + archiveName + " missing from checksums.txt; cannot verify integrity.");
|
|
148
|
-
}
|
|
149
|
-
if (!verifySha256(archive, expected)) {
|
|
150
|
-
throw new Error(
|
|
151
|
-
"@servelink/serve: checksum mismatch for " + archiveName +
|
|
152
|
-
". Refusing to install an unverified binary."
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const files = readTarFiles(zlib.gunzipSync(archive));
|
|
157
|
-
const entry = files.get("serve");
|
|
158
|
-
if (!entry) {
|
|
159
|
-
throw new Error("@servelink/serve: 'serve' not found in " + archiveName);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const binPath = path.join(distDir, "serve");
|
|
163
|
-
fs.writeFileSync(binPath, entry);
|
|
164
|
-
fs.chmodSync(binPath, 0o755);
|
|
165
|
-
|
|
166
|
-
const { spawnSync } = require("node:child_process");
|
|
167
|
-
const probe = spawnSync(binPath, ["--version"], { encoding: "utf8" });
|
|
168
|
-
if (probe.status !== 0) {
|
|
169
|
-
throw new Error(
|
|
170
|
-
"@servelink/serve: installed binary failed --version check: " +
|
|
171
|
-
(probe.stderr || probe.stdout || "exit " + probe.status)
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
console.log("@servelink/serve: installed serve " + probe.stdout.trim() + " (" + os + "/" + arch + ")");
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
if (require.main === module) {
|
|
178
|
-
run().catch((err) => {
|
|
179
|
-
console.error(err && err.message ? err.message : String(err));
|
|
180
|
-
process.exit(1);
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
module.exports = { resolvePlatform, checksumFor, verifySha256, readTarFiles };
|