mmt-testlight 1.42.1-pre → 1.42.3-pre

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
@@ -1,6 +1,8 @@
1
1
  # Multimeter CLI (mmtcli)
2
2
 
3
- CLI runner for Multimeter test files. The npm package is `mmt-testlight`; the command you run is `testlight`.
3
+ **testlight** runs Multimeter YAML `.mmt` tests in CI and locally. npm package: `mmt-testlight`.
4
+
5
+ Multimeter is an AI-powered REST Client and API testing tool for VS Code and CI. It is not an electrical multimeter. Git-native alternative to Postman. Docs: https://mmt.dev
4
6
 
5
7
  ## Usage
6
8
 
@@ -22,6 +24,8 @@ npm run build
22
24
 
23
25
  ## Bundle standalone binaries (pkg)
24
26
 
27
+ Uses `@yao-pkg/pkg` in SEA mode (Node 22). Requires Node ≥ 22 on the machine that builds binaries.
28
+
25
29
  ```
26
30
  npm run pkg
27
31
  ```
package/dist/cli.js CHANGED
@@ -25513,6 +25513,7 @@ function resolvePassphrase(passphrasePlain, passphraseEnv, envVars, processEnv)
25513
25513
  var DEFAULT_NETWORK_CONFIG;
25514
25514
  var init_NetworkData = __esm({
25515
25515
  "../core/src/NetworkData.ts"() {
25516
+ "use strict";
25516
25517
  DEFAULT_NETWORK_CONFIG = {
25517
25518
  ca: { enabled: false },
25518
25519
  clients: [],
@@ -36327,118 +36328,138 @@ var require_browser = __commonJS({
36327
36328
  }
36328
36329
  });
36329
36330
 
36330
- // node_modules/has-flag/index.js
36331
- var require_has_flag = __commonJS({
36332
- "node_modules/has-flag/index.js"(exports2, module2) {
36333
- "use strict";
36334
- module2.exports = (flag, argv = process.argv) => {
36335
- const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
36336
- const position = argv.indexOf(prefix + flag);
36337
- const terminatorPosition = argv.indexOf("--");
36338
- return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
36339
- };
36340
- }
36331
+ // ../node_modules/supports-color/index.js
36332
+ var supports_color_exports = {};
36333
+ __export(supports_color_exports, {
36334
+ createSupportsColor: () => createSupportsColor,
36335
+ default: () => supports_color_default
36341
36336
  });
36342
-
36343
- // node_modules/supports-color/index.js
36344
- var require_supports_color = __commonJS({
36345
- "node_modules/supports-color/index.js"(exports2, module2) {
36346
- "use strict";
36347
- var os3 = require("os");
36348
- var tty2 = require("tty");
36349
- var hasFlag2 = require_has_flag();
36350
- var { env: env2 } = process;
36351
- var forceColor;
36352
- if (hasFlag2("no-color") || hasFlag2("no-colors") || hasFlag2("color=false") || hasFlag2("color=never")) {
36353
- forceColor = 0;
36354
- } else if (hasFlag2("color") || hasFlag2("colors") || hasFlag2("color=true") || hasFlag2("color=always")) {
36355
- forceColor = 1;
36356
- }
36357
- if ("FORCE_COLOR" in env2) {
36358
- if (env2.FORCE_COLOR === "true") {
36359
- forceColor = 1;
36360
- } else if (env2.FORCE_COLOR === "false") {
36361
- forceColor = 0;
36362
- } else {
36363
- forceColor = env2.FORCE_COLOR.length === 0 ? 1 : Math.min(parseInt(env2.FORCE_COLOR, 10), 3);
36364
- }
36337
+ function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : import_node_process.default.argv) {
36338
+ const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
36339
+ const position = argv.indexOf(prefix + flag);
36340
+ const terminatorPosition = argv.indexOf("--");
36341
+ return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
36342
+ }
36343
+ function envForceColor() {
36344
+ if ("FORCE_COLOR" in env) {
36345
+ if (env.FORCE_COLOR === "true") {
36346
+ return 1;
36365
36347
  }
36366
- function translateLevel2(level) {
36367
- if (level === 0) {
36368
- return false;
36369
- }
36370
- return {
36371
- level,
36372
- hasBasic: true,
36373
- has256: level >= 2,
36374
- has16m: level >= 3
36375
- };
36348
+ if (env.FORCE_COLOR === "false") {
36349
+ return 0;
36376
36350
  }
36377
- function supportsColor2(haveStream, streamIsTTY) {
36378
- if (forceColor === 0) {
36379
- return 0;
36380
- }
36381
- if (hasFlag2("color=16m") || hasFlag2("color=full") || hasFlag2("color=truecolor")) {
36382
- return 3;
36383
- }
36384
- if (hasFlag2("color=256")) {
36385
- return 2;
36386
- }
36387
- if (haveStream && !streamIsTTY && forceColor === void 0) {
36388
- return 0;
36389
- }
36390
- const min = forceColor || 0;
36391
- if (env2.TERM === "dumb") {
36392
- return min;
36393
- }
36394
- if (process.platform === "win32") {
36395
- const osRelease = os3.release().split(".");
36396
- if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
36397
- return Number(osRelease[2]) >= 14931 ? 3 : 2;
36398
- }
36399
- return 1;
36400
- }
36401
- if ("CI" in env2) {
36402
- if (["TRAVIS", "CIRCLECI", "APPVEYOR", "GITLAB_CI", "GITHUB_ACTIONS", "BUILDKITE"].some((sign) => sign in env2) || env2.CI_NAME === "codeship") {
36403
- return 1;
36404
- }
36405
- return min;
36406
- }
36407
- if ("TEAMCITY_VERSION" in env2) {
36408
- return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env2.TEAMCITY_VERSION) ? 1 : 0;
36409
- }
36410
- if (env2.COLORTERM === "truecolor") {
36411
- return 3;
36412
- }
36413
- if ("TERM_PROGRAM" in env2) {
36414
- const version = parseInt((env2.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
36415
- switch (env2.TERM_PROGRAM) {
36416
- case "iTerm.app":
36417
- return version >= 3 ? 3 : 2;
36418
- case "Apple_Terminal":
36419
- return 2;
36420
- }
36351
+ return env.FORCE_COLOR.length === 0 ? 1 : Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3);
36352
+ }
36353
+ }
36354
+ function translateLevel(level) {
36355
+ if (level === 0) {
36356
+ return false;
36357
+ }
36358
+ return {
36359
+ level,
36360
+ hasBasic: true,
36361
+ has256: level >= 2,
36362
+ has16m: level >= 3
36363
+ };
36364
+ }
36365
+ function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
36366
+ const noFlagForceColor = envForceColor();
36367
+ if (noFlagForceColor !== void 0) {
36368
+ flagForceColor = noFlagForceColor;
36369
+ }
36370
+ const forceColor = sniffFlags ? flagForceColor : noFlagForceColor;
36371
+ if (forceColor === 0) {
36372
+ return 0;
36373
+ }
36374
+ if (sniffFlags) {
36375
+ if (hasFlag("color=16m") || hasFlag("color=full") || hasFlag("color=truecolor")) {
36376
+ return 3;
36377
+ }
36378
+ if (hasFlag("color=256")) {
36379
+ return 2;
36380
+ }
36381
+ }
36382
+ if ("TF_BUILD" in env && "AGENT_NAME" in env) {
36383
+ return 1;
36384
+ }
36385
+ if (haveStream && !streamIsTTY && forceColor === void 0) {
36386
+ return 0;
36387
+ }
36388
+ const min = forceColor || 0;
36389
+ if (env.TERM === "dumb") {
36390
+ return min;
36391
+ }
36392
+ if (import_node_process.default.platform === "win32") {
36393
+ const osRelease = import_node_os.default.release().split(".");
36394
+ if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
36395
+ return Number(osRelease[2]) >= 14931 ? 3 : 2;
36396
+ }
36397
+ return 1;
36398
+ }
36399
+ if ("CI" in env) {
36400
+ if ("GITHUB_ACTIONS" in env || "GITEA_ACTIONS" in env) {
36401
+ return 3;
36402
+ }
36403
+ if (["TRAVIS", "CIRCLECI", "APPVEYOR", "GITLAB_CI", "BUILDKITE", "DRONE"].some((sign) => sign in env) || env.CI_NAME === "codeship") {
36404
+ return 1;
36405
+ }
36406
+ return min;
36407
+ }
36408
+ if ("TEAMCITY_VERSION" in env) {
36409
+ return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0;
36410
+ }
36411
+ if (env.COLORTERM === "truecolor") {
36412
+ return 3;
36413
+ }
36414
+ if (env.TERM === "xterm-kitty") {
36415
+ return 3;
36416
+ }
36417
+ if ("TERM_PROGRAM" in env) {
36418
+ const version = Number.parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
36419
+ switch (env.TERM_PROGRAM) {
36420
+ case "iTerm.app": {
36421
+ return version >= 3 ? 3 : 2;
36421
36422
  }
36422
- if (/-256(color)?$/i.test(env2.TERM)) {
36423
+ case "Apple_Terminal": {
36423
36424
  return 2;
36424
36425
  }
36425
- if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env2.TERM)) {
36426
- return 1;
36427
- }
36428
- if ("COLORTERM" in env2) {
36429
- return 1;
36430
- }
36431
- return min;
36432
36426
  }
36433
- function getSupportLevel(stream4) {
36434
- const level = supportsColor2(stream4, stream4 && stream4.isTTY);
36435
- return translateLevel2(level);
36427
+ }
36428
+ if (/-256(color)?$/i.test(env.TERM)) {
36429
+ return 2;
36430
+ }
36431
+ if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) {
36432
+ return 1;
36433
+ }
36434
+ if ("COLORTERM" in env) {
36435
+ return 1;
36436
+ }
36437
+ return min;
36438
+ }
36439
+ function createSupportsColor(stream4, options = {}) {
36440
+ const level = _supportsColor(stream4, {
36441
+ streamIsTTY: stream4 && stream4.isTTY,
36442
+ ...options
36443
+ });
36444
+ return translateLevel(level);
36445
+ }
36446
+ var import_node_process, import_node_os, import_node_tty, env, flagForceColor, supportsColor, supports_color_default;
36447
+ var init_supports_color = __esm({
36448
+ "../node_modules/supports-color/index.js"() {
36449
+ import_node_process = __toESM(require("node:process"), 1);
36450
+ import_node_os = __toESM(require("node:os"), 1);
36451
+ import_node_tty = __toESM(require("node:tty"), 1);
36452
+ ({ env } = import_node_process.default);
36453
+ if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) {
36454
+ flagForceColor = 0;
36455
+ } else if (hasFlag("color") || hasFlag("colors") || hasFlag("color=true") || hasFlag("color=always")) {
36456
+ flagForceColor = 1;
36436
36457
  }
36437
- module2.exports = {
36438
- supportsColor: getSupportLevel,
36439
- stdout: translateLevel2(supportsColor2(true, tty2.isatty(1))),
36440
- stderr: translateLevel2(supportsColor2(true, tty2.isatty(2)))
36458
+ supportsColor = {
36459
+ stdout: createSupportsColor({ isTTY: import_node_tty.default.isatty(1) }),
36460
+ stderr: createSupportsColor({ isTTY: import_node_tty.default.isatty(2) })
36441
36461
  };
36462
+ supports_color_default = supportsColor;
36442
36463
  }
36443
36464
  });
36444
36465
 
@@ -36460,7 +36481,7 @@ var require_node = __commonJS({
36460
36481
  );
36461
36482
  exports2.colors = [6, 2, 3, 4, 5, 1];
36462
36483
  try {
36463
- const supportsColor2 = require_supports_color();
36484
+ const supportsColor2 = (init_supports_color(), __toCommonJS(supports_color_exports));
36464
36485
  if (supportsColor2 && (supportsColor2.stderr || supportsColor2).level >= 2) {
36465
36486
  exports2.colors = [
36466
36487
  20,
@@ -51750,141 +51771,6 @@ var require_browser2 = __commonJS({
51750
51771
  }
51751
51772
  });
51752
51773
 
51753
- // ../node_modules/supports-color/index.js
51754
- var supports_color_exports = {};
51755
- __export(supports_color_exports, {
51756
- createSupportsColor: () => createSupportsColor,
51757
- default: () => supports_color_default
51758
- });
51759
- function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : import_node_process.default.argv) {
51760
- const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
51761
- const position = argv.indexOf(prefix + flag);
51762
- const terminatorPosition = argv.indexOf("--");
51763
- return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
51764
- }
51765
- function envForceColor() {
51766
- if ("FORCE_COLOR" in env) {
51767
- if (env.FORCE_COLOR === "true") {
51768
- return 1;
51769
- }
51770
- if (env.FORCE_COLOR === "false") {
51771
- return 0;
51772
- }
51773
- return env.FORCE_COLOR.length === 0 ? 1 : Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3);
51774
- }
51775
- }
51776
- function translateLevel(level) {
51777
- if (level === 0) {
51778
- return false;
51779
- }
51780
- return {
51781
- level,
51782
- hasBasic: true,
51783
- has256: level >= 2,
51784
- has16m: level >= 3
51785
- };
51786
- }
51787
- function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
51788
- const noFlagForceColor = envForceColor();
51789
- if (noFlagForceColor !== void 0) {
51790
- flagForceColor = noFlagForceColor;
51791
- }
51792
- const forceColor = sniffFlags ? flagForceColor : noFlagForceColor;
51793
- if (forceColor === 0) {
51794
- return 0;
51795
- }
51796
- if (sniffFlags) {
51797
- if (hasFlag("color=16m") || hasFlag("color=full") || hasFlag("color=truecolor")) {
51798
- return 3;
51799
- }
51800
- if (hasFlag("color=256")) {
51801
- return 2;
51802
- }
51803
- }
51804
- if ("TF_BUILD" in env && "AGENT_NAME" in env) {
51805
- return 1;
51806
- }
51807
- if (haveStream && !streamIsTTY && forceColor === void 0) {
51808
- return 0;
51809
- }
51810
- const min = forceColor || 0;
51811
- if (env.TERM === "dumb") {
51812
- return min;
51813
- }
51814
- if (import_node_process.default.platform === "win32") {
51815
- const osRelease = import_node_os.default.release().split(".");
51816
- if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
51817
- return Number(osRelease[2]) >= 14931 ? 3 : 2;
51818
- }
51819
- return 1;
51820
- }
51821
- if ("CI" in env) {
51822
- if ("GITHUB_ACTIONS" in env || "GITEA_ACTIONS" in env) {
51823
- return 3;
51824
- }
51825
- if (["TRAVIS", "CIRCLECI", "APPVEYOR", "GITLAB_CI", "BUILDKITE", "DRONE"].some((sign) => sign in env) || env.CI_NAME === "codeship") {
51826
- return 1;
51827
- }
51828
- return min;
51829
- }
51830
- if ("TEAMCITY_VERSION" in env) {
51831
- return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0;
51832
- }
51833
- if (env.COLORTERM === "truecolor") {
51834
- return 3;
51835
- }
51836
- if (env.TERM === "xterm-kitty") {
51837
- return 3;
51838
- }
51839
- if ("TERM_PROGRAM" in env) {
51840
- const version = Number.parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
51841
- switch (env.TERM_PROGRAM) {
51842
- case "iTerm.app": {
51843
- return version >= 3 ? 3 : 2;
51844
- }
51845
- case "Apple_Terminal": {
51846
- return 2;
51847
- }
51848
- }
51849
- }
51850
- if (/-256(color)?$/i.test(env.TERM)) {
51851
- return 2;
51852
- }
51853
- if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) {
51854
- return 1;
51855
- }
51856
- if ("COLORTERM" in env) {
51857
- return 1;
51858
- }
51859
- return min;
51860
- }
51861
- function createSupportsColor(stream4, options = {}) {
51862
- const level = _supportsColor(stream4, {
51863
- streamIsTTY: stream4 && stream4.isTTY,
51864
- ...options
51865
- });
51866
- return translateLevel(level);
51867
- }
51868
- var import_node_process, import_node_os, import_node_tty, env, flagForceColor, supportsColor, supports_color_default;
51869
- var init_supports_color = __esm({
51870
- "../node_modules/supports-color/index.js"() {
51871
- import_node_process = __toESM(require("node:process"), 1);
51872
- import_node_os = __toESM(require("node:os"), 1);
51873
- import_node_tty = __toESM(require("node:tty"), 1);
51874
- ({ env } = import_node_process.default);
51875
- if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) {
51876
- flagForceColor = 0;
51877
- } else if (hasFlag("color") || hasFlag("colors") || hasFlag("color=true") || hasFlag("color=always")) {
51878
- flagForceColor = 1;
51879
- }
51880
- supportsColor = {
51881
- stdout: createSupportsColor({ isTTY: import_node_tty.default.isatty(1) }),
51882
- stderr: createSupportsColor({ isTTY: import_node_tty.default.isatty(2) })
51883
- };
51884
- supports_color_default = supportsColor;
51885
- }
51886
- });
51887
-
51888
51774
  // ../node_modules/debug/src/node.js
51889
51775
  var require_node2 = __commonJS({
51890
51776
  "../node_modules/debug/src/node.js"(exports2, module2) {
@@ -84329,10 +84215,18 @@ function detectPlatform(unameOs = process.platform, unameArch = process.arch) {
84329
84215
  }
84330
84216
  return `${osName}-${arch}`;
84331
84217
  }
84218
+ function isSeaBinary() {
84219
+ try {
84220
+ const sea = require("node:sea");
84221
+ return typeof sea.isSea === "function" && sea.isSea();
84222
+ } catch {
84223
+ return false;
84224
+ }
84225
+ }
84332
84226
  function detectInstallChannel(execPath, scriptPath) {
84333
84227
  const exec = execPath.replace(/\\/g, "/");
84334
84228
  const script = (scriptPath || "").replace(/\\/g, "/");
84335
- if (process.pkg) {
84229
+ if (process.pkg || isSeaBinary()) {
84336
84230
  return "standalone";
84337
84231
  }
84338
84232
  if (/\/Cellar\/mmt-testlight\//i.test(exec) || /\/homebrew\/.*(mmt-testlight|testlight)/i.test(exec)) {
@@ -0,0 +1,39 @@
1
+ import * as esbuild from 'esbuild';
2
+ import path from 'path';
3
+ import {fileURLToPath} from 'url';
4
+
5
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
+ const coreDir = path.resolve(__dirname, '..', 'core', 'src');
7
+
8
+ // Same resolver as esbuild.mjs so the pkg bundle inlines core from source.
9
+ const mmtCorePlugin = {
10
+ name: 'mmt-core-resolver',
11
+ setup(build) {
12
+ build.onResolve({filter: /^mmt-core$/}, () => ({
13
+ path: path.join(coreDir, 'index.ts'),
14
+ }));
15
+ build.onResolve({filter: /^mmt-core\//}, (args) => {
16
+ const sub = args.path.replace(/^mmt-core\//, '');
17
+ return {path: path.join(coreDir, sub + '.ts')};
18
+ });
19
+ },
20
+ };
21
+
22
+ await esbuild.build({
23
+ entryPoints: ['src/pkg-entry.cjs'],
24
+ bundle: true,
25
+ platform: 'node',
26
+ target: 'node22',
27
+ format: 'cjs',
28
+ outfile: 'dist-cjs/pkg-bundle.cjs',
29
+ plugins: [mmtCorePlugin],
30
+ resolveExtensions: ['.ts', '.js', '.cjs', '.mjs', '.json'],
31
+ // Native addons + reflection protos stay on disk for pkg to embed.
32
+ external: [
33
+ '@grpc/grpc-js',
34
+ '@grpc/proto-loader',
35
+ ],
36
+ logOverride: {
37
+ 'unsupported-dynamic-import': 'silent',
38
+ },
39
+ });
package/package.json CHANGED
@@ -1,9 +1,29 @@
1
1
  {
2
2
  "name": "mmt-testlight",
3
- "version": "1.42.1-pre",
4
- "description": "Multimeter CLI test runner",
3
+ "version": "1.42.3-pre",
4
+ "description": "AI-powered REST Client and Git-native API testing CLI for YAML .mmt files. Same tests in VS Code and CI. Alternative to Postman.",
5
5
  "license": "Apache-2.0",
6
6
  "private": false,
7
+ "homepage": "https://mmt.dev/docs/features/testlight/install",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/mshobeyri/multimeter.git",
11
+ "directory": "mmtcli"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/mshobeyri/multimeter/issues"
15
+ },
16
+ "keywords": [
17
+ "api-testing",
18
+ "rest-client",
19
+ "cli",
20
+ "ci",
21
+ "yaml",
22
+ "postman-alternative",
23
+ "testlight",
24
+ "multimeter",
25
+ "mcp"
26
+ ],
7
27
  "bin": {
8
28
  "testlight": "dist/cli.js",
9
29
  "mmt": "dist/cli.js"
@@ -13,25 +33,20 @@
13
33
  "build": "node esbuild.mjs",
14
34
  "build:tsc": "tsc -p tsconfig.json",
15
35
  "build:cjs": "tsc -p tsconfig.cjs.json",
36
+ "build:pkg-bundle": "node esbuild.pkg.mjs",
16
37
  "dev": "tsx src/cli.ts --help",
17
38
  "clean": "rimraf dist",
18
- "pkg": "rimraf dist-cjs && npm run build:cjs && bash ../scripts/build-binaries.sh",
39
+ "pkg": "rimraf dist-cjs && npm run build:pkg-bundle && bash ../scripts/build-binaries.sh",
19
40
  "test": "echo \"(no tests yet)\""
20
41
  },
21
42
  "pkg": {
22
- "scripts": [
23
- "dist-cjs/**/*.js",
24
- "node_modules/mmt-core/dist/**/*.js",
25
- "../core/dist/**/*.js"
26
- ],
27
43
  "assets": [
28
- "node_modules/axios/dist/**",
29
- "node_modules/axios/lib/**",
30
- "node_modules/axios/dist/node/axios.cjs",
31
- "node_modules/ws/**/*.js",
32
- "node_modules/mmt-core/dist/**",
33
- "node_modules/mmt-core/package.json",
34
- "../core/dist/**"
44
+ "node_modules/@grpc/grpc-js/**",
45
+ "node_modules/@grpc/proto-loader/**",
46
+ "../node_modules/@grpc/grpc-js/**",
47
+ "../node_modules/@grpc/proto-loader/**",
48
+ "../core/node_modules/@grpc/grpc-js/**",
49
+ "../core/node_modules/@grpc/proto-loader/**"
35
50
  ]
36
51
  },
37
52
  "dependencies": {
@@ -44,9 +59,14 @@
44
59
  "node-forge": "^1.3.2"
45
60
  },
46
61
  "devDependencies": {
62
+ "@grpc/grpc-js": "^1.12.6",
63
+ "@grpc/proto-loader": "^0.7.13",
47
64
  "@types/js-yaml": "^4.0.9",
48
65
  "@types/node": "^20.11.30",
49
- "pkg": "^5.8.1",
66
+ "@yao-pkg/pkg": "^6.22.0",
67
+ "pe-library": "^2.0.1",
68
+ "png-to-ico": "^2.1.8",
69
+ "resedit": "^3.1.0",
50
70
  "rimraf": "^5.0.5",
51
71
  "tsx": "^4.7.0",
52
72
  "typescript": "^5.4.2"
package/src/pkg-entry.cjs CHANGED
@@ -547,6 +547,7 @@ function printHelp() {
547
547
  const commands = formatHelpRows([
548
548
  ['run [options] <file>', 'Run a .mmt file'],
549
549
  ['print-js [options] <file>', 'Print generated JS for a test'],
550
+ ['update [options]', 'Update this standalone binary from GitHub Releases'],
550
551
  ['debug-resolve', 'Print pkg module resolution info'],
551
552
  ]);
552
553
  const options = formatHelpRows([
@@ -569,6 +570,8 @@ function printHelp() {
569
570
  ['testlight run path/to/test.mmt'],
570
571
  ['testlight run path/to/test.mmt -e api_url=https://test.mmt.dev -q'],
571
572
  ['testlight print-js path/to/test.mmt'],
573
+ ['testlight update'],
574
+ ['testlight update --check'],
572
575
  ], ' ', 0);
573
576
  const out = [
574
577
  'Usage: testlight <command> [options]',
@@ -643,6 +646,35 @@ function printPrintJsHelp() {
643
646
  process.stdout.write(out + '\n');
644
647
  }
645
648
 
649
+ function printUpdateHelp() {
650
+ const options = formatHelpRows([
651
+ ['-h, --help', 'Show help'],
652
+ ['--check', 'Only check whether an update is available'],
653
+ ['--to <version>', 'Install a specific version (e.g. 1.38.1)'],
654
+ ['--channel <name>', 'Latest matching channel (beta, rc, prerelease)'],
655
+ ['--force', 'Reinstall even when already newest'],
656
+ ['--repo <owner/name>', 'GitHub repo (default: mshobeyri/multimeter)'],
657
+ ['--base-url <url>', 'Portal/mirror base URL'],
658
+ ]);
659
+ const examples = formatHelpRows([
660
+ ['testlight update'],
661
+ ['testlight update --check'],
662
+ ['testlight update --to 1.38.1'],
663
+ ], ' ', 0);
664
+ const out = [
665
+ 'Usage: testlight update [options]',
666
+ '',
667
+ 'Update this standalone binary from GitHub Releases (or a portal mirror)',
668
+ '',
669
+ 'Options:',
670
+ ...options,
671
+ '',
672
+ 'Examples:',
673
+ ...examples,
674
+ ].join('\n');
675
+ process.stdout.write(out + '\n');
676
+ }
677
+
646
678
  function coerceCliValue(v) {
647
679
  const t = String(v ?? '').trim();
648
680
  if (/^(true|false)$/i.test(t)) {
@@ -919,6 +951,53 @@ function parsePrintJsArgv(argv) {
919
951
  return { filePath, opts };
920
952
  }
921
953
 
954
+ function parseUpdateArgv(argv) {
955
+ const opts = {
956
+ check: false,
957
+ to: undefined,
958
+ channel: undefined,
959
+ force: false,
960
+ repo: undefined,
961
+ baseUrl: undefined,
962
+ help: false,
963
+ };
964
+ for (let i = 1; i < argv.length; i++) {
965
+ const a = argv[i];
966
+ if (!a) {
967
+ continue;
968
+ }
969
+ if (a === '-h' || a === '--help') {
970
+ opts.help = true;
971
+ continue;
972
+ }
973
+ if (a === '--check') {
974
+ opts.check = true;
975
+ continue;
976
+ }
977
+ if (a === '--force') {
978
+ opts.force = true;
979
+ continue;
980
+ }
981
+ if (a === '--to') {
982
+ opts.to = argv[++i];
983
+ continue;
984
+ }
985
+ if (a === '--channel') {
986
+ opts.channel = argv[++i];
987
+ continue;
988
+ }
989
+ if (a === '--repo') {
990
+ opts.repo = argv[++i];
991
+ continue;
992
+ }
993
+ if (a === '--base-url') {
994
+ opts.baseUrl = argv[++i];
995
+ continue;
996
+ }
997
+ }
998
+ return opts;
999
+ }
1000
+
922
1001
  function parseLogLevel(argv) {
923
1002
  for (let i = 1; i < argv.length; i++) {
924
1003
  const a = argv[i];
@@ -1023,6 +1102,39 @@ async function main() {
1023
1102
  process.stdout.write(msgs.join('\n') + '\n');
1024
1103
  return;
1025
1104
  }
1105
+ if (command === 'update') {
1106
+ const parsed = parseUpdateArgv(argv);
1107
+ if (parsed.help) {
1108
+ printUpdateHelp();
1109
+ return;
1110
+ }
1111
+ let currentVersion = '';
1112
+ try {
1113
+ currentVersion = String(require('../package.json').version || '');
1114
+ } catch {
1115
+ currentVersion = '';
1116
+ }
1117
+ try {
1118
+ const {runUpdate} = require('./selfUpdate');
1119
+ const result = await runUpdate({
1120
+ currentVersion,
1121
+ checkOnly: !!parsed.check,
1122
+ version: parsed.to,
1123
+ channel: parsed.channel,
1124
+ force: !!parsed.force,
1125
+ repo: parsed.repo,
1126
+ releaseBaseUrl: parsed.baseUrl,
1127
+ });
1128
+ process.stdout.write(String(result.message || '') + '\n');
1129
+ if (!result.ok) {
1130
+ process.exitCode = 2;
1131
+ }
1132
+ } catch (e) {
1133
+ process.stderr.write(`Error updating testlight: ${e && e.message ? e.message : e}\n`);
1134
+ process.exitCode = 2;
1135
+ }
1136
+ return;
1137
+ }
1026
1138
  if (command !== 'run' && command !== 'print-js') {
1027
1139
  process.stderr.write(`Unknown command: ${command}\n`);
1028
1140
  printHelp();
@@ -1,3 +1,7 @@
1
+ import fs from 'fs';
2
+ import os from 'os';
3
+ import path from 'path';
4
+ import {execFileSync} from 'child_process';
1
5
  import {
2
6
  buildDownloadUrl,
3
7
  compareVersions,
@@ -5,6 +9,7 @@ import {
5
9
  detectPlatform,
6
10
  normalizeVersion,
7
11
  planUpdate,
12
+ runUpdate,
8
13
  } from './selfUpdate';
9
14
 
10
15
  describe('selfUpdate helpers', () => {
@@ -31,6 +36,7 @@ describe('selfUpdate helpers', () => {
31
36
  '/usr/local/bin/node',
32
37
  '/usr/local/lib/node_modules/mmt-testlight/dist/cli.js'))
33
38
  .toBe('npm');
39
+ expect(detectInstallChannel('/Applications/testlight')).toBe('standalone');
34
40
  });
35
41
 
36
42
  it('builds github and portal download urls', () => {
@@ -76,4 +82,41 @@ describe('selfUpdate helpers', () => {
76
82
  expect(plan.action).toBe('download');
77
83
  expect(plan.downloadUrl).toContain('/v1.2.3/testlight-');
78
84
  });
85
+
86
+ it('replaces a standalone binary from a downloaded archive', async () => {
87
+ if (process.platform === 'win32') {
88
+ return;
89
+ }
90
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'testlight-update-test-'));
91
+ try {
92
+ const installDir = path.join(tmp, 'install');
93
+ const payloadDir = path.join(tmp, 'payload');
94
+ fs.mkdirSync(installDir);
95
+ fs.mkdirSync(payloadDir);
96
+ const current = path.join(installDir, 'testlight');
97
+ fs.writeFileSync(current, 'OLD');
98
+ fs.chmodSync(current, 0o755);
99
+ fs.writeFileSync(path.join(payloadDir, 'testlight'), 'NEW');
100
+ const archive = path.join(tmp, 'pkg.tar.gz');
101
+ execFileSync('tar', ['-czf', archive, '-C', payloadDir, 'testlight']);
102
+
103
+ const result = await runUpdate({
104
+ currentVersion: '1.0.0',
105
+ version: '1.2.3',
106
+ execPath: current,
107
+ fetchJson: async () => ({tag_name: 'v1.2.3'}),
108
+ downloadToFile: async (_url, dest) => {
109
+ fs.copyFileSync(archive, dest);
110
+ },
111
+ log: () => {},
112
+ });
113
+
114
+ expect(result.ok).toBe(true);
115
+ expect(result.updated).toBe(true);
116
+ expect(fs.readFileSync(current, 'utf8')).toBe('NEW');
117
+ expect(fs.lstatSync(path.join(installDir, 'mmt')).isSymbolicLink()).toBe(true);
118
+ } finally {
119
+ fs.rmSync(tmp, {recursive: true, force: true});
120
+ }
121
+ });
79
122
  });
package/src/selfUpdate.ts CHANGED
@@ -107,11 +107,21 @@ export function detectPlatform(
107
107
  return `${osName}-${arch}` as UpdatePlatform;
108
108
  }
109
109
 
110
+ function isSeaBinary(): boolean {
111
+ try {
112
+ // Node 20+ official SEA (used by @yao-pkg/pkg --sea). Not set for vercel/pkg.
113
+ const sea = require('node:sea') as {isSea?: () => boolean};
114
+ return typeof sea.isSea === 'function' && sea.isSea();
115
+ } catch {
116
+ return false;
117
+ }
118
+ }
119
+
110
120
  export function detectInstallChannel(execPath: string, scriptPath?: string):
111
121
  InstallChannel {
112
122
  const exec = execPath.replace(/\\/g, '/');
113
123
  const script = (scriptPath || '').replace(/\\/g, '/');
114
- if ((process as any).pkg) {
124
+ if ((process as any).pkg || isSeaBinary()) {
115
125
  return 'standalone';
116
126
  }
117
127
  if (/\/Cellar\/mmt-testlight\//i.test(exec) ||