gdx 0.4.6 → 0.5.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdx",
3
- "version": "0.4.6",
3
+ "version": "0.5.0",
4
4
  "description": "Git, but with better DX. The raw power of Git, aligned with human workflows.",
5
5
  "homepage": "https://github.com/Type-Delta/gdx#readme",
6
6
  "bugs": {
@@ -25,12 +25,13 @@
25
25
  "files": [
26
26
  "dist/*.js",
27
27
  "dist/workers/*.js",
28
+ "dist/diagnostics/*.js",
28
29
  "scripts/postinstall.cjs",
29
30
  "scripts/launcher.cjs",
30
31
  "README.md",
31
32
  "LICENSE"
32
33
  ],
33
- "packageManager": "bun@1.3.10",
34
+ "packageManager": "bun@1.3.14",
34
35
  "scripts": {
35
36
  "prepare-dev": "bun i && bun run transpile-esm",
36
37
  "start": "bun run src/index.ts",
@@ -38,11 +39,17 @@
38
39
  "start:node": "tsx src/index.ts",
39
40
  "transpile-esm": "bunx tsc -p lib/tsconfig.json && bun run scripts/transform-tools.mjs",
40
41
  "check": "bun run ts-check && bun run lint && bun run build",
41
- "lint": "bun run ts-check && eslint src --ext .ts -c eslint.config.ts --fix",
42
- "test": "bun test",
43
- "build": "bun run transpile-esm && bun build ./src/index.ts --outfile=./bin/gdx --compile --bytecode --production --keep-names",
42
+ "lint": "bun run ts-check && eslint src --ext .ts -c eslint.config.ts",
43
+ "lint:fix": "bun run ts-check && eslint src --ext .ts -c eslint.config.ts --fix",
44
+ "test:serial": "bun test",
45
+ "test": "bun test --parallel --timeout 20000",
46
+ "build": "bun run transpile-esm && bun run build:diagnostics:bin && GDX_BUILD=$(git describe --tags --always --dirty) bun build ./src/index.ts --env='GDX_BUILD*' --outfile=./bin/gdx --compile --bytecode --production --keep-names",
47
+ "build:release:win32-x64": "bun run transpile-esm && bun run build:diagnostics:bin && GDX_BUILD=$(git describe --tags --always --dirty) bun build ./src/index.ts --env='GDX_BUILD*' --outfile=./bin/gdx-win32-x64.exe --compile --bytecode --production --keep-names",
48
+ "build:diagnostics:bin": "bun build ./test/postinstall.validator.ts --outfile=./bin/diagnostics/postinstall.validator.js --target=bun --format=esm --production --minify --keep-names",
49
+ "build:diagnostics:node": "bun build ./test/postinstall.validator.ts --outfile=./dist/diagnostics/postinstall.validator.js --target=node --format=esm --production --minify --keep-names",
44
50
  "ts-check": "bunx tsc --noEmit",
45
- "package:node": "bun run transpile-esm && bun build ./src/index.ts --outdir=./dist --target=node --external=keytar --external=cspell-lib --external=@shikijs/cli --external=shiki --external=yaml --external=openai --external=fflate --external=diff --format=esm --production --keep-names && bun build ./src/workers/generic.worker.ts --outfile=./dist/workers/generic.worker.min.js --target=node --external=@shikijs/cli --format=esm --production --minify --keep-names",
51
+ "package:node": "bun run transpile-esm && GDX_BUILD=$(git describe --tags --always --dirty) bun build ./src/index.ts --env='GDX_BUILD*' --outdir=./dist --target=node --external=keytar --external=cspell-lib --external=@shikijs/cli --external=shiki --external=yaml --external=openai --external=fflate --external=diff --external=@leeoniya/ufuzzy --format=esm --production --keep-names && bun build ./src/workers/generic.worker.ts --outfile=./dist/workers/generic.worker.min.js --target=node --external=@shikijs/cli --format=esm --production --minify --keep-names && bun run build:diagnostics:node",
52
+ "publish:npm": "npm login && npm publish --access public",
46
53
  "prepack": "bun run package:node",
47
54
  "postinstall": "node scripts/postinstall.cjs",
48
55
  "prettier": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\" \"./*.md\"",
@@ -56,14 +63,14 @@
56
63
  "@babel/core": "^7.29.0",
57
64
  "@babel/preset-env": "^7.29.5",
58
65
  "@babel/preset-typescript": "^7.28.5",
59
- "@eslint/js": "^9.39.4",
66
+ "@eslint/js": "^10.0.1",
60
67
  "@sinclair/typebox": "^0.34.49",
61
68
  "@types/chai": "^5.2.3",
62
69
  "@types/ini": "^4.1.1",
63
70
  "@types/which": "^3.0.4",
64
71
  "@typescript-eslint/eslint-plugin": "^8.59.4",
65
72
  "@typescript-eslint/parser": "^8.59.4",
66
- "bun-types": "latest",
73
+ "bun-types": "1.3.14",
67
74
  "chai": "^6.2.2",
68
75
  "dedent": "^1.7.2",
69
76
  "eslint": "^10.4.0",
@@ -81,6 +88,7 @@
81
88
  "which": "2.0.2"
82
89
  },
83
90
  "dependencies": {
91
+ "@leeoniya/ufuzzy": "^1.0.19",
84
92
  "@shikijs/cli": "^4.1.0",
85
93
  "cspell-lib": "10.0.0",
86
94
  "diff": "^9.0.0",
@@ -6,36 +6,55 @@ const fs = require('fs');
6
6
  const path = require('path');
7
7
  const { spawn } = require('child_process');
8
8
 
9
- const DIST_DIR = path.join(__dirname, '../dist');
10
- const NATIVE_DIR = path.join(DIST_DIR, 'native');
9
+ const DIST_DIR = path.join(__dirname, '../dist');
10
+ const NATIVE_DIR = path.join(__dirname, '../bin/native');
11
11
  const isWin = process.platform === 'win32';
12
12
  const binaryName = isWin ? 'gdx.exe' : 'gdx';
13
- const binaryPath = path.join(NATIVE_DIR, binaryName);
14
13
 
15
- if (process.env.GDX_NODE_SHIM === '1' && process.argv[2] === '--') {
14
+ if (
15
+ (process.env.GDX_RUNTIME_SHIM === '1' || process.env.GDX_NODE_SHIM === '1') &&
16
+ process.argv[2] === '--'
17
+ ) {
16
18
  process.argv.splice(2, 1);
17
19
  }
18
20
 
19
- // Check if native binary exists
20
- if (fs.existsSync(binaryPath)) {
21
- const child = spawn(binaryPath, process.argv.slice(2), {
22
- stdio: 'inherit'
23
- });
24
-
25
- child.on('close', (code) => {
26
- process.exit(code);
27
- });
28
-
29
- child.on('error', (err) => {
30
- console.error('Failed to start native binary:', err);
31
- process.exit(1);
32
- });
33
- } else {
34
- // Fallback to Node.js runtime (dist/index.js)
35
- // Since dist/index.js is ESM, we use dynamic import
36
- const jsEntry = path.join(DIST_DIR, 'index.js');
37
- import(require('url').pathToFileURL(jsEntry)).catch(err => {
38
- console.error('Failed to load JS fallback:', err);
39
- process.exit(1);
40
- });
41
- }
21
+ function getNativeBinaryPath() {
22
+ return path.join(NATIVE_DIR, binaryName);
23
+ }
24
+
25
+ function runLauncher() {
26
+ const binaryPath = getNativeBinaryPath();
27
+
28
+ // Check if native binary exists
29
+ if (fs.existsSync(binaryPath)) {
30
+ const child = spawn(binaryPath, process.argv.slice(2), {
31
+ stdio: 'inherit'
32
+ });
33
+
34
+ child.on('close', (code) => {
35
+ process.exit(code);
36
+ });
37
+
38
+ child.on('error', (err) => {
39
+ console.error('Failed to start native binary:', err);
40
+ process.exit(1);
41
+ });
42
+ } else {
43
+ // Fallback to Node.js runtime (dist/index.js)
44
+ // Since dist/index.js is ESM, we use dynamic import
45
+ const jsEntry = path.join(DIST_DIR, 'index.js');
46
+ import(require('url').pathToFileURL(jsEntry)).catch(err => {
47
+ console.error('Failed to load JS fallback:', err);
48
+ process.exit(1);
49
+ });
50
+ }
51
+ }
52
+
53
+ if (require.main === module) {
54
+ runLauncher();
55
+ }
56
+
57
+ module.exports = {
58
+ getNativeBinaryPath,
59
+ runLauncher,
60
+ };
@@ -2,6 +2,7 @@
2
2
  /* eslint-disable @typescript-eslint/no-require-imports */
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
+ const crypto = require('crypto');
5
6
  const { spawnSync, execFileSync } = require('child_process');
6
7
 
7
8
  // Configuration
@@ -38,6 +39,35 @@ function writeInstallInfo(info) {
38
39
  fs.writeFileSync(INSTALL_INFO_PATH, JSON.stringify(info, null, 2));
39
40
  }
40
41
 
42
+ function getNativeBinaryName(platform = process.platform) {
43
+ return platform === 'win32' ? 'gdx.exe' : 'gdx';
44
+ }
45
+
46
+ function getNativeBuildArgs(finalPath) {
47
+ return [
48
+ 'build',
49
+ PKG_SRC_PATH,
50
+ `--outfile=${finalPath}`,
51
+ '--compile',
52
+ '--bytecode',
53
+ '--production',
54
+ '--keep-names',
55
+ ];
56
+ }
57
+
58
+ function createInstallInfo(mode, finalPath, useNativeShim) {
59
+ return {
60
+ mode,
61
+ platform: process.platform,
62
+ arch: process.arch,
63
+ version: getPackageVersion(),
64
+ userAgent: process.env.npm_config_user_agent || null,
65
+ useNativeShim,
66
+ ts: (new Date).toLocaleString(),
67
+ binaryPath: finalPath
68
+ };
69
+ }
70
+
41
71
  function isTruthy(v) {
42
72
  return v === '1' || v === 'true' || v === 'yes';
43
73
  }
@@ -51,11 +81,10 @@ function getPrefixFromEnvOrNpm() {
51
81
  const npmExecPath = process.env.npm_execpath;
52
82
  if (!npmExecPath) return null;
53
83
 
54
- const prefix = execFileSync(
55
- `"${npmExecPath}"`,
56
- ['config', 'get', 'prefix'],
57
- { encoding: 'utf8', shell: true }
58
- ).trim();
84
+ const prefix = execFileSync(npmExecPath, ['config', 'get', 'prefix'], {
85
+ encoding: 'utf8',
86
+ shell: false,
87
+ }).trim();
59
88
 
60
89
  return prefix || null;
61
90
  }
@@ -66,7 +95,10 @@ async function checkUrlExists(url) {
66
95
  const res = await fetch(url, { method: 'HEAD', redirect: 'follow' });
67
96
  return res.ok;
68
97
  } catch (err) {
69
- throw new Error(`Network error while checking prebuilt availability: ${err.message} (${url})`);
98
+ throw new Error(
99
+ `Network error while checking prebuilt availability: ${err.message} (${url})`,
100
+ { cause: err }
101
+ );
70
102
  }
71
103
  }
72
104
 
@@ -76,13 +108,38 @@ async function downloadFile(url, tmpPath, destPath) {
76
108
  throw new Error(`Failed to download: ${res.statusText} (${url})`);
77
109
  }
78
110
 
79
- const fileStream = fs.createWriteStream(destPath);
111
+ const fileStream = fs.createWriteStream(tmpPath);
80
112
  const stream = require('stream');
81
113
  const { promisify } = require('util');
82
114
  const pipeline = promisify(stream.pipeline);
83
115
 
84
116
  await pipeline(res.body, fileStream);
85
- fs.renameSync(tmpPath, destPath);
117
+ if (tmpPath !== destPath) {
118
+ fs.renameSync(tmpPath, destPath);
119
+ }
120
+ }
121
+
122
+ function readSha256FromText(text, assetName) {
123
+ const trimmed = text.trim();
124
+ const match = trimmed.match(/\b([a-fA-F0-9]{64})\b/);
125
+ if (!match) {
126
+ throw new Error(`Invalid SHA256 checksum for ${assetName}.`);
127
+ }
128
+ return match[1].toLowerCase();
129
+ }
130
+
131
+ function sha256File(filePath) {
132
+ return crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');
133
+ }
134
+
135
+ function verifySha256(filePath, checksumText, assetName) {
136
+ const expected = readSha256FromText(checksumText, assetName);
137
+ const actual = sha256File(filePath);
138
+ if (actual !== expected) {
139
+ throw new Error(
140
+ `Checksum mismatch for ${assetName}. Expected ${expected}, got ${actual}.`
141
+ );
142
+ }
86
143
  }
87
144
 
88
145
  function setExecutable(filePath) {
@@ -102,25 +159,25 @@ function writeFileExecutable(filePath, content) {
102
159
  setExecutable(filePath);
103
160
  }
104
161
 
105
- function buildNodeShimContents(nodeAbsPath, launcherAbsPath) {
162
+ function buildRuntimeShimContents(runtimeAbsPath, launcherAbsPath) {
106
163
  return {
107
164
  cmd: [
108
165
  '@echo off',
109
- 'set "GDX_NODE_SHIM=1"',
110
- `"${nodeAbsPath}" "${launcherAbsPath}" -- %*`,
166
+ 'set "GDX_RUNTIME_SHIM=1"',
167
+ `"${runtimeAbsPath}" "${launcherAbsPath}" -- %*`,
111
168
  'exit /b %ERRORLEVEL%',
112
169
  ''
113
170
  ].join('\r\n'),
114
171
  ps1: [
115
- '$env:GDX_NODE_SHIM = "1"',
116
- `& "${nodeAbsPath}" "${launcherAbsPath}" -- @args`,
172
+ '$env:GDX_RUNTIME_SHIM = "1"',
173
+ `& "${runtimeAbsPath}" "${launcherAbsPath}" -- @args`,
117
174
  'exit $LASTEXITCODE',
118
175
  ''
119
176
  ].join('\r\n'),
120
177
  sh: [
121
178
  '#!/usr/bin/env sh',
122
- 'export GDX_NODE_SHIM=1',
123
- `exec "${nodeAbsPath}" "${launcherAbsPath}" -- "$@"`,
179
+ 'export GDX_RUNTIME_SHIM=1',
180
+ `exec "${runtimeAbsPath}" "${launcherAbsPath}" -- "$@"`,
124
181
  ''
125
182
  ].join('\n'),
126
183
  };
@@ -130,11 +187,10 @@ function getLocalNodeModulesBinDir() {
130
187
  return path.resolve(PACKAGE_DIR, '..', '.bin');
131
188
  }
132
189
 
133
- function overwriteNodeShim(binDir, launcherAbsPath) {
190
+ function overwriteRuntimeShim(binDir, launcherAbsPath, runtime) {
134
191
  if (!binDir || !fs.existsSync(binDir)) return false;
135
192
 
136
- const nodeAbsPath = process.execPath;
137
- const shims = buildNodeShimContents(nodeAbsPath, launcherAbsPath);
193
+ const shims = buildRuntimeShimContents(runtime.executable, launcherAbsPath);
138
194
 
139
195
  if (process.platform === 'win32') {
140
196
  writeFileExecutable(path.join(binDir, 'gdx.cmd'), shims.cmd);
@@ -168,7 +224,7 @@ function overwriteGlobalShim(nativeAbsPath) {
168
224
 
169
225
  if (!(process.env.npm_config_user_agent || '').includes('npm/')) {
170
226
  log('Non-npm global install detected; skipping global shim overwrite.');
171
- log('This may result in overhead introduced by the Node.js launch script.');
227
+ log('This may result in overhead introduced by the JavaScript launch script.');
172
228
  return false;
173
229
  }
174
230
 
@@ -208,14 +264,46 @@ function overwriteGlobalShim(nativeAbsPath) {
208
264
  return true;
209
265
  }
210
266
 
211
- function installNodeFallbackShims() {
267
+ function findRuntimeExecutable(name, platform = process.platform) {
268
+ const locator = platform === 'win32' ? 'where.exe' : 'which';
269
+ const located = spawnSync(locator, [name], {
270
+ encoding: 'utf8',
271
+ windowsHide: true,
272
+ });
273
+ if (located.error || located.status !== 0) return null;
274
+
275
+ for (const executable of String(located.stdout || '').split(/\r?\n/).filter(Boolean)) {
276
+ const needsShell = platform === 'win32' && /\.(?:cmd|bat)$/i.test(executable);
277
+ const probe = spawnSync(
278
+ executable,
279
+ ['-e', "require('path');process.stdout.write('gdx-runtime')"],
280
+ {
281
+ encoding: 'utf8',
282
+ shell: needsShell,
283
+ windowsHide: true,
284
+ }
285
+ );
286
+ if (!probe.error && probe.status === 0 && probe.stdout === 'gdx-runtime') return executable;
287
+ }
288
+ return null;
289
+ }
290
+
291
+ function selectFallbackRuntime(findExecutable = findRuntimeExecutable) {
292
+ for (const name of ['bun']) { // FIXME: waiting to runtimes with latency lower than Bun's
293
+ const executable = findExecutable(name);
294
+ if (executable) return { name, executable };
295
+ }
296
+ return { name: 'node', executable: process.execPath };
297
+ }
298
+
299
+ function installRuntimeFallbackShims(runtime) {
212
300
  const launcherAbsPath = path.join(__dirname, 'launcher.cjs');
213
301
  const localBin = getLocalNodeModulesBinDir();
214
302
  const globalBin = getNpmGlobalBinDir();
215
303
 
216
304
  return {
217
- local: overwriteNodeShim(localBin, launcherAbsPath),
218
- global: overwriteNodeShim(globalBin, launcherAbsPath),
305
+ local: overwriteRuntimeShim(localBin, launcherAbsPath, runtime),
306
+ global: overwriteRuntimeShim(globalBin, launcherAbsPath, runtime),
219
307
  };
220
308
  }
221
309
 
@@ -232,6 +320,7 @@ async function tryDownloadPrebuilt() {
232
320
  const ext = platform === 'win32' ? '.exe' : '';
233
321
  const assetName = `gdx-${platform}-${arch}${ext}`;
234
322
  const url = `${PREBUILT_BASE_URL}/v${version}/${assetName}`;
323
+ const checksumUrl = `${url}.sha256`;
235
324
 
236
325
  log(`Checking availability of prebuilt binary: ${url}`);
237
326
  const exists = await checkUrlExists(url);
@@ -241,11 +330,21 @@ async function tryDownloadPrebuilt() {
241
330
 
242
331
  log(`Downloading prebuilt binary...`);
243
332
  const tmpPath = path.join(NATIVE_DIR, `${assetName}.tmp`);
333
+ const checksumTmpPath = path.join(NATIVE_DIR, `${assetName}.sha256.tmp`);
334
+ const checksumPath = path.join(NATIVE_DIR, `${assetName}.sha256`);
244
335
  const finalPath = path.join(NATIVE_DIR, 'gdx' + ext);
245
336
 
246
337
  ensureBinDir();
247
- await downloadFile(url, tmpPath, finalPath);
248
- setExecutable(finalPath);
338
+ try {
339
+ await downloadFile(url, tmpPath, tmpPath);
340
+ await downloadFile(checksumUrl, checksumTmpPath, checksumPath);
341
+ verifySha256(tmpPath, fs.readFileSync(checksumPath, 'utf8'), assetName);
342
+ fs.renameSync(tmpPath, finalPath);
343
+ setExecutable(finalPath);
344
+ } finally {
345
+ fs.rmSync(tmpPath, { force: true });
346
+ fs.rmSync(checksumTmpPath, { force: true });
347
+ }
249
348
 
250
349
  log(`Prebuilt binary installed to ${finalPath}`);
251
350
 
@@ -271,21 +370,11 @@ function tryBuildNative() {
271
370
  }
272
371
 
273
372
  const platform = process.platform;
274
- const arch = process.arch;
275
- const isWin = platform === 'win32';
276
- const binaryName = isWin ? 'gdx.exe' : 'gdx';
373
+ const binaryName = getNativeBinaryName(platform);
277
374
  const finalPath = path.join(NATIVE_DIR, binaryName);
278
375
 
279
376
  // Build command
280
- const args = [
281
- 'build',
282
- PKG_SRC_PATH,
283
- `--outfile=${finalPath}`,
284
- '--compile',
285
- '--bytecode',
286
- '--production',
287
- '--keep-names',
288
- ];
377
+ const args = getNativeBuildArgs(finalPath);
289
378
 
290
379
  ensureBinDir();
291
380
  log(`Running: bun ${args.join(' ')}`);
@@ -297,16 +386,7 @@ function tryBuildNative() {
297
386
 
298
387
  log(`Native binary built at ${finalPath}`);
299
388
 
300
- writeInstallInfo({
301
- mode: 'built',
302
- platform,
303
- arch,
304
- version: getPackageVersion(),
305
- userAgent: process.env.npm_config_user_agent || null,
306
- useNativeShim: overwriteGlobalShim(finalPath),
307
- ts: (new Date).toLocaleString(),
308
- binaryPath: finalPath
309
- });
389
+ writeInstallInfo(createInstallInfo('built', finalPath, overwriteGlobalShim(finalPath)));
310
390
  }
311
391
 
312
392
  async function main() {
@@ -323,10 +403,13 @@ async function main() {
323
403
  } else if (isTruthy(process.env.GDX_BUILD_NATIVE)) {
324
404
  tryBuildNative();
325
405
  } else {
326
- log('No native install requested (default). Using Node.js fallback.');
327
- const shimInstall = installNodeFallbackShims();
406
+ const runtime = selectFallbackRuntime();
407
+ log(`No native install requested (default). Using ${runtime.name} fallback.`);
408
+ const shimInstall = installRuntimeFallbackShims(runtime);
328
409
  writeInstallInfo({
329
- mode: 'node',
410
+ mode: 'runtime',
411
+ runtime: runtime.name,
412
+ runtimePath: runtime.executable,
330
413
  platform: process.platform,
331
414
  arch: process.arch,
332
415
  version: getPackageVersion(),
@@ -348,8 +431,16 @@ if (require.main === module) {
348
431
  }
349
432
 
350
433
  module.exports = {
351
- buildNodeShimContents,
434
+ buildRuntimeShimContents,
435
+ createInstallInfo,
436
+ downloadFile,
437
+ getNativeBinaryName,
438
+ getNativeBuildArgs,
352
439
  getLocalNodeModulesBinDir,
353
- installNodeFallbackShims,
354
- overwriteNodeShim,
440
+ findRuntimeExecutable,
441
+ installRuntimeFallbackShims,
442
+ overwriteRuntimeShim,
443
+ readSha256FromText,
444
+ selectFallbackRuntime,
445
+ verifySha256,
355
446
  };