executor 2.0.0-beta.36 → 2.0.0-beta.38
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 +26 -19
- package/bin.mjs +199 -22
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# Executor 2 beta
|
|
2
2
|
|
|
3
|
-
Install with npm, `npm i -g executor@beta
|
|
4
|
-
or newer. Without Node, run `curl -fsSL https://executor.sh/docs/install.sh | sh`
|
|
3
|
+
Install with npm, `npm i -g executor@beta`, on Node 14.18 or newer. Without Node, run `curl -fsSL https://executor.sh/docs/install.sh | sh`
|
|
5
4
|
on macOS or Linux, or `irm https://executor.sh/docs/install.ps1 | iex` in
|
|
6
5
|
PowerShell on Windows. Each release also attaches its archives and `SHA256SUMS`.
|
|
7
6
|
Run `executor` to start the background Executor, or join the one already
|
|
@@ -23,10 +22,13 @@ Executor is one executable, built with Bun, with the files it runs or reads
|
|
|
23
22
|
beside it: the dashboard, the app framework and compiler references, workerd,
|
|
24
23
|
Git, the native credential-store binding and the Motel collector. It needs no
|
|
25
24
|
source checkout, Node, Bun or 1Password on the user machine. The `executor` npm
|
|
26
|
-
package is a launcher
|
|
27
|
-
|
|
28
|
-
(arm64 and x64, glibc)
|
|
29
|
-
|
|
25
|
+
package is a launcher. The first time a version starts, it downloads that
|
|
26
|
+
release's archive for the platform, for macOS (Apple Silicon and Intel), Linux
|
|
27
|
+
(arm64 and x64, glibc) or Windows (x64), from the public GitHub release, checks it
|
|
28
|
+
against the SHA-256 the package records, and keeps it in
|
|
29
|
+
`~/.executor/v2/launcher/<version>` with the version before it. The same archive
|
|
30
|
+
is the release download the install scripts and Homebrew use.
|
|
31
|
+
`EXECUTOR_DOWNLOAD_URL` names a mirror of the releases for all of them.
|
|
30
32
|
|
|
31
33
|
Beta uses `~/.executor/v2/cli` for its data. This path stays the same at the
|
|
32
34
|
stable release. It is separate from Executor 1 data. One server runs per data
|
|
@@ -79,20 +81,25 @@ Native artifacts are under `.local/releases/<version>-<platform>-<arch>/`.
|
|
|
79
81
|
`release:cli` bundles the runtime twice. `package/` holds the JavaScript runtime
|
|
80
82
|
the desktop app runs with its own Node. `standalone/` holds the release install:
|
|
81
83
|
the `executor` executable (`bun build --compile`) and the files beside it, which
|
|
82
|
-
`executor-<version>-<platform>-<arch>.tgz` packs as the platform's
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
`executor-<version>-<platform>-<arch>.tgz` packs as the platform's release
|
|
85
|
+
archive. The executable takes `runtime/cli.mjs` in its install directory as
|
|
86
|
+
every module's location, so resources, `require` and `import.meta.resolve` find
|
|
87
|
+
the same files beside it wherever the archive is unpacked, through any launcher
|
|
88
|
+
symlink. Packages that must stay on disk load from
|
|
87
89
|
`runtime/node_modules`; Git is in `runtime/git`.
|
|
88
|
-
The npm launcher is under `.local/releases/<version>/wrapper
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
90
|
+
The npm launcher is under `.local/releases/<version>/wrapper/`. Its
|
|
91
|
+
package.json records the SHA-256 of each platform archive found under
|
|
92
|
+
`.local/releases/<version>-<platform>-<arch>/`, or under the directory
|
|
93
|
+
`release:wrapper` is given; publishing requires all five. The runtimes are never
|
|
94
|
+
published to npm. Through 2.0.0-beta.36 each was published as a version of the
|
|
95
|
+
`executor` package, such as `2.0.0-beta.36-win32-x64`, which semver ranks above
|
|
96
|
+
the launcher, so a range update such as `bun update -g` replaced the launcher
|
|
97
|
+
with a runtime. Those versions are deprecated so package managers skip them.
|
|
98
|
+
These build commands do not publish anything.
|
|
99
|
+
The app compiler and framework are prepared for workerd at build time, so the
|
|
100
|
+
executable downloads nothing more. Most of each archive is the executable,
|
|
101
|
+
workerd and the bundled Git, which keeps only what the runtime runs (`keptGit`
|
|
102
|
+
in `runtime-bundle.ts`).
|
|
96
103
|
External source maps stay in the separate CI artifact for release diagnostics.
|
|
97
104
|
|
|
98
105
|
Stock Bun lets any local process run its own code inside a compiled executable,
|
package/bin.mjs
CHANGED
|
@@ -1,39 +1,216 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// The `executor` npm package's command.
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
|
|
2
|
+
// The `executor` npm package's command. The package holds no runtime: on first use of a version it
|
|
3
|
+
// downloads that release's archive for this platform from the public GitHub release, checks it
|
|
4
|
+
// against the SHA-256 this package's package.json records, and unpacks it into
|
|
5
|
+
// ~/.executor/v2/launcher/<version>. It then starts the Executor executable there, passes signals
|
|
6
|
+
// on, and exits as it exits.
|
|
7
|
+
//
|
|
8
|
+
// npm, Bun, pnpm and Yarn resolve `executor` alone, so an update can only ever land on another
|
|
9
|
+
// launcher; the running executable lives outside node_modules, so an update never has to replace a
|
|
10
|
+
// file in use. The executable carries its own runtime, so this file uses only what every Node since
|
|
11
|
+
// 14 has.
|
|
12
|
+
import { spawn, spawnSync } from "child_process";
|
|
13
|
+
import { createHash, randomBytes } from "crypto";
|
|
14
|
+
import {
|
|
15
|
+
createWriteStream,
|
|
16
|
+
existsSync,
|
|
17
|
+
mkdirSync,
|
|
18
|
+
readdirSync,
|
|
19
|
+
renameSync,
|
|
20
|
+
rmSync,
|
|
21
|
+
statSync,
|
|
22
|
+
utimesSync,
|
|
23
|
+
} from "fs";
|
|
24
|
+
import { request as httpRequest } from "http";
|
|
25
|
+
import { request as httpsRequest } from "https";
|
|
6
26
|
import { createRequire } from "module";
|
|
7
|
-
import {
|
|
27
|
+
import { homedir } from "os";
|
|
28
|
+
import { join } from "path";
|
|
8
29
|
import { fileURLToPath } from "url";
|
|
9
30
|
|
|
10
31
|
const require = createRequire(import.meta.url);
|
|
11
32
|
const manifest = require("./package.json");
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
33
|
+
const version = manifest.version;
|
|
34
|
+
const target = process.platform + "-" + process.arch;
|
|
35
|
+
const digest = (manifest.runtimes || {})[target];
|
|
36
|
+
const channel = version.indexOf("-beta.") === -1 ? "" : "@beta";
|
|
37
|
+
const executableName = process.platform === "win32" ? "executor.exe" : "executor";
|
|
38
|
+
const archive = "executor-" + version + "-" + target + ".tgz";
|
|
39
|
+
// The same mirror setting the install scripts and `executor upgrade` read.
|
|
40
|
+
const downloads = (
|
|
41
|
+
process.env.EXECUTOR_DOWNLOAD_URL ||
|
|
42
|
+
"https://github.com/UsefulSoftwareCo/executor/releases/download"
|
|
43
|
+
).replace(/\/+$/, "");
|
|
44
|
+
const url = downloads + "/executor@" + version + "/" + archive;
|
|
45
|
+
const root = join(homedir(), ".executor", "v2", "launcher");
|
|
46
|
+
const installed = join(root, version);
|
|
47
|
+
const executable = join(installed, executableName);
|
|
15
48
|
|
|
16
49
|
const fail = (lines) => {
|
|
17
50
|
for (const line of lines) console.error(line);
|
|
18
51
|
process.exit(1);
|
|
19
52
|
};
|
|
20
53
|
|
|
21
|
-
if (
|
|
54
|
+
if (typeof digest !== "string")
|
|
22
55
|
fail(["Executor does not support " + process.platform + "/" + process.arch + "."]);
|
|
23
56
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
57
|
+
/** GET `address`, following redirects; resolves to the response of the final location. */
|
|
58
|
+
const get = (address, redirects) =>
|
|
59
|
+
new Promise((resolve, reject) => {
|
|
60
|
+
const request = (address.startsWith("http:") ? httpRequest : httpsRequest)(
|
|
61
|
+
address,
|
|
62
|
+
{ headers: { "user-agent": "executor-launcher/" + version } },
|
|
63
|
+
(response) => {
|
|
64
|
+
const status = response.statusCode || 0;
|
|
65
|
+
if (status >= 300 && status < 400 && response.headers.location) {
|
|
66
|
+
response.resume();
|
|
67
|
+
if (redirects >= 10) return reject(new Error("too many redirects"));
|
|
68
|
+
return resolve(get(new URL(response.headers.location, address).href, redirects + 1));
|
|
69
|
+
}
|
|
70
|
+
if (status !== 200) {
|
|
71
|
+
response.resume();
|
|
72
|
+
return reject(new Error("HTTP " + status));
|
|
73
|
+
}
|
|
74
|
+
resolve(response);
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
// A connection that stops sending for a minute is not coming back.
|
|
78
|
+
request.setTimeout(60000, () => request.destroy(new Error("the connection stalled")));
|
|
79
|
+
request.on("error", reject);
|
|
80
|
+
request.end();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
/** Download the archive to `file`, hashing it as it arrives; resolves to its SHA-256. */
|
|
84
|
+
const download = async (file) => {
|
|
85
|
+
const response = await get(url, 0);
|
|
86
|
+
const total = Number(response.headers["content-length"]) || 0;
|
|
87
|
+
const hash = createHash("sha256");
|
|
88
|
+
const out = createWriteStream(file);
|
|
89
|
+
const tty = process.stderr.isTTY;
|
|
90
|
+
let received = 0;
|
|
91
|
+
let shown = -1;
|
|
92
|
+
const label = "Downloading Executor " + version + " for " + target;
|
|
93
|
+
if (!tty) console.error(label + " (first run of this version)");
|
|
94
|
+
await new Promise((resolve, reject) => {
|
|
95
|
+
response.on("data", (chunk) => {
|
|
96
|
+
hash.update(chunk);
|
|
97
|
+
received += chunk.length;
|
|
98
|
+
const step = total ? Math.floor((received * 100) / total) : Math.floor(received / 1048576);
|
|
99
|
+
if (tty && step !== shown) {
|
|
100
|
+
shown = step;
|
|
101
|
+
const amount = (received / 1048576).toFixed(1) + " MB";
|
|
102
|
+
process.stderr.write(
|
|
103
|
+
"\r\x1b[2K" + label + (total ? " " + step + "%" : "") + " (" + amount + ")",
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
response.on("error", reject);
|
|
108
|
+
out.on("error", reject);
|
|
109
|
+
out.on("finish", resolve);
|
|
110
|
+
response.pipe(out);
|
|
111
|
+
});
|
|
112
|
+
if (tty) process.stderr.write("\r\x1b[2K");
|
|
113
|
+
return hash.digest("hex");
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Keep this version and the most recently installed other one, which a background Executor started
|
|
118
|
+
* before an update may still run from, as the install scripts keep `previous`. Downloads that
|
|
119
|
+
* stopped part way are removed after a day. Anything in use, which Windows refuses to delete, stays
|
|
120
|
+
* for a later download to remove.
|
|
121
|
+
*/
|
|
122
|
+
const prune = () => {
|
|
123
|
+
const day = 24 * 60 * 60 * 1000;
|
|
124
|
+
const entries = readdirSync(root)
|
|
125
|
+
.filter((name) => name !== version)
|
|
126
|
+
.map((name) => ({ name, time: statSync(join(root, name)).mtimeMs }));
|
|
127
|
+
const versions = entries.filter((entry) => !entry.name.startsWith("."));
|
|
128
|
+
versions.sort((left, right) => right.time - left.time);
|
|
129
|
+
const stale = versions
|
|
130
|
+
.slice(1)
|
|
131
|
+
.concat(entries.filter((entry) => entry.name.startsWith(".") && Date.now() - entry.time > day));
|
|
132
|
+
for (const entry of stale) {
|
|
133
|
+
try {
|
|
134
|
+
rmSync(join(root, entry.name), { recursive: true, force: true });
|
|
135
|
+
} catch {
|
|
136
|
+
// In use; a later download removes it.
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const install = async () => {
|
|
142
|
+
mkdirSync(root, { recursive: true });
|
|
143
|
+
const staging = join(root, ".download-" + process.pid + "-" + randomBytes(4).toString("hex"));
|
|
144
|
+
mkdirSync(staging);
|
|
145
|
+
// `fail` exits at once, without running `finally`.
|
|
146
|
+
const cleanup = () => rmSync(staging, { recursive: true, force: true });
|
|
147
|
+
process.on("exit", cleanup);
|
|
148
|
+
try {
|
|
149
|
+
let actual;
|
|
150
|
+
try {
|
|
151
|
+
actual = await download(join(staging, archive));
|
|
152
|
+
} catch (error) {
|
|
153
|
+
fail([
|
|
154
|
+
"Executor could not download its runtime from " + url + ": " + error.message + ".",
|
|
155
|
+
"Check your connection and try again. EXECUTOR_DOWNLOAD_URL names a mirror of the releases.",
|
|
156
|
+
]);
|
|
157
|
+
}
|
|
158
|
+
if (actual !== digest)
|
|
159
|
+
fail([
|
|
160
|
+
archive +
|
|
161
|
+
" does not match the SHA-256 this package records (expected " +
|
|
162
|
+
digest +
|
|
163
|
+
", got " +
|
|
164
|
+
actual +
|
|
165
|
+
").",
|
|
166
|
+
"Nothing was installed. Check EXECUTOR_DOWNLOAD_URL, or reinstall with: npm i -g executor" +
|
|
167
|
+
channel,
|
|
168
|
+
]);
|
|
169
|
+
// The system's tar, as the install scripts use: Windows' own bsdtar, since Git's GNU tar on
|
|
170
|
+
// PATH reads a drive letter as a remote host. The archive path is relative for the same reason.
|
|
171
|
+
const tar =
|
|
172
|
+
process.platform === "win32"
|
|
173
|
+
? join(process.env.SystemRoot || "C:\\Windows", "System32", "tar.exe")
|
|
174
|
+
: "tar";
|
|
175
|
+
// Nothing reaches stdout, which an MCP client starting `executor mcp` reads as the protocol.
|
|
176
|
+
const unpacked = spawnSync(tar, ["-xzf", archive], {
|
|
177
|
+
cwd: staging,
|
|
178
|
+
stdio: ["ignore", "ignore", "inherit"],
|
|
179
|
+
});
|
|
180
|
+
if (unpacked.status !== 0)
|
|
181
|
+
fail([
|
|
182
|
+
"Executor could not unpack " +
|
|
183
|
+
archive +
|
|
184
|
+
" with " +
|
|
185
|
+
tar +
|
|
186
|
+
(unpacked.error ? ": " + unpacked.error.message : "."),
|
|
187
|
+
]);
|
|
188
|
+
try {
|
|
189
|
+
renameSync(join(staging, "package"), installed);
|
|
190
|
+
} catch (error) {
|
|
191
|
+
// Another `executor` command installed this version first.
|
|
192
|
+
if (!existsSync(executable))
|
|
193
|
+
fail([
|
|
194
|
+
"Executor could not install its runtime in " + installed + ": " + error.message,
|
|
195
|
+
"Remove " + installed + " and try again.",
|
|
196
|
+
]);
|
|
197
|
+
}
|
|
198
|
+
// The version's time orders it for pruning; the archive's own times are from 1985.
|
|
199
|
+
const now = new Date();
|
|
200
|
+
utimesSync(installed, now, now);
|
|
201
|
+
} finally {
|
|
202
|
+
process.removeListener("exit", cleanup);
|
|
203
|
+
cleanup();
|
|
204
|
+
}
|
|
205
|
+
try {
|
|
206
|
+
prune();
|
|
207
|
+
} catch {
|
|
208
|
+
// Pruning never stops Executor from starting.
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
if (!existsSync(executable)) await install();
|
|
35
213
|
|
|
36
|
-
const executable = join(root, process.platform === "win32" ? "executor.exe" : "executor");
|
|
37
214
|
const child = spawn(executable, process.argv.slice(2), {
|
|
38
215
|
stdio: "inherit",
|
|
39
216
|
env: Object.assign({}, process.env, {
|
|
@@ -57,7 +234,7 @@ for (const signal of signals) process.on(signal, forward);
|
|
|
57
234
|
child.on("error", (error) =>
|
|
58
235
|
fail([
|
|
59
236
|
"Executor could not start " + executable + ": " + error.message,
|
|
60
|
-
"
|
|
237
|
+
"Remove " + installed + " to download it again.",
|
|
61
238
|
]),
|
|
62
239
|
);
|
|
63
240
|
child.on("exit", (code, signal) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "executor",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.38",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Run Executor locally and connect your agents to your apps.",
|
|
6
6
|
"homepage": "https://executor.sh",
|
|
@@ -27,11 +27,11 @@
|
|
|
27
27
|
"access": "public",
|
|
28
28
|
"tag": "beta"
|
|
29
29
|
},
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
30
|
+
"runtimes": {
|
|
31
|
+
"darwin-arm64": "eb01447cfdb59c03aec9e16e5989d717ddc33bded40c2b864e2aa1b0482b73e0",
|
|
32
|
+
"darwin-x64": "126fb19eb8a421507a199ded24bec5fc79ee345d76f17b8fb571f5639bd31ba4",
|
|
33
|
+
"linux-x64": "01f232a7cd13aa5116092fc8767741c12d566502fdfc53ab851ab9b3ead8e413",
|
|
34
|
+
"linux-arm64": "172784c097fccf30d4d3f238b72ba048bc724c0d5d263feac189cbb67d73b3f9",
|
|
35
|
+
"win32-x64": "2e9726e74d6b2414a29971bd7603056675542a9cc2edeba35eef388b5a954936"
|
|
36
36
|
}
|
|
37
37
|
}
|