@t3x-dev/local 0.1.2 → 0.1.4

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 CHANGED
@@ -8,5 +8,8 @@ Current PR2 scope:
8
8
  - `t3x` forwards to `@t3x-dev/cli`
9
9
  - `t3x-mcp` forwards to `@t3x-dev/mcp`
10
10
 
11
- This phase uses local build artifacts already present in the monorepo.
12
- It does not download runtime assets yet.
11
+ On package install, `postinstall` downloads the platform runtime asset from
12
+ `runtime-manifest.json`.
13
+
14
+ For private GitHub releases, set `T3X_LOCAL_GITHUB_TOKEN`, `GH_TOKEN`, or
15
+ `GITHUB_TOKEN` to a token with access to the runtime release.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@t3x-dev/local",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "T3X local entry package",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -17,7 +17,7 @@
17
17
  "runtime-manifest.json"
18
18
  ],
19
19
  "publishConfig": {
20
- "access": "public"
20
+ "access": "restricted"
21
21
  },
22
22
  "repository": {
23
23
  "type": "git",
@@ -38,10 +38,10 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@hono/node-server": "^1.15.2",
41
- "@t3x-dev/api": "0.1.2",
42
- "@t3x-dev/cli": "0.1.2",
43
- "@t3x-dev/mcp": "0.1.2",
44
- "@t3x-dev/storage": "0.1.2",
41
+ "@t3x-dev/api": "0.1.4",
42
+ "@t3x-dev/cli": "0.1.4",
43
+ "@t3x-dev/mcp": "0.1.4",
44
+ "@t3x-dev/storage": "0.1.4",
45
45
  "commander": "^13.1.0"
46
46
  },
47
47
  "devDependencies": {
@@ -1,38 +1,26 @@
1
1
  {
2
2
  "manifestVersion": 1,
3
- "packageVersion": "0.1.2",
4
- "fixedVersion": "0.1.2",
5
- "generatedAt": "2026-04-23T09:12:14.667Z",
3
+ "packageVersion": "0.1.4",
4
+ "fixedVersion": "0.1.4",
5
+ "generatedAt": "2026-04-27T05:44:57.580Z",
6
6
  "dependencies": {
7
- "@t3x-dev/yops": "0.1.2",
8
- "@t3x-dev/yschema": "0.1.2",
9
- "@t3x-dev/core": "0.1.2",
10
- "@t3x-dev/storage": "0.1.2",
11
- "@t3x-dev/api": "0.1.2",
12
- "@t3x-dev/api-client": "0.1.2",
13
- "@t3x-dev/cli": "0.1.2",
14
- "@t3x-dev/mcp": "0.1.2",
15
- "@t3x-dev/local": "0.1.2",
7
+ "@t3x-dev/yops": "0.1.4",
8
+ "@t3x-dev/yschema": "0.1.4",
9
+ "@t3x-dev/core": "0.1.4",
10
+ "@t3x-dev/storage": "0.1.4",
11
+ "@t3x-dev/api": "0.1.4",
12
+ "@t3x-dev/api-client": "0.1.4",
13
+ "@t3x-dev/cli": "0.1.4",
14
+ "@t3x-dev/mcp": "0.1.4",
15
+ "@t3x-dev/local": "0.1.4",
16
16
  "web": "0.1.0"
17
17
  },
18
18
  "platforms": {
19
19
  "darwin-arm64": {
20
- "fileName": "t3x-local-runtime-0.1.2-darwin-arm64.tar.gz",
21
- "url": "https://github.com/t3x-dev/t3x-core/releases/download/t3x-local-v0.1.2/t3x-local-runtime-0.1.2-darwin-arm64.tar.gz",
22
- "sha256": "d90390ca1a90be083a78d4caf670d027985084eafbbfbc001fb84f30565b7a85",
23
- "size": 20245492
24
- },
25
- "linux-arm64": {
26
- "fileName": "t3x-local-runtime-0.1.2-linux-arm64.tar.gz",
27
- "url": "https://github.com/t3x-dev/t3x-core/releases/download/t3x-local-v0.1.2/t3x-local-runtime-0.1.2-linux-arm64.tar.gz",
28
- "sha256": "384afcf94e9208122a995980fbdf9fee072e14514ce269898726fd357e359b2c",
29
- "size": 28136616
30
- },
31
- "linux-x64": {
32
- "fileName": "t3x-local-runtime-0.1.2-linux-x64.tar.gz",
33
- "url": "https://github.com/t3x-dev/t3x-core/releases/download/t3x-local-v0.1.2/t3x-local-runtime-0.1.2-linux-x64.tar.gz",
34
- "sha256": "5ed8c8fb5597c2ecac0593ffc48f604b7484df538965c07d81663d3ad29a7c87",
35
- "size": 27977895
20
+ "fileName": "t3x-local-runtime-0.1.4-darwin-arm64.tar.gz",
21
+ "url": "https://github.com/t3x-dev/t3x-core/releases/download/t3x-local-v0.1.4/t3x-local-runtime-0.1.4-darwin-arm64.tar.gz",
22
+ "sha256": "6d5ab00096f233a4a02ab982cdea0375d3ec793d5f7454bfc7bf9cd2fd90b830",
23
+ "size": 20377274
36
24
  }
37
25
  }
38
26
  }
@@ -35,6 +35,7 @@ const LOCAL_DIRECT_FIXED_DEPENDENCIES = [
35
35
  '@t3x-dev/mcp',
36
36
  '@t3x-dev/storage',
37
37
  ];
38
+ const GITHUB_TOKEN_ENV_NAMES = ['T3X_LOCAL_GITHUB_TOKEN', 'GH_TOKEN', 'GITHUB_TOKEN'];
38
39
 
39
40
  if (process.env.T3X_LOCAL_SKIP_DOWNLOAD === '1' || process.env.T3X_LOCAL_SKIP_DOWNLOAD === 'true') {
40
41
  console.log(
@@ -131,11 +132,11 @@ async function downloadArtifact(source, destinationPath) {
131
132
  }
132
133
 
133
134
  if (source.startsWith('http://') || source.startsWith('https://')) {
134
- const response = await fetch(source);
135
+ const response = await fetchRuntimeArtifact(source);
135
136
 
136
137
  if (!response.ok) {
137
138
  throw new Error(
138
- `[t3x-local:postinstall] Failed to download runtime: HTTP ${response.status}`
139
+ `[t3x-local:postinstall] Failed to download runtime: HTTP ${response.status}${getDownloadHint(source)}`
139
140
  );
140
141
  }
141
142
 
@@ -147,6 +148,145 @@ async function downloadArtifact(source, destinationPath) {
147
148
  await fs.copyFile(source, destinationPath);
148
149
  }
149
150
 
151
+ async function fetchRuntimeArtifact(source) {
152
+ const response = await fetch(source, { headers: getDownloadHeaders(source) });
153
+ const token = getGitHubToken();
154
+
155
+ if (
156
+ response.ok ||
157
+ !token ||
158
+ !isGitHubReleaseDownloadUrl(source) ||
159
+ !isAuthRetryStatus(response.status)
160
+ ) {
161
+ return response;
162
+ }
163
+
164
+ const apiResponse = await fetchGitHubReleaseAsset(source, token);
165
+ return apiResponse ?? response;
166
+ }
167
+
168
+ function getDownloadHeaders(source) {
169
+ if (!isGitHubUrl(source)) {
170
+ return undefined;
171
+ }
172
+
173
+ const token = getGitHubToken();
174
+ if (!token) {
175
+ return undefined;
176
+ }
177
+
178
+ return {
179
+ Authorization: `Bearer ${token}`,
180
+ 'User-Agent': '@t3x-dev/local postinstall',
181
+ };
182
+ }
183
+
184
+ function getDownloadHint(source) {
185
+ if (!isGitHubUrl(source) || getGitHubToken()) {
186
+ return '';
187
+ }
188
+
189
+ return `; set ${GITHUB_TOKEN_ENV_NAMES[0]}, GH_TOKEN, or GITHUB_TOKEN if the runtime release is private`;
190
+ }
191
+
192
+ function getGitHubToken() {
193
+ for (const envName of GITHUB_TOKEN_ENV_NAMES) {
194
+ const token = process.env[envName]?.trim();
195
+ if (token) {
196
+ return token;
197
+ }
198
+ }
199
+
200
+ return null;
201
+ }
202
+
203
+ async function fetchGitHubReleaseAsset(source, token) {
204
+ const releaseAsset = parseGitHubReleaseDownloadUrl(source);
205
+ if (!releaseAsset) {
206
+ return null;
207
+ }
208
+
209
+ const releaseUrl = `https://api.github.com/repos/${releaseAsset.owner}/${releaseAsset.repo}/releases/tags/${releaseAsset.tag}`;
210
+ const releaseResponse = await fetch(releaseUrl, {
211
+ headers: getGitHubApiHeaders(token),
212
+ });
213
+
214
+ if (!releaseResponse.ok) {
215
+ return releaseResponse;
216
+ }
217
+
218
+ const release = await releaseResponse.json();
219
+ const asset = release.assets?.find((candidate) => candidate.name === releaseAsset.assetName);
220
+
221
+ if (!asset?.url) {
222
+ return new Response(null, {
223
+ status: 404,
224
+ statusText: `Release asset ${releaseAsset.assetName} not found`,
225
+ });
226
+ }
227
+
228
+ return fetch(asset.url, {
229
+ headers: {
230
+ ...getGitHubApiHeaders(token),
231
+ Accept: 'application/octet-stream',
232
+ },
233
+ });
234
+ }
235
+
236
+ function getGitHubApiHeaders(token) {
237
+ return {
238
+ Authorization: `Bearer ${token}`,
239
+ Accept: 'application/vnd.github+json',
240
+ 'X-GitHub-Api-Version': '2022-11-28',
241
+ 'User-Agent': '@t3x-dev/local postinstall',
242
+ };
243
+ }
244
+
245
+ function isGitHubUrl(source) {
246
+ try {
247
+ const { hostname } = new URL(source);
248
+ return hostname === 'github.com' || hostname === 'api.github.com';
249
+ } catch {
250
+ return false;
251
+ }
252
+ }
253
+
254
+ function isGitHubReleaseDownloadUrl(source) {
255
+ return parseGitHubReleaseDownloadUrl(source) !== null;
256
+ }
257
+
258
+ function parseGitHubReleaseDownloadUrl(source) {
259
+ try {
260
+ const url = new URL(source);
261
+ if (url.hostname !== 'github.com') {
262
+ return null;
263
+ }
264
+
265
+ const [, owner, repo, releases, download, tag, ...assetParts] = url.pathname.split('/');
266
+ if (!owner || !repo || releases !== 'releases' || download !== 'download' || !tag) {
267
+ return null;
268
+ }
269
+
270
+ const assetName = assetParts.join('/');
271
+ if (!assetName) {
272
+ return null;
273
+ }
274
+
275
+ return {
276
+ owner: encodeURIComponent(owner),
277
+ repo: encodeURIComponent(repo),
278
+ tag: encodeURIComponent(decodeURIComponent(tag)),
279
+ assetName: decodeURIComponent(assetName),
280
+ };
281
+ } catch {
282
+ return null;
283
+ }
284
+ }
285
+
286
+ function isAuthRetryStatus(status) {
287
+ return status === 403 || status === 404;
288
+ }
289
+
150
290
  async function verifyArchiveSha(archivePath, expectedSha) {
151
291
  const file = await fs.readFile(archivePath);
152
292
  const actualSha = crypto.createHash('sha256').update(file).digest('hex');