mb-run 1.0.2-dev-20260929-2a0c292 → 1.0.2

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/CHANGELOG.md CHANGED
@@ -23,7 +23,7 @@ If you like this project and find it useful, please consider giving it a star on
23
23
 
24
24
  <a href="https://www.buymeacoffee.com/luligugithub"><img src="https://matterbridge.io/assets/bmc-button.svg" alt="Buy me a coffee" width="120"></a>
25
25
 
26
- ## [1.0.2] - Dev branch
26
+ ## [1.0.2] - 2026-09-29
27
27
 
28
28
  ### Added
29
29
 
@@ -49,7 +49,13 @@ If you like this project and find it useful, please consider giving it a star on
49
49
 
50
50
  - [antigravity]: Bump the vendored `.antigravity/settings.json` to v.1.0.5: count the removed sandboxing comments and allow the read-only git commands already approved in `.vscode/settings.json`.
51
51
  - [vscode]: Bump the vendored `.vscode/settings.json` to v.1.0.14: ask before dependency installs and removals and approve `npm run test:watch` and `npm run test:verbose`, in sync with `.antigravity/settings.json`.
52
- - [scripts]: Remove the obsolete `eslint-disable` comments from the vendored `scripts/*.mjs` and bump each touched script's patch version (scripts are ignored by oxlint).
52
+ - [scripts]: Remove the obsolete `eslint-disable` comments from the vendored `scripts/*.mjs` and bump each touched script's patch version.
53
+ - [scripts]: Make the vendored `scripts/*.mjs` pass oxlint: file-level `no-console` disables, behavior-preserving fixes, and local disables where a fix would change runtime behavior.
54
+ - [esbuild]: Make the vendored `scripts/esbuild.mjs` pass oxlint: file-level `no-console` disable, JSDoc descriptions and an optional chain.
55
+ - [oxlint]: Bump the vendored `.oxlintrc.root.json` to v.1.0.19: lint `scripts/` and `bin/`, and ignore `docs/**`, `**/tmp/**` and `**/bun.lock`.
56
+ - [oxfmt]: Bump the vendored `.oxfmtrc.root.json` to v.1.0.7: ignore `docs/**`, `**/tmp/**` and `**/bun.lock`, and drop the `scripts/*.html` and `eslint.config.js` ignores.
57
+ - [scripts]: Remove the unused vendored `scripts/run-automator.mjs`, which every upgrade deleted right after copying it.
58
+ - [upgrade]: Delete the obsolete `scripts/install-experimental.mjs` from upgraded repositories.
53
59
  - [version]: Breaking change: rename `--version [tag]` to `--set-version [tag]`, so `--version` no longer rewrites `package.json`. The old `mb-run --version <tag>` form exits with an error pointing to `--set-version`.
54
60
  - [package]: Upgrade package.
55
61
  - [package]: Bump `oxfmt` to v.0.71.0.
package/dist/upgrade.js CHANGED
@@ -197,6 +197,7 @@ export async function runPackageJsonUpgrade(opts, pkgPath, pkgJson, isMonorepo =
197
197
  unlinkSafe(path.join(dstDir, 'scripts', 'run-chip-tests.mjs'));
198
198
  unlinkSafe(path.join(dstDir, 'scripts', 'run-automator.mjs'));
199
199
  unlinkSafe(path.join(dstDir, 'scripts', 'runAutomator.mjs'));
200
+ unlinkSafe(path.join(dstDir, 'scripts', 'install-experimental.mjs'));
200
201
  unlinkSafe(path.join(dstDir, 'scripts', 'prune-tags.sh'));
201
202
  unlinkSafe(path.join(dstDir, 'scripts', 'git-status.sh'));
202
203
  unlinkSafe(path.join(dstDir, 'scripts', 'mb-run.mjs'));
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "mb-run",
3
- "version": "1.0.2-dev-20260929-2a0c292",
3
+ "version": "1.0.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "mb-run",
9
- "version": "1.0.2-dev-20260929-2a0c292",
9
+ "version": "1.0.2",
10
10
  "license": "Apache-2.0",
11
11
  "dependencies": {
12
12
  "cross-spawn": "7.0.6",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mb-run",
3
- "version": "1.0.2-dev-20260929-2a0c292",
3
+ "version": "1.0.2",
4
4
  "description": "Matterbridge command line executor",
5
5
  "author": "https://github.com/Luligu",
6
6
  "license": "Apache-2.0",
@@ -1,4 +1,4 @@
1
- // .oxfmtrc.json 1.0.6
1
+ // .oxfmtrc.json 1.0.7
2
2
 
3
3
  // This Oxfmt configuration (shared between all matterbridge packages) is designed for a TypeScript project using ESM modules.
4
4
  {
@@ -10,16 +10,16 @@
10
10
  "**/build/**",
11
11
  "chip/**",
12
12
  "**/coverage/**",
13
- "docs",
13
+ "docs/**",
14
14
  "**/dist/**",
15
15
  "**/temp/**",
16
+ "**/tmp/**",
16
17
  "**/node_modules/**",
17
18
  "**/out/**",
18
19
  "**/screenshots/**",
19
20
  "vendor/**",
20
21
  "**/package-lock.json",
21
- "scripts/*.html",
22
- "eslint.config.js"
22
+ "**/bun.lock"
23
23
  ],
24
24
  "printWidth": 180,
25
25
  "tabWidth": 2,
@@ -1,4 +1,4 @@
1
- // .oxlintrc.json 1.0.18
1
+ // .oxlintrc.json 1.0.19
2
2
 
3
3
  // This Oxlint configuration (shared between all matterbridge packages) is designed for a TypeScript project using ESM modules.
4
4
  {
@@ -10,16 +10,16 @@
10
10
  "**/build/**",
11
11
  "chip/**",
12
12
  "**/coverage/**",
13
+ "docs/**",
13
14
  "**/dist/**",
14
15
  "**/temp/**",
16
+ "**/tmp/**",
15
17
  "**/node_modules/**",
16
18
  "**/out/**",
17
- "**/scripts/**",
18
19
  "**/screenshots/**",
19
20
  "vendor/**",
20
21
  "**/package-lock.json",
21
- "**/bin/**",
22
- "**/src/mock/**"
22
+ "**/bun.lock"
23
23
  ],
24
24
  "env": {
25
25
  "builtin": true,
@@ -21,6 +21,8 @@
21
21
  * Unknown arguments are rejected with exit code 1, so a mistyped flag never starts a clean.
22
22
  */
23
23
 
24
+ /* oxlint-disable no-console */
25
+
24
26
  import { lstatSync, existsSync, readdirSync, readFileSync, rmSync } from 'node:fs';
25
27
  import path from 'node:path';
26
28
 
@@ -37,20 +39,17 @@ const handleArgs = () => {
37
39
  const knownArgs = new Set(['--workspaces', '--version', '-v', '--help', '-h']);
38
40
  const unknownArgs = args.filter((arg) => !knownArgs.has(arg));
39
41
  if (unknownArgs.length > 0) {
40
- // oxlint-disable-next-line no-console
41
42
  console.error(`Unknown argument${unknownArgs.length === 1 ? '' : 's'}: ${unknownArgs.join(', ')}. Run with --help for usage.`);
42
43
  process.exitCode = 1;
43
44
  return false;
44
45
  }
45
46
 
46
47
  if (args.includes('--version') || args.includes('-v')) {
47
- // oxlint-disable-next-line no-console
48
48
  console.log(version);
49
49
  return false;
50
50
  }
51
51
 
52
52
  if (args.includes('--help') || args.includes('-h')) {
53
- // oxlint-disable-next-line no-console
54
53
  console.log(`${scriptName} v.${version}
55
54
 
56
55
  Remove every *.tsbuildinfo file and the dist and build directories.
@@ -71,7 +70,6 @@ Options:
71
70
  const shouldClean = handleArgs();
72
71
 
73
72
  if (shouldClean) {
74
- // oxlint-disable-next-line no-console
75
73
  console.log(`${scriptName} v.${version}`);
76
74
  }
77
75
 
@@ -80,7 +78,7 @@ const root = process.cwd();
80
78
 
81
79
  // Colors follow the NO_COLOR convention and are left out when the output is redirected,
82
80
  // so a piped or captured log stays free of escape sequences.
83
- const useColor = process.env.NO_COLOR === undefined && (process.stdout.isTTY === true || process.env.FORCE_COLOR !== undefined);
81
+ const useColor = process.env.NO_COLOR === undefined && (process.stdout.isTTY || process.env.FORCE_COLOR !== undefined);
84
82
  const red = (text) => (useColor ? `\u001B[31m${text}\u001B[0m` : text);
85
83
 
86
84
  let removed = 0;
@@ -91,11 +89,9 @@ const logRemoved = (dir, entry) => {
91
89
  if (loggedDir !== dir) {
92
90
  loggedDir = dir;
93
91
  const name = path.relative(root, dir);
94
- // oxlint-disable-next-line no-console
95
92
  console.log(name === '' ? path.basename(dir) : name);
96
93
  }
97
94
  removed += 1;
98
- // oxlint-disable-next-line no-console
99
95
  console.log(` ${red('-')} ${entry}`);
100
96
  };
101
97
 
@@ -114,7 +110,6 @@ const rm = (dir, target) => {
114
110
  rmSync(path.resolve(dir, target), { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
115
111
  } catch (error) {
116
112
  if (error.code === 'EPERM' || error.code === 'EBUSY' || error.code === 'ENOTEMPTY') {
117
- // oxlint-disable-next-line no-console
118
113
  console.warn(`Skipped locked path (${error.code}): ${error.path ?? target} — likely held by a running process.`);
119
114
  return;
120
115
  }
@@ -177,6 +172,5 @@ if (shouldClean) {
177
172
  }
178
173
 
179
174
  const elapsed = `${Math.round(performance.now() - start)}ms`;
180
- // oxlint-disable-next-line no-console
181
175
  console.log(removed === 0 ? `Nothing to clean in ${elapsed}.` : `Cleaned ${removed} path${removed === 1 ? '' : 's'} in ${elapsed}.`);
182
176
  }
@@ -16,6 +16,8 @@
16
16
  * - Print tag/title/description and pause for user confirmation before creating
17
17
  */
18
18
 
19
+ /* oxlint-disable no-console */
20
+
19
21
  import { spawn } from 'node:child_process';
20
22
  import fs from 'node:fs/promises';
21
23
  import os from 'node:os';
@@ -90,6 +92,7 @@ async function promptToContinue({ tag, title, description }) {
90
92
  console.log(`Tag: ${tag}`);
91
93
  console.log(`Title: ${title}`);
92
94
  console.log('Description:');
95
+ // oxlint-disable-next-line typescript/prefer-nullish-coalescing
93
96
  console.log(description || '(empty)');
94
97
  console.log('---');
95
98
 
@@ -106,7 +109,7 @@ async function promptToContinue({ tag, title, description }) {
106
109
  }
107
110
  }
108
111
 
109
- function runGhReleaseCreate({ tag, title, notesFilePath }) {
112
+ async function runGhReleaseCreate({ tag, title, notesFilePath }) {
110
113
  return new Promise((resolve, reject) => {
111
114
  const args = ['release', 'create', tag, '--title', title, '--notes-file', notesFilePath, '--target', 'main'];
112
115
  const child = spawn('gh', args, {
@@ -157,6 +160,7 @@ async function main() {
157
160
  }
158
161
  }
159
162
 
163
+ // oxlint-disable-next-line typescript/use-unknown-in-catch-callback-variable
160
164
  main().catch((err) => {
161
165
  console.error(`create-release: ${err?.message ?? err}`);
162
166
  console.error('Make sure you are authenticated with GitHub CLI: gh auth status');
@@ -28,6 +28,8 @@
28
28
  * Unknown arguments are rejected with exit code 1, so a mistyped flag never starts a clean.
29
29
  */
30
30
 
31
+ /* oxlint-disable no-console */
32
+
31
33
  import { existsSync, lstatSync, readdirSync, readFileSync, rmSync } from 'node:fs';
32
34
  import path from 'node:path';
33
35
 
@@ -44,20 +46,17 @@ const handleArgs = () => {
44
46
  const knownArgs = new Set(['--workspaces', '--version', '-v', '--help', '-h']);
45
47
  const unknownArgs = args.filter((arg) => !knownArgs.has(arg));
46
48
  if (unknownArgs.length > 0) {
47
- // oxlint-disable-next-line no-console
48
49
  console.error(`Unknown argument${unknownArgs.length === 1 ? '' : 's'}: ${unknownArgs.join(', ')}. Run with --help for usage.`);
49
50
  process.exitCode = 1;
50
51
  return false;
51
52
  }
52
53
 
53
54
  if (args.includes('--version') || args.includes('-v')) {
54
- // oxlint-disable-next-line no-console
55
55
  console.log(version);
56
56
  return false;
57
57
  }
58
58
 
59
59
  if (args.includes('--help') || args.includes('-h')) {
60
- // oxlint-disable-next-line no-console
61
60
  console.log(`${scriptName} v.${version}
62
61
 
63
62
  Remove every *.tsbuildinfo file, the build/test output directories and the lock files,\nand empty .cache and node_modules.
@@ -78,7 +77,6 @@ Options:
78
77
  const shouldClean = handleArgs();
79
78
 
80
79
  if (shouldClean) {
81
- // oxlint-disable-next-line no-console
82
80
  console.log(`${scriptName} v.${version}`);
83
81
  }
84
82
 
@@ -87,7 +85,7 @@ const root = process.cwd();
87
85
 
88
86
  // Colors follow the NO_COLOR convention and are left out when the output is redirected,
89
87
  // so a piped or captured log stays free of escape sequences.
90
- const useColor = process.env.NO_COLOR === undefined && (process.stdout.isTTY === true || process.env.FORCE_COLOR !== undefined);
88
+ const useColor = process.env.NO_COLOR === undefined && (process.stdout.isTTY || process.env.FORCE_COLOR !== undefined);
91
89
  const red = (text) => (useColor ? `\u001B[31m${text}\u001B[0m` : text);
92
90
 
93
91
  let removed = 0;
@@ -98,11 +96,9 @@ const logRemoved = (dir, entry, count = 1) => {
98
96
  if (loggedDir !== dir) {
99
97
  loggedDir = dir;
100
98
  const name = path.relative(root, dir);
101
- // oxlint-disable-next-line no-console
102
99
  console.log(name === '' ? path.basename(dir) : name);
103
100
  }
104
101
  removed += count;
105
- // oxlint-disable-next-line no-console
106
102
  console.log(` ${red('-')} ${entry}`);
107
103
  };
108
104
 
@@ -123,7 +119,6 @@ const rm = (dir, target) => {
123
119
  rmSync(path.resolve(dir, target), { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
124
120
  } catch (error) {
125
121
  if (error.code === 'EPERM' || error.code === 'EBUSY' || error.code === 'ENOTEMPTY') {
126
- // oxlint-disable-next-line no-console
127
122
  console.warn(`Skipped locked path (${error.code}): ${error.path ?? target} — likely held by a running process.`);
128
123
  return null;
129
124
  }
@@ -214,6 +209,5 @@ if (shouldClean) {
214
209
  }
215
210
 
216
211
  const elapsed = `${Math.round(performance.now() - start)}ms`;
217
- // oxlint-disable-next-line no-console
218
212
  console.log(removed === 0 ? `Nothing to clean in ${elapsed}.` : `Cleaned ${removed} path${removed === 1 ? '' : 's'} in ${elapsed}.`);
219
213
  }
@@ -11,6 +11,12 @@
11
11
  * Node.js 18+ (for global fetch)
12
12
  */
13
13
 
14
+ /* oxlint-disable no-console */
15
+ /* oxlint-disable typescript/no-unnecessary-type-conversion */
16
+ /* oxlint-disable typescript/use-unknown-in-catch-callback-variable */
17
+ /* oxlint-disable typescript/prefer-nullish-coalescing */
18
+ /* oxlint-disable typescript/no-unsafe-type-assertion */
19
+
14
20
  import { readFile } from 'node:fs/promises';
15
21
  import { fileURLToPath } from 'node:url';
16
22
 
@@ -94,7 +100,7 @@ async function fetchCurrentDayDownloads(pkgName) {
94
100
  */
95
101
  function sumDownloads(rows) {
96
102
  let total = 0;
97
- for (const r of rows) total += Number(r.downloads) || 0;
103
+ for (const r of rows) total += r.downloads || 0;
98
104
  return total;
99
105
  }
100
106
 
@@ -106,8 +112,7 @@ function sumDownloads(rows) {
106
112
  * @returns {string} Left-padded string.
107
113
  */
108
114
  function padLeft(s, width) {
109
- const str = String(s);
110
- return str.length >= width ? str : ' '.repeat(width - str.length) + str;
115
+ return s.length >= width ? s : ' '.repeat(width - s.length) + s;
111
116
  }
112
117
 
113
118
  /**
@@ -118,8 +123,7 @@ function padLeft(s, width) {
118
123
  * @returns {string} Right-padded string.
119
124
  */
120
125
  function padRight(s, width) {
121
- const str = String(s);
122
- return str.length >= width ? str : str + ' '.repeat(width - str.length);
126
+ return s.length >= width ? s : s + ' '.repeat(width - s.length);
123
127
  }
124
128
 
125
129
  /**
@@ -129,7 +133,7 @@ function padRight(s, width) {
129
133
  * @returns {string} Formatted number.
130
134
  */
131
135
  function formatNumber(n) {
132
- return new Intl.NumberFormat(undefined, { maximumFractionDigits: 0 }).format(Number(n) || 0);
136
+ return new Intl.NumberFormat(undefined, { maximumFractionDigits: 0 }).format(n || 0);
133
137
  }
134
138
 
135
139
  /**
@@ -230,10 +234,14 @@ async function main() {
230
234
  const total = sumDownloads(summaryRows);
231
235
  const days = summaryRows.length || 1;
232
236
  const avg = Math.round(total / days);
233
- const minRow = summaryRows.reduce((m, r) => (r.downloads < m.downloads ? r : m), summaryRows[0] ?? { day: '-', downloads: 0 });
234
- const maxRow = summaryRows.reduce((m, r) => (r.downloads > m.downloads ? r : m), summaryRows[0] ?? { day: '-', downloads: 0 });
237
+ let minRow = summaryRows[0] ?? { day: '-', downloads: 0 };
238
+ let maxRow = minRow;
239
+ for (const r of summaryRows) {
240
+ if (r.downloads < minRow.downloads) minRow = r;
241
+ if (r.downloads > maxRow.downloads) maxRow = r;
242
+ }
235
243
 
236
- const dayWidth = Math.max(3, ...rows.map((r) => String(r.day).length));
244
+ const dayWidth = Math.max(3, ...rows.map((r) => r.day.length));
237
245
  const downloadStrings = rows.map((r) => formatNumber(r.downloads));
238
246
  const downloadsWidth = Math.max('DOWNLOADS'.length, ...downloadStrings.map((s) => s.length));
239
247
  const rangeStart = rows[0]?.day ?? lastMonthData.start;
@@ -11,6 +11,8 @@
11
11
  * Node.js 18+ (for global fetch)
12
12
  */
13
13
 
14
+ /* oxlint-disable no-console */
15
+
14
16
  import { readdir, rm, stat, writeFile } from 'node:fs/promises';
15
17
  import { createRequire } from 'node:module';
16
18
  import path from 'node:path';
@@ -47,14 +49,14 @@ if (dirs.length === 0) {
47
49
  /**
48
50
  * Recursively lists all files under a directory.
49
51
  *
50
- * @param {string} rootDir
51
- * @returns {Promise<string[]>}
52
+ * @param {string} rootDir - Directory to walk.
53
+ * @returns {Promise<string[]>} Absolute paths of all files below rootDir.
52
54
  */
53
55
  const listFilesRecursive = async (rootDir) => {
54
56
  /** @type {string[]} */
55
57
  const files = [];
56
58
 
57
- /** @param {string} currentDir */
59
+ /** @param {string} currentDir - Directory being walked. */
58
60
  const walk = async (currentDir) => {
59
61
  const entries = await readdir(currentDir, { withFileTypes: true });
60
62
  for (const entry of entries) {
@@ -71,11 +73,11 @@ const listFilesRecursive = async (rootDir) => {
71
73
  return files;
72
74
  };
73
75
 
74
- /** @param {string} distDir */
76
+ /** @param {string} distDir - Dist directory to bundle in place. */
75
77
  const bundleDir = async (distDir) => {
76
78
  console.log(`[esbuild] dir: ${distDir}`);
77
79
  const dirStat = await stat(distDir).catch(() => null);
78
- if (!dirStat || !dirStat.isDirectory()) {
80
+ if (!dirStat?.isDirectory()) {
79
81
  throw new Error(`Not a directory: ${distDir}`);
80
82
  }
81
83
 
@@ -8,6 +8,9 @@
8
8
  * node scripts/git-status.mjs
9
9
  */
10
10
 
11
+ /* oxlint-disable no-console */
12
+ /* oxlint-disable typescript/prefer-nullish-coalescing */
13
+
11
14
  import { spawnSync } from 'node:child_process';
12
15
 
13
16
  class ExitError extends Error {
@@ -67,6 +70,13 @@ function section(title) {
67
70
  hr();
68
71
  }
69
72
 
73
+ /**
74
+ * Throw an ExitError.
75
+ *
76
+ * @param {string} message - Error message.
77
+ * @param {number} [code] - Exit code.
78
+ * @returns {never} Never returns.
79
+ */
70
80
  function fail(message, code = 1) {
71
81
  throw new ExitError(message, code);
72
82
  }
@@ -74,6 +84,7 @@ function fail(message, code = 1) {
74
84
  function parseArgs(argv) {
75
85
  if (argv.includes('--help') || argv.includes('-h')) {
76
86
  console.log(usage());
87
+ // oxlint-disable-next-line unicorn/no-useless-undefined
77
88
  return undefined;
78
89
  }
79
90
 
@@ -88,6 +99,7 @@ function parseArgs(argv) {
88
99
  };
89
100
  }
90
101
 
102
+ // oxlint-disable-next-line typescript/consistent-return -- ends with fail(), which always throws
91
103
  function git(args, options = {}) {
92
104
  const { allowFailure = false, input } = options;
93
105
  const result = spawnSync('git', args, {
@@ -106,6 +118,7 @@ function git(args, options = {}) {
106
118
  }
107
119
 
108
120
  if (allowFailure) {
121
+ // oxlint-disable-next-line unicorn/no-useless-undefined
109
122
  return undefined;
110
123
  }
111
124
 
@@ -132,6 +145,7 @@ function printKeyValue(key, value, colorizer) {
132
145
  function parseRemoteTagCount(remote) {
133
146
  const raw = git(['ls-remote', '--tags', remote], { allowFailure: true });
134
147
  if (raw === undefined) {
148
+ // oxlint-disable-next-line unicorn/no-useless-undefined
135
149
  return undefined;
136
150
  }
137
151
 
@@ -263,7 +277,7 @@ function main() {
263
277
 
264
278
  section('Repository status');
265
279
  const repoRoot = git(['rev-parse', '--show-toplevel']);
266
- const repoName = repoRoot.split(/[\\/]/).filter(Boolean).pop() || repoRoot;
280
+ const repoName = repoRoot.split(/[\\/]/).findLast(Boolean) ?? repoRoot;
267
281
  const branch = git(['branch', '--show-current'], { allowFailure: true }) || '(detached)';
268
282
  const head = git(['rev-parse', '--short', 'HEAD']);
269
283
  const dirty = git(['status', '--porcelain']) ? 'yes' : 'no';
@@ -281,11 +295,11 @@ function main() {
281
295
  section('Tag reachability');
282
296
  const tagOnlyCount = git(['rev-list', '--tags', '--not', '--branches', '--count'], { allowFailure: true }) || '0';
283
297
  printKeyValue('Tag-only commits (should be 0):', tagOnlyCount, Number(tagOnlyCount) === 0 ? colors.green : colors.yellow);
284
- if (git(['remote', 'get-url', remote], { allowFailure: true }) !== undefined) {
298
+ if (git(['remote', 'get-url', remote], { allowFailure: true }) === undefined) {
299
+ printKeyValue('Remote tag count:', '(remote not found)', colors.yellow);
300
+ } else {
285
301
  const remoteTagCount = parseRemoteTagCount(remote);
286
302
  printKeyValue(`Remote tag count (${remote}):`, remoteTagCount ?? '(remote query failed)');
287
- } else {
288
- printKeyValue('Remote tag count:', '(remote not found)', colors.yellow);
289
303
  }
290
304
  console.log();
291
305
 
@@ -10,6 +10,8 @@
10
10
  * node scripts/git-sync-dev.mjs rebase
11
11
  */
12
12
 
13
+ /* oxlint-disable no-console */
14
+
13
15
  import { execFileSync } from 'node:child_process';
14
16
 
15
17
  /**
@@ -18,8 +18,10 @@
18
18
  * node scripts/prepublish-clean.mjs --workspaces
19
19
  */
20
20
 
21
+ /* oxlint-disable no-console */
22
+
21
23
  import { existsSync, readdirSync, readFileSync, rmSync } from 'node:fs';
22
- import { resolve } from 'node:path';
24
+ import path from 'node:path';
23
25
 
24
26
  const root = process.cwd();
25
27
 
@@ -29,7 +31,7 @@ const root = process.cwd();
29
31
  // so warn and continue instead of aborting the whole clean.
30
32
  const rm = (dir, target) => {
31
33
  try {
32
- rmSync(resolve(dir, target), { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
34
+ rmSync(path.resolve(dir, target), { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
33
35
  } catch (error) {
34
36
  if (error.code === 'EPERM' || error.code === 'EBUSY' || error.code === 'ENOTEMPTY') {
35
37
  console.warn(`Skipped locked path (${error.code}): ${error.path ?? target} — likely held by a running process.`);
@@ -42,8 +44,8 @@ const rm = (dir, target) => {
42
44
  const clean = (dir) => {
43
45
  // Empty the contents (including dotfiles) of node_modules but keep the directory itself.
44
46
  try {
45
- for (const entry of readdirSync(resolve(dir, 'node_modules'))) {
46
- rm(resolve(dir, 'node_modules'), entry);
47
+ for (const entry of readdirSync(path.resolve(dir, 'node_modules'))) {
48
+ rm(path.resolve(dir, 'node_modules'), entry);
47
49
  }
48
50
  } catch {
49
51
  // node_modules does not exist, nothing to empty.
@@ -55,18 +57,18 @@ const clean = (dir) => {
55
57
  };
56
58
 
57
59
  const getWorkspaceDirs = () => {
58
- const { workspaces = [] } = JSON.parse(readFileSync(resolve(root, 'package.json'), 'utf8'));
60
+ const { workspaces = [] } = JSON.parse(readFileSync(path.resolve(root, 'package.json'), 'utf8'));
59
61
  const patterns = Array.isArray(workspaces) ? workspaces : (workspaces.packages ?? []);
60
62
  const dirs = [];
61
63
 
62
64
  for (const pattern of patterns) {
63
65
  if (!pattern.endsWith('/*')) {
64
- const dir = resolve(root, pattern);
65
- if (existsSync(resolve(dir, 'package.json'))) dirs.push(dir);
66
+ const dir = path.resolve(root, pattern);
67
+ if (existsSync(path.resolve(dir, 'package.json'))) dirs.push(dir);
66
68
  continue;
67
69
  }
68
70
 
69
- const parentDir = resolve(root, pattern.slice(0, -2));
71
+ const parentDir = path.resolve(root, pattern.slice(0, -2));
70
72
  let entries;
71
73
  try {
72
74
  entries = readdirSync(parentDir, { withFileTypes: true });
@@ -76,8 +78,8 @@ const getWorkspaceDirs = () => {
76
78
 
77
79
  for (const entry of entries) {
78
80
  if (!entry.isDirectory()) continue;
79
- const dir = resolve(parentDir, entry.name);
80
- if (existsSync(resolve(dir, 'package.json'))) dirs.push(dir);
81
+ const dir = path.resolve(parentDir, entry.name);
82
+ if (existsSync(path.resolve(dir, 'package.json'))) dirs.push(dir);
81
83
  }
82
84
  }
83
85
 
@@ -8,6 +8,9 @@
8
8
  * node scripts/prune-releases.mjs [--help|-h] [--dry-run|-n] <tag-prefix-to-keep>
9
9
  */
10
10
 
11
+ /* oxlint-disable no-console */
12
+ /* oxlint-disable typescript/prefer-nullish-coalescing */
13
+
11
14
  import { spawnSync } from 'node:child_process';
12
15
  import { readFileSync } from 'node:fs';
13
16
  import { createInterface } from 'node:readline';
@@ -70,6 +73,13 @@ function section(title) {
70
73
  hr();
71
74
  }
72
75
 
76
+ /**
77
+ * Throw an ExitError.
78
+ *
79
+ * @param {string} message - Error message.
80
+ * @param {number} [code] - Exit code.
81
+ * @returns {never} Never returns.
82
+ */
73
83
  function fail(message, code = 1) {
74
84
  throw new ExitError(message, code);
75
85
  }
@@ -77,6 +87,7 @@ function fail(message, code = 1) {
77
87
  function parseArgs(argv) {
78
88
  if (argv.includes('--help') || argv.includes('-h')) {
79
89
  console.log(usage());
90
+ // oxlint-disable-next-line unicorn/no-useless-undefined
80
91
  return undefined;
81
92
  }
82
93
 
@@ -88,8 +99,7 @@ function parseArgs(argv) {
88
99
  fail(`Unknown option(s): ${unknownFlags.join(', ')}\n\n${usage()}`);
89
100
  }
90
101
 
91
- const positional = argv.filter((value) => !value.startsWith('-'));
92
- const [keepPrefix] = positional;
102
+ const keepPrefix = argv.find((value) => !value.startsWith('-'));
93
103
  if (!keepPrefix) {
94
104
  fail(usage());
95
105
  }
@@ -97,6 +107,7 @@ function parseArgs(argv) {
97
107
  return { dryRun, keepPrefix };
98
108
  }
99
109
 
110
+ // oxlint-disable-next-line typescript/consistent-return -- ends with fail(), which always throws
100
111
  function runCapture(command, args, options = {}) {
101
112
  const { allowFailure = false } = options;
102
113
  const result = spawnSync(command, args, {
@@ -178,7 +189,7 @@ function normalizeRepositoryUrl(remoteUrl) {
178
189
  /**
179
190
  * Read the repository from git remote.origin.url.
180
191
  *
181
- * @returns {Promise<string | null>} Repository in owner/repo form, or null.
192
+ * @returns {string | null} Repository in owner/repo form, or null.
182
193
  */
183
194
  function getRepositoryFromGit() {
184
195
  const result = runCapture('git', ['remote', 'get-url', 'origin'], { allowFailure: true });
@@ -191,7 +202,7 @@ function getRepositoryFromGit() {
191
202
  /**
192
203
  * Read the repository from package.json.
193
204
  *
194
- * @returns {Promise<string | null>} Repository in owner/repo form, or null.
205
+ * @returns {string | null} Repository in owner/repo form, or null.
195
206
  */
196
207
  function getRepositoryFromPackageJson() {
197
208
  try {
@@ -208,7 +219,7 @@ function getRepositoryFromPackageJson() {
208
219
  /**
209
220
  * Determine the current GitHub repository.
210
221
  *
211
- * @returns {Promise<string>} Repository in owner/repo form.
222
+ * @returns {string} Repository in owner/repo form.
212
223
  */
213
224
  function getRepositoryNameWithOwner() {
214
225
  const fromGit = getRepositoryFromGit();
@@ -228,7 +239,7 @@ function getRepositoryNameWithOwner() {
228
239
  * Call the GitHub API and parse a JSON response.
229
240
  *
230
241
  * @param {string} pathname - GitHub API path.
231
- * @returns {Promise<unknown>} Parsed JSON payload.
242
+ * @returns {unknown} Parsed JSON payload.
232
243
  */
233
244
  function ghApiJson(pathname) {
234
245
  const { stdout } = runCapture('gh', ['api', '--method', 'GET', pathname]);
@@ -8,6 +8,8 @@
8
8
  * node scripts/prune-tags.mjs [--dry-run|-n] <tag-prefix-to-keep> [remote]
9
9
  */
10
10
 
11
+ /* oxlint-disable no-console */
12
+
11
13
  import { spawnSync } from 'node:child_process';
12
14
  import { createInterface } from 'node:readline';
13
15
 
@@ -68,6 +70,13 @@ function section(title) {
68
70
  hr();
69
71
  }
70
72
 
73
+ /**
74
+ * Throw an ExitError.
75
+ *
76
+ * @param {string} message - Error message.
77
+ * @param {number} [code] - Exit code.
78
+ * @returns {never} Never returns.
79
+ */
71
80
  function fail(message, code = 1) {
72
81
  throw new ExitError(message, code);
73
82
  }
@@ -75,6 +84,7 @@ function fail(message, code = 1) {
75
84
  function parseArgs(argv) {
76
85
  if (argv.includes('--help') || argv.includes('-h')) {
77
86
  console.log(usage());
87
+ // oxlint-disable-next-line unicorn/no-useless-undefined
78
88
  return undefined;
79
89
  }
80
90
 
@@ -95,6 +105,7 @@ function parseArgs(argv) {
95
105
  return { dryRun, keepPrefix, remote };
96
106
  }
97
107
 
108
+ // oxlint-disable-next-line typescript/consistent-return -- ends with fail(), which always throws
98
109
  function runGit(args, options = {}) {
99
110
  const { allowFailure = false, input, inherit = false } = options;
100
111
  const result = spawnSync('git', args, {
@@ -158,7 +169,7 @@ function parseTagNames(raw) {
158
169
  tags.add(ref.replace(/^refs\/tags\//, '').replace(/\^\{\}$/, ''));
159
170
  }
160
171
 
161
- return Array.from(tags).sort((left, right) => left.localeCompare(right));
172
+ return Array.from(tags).toSorted((left, right) => left.localeCompare(right));
162
173
  }
163
174
 
164
175
  function printTagTable(tags, colorizer = (value) => value) {
@@ -306,7 +317,7 @@ async function main() {
306
317
  ? localTagsRaw
307
318
  .split('\n')
308
319
  .filter(Boolean)
309
- .sort((left, right) => left.localeCompare(right))
320
+ .toSorted((left, right) => left.localeCompare(right))
310
321
  : [];
311
322
  printTagTable(localTags, colors.green);
312
323
  }
@@ -13,6 +13,9 @@
13
13
  * git remote.origin.url configured, or package.json repository.url set
14
14
  */
15
15
 
16
+ /* oxlint-disable no-console */
17
+ /* oxlint-disable typescript/prefer-nullish-coalescing */
18
+
16
19
  import { spawn } from 'node:child_process';
17
20
  import { readFile } from 'node:fs/promises';
18
21
 
@@ -92,7 +95,7 @@ function parseArgs(argv) {
92
95
  * @param {string[]} args - Command arguments.
93
96
  * @returns {Promise<{ stdout: string, stderr: string }>} Captured output.
94
97
  */
95
- function execCapture(command, args) {
98
+ async function execCapture(command, args) {
96
99
  return new Promise((resolve, reject) => {
97
100
  const child = spawn(command, args, {
98
101
  shell: false,
@@ -411,6 +414,7 @@ async function main() {
411
414
  }
412
415
  }
413
416
 
417
+ // oxlint-disable-next-line typescript/use-unknown-in-catch-callback-variable
414
418
  main().catch((error) => {
415
419
  console.error(`remove-workflows: ${error?.message || error}`);
416
420
  console.error('Make sure GitHub CLI is installed and authenticated: gh auth status');
@@ -52,16 +52,18 @@
52
52
  * requiring the CSA reference app's --app-pipe debug hook, which Matterbridge doesn't implement).
53
53
  */
54
54
 
55
+ /* oxlint-disable no-console */
56
+
55
57
  import { spawnSync } from 'node:child_process';
56
58
  import { appendFileSync, readFileSync, writeFileSync } from 'node:fs';
57
- import { join, resolve } from 'node:path';
59
+ import path from 'node:path';
58
60
 
59
61
  const root = process.cwd();
60
62
  const containerName = 'chip-test';
61
63
  const image = 'luligu/matterbridge:chip-test';
62
- const testsFile = resolve(root, 'chipTests.json');
63
- const logFile = resolve(root, 'chipTests.log');
64
- const summaryLogFile = resolve(root, 'chipTestsSummary.log');
64
+ const testsFile = path.resolve(root, 'chipTests.json');
65
+ const logFile = path.resolve(root, 'chipTests.log');
66
+ const summaryLogFile = path.resolve(root, 'chipTestsSummary.log');
65
67
  // Node storage for the bridged endpoints; only stateful cluster attributes that get written during a
66
68
  // test create a file here, so these globs only ever remove test-mutated state, never device identity.
67
69
  const matterstorageRoot = '/root/.matterbridge/matterstorage/Matterbridge';
@@ -175,7 +177,7 @@ function start() {
175
177
  '-p',
176
178
  '8585:8283',
177
179
  '-v',
178
- `${join(root, 'temp')}:/tmp/matter_testing/logs`,
180
+ `${path.join(root, 'temp')}:/tmp/matter_testing/logs`,
179
181
  '-v',
180
182
  `${root}:/root/Matterbridge/${pluginName}`,
181
183
  // Shadows just the node_modules subpath of the bind mount above with a named volume backed by the
@@ -236,6 +238,7 @@ function waitForContainerReady(sinceIso, timeoutMs = 45000, pollMs = 1000) {
236
238
  const result = run('docker', ['logs', '--since', sinceIso, containerName], { capture: true });
237
239
  // Matterbridge colorizes its log output with ANSI escapes even without a TTY, splitting the marker
238
240
  // text across escape sequences (e.g. "Matterbridge " <esc> "is online"); strip them before matching.
241
+ // oxlint-disable-next-line eslint/no-control-regex
239
242
  const plainOutput = `${result.stdout ?? ''}${result.stderr ?? ''}`.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '');
240
243
  if (plainOutput.includes(readyLogMarker)) {
241
244
  return;
@@ -280,7 +283,7 @@ function loadChipTestsFile() {
280
283
  const parsed = JSON.parse(raw);
281
284
 
282
285
  pluginConfig = parsed.config;
283
- if (!pluginConfig || !pluginConfig.name) {
286
+ if (!pluginConfig?.name) {
284
287
  fail(`Expected a "config" object with a "name" property in ${testsFile}`);
285
288
  }
286
289
  pluginName = pluginConfig.name;
@@ -355,6 +358,7 @@ function filterTests(tests, nameFilter) {
355
358
  }
356
359
 
357
360
  const needle = nameFilter.toLowerCase();
361
+ // oxlint-disable-next-line typescript/prefer-nullish-coalescing
358
362
  const filtered = tests.filter((test) => test.name.toLowerCase().includes(needle) || test.test.toLowerCase().includes(needle));
359
363
  if (filtered.length === 0) {
360
364
  fail(`No test found with "name" or "test" including ${JSON.stringify(nameFilter)}`);
@@ -9,6 +9,8 @@
9
9
  * node scripts/version.mjs <dev|edge|git|local|bun> [--dry-run]
10
10
  */
11
11
 
12
+ /* oxlint-disable no-console */
13
+
12
14
  import { execFileSync } from 'node:child_process';
13
15
  import fs from 'node:fs/promises';
14
16
  import path from 'node:path';
@@ -40,7 +42,7 @@ function shortSha7FromGit(repoRoot) {
40
42
  encoding: 'utf8',
41
43
  stdio: ['ignore', 'pipe', 'ignore'],
42
44
  });
43
- const sha = String(out).trim();
45
+ const sha = out.trim();
44
46
  if (!/^[0-9a-f]{7}$/i.test(sha)) {
45
47
  throw new Error(`Unexpected git short SHA output: ${JSON.stringify(sha)}`);
46
48
  }
@@ -74,8 +76,7 @@ if (unknownFlags.length > 0) {
74
76
  }
75
77
 
76
78
  const dryRun = args.includes('--dry-run') || args.includes('-n');
77
- const positional = args.filter((a) => !a.startsWith('-'));
78
- const tag = positional[0]?.toLowerCase();
79
+ const tag = args.find((a) => !a.startsWith('-'))?.toLowerCase();
79
80
  if (tag !== 'dev' && tag !== 'edge' && tag !== 'git' && tag !== 'local' && tag !== 'bun') {
80
81
  console.error(usage());
81
82
  process.exitCode = 1;
@@ -1,77 +0,0 @@
1
- /**
2
- * run-automator.mjs
3
- * Version: 1.0.2
4
- *
5
- * Launcher script for a developer "automator".
6
- *
7
- * The automator is a tool that automates package updates for internal projects.
8
- *
9
- * - Locates `automator.mjs` either via `AUTOMATOR_PATH` or common OneDrive roots.
10
- * - Invokes it with the current Node executable, forwarding CLI args and stdio.
11
- * - Exits with the same status code (or 1 on failure).
12
- *
13
- * Usage:
14
- * node scripts/run-automator.mjs [args...]
15
- */
16
-
17
- import { spawnSync } from 'node:child_process';
18
- import { access } from 'node:fs/promises';
19
- import os from 'node:os';
20
- import path from 'node:path';
21
-
22
- const argv = process.argv.slice(2);
23
-
24
- const candidateOneDriveRoots = [
25
- process.env.ONE_DRIVE_ROOT,
26
- process.env.OneDriveCommercial,
27
- process.env.OneDriveConsumer,
28
- process.env.OneDrive,
29
- process.env.ONEDRIVE,
30
- process.env.HOMEPATH && process.env.HOMEDRIVE ? path.join(process.env.HOMEDRIVE, process.env.HOMEPATH, 'OneDrive') : undefined,
31
- process.env.USERPROFILE ? path.join(process.env.USERPROFILE, 'OneDrive') : undefined,
32
- process.env.HOME ? path.join(process.env.HOME, 'OneDrive') : undefined,
33
- ].filter(Boolean);
34
-
35
- const defaultAutomatorRelative = path.join('Code', 'automator', 'automator.mjs');
36
-
37
- const candidateAutomatorPaths = [process.env.AUTOMATOR_PATH, ...candidateOneDriveRoots.map((root) => path.join(root, defaultAutomatorRelative))].filter(Boolean);
38
-
39
- const exists = async (filePath) => {
40
- try {
41
- await access(filePath);
42
- return true;
43
- } catch {
44
- return false;
45
- }
46
- };
47
-
48
- let automatorPath;
49
- for (const candidate of candidateAutomatorPaths) {
50
- if (await exists(candidate)) {
51
- automatorPath = candidate;
52
- break;
53
- }
54
- }
55
-
56
- if (!automatorPath) {
57
- const lines = [
58
- '[automator] Could not locate automator.mjs.',
59
- '',
60
- 'Tried:',
61
- ...candidateAutomatorPaths.map((p) => ` - ${p}`),
62
- '',
63
- 'Fix options:',
64
- ' - Set env var AUTOMATOR_PATH to the full path of automator.mjs',
65
- ' - Or set env var ONE_DRIVE_ROOT to your OneDrive root directory',
66
- ' - Or ensure OneDrive env vars (OneDrive/OneDriveCommercial/OneDriveConsumer) are set',
67
- ];
68
- console.error(lines.join(os.EOL));
69
- process.exit(1);
70
- }
71
-
72
- const result = spawnSync(process.execPath, [automatorPath, ...argv], {
73
- stdio: 'inherit',
74
- windowsHide: true,
75
- });
76
-
77
- process.exit(result.status ?? 1);