pubm 0.2.0 → 0.2.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/bin/cli.js CHANGED
@@ -453,7 +453,7 @@ __export(base_exports, {
453
453
  scrollDown: () => scrollDown,
454
454
  scrollUp: () => scrollUp
455
455
  });
456
- import process6 from "node:process";
456
+ import process4 from "node:process";
457
457
  var ESC, OSC, BEL, SEP, isTerminalApp, isWindows3, cwdFunction, cursorTo, cursorMove, cursorUp, cursorDown, cursorForward, cursorBackward, cursorLeft, cursorSavePosition, cursorRestorePosition, cursorGetPosition, cursorNextLine, cursorPrevLine, cursorHide, cursorShow, eraseLines, eraseEndLine, eraseStartLine, eraseLine, eraseDown, eraseUp, eraseScreen, scrollUp, scrollDown, clearScreen, clearTerminal, enterAlternativeScreen, exitAlternativeScreen, beep, link, image, iTerm;
458
458
  var init_base = __esm({
459
459
  "node_modules/.pnpm/ansi-escapes@7.0.0/node_modules/ansi-escapes/base.js"() {
@@ -463,11 +463,11 @@ var init_base = __esm({
463
463
  OSC = "\x1B]";
464
464
  BEL = "\x07";
465
465
  SEP = ";";
466
- isTerminalApp = !isBrowser && process6.env.TERM_PROGRAM === "Apple_Terminal";
467
- isWindows3 = !isBrowser && process6.platform === "win32";
466
+ isTerminalApp = !isBrowser && process4.env.TERM_PROGRAM === "Apple_Terminal";
467
+ isWindows3 = !isBrowser && process4.platform === "win32";
468
468
  cwdFunction = isBrowser ? () => {
469
469
  throw new Error("`process.cwd()` only works in Node.js, not the browser.");
470
- } : process6.cwd;
470
+ } : process4.cwd;
471
471
  cursorTo = (x, y) => {
472
472
  if (typeof x !== "number") {
473
473
  throw new TypeError("The `x` argument is required");
@@ -708,7 +708,7 @@ var init_signals = __esm({
708
708
  });
709
709
 
710
710
  // node_modules/.pnpm/signal-exit@4.1.0/node_modules/signal-exit/dist/mjs/index.js
711
- var processOk, kExitEmitter, global, ObjectDefineProperty, Emitter, SignalExitBase, signalExitWrap, SignalExitFallback, _hupSig, _emitter, _process, _originalProcessEmit, _originalProcessReallyExit, _sigListeners, _loaded, _SignalExit_instances, processReallyExit_fn, processEmit_fn, SignalExit, process7, onExit, load, unload;
711
+ var processOk, kExitEmitter, global, ObjectDefineProperty, Emitter, SignalExitBase, signalExitWrap, SignalExitFallback, _hupSig, _emitter, _process, _originalProcessEmit, _originalProcessReallyExit, _sigListeners, _loaded, _SignalExit_instances, processReallyExit_fn, processEmit_fn, SignalExit, process5, onExit, load, unload;
712
712
  var init_mjs = __esm({
713
713
  "node_modules/.pnpm/signal-exit@4.1.0/node_modules/signal-exit/dist/mjs/index.js"() {
714
714
  "use strict";
@@ -801,7 +801,7 @@ var init_mjs = __esm({
801
801
  // "SIGHUP" throws an `ENOSYS` error on Windows,
802
802
  // so use a supported signal instead
803
803
  /* c8 ignore start */
804
- __privateAdd(this, _hupSig, process7.platform === "win32" ? "SIGINT" : "SIGHUP");
804
+ __privateAdd(this, _hupSig, process5.platform === "win32" ? "SIGINT" : "SIGHUP");
805
805
  /* c8 ignore stop */
806
806
  __privateAdd(this, _emitter, new Emitter());
807
807
  __privateAdd(this, _process);
@@ -918,7 +918,7 @@ var init_mjs = __esm({
918
918
  return og.call(__privateGet(this, _process), ev, ...args);
919
919
  }
920
920
  };
921
- process7 = globalThis.process;
921
+ process5 = globalThis.process;
922
922
  ({
923
923
  onExit: (
924
924
  /**
@@ -952,19 +952,19 @@ var init_mjs = __esm({
952
952
  */
953
953
  unload
954
954
  )
955
- } = signalExitWrap(processOk(process7) ? new SignalExit(process7) : new SignalExitFallback()));
955
+ } = signalExitWrap(processOk(process5) ? new SignalExit(process5) : new SignalExitFallback()));
956
956
  }
957
957
  });
958
958
 
959
959
  // node_modules/.pnpm/restore-cursor@5.1.0/node_modules/restore-cursor/index.js
960
- import process8 from "node:process";
960
+ import process6 from "node:process";
961
961
  var terminal, restoreCursor, restore_cursor_default;
962
962
  var init_restore_cursor = __esm({
963
963
  "node_modules/.pnpm/restore-cursor@5.1.0/node_modules/restore-cursor/index.js"() {
964
964
  "use strict";
965
965
  init_onetime();
966
966
  init_mjs();
967
- terminal = process8.stderr.isTTY ? process8.stderr : process8.stdout.isTTY ? process8.stdout : void 0;
967
+ terminal = process6.stderr.isTTY ? process6.stderr : process6.stdout.isTTY ? process6.stdout : void 0;
968
968
  restoreCursor = terminal ? onetime_default(() => {
969
969
  onExit(() => {
970
970
  terminal.write("\x1B[?25h");
@@ -976,7 +976,7 @@ var init_restore_cursor = __esm({
976
976
  });
977
977
 
978
978
  // node_modules/.pnpm/cli-cursor@5.0.0/node_modules/cli-cursor/index.js
979
- import process9 from "node:process";
979
+ import process7 from "node:process";
980
980
  var isHidden, cliCursor, cli_cursor_default;
981
981
  var init_cli_cursor = __esm({
982
982
  "node_modules/.pnpm/cli-cursor@5.0.0/node_modules/cli-cursor/index.js"() {
@@ -984,14 +984,14 @@ var init_cli_cursor = __esm({
984
984
  init_restore_cursor();
985
985
  isHidden = false;
986
986
  cliCursor = {};
987
- cliCursor.show = (writableStream = process9.stderr) => {
987
+ cliCursor.show = (writableStream = process7.stderr) => {
988
988
  if (!writableStream.isTTY) {
989
989
  return;
990
990
  }
991
991
  isHidden = false;
992
992
  writableStream.write("\x1B[?25h");
993
993
  };
994
- cliCursor.hide = (writableStream = process9.stderr) => {
994
+ cliCursor.hide = (writableStream = process7.stderr) => {
995
995
  if (!writableStream.isTTY) {
996
996
  return;
997
997
  }
@@ -1667,7 +1667,7 @@ __export(log_update_exports, {
1667
1667
  default: () => log_update_default,
1668
1668
  logUpdateStderr: () => logUpdateStderr
1669
1669
  });
1670
- import process10 from "node:process";
1670
+ import process8 from "node:process";
1671
1671
  function createLogUpdate(stream, { showCursor = false } = {}) {
1672
1672
  let previousLineCount = 0;
1673
1673
  let previousWidth = getWidth(stream);
@@ -1721,9 +1721,9 @@ var init_log_update = __esm({
1721
1721
  const toRemove = Math.max(0, lines.length - terminalHeight);
1722
1722
  return toRemove ? sliceAnsi(text, stripAnsi(lines.slice(0, toRemove).join("\n")).length + 1) : text;
1723
1723
  };
1724
- logUpdate = createLogUpdate(process10.stdout);
1724
+ logUpdate = createLogUpdate(process8.stdout);
1725
1725
  log_update_default = logUpdate;
1726
- logUpdateStderr = createLogUpdate(process10.stderr);
1726
+ logUpdateStderr = createLogUpdate(process8.stderr);
1727
1727
  }
1728
1728
  });
1729
1729
 
@@ -2225,178 +2225,6 @@ function registerPreCommand(cli2) {
2225
2225
  });
2226
2226
  }
2227
2227
 
2228
- // src/commands/snapshot.ts
2229
- function registerSnapshotCommand(cli2) {
2230
- cli2.command("snapshot [tag]", "Create a snapshot release").action(async (tag) => {
2231
- console.log(`pubm snapshot ${tag ?? ""} \u2014 coming in next phase`);
2232
- });
2233
- }
2234
-
2235
- // src/changeset/status.ts
2236
- import process5 from "node:process";
2237
-
2238
- // src/changeset/bump-utils.ts
2239
- var BUMP_ORDER = {
2240
- patch: 0,
2241
- minor: 1,
2242
- major: 2
2243
- };
2244
- function maxBump(a2, b) {
2245
- return BUMP_ORDER[a2] >= BUMP_ORDER[b] ? a2 : b;
2246
- }
2247
-
2248
- // src/changeset/reader.ts
2249
- import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync2 } from "node:fs";
2250
- import path5 from "node:path";
2251
- import process4 from "node:process";
2252
-
2253
- // src/changeset/parser.ts
2254
- import { parse as parseYaml } from "yaml";
2255
- var VALID_BUMP_TYPES = /* @__PURE__ */ new Set(["patch", "minor", "major"]);
2256
- function parseChangeset(content, fileName) {
2257
- const frontmatterRegex = /^---\n([\s\S]*?)---/;
2258
- const match = content.match(frontmatterRegex);
2259
- if (!match) {
2260
- throw new Error(
2261
- `Invalid changeset format in "${fileName}": missing frontmatter`
2262
- );
2263
- }
2264
- const yamlContent = match[1];
2265
- const body = content.slice(match[0].length).trim();
2266
- const parsed = parseYaml(yamlContent);
2267
- const releases = [];
2268
- if (parsed) {
2269
- for (const [name, type] of Object.entries(parsed)) {
2270
- if (!VALID_BUMP_TYPES.has(type)) {
2271
- throw new Error(
2272
- `Invalid bump type "${type}" for package "${name}" in "${fileName}". Expected: patch, minor, or major.`
2273
- );
2274
- }
2275
- releases.push({ name, type });
2276
- }
2277
- }
2278
- const id = fileName.replace(/\.md$/, "");
2279
- return {
2280
- id,
2281
- summary: body,
2282
- releases
2283
- };
2284
- }
2285
-
2286
- // src/changeset/reader.ts
2287
- function readChangesets(cwd = process4.cwd()) {
2288
- const changesetsDir = path5.join(cwd, ".pubm", "changesets");
2289
- if (!existsSync4(changesetsDir)) {
2290
- return [];
2291
- }
2292
- const files = readdirSync2(changesetsDir);
2293
- const changesets = [];
2294
- for (const file of files) {
2295
- if (!file.endsWith(".md") || file === "README.md") {
2296
- continue;
2297
- }
2298
- const filePath = path5.join(changesetsDir, file);
2299
- const content = readFileSync2(filePath, "utf-8");
2300
- changesets.push(parseChangeset(content, file));
2301
- }
2302
- return changesets;
2303
- }
2304
-
2305
- // src/changeset/status.ts
2306
- function getStatus(cwd = process5.cwd()) {
2307
- const changesets = readChangesets(cwd);
2308
- const packages = /* @__PURE__ */ new Map();
2309
- for (const changeset of changesets) {
2310
- for (const release of changeset.releases) {
2311
- const existing = packages.get(release.name);
2312
- if (existing) {
2313
- existing.bumpType = maxBump(existing.bumpType, release.type);
2314
- existing.changesetCount += 1;
2315
- existing.summaries.push(changeset.summary);
2316
- } else {
2317
- packages.set(release.name, {
2318
- bumpType: release.type,
2319
- changesetCount: 1,
2320
- summaries: [changeset.summary]
2321
- });
2322
- }
2323
- }
2324
- }
2325
- return {
2326
- packages,
2327
- changesets,
2328
- hasChangesets: changesets.length > 0
2329
- };
2330
- }
2331
-
2332
- // src/commands/status.ts
2333
- function registerStatusCommand(cli2) {
2334
- cli2.command("status", "Show pending changeset status").option("--verbose", "Show full changeset contents").option("--since <ref>", "Only check changesets since git ref").action(async (options) => {
2335
- const status = getStatus();
2336
- if (!status.hasChangesets) {
2337
- if (options.since) {
2338
- console.log("No changesets found.");
2339
- process.exit(1);
2340
- }
2341
- console.log("No pending changesets.");
2342
- return;
2343
- }
2344
- console.log("Pending changesets:");
2345
- for (const [name, info] of status.packages) {
2346
- console.log(
2347
- ` ${name}: ${info.bumpType} (${info.changesetCount} changeset${info.changesetCount > 1 ? "s" : ""})`
2348
- );
2349
- if (options.verbose) {
2350
- for (const summary of info.summaries) {
2351
- console.log(` - ${summary}`);
2352
- }
2353
- }
2354
- }
2355
- });
2356
- }
2357
-
2358
- // src/commands/update.ts
2359
- import { UpdateKit } from "update-kit";
2360
- function registerUpdateCommand(cli2) {
2361
- cli2.command("update", "Update pubm to the latest version").action(async () => {
2362
- const kit = await UpdateKit.create({
2363
- sources: [{ type: "npm", packageName: "pubm" }],
2364
- delegateMode: "execute"
2365
- });
2366
- const result = await kit.autoUpdate({
2367
- onProgress: (p) => {
2368
- if (p.phase === "downloading" && p.totalBytes) {
2369
- const pct = Math.round(p.bytesDownloaded / p.totalBytes * 100);
2370
- process.stderr.write(`\rDownloading... ${pct}%`);
2371
- } else {
2372
- console.error(`${p.phase}...`);
2373
- }
2374
- }
2375
- });
2376
- switch (result.kind) {
2377
- case "success":
2378
- console.log(
2379
- `Updated from ${result.fromVersion} to ${result.toVersion}`
2380
- );
2381
- break;
2382
- case "needs-restart":
2383
- console.log(result.message);
2384
- break;
2385
- case "failed":
2386
- console.error(`Update failed: ${result.error.message}`);
2387
- process.exitCode = 1;
2388
- break;
2389
- }
2390
- });
2391
- }
2392
-
2393
- // src/commands/version-cmd.ts
2394
- function registerVersionCommand(cli2) {
2395
- cli2.command("version", "Consume changesets and bump versions").action(async () => {
2396
- console.log("pubm version \u2014 coming in next phase");
2397
- });
2398
- }
2399
-
2400
2228
  // node_modules/.pnpm/eventemitter3@5.0.1/node_modules/eventemitter3/index.mjs
2401
2229
  var import_index = __toESM(require_eventemitter3(), 1);
2402
2230
  var eventemitter3_default = import_index.default;
@@ -4865,56 +4693,435 @@ function consoleError(error) {
4865
4693
  `);
4866
4694
  }
4867
4695
 
4868
- // src/git.ts
4869
- import semver from "semver";
4696
+ // src/tasks/preflight.ts
4697
+ import { ListrEnquirerPromptAdapter } from "@listr2/prompt-adapter-enquirer";
4870
4698
  import { exec as exec2 } from "tinyexec";
4871
- var GitError = class extends AbstractError {
4699
+
4700
+ // src/utils/db.ts
4701
+ import { createCipheriv, createDecipheriv, createHash } from "node:crypto";
4702
+ import { mkdirSync as mkdirSync5, readFileSync as readFileSync2, statSync, writeFileSync as writeFileSync4 } from "node:fs";
4703
+ import path5 from "node:path";
4704
+ var a = "aes-256-cbc";
4705
+ var n = statSync(import.meta.dirname);
4706
+ var k = `${n.rdev}${n.birthtimeMs}${n.nlink}${n.gid}`;
4707
+ var l = createHash("md5").update(k).digest();
4708
+ function e(e2, f) {
4709
+ const c = createCipheriv(a, createHash("sha-256").update(f).digest(), l);
4710
+ return c.update(e2, "utf8", "hex") + c.final("hex");
4711
+ }
4712
+ function d(g, h) {
4713
+ const d2 = createDecipheriv(a, createHash("sha-256").update(h).digest(), l);
4714
+ return d2.update(g, "hex", "utf8") + d2.final("utf8");
4715
+ }
4716
+ var Db = class {
4872
4717
  constructor() {
4873
- super(...arguments);
4874
- __publicField(this, "name", "Git Error");
4875
- }
4876
- };
4877
- var Git = class {
4878
- async git(args) {
4879
- const { stdout } = await exec2("git", args, { throwOnError: true });
4880
- return stdout;
4718
+ __publicField(this, "path", path5.resolve(import.meta.dirname, ".pubm"));
4719
+ try {
4720
+ if (!statSync(this.path).isDirectory()) {
4721
+ mkdirSync5(this.path);
4722
+ }
4723
+ } catch {
4724
+ try {
4725
+ mkdirSync5(this.path);
4726
+ } catch (error) {
4727
+ throw new Error(
4728
+ `Failed to create token storage directory at '${this.path}': ${error instanceof Error ? error.message : error}`
4729
+ );
4730
+ }
4731
+ }
4881
4732
  }
4882
- async userName() {
4733
+ set(field, value) {
4883
4734
  try {
4884
- return (await this.git(["config", "--get", "user.name"])).trim();
4735
+ writeFileSync4(
4736
+ path5.resolve(
4737
+ this.path,
4738
+ Buffer.from(e(field, field)).toString("base64")
4739
+ ),
4740
+ Buffer.from(e(`${value}`, field)),
4741
+ { encoding: "binary" }
4742
+ );
4885
4743
  } catch (error) {
4886
- throw new GitError("Failed to run `git config --get user.name`", {
4887
- cause: error
4888
- });
4744
+ throw new Error(
4745
+ `Failed to save token for '${field}': ${error instanceof Error ? error.message : error}`
4746
+ );
4889
4747
  }
4890
4748
  }
4891
- async latestTag() {
4749
+ get(field) {
4750
+ const filePath = path5.resolve(
4751
+ this.path,
4752
+ Buffer.from(e(field, field)).toString("base64")
4753
+ );
4754
+ let raw;
4892
4755
  try {
4893
- return (await this.git(["describe", "--tags", "--abbrev=0"])).trim();
4756
+ raw = readFileSync2(filePath);
4894
4757
  } catch {
4895
4758
  return null;
4896
4759
  }
4897
- }
4898
- async tags() {
4899
- try {
4900
- return (await this.git(["tag", "-l"])).trim().split("\n").sort(semver.compareIdentifiers);
4901
- } catch (error) {
4902
- throw new GitError("Failed to run `git tag -l`", {
4903
- cause: error
4904
- });
4905
- }
4906
- }
4907
- async previousTag(tag) {
4908
4760
  try {
4909
- const tags = await this.tags();
4910
- const strip = (t) => t.replace(/^v/, "");
4911
- return tags.at(tags.findIndex((t) => strip(t) === strip(tag)) - 1) ?? null;
4761
+ return d(Buffer.from(raw).toString(), field);
4912
4762
  } catch {
4763
+ console.warn(
4764
+ `Stored token for '${field}' appears corrupted. It will be re-requested.`
4765
+ );
4913
4766
  return null;
4914
4767
  }
4915
4768
  }
4916
- async dryFetch() {
4917
- try {
4769
+ };
4770
+
4771
+ // src/utils/token.ts
4772
+ var TOKEN_CONFIG = {
4773
+ npm: {
4774
+ envVar: "NODE_AUTH_TOKEN",
4775
+ dbKey: "npm-token",
4776
+ ghSecretName: "NODE_AUTH_TOKEN",
4777
+ promptLabel: "npm access token"
4778
+ },
4779
+ jsr: {
4780
+ envVar: "JSR_TOKEN",
4781
+ dbKey: "jsr-token",
4782
+ ghSecretName: "JSR_TOKEN",
4783
+ promptLabel: "jsr API token"
4784
+ },
4785
+ crates: {
4786
+ envVar: "CARGO_REGISTRY_TOKEN",
4787
+ dbKey: "cargo-token",
4788
+ ghSecretName: "CARGO_REGISTRY_TOKEN",
4789
+ promptLabel: "crates.io API token"
4790
+ }
4791
+ };
4792
+ function loadTokensFromDb(registries) {
4793
+ const db = new Db();
4794
+ const tokens = {};
4795
+ for (const registry of registries) {
4796
+ const config = TOKEN_CONFIG[registry];
4797
+ if (!config) continue;
4798
+ const token = db.get(config.dbKey);
4799
+ if (token) tokens[registry] = token;
4800
+ }
4801
+ return tokens;
4802
+ }
4803
+ function injectTokensToEnv(tokens) {
4804
+ const originals = {};
4805
+ for (const [registry, token] of Object.entries(tokens)) {
4806
+ const config = TOKEN_CONFIG[registry];
4807
+ if (!config) continue;
4808
+ originals[config.envVar] = process.env[config.envVar];
4809
+ process.env[config.envVar] = token;
4810
+ }
4811
+ return () => {
4812
+ for (const [envVar, original] of Object.entries(originals)) {
4813
+ if (original === void 0) {
4814
+ delete process.env[envVar];
4815
+ } else {
4816
+ process.env[envVar] = original;
4817
+ }
4818
+ }
4819
+ };
4820
+ }
4821
+
4822
+ // src/tasks/preflight.ts
4823
+ var PreflightError = class extends AbstractError {
4824
+ constructor() {
4825
+ super(...arguments);
4826
+ __publicField(this, "name", "Preflight Error");
4827
+ }
4828
+ };
4829
+ async function collectTokens(registries, task) {
4830
+ const existing = loadTokensFromDb(registries);
4831
+ const tokens = { ...existing };
4832
+ for (const registry of registries) {
4833
+ const config = TOKEN_CONFIG[registry];
4834
+ if (!config || tokens[registry]) continue;
4835
+ task.output = `Enter ${config.promptLabel}`;
4836
+ const token = await task.prompt(ListrEnquirerPromptAdapter).run({
4837
+ type: "password",
4838
+ message: `Enter ${config.promptLabel}`
4839
+ });
4840
+ tokens[registry] = token;
4841
+ new Db().set(config.dbKey, token);
4842
+ }
4843
+ return tokens;
4844
+ }
4845
+ async function syncGhSecrets(tokens) {
4846
+ for (const [registry, token] of Object.entries(tokens)) {
4847
+ const config = TOKEN_CONFIG[registry];
4848
+ if (!config) continue;
4849
+ await exec2("gh", ["secret", "set", config.ghSecretName], {
4850
+ throwOnError: true,
4851
+ nodeOptions: { input: token }
4852
+ });
4853
+ }
4854
+ }
4855
+ async function promptGhSecretsSync(tokens, task) {
4856
+ const shouldSync = await task.prompt(ListrEnquirerPromptAdapter).run({
4857
+ type: "toggle",
4858
+ message: "Sync tokens to GitHub Secrets?",
4859
+ enabled: "Yes",
4860
+ disabled: "No"
4861
+ });
4862
+ if (shouldSync) {
4863
+ task.output = "Syncing tokens to GitHub Secrets...";
4864
+ try {
4865
+ await syncGhSecrets(tokens);
4866
+ task.output = "Tokens synced to GitHub Secrets.";
4867
+ } catch (error) {
4868
+ throw new PreflightError(
4869
+ "Failed to sync tokens to GitHub Secrets. Ensure `gh` CLI is installed and authenticated (`gh auth login`).",
4870
+ { cause: error }
4871
+ );
4872
+ }
4873
+ }
4874
+ }
4875
+
4876
+ // src/commands/secrets.ts
4877
+ function registerSecretsCommand(cli2) {
4878
+ cli2.command("secrets sync", "Sync stored tokens to GitHub Secrets").option("--registry <...registries>", "Filter to specific registries", {
4879
+ // biome-ignore lint/suspicious/noExplicitAny: CAC option type mismatch
4880
+ type: String
4881
+ }).action(async (options) => {
4882
+ try {
4883
+ const registries = options.registry ? options.registry.split(",") : ["npm", "jsr", "crates"];
4884
+ const tokens = loadTokensFromDb(registries);
4885
+ if (Object.keys(tokens).length === 0) {
4886
+ console.log(
4887
+ "No stored tokens found. Run `pubm --preflight` first to save tokens."
4888
+ );
4889
+ return;
4890
+ }
4891
+ console.log(
4892
+ `Syncing ${Object.keys(tokens).length} token(s) to GitHub Secrets...`
4893
+ );
4894
+ await syncGhSecrets(tokens);
4895
+ console.log("Done! Tokens synced to GitHub Secrets.");
4896
+ } catch (e2) {
4897
+ consoleError(e2);
4898
+ process.exitCode = 1;
4899
+ }
4900
+ });
4901
+ }
4902
+
4903
+ // src/commands/snapshot.ts
4904
+ function registerSnapshotCommand(cli2) {
4905
+ cli2.command("snapshot [tag]", "Create a snapshot release").action(async (tag) => {
4906
+ console.log(`pubm snapshot ${tag ?? ""} \u2014 coming in next phase`);
4907
+ });
4908
+ }
4909
+
4910
+ // src/changeset/status.ts
4911
+ import process10 from "node:process";
4912
+
4913
+ // src/changeset/bump-utils.ts
4914
+ var BUMP_ORDER = {
4915
+ patch: 0,
4916
+ minor: 1,
4917
+ major: 2
4918
+ };
4919
+ function maxBump(a2, b) {
4920
+ return BUMP_ORDER[a2] >= BUMP_ORDER[b] ? a2 : b;
4921
+ }
4922
+
4923
+ // src/changeset/reader.ts
4924
+ import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "node:fs";
4925
+ import path6 from "node:path";
4926
+ import process9 from "node:process";
4927
+
4928
+ // src/changeset/parser.ts
4929
+ import { parse as parseYaml } from "yaml";
4930
+ var VALID_BUMP_TYPES = /* @__PURE__ */ new Set(["patch", "minor", "major"]);
4931
+ function parseChangeset(content, fileName) {
4932
+ const frontmatterRegex = /^---\n([\s\S]*?)---/;
4933
+ const match = content.match(frontmatterRegex);
4934
+ if (!match) {
4935
+ throw new Error(
4936
+ `Invalid changeset format in "${fileName}": missing frontmatter`
4937
+ );
4938
+ }
4939
+ const yamlContent = match[1];
4940
+ const body = content.slice(match[0].length).trim();
4941
+ const parsed = parseYaml(yamlContent);
4942
+ const releases = [];
4943
+ if (parsed) {
4944
+ for (const [name, type] of Object.entries(parsed)) {
4945
+ if (!VALID_BUMP_TYPES.has(type)) {
4946
+ throw new Error(
4947
+ `Invalid bump type "${type}" for package "${name}" in "${fileName}". Expected: patch, minor, or major.`
4948
+ );
4949
+ }
4950
+ releases.push({ name, type });
4951
+ }
4952
+ }
4953
+ const id = fileName.replace(/\.md$/, "");
4954
+ return {
4955
+ id,
4956
+ summary: body,
4957
+ releases
4958
+ };
4959
+ }
4960
+
4961
+ // src/changeset/reader.ts
4962
+ function readChangesets(cwd = process9.cwd()) {
4963
+ const changesetsDir = path6.join(cwd, ".pubm", "changesets");
4964
+ if (!existsSync4(changesetsDir)) {
4965
+ return [];
4966
+ }
4967
+ const files = readdirSync2(changesetsDir);
4968
+ const changesets = [];
4969
+ for (const file of files) {
4970
+ if (!file.endsWith(".md") || file === "README.md") {
4971
+ continue;
4972
+ }
4973
+ const filePath = path6.join(changesetsDir, file);
4974
+ const content = readFileSync3(filePath, "utf-8");
4975
+ changesets.push(parseChangeset(content, file));
4976
+ }
4977
+ return changesets;
4978
+ }
4979
+
4980
+ // src/changeset/status.ts
4981
+ function getStatus(cwd = process10.cwd()) {
4982
+ const changesets = readChangesets(cwd);
4983
+ const packages = /* @__PURE__ */ new Map();
4984
+ for (const changeset of changesets) {
4985
+ for (const release of changeset.releases) {
4986
+ const existing = packages.get(release.name);
4987
+ if (existing) {
4988
+ existing.bumpType = maxBump(existing.bumpType, release.type);
4989
+ existing.changesetCount += 1;
4990
+ existing.summaries.push(changeset.summary);
4991
+ } else {
4992
+ packages.set(release.name, {
4993
+ bumpType: release.type,
4994
+ changesetCount: 1,
4995
+ summaries: [changeset.summary]
4996
+ });
4997
+ }
4998
+ }
4999
+ }
5000
+ return {
5001
+ packages,
5002
+ changesets,
5003
+ hasChangesets: changesets.length > 0
5004
+ };
5005
+ }
5006
+
5007
+ // src/commands/status.ts
5008
+ function registerStatusCommand(cli2) {
5009
+ cli2.command("status", "Show pending changeset status").option("--verbose", "Show full changeset contents").option("--since <ref>", "Only check changesets since git ref").action(async (options) => {
5010
+ const status = getStatus();
5011
+ if (!status.hasChangesets) {
5012
+ if (options.since) {
5013
+ console.log("No changesets found.");
5014
+ process.exit(1);
5015
+ }
5016
+ console.log("No pending changesets.");
5017
+ return;
5018
+ }
5019
+ console.log("Pending changesets:");
5020
+ for (const [name, info] of status.packages) {
5021
+ console.log(
5022
+ ` ${name}: ${info.bumpType} (${info.changesetCount} changeset${info.changesetCount > 1 ? "s" : ""})`
5023
+ );
5024
+ if (options.verbose) {
5025
+ for (const summary of info.summaries) {
5026
+ console.log(` - ${summary}`);
5027
+ }
5028
+ }
5029
+ }
5030
+ });
5031
+ }
5032
+
5033
+ // src/commands/update.ts
5034
+ import { UpdateKit } from "update-kit";
5035
+ function registerUpdateCommand(cli2) {
5036
+ cli2.command("update", "Update pubm to the latest version").action(async () => {
5037
+ const kit = await UpdateKit.create({
5038
+ sources: [{ type: "npm", packageName: "pubm" }],
5039
+ delegateMode: "execute"
5040
+ });
5041
+ const result = await kit.autoUpdate({
5042
+ onProgress: (p) => {
5043
+ if (p.phase === "downloading" && p.totalBytes) {
5044
+ const pct = Math.round(p.bytesDownloaded / p.totalBytes * 100);
5045
+ process.stderr.write(`\rDownloading... ${pct}%`);
5046
+ } else {
5047
+ console.error(`${p.phase}...`);
5048
+ }
5049
+ }
5050
+ });
5051
+ switch (result.kind) {
5052
+ case "success":
5053
+ console.log(
5054
+ `Updated from ${result.fromVersion} to ${result.toVersion}`
5055
+ );
5056
+ break;
5057
+ case "needs-restart":
5058
+ console.log(result.message);
5059
+ break;
5060
+ case "failed":
5061
+ console.error(`Update failed: ${result.error.message}`);
5062
+ process.exitCode = 1;
5063
+ break;
5064
+ }
5065
+ });
5066
+ }
5067
+
5068
+ // src/commands/version-cmd.ts
5069
+ function registerVersionCommand(cli2) {
5070
+ cli2.command("version", "Consume changesets and bump versions").action(async () => {
5071
+ console.log("pubm version \u2014 coming in next phase");
5072
+ });
5073
+ }
5074
+
5075
+ // src/git.ts
5076
+ import semver from "semver";
5077
+ import { exec as exec3 } from "tinyexec";
5078
+ var GitError = class extends AbstractError {
5079
+ constructor() {
5080
+ super(...arguments);
5081
+ __publicField(this, "name", "Git Error");
5082
+ }
5083
+ };
5084
+ var Git = class {
5085
+ async git(args) {
5086
+ const { stdout } = await exec3("git", args, { throwOnError: true });
5087
+ return stdout;
5088
+ }
5089
+ async userName() {
5090
+ try {
5091
+ return (await this.git(["config", "--get", "user.name"])).trim();
5092
+ } catch (error) {
5093
+ throw new GitError("Failed to run `git config --get user.name`", {
5094
+ cause: error
5095
+ });
5096
+ }
5097
+ }
5098
+ async latestTag() {
5099
+ try {
5100
+ return (await this.git(["describe", "--tags", "--abbrev=0"])).trim();
5101
+ } catch {
5102
+ return null;
5103
+ }
5104
+ }
5105
+ async tags() {
5106
+ try {
5107
+ return (await this.git(["tag", "-l"])).trim().split("\n").sort(semver.compareIdentifiers);
5108
+ } catch (error) {
5109
+ throw new GitError("Failed to run `git tag -l`", {
5110
+ cause: error
5111
+ });
5112
+ }
5113
+ }
5114
+ async previousTag(tag) {
5115
+ try {
5116
+ const tags = await this.tags();
5117
+ const strip = (t) => t.replace(/^v/, "");
5118
+ return tags.at(tags.findIndex((t) => strip(t) === strip(tag)) - 1) ?? null;
5119
+ } catch {
5120
+ return null;
5121
+ }
5122
+ }
5123
+ async dryFetch() {
5124
+ try {
4918
5125
  return await this.git(["fetch", "--dry-run"]);
4919
5126
  } catch (error) {
4920
5127
  throw new GitError("Failed to run `git fetch --dry-run`", {
@@ -5130,7 +5337,7 @@ var Git = class {
5130
5337
  async push(options) {
5131
5338
  const args = ["push", options].filter((v) => v);
5132
5339
  try {
5133
- const { stderr } = await exec2("git", args, { throwOnError: true });
5340
+ const { stderr } = await exec3("git", args, { throwOnError: true });
5134
5341
  if (`${stderr}`.includes("GH006")) {
5135
5342
  return false;
5136
5343
  }
@@ -5190,7 +5397,7 @@ function resolveConfig(config) {
5190
5397
 
5191
5398
  // src/config/loader.ts
5192
5399
  import { stat } from "node:fs/promises";
5193
- import path6 from "node:path";
5400
+ import path7 from "node:path";
5194
5401
  var CONFIG_FILES = [
5195
5402
  "pubm.config.ts",
5196
5403
  "pubm.config.mts",
@@ -5201,7 +5408,7 @@ var CONFIG_FILES = [
5201
5408
  ];
5202
5409
  async function findConfigFile(cwd) {
5203
5410
  for (const file of CONFIG_FILES) {
5204
- const filePath = path6.join(cwd, file);
5411
+ const filePath = path7.join(cwd, file);
5205
5412
  try {
5206
5413
  if ((await stat(filePath)).isFile()) {
5207
5414
  return filePath;
@@ -5241,7 +5448,7 @@ import process14 from "node:process";
5241
5448
  import npmCli3 from "@npmcli/promise-spawn";
5242
5449
  import SemVer from "semver";
5243
5450
  import { isCI as isCI2 } from "std-env";
5244
- import { exec as exec7 } from "tinyexec";
5451
+ import { exec as exec8 } from "tinyexec";
5245
5452
 
5246
5453
  // src/utils/cli.ts
5247
5454
  var warningBadge = color.bgYellow(" Warning ");
@@ -5251,7 +5458,7 @@ function link2(text, url) {
5251
5458
 
5252
5459
  // src/ecosystem/rust.ts
5253
5460
  import { readFile, stat as stat2, writeFile } from "node:fs/promises";
5254
- import path7 from "node:path";
5461
+ import path8 from "node:path";
5255
5462
  import { parse, stringify } from "smol-toml";
5256
5463
 
5257
5464
  // src/ecosystem/ecosystem.ts
@@ -5265,14 +5472,14 @@ var Ecosystem = class {
5265
5472
  var RustEcosystem = class extends Ecosystem {
5266
5473
  static async detect(packagePath) {
5267
5474
  try {
5268
- return (await stat2(path7.join(packagePath, "Cargo.toml"))).isFile();
5475
+ return (await stat2(path8.join(packagePath, "Cargo.toml"))).isFile();
5269
5476
  } catch {
5270
5477
  return false;
5271
5478
  }
5272
5479
  }
5273
5480
  async readCargoToml() {
5274
5481
  const raw = await readFile(
5275
- path7.join(this.packagePath, "Cargo.toml"),
5482
+ path8.join(this.packagePath, "Cargo.toml"),
5276
5483
  "utf-8"
5277
5484
  );
5278
5485
  return parse(raw);
@@ -5288,7 +5495,7 @@ var RustEcosystem = class extends Ecosystem {
5288
5495
  return pkg.version;
5289
5496
  }
5290
5497
  async writeVersion(newVersion) {
5291
- const filePath = path7.join(this.packagePath, "Cargo.toml");
5498
+ const filePath = path8.join(this.packagePath, "Cargo.toml");
5292
5499
  const raw = await readFile(filePath, "utf-8");
5293
5500
  const cargo = parse(raw);
5294
5501
  const pkg = cargo.package;
@@ -5408,7 +5615,7 @@ function createListr(...args) {
5408
5615
 
5409
5616
  // src/utils/package.ts
5410
5617
  import { readFile as readFile2, stat as stat3, writeFile as writeFile2 } from "node:fs/promises";
5411
- import path8 from "node:path";
5618
+ import path9 from "node:path";
5412
5619
  import process11 from "node:process";
5413
5620
  var cachedPackageJson = {};
5414
5621
  var cachedJsrJson = {};
@@ -5418,16 +5625,16 @@ function patchCachedJsrJson(contents, { cwd = process11.cwd() } = {}) {
5418
5625
  async function findOutFile(file, { cwd = process11.cwd() } = {}) {
5419
5626
  let directory = cwd;
5420
5627
  let filePath = "";
5421
- const { root } = path8.parse(cwd);
5628
+ const { root } = path9.parse(cwd);
5422
5629
  while (directory) {
5423
- filePath = path8.join(directory, file);
5630
+ filePath = path9.join(directory, file);
5424
5631
  try {
5425
5632
  if ((await stat3(filePath)).isFile()) {
5426
5633
  break;
5427
5634
  }
5428
5635
  } catch {
5429
5636
  }
5430
- directory = path8.dirname(directory);
5637
+ directory = path9.dirname(directory);
5431
5638
  if (directory === root) return null;
5432
5639
  }
5433
5640
  return filePath;
@@ -5593,7 +5800,7 @@ async function replaceVersion(version2, packages) {
5593
5800
  return "jsr.json";
5594
5801
  })(),
5595
5802
  ...(packages ?? []).filter((pkg) => pkg.registries.includes("crates")).map(async (pkg) => {
5596
- const eco = new RustEcosystem(path8.resolve(pkg.path));
5803
+ const eco = new RustEcosystem(path9.resolve(pkg.path));
5597
5804
  try {
5598
5805
  await eco.writeVersion(version2);
5599
5806
  } catch (error) {
@@ -5602,7 +5809,7 @@ async function replaceVersion(version2, packages) {
5602
5809
  { cause: error }
5603
5810
  );
5604
5811
  }
5605
- return path8.join(pkg.path, "Cargo.toml");
5812
+ return path9.join(pkg.path, "Cargo.toml");
5606
5813
  })
5607
5814
  ]);
5608
5815
  return results.filter((v) => v);
@@ -5643,8 +5850,8 @@ function collectRegistries(ctx) {
5643
5850
  }
5644
5851
 
5645
5852
  // src/registry/crates.ts
5646
- import path9 from "node:path";
5647
- import { exec as exec3, NonZeroExitError } from "tinyexec";
5853
+ import path10 from "node:path";
5854
+ import { exec as exec4, NonZeroExitError } from "tinyexec";
5648
5855
 
5649
5856
  // src/registry/registry.ts
5650
5857
  var Registry = class {
@@ -5652,6 +5859,8 @@ var Registry = class {
5652
5859
  this.packageName = packageName;
5653
5860
  this.registry = registry;
5654
5861
  }
5862
+ async dryRunPublish(_manifestDir) {
5863
+ }
5655
5864
  };
5656
5865
 
5657
5866
  // src/registry/crates.ts
@@ -5682,7 +5891,7 @@ var CratesRegistry = class extends Registry {
5682
5891
  }
5683
5892
  async isInstalled() {
5684
5893
  try {
5685
- await exec3("cargo", ["--version"]);
5894
+ await exec4("cargo", ["--version"]);
5686
5895
  return true;
5687
5896
  } catch {
5688
5897
  return false;
@@ -5721,9 +5930,9 @@ var CratesRegistry = class extends Registry {
5721
5930
  try {
5722
5931
  const args = ["publish"];
5723
5932
  if (manifestDir) {
5724
- args.push("--manifest-path", path9.join(manifestDir, "Cargo.toml"));
5933
+ args.push("--manifest-path", path10.join(manifestDir, "Cargo.toml"));
5725
5934
  }
5726
- await exec3("cargo", args, { throwOnError: true });
5935
+ await exec4("cargo", args, { throwOnError: true });
5727
5936
  return true;
5728
5937
  } catch (error) {
5729
5938
  const stderr = error instanceof NonZeroExitError ? error.output?.stderr : void 0;
@@ -5732,6 +5941,20 @@ ${stderr}` : "Failed to run `cargo publish`";
5732
5941
  throw new CratesError(message, { cause: error });
5733
5942
  }
5734
5943
  }
5944
+ async dryRunPublish(manifestDir) {
5945
+ try {
5946
+ const args = ["publish", "--dry-run"];
5947
+ if (manifestDir) {
5948
+ args.push("--manifest-path", path10.join(manifestDir, "Cargo.toml"));
5949
+ }
5950
+ await exec4("cargo", args, { throwOnError: true });
5951
+ } catch (error) {
5952
+ const stderr = error instanceof NonZeroExitError ? error.output?.stderr : void 0;
5953
+ const message = stderr ? `Failed to run \`cargo publish --dry-run\`:
5954
+ ${stderr}` : "Failed to run `cargo publish --dry-run`";
5955
+ throw new CratesError(message, { cause: error });
5956
+ }
5957
+ }
5735
5958
  async isPublished() {
5736
5959
  try {
5737
5960
  const response = await fetch(
@@ -5818,84 +6041,11 @@ function createCratesPublishTask(packagePath) {
5818
6041
  var cratesAvailableCheckTasks = createCratesAvailableCheckTask();
5819
6042
  var cratesPublishTasks = createCratesPublishTask();
5820
6043
 
5821
- // src/tasks/jsr.ts
5822
- import process12 from "node:process";
5823
- import { ListrEnquirerPromptAdapter } from "@listr2/prompt-adapter-enquirer";
5824
- import npmCli from "@npmcli/promise-spawn";
6044
+ // src/tasks/dry-run-publish.ts
6045
+ import { ListrEnquirerPromptAdapter as ListrEnquirerPromptAdapter2 } from "@listr2/prompt-adapter-enquirer";
5825
6046
 
5826
6047
  // src/registry/jsr.ts
5827
- import { exec as exec4, NonZeroExitError as NonZeroExitError2 } from "tinyexec";
5828
-
5829
- // src/utils/db.ts
5830
- import { createCipheriv, createDecipheriv, createHash } from "node:crypto";
5831
- import { mkdirSync as mkdirSync5, readFileSync as readFileSync3, statSync, writeFileSync as writeFileSync4 } from "node:fs";
5832
- import path10 from "node:path";
5833
- var a = "aes-256-cbc";
5834
- var n = statSync(import.meta.dirname);
5835
- var k = `${n.rdev}${n.birthtimeMs}${n.nlink}${n.gid}`;
5836
- var l = createHash("md5").update(k).digest();
5837
- function e(e2, f) {
5838
- const c = createCipheriv(a, createHash("sha-256").update(f).digest(), l);
5839
- return c.update(e2, "utf8", "hex") + c.final("hex");
5840
- }
5841
- function d(g, h) {
5842
- const d2 = createDecipheriv(a, createHash("sha-256").update(h).digest(), l);
5843
- return d2.update(g, "hex", "utf8") + d2.final("utf8");
5844
- }
5845
- var Db = class {
5846
- constructor() {
5847
- __publicField(this, "path", path10.resolve(import.meta.dirname, ".pubm"));
5848
- try {
5849
- if (!statSync(this.path).isDirectory()) {
5850
- mkdirSync5(this.path);
5851
- }
5852
- } catch {
5853
- try {
5854
- mkdirSync5(this.path);
5855
- } catch (error) {
5856
- throw new Error(
5857
- `Failed to create token storage directory at '${this.path}': ${error instanceof Error ? error.message : error}`
5858
- );
5859
- }
5860
- }
5861
- }
5862
- set(field, value) {
5863
- try {
5864
- writeFileSync4(
5865
- path10.resolve(
5866
- this.path,
5867
- Buffer.from(e(field, field)).toString("base64")
5868
- ),
5869
- Buffer.from(e(`${value}`, field)),
5870
- { encoding: "binary" }
5871
- );
5872
- } catch (error) {
5873
- throw new Error(
5874
- `Failed to save token for '${field}': ${error instanceof Error ? error.message : error}`
5875
- );
5876
- }
5877
- }
5878
- get(field) {
5879
- const filePath = path10.resolve(
5880
- this.path,
5881
- Buffer.from(e(field, field)).toString("base64")
5882
- );
5883
- let raw;
5884
- try {
5885
- raw = readFileSync3(filePath);
5886
- } catch {
5887
- return null;
5888
- }
5889
- try {
5890
- return d(Buffer.from(raw).toString(), field);
5891
- } catch {
5892
- console.warn(
5893
- `Stored token for '${field}' appears corrupted. It will be re-requested.`
5894
- );
5895
- return null;
5896
- }
5897
- }
5898
- };
6048
+ import { exec as exec5, NonZeroExitError as NonZeroExitError2 } from "tinyexec";
5899
6049
 
5900
6050
  // src/utils/package-name.ts
5901
6051
  import { builtinModules } from "node:module";
@@ -5962,7 +6112,7 @@ var JsrRegisry = class extends Registry {
5962
6112
  this.client = new JsrClient(getApiEndpoint(this.registry));
5963
6113
  }
5964
6114
  async jsr(args) {
5965
- const { stdout } = await exec4("jsr", args, { throwOnError: true });
6115
+ const { stdout } = await exec5("jsr", args, { throwOnError: true });
5966
6116
  return stdout;
5967
6117
  }
5968
6118
  async isInstalled() {
@@ -5978,7 +6128,7 @@ var JsrRegisry = class extends Registry {
5978
6128
  }
5979
6129
  async ping() {
5980
6130
  try {
5981
- const { stdout } = await exec4(
6131
+ const { stdout } = await exec5(
5982
6132
  "ping",
5983
6133
  [new URL(this.registry).hostname, "-c", "1"],
5984
6134
  { throwOnError: true }
@@ -5993,7 +6143,7 @@ var JsrRegisry = class extends Registry {
5993
6143
  }
5994
6144
  async publish() {
5995
6145
  try {
5996
- await exec4(
6146
+ await exec5(
5997
6147
  "jsr",
5998
6148
  ["publish", "--allow-dirty", "--token", `${JsrClient.token}`],
5999
6149
  {
@@ -6022,6 +6172,25 @@ ${stderr}` : ""}`,
6022
6172
  );
6023
6173
  }
6024
6174
  }
6175
+ async dryRunPublish() {
6176
+ try {
6177
+ await exec5(
6178
+ "jsr",
6179
+ [
6180
+ "publish",
6181
+ "--dry-run",
6182
+ "--allow-dirty",
6183
+ "--token",
6184
+ `${JsrClient.token}`
6185
+ ],
6186
+ { throwOnError: true }
6187
+ );
6188
+ } catch (error) {
6189
+ throw new JsrError("Failed to run `jsr publish --dry-run`", {
6190
+ cause: error
6191
+ });
6192
+ }
6193
+ }
6025
6194
  async version() {
6026
6195
  return await this.jsr(["--version"]);
6027
6196
  }
@@ -6263,7 +6432,7 @@ async function jsrRegistry() {
6263
6432
  }
6264
6433
 
6265
6434
  // src/registry/npm.ts
6266
- import { exec as exec5, NonZeroExitError as NonZeroExitError3 } from "tinyexec";
6435
+ import { exec as exec6, NonZeroExitError as NonZeroExitError3 } from "tinyexec";
6267
6436
  var NpmError = class extends AbstractError {
6268
6437
  constructor() {
6269
6438
  super(...arguments);
@@ -6276,7 +6445,7 @@ var NpmRegistry = class extends Registry {
6276
6445
  __publicField(this, "registry", "https://registry.npmjs.org");
6277
6446
  }
6278
6447
  async npm(args) {
6279
- const { stdout } = await exec5("npm", args, { throwOnError: true });
6448
+ const { stdout } = await exec6("npm", args, { throwOnError: true });
6280
6449
  return stdout;
6281
6450
  }
6282
6451
  async isInstalled() {
@@ -6387,7 +6556,7 @@ var NpmRegistry = class extends Registry {
6387
6556
  }
6388
6557
  async ping() {
6389
6558
  try {
6390
- await exec5("npm", ["ping"], { throwOnError: true });
6559
+ await exec6("npm", ["ping"], { throwOnError: true });
6391
6560
  return true;
6392
6561
  } catch (error) {
6393
6562
  throw new NpmError("Failed to run `npm ping`", { cause: error });
@@ -6416,6 +6585,24 @@ var NpmRegistry = class extends Registry {
6416
6585
  throw this.classifyPublishError(error);
6417
6586
  }
6418
6587
  }
6588
+ async dryRunPublish() {
6589
+ 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
6594
+ });
6595
+ }
6596
+ }
6597
+ async twoFactorAuthMode() {
6598
+ try {
6599
+ const output = await this.npm(["profile", "get", "--json"]);
6600
+ const profile = JSON.parse(output);
6601
+ return profile?.tfa?.mode ?? null;
6602
+ } catch {
6603
+ return null;
6604
+ }
6605
+ }
6419
6606
  async isPackageNameAvaliable() {
6420
6607
  return isValidPackageName(this.packageName);
6421
6608
  }
@@ -6452,7 +6639,80 @@ async function npmRegistry() {
6452
6639
  return new NpmRegistry(packageJson.name);
6453
6640
  }
6454
6641
 
6642
+ // src/tasks/dry-run-publish.ts
6643
+ var AUTH_ERROR_PATTERNS = [
6644
+ /401/i,
6645
+ /403/i,
6646
+ /unauthorized/i,
6647
+ /forbidden/i,
6648
+ /invalid.token/i,
6649
+ /eotp/i
6650
+ ];
6651
+ function isAuthError(error) {
6652
+ const message = error instanceof Error ? error.message : String(error);
6653
+ return AUTH_ERROR_PATTERNS.some((pattern) => pattern.test(message));
6654
+ }
6655
+ async function withTokenRetry(registryKey, task, action) {
6656
+ try {
6657
+ await action();
6658
+ } catch (error) {
6659
+ if (!isAuthError(error)) throw error;
6660
+ const config = TOKEN_CONFIG[registryKey];
6661
+ if (!config) throw error;
6662
+ task.output = `Auth failed. Re-enter ${config.promptLabel}`;
6663
+ const newToken = await task.prompt(ListrEnquirerPromptAdapter2).run({
6664
+ type: "password",
6665
+ message: `Re-enter ${config.promptLabel}`
6666
+ });
6667
+ new Db().set(config.dbKey, newToken);
6668
+ process.env[config.envVar] = newToken;
6669
+ await action();
6670
+ }
6671
+ }
6672
+ var npmDryRunPublishTask = {
6673
+ title: "Dry-run npm publish",
6674
+ task: async (_, task) => {
6675
+ task.output = "Running npm publish --dry-run...";
6676
+ await withTokenRetry("npm", task, async () => {
6677
+ const npm = await npmRegistry();
6678
+ await npm.dryRunPublish();
6679
+ });
6680
+ }
6681
+ };
6682
+ var jsrDryRunPublishTask = {
6683
+ title: "Dry-run jsr publish",
6684
+ task: async (_, task) => {
6685
+ task.output = "Running jsr publish --dry-run...";
6686
+ await withTokenRetry("jsr", task, async () => {
6687
+ const jsr = await jsrRegistry();
6688
+ await jsr.dryRunPublish();
6689
+ });
6690
+ }
6691
+ };
6692
+ async function getCrateName2(packagePath) {
6693
+ const eco = new RustEcosystem(packagePath ?? process.cwd());
6694
+ return await eco.packageName();
6695
+ }
6696
+ function createCratesDryRunPublishTask(packagePath) {
6697
+ const label = packagePath ? ` (${packagePath})` : "";
6698
+ return {
6699
+ title: `Dry-run crates.io publish${label}`,
6700
+ task: async (_, task) => {
6701
+ task.output = "Running cargo publish --dry-run...";
6702
+ await withTokenRetry("crates", task, async () => {
6703
+ const packageName = await getCrateName2(packagePath);
6704
+ const registry = new CratesRegistry(packageName);
6705
+ await registry.dryRunPublish(packagePath);
6706
+ });
6707
+ }
6708
+ };
6709
+ }
6710
+ var cratesDryRunPublishTask = createCratesDryRunPublishTask();
6711
+
6455
6712
  // src/tasks/jsr.ts
6713
+ import process12 from "node:process";
6714
+ import { ListrEnquirerPromptAdapter as ListrEnquirerPromptAdapter3 } from "@listr2/prompt-adapter-enquirer";
6715
+ import npmCli from "@npmcli/promise-spawn";
6456
6716
  var { open } = npmCli;
6457
6717
  var JsrAvailableError = class extends AbstractError {
6458
6718
  constructor(message, { cause } = {}) {
@@ -6480,7 +6740,7 @@ var jsrAvailableCheckTasks = {
6480
6740
  if (ctx.promptEnabled) {
6481
6741
  const maxAttempts = 3;
6482
6742
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
6483
- JsrClient.token = await task.prompt(ListrEnquirerPromptAdapter).run({
6743
+ JsrClient.token = await task.prompt(ListrEnquirerPromptAdapter3).run({
6484
6744
  type: "password",
6485
6745
  message: `Please enter the jsr ${color.bold("API token")}${attempt > 1 ? ` (attempt ${attempt}/${maxAttempts})` : ""}`,
6486
6746
  footer: `
@@ -6530,7 +6790,7 @@ Generate a token from ${color.bold(link2("jsr.io", "https://jsr.io/account/token
6530
6790
  )
6531
6791
  )).filter((v) => v !== null);
6532
6792
  if (searchResults.length > 0) {
6533
- jsrName = await task.prompt(ListrEnquirerPromptAdapter).run({
6793
+ jsrName = await task.prompt(ListrEnquirerPromptAdapter3).run({
6534
6794
  type: "select",
6535
6795
  message: "Is there a scoped package you want to publish in the already published list?",
6536
6796
  choices: [
@@ -6548,7 +6808,7 @@ Generate a token from ${color.bold(link2("jsr.io", "https://jsr.io/account/token
6548
6808
  }
6549
6809
  const userName = await new Git().userName();
6550
6810
  task.output = "Select the scope of the package to publish";
6551
- jsrName = await task.prompt(ListrEnquirerPromptAdapter).run({
6811
+ jsrName = await task.prompt(ListrEnquirerPromptAdapter3).run({
6552
6812
  type: "select",
6553
6813
  message: "jsr.json does not exist, and the package name is not scoped. Please select a scope for the 'jsr' package",
6554
6814
  choices: [
@@ -6576,7 +6836,7 @@ Generate a token from ${color.bold(link2("jsr.io", "https://jsr.io/account/token
6576
6836
  });
6577
6837
  if (jsrName === "specify") {
6578
6838
  while (!isScopedPackage(jsrName)) {
6579
- jsrName = await task.prompt(ListrEnquirerPromptAdapter).run({
6839
+ jsrName = await task.prompt(ListrEnquirerPromptAdapter3).run({
6580
6840
  type: "input",
6581
6841
  message: "Package name"
6582
6842
  });
@@ -6644,7 +6904,7 @@ var jsrPublishTasks = {
6644
6904
  ${urls.map((url) => ` ${color.cyan(url)}`).join("\n")}`;
6645
6905
  open(urls[0]);
6646
6906
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
6647
- await task.prompt(ListrEnquirerPromptAdapter).run({
6907
+ await task.prompt(ListrEnquirerPromptAdapter3).run({
6648
6908
  type: "input",
6649
6909
  message: `Press ${color.bold("enter")} after creating the package on jsr.io${attempt > 1 ? ` (attempt ${attempt}/${maxAttempts})` : ""}`
6650
6910
  });
@@ -6673,7 +6933,7 @@ ${jsr.packageCreationUrls.join("\n")}`
6673
6933
  // src/tasks/npm.ts
6674
6934
  import { spawn } from "node:child_process";
6675
6935
  import process13 from "node:process";
6676
- import { ListrEnquirerPromptAdapter as ListrEnquirerPromptAdapter2 } from "@listr2/prompt-adapter-enquirer";
6936
+ import { ListrEnquirerPromptAdapter as ListrEnquirerPromptAdapter4 } from "@listr2/prompt-adapter-enquirer";
6677
6937
  import npmCli2 from "@npmcli/promise-spawn";
6678
6938
  var { open: open2 } = npmCli2;
6679
6939
  var NpmAvailableError = class extends AbstractError {
@@ -6685,7 +6945,6 @@ var NpmAvailableError = class extends AbstractError {
6685
6945
  };
6686
6946
  var npmAvailableCheckTasks = {
6687
6947
  title: "Checking npm avaliable for publising",
6688
- skip: (ctx) => !!ctx.preview,
6689
6948
  task: async (ctx, task) => {
6690
6949
  const npm = await npmRegistry();
6691
6950
  if (!await npm.isLoggedIn()) {
@@ -6748,6 +7007,14 @@ var npmAvailableCheckTasks = {
6748
7007
  More information: ${link2("npm naming rules", "https://github.com/npm/validate-npm-package-name?tab=readme-ov-file#naming-rules")}`
6749
7008
  );
6750
7009
  }
7010
+ if (!ctx.promptEnabled) {
7011
+ const tfaMode = await npm.twoFactorAuthMode();
7012
+ if (tfaMode === "auth-and-writes") {
7013
+ throw new NpmAvailableError(
7014
+ `npm account has 2FA enabled for writes (auth-and-writes). CI publish will fail with EOTP. Use an automation token or configure granular access token at https://www.npmjs.com/package/${npm.packageName}/access`
7015
+ );
7016
+ }
7017
+ }
6751
7018
  }
6752
7019
  };
6753
7020
  var npmPublishTasks = {
@@ -6763,7 +7030,7 @@ var npmPublishTasks = {
6763
7030
  const maxAttempts = 3;
6764
7031
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
6765
7032
  result = await npm.publish(
6766
- await task.prompt(ListrEnquirerPromptAdapter2).run({
7033
+ await task.prompt(ListrEnquirerPromptAdapter4).run({
6767
7034
  type: "password",
6768
7035
  message: `npm OTP code${attempt > 1 ? ` (attempt ${attempt}/${maxAttempts})` : ""}`
6769
7036
  })
@@ -6798,7 +7065,7 @@ var npmPublishTasks = {
6798
7065
  };
6799
7066
 
6800
7067
  // src/tasks/prerequisites-check.ts
6801
- import { ListrEnquirerPromptAdapter as ListrEnquirerPromptAdapter3 } from "@listr2/prompt-adapter-enquirer";
7068
+ import { ListrEnquirerPromptAdapter as ListrEnquirerPromptAdapter5 } from "@listr2/prompt-adapter-enquirer";
6802
7069
  var PrerequisitesCheckError = class extends AbstractError {
6803
7070
  constructor(message, { cause } = {}) {
6804
7071
  super(message, { cause });
@@ -6818,7 +7085,7 @@ var prerequisitesCheckTask = (options) => {
6818
7085
  title: "Verifying current branch is a release branch",
6819
7086
  task: async (ctx, task) => {
6820
7087
  if (await git.branch() !== ctx.branch) {
6821
- const swtichBranch = await task.prompt(ListrEnquirerPromptAdapter3).run({
7088
+ const swtichBranch = await task.prompt(ListrEnquirerPromptAdapter5).run({
6822
7089
  type: "toggle",
6823
7090
  message: `${warningBadge} The current HEAD branch is not the release target branch. Do you want to switch branch to ${ctx.branch}?`,
6824
7091
  enabled: "Yes",
@@ -6840,7 +7107,7 @@ var prerequisitesCheckTask = (options) => {
6840
7107
  task: async (_2, task) => {
6841
7108
  task.output = "Checking for updates with `git fetch`";
6842
7109
  if ((await git.dryFetch()).trim()) {
6843
- const fetch2 = await task.prompt(ListrEnquirerPromptAdapter3).run({
7110
+ const fetch2 = await task.prompt(ListrEnquirerPromptAdapter5).run({
6844
7111
  type: "toggle",
6845
7112
  message: `${warningBadge} Local history is outdated. Do you want to run \`git fetch\`?`,
6846
7113
  enabled: "Yes",
@@ -6857,7 +7124,7 @@ var prerequisitesCheckTask = (options) => {
6857
7124
  }
6858
7125
  task.output = "Checking for updates with `git pull`";
6859
7126
  if (await git.revisionDiffsCount()) {
6860
- const pull = await task.prompt(ListrEnquirerPromptAdapter3).run({
7127
+ const pull = await task.prompt(ListrEnquirerPromptAdapter5).run({
6861
7128
  type: "toggle",
6862
7129
  message: `${warningBadge} Local history is outdated. Do you want to run \`git pull\`?`,
6863
7130
  enabled: "Yes",
@@ -6879,7 +7146,7 @@ var prerequisitesCheckTask = (options) => {
6879
7146
  task: async (ctx, task) => {
6880
7147
  if (await git.status()) {
6881
7148
  task.output = "Local working tree is not clean.";
6882
- if (!await task.prompt(ListrEnquirerPromptAdapter3).run({
7149
+ if (!await task.prompt(ListrEnquirerPromptAdapter5).run({
6883
7150
  type: "toggle",
6884
7151
  message: `${warningBadge} Local working tree is not clean. Do you want to skip?`,
6885
7152
  enabled: "Yes",
@@ -6904,7 +7171,7 @@ var prerequisitesCheckTask = (options) => {
6904
7171
  return void 0;
6905
7172
  }
6906
7173
  if ((await git.commits(latestTag, "HEAD")).length <= 0) {
6907
- if (!await task.prompt(ListrEnquirerPromptAdapter3).run({
7174
+ if (!await task.prompt(ListrEnquirerPromptAdapter5).run({
6908
7175
  type: "toggle",
6909
7176
  message: `${warningBadge} No commits exist from the latest tag. Do you want to skip?`,
6910
7177
  enabled: "Yes",
@@ -6922,7 +7189,7 @@ var prerequisitesCheckTask = (options) => {
6922
7189
  task: async (ctx, task) => {
6923
7190
  const gitTag = `v${ctx.version}`;
6924
7191
  if (await git.checkTagExist(gitTag)) {
6925
- const deleteTag = await task.prompt(ListrEnquirerPromptAdapter3).run({
7192
+ const deleteTag = await task.prompt(ListrEnquirerPromptAdapter5).run({
6926
7193
  type: "toggle",
6927
7194
  message: `${warningBadge} The Git tag '${gitTag}' already exists. Do you want to delete tag?`,
6928
7195
  enabled: "Yes",
@@ -6944,13 +7211,13 @@ var prerequisitesCheckTask = (options) => {
6944
7211
  };
6945
7212
 
6946
7213
  // src/tasks/required-conditions-check.ts
6947
- import { ListrEnquirerPromptAdapter as ListrEnquirerPromptAdapter4 } from "@listr2/prompt-adapter-enquirer";
7214
+ import { ListrEnquirerPromptAdapter as ListrEnquirerPromptAdapter6 } from "@listr2/prompt-adapter-enquirer";
6948
7215
 
6949
7216
  // src/registry/custom-registry.ts
6950
- import { exec as exec6 } from "tinyexec";
7217
+ import { exec as exec7 } from "tinyexec";
6951
7218
  var CustomRegistry = class extends NpmRegistry {
6952
7219
  async npm(args) {
6953
- const { stdout } = await exec6(
7220
+ const { stdout } = await exec7(
6954
7221
  "npm",
6955
7222
  args.concat("--registry", this.registry),
6956
7223
  { throwOnError: true }
@@ -7052,7 +7319,7 @@ var requiredConditionsCheckTask = (options) => createListr({
7052
7319
  task: async (_3, task) => {
7053
7320
  const jsr = await jsrRegistry();
7054
7321
  if (!await jsr.isInstalled()) {
7055
- const install = await task.prompt(ListrEnquirerPromptAdapter4).run({
7322
+ const install = await task.prompt(ListrEnquirerPromptAdapter6).run({
7056
7323
  type: "toggle",
7057
7324
  message: `${warningBadge} jsr is not installed. Do you want to install jsr?`,
7058
7325
  enabled: "Yes",
@@ -7186,14 +7453,66 @@ async function collectPublishTasks(ctx) {
7186
7453
  }
7187
7454
  return collectRegistries(ctx).map(registryTask);
7188
7455
  }
7456
+ function dryRunRegistryTask(registry) {
7457
+ switch (registry) {
7458
+ case "npm":
7459
+ return npmDryRunPublishTask;
7460
+ case "jsr":
7461
+ return jsrDryRunPublishTask;
7462
+ case "crates":
7463
+ return cratesDryRunPublishTask;
7464
+ default:
7465
+ return npmDryRunPublishTask;
7466
+ }
7467
+ }
7468
+ async function collectDryRunPublishTasks(ctx) {
7469
+ if (ctx.packages?.length) {
7470
+ const nonCratesTasks = ctx.packages.flatMap(
7471
+ (pkg) => pkg.registries.filter((reg) => reg !== "crates").map((reg) => dryRunRegistryTask(reg))
7472
+ );
7473
+ const cratesPaths = ctx.packages.filter((pkg) => pkg.registries.includes("crates")).map((pkg) => pkg.path);
7474
+ if (cratesPaths.length === 0) {
7475
+ return nonCratesTasks;
7476
+ }
7477
+ const sortedPaths = await sortCratesByDependencyOrder(cratesPaths);
7478
+ const sequentialCratesTask = {
7479
+ title: "Dry-run crates.io publish (sequential)",
7480
+ task: (_ctx, task) => task.newListr(
7481
+ sortedPaths.map((p) => createCratesDryRunPublishTask(p)),
7482
+ { concurrent: false }
7483
+ )
7484
+ };
7485
+ return [...nonCratesTasks, sequentialCratesTask];
7486
+ }
7487
+ return collectRegistries(ctx).map(dryRunRegistryTask);
7488
+ }
7189
7489
  async function run(options) {
7190
7490
  const ctx = {
7191
7491
  ...options,
7192
7492
  promptEnabled: !isCI2 && process14.stdin.isTTY
7193
7493
  };
7494
+ let cleanupEnv;
7194
7495
  try {
7195
7496
  if (options.contents) process14.chdir(options.contents);
7196
- if (!options.publishOnly) {
7497
+ if (options.preflight) {
7498
+ await createListr({
7499
+ title: "Collecting registry tokens",
7500
+ task: async (ctx2, task) => {
7501
+ const registries2 = collectRegistries(ctx2);
7502
+ const tokens = await collectTokens(registries2, task);
7503
+ await promptGhSecretsSync(tokens, task);
7504
+ cleanupEnv = injectTokensToEnv(tokens);
7505
+ ctx2.promptEnabled = false;
7506
+ }
7507
+ }).run(ctx);
7508
+ await prerequisitesCheckTask({
7509
+ skip: options.skipPrerequisitesCheck
7510
+ }).run(ctx);
7511
+ await requiredConditionsCheckTask({
7512
+ skip: options.skipConditionsCheck
7513
+ }).run(ctx);
7514
+ }
7515
+ if (!options.publishOnly && !options.preflight) {
7197
7516
  await prerequisitesCheckTask({
7198
7517
  skip: options.skipPrerequisitesCheck
7199
7518
  }).run(ctx);
@@ -7207,14 +7526,14 @@ async function run(options) {
7207
7526
  task: async (ctx2, parentTask) => parentTask.newListr(await collectPublishTasks(ctx2), {
7208
7527
  concurrent: true
7209
7528
  })
7210
- } : [
7529
+ } : options.preflight ? [
7211
7530
  {
7212
7531
  skip: options.skipTests,
7213
7532
  title: "Running tests",
7214
7533
  task: async (ctx2) => {
7215
7534
  const packageManager = await getPackageManager();
7216
7535
  try {
7217
- await exec7(packageManager, ["run", ctx2.testScript], {
7536
+ await exec8(packageManager, ["run", ctx2.testScript], {
7218
7537
  throwOnError: true
7219
7538
  });
7220
7539
  } catch (error) {
@@ -7231,7 +7550,48 @@ async function run(options) {
7231
7550
  task: async (ctx2) => {
7232
7551
  const packageManager = await getPackageManager();
7233
7552
  try {
7234
- await exec7(packageManager, ["run", ctx2.buildScript], {
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
+ ] : [
7571
+ {
7572
+ skip: options.skipTests,
7573
+ title: "Running tests",
7574
+ task: async (ctx2) => {
7575
+ const packageManager = await getPackageManager();
7576
+ try {
7577
+ await exec8(packageManager, ["run", ctx2.testScript], {
7578
+ throwOnError: true
7579
+ });
7580
+ } catch (error) {
7581
+ throw new AbstractError(
7582
+ `Test script '${ctx2.testScript}' failed. Run \`${packageManager} run ${ctx2.testScript}\` locally to see full output.`,
7583
+ { cause: error }
7584
+ );
7585
+ }
7586
+ }
7587
+ },
7588
+ {
7589
+ skip: options.skipBuild,
7590
+ title: "Building the project",
7591
+ task: async (ctx2) => {
7592
+ const packageManager = await getPackageManager();
7593
+ try {
7594
+ await exec8(packageManager, ["run", ctx2.buildScript], {
7235
7595
  throwOnError: true
7236
7596
  });
7237
7597
  } catch (error) {
@@ -7356,13 +7716,24 @@ ${repositoryUrl}/compare/${lastRev}...${latestTag}`;
7356
7716
  parts.push(`${color.bold(name)} on ${color.red("crates.io")}`);
7357
7717
  }
7358
7718
  }
7359
- console.log(
7360
- `
7719
+ if (options.preflight) {
7720
+ cleanupEnv?.();
7721
+ console.log(
7722
+ `
7723
+
7724
+ \u2705 Preflight check passed. CI publish should succeed for ${parts.join(", ")}.
7725
+ `
7726
+ );
7727
+ } else {
7728
+ console.log(
7729
+ `
7361
7730
 
7362
7731
  \u{1F680} Successfully published ${parts.join(", ")} ${color.blueBright(`v${ctx.version}`)} \u{1F680}
7363
7732
  `
7364
- );
7733
+ );
7734
+ }
7365
7735
  } catch (e2) {
7736
+ cleanupEnv?.();
7366
7737
  consoleError(e2);
7367
7738
  await rollback();
7368
7739
  process14.exit(1);
@@ -7411,7 +7782,7 @@ async function pubm(options) {
7411
7782
  }
7412
7783
 
7413
7784
  // src/tasks/required-missing-information.ts
7414
- import { ListrEnquirerPromptAdapter as ListrEnquirerPromptAdapter5 } from "@listr2/prompt-adapter-enquirer";
7785
+ import { ListrEnquirerPromptAdapter as ListrEnquirerPromptAdapter7 } from "@listr2/prompt-adapter-enquirer";
7415
7786
  import semver2 from "semver";
7416
7787
  var { RELEASE_TYPES, SemVer: SemVer2, prerelease: prerelease2 } = semver2;
7417
7788
  var requiredMissingInformationTasks = (options) => createListr({
@@ -7423,7 +7794,7 @@ var requiredMissingInformationTasks = (options) => createListr({
7423
7794
  skip: (ctx) => !!ctx.version,
7424
7795
  task: async (ctx, task) => {
7425
7796
  const currentVersion = await version();
7426
- let nextVersion = await task.prompt(ListrEnquirerPromptAdapter5).run({
7797
+ let nextVersion = await task.prompt(ListrEnquirerPromptAdapter7).run({
7427
7798
  type: "select",
7428
7799
  message: "Select SemVer increment or specify new version",
7429
7800
  choices: RELEASE_TYPES.map((releaseType) => {
@@ -7438,7 +7809,7 @@ var requiredMissingInformationTasks = (options) => createListr({
7438
7809
  name: "version"
7439
7810
  });
7440
7811
  if (nextVersion === "specify") {
7441
- nextVersion = await task.prompt(ListrEnquirerPromptAdapter5).run({
7812
+ nextVersion = await task.prompt(ListrEnquirerPromptAdapter7).run({
7442
7813
  type: "input",
7443
7814
  message: "Version",
7444
7815
  name: "version"
@@ -7460,7 +7831,7 @@ var requiredMissingInformationTasks = (options) => createListr({
7460
7831
  )
7461
7832
  ].filter((tag2) => tag2 !== defaultOptions.tag);
7462
7833
  if (distTags.length <= 0) distTags.push("next");
7463
- let tag = await task.prompt(ListrEnquirerPromptAdapter5).run({
7834
+ let tag = await task.prompt(ListrEnquirerPromptAdapter7).run({
7464
7835
  type: "select",
7465
7836
  message: "Select the tag for this pre-release version in npm",
7466
7837
  choices: distTags.map((distTag) => ({
@@ -7472,7 +7843,7 @@ var requiredMissingInformationTasks = (options) => createListr({
7472
7843
  name: "tag"
7473
7844
  });
7474
7845
  if (tag === "specify") {
7475
- tag = await task.prompt(ListrEnquirerPromptAdapter5).run({
7846
+ tag = await task.prompt(ListrEnquirerPromptAdapter7).run({
7476
7847
  type: "input",
7477
7848
  message: "Tag",
7478
7849
  name: "tag"
@@ -7558,6 +7929,11 @@ var publishOptions = [
7558
7929
  description: "Run only publish task for latest tag",
7559
7930
  options: { type: Boolean }
7560
7931
  },
7932
+ {
7933
+ rawName: "--preflight",
7934
+ description: "Simulate CI publish locally (dry-run with token-based auth)",
7935
+ options: { type: Boolean }
7936
+ },
7561
7937
  {
7562
7938
  rawName: "-t, --tag <name>",
7563
7939
  description: "Publish under a specific dist-tag",
@@ -7588,7 +7964,8 @@ function resolveCliOptions(options) {
7588
7964
  skipBuild: !options.build,
7589
7965
  registries: options.registry?.split(","),
7590
7966
  skipPrerequisitesCheck: !options.preCheck,
7591
- skipConditionsCheck: !options.conditionCheck
7967
+ skipConditionsCheck: !options.conditionCheck,
7968
+ preflight: options.preflight
7592
7969
  };
7593
7970
  }
7594
7971
  var cli = cac("pubm");
@@ -7600,6 +7977,7 @@ registerSnapshotCommand(cli);
7600
7977
  registerInitCommand(cli);
7601
7978
  registerMigrateCommand(cli);
7602
7979
  registerUpdateCommand(cli);
7980
+ registerSecretsCommand(cli);
7603
7981
  var defaultCmd = cli.command("[version]", "Publish packages to registries");
7604
7982
  for (const option of publishOptions) {
7605
7983
  defaultCmd.option(option.rawName, option.description, option.options);
@@ -7615,7 +7993,9 @@ defaultCmd.action(
7615
7993
  tag: options.tag
7616
7994
  };
7617
7995
  try {
7618
- if (isCI3) {
7996
+ if (options.preflight) {
7997
+ context.version = nextVersion || "0.0.0-preflight";
7998
+ } else if (isCI3) {
7619
7999
  if (options.publishOnly) {
7620
8000
  const git = new Git();
7621
8001
  const latestVersion = (await git.latestTag())?.slice(1);