@paritylabs/par3 0.0.0-bootstrap.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 ADDED
@@ -0,0 +1,3 @@
1
+ # Server
2
+
3
+ Node.js WebSocket server for the Par3 app.
package/bin/t3.mjs ADDED
@@ -0,0 +1,176 @@
1
+ #!/usr/bin/env node
2
+ // The `@paritylabs/par3` npm package. Downloads this version's CLI archive from the GitHub
3
+ // Release into <T3 home>/runtime/versions/<version> (the folder the install
4
+ // scripts and `par3 update` use), verifies it against the release's SHA256SUMS,
5
+ // then runs it. `dist/bin.mjs` re-enters here for background services
6
+ // installed before 0.0.41, which start the npm package with Node and talk to
7
+ // it over an IPC channel.
8
+ import { spawn, spawnSync } from "node:child_process";
9
+ import crypto from "node:crypto";
10
+ import fs from "node:fs";
11
+ import os from "node:os";
12
+ import path from "node:path";
13
+
14
+ const { version } = JSON.parse(
15
+ fs.readFileSync(path.join(import.meta.dirname, "../package.json"), "utf8"),
16
+ );
17
+
18
+ const SUPPORTED = ["darwin-arm64", "linux-arm64", "linux-x64", "win32-arm64", "win32-x64"];
19
+
20
+ function t3Home() {
21
+ const raw = (process.env.T3CODE_HOME || "").trim();
22
+ if (raw === "") return path.join(os.homedir(), ".t3");
23
+ if (raw === "~") return os.homedir();
24
+ if (raw.startsWith("~/") || raw.startsWith("~\\")) return path.join(os.homedir(), raw.slice(2));
25
+ return path.resolve(raw);
26
+ }
27
+
28
+ async function get(url) {
29
+ const response = await fetch(url);
30
+ if (response.status === 404) {
31
+ throw new Error(
32
+ `${url} does not exist yet. If ${version} was just released, try again in a few minutes.`,
33
+ );
34
+ }
35
+ if (!response.ok) throw new Error(`GET ${url} returned HTTP ${response.status}`);
36
+ return response;
37
+ }
38
+
39
+ /** Downloads `url` to `file` and returns its sha256, showing progress on a terminal. */
40
+ async function download(url, file) {
41
+ const response = await get(url);
42
+ const total = Number(response.headers.get("content-length")) || 0;
43
+ const hash = crypto.createHash("sha256");
44
+ const out = fs.openSync(file, "w");
45
+ const tty = process.stderr.isTTY;
46
+ let received = 0;
47
+ let shown = -1;
48
+ try {
49
+ for await (const chunk of response.body) {
50
+ hash.update(chunk);
51
+ fs.writeSync(out, chunk);
52
+ received += chunk.byteLength;
53
+ // Redraw once per percent (or per MB without a length), not per chunk.
54
+ const step = total ? Math.floor((received * 100) / total) : Math.floor(received / 1048576);
55
+ if (tty && step !== shown) {
56
+ shown = step;
57
+ const percent = total ? ` ${step}%` : "";
58
+ process.stderr.write(
59
+ `\r\x1b[2Kpar3: downloading Par3 ${version}${percent} (${(received / 1048576).toFixed(1)} MB)`,
60
+ );
61
+ }
62
+ }
63
+ } finally {
64
+ fs.closeSync(out);
65
+ if (tty) process.stderr.write("\r\x1b[2K");
66
+ }
67
+ return hash.digest("hex");
68
+ }
69
+
70
+ async function install(dir, key) {
71
+ const archive = `t3-${version}-${key}.${process.platform === "win32" ? "zip" : "tar.gz"}`;
72
+ const base = (
73
+ process.env.T3CODE_RELEASE_BASE_URL || "https://github.com/Pho3niX90/par3/releases/download"
74
+ ).replace(/\/+$/, "");
75
+ const sums = await (await get(`${base}/v${version}/SHA256SUMS`)).text();
76
+ const expected = /^([0-9a-f]{64})\s+\*?(.+)$/im;
77
+ const line = sums
78
+ .split(/\r?\n/)
79
+ .map((l) => expected.exec(l.trim()))
80
+ .find((m) => m && m[2] === archive);
81
+ if (!line) throw new Error(`${archive} is not listed in the release's SHA256SUMS`);
82
+
83
+ // Extract beside the final folder and rename it into place, so an
84
+ // interrupted download never leaves a half-installed version behind.
85
+ fs.mkdirSync(path.dirname(dir), { recursive: true });
86
+ const staging = fs.mkdtempSync(`${dir}.staging-`);
87
+ try {
88
+ const file = path.join(staging, archive);
89
+ if ((await download(`${base}/v${version}/${archive}`, file)) !== line[1].toLowerCase()) {
90
+ throw new Error(`checksum mismatch for ${archive}`);
91
+ }
92
+ // Windows' own tar.exe reads zip; a Git-for-Windows tar on PATH cannot.
93
+ const tar =
94
+ process.platform === "win32" ? `${process.env.SystemRoot}\\System32\\tar.exe` : "tar";
95
+ const extract = spawnSync(tar, ["-xf", file, "-C", staging, "--strip-components=1"], {
96
+ stdio: "inherit",
97
+ });
98
+ if (extract.status !== 0) throw new Error(`could not extract ${archive}`);
99
+ fs.rmSync(file);
100
+ fs.writeFileSync(path.join(staging, ".install-complete"), `${version}\n`);
101
+ publish(staging, dir);
102
+ } finally {
103
+ fs.rmSync(staging, { recursive: true, force: true });
104
+ }
105
+ }
106
+
107
+ const isComplete = (dir) => {
108
+ try {
109
+ return fs.readFileSync(path.join(dir, ".install-complete"), "utf8").trim() === version;
110
+ } catch {
111
+ return false;
112
+ }
113
+ };
114
+
115
+ /**
116
+ * Moves a finished staging folder to `dir`. Rename never replaces an existing
117
+ * folder, so a complete install another launcher (or `par3 update`) published
118
+ * first is kept, never deleted from under a running process. Only a folder
119
+ * without the completion marker, left by an interrupted older installer, is
120
+ * cleared before trying again.
121
+ */
122
+ function publish(staging, dir) {
123
+ try {
124
+ fs.renameSync(staging, dir);
125
+ } catch (error) {
126
+ if (isComplete(dir)) return;
127
+ if (!fs.existsSync(dir)) throw error;
128
+ fs.rmSync(dir, { recursive: true, force: true });
129
+ fs.renameSync(staging, dir);
130
+ }
131
+ }
132
+
133
+ async function main() {
134
+ const key = `${process.platform}-${process.arch}`;
135
+ if (!SUPPORTED.includes(key)) {
136
+ throw new Error(
137
+ `no Par3 CLI build is available for ${key} (supported: ${SUPPORTED.join(", ")}).\n` +
138
+ "The desktop app is at https://github.com/Pho3niX90/par3/releases",
139
+ );
140
+ }
141
+ const dir = path.join(t3Home(), "runtime", "versions", version);
142
+ if (!isComplete(dir)) await install(dir, key);
143
+
144
+ // A service launcher talks to its server over IPC; pass that channel and
145
+ // signals through. In a terminal, Ctrl-C already reaches the child directly,
146
+ // so forwarding SIGINT too would deliver it twice.
147
+ const ipc = process.send !== undefined;
148
+ const child = spawn(
149
+ path.join(dir, process.platform === "win32" ? "t3.exe" : "t3"),
150
+ process.argv.slice(2),
151
+ {
152
+ stdio: ipc ? ["inherit", "inherit", "inherit", "ipc"] : "inherit",
153
+ // The executable lives in the T3 home, so this is how it knows npm launched it.
154
+ env: { ...process.env, T3CODE_NPM_LAUNCHER_PATH: import.meta.filename },
155
+ },
156
+ );
157
+ if (ipc) {
158
+ process.on("message", (message) => child.connected && child.send(message));
159
+ child.on("message", (message) => process.connected && process.send(message));
160
+ process.on("disconnect", () => child.connected && child.disconnect());
161
+ }
162
+ for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
163
+ process.on(signal, () => (ipc || signal !== "SIGINT") && child.kill(signal));
164
+ }
165
+ child.on("error", (error) => fail(error));
166
+ child.on("exit", (code, signal) =>
167
+ process.exit(code ?? 128 + (os.constants.signals[signal] || 1)),
168
+ );
169
+ }
170
+
171
+ function fail(error) {
172
+ process.stderr.write(`par3: ${error.message}\n`);
173
+ process.exit(1);
174
+ }
175
+
176
+ await main().catch(fail);
package/dist/bin.mjs ADDED
@@ -0,0 +1,2 @@
1
+ // Background services installed before 0.0.41 start the npm package from this path.
2
+ import "../bin/t3.mjs";
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@paritylabs/par3",
3
+ "version": "0.0.0-bootstrap.0",
4
+ "description": "Par3 CLI. Downloads the self-contained executable for this platform from the GitHub Release.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/pingdotgg/t3code",
9
+ "directory": "apps/server"
10
+ },
11
+ "bin": {
12
+ "par3": "./bin/t3.mjs",
13
+ "p3": "./bin/t3.mjs"
14
+ },
15
+ "files": [
16
+ "bin",
17
+ "dist"
18
+ ],
19
+ "engines": {
20
+ "node": ">=20.11"
21
+ }
22
+ }