mcp-server-diff 2.1.0 → 2.1.5

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,10 +1,13 @@
1
1
  # MCP Server Diff
2
2
 
3
3
  [![GitHub Marketplace](https://img.shields.io/badge/Marketplace-MCP%20Server%20Diff-blue?logo=github)](https://github.com/marketplace/actions/mcp-server-diff)
4
+ [![npm version](https://img.shields.io/npm/v/mcp-server-diff)](https://www.npmjs.com/package/mcp-server-diff)
4
5
  [![GitHub release](https://img.shields.io/github/v/release/SamMorrowDrums/mcp-server-diff)](https://github.com/SamMorrowDrums/mcp-server-diff/releases)
5
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
7
 
7
- A GitHub Action for diffing [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server **public interfaces** between versions. This action compares the current branch against a baseline to surface any changes to your server's exposed tools, resources, prompts, and capabilities—helping you document API evolution and catch unintended modifications.
8
+ A GitHub Action for diffing [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server **public interfaces** between versions. Compares the current branch against a baseline to surface any changes to your server's exposed tools, resources, prompts, and capabilities.
9
+
10
+ > **Also available as a standalone CLI** — see [CLI Documentation](#cli-tool) or install with `npx mcp-server-diff`
8
11
 
9
12
  ## Overview
10
13
 
@@ -496,6 +499,110 @@ jobs:
496
499
  - Ensure the server binds to `0.0.0.0` or `127.0.0.1`, not just `localhost` on some systems
497
500
  - Check firewall or container networking if running in Docker
498
501
 
502
+ ---
503
+
504
+ ## CLI Tool
505
+
506
+ The CLI lets you diff any two MCP servers directly from your terminal—useful for local development, CI pipelines, or comparing servers across different implementations.
507
+
508
+ ### Installation
509
+
510
+ ```bash
511
+ # Run directly with npx (no install required)
512
+ npx mcp-server-diff --help
513
+
514
+ # Or install globally
515
+ npm install -g mcp-server-diff
516
+ ```
517
+
518
+ ### Basic Usage
519
+
520
+ ```bash
521
+ # Compare two local stdio servers
522
+ npx mcp-server-diff -b "python -m mcp_server" -t "node dist/stdio.js"
523
+
524
+ # Compare local server vs remote HTTP endpoint
525
+ npx mcp-server-diff -b "go run ./cmd/server stdio" -t "https://mcp.example.com/api"
526
+
527
+ # Output formats
528
+ npx mcp-server-diff -b "..." -t "..." -o diff # Raw diff hunks only
529
+ npx mcp-server-diff -b "..." -t "..." -o json # Full JSON with details
530
+ npx mcp-server-diff -b "..." -t "..." -o markdown # Formatted report
531
+ npx mcp-server-diff -b "..." -t "..." -o summary # One-line summary (default)
532
+ ```
533
+
534
+ ### HTTP Headers & Authentication
535
+
536
+ For authenticated HTTP endpoints, pass headers with `-H`:
537
+
538
+ ```bash
539
+ # Direct header value
540
+ npx mcp-server-diff -b "./server" -t "https://api.example.com/mcp" \
541
+ -H "Authorization: Bearer your-token-here"
542
+
543
+ # Read from environment variable (keeps secrets out of shell history)
544
+ export MCP_TOKEN="your-secret-token"
545
+ npx mcp-server-diff -b "./server" -t "https://api.example.com/mcp" \
546
+ -H "Authorization: env:MCP_TOKEN"
547
+
548
+ # Prompt for secret interactively (hidden input)
549
+ npx mcp-server-diff -b "./server" -t "https://api.example.com/mcp" \
550
+ -H "Authorization: secret:"
551
+
552
+ # Multiple headers
553
+ npx mcp-server-diff -b "./server" -t "https://api.example.com/mcp" \
554
+ -H "Authorization: env:TOKEN" -H "X-Custom-Header: value"
555
+ ```
556
+
557
+ ### Config File
558
+
559
+ For complex comparisons or multiple targets, use a config file:
560
+
561
+ ```bash
562
+ npx mcp-server-diff -c servers.json -o diff
563
+ ```
564
+
565
+ ```json
566
+ {
567
+ "base": {
568
+ "name": "python-server",
569
+ "transport": "stdio",
570
+ "start_command": "python -m mcp_server"
571
+ },
572
+ "targets": [
573
+ {
574
+ "name": "typescript-server",
575
+ "transport": "stdio",
576
+ "start_command": "node dist/stdio.js"
577
+ },
578
+ {
579
+ "name": "remote-server",
580
+ "transport": "streamable-http",
581
+ "server_url": "https://mcp.example.com/api",
582
+ "headers": {
583
+ "Authorization": "Bearer token"
584
+ }
585
+ }
586
+ ]
587
+ }
588
+ ```
589
+
590
+ ### CLI Options Reference
591
+
592
+ | Option | Description |
593
+ |--------|-------------|
594
+ | `-b, --base <cmd\|url>` | Base server command (stdio) or URL (http) |
595
+ | `-t, --target <cmd\|url>` | Target server command (stdio) or URL (http) |
596
+ | `-H, --header <header>` | HTTP header (repeatable). Use `env:VAR` or `secret:` for values |
597
+ | `-c, --config <file>` | Config file with base and targets |
598
+ | `-o, --output <format>` | Output: `diff`, `json`, `markdown`, `summary` (default) |
599
+ | `-v, --verbose` | Verbose output |
600
+ | `-q, --quiet` | Quiet mode (only output result) |
601
+ | `-h, --help` | Show help |
602
+ | `--version` | Show version |
603
+
604
+ ---
605
+
499
606
  ## License
500
607
 
501
608
  MIT License. See [LICENSE](LICENSE) for details.
package/dist/cli/index.js CHANGED
@@ -38918,6 +38918,8 @@ var __webpack_exports__ = {};
38918
38918
  var external_node_util_ = __nccwpck_require__(7975);
38919
38919
  // EXTERNAL MODULE: external "fs"
38920
38920
  var external_fs_ = __nccwpck_require__(9896);
38921
+ ;// CONCATENATED MODULE: external "readline"
38922
+ const external_readline_namespaceObject = __WEBPACK_EXTERNAL_createRequire(import.meta.url)("readline");
38921
38923
  ;// CONCATENATED MODULE: ./node_modules/zod/v4/core/core.js
38922
38924
  /** A special constant with type `never` */
38923
38925
  const NEVER = Object.freeze({
@@ -56353,6 +56355,13 @@ const log = {
56353
56355
 
56354
56356
 
56355
56357
 
56358
+ /**
56359
+ * Check if an error is "Method not found" (-32601)
56360
+ */
56361
+ function isMethodNotFound(error) {
56362
+ const errorStr = String(error);
56363
+ return errorStr.includes("-32601") || errorStr.includes("Method not found");
56364
+ }
56356
56365
  /**
56357
56366
  * Probes an MCP server and returns capability snapshots
56358
56367
  */
@@ -56425,7 +56434,12 @@ async function probeServer(options) {
56425
56434
  log.info(` Listed ${result.tools.tools.length} tools`);
56426
56435
  }
56427
56436
  catch (error) {
56428
- log.warning(` Failed to list tools: ${error}`);
56437
+ if (isMethodNotFound(error)) {
56438
+ log.info(" Server does not implement tools/list");
56439
+ }
56440
+ else {
56441
+ log.warning(` Failed to list tools: ${error}`);
56442
+ }
56429
56443
  }
56430
56444
  }
56431
56445
  else {
@@ -56439,7 +56453,12 @@ async function probeServer(options) {
56439
56453
  log.info(` Listed ${result.prompts.prompts.length} prompts`);
56440
56454
  }
56441
56455
  catch (error) {
56442
- log.warning(` Failed to list prompts: ${error}`);
56456
+ if (isMethodNotFound(error)) {
56457
+ log.info(" Server does not implement prompts/list");
56458
+ }
56459
+ else {
56460
+ log.warning(` Failed to list prompts: ${error}`);
56461
+ }
56443
56462
  }
56444
56463
  }
56445
56464
  else {
@@ -56453,16 +56472,26 @@ async function probeServer(options) {
56453
56472
  log.info(` Listed ${result.resources.resources.length} resources`);
56454
56473
  }
56455
56474
  catch (error) {
56456
- log.warning(` Failed to list resources: ${error}`);
56475
+ if (isMethodNotFound(error)) {
56476
+ log.info(" Server does not implement resources/list");
56477
+ }
56478
+ else {
56479
+ log.warning(` Failed to list resources: ${error}`);
56480
+ }
56457
56481
  }
56458
- // Also get resource templates
56482
+ // Also try resource templates - some servers support resources but not templates
56459
56483
  try {
56460
56484
  const templatesResult = await client.listResourceTemplates();
56461
56485
  result.resourceTemplates = templatesResult;
56462
56486
  log.info(` Listed ${result.resourceTemplates.resourceTemplates.length} resource templates`);
56463
56487
  }
56464
56488
  catch (error) {
56465
- log.warning(` Failed to list resource templates: ${error}`);
56489
+ if (isMethodNotFound(error)) {
56490
+ log.info(" Server does not implement resources/templates/list");
56491
+ }
56492
+ else {
56493
+ log.warning(` Failed to list resource templates: ${error}`);
56494
+ }
56466
56495
  }
56467
56496
  }
56468
56497
  else {
@@ -56831,6 +56860,7 @@ function diffsToMap(diffs) {
56831
56860
 
56832
56861
 
56833
56862
 
56863
+
56834
56864
  /**
56835
56865
  * Parse command line arguments
56836
56866
  */
@@ -56839,6 +56869,7 @@ function parseCliArgs() {
56839
56869
  options: {
56840
56870
  base: { type: "string", short: "b" },
56841
56871
  target: { type: "string", short: "t" },
56872
+ header: { type: "string", short: "H", multiple: true },
56842
56873
  config: { type: "string", short: "c" },
56843
56874
  output: { type: "string", short: "o", default: "summary" },
56844
56875
  verbose: { type: "boolean", short: "v", default: false },
@@ -56866,8 +56897,11 @@ USAGE:
56866
56897
  OPTIONS:
56867
56898
  -b, --base <command> Base server command (stdio) or URL (http)
56868
56899
  -t, --target <command> Target server command (stdio) or URL (http)
56900
+ -H, --header <header> HTTP header (can be repeated, e.g. -H "Authorization: Bearer ...")
56901
+ Use env:VAR_NAME to read value from environment variable
56902
+ Use secret: to prompt for value securely (hidden input)
56869
56903
  -c, --config <file> Config file with base and targets
56870
- -o, --output <format> Output format: json, markdown, summary (default: summary)
56904
+ -o, --output <format> Output format: diff, json, markdown, summary (default: summary)
56871
56905
  -v, --verbose Verbose output
56872
56906
  -q, --quiet Quiet mode (only output diffs)
56873
56907
  -h, --help Show this help
@@ -56890,6 +56924,7 @@ CONFIG FILE FORMAT:
56890
56924
  }
56891
56925
 
56892
56926
  OUTPUT FORMATS:
56927
+ diff - Raw diff output only
56893
56928
  summary - One line per comparison (default)
56894
56929
  json - Raw JSON with full diff details
56895
56930
  markdown - Formatted markdown report
@@ -56906,6 +56941,18 @@ EXAMPLES:
56906
56941
 
56907
56942
  # Output raw JSON for CI
56908
56943
  mcp-server-diff -c servers.json -o json -q
56944
+
56945
+ # Compare with HTTP headers (for authenticated endpoints)
56946
+ mcp-server-diff -b "go run ./cmd/server stdio" -t "https://api.example.com/mcp" \\
56947
+ -H "Authorization: Bearer token" -o diff
56948
+
56949
+ # Use environment variable for secret (keeps token out of shell history)
56950
+ mcp-server-diff -b "./server" -t "https://api.example.com/mcp" \\
56951
+ -H "Authorization: env:MY_API_TOKEN"
56952
+
56953
+ # Prompt for secret interactively (hidden input)
56954
+ mcp-server-diff -b "./server" -t "https://api.example.com/mcp" \\
56955
+ -H "Authorization: secret:"
56909
56956
  `);
56910
56957
  }
56911
56958
  /**
@@ -56925,12 +56972,13 @@ function loadConfig(configPath) {
56925
56972
  /**
56926
56973
  * Create a server config from a command string
56927
56974
  */
56928
- function commandToConfig(command, name) {
56975
+ function commandToConfig(command, name, headers) {
56929
56976
  if (command.startsWith("http://") || command.startsWith("https://")) {
56930
56977
  return {
56931
56978
  name,
56932
56979
  transport: "streamable-http",
56933
56980
  server_url: command,
56981
+ headers,
56934
56982
  };
56935
56983
  }
56936
56984
  return {
@@ -56939,6 +56987,129 @@ function commandToConfig(command, name) {
56939
56987
  start_command: command,
56940
56988
  };
56941
56989
  }
56990
+ /**
56991
+ * Parse header strings into a record
56992
+ * Accepts formats: "Header: value" or "Header=value"
56993
+ * Values starting with "env:" read from environment variables
56994
+ * Values starting with "secret:" will be prompted (collected separately)
56995
+ */
56996
+ function parseHeaders(headerStrings, secretValues) {
56997
+ const headers = {};
56998
+ if (!headerStrings)
56999
+ return headers;
57000
+ for (const h of headerStrings) {
57001
+ const colonIdx = h.indexOf(":");
57002
+ const eqIdx = h.indexOf("=");
57003
+ const sepIdx = colonIdx > 0 ? colonIdx : eqIdx;
57004
+ if (sepIdx > 0) {
57005
+ const key = h.substring(0, sepIdx).trim();
57006
+ let value = h.substring(sepIdx + 1).trim();
57007
+ // Check for env: prefix to read from environment variable
57008
+ if (value.startsWith("env:")) {
57009
+ const envVar = value.substring(4);
57010
+ const envValue = process.env[envVar];
57011
+ if (!envValue) {
57012
+ throw new Error(`Environment variable ${envVar} not set (referenced in header ${key})`);
57013
+ }
57014
+ value = envValue;
57015
+ }
57016
+ else if (value.startsWith("secret:")) {
57017
+ // Use prompted secret value
57018
+ const secretKey = `${key}`;
57019
+ if (secretValues?.has(secretKey)) {
57020
+ value = secretValues.get(secretKey);
57021
+ }
57022
+ else {
57023
+ throw new Error(`Secret value for header ${key} not collected`);
57024
+ }
57025
+ }
57026
+ headers[key] = value;
57027
+ }
57028
+ }
57029
+ return headers;
57030
+ }
57031
+ /**
57032
+ * Find headers that need secret prompts
57033
+ */
57034
+ function findSecretHeaders(headerStrings) {
57035
+ const secrets = [];
57036
+ if (!headerStrings)
57037
+ return secrets;
57038
+ for (const h of headerStrings) {
57039
+ const colonIdx = h.indexOf(":");
57040
+ const eqIdx = h.indexOf("=");
57041
+ const sepIdx = colonIdx > 0 ? colonIdx : eqIdx;
57042
+ if (sepIdx > 0) {
57043
+ const key = h.substring(0, sepIdx).trim();
57044
+ const value = h.substring(sepIdx + 1).trim();
57045
+ if (value.startsWith("secret:")) {
57046
+ secrets.push(key);
57047
+ }
57048
+ }
57049
+ }
57050
+ return secrets;
57051
+ }
57052
+ /**
57053
+ * Prompt for a secret value with hidden input
57054
+ */
57055
+ async function promptSecret(prompt) {
57056
+ return new Promise((resolve) => {
57057
+ const rl = external_readline_namespaceObject.createInterface({
57058
+ input: process.stdin,
57059
+ output: process.stdout,
57060
+ });
57061
+ // Hide input by using raw mode if available
57062
+ if (process.stdin.isTTY) {
57063
+ process.stdout.write(`${prompt}: `);
57064
+ process.stdin.setRawMode(true);
57065
+ process.stdin.resume();
57066
+ let value = "";
57067
+ const onData = (char) => {
57068
+ const c = char.toString();
57069
+ if (c === "\n" || c === "\r") {
57070
+ process.stdin.setRawMode(false);
57071
+ process.stdin.removeListener("data", onData);
57072
+ rl.close();
57073
+ process.stdout.write("\n");
57074
+ resolve(value);
57075
+ }
57076
+ else if (c === "\u0003") {
57077
+ // Ctrl+C
57078
+ process.exit(1);
57079
+ }
57080
+ else if (c === "\u007F" || c === "\b") {
57081
+ // Backspace
57082
+ if (value.length > 0) {
57083
+ value = value.slice(0, -1);
57084
+ }
57085
+ }
57086
+ else {
57087
+ value += c;
57088
+ }
57089
+ };
57090
+ process.stdin.on("data", onData);
57091
+ }
57092
+ else {
57093
+ // Non-TTY: just read the line (won't be hidden)
57094
+ rl.question(`${prompt}: `, (answer) => {
57095
+ rl.close();
57096
+ resolve(answer);
57097
+ });
57098
+ }
57099
+ });
57100
+ }
57101
+ /**
57102
+ * Prompt for secret values with hidden input
57103
+ */
57104
+ async function promptSecrets(headerNames) {
57105
+ const secrets = new Map();
57106
+ if (headerNames.length === 0)
57107
+ return secrets;
57108
+ for (const name of headerNames) {
57109
+ secrets.set(name, await promptSecret(`Enter value for header "${name}"`));
57110
+ }
57111
+ return secrets;
57112
+ }
56942
57113
  /**
56943
57114
  * Probe a server and return results
56944
57115
  */
@@ -57027,6 +57198,23 @@ async function runComparisons(config) {
57027
57198
  }
57028
57199
  return results;
57029
57200
  }
57201
+ /**
57202
+ * Output raw diff only
57203
+ */
57204
+ function outputDiff(results) {
57205
+ for (const result of results) {
57206
+ if (result.diffs.length > 0) {
57207
+ if (results.length > 1) {
57208
+ console.log(`# ${result.target}`);
57209
+ }
57210
+ for (const { endpoint, diff } of result.diffs) {
57211
+ console.log(`## ${endpoint}`);
57212
+ console.log(diff);
57213
+ console.log("");
57214
+ }
57215
+ }
57216
+ }
57217
+ }
57030
57218
  /**
57031
57219
  * Output results in summary format
57032
57220
  */
@@ -57133,7 +57321,7 @@ async function main() {
57133
57321
  process.exit(0);
57134
57322
  }
57135
57323
  if (values.version) {
57136
- console.log("mcp-server-diff v2.1.0");
57324
+ console.log("mcp-server-diff v2.1.1");
57137
57325
  process.exit(0);
57138
57326
  }
57139
57327
  // Set up logger - CLI uses console logger by default
@@ -57148,9 +57336,14 @@ async function main() {
57148
57336
  config = loadConfig(values.config);
57149
57337
  }
57150
57338
  else if (values.base && values.target) {
57339
+ const headerStrings = values.header;
57340
+ // Prompt for any secret: values before parsing
57341
+ const secretHeaderNames = findSecretHeaders(headerStrings);
57342
+ const secretValues = await promptSecrets(secretHeaderNames);
57343
+ const headers = parseHeaders(headerStrings, secretValues);
57151
57344
  config = {
57152
57345
  base: commandToConfig(values.base, "base"),
57153
- targets: [commandToConfig(values.target, "target")],
57346
+ targets: [commandToConfig(values.target, "target", headers)],
57154
57347
  };
57155
57348
  }
57156
57349
  else {
@@ -57161,6 +57354,9 @@ async function main() {
57161
57354
  const results = await runComparisons(config);
57162
57355
  const outputFormat = values.output || "summary";
57163
57356
  switch (outputFormat) {
57357
+ case "diff":
57358
+ outputDiff(results);
57359
+ break;
57164
57360
  case "json":
57165
57361
  outputJson(results);
57166
57362
  break;
package/dist/index.js CHANGED
@@ -56605,6 +56605,13 @@ const log = {
56605
56605
 
56606
56606
 
56607
56607
 
56608
+ /**
56609
+ * Check if an error is "Method not found" (-32601)
56610
+ */
56611
+ function isMethodNotFound(error) {
56612
+ const errorStr = String(error);
56613
+ return errorStr.includes("-32601") || errorStr.includes("Method not found");
56614
+ }
56608
56615
  /**
56609
56616
  * Probes an MCP server and returns capability snapshots
56610
56617
  */
@@ -56677,7 +56684,12 @@ async function probeServer(options) {
56677
56684
  log.info(` Listed ${result.tools.tools.length} tools`);
56678
56685
  }
56679
56686
  catch (error) {
56680
- log.warning(` Failed to list tools: ${error}`);
56687
+ if (isMethodNotFound(error)) {
56688
+ log.info(" Server does not implement tools/list");
56689
+ }
56690
+ else {
56691
+ log.warning(` Failed to list tools: ${error}`);
56692
+ }
56681
56693
  }
56682
56694
  }
56683
56695
  else {
@@ -56691,7 +56703,12 @@ async function probeServer(options) {
56691
56703
  log.info(` Listed ${result.prompts.prompts.length} prompts`);
56692
56704
  }
56693
56705
  catch (error) {
56694
- log.warning(` Failed to list prompts: ${error}`);
56706
+ if (isMethodNotFound(error)) {
56707
+ log.info(" Server does not implement prompts/list");
56708
+ }
56709
+ else {
56710
+ log.warning(` Failed to list prompts: ${error}`);
56711
+ }
56695
56712
  }
56696
56713
  }
56697
56714
  else {
@@ -56705,16 +56722,26 @@ async function probeServer(options) {
56705
56722
  log.info(` Listed ${result.resources.resources.length} resources`);
56706
56723
  }
56707
56724
  catch (error) {
56708
- log.warning(` Failed to list resources: ${error}`);
56725
+ if (isMethodNotFound(error)) {
56726
+ log.info(" Server does not implement resources/list");
56727
+ }
56728
+ else {
56729
+ log.warning(` Failed to list resources: ${error}`);
56730
+ }
56709
56731
  }
56710
- // Also get resource templates
56732
+ // Also try resource templates - some servers support resources but not templates
56711
56733
  try {
56712
56734
  const templatesResult = await client.listResourceTemplates();
56713
56735
  result.resourceTemplates = templatesResult;
56714
56736
  log.info(` Listed ${result.resourceTemplates.resourceTemplates.length} resource templates`);
56715
56737
  }
56716
56738
  catch (error) {
56717
- log.warning(` Failed to list resource templates: ${error}`);
56739
+ if (isMethodNotFound(error)) {
56740
+ log.info(" Server does not implement resources/templates/list");
56741
+ }
56742
+ else {
56743
+ log.warning(` Failed to list resource templates: ${error}`);
56744
+ }
56718
56745
  }
56719
56746
  }
56720
56747
  else {
package/package.json CHANGED
@@ -1,11 +1,16 @@
1
1
  {
2
2
  "name": "mcp-server-diff",
3
- "version": "2.1.0",
3
+ "version": "2.1.5",
4
4
  "description": "Diff MCP server public interfaces - CLI tool and GitHub Action",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
7
7
  "mcp-server-diff": "dist/cli/index.js"
8
8
  },
9
+ "files": [
10
+ "dist/",
11
+ "README.md",
12
+ "LICENSE"
13
+ ],
9
14
  "type": "module",
10
15
  "scripts": {
11
16
  "build": "npm run build:action && npm run build:cli",
@@ -1,21 +0,0 @@
1
- version: 2
2
- updates:
3
- - package-ecosystem: "github-actions"
4
- directory: "/"
5
- schedule:
6
- interval: "weekly"
7
- commit-message:
8
- prefix: "ci"
9
- labels:
10
- - "dependencies"
11
- - "github-actions"
12
-
13
- - package-ecosystem: "npm"
14
- directory: "/probe"
15
- schedule:
16
- interval: "weekly"
17
- commit-message:
18
- prefix: "deps"
19
- labels:
20
- - "dependencies"
21
- - "javascript"
@@ -1,51 +0,0 @@
1
- name: CI
2
-
3
- on:
4
- push:
5
- branches: [main]
6
- pull_request:
7
- branches: [main]
8
-
9
- permissions:
10
- contents: read
11
-
12
- jobs:
13
- test:
14
- runs-on: ubuntu-latest
15
- strategy:
16
- matrix:
17
- node-version: [20, 22]
18
-
19
- steps:
20
- - name: Checkout
21
- uses: actions/checkout@v4
22
-
23
- - name: Setup Node.js ${{ matrix.node-version }}
24
- uses: actions/setup-node@v6
25
- with:
26
- node-version: ${{ matrix.node-version }}
27
- cache: 'npm'
28
-
29
- - name: Install dependencies
30
- run: npm ci
31
-
32
- - name: Type Check
33
- run: npm run typecheck
34
-
35
- - name: Lint
36
- run: npm run lint
37
-
38
- - name: Check Formatting
39
- run: npm run format:check
40
-
41
- - name: Run Tests
42
- run: npm test
43
-
44
- - name: Build
45
- run: npm run build
46
-
47
- - name: Verify dist is up to date
48
- if: matrix.node-version == 20
49
- run: |
50
- # Check if dist files are up to date
51
- git diff --exit-code dist/ || (echo "::error::dist/ is out of date. Run 'npm run build' and commit the changes." && exit 1)
@@ -1,36 +0,0 @@
1
- name: Publish to npm
2
-
3
- on:
4
- release:
5
- types: [published]
6
-
7
- permissions:
8
- contents: read
9
- id-token: write # Required for npm OIDC provenance
10
-
11
- jobs:
12
- publish:
13
- runs-on: ubuntu-latest
14
- steps:
15
- - name: Checkout
16
- uses: actions/checkout@v4
17
-
18
- - name: Setup Node.js
19
- uses: actions/setup-node@v4
20
- with:
21
- node-version: '22'
22
- registry-url: 'https://registry.npmjs.org'
23
-
24
- - name: Install dependencies
25
- run: npm ci
26
-
27
- - name: Run checks
28
- run: npm run check
29
-
30
- - name: Build
31
- run: npm run build
32
-
33
- # Tokenless publish using npm's OIDC trust
34
- # Requires package to be linked to this repo on npmjs.com
35
- - name: Publish to npm with provenance
36
- run: npm publish --provenance --access public