@t3x-dev/local 0.1.4 → 0.3.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/package.json +5 -5
- package/runtime-manifest.json +16 -16
- package/scripts/postinstall-download.mjs +84 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@t3x-dev/local",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "T3X local entry package",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -38,10 +38,10 @@
|
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@hono/node-server": "^1.15.2",
|
|
41
|
-
"@t3x-dev/api": "0.
|
|
42
|
-
"@t3x-dev/cli": "0.
|
|
43
|
-
"@t3x-dev/mcp": "0.
|
|
44
|
-
"@t3x-dev/storage": "0.
|
|
41
|
+
"@t3x-dev/api": "0.3.0",
|
|
42
|
+
"@t3x-dev/cli": "0.3.0",
|
|
43
|
+
"@t3x-dev/mcp": "0.3.0",
|
|
44
|
+
"@t3x-dev/storage": "0.3.0",
|
|
45
45
|
"commander": "^13.1.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
package/runtime-manifest.json
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifestVersion": 1,
|
|
3
|
-
"packageVersion": "0.
|
|
4
|
-
"fixedVersion": "0.
|
|
5
|
-
"generatedAt": "2026-
|
|
3
|
+
"packageVersion": "0.3.0",
|
|
4
|
+
"fixedVersion": "0.3.0",
|
|
5
|
+
"generatedAt": "2026-05-03T07:35:37.449Z",
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"@t3x-dev/yops": "0.
|
|
8
|
-
"@t3x-dev/yschema": "0.
|
|
9
|
-
"@t3x-dev/core": "0.
|
|
10
|
-
"@t3x-dev/storage": "0.
|
|
11
|
-
"@t3x-dev/api": "0.
|
|
12
|
-
"@t3x-dev/api-client": "0.
|
|
13
|
-
"@t3x-dev/cli": "0.
|
|
14
|
-
"@t3x-dev/mcp": "0.
|
|
15
|
-
"@t3x-dev/local": "0.
|
|
7
|
+
"@t3x-dev/yops": "0.3.0",
|
|
8
|
+
"@t3x-dev/yschema": "0.3.0",
|
|
9
|
+
"@t3x-dev/core": "0.3.0",
|
|
10
|
+
"@t3x-dev/storage": "0.3.0",
|
|
11
|
+
"@t3x-dev/api": "0.3.0",
|
|
12
|
+
"@t3x-dev/api-client": "0.3.0",
|
|
13
|
+
"@t3x-dev/cli": "0.3.0",
|
|
14
|
+
"@t3x-dev/mcp": "0.3.0",
|
|
15
|
+
"@t3x-dev/local": "0.3.0",
|
|
16
16
|
"web": "0.1.0"
|
|
17
17
|
},
|
|
18
18
|
"platforms": {
|
|
19
19
|
"darwin-arm64": {
|
|
20
|
-
"fileName": "t3x-local-runtime-0.
|
|
21
|
-
"url": "https://github.com/t3x-dev/t3x-core/releases/download/t3x-local-v0.
|
|
22
|
-
"sha256": "
|
|
23
|
-
"size":
|
|
20
|
+
"fileName": "t3x-local-runtime-0.3.0-darwin-arm64.tar.gz",
|
|
21
|
+
"url": "https://github.com/t3x-dev/t3x-core/releases/download/t3x-local-v0.3.0/t3x-local-runtime-0.3.0-darwin-arm64.tar.gz",
|
|
22
|
+
"sha256": "7f9c52d3ce51585aa7232bd2d16078f8e6e080264224862462423429227d4cbb",
|
|
23
|
+
"size": 20420629
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
}
|
|
@@ -2,9 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
import { spawnSync } from 'node:child_process';
|
|
4
4
|
import crypto from 'node:crypto';
|
|
5
|
+
import { createWriteStream } from 'node:fs';
|
|
5
6
|
import fs from 'node:fs/promises';
|
|
6
7
|
import os from 'node:os';
|
|
7
8
|
import path from 'node:path';
|
|
9
|
+
import { Readable } from 'node:stream';
|
|
10
|
+
import { pipeline } from 'node:stream/promises';
|
|
11
|
+
import { setTimeout as sleep } from 'node:timers/promises';
|
|
8
12
|
import {
|
|
9
13
|
assertRuntimeLayout,
|
|
10
14
|
ensureDir,
|
|
@@ -36,6 +40,11 @@ const LOCAL_DIRECT_FIXED_DEPENDENCIES = [
|
|
|
36
40
|
'@t3x-dev/storage',
|
|
37
41
|
];
|
|
38
42
|
const GITHUB_TOKEN_ENV_NAMES = ['T3X_LOCAL_GITHUB_TOKEN', 'GH_TOKEN', 'GITHUB_TOKEN'];
|
|
43
|
+
const DOWNLOAD_MAX_ATTEMPTS = parsePositiveInt(process.env.T3X_LOCAL_DOWNLOAD_ATTEMPTS, 3);
|
|
44
|
+
const DOWNLOAD_RETRY_DELAY_MS = parsePositiveInt(
|
|
45
|
+
process.env.T3X_LOCAL_DOWNLOAD_RETRY_DELAY_MS,
|
|
46
|
+
3000
|
|
47
|
+
);
|
|
39
48
|
|
|
40
49
|
if (process.env.T3X_LOCAL_SKIP_DOWNLOAD === '1' || process.env.T3X_LOCAL_SKIP_DOWNLOAD === 'true') {
|
|
41
50
|
console.log(
|
|
@@ -132,20 +141,58 @@ async function downloadArtifact(source, destinationPath) {
|
|
|
132
141
|
}
|
|
133
142
|
|
|
134
143
|
if (source.startsWith('http://') || source.startsWith('https://')) {
|
|
135
|
-
|
|
144
|
+
await downloadHttpArtifact(source, destinationPath);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
await fs.copyFile(source, destinationPath);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function downloadHttpArtifact(source, destinationPath) {
|
|
152
|
+
let lastError = null;
|
|
153
|
+
|
|
154
|
+
for (let attempt = 1; attempt <= DOWNLOAD_MAX_ATTEMPTS; attempt += 1) {
|
|
155
|
+
try {
|
|
156
|
+
await fs.rm(destinationPath, { force: true });
|
|
136
157
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
158
|
+
const response = await fetchRuntimeArtifact(source);
|
|
159
|
+
if (!response.ok) {
|
|
160
|
+
throw buildHttpDownloadError(source, response.status);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
await writeResponseBody(response, destinationPath);
|
|
164
|
+
return;
|
|
165
|
+
} catch (error) {
|
|
166
|
+
lastError = error;
|
|
167
|
+
|
|
168
|
+
if (attempt === DOWNLOAD_MAX_ATTEMPTS || !isRetryableDownloadError(error)) {
|
|
169
|
+
throw error;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
console.log(
|
|
173
|
+
`[t3x-local:postinstall] Runtime download failed (${getErrorSummary(error)}). Retrying ${attempt + 1}/${DOWNLOAD_MAX_ATTEMPTS}...`
|
|
140
174
|
);
|
|
175
|
+
await sleep(DOWNLOAD_RETRY_DELAY_MS * attempt);
|
|
141
176
|
}
|
|
177
|
+
}
|
|
142
178
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
179
|
+
throw lastError;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function writeResponseBody(response, destinationPath) {
|
|
183
|
+
if (!response.body) {
|
|
184
|
+
throw new Error('[t3x-local:postinstall] Runtime download response did not contain a body');
|
|
146
185
|
}
|
|
147
186
|
|
|
148
|
-
await
|
|
187
|
+
await pipeline(Readable.fromWeb(response.body), createWriteStream(destinationPath));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function buildHttpDownloadError(source, status) {
|
|
191
|
+
const error = new Error(
|
|
192
|
+
`[t3x-local:postinstall] Failed to download runtime: HTTP ${status}${getDownloadHint(source)}`
|
|
193
|
+
);
|
|
194
|
+
error.httpStatus = status;
|
|
195
|
+
return error;
|
|
149
196
|
}
|
|
150
197
|
|
|
151
198
|
async function fetchRuntimeArtifact(source) {
|
|
@@ -287,6 +334,35 @@ function isAuthRetryStatus(status) {
|
|
|
287
334
|
return status === 403 || status === 404;
|
|
288
335
|
}
|
|
289
336
|
|
|
337
|
+
function isRetryableDownloadError(error) {
|
|
338
|
+
if (isRetryableHttpStatus(error?.httpStatus)) {
|
|
339
|
+
return true;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const code = error?.code ?? error?.cause?.code;
|
|
343
|
+
return (
|
|
344
|
+
code === 'ETIMEDOUT' ||
|
|
345
|
+
code === 'ECONNRESET' ||
|
|
346
|
+
code === 'EPIPE' ||
|
|
347
|
+
code === 'UND_ERR_BODY_TIMEOUT' ||
|
|
348
|
+
code === 'UND_ERR_SOCKET' ||
|
|
349
|
+
error instanceof TypeError
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function isRetryableHttpStatus(status) {
|
|
354
|
+
return status === 408 || status === 429 || (status >= 500 && status <= 599);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function getErrorSummary(error) {
|
|
358
|
+
return error?.cause?.code ?? error?.code ?? error?.message ?? String(error);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function parsePositiveInt(value, fallbackValue) {
|
|
362
|
+
const parsed = Number.parseInt(value ?? '', 10);
|
|
363
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallbackValue;
|
|
364
|
+
}
|
|
365
|
+
|
|
290
366
|
async function verifyArchiveSha(archivePath, expectedSha) {
|
|
291
367
|
const file = await fs.readFile(archivePath);
|
|
292
368
|
const actualSha = crypto.createHash('sha256').update(file).digest('hex');
|