pubm 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -70,6 +70,7 @@ pubm patch --preview
70
70
  | Flag | Purpose |
71
71
  |------|---------|
72
72
  | `-p, --preview` | Dry‑run: show tasks, no side‑effects |
73
+ | `--preflight` | Simulate CI publish locally (token‑based auth + dry‑run) |
73
74
  | `--registry <list>` | Comma‑separated targets, e.g. `npm,jsr,https://registry.example.com` |
74
75
  | `--branch <name>` / `--any-branch` | Release branch guard control |
75
76
  | `--no-pre-check` / `--no-condition-check` | Skip guard stages |
@@ -84,9 +85,25 @@ pubm patch --preview
84
85
  2. **Required condition checks** – registry ping, login & permission, engine versions.
85
86
  3. **Test & build** *(optional)*
86
87
  4. **Version bump & tag** (SemVer)
87
- 5. **Concurrent publish** – npm (OTP/provenance), jsr, plugins.
88
+ 5. **Concurrent publish** – npm (OTP/provenance), jsr, crates.io, plugins.
88
89
  6. **Git push & GitHub release draft**
89
90
 
91
+ ### Preflight Mode
92
+
93
+ Validate that your CI publish pipeline will work **before** pushing:
94
+
95
+ ```bash
96
+ pubm --preflight
97
+ ```
98
+
99
+ Preflight collects registry tokens interactively, then runs the entire pipeline with `promptEnabled=false` (simulating CI). Publish steps are replaced with dry‑run. If a token is invalid, it re‑prompts and retries.
100
+
101
+ After validation, pubm offers to sync tokens to GitHub Secrets via `gh secret set`. You can also sync manually:
102
+
103
+ ```bash
104
+ pubm secrets sync
105
+ ```
106
+
90
107
  ---
91
108
 
92
109
  ## FAQ
package/bin/cli.js CHANGED
@@ -4694,6 +4694,7 @@ function consoleError(error) {
4694
4694
  }
4695
4695
 
4696
4696
  // src/tasks/preflight.ts
4697
+ import { createHash as createHash2 } from "node:crypto";
4697
4698
  import { ListrEnquirerPromptAdapter } from "@listr2/prompt-adapter-enquirer";
4698
4699
  import { exec as exec2 } from "tinyexec";
4699
4700
 
@@ -4846,13 +4847,29 @@ async function syncGhSecrets(tokens) {
4846
4847
  for (const [registry, token] of Object.entries(tokens)) {
4847
4848
  const config = TOKEN_CONFIG[registry];
4848
4849
  if (!config) continue;
4849
- await exec2("gh", ["secret", "set", config.ghSecretName], {
4850
- throwOnError: true,
4851
- nodeOptions: { input: token }
4850
+ const result = exec2("gh", ["secret", "set", config.ghSecretName], {
4851
+ throwOnError: true
4852
4852
  });
4853
+ const proc = result.process;
4854
+ if (proc?.stdin) {
4855
+ proc.stdin.end(token);
4856
+ }
4857
+ await result;
4853
4858
  }
4854
4859
  }
4860
+ function tokensSyncHash(tokens) {
4861
+ const sorted = Object.entries(tokens).sort(([a2], [b]) => a2.localeCompare(b));
4862
+ return createHash2("sha256").update(JSON.stringify(sorted)).digest("hex").slice(0, 16);
4863
+ }
4864
+ var SYNC_HASH_DB_KEY = "gh-secrets-sync-hash";
4855
4865
  async function promptGhSecretsSync(tokens, task) {
4866
+ const db = new Db();
4867
+ const currentHash = tokensSyncHash(tokens);
4868
+ const storedHash = db.get(SYNC_HASH_DB_KEY);
4869
+ if (storedHash === currentHash) {
4870
+ task.output = "Tokens already synced to GitHub Secrets.";
4871
+ return;
4872
+ }
4856
4873
  const shouldSync = await task.prompt(ListrEnquirerPromptAdapter).run({
4857
4874
  type: "toggle",
4858
4875
  message: "Sync tokens to GitHub Secrets?",
@@ -4863,6 +4880,7 @@ async function promptGhSecretsSync(tokens, task) {
4863
4880
  task.output = "Syncing tokens to GitHub Secrets...";
4864
4881
  try {
4865
4882
  await syncGhSecrets(tokens);
4883
+ db.set(SYNC_HASH_DB_KEY, currentHash);
4866
4884
  task.output = "Tokens synced to GitHub Secrets.";
4867
4885
  } catch (error) {
4868
4886
  throw new PreflightError(
@@ -6432,6 +6450,8 @@ async function jsrRegistry() {
6432
6450
  }
6433
6451
 
6434
6452
  // src/registry/npm.ts
6453
+ import { tmpdir } from "node:os";
6454
+ import { join } from "node:path";
6435
6455
  import { exec as exec6, NonZeroExitError as NonZeroExitError3 } from "tinyexec";
6436
6456
  var NpmError = class extends AbstractError {
6437
6457
  constructor() {
@@ -6587,11 +6607,22 @@ var NpmRegistry = class extends Registry {
6587
6607
  }
6588
6608
  async dryRunPublish() {
6589
6609
  try {
6590
- await this.npm(["publish", "--dry-run"]);
6591
- } catch (error) {
6592
- throw new NpmError("Failed to run `npm publish --dry-run`", {
6593
- cause: error
6610
+ await exec6("npm", ["publish", "--dry-run"], {
6611
+ throwOnError: true,
6612
+ nodeOptions: {
6613
+ env: {
6614
+ ...process.env,
6615
+ npm_config_cache: join(tmpdir(), "pubm-npm-cache")
6616
+ }
6617
+ }
6594
6618
  });
6619
+ } catch (error) {
6620
+ const stderr = error instanceof NonZeroExitError3 ? error.output?.stderr : void 0;
6621
+ throw new NpmError(
6622
+ `Failed to run \`npm publish --dry-run\`${stderr ? `
6623
+ ${stderr}` : ""}`,
6624
+ { cause: error }
6625
+ );
6595
6626
  }
6596
6627
  }
6597
6628
  async twoFactorAuthMode() {
@@ -7526,48 +7557,7 @@ async function run(options) {
7526
7557
  task: async (ctx2, parentTask) => parentTask.newListr(await collectPublishTasks(ctx2), {
7527
7558
  concurrent: true
7528
7559
  })
7529
- } : options.preflight ? [
7530
- {
7531
- skip: options.skipTests,
7532
- title: "Running tests",
7533
- task: async (ctx2) => {
7534
- const packageManager = await getPackageManager();
7535
- try {
7536
- await exec8(packageManager, ["run", ctx2.testScript], {
7537
- throwOnError: true
7538
- });
7539
- } catch (error) {
7540
- throw new AbstractError(
7541
- `Test script '${ctx2.testScript}' failed. Run \`${packageManager} run ${ctx2.testScript}\` locally to see full output.`,
7542
- { cause: error }
7543
- );
7544
- }
7545
- }
7546
- },
7547
- {
7548
- skip: options.skipBuild,
7549
- title: "Building the project",
7550
- task: async (ctx2) => {
7551
- const packageManager = await getPackageManager();
7552
- try {
7553
- await exec8(packageManager, ["run", ctx2.buildScript], {
7554
- throwOnError: true
7555
- });
7556
- } catch (error) {
7557
- throw new AbstractError(
7558
- `Build script '${ctx2.buildScript}' failed. Run \`${packageManager} run ${ctx2.buildScript}\` locally to see full output.`,
7559
- { cause: error }
7560
- );
7561
- }
7562
- }
7563
- },
7564
- {
7565
- title: "Validating publish (dry-run)",
7566
- task: async (ctx2, parentTask) => parentTask.newListr(await collectDryRunPublishTasks(ctx2), {
7567
- concurrent: true
7568
- })
7569
- }
7570
- ] : [
7560
+ } : [
7571
7561
  {
7572
7562
  skip: options.skipTests,
7573
7563
  title: "Running tests",
@@ -7651,12 +7641,19 @@ async function run(options) {
7651
7641
  }
7652
7642
  },
7653
7643
  {
7654
- skip: (ctx2) => options.skipPublish || !!ctx2.preview,
7644
+ skip: (ctx2) => options.skipPublish || !!ctx2.preview || options.preflight,
7655
7645
  title: "Publishing",
7656
7646
  task: async (ctx2, parentTask) => parentTask.newListr(await collectPublishTasks(ctx2), {
7657
7647
  concurrent: true
7658
7648
  })
7659
7649
  },
7650
+ {
7651
+ skip: !options.preflight,
7652
+ title: "Validating publish (dry-run)",
7653
+ task: async (ctx2, parentTask) => parentTask.newListr(await collectDryRunPublishTasks(ctx2), {
7654
+ concurrent: true
7655
+ })
7656
+ },
7660
7657
  {
7661
7658
  title: "Pushing tags to GitHub",
7662
7659
  skip: (ctx2) => !!ctx2.preview,
@@ -7751,7 +7748,7 @@ import micromatch from "micromatch";
7751
7748
 
7752
7749
  // src/monorepo/workspace.ts
7753
7750
  import { existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
7754
- import { join } from "node:path";
7751
+ import { join as join2 } from "node:path";
7755
7752
  import { parse as parse2 } from "yaml";
7756
7753
 
7757
7754
  // src/monorepo/groups.ts
@@ -7994,7 +7991,7 @@ defaultCmd.action(
7994
7991
  };
7995
7992
  try {
7996
7993
  if (options.preflight) {
7997
- context.version = nextVersion || "0.0.0-preflight";
7994
+ await requiredMissingInformationTasks().run(context);
7998
7995
  } else if (isCI3) {
7999
7996
  if (options.publishOnly) {
8000
7997
  const git = new Git();
package/dist/index.cjs CHANGED
@@ -5914,6 +5914,8 @@ async function jsrRegistry() {
5914
5914
  }
5915
5915
 
5916
5916
  // src/registry/npm.ts
5917
+ var import_node_os = require("os");
5918
+ var import_node_path6 = require("path");
5917
5919
  var import_tinyexec4 = require("tinyexec");
5918
5920
  var NpmError = class extends AbstractError {
5919
5921
  constructor() {
@@ -6069,11 +6071,22 @@ var NpmRegistry = class extends Registry {
6069
6071
  }
6070
6072
  async dryRunPublish() {
6071
6073
  try {
6072
- await this.npm(["publish", "--dry-run"]);
6073
- } catch (error) {
6074
- throw new NpmError("Failed to run `npm publish --dry-run`", {
6075
- cause: error
6074
+ await (0, import_tinyexec4.exec)("npm", ["publish", "--dry-run"], {
6075
+ throwOnError: true,
6076
+ nodeOptions: {
6077
+ env: {
6078
+ ...process.env,
6079
+ npm_config_cache: (0, import_node_path6.join)((0, import_node_os.tmpdir)(), "pubm-npm-cache")
6080
+ }
6081
+ }
6076
6082
  });
6083
+ } catch (error) {
6084
+ const stderr = error instanceof import_tinyexec4.NonZeroExitError ? error.output?.stderr : void 0;
6085
+ throw new NpmError(
6086
+ `Failed to run \`npm publish --dry-run\`${stderr ? `
6087
+ ${stderr}` : ""}`,
6088
+ { cause: error }
6089
+ );
6077
6090
  }
6078
6091
  }
6079
6092
  async twoFactorAuthMode() {
@@ -6547,6 +6560,7 @@ var npmPublishTasks = {
6547
6560
  };
6548
6561
 
6549
6562
  // src/tasks/preflight.ts
6563
+ var import_node_crypto2 = require("crypto");
6550
6564
  var import_prompt_adapter_enquirer4 = require("@listr2/prompt-adapter-enquirer");
6551
6565
  var import_tinyexec5 = require("tinyexec");
6552
6566
  var PreflightError = class extends AbstractError {
@@ -6575,13 +6589,29 @@ async function syncGhSecrets(tokens) {
6575
6589
  for (const [registry, token] of Object.entries(tokens)) {
6576
6590
  const config = TOKEN_CONFIG[registry];
6577
6591
  if (!config) continue;
6578
- await (0, import_tinyexec5.exec)("gh", ["secret", "set", config.ghSecretName], {
6579
- throwOnError: true,
6580
- nodeOptions: { input: token }
6592
+ const result = (0, import_tinyexec5.exec)("gh", ["secret", "set", config.ghSecretName], {
6593
+ throwOnError: true
6581
6594
  });
6595
+ const proc = result.process;
6596
+ if (proc?.stdin) {
6597
+ proc.stdin.end(token);
6598
+ }
6599
+ await result;
6582
6600
  }
6583
6601
  }
6602
+ function tokensSyncHash(tokens) {
6603
+ const sorted = Object.entries(tokens).sort(([a2], [b]) => a2.localeCompare(b));
6604
+ return (0, import_node_crypto2.createHash)("sha256").update(JSON.stringify(sorted)).digest("hex").slice(0, 16);
6605
+ }
6606
+ var SYNC_HASH_DB_KEY = "gh-secrets-sync-hash";
6584
6607
  async function promptGhSecretsSync(tokens, task) {
6608
+ const db = new Db();
6609
+ const currentHash = tokensSyncHash(tokens);
6610
+ const storedHash = db.get(SYNC_HASH_DB_KEY);
6611
+ if (storedHash === currentHash) {
6612
+ task.output = "Tokens already synced to GitHub Secrets.";
6613
+ return;
6614
+ }
6585
6615
  const shouldSync = await task.prompt(import_prompt_adapter_enquirer4.ListrEnquirerPromptAdapter).run({
6586
6616
  type: "toggle",
6587
6617
  message: "Sync tokens to GitHub Secrets?",
@@ -6592,6 +6622,7 @@ async function promptGhSecretsSync(tokens, task) {
6592
6622
  task.output = "Syncing tokens to GitHub Secrets...";
6593
6623
  try {
6594
6624
  await syncGhSecrets(tokens);
6625
+ db.set(SYNC_HASH_DB_KEY, currentHash);
6595
6626
  task.output = "Tokens synced to GitHub Secrets.";
6596
6627
  } catch (error) {
6597
6628
  throw new PreflightError(
@@ -7065,48 +7096,7 @@ async function run(options) {
7065
7096
  task: async (ctx2, parentTask) => parentTask.newListr(await collectPublishTasks(ctx2), {
7066
7097
  concurrent: true
7067
7098
  })
7068
- } : options.preflight ? [
7069
- {
7070
- skip: options.skipTests,
7071
- title: "Running tests",
7072
- task: async (ctx2) => {
7073
- const packageManager = await getPackageManager();
7074
- try {
7075
- await (0, import_tinyexec7.exec)(packageManager, ["run", ctx2.testScript], {
7076
- throwOnError: true
7077
- });
7078
- } catch (error) {
7079
- throw new AbstractError(
7080
- `Test script '${ctx2.testScript}' failed. Run \`${packageManager} run ${ctx2.testScript}\` locally to see full output.`,
7081
- { cause: error }
7082
- );
7083
- }
7084
- }
7085
- },
7086
- {
7087
- skip: options.skipBuild,
7088
- title: "Building the project",
7089
- task: async (ctx2) => {
7090
- const packageManager = await getPackageManager();
7091
- try {
7092
- await (0, import_tinyexec7.exec)(packageManager, ["run", ctx2.buildScript], {
7093
- throwOnError: true
7094
- });
7095
- } catch (error) {
7096
- throw new AbstractError(
7097
- `Build script '${ctx2.buildScript}' failed. Run \`${packageManager} run ${ctx2.buildScript}\` locally to see full output.`,
7098
- { cause: error }
7099
- );
7100
- }
7101
- }
7102
- },
7103
- {
7104
- title: "Validating publish (dry-run)",
7105
- task: async (ctx2, parentTask) => parentTask.newListr(await collectDryRunPublishTasks(ctx2), {
7106
- concurrent: true
7107
- })
7108
- }
7109
- ] : [
7099
+ } : [
7110
7100
  {
7111
7101
  skip: options.skipTests,
7112
7102
  title: "Running tests",
@@ -7190,12 +7180,19 @@ async function run(options) {
7190
7180
  }
7191
7181
  },
7192
7182
  {
7193
- skip: (ctx2) => options.skipPublish || !!ctx2.preview,
7183
+ skip: (ctx2) => options.skipPublish || !!ctx2.preview || options.preflight,
7194
7184
  title: "Publishing",
7195
7185
  task: async (ctx2, parentTask) => parentTask.newListr(await collectPublishTasks(ctx2), {
7196
7186
  concurrent: true
7197
7187
  })
7198
7188
  },
7189
+ {
7190
+ skip: !options.preflight,
7191
+ title: "Validating publish (dry-run)",
7192
+ task: async (ctx2, parentTask) => parentTask.newListr(await collectDryRunPublishTasks(ctx2), {
7193
+ concurrent: true
7194
+ })
7195
+ },
7199
7196
  {
7200
7197
  title: "Pushing tags to GitHub",
7201
7198
  skip: (ctx2) => !!ctx2.preview,
@@ -7321,11 +7318,11 @@ function generateChangelog(version2, entries, depUpdates) {
7321
7318
 
7322
7319
  // src/changeset/migrate.ts
7323
7320
  var import_node_fs2 = require("fs");
7324
- var import_node_path6 = __toESM(require("path"), 1);
7321
+ var import_node_path7 = __toESM(require("path"), 1);
7325
7322
  var import_node_process9 = __toESM(require("process"), 1);
7326
7323
  var SKIPPED_FILES = /* @__PURE__ */ new Set(["config.json", "README.md"]);
7327
7324
  function migrateFromChangesets(cwd = import_node_process9.default.cwd()) {
7328
- const changesetDir = import_node_path6.default.join(cwd, ".changeset");
7325
+ const changesetDir = import_node_path7.default.join(cwd, ".changeset");
7329
7326
  if (!(0, import_node_fs2.existsSync)(changesetDir)) {
7330
7327
  return {
7331
7328
  success: false,
@@ -7334,7 +7331,7 @@ function migrateFromChangesets(cwd = import_node_process9.default.cwd()) {
7334
7331
  configMigrated: false
7335
7332
  };
7336
7333
  }
7337
- const pubmDir = import_node_path6.default.join(cwd, ".pubm", "changesets");
7334
+ const pubmDir = import_node_path7.default.join(cwd, ".pubm", "changesets");
7338
7335
  (0, import_node_fs2.mkdirSync)(pubmDir, { recursive: true });
7339
7336
  const files = (0, import_node_fs2.readdirSync)(changesetDir);
7340
7337
  const migratedFiles = [];
@@ -7349,15 +7346,15 @@ function migrateFromChangesets(cwd = import_node_process9.default.cwd()) {
7349
7346
  }
7350
7347
  if (file === "pre.json") {
7351
7348
  (0, import_node_fs2.copyFileSync)(
7352
- import_node_path6.default.join(changesetDir, file),
7353
- import_node_path6.default.resolve(cwd, ".pubm", "pre.json")
7349
+ import_node_path7.default.join(changesetDir, file),
7350
+ import_node_path7.default.resolve(cwd, ".pubm", "pre.json")
7354
7351
  );
7355
7352
  migratedFiles.push(file);
7356
7353
  continue;
7357
7354
  }
7358
7355
  if (file.endsWith(".md")) {
7359
- const src = import_node_path6.default.join(changesetDir, file);
7360
- const dest = import_node_path6.default.join(pubmDir, file);
7356
+ const src = import_node_path7.default.join(changesetDir, file);
7357
+ const dest = import_node_path7.default.join(pubmDir, file);
7361
7358
  (0, import_node_fs2.copyFileSync)(src, dest);
7362
7359
  migratedFiles.push(file);
7363
7360
  }
@@ -7404,10 +7401,10 @@ function parseChangeset(content, fileName) {
7404
7401
 
7405
7402
  // src/changeset/reader.ts
7406
7403
  var import_node_fs3 = require("fs");
7407
- var import_node_path7 = __toESM(require("path"), 1);
7404
+ var import_node_path8 = __toESM(require("path"), 1);
7408
7405
  var import_node_process10 = __toESM(require("process"), 1);
7409
7406
  function readChangesets(cwd = import_node_process10.default.cwd()) {
7410
- const changesetsDir = import_node_path7.default.join(cwd, ".pubm", "changesets");
7407
+ const changesetsDir = import_node_path8.default.join(cwd, ".pubm", "changesets");
7411
7408
  if (!(0, import_node_fs3.existsSync)(changesetsDir)) {
7412
7409
  return [];
7413
7410
  }
@@ -7417,7 +7414,7 @@ function readChangesets(cwd = import_node_process10.default.cwd()) {
7417
7414
  if (!file.endsWith(".md") || file === "README.md") {
7418
7415
  continue;
7419
7416
  }
7420
- const filePath = import_node_path7.default.join(changesetsDir, file);
7417
+ const filePath = import_node_path8.default.join(changesetsDir, file);
7421
7418
  const content = (0, import_node_fs3.readFileSync)(filePath, "utf-8");
7422
7419
  changesets.push(parseChangeset(content, file));
7423
7420
  }
@@ -7487,7 +7484,7 @@ function calculateVersionBumps(currentVersions, cwd = import_node_process12.defa
7487
7484
 
7488
7485
  // src/changeset/writer.ts
7489
7486
  var import_node_fs4 = require("fs");
7490
- var import_node_path8 = __toESM(require("path"), 1);
7487
+ var import_node_path9 = __toESM(require("path"), 1);
7491
7488
  var import_node_process13 = __toESM(require("process"), 1);
7492
7489
  var import_yaml2 = require("yaml");
7493
7490
  var adjectives = [
@@ -7582,11 +7579,11 @@ ${summary}
7582
7579
  return content;
7583
7580
  }
7584
7581
  function writeChangeset(releases, summary, cwd = import_node_process13.default.cwd()) {
7585
- const changesetsDir = import_node_path8.default.join(cwd, ".pubm", "changesets");
7582
+ const changesetsDir = import_node_path9.default.join(cwd, ".pubm", "changesets");
7586
7583
  (0, import_node_fs4.mkdirSync)(changesetsDir, { recursive: true });
7587
7584
  const id = generateChangesetId();
7588
7585
  const fileName = `${id}.md`;
7589
- const filePath = import_node_path8.default.join(changesetsDir, fileName);
7586
+ const filePath = import_node_path9.default.join(changesetsDir, fileName);
7590
7587
  const content = generateChangesetContent(releases, summary);
7591
7588
  (0, import_node_fs4.writeFileSync)(filePath, content, "utf-8");
7592
7589
  return filePath;
@@ -7645,23 +7642,23 @@ function topologicalSort(graph) {
7645
7642
 
7646
7643
  // src/monorepo/discover.ts
7647
7644
  var import_node_fs6 = require("fs");
7648
- var import_node_path10 = __toESM(require("path"), 1);
7645
+ var import_node_path11 = __toESM(require("path"), 1);
7649
7646
  var import_micromatch = __toESM(require("micromatch"), 1);
7650
7647
 
7651
7648
  // src/monorepo/workspace.ts
7652
7649
  var import_node_fs5 = require("fs");
7653
- var import_node_path9 = require("path");
7650
+ var import_node_path10 = require("path");
7654
7651
  var import_yaml3 = require("yaml");
7655
7652
  function detectWorkspace(cwd) {
7656
7653
  const root = cwd ?? process.cwd();
7657
- const pnpmWorkspacePath = (0, import_node_path9.join)(root, "pnpm-workspace.yaml");
7654
+ const pnpmWorkspacePath = (0, import_node_path10.join)(root, "pnpm-workspace.yaml");
7658
7655
  if ((0, import_node_fs5.existsSync)(pnpmWorkspacePath)) {
7659
7656
  const content = (0, import_node_fs5.readFileSync)(pnpmWorkspacePath, "utf-8");
7660
7657
  const parsed = (0, import_yaml3.parse)(content);
7661
7658
  const packages = parsed?.packages ?? [];
7662
7659
  return { type: "pnpm", patterns: packages };
7663
7660
  }
7664
- const packageJsonPath = (0, import_node_path9.join)(root, "package.json");
7661
+ const packageJsonPath = (0, import_node_path10.join)(root, "package.json");
7665
7662
  if ((0, import_node_fs5.existsSync)(packageJsonPath)) {
7666
7663
  const content = (0, import_node_fs5.readFileSync)(packageJsonPath, "utf-8");
7667
7664
  const pkg = JSON.parse(content);
@@ -7722,9 +7719,9 @@ function applyLinkedGroup(bumps, group) {
7722
7719
 
7723
7720
  // src/prerelease/pre.ts
7724
7721
  var import_node_fs7 = require("fs");
7725
- var import_node_path11 = __toESM(require("path"), 1);
7722
+ var import_node_path12 = __toESM(require("path"), 1);
7726
7723
  function getPreStatePath(cwd) {
7727
- return import_node_path11.default.resolve(cwd ?? process.cwd(), ".pubm", "pre.json");
7724
+ return import_node_path12.default.resolve(cwd ?? process.cwd(), ".pubm", "pre.json");
7728
7725
  }
7729
7726
  function readPreState(cwd) {
7730
7727
  const filePath = getPreStatePath(cwd);
@@ -7741,7 +7738,7 @@ function enterPreMode(tag, cwd) {
7741
7738
  "Already in pre mode. Exit pre mode first before entering a new one."
7742
7739
  );
7743
7740
  }
7744
- const dir = import_node_path11.default.dirname(filePath);
7741
+ const dir = import_node_path12.default.dirname(filePath);
7745
7742
  if (!(0, import_node_fs7.existsSync)(dir)) {
7746
7743
  (0, import_node_fs7.mkdirSync)(dir, { recursive: true });
7747
7744
  }
@@ -7783,10 +7780,10 @@ function generateSnapshotVersion(options) {
7783
7780
 
7784
7781
  // src/validate/entry-points.ts
7785
7782
  var import_node_fs8 = require("fs");
7786
- var import_node_path12 = __toESM(require("path"), 1);
7783
+ var import_node_path13 = __toESM(require("path"), 1);
7787
7784
  var SIMPLE_FIELDS = ["main", "module", "types", "typings"];
7788
7785
  function checkPath(filePath, cwd) {
7789
- return (0, import_node_fs8.existsSync)(import_node_path12.default.resolve(cwd, filePath));
7786
+ return (0, import_node_fs8.existsSync)(import_node_path13.default.resolve(cwd, filePath));
7790
7787
  }
7791
7788
  function validateExports(exports2, cwd, prefix = "exports") {
7792
7789
  const errors = [];
package/dist/index.js CHANGED
@@ -5879,6 +5879,8 @@ async function jsrRegistry() {
5879
5879
  }
5880
5880
 
5881
5881
  // src/registry/npm.ts
5882
+ import { tmpdir } from "node:os";
5883
+ import { join } from "node:path";
5882
5884
  import { exec as exec5, NonZeroExitError as NonZeroExitError3 } from "tinyexec";
5883
5885
  var NpmError = class extends AbstractError {
5884
5886
  constructor() {
@@ -6034,11 +6036,22 @@ var NpmRegistry = class extends Registry {
6034
6036
  }
6035
6037
  async dryRunPublish() {
6036
6038
  try {
6037
- await this.npm(["publish", "--dry-run"]);
6038
- } catch (error) {
6039
- throw new NpmError("Failed to run `npm publish --dry-run`", {
6040
- cause: error
6039
+ await exec5("npm", ["publish", "--dry-run"], {
6040
+ throwOnError: true,
6041
+ nodeOptions: {
6042
+ env: {
6043
+ ...process.env,
6044
+ npm_config_cache: join(tmpdir(), "pubm-npm-cache")
6045
+ }
6046
+ }
6041
6047
  });
6048
+ } catch (error) {
6049
+ const stderr = error instanceof NonZeroExitError3 ? error.output?.stderr : void 0;
6050
+ throw new NpmError(
6051
+ `Failed to run \`npm publish --dry-run\`${stderr ? `
6052
+ ${stderr}` : ""}`,
6053
+ { cause: error }
6054
+ );
6042
6055
  }
6043
6056
  }
6044
6057
  async twoFactorAuthMode() {
@@ -6512,6 +6525,7 @@ var npmPublishTasks = {
6512
6525
  };
6513
6526
 
6514
6527
  // src/tasks/preflight.ts
6528
+ import { createHash as createHash2 } from "node:crypto";
6515
6529
  import { ListrEnquirerPromptAdapter as ListrEnquirerPromptAdapter4 } from "@listr2/prompt-adapter-enquirer";
6516
6530
  import { exec as exec6 } from "tinyexec";
6517
6531
  var PreflightError = class extends AbstractError {
@@ -6540,13 +6554,29 @@ async function syncGhSecrets(tokens) {
6540
6554
  for (const [registry, token] of Object.entries(tokens)) {
6541
6555
  const config = TOKEN_CONFIG[registry];
6542
6556
  if (!config) continue;
6543
- await exec6("gh", ["secret", "set", config.ghSecretName], {
6544
- throwOnError: true,
6545
- nodeOptions: { input: token }
6557
+ const result = exec6("gh", ["secret", "set", config.ghSecretName], {
6558
+ throwOnError: true
6546
6559
  });
6560
+ const proc = result.process;
6561
+ if (proc?.stdin) {
6562
+ proc.stdin.end(token);
6563
+ }
6564
+ await result;
6547
6565
  }
6548
6566
  }
6567
+ function tokensSyncHash(tokens) {
6568
+ const sorted = Object.entries(tokens).sort(([a2], [b]) => a2.localeCompare(b));
6569
+ return createHash2("sha256").update(JSON.stringify(sorted)).digest("hex").slice(0, 16);
6570
+ }
6571
+ var SYNC_HASH_DB_KEY = "gh-secrets-sync-hash";
6549
6572
  async function promptGhSecretsSync(tokens, task) {
6573
+ const db = new Db();
6574
+ const currentHash = tokensSyncHash(tokens);
6575
+ const storedHash = db.get(SYNC_HASH_DB_KEY);
6576
+ if (storedHash === currentHash) {
6577
+ task.output = "Tokens already synced to GitHub Secrets.";
6578
+ return;
6579
+ }
6550
6580
  const shouldSync = await task.prompt(ListrEnquirerPromptAdapter4).run({
6551
6581
  type: "toggle",
6552
6582
  message: "Sync tokens to GitHub Secrets?",
@@ -6557,6 +6587,7 @@ async function promptGhSecretsSync(tokens, task) {
6557
6587
  task.output = "Syncing tokens to GitHub Secrets...";
6558
6588
  try {
6559
6589
  await syncGhSecrets(tokens);
6590
+ db.set(SYNC_HASH_DB_KEY, currentHash);
6560
6591
  task.output = "Tokens synced to GitHub Secrets.";
6561
6592
  } catch (error) {
6562
6593
  throw new PreflightError(
@@ -7029,48 +7060,7 @@ async function run(options) {
7029
7060
  task: async (ctx2, parentTask) => parentTask.newListr(await collectPublishTasks(ctx2), {
7030
7061
  concurrent: true
7031
7062
  })
7032
- } : options.preflight ? [
7033
- {
7034
- skip: options.skipTests,
7035
- title: "Running tests",
7036
- task: async (ctx2) => {
7037
- const packageManager = await getPackageManager();
7038
- try {
7039
- await exec8(packageManager, ["run", ctx2.testScript], {
7040
- throwOnError: true
7041
- });
7042
- } catch (error) {
7043
- throw new AbstractError(
7044
- `Test script '${ctx2.testScript}' failed. Run \`${packageManager} run ${ctx2.testScript}\` locally to see full output.`,
7045
- { cause: error }
7046
- );
7047
- }
7048
- }
7049
- },
7050
- {
7051
- skip: options.skipBuild,
7052
- title: "Building the project",
7053
- task: async (ctx2) => {
7054
- const packageManager = await getPackageManager();
7055
- try {
7056
- await exec8(packageManager, ["run", ctx2.buildScript], {
7057
- throwOnError: true
7058
- });
7059
- } catch (error) {
7060
- throw new AbstractError(
7061
- `Build script '${ctx2.buildScript}' failed. Run \`${packageManager} run ${ctx2.buildScript}\` locally to see full output.`,
7062
- { cause: error }
7063
- );
7064
- }
7065
- }
7066
- },
7067
- {
7068
- title: "Validating publish (dry-run)",
7069
- task: async (ctx2, parentTask) => parentTask.newListr(await collectDryRunPublishTasks(ctx2), {
7070
- concurrent: true
7071
- })
7072
- }
7073
- ] : [
7063
+ } : [
7074
7064
  {
7075
7065
  skip: options.skipTests,
7076
7066
  title: "Running tests",
@@ -7154,12 +7144,19 @@ async function run(options) {
7154
7144
  }
7155
7145
  },
7156
7146
  {
7157
- skip: (ctx2) => options.skipPublish || !!ctx2.preview,
7147
+ skip: (ctx2) => options.skipPublish || !!ctx2.preview || options.preflight,
7158
7148
  title: "Publishing",
7159
7149
  task: async (ctx2, parentTask) => parentTask.newListr(await collectPublishTasks(ctx2), {
7160
7150
  concurrent: true
7161
7151
  })
7162
7152
  },
7153
+ {
7154
+ skip: !options.preflight,
7155
+ title: "Validating publish (dry-run)",
7156
+ task: async (ctx2, parentTask) => parentTask.newListr(await collectDryRunPublishTasks(ctx2), {
7157
+ concurrent: true
7158
+ })
7159
+ },
7163
7160
  {
7164
7161
  title: "Pushing tags to GitHub",
7165
7162
  skip: (ctx2) => !!ctx2.preview,
@@ -7614,18 +7611,18 @@ import micromatch from "micromatch";
7614
7611
 
7615
7612
  // src/monorepo/workspace.ts
7616
7613
  import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
7617
- import { join } from "node:path";
7614
+ import { join as join2 } from "node:path";
7618
7615
  import { parse as parse2 } from "yaml";
7619
7616
  function detectWorkspace(cwd) {
7620
7617
  const root = cwd ?? process.cwd();
7621
- const pnpmWorkspacePath = join(root, "pnpm-workspace.yaml");
7618
+ const pnpmWorkspacePath = join2(root, "pnpm-workspace.yaml");
7622
7619
  if (existsSync3(pnpmWorkspacePath)) {
7623
7620
  const content = readFileSync3(pnpmWorkspacePath, "utf-8");
7624
7621
  const parsed = parse2(content);
7625
7622
  const packages = parsed?.packages ?? [];
7626
7623
  return { type: "pnpm", patterns: packages };
7627
7624
  }
7628
- const packageJsonPath = join(root, "package.json");
7625
+ const packageJsonPath = join2(root, "package.json");
7629
7626
  if (existsSync3(packageJsonPath)) {
7630
7627
  const content = readFileSync3(packageJsonPath, "utf-8");
7631
7628
  const pkg = JSON.parse(content);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pubm",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "engines": {
5
5
  "node": ">=18",
6
6
  "git": ">=2.11.0"
@@ -33,7 +33,7 @@
33
33
  "typecheck": "tsc --noEmit",
34
34
  "test": "vitest --run",
35
35
  "coverage": "vitest --run --coverage",
36
- "release": "pnpm build && node bin/cli.js --no-publish",
36
+ "release": "pnpm build && node bin/cli.js --preflight",
37
37
  "ci:release": "node bin/cli.js --publish-only"
38
38
  },
39
39
  "dependencies": {