pubm 0.2.1 → 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/dist/index.cjs CHANGED
@@ -4486,7 +4486,7 @@ var Listr = (_a23 = class {
4486
4486
  // src/tasks/runner.ts
4487
4487
  var import_semver3 = __toESM(require("semver"), 1);
4488
4488
  var import_std_env = require("std-env");
4489
- var import_tinyexec6 = require("tinyexec");
4489
+ var import_tinyexec7 = require("tinyexec");
4490
4490
 
4491
4491
  // src/error.ts
4492
4492
  var AbstractError = class extends Error {
@@ -5207,8 +5207,131 @@ function collectRegistries(ctx) {
5207
5207
  return ctx.registries;
5208
5208
  }
5209
5209
 
5210
- // src/registry/crates.ts
5210
+ // src/utils/db.ts
5211
+ var import_node_crypto = require("crypto");
5212
+ var import_node_fs = require("fs");
5211
5213
  var import_node_path4 = __toESM(require("path"), 1);
5214
+ var import_meta = {};
5215
+ var a = "aes-256-cbc";
5216
+ var n = (0, import_node_fs.statSync)(import_meta.dirname);
5217
+ var k = `${n.rdev}${n.birthtimeMs}${n.nlink}${n.gid}`;
5218
+ var l = (0, import_node_crypto.createHash)("md5").update(k).digest();
5219
+ function e(e2, f) {
5220
+ const c = (0, import_node_crypto.createCipheriv)(a, (0, import_node_crypto.createHash)("sha-256").update(f).digest(), l);
5221
+ return c.update(e2, "utf8", "hex") + c.final("hex");
5222
+ }
5223
+ function d(g, h) {
5224
+ const d2 = (0, import_node_crypto.createDecipheriv)(a, (0, import_node_crypto.createHash)("sha-256").update(h).digest(), l);
5225
+ return d2.update(g, "hex", "utf8") + d2.final("utf8");
5226
+ }
5227
+ var Db = class {
5228
+ constructor() {
5229
+ __publicField(this, "path", import_node_path4.default.resolve(import_meta.dirname, ".pubm"));
5230
+ try {
5231
+ if (!(0, import_node_fs.statSync)(this.path).isDirectory()) {
5232
+ (0, import_node_fs.mkdirSync)(this.path);
5233
+ }
5234
+ } catch {
5235
+ try {
5236
+ (0, import_node_fs.mkdirSync)(this.path);
5237
+ } catch (error) {
5238
+ throw new Error(
5239
+ `Failed to create token storage directory at '${this.path}': ${error instanceof Error ? error.message : error}`
5240
+ );
5241
+ }
5242
+ }
5243
+ }
5244
+ set(field, value) {
5245
+ try {
5246
+ (0, import_node_fs.writeFileSync)(
5247
+ import_node_path4.default.resolve(
5248
+ this.path,
5249
+ Buffer.from(e(field, field)).toString("base64")
5250
+ ),
5251
+ Buffer.from(e(`${value}`, field)),
5252
+ { encoding: "binary" }
5253
+ );
5254
+ } catch (error) {
5255
+ throw new Error(
5256
+ `Failed to save token for '${field}': ${error instanceof Error ? error.message : error}`
5257
+ );
5258
+ }
5259
+ }
5260
+ get(field) {
5261
+ const filePath = import_node_path4.default.resolve(
5262
+ this.path,
5263
+ Buffer.from(e(field, field)).toString("base64")
5264
+ );
5265
+ let raw;
5266
+ try {
5267
+ raw = (0, import_node_fs.readFileSync)(filePath);
5268
+ } catch {
5269
+ return null;
5270
+ }
5271
+ try {
5272
+ return d(Buffer.from(raw).toString(), field);
5273
+ } catch {
5274
+ console.warn(
5275
+ `Stored token for '${field}' appears corrupted. It will be re-requested.`
5276
+ );
5277
+ return null;
5278
+ }
5279
+ }
5280
+ };
5281
+
5282
+ // src/utils/token.ts
5283
+ var TOKEN_CONFIG = {
5284
+ npm: {
5285
+ envVar: "NODE_AUTH_TOKEN",
5286
+ dbKey: "npm-token",
5287
+ ghSecretName: "NODE_AUTH_TOKEN",
5288
+ promptLabel: "npm access token"
5289
+ },
5290
+ jsr: {
5291
+ envVar: "JSR_TOKEN",
5292
+ dbKey: "jsr-token",
5293
+ ghSecretName: "JSR_TOKEN",
5294
+ promptLabel: "jsr API token"
5295
+ },
5296
+ crates: {
5297
+ envVar: "CARGO_REGISTRY_TOKEN",
5298
+ dbKey: "cargo-token",
5299
+ ghSecretName: "CARGO_REGISTRY_TOKEN",
5300
+ promptLabel: "crates.io API token"
5301
+ }
5302
+ };
5303
+ function loadTokensFromDb(registries) {
5304
+ const db = new Db();
5305
+ const tokens = {};
5306
+ for (const registry of registries) {
5307
+ const config = TOKEN_CONFIG[registry];
5308
+ if (!config) continue;
5309
+ const token = db.get(config.dbKey);
5310
+ if (token) tokens[registry] = token;
5311
+ }
5312
+ return tokens;
5313
+ }
5314
+ function injectTokensToEnv(tokens) {
5315
+ const originals = {};
5316
+ for (const [registry, token] of Object.entries(tokens)) {
5317
+ const config = TOKEN_CONFIG[registry];
5318
+ if (!config) continue;
5319
+ originals[config.envVar] = process.env[config.envVar];
5320
+ process.env[config.envVar] = token;
5321
+ }
5322
+ return () => {
5323
+ for (const [envVar, original] of Object.entries(originals)) {
5324
+ if (original === void 0) {
5325
+ delete process.env[envVar];
5326
+ } else {
5327
+ process.env[envVar] = original;
5328
+ }
5329
+ }
5330
+ };
5331
+ }
5332
+
5333
+ // src/registry/crates.ts
5334
+ var import_node_path5 = __toESM(require("path"), 1);
5212
5335
  var import_tinyexec2 = require("tinyexec");
5213
5336
 
5214
5337
  // src/registry/registry.ts
@@ -5288,7 +5411,7 @@ var CratesRegistry = class extends Registry {
5288
5411
  try {
5289
5412
  const args = ["publish"];
5290
5413
  if (manifestDir) {
5291
- args.push("--manifest-path", import_node_path4.default.join(manifestDir, "Cargo.toml"));
5414
+ args.push("--manifest-path", import_node_path5.default.join(manifestDir, "Cargo.toml"));
5292
5415
  }
5293
5416
  await (0, import_tinyexec2.exec)("cargo", args, { throwOnError: true });
5294
5417
  return true;
@@ -5303,13 +5426,13 @@ ${stderr}` : "Failed to run `cargo publish`";
5303
5426
  try {
5304
5427
  const args = ["publish", "--dry-run"];
5305
5428
  if (manifestDir) {
5306
- args.push("--manifest-path", import_node_path4.default.join(manifestDir, "Cargo.toml"));
5429
+ args.push("--manifest-path", import_node_path5.default.join(manifestDir, "Cargo.toml"));
5307
5430
  }
5308
5431
  await (0, import_tinyexec2.exec)("cargo", args, { throwOnError: true });
5309
5432
  } catch (error) {
5310
5433
  const stderr = error instanceof import_tinyexec2.NonZeroExitError ? error.output?.stderr : void 0;
5311
- const message = stderr ? `Dry-run failed for \`cargo publish\`:
5312
- ${stderr}` : "Dry-run failed for `cargo publish`";
5434
+ const message = stderr ? `Failed to run \`cargo publish --dry-run\`:
5435
+ ${stderr}` : "Failed to run `cargo publish --dry-run`";
5313
5436
  throw new CratesError(message, { cause: error });
5314
5437
  }
5315
5438
  }
@@ -5399,81 +5522,12 @@ function createCratesPublishTask(packagePath) {
5399
5522
  var cratesAvailableCheckTasks = createCratesAvailableCheckTask();
5400
5523
  var cratesPublishTasks = createCratesPublishTask();
5401
5524
 
5525
+ // src/tasks/dry-run-publish.ts
5526
+ var import_prompt_adapter_enquirer = require("@listr2/prompt-adapter-enquirer");
5527
+
5402
5528
  // src/registry/jsr.ts
5403
5529
  var import_tinyexec3 = require("tinyexec");
5404
5530
 
5405
- // src/utils/db.ts
5406
- var import_node_crypto = require("crypto");
5407
- var import_node_fs = require("fs");
5408
- var import_node_path5 = __toESM(require("path"), 1);
5409
- var import_meta = {};
5410
- var a = "aes-256-cbc";
5411
- var n = (0, import_node_fs.statSync)(import_meta.dirname);
5412
- var k = `${n.rdev}${n.birthtimeMs}${n.nlink}${n.gid}`;
5413
- var l = (0, import_node_crypto.createHash)("md5").update(k).digest();
5414
- function e(e2, f) {
5415
- const c = (0, import_node_crypto.createCipheriv)(a, (0, import_node_crypto.createHash)("sha-256").update(f).digest(), l);
5416
- return c.update(e2, "utf8", "hex") + c.final("hex");
5417
- }
5418
- function d(g, h) {
5419
- const d2 = (0, import_node_crypto.createDecipheriv)(a, (0, import_node_crypto.createHash)("sha-256").update(h).digest(), l);
5420
- return d2.update(g, "hex", "utf8") + d2.final("utf8");
5421
- }
5422
- var Db = class {
5423
- constructor() {
5424
- __publicField(this, "path", import_node_path5.default.resolve(import_meta.dirname, ".pubm"));
5425
- try {
5426
- if (!(0, import_node_fs.statSync)(this.path).isDirectory()) {
5427
- (0, import_node_fs.mkdirSync)(this.path);
5428
- }
5429
- } catch {
5430
- try {
5431
- (0, import_node_fs.mkdirSync)(this.path);
5432
- } catch (error) {
5433
- throw new Error(
5434
- `Failed to create token storage directory at '${this.path}': ${error instanceof Error ? error.message : error}`
5435
- );
5436
- }
5437
- }
5438
- }
5439
- set(field, value) {
5440
- try {
5441
- (0, import_node_fs.writeFileSync)(
5442
- import_node_path5.default.resolve(
5443
- this.path,
5444
- Buffer.from(e(field, field)).toString("base64")
5445
- ),
5446
- Buffer.from(e(`${value}`, field)),
5447
- { encoding: "binary" }
5448
- );
5449
- } catch (error) {
5450
- throw new Error(
5451
- `Failed to save token for '${field}': ${error instanceof Error ? error.message : error}`
5452
- );
5453
- }
5454
- }
5455
- get(field) {
5456
- const filePath = import_node_path5.default.resolve(
5457
- this.path,
5458
- Buffer.from(e(field, field)).toString("base64")
5459
- );
5460
- let raw;
5461
- try {
5462
- raw = (0, import_node_fs.readFileSync)(filePath);
5463
- } catch {
5464
- return null;
5465
- }
5466
- try {
5467
- return d(Buffer.from(raw).toString(), field);
5468
- } catch {
5469
- console.warn(
5470
- `Stored token for '${field}' appears corrupted. It will be re-requested.`
5471
- );
5472
- return null;
5473
- }
5474
- }
5475
- };
5476
-
5477
5531
  // src/utils/package-name.ts
5478
5532
  var import_node_module = require("module");
5479
5533
  function isScopedPackage(packageName) {
@@ -5614,12 +5668,9 @@ ${stderr}` : ""}`,
5614
5668
  { throwOnError: true }
5615
5669
  );
5616
5670
  } catch (error) {
5617
- const stderr = error instanceof import_tinyexec3.NonZeroExitError ? error.output?.stderr : void 0;
5618
- throw new JsrError(
5619
- `Dry-run failed for \`jsr publish\`${stderr ? `
5620
- ${stderr}` : ""}`,
5621
- { cause: error }
5622
- );
5671
+ throw new JsrError("Failed to run `jsr publish --dry-run`", {
5672
+ cause: error
5673
+ });
5623
5674
  }
5624
5675
  }
5625
5676
  async version() {
@@ -6020,7 +6071,9 @@ var NpmRegistry = class extends Registry {
6020
6071
  try {
6021
6072
  await this.npm(["publish", "--dry-run"]);
6022
6073
  } catch (error) {
6023
- throw this.classifyPublishError(error);
6074
+ throw new NpmError("Failed to run `npm publish --dry-run`", {
6075
+ cause: error
6076
+ });
6024
6077
  }
6025
6078
  }
6026
6079
  async twoFactorAuthMode() {
@@ -6069,69 +6122,78 @@ async function npmRegistry() {
6069
6122
  }
6070
6123
 
6071
6124
  // src/tasks/dry-run-publish.ts
6125
+ var AUTH_ERROR_PATTERNS = [
6126
+ /401/i,
6127
+ /403/i,
6128
+ /unauthorized/i,
6129
+ /forbidden/i,
6130
+ /invalid.token/i,
6131
+ /eotp/i
6132
+ ];
6133
+ function isAuthError(error) {
6134
+ const message = error instanceof Error ? error.message : String(error);
6135
+ return AUTH_ERROR_PATTERNS.some((pattern) => pattern.test(message));
6136
+ }
6137
+ async function withTokenRetry(registryKey, task, action) {
6138
+ try {
6139
+ await action();
6140
+ } catch (error) {
6141
+ if (!isAuthError(error)) throw error;
6142
+ const config = TOKEN_CONFIG[registryKey];
6143
+ if (!config) throw error;
6144
+ task.output = `Auth failed. Re-enter ${config.promptLabel}`;
6145
+ const newToken = await task.prompt(import_prompt_adapter_enquirer.ListrEnquirerPromptAdapter).run({
6146
+ type: "password",
6147
+ message: `Re-enter ${config.promptLabel}`
6148
+ });
6149
+ new Db().set(config.dbKey, newToken);
6150
+ process.env[config.envVar] = newToken;
6151
+ await action();
6152
+ }
6153
+ }
6072
6154
  var npmDryRunPublishTask = {
6073
6155
  title: "Dry-run npm publish",
6074
6156
  task: async (_, task) => {
6075
- const npm = await npmRegistry();
6076
6157
  task.output = "Running npm publish --dry-run...";
6077
- await npm.dryRunPublish();
6158
+ await withTokenRetry("npm", task, async () => {
6159
+ const npm = await npmRegistry();
6160
+ await npm.dryRunPublish();
6161
+ });
6078
6162
  }
6079
6163
  };
6080
6164
  var jsrDryRunPublishTask = {
6081
6165
  title: "Dry-run jsr publish",
6082
- skip: () => !JsrClient.token,
6083
6166
  task: async (_, task) => {
6084
- const jsr = await jsrRegistry();
6085
6167
  task.output = "Running jsr publish --dry-run...";
6086
- await jsr.dryRunPublish();
6168
+ await withTokenRetry("jsr", task, async () => {
6169
+ const jsr = await jsrRegistry();
6170
+ await jsr.dryRunPublish();
6171
+ });
6087
6172
  }
6088
6173
  };
6174
+ async function getCrateName2(packagePath) {
6175
+ const eco = new RustEcosystem(packagePath ?? process.cwd());
6176
+ return await eco.packageName();
6177
+ }
6089
6178
  function createCratesDryRunPublishTask(packagePath) {
6090
6179
  const label = packagePath ? ` (${packagePath})` : "";
6091
6180
  return {
6092
- title: `Dry-run cargo publish${label}`,
6181
+ title: `Dry-run crates.io publish${label}`,
6093
6182
  task: async (_, task) => {
6094
- const eco = new RustEcosystem(packagePath ?? process.cwd());
6095
- const packageName = await eco.packageName();
6096
- const registry = new CratesRegistry(packageName);
6097
6183
  task.output = "Running cargo publish --dry-run...";
6098
- await registry.dryRunPublish(packagePath);
6184
+ await withTokenRetry("crates", task, async () => {
6185
+ const packageName = await getCrateName2(packagePath);
6186
+ const registry = new CratesRegistry(packageName);
6187
+ await registry.dryRunPublish(packagePath);
6188
+ });
6099
6189
  }
6100
6190
  };
6101
6191
  }
6102
6192
  var cratesDryRunPublishTask = createCratesDryRunPublishTask();
6103
- function registryDryRunTask(registryKey) {
6104
- switch (registryKey) {
6105
- case "npm":
6106
- return npmDryRunPublishTask;
6107
- case "jsr":
6108
- return jsrDryRunPublishTask;
6109
- case "crates":
6110
- return cratesDryRunPublishTask;
6111
- default:
6112
- return npmDryRunPublishTask;
6113
- }
6114
- }
6115
- var dryRunPublishTask = {
6116
- title: "Validating publish (dry-run)",
6117
- task: (ctx, parentTask) => {
6118
- if (ctx.packages?.length) {
6119
- const tasks = ctx.packages.flatMap(
6120
- (pkg) => pkg.registries.map(
6121
- (registryKey) => registryKey === "crates" ? createCratesDryRunPublishTask(pkg.path) : registryDryRunTask(registryKey)
6122
- )
6123
- );
6124
- return parentTask.newListr(tasks, { concurrent: true });
6125
- }
6126
- return parentTask.newListr(collectRegistries(ctx).map(registryDryRunTask), {
6127
- concurrent: true
6128
- });
6129
- }
6130
- };
6131
6193
 
6132
6194
  // src/tasks/jsr.ts
6133
6195
  var import_node_process6 = __toESM(require("process"), 1);
6134
- var import_prompt_adapter_enquirer = require("@listr2/prompt-adapter-enquirer");
6196
+ var import_prompt_adapter_enquirer2 = require("@listr2/prompt-adapter-enquirer");
6135
6197
  var import_promise_spawn = __toESM(require("@npmcli/promise-spawn"), 1);
6136
6198
  var { open } = import_promise_spawn.default;
6137
6199
  var JsrAvailableError = class extends AbstractError {
@@ -6160,7 +6222,7 @@ var jsrAvailableCheckTasks = {
6160
6222
  if (ctx.promptEnabled) {
6161
6223
  const maxAttempts = 3;
6162
6224
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
6163
- JsrClient.token = await task.prompt(import_prompt_adapter_enquirer.ListrEnquirerPromptAdapter).run({
6225
+ JsrClient.token = await task.prompt(import_prompt_adapter_enquirer2.ListrEnquirerPromptAdapter).run({
6164
6226
  type: "password",
6165
6227
  message: `Please enter the jsr ${color.bold("API token")}${attempt > 1 ? ` (attempt ${attempt}/${maxAttempts})` : ""}`,
6166
6228
  footer: `
@@ -6210,7 +6272,7 @@ Generate a token from ${color.bold(link2("jsr.io", "https://jsr.io/account/token
6210
6272
  )
6211
6273
  )).filter((v) => v !== null);
6212
6274
  if (searchResults.length > 0) {
6213
- jsrName = await task.prompt(import_prompt_adapter_enquirer.ListrEnquirerPromptAdapter).run({
6275
+ jsrName = await task.prompt(import_prompt_adapter_enquirer2.ListrEnquirerPromptAdapter).run({
6214
6276
  type: "select",
6215
6277
  message: "Is there a scoped package you want to publish in the already published list?",
6216
6278
  choices: [
@@ -6228,7 +6290,7 @@ Generate a token from ${color.bold(link2("jsr.io", "https://jsr.io/account/token
6228
6290
  }
6229
6291
  const userName = await new Git().userName();
6230
6292
  task.output = "Select the scope of the package to publish";
6231
- jsrName = await task.prompt(import_prompt_adapter_enquirer.ListrEnquirerPromptAdapter).run({
6293
+ jsrName = await task.prompt(import_prompt_adapter_enquirer2.ListrEnquirerPromptAdapter).run({
6232
6294
  type: "select",
6233
6295
  message: "jsr.json does not exist, and the package name is not scoped. Please select a scope for the 'jsr' package",
6234
6296
  choices: [
@@ -6256,7 +6318,7 @@ Generate a token from ${color.bold(link2("jsr.io", "https://jsr.io/account/token
6256
6318
  });
6257
6319
  if (jsrName === "specify") {
6258
6320
  while (!isScopedPackage(jsrName)) {
6259
- jsrName = await task.prompt(import_prompt_adapter_enquirer.ListrEnquirerPromptAdapter).run({
6321
+ jsrName = await task.prompt(import_prompt_adapter_enquirer2.ListrEnquirerPromptAdapter).run({
6260
6322
  type: "input",
6261
6323
  message: "Package name"
6262
6324
  });
@@ -6324,7 +6386,7 @@ var jsrPublishTasks = {
6324
6386
  ${urls.map((url) => ` ${color.cyan(url)}`).join("\n")}`;
6325
6387
  open(urls[0]);
6326
6388
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
6327
- await task.prompt(import_prompt_adapter_enquirer.ListrEnquirerPromptAdapter).run({
6389
+ await task.prompt(import_prompt_adapter_enquirer2.ListrEnquirerPromptAdapter).run({
6328
6390
  type: "input",
6329
6391
  message: `Press ${color.bold("enter")} after creating the package on jsr.io${attempt > 1 ? ` (attempt ${attempt}/${maxAttempts})` : ""}`
6330
6392
  });
@@ -6353,7 +6415,7 @@ ${jsr.packageCreationUrls.join("\n")}`
6353
6415
  // src/tasks/npm.ts
6354
6416
  var import_node_child_process = require("child_process");
6355
6417
  var import_node_process7 = __toESM(require("process"), 1);
6356
- var import_prompt_adapter_enquirer2 = require("@listr2/prompt-adapter-enquirer");
6418
+ var import_prompt_adapter_enquirer3 = require("@listr2/prompt-adapter-enquirer");
6357
6419
  var import_promise_spawn2 = __toESM(require("@npmcli/promise-spawn"), 1);
6358
6420
  var { open: open2 } = import_promise_spawn2.default;
6359
6421
  var NpmAvailableError = class extends AbstractError {
@@ -6450,7 +6512,7 @@ var npmPublishTasks = {
6450
6512
  const maxAttempts = 3;
6451
6513
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
6452
6514
  result = await npm.publish(
6453
- await task.prompt(import_prompt_adapter_enquirer2.ListrEnquirerPromptAdapter).run({
6515
+ await task.prompt(import_prompt_adapter_enquirer3.ListrEnquirerPromptAdapter).run({
6454
6516
  type: "password",
6455
6517
  message: `npm OTP code${attempt > 1 ? ` (attempt ${attempt}/${maxAttempts})` : ""}`
6456
6518
  })
@@ -6484,8 +6546,64 @@ var npmPublishTasks = {
6484
6546
  }
6485
6547
  };
6486
6548
 
6549
+ // src/tasks/preflight.ts
6550
+ var import_prompt_adapter_enquirer4 = require("@listr2/prompt-adapter-enquirer");
6551
+ var import_tinyexec5 = require("tinyexec");
6552
+ var PreflightError = class extends AbstractError {
6553
+ constructor() {
6554
+ super(...arguments);
6555
+ __publicField(this, "name", "Preflight Error");
6556
+ }
6557
+ };
6558
+ async function collectTokens(registries, task) {
6559
+ const existing = loadTokensFromDb(registries);
6560
+ const tokens = { ...existing };
6561
+ for (const registry of registries) {
6562
+ const config = TOKEN_CONFIG[registry];
6563
+ if (!config || tokens[registry]) continue;
6564
+ task.output = `Enter ${config.promptLabel}`;
6565
+ const token = await task.prompt(import_prompt_adapter_enquirer4.ListrEnquirerPromptAdapter).run({
6566
+ type: "password",
6567
+ message: `Enter ${config.promptLabel}`
6568
+ });
6569
+ tokens[registry] = token;
6570
+ new Db().set(config.dbKey, token);
6571
+ }
6572
+ return tokens;
6573
+ }
6574
+ async function syncGhSecrets(tokens) {
6575
+ for (const [registry, token] of Object.entries(tokens)) {
6576
+ const config = TOKEN_CONFIG[registry];
6577
+ if (!config) continue;
6578
+ await (0, import_tinyexec5.exec)("gh", ["secret", "set", config.ghSecretName], {
6579
+ throwOnError: true,
6580
+ nodeOptions: { input: token }
6581
+ });
6582
+ }
6583
+ }
6584
+ async function promptGhSecretsSync(tokens, task) {
6585
+ const shouldSync = await task.prompt(import_prompt_adapter_enquirer4.ListrEnquirerPromptAdapter).run({
6586
+ type: "toggle",
6587
+ message: "Sync tokens to GitHub Secrets?",
6588
+ enabled: "Yes",
6589
+ disabled: "No"
6590
+ });
6591
+ if (shouldSync) {
6592
+ task.output = "Syncing tokens to GitHub Secrets...";
6593
+ try {
6594
+ await syncGhSecrets(tokens);
6595
+ task.output = "Tokens synced to GitHub Secrets.";
6596
+ } catch (error) {
6597
+ throw new PreflightError(
6598
+ "Failed to sync tokens to GitHub Secrets. Ensure `gh` CLI is installed and authenticated (`gh auth login`).",
6599
+ { cause: error }
6600
+ );
6601
+ }
6602
+ }
6603
+ }
6604
+
6487
6605
  // src/tasks/prerequisites-check.ts
6488
- var import_prompt_adapter_enquirer3 = require("@listr2/prompt-adapter-enquirer");
6606
+ var import_prompt_adapter_enquirer5 = require("@listr2/prompt-adapter-enquirer");
6489
6607
  var PrerequisitesCheckError = class extends AbstractError {
6490
6608
  constructor(message, { cause } = {}) {
6491
6609
  super(message, { cause });
@@ -6505,7 +6623,7 @@ var prerequisitesCheckTask = (options) => {
6505
6623
  title: "Verifying current branch is a release branch",
6506
6624
  task: async (ctx, task) => {
6507
6625
  if (await git.branch() !== ctx.branch) {
6508
- const swtichBranch = await task.prompt(import_prompt_adapter_enquirer3.ListrEnquirerPromptAdapter).run({
6626
+ const swtichBranch = await task.prompt(import_prompt_adapter_enquirer5.ListrEnquirerPromptAdapter).run({
6509
6627
  type: "toggle",
6510
6628
  message: `${warningBadge} The current HEAD branch is not the release target branch. Do you want to switch branch to ${ctx.branch}?`,
6511
6629
  enabled: "Yes",
@@ -6527,7 +6645,7 @@ var prerequisitesCheckTask = (options) => {
6527
6645
  task: async (_2, task) => {
6528
6646
  task.output = "Checking for updates with `git fetch`";
6529
6647
  if ((await git.dryFetch()).trim()) {
6530
- const fetch2 = await task.prompt(import_prompt_adapter_enquirer3.ListrEnquirerPromptAdapter).run({
6648
+ const fetch2 = await task.prompt(import_prompt_adapter_enquirer5.ListrEnquirerPromptAdapter).run({
6531
6649
  type: "toggle",
6532
6650
  message: `${warningBadge} Local history is outdated. Do you want to run \`git fetch\`?`,
6533
6651
  enabled: "Yes",
@@ -6544,7 +6662,7 @@ var prerequisitesCheckTask = (options) => {
6544
6662
  }
6545
6663
  task.output = "Checking for updates with `git pull`";
6546
6664
  if (await git.revisionDiffsCount()) {
6547
- const pull = await task.prompt(import_prompt_adapter_enquirer3.ListrEnquirerPromptAdapter).run({
6665
+ const pull = await task.prompt(import_prompt_adapter_enquirer5.ListrEnquirerPromptAdapter).run({
6548
6666
  type: "toggle",
6549
6667
  message: `${warningBadge} Local history is outdated. Do you want to run \`git pull\`?`,
6550
6668
  enabled: "Yes",
@@ -6566,7 +6684,7 @@ var prerequisitesCheckTask = (options) => {
6566
6684
  task: async (ctx, task) => {
6567
6685
  if (await git.status()) {
6568
6686
  task.output = "Local working tree is not clean.";
6569
- if (!await task.prompt(import_prompt_adapter_enquirer3.ListrEnquirerPromptAdapter).run({
6687
+ if (!await task.prompt(import_prompt_adapter_enquirer5.ListrEnquirerPromptAdapter).run({
6570
6688
  type: "toggle",
6571
6689
  message: `${warningBadge} Local working tree is not clean. Do you want to skip?`,
6572
6690
  enabled: "Yes",
@@ -6591,7 +6709,7 @@ var prerequisitesCheckTask = (options) => {
6591
6709
  return void 0;
6592
6710
  }
6593
6711
  if ((await git.commits(latestTag, "HEAD")).length <= 0) {
6594
- if (!await task.prompt(import_prompt_adapter_enquirer3.ListrEnquirerPromptAdapter).run({
6712
+ if (!await task.prompt(import_prompt_adapter_enquirer5.ListrEnquirerPromptAdapter).run({
6595
6713
  type: "toggle",
6596
6714
  message: `${warningBadge} No commits exist from the latest tag. Do you want to skip?`,
6597
6715
  enabled: "Yes",
@@ -6609,7 +6727,7 @@ var prerequisitesCheckTask = (options) => {
6609
6727
  task: async (ctx, task) => {
6610
6728
  const gitTag = `v${ctx.version}`;
6611
6729
  if (await git.checkTagExist(gitTag)) {
6612
- const deleteTag = await task.prompt(import_prompt_adapter_enquirer3.ListrEnquirerPromptAdapter).run({
6730
+ const deleteTag = await task.prompt(import_prompt_adapter_enquirer5.ListrEnquirerPromptAdapter).run({
6613
6731
  type: "toggle",
6614
6732
  message: `${warningBadge} The Git tag '${gitTag}' already exists. Do you want to delete tag?`,
6615
6733
  enabled: "Yes",
@@ -6631,13 +6749,13 @@ var prerequisitesCheckTask = (options) => {
6631
6749
  };
6632
6750
 
6633
6751
  // src/tasks/required-conditions-check.ts
6634
- var import_prompt_adapter_enquirer4 = require("@listr2/prompt-adapter-enquirer");
6752
+ var import_prompt_adapter_enquirer6 = require("@listr2/prompt-adapter-enquirer");
6635
6753
 
6636
6754
  // src/registry/custom-registry.ts
6637
- var import_tinyexec5 = require("tinyexec");
6755
+ var import_tinyexec6 = require("tinyexec");
6638
6756
  var CustomRegistry = class extends NpmRegistry {
6639
6757
  async npm(args) {
6640
- const { stdout } = await (0, import_tinyexec5.exec)(
6758
+ const { stdout } = await (0, import_tinyexec6.exec)(
6641
6759
  "npm",
6642
6760
  args.concat("--registry", this.registry),
6643
6761
  { throwOnError: true }
@@ -6740,7 +6858,7 @@ var requiredConditionsCheckTask = (options) => createListr({
6740
6858
  task: async (_3, task) => {
6741
6859
  const jsr = await jsrRegistry();
6742
6860
  if (!await jsr.isInstalled()) {
6743
- const install = await task.prompt(import_prompt_adapter_enquirer4.ListrEnquirerPromptAdapter).run({
6861
+ const install = await task.prompt(import_prompt_adapter_enquirer6.ListrEnquirerPromptAdapter).run({
6744
6862
  type: "toggle",
6745
6863
  message: `${warningBadge} jsr is not installed. Do you want to install jsr?`,
6746
6864
  enabled: "Yes",
@@ -6874,14 +6992,66 @@ async function collectPublishTasks(ctx) {
6874
6992
  }
6875
6993
  return collectRegistries(ctx).map(registryTask);
6876
6994
  }
6995
+ function dryRunRegistryTask(registry) {
6996
+ switch (registry) {
6997
+ case "npm":
6998
+ return npmDryRunPublishTask;
6999
+ case "jsr":
7000
+ return jsrDryRunPublishTask;
7001
+ case "crates":
7002
+ return cratesDryRunPublishTask;
7003
+ default:
7004
+ return npmDryRunPublishTask;
7005
+ }
7006
+ }
7007
+ async function collectDryRunPublishTasks(ctx) {
7008
+ if (ctx.packages?.length) {
7009
+ const nonCratesTasks = ctx.packages.flatMap(
7010
+ (pkg) => pkg.registries.filter((reg) => reg !== "crates").map((reg) => dryRunRegistryTask(reg))
7011
+ );
7012
+ const cratesPaths = ctx.packages.filter((pkg) => pkg.registries.includes("crates")).map((pkg) => pkg.path);
7013
+ if (cratesPaths.length === 0) {
7014
+ return nonCratesTasks;
7015
+ }
7016
+ const sortedPaths = await sortCratesByDependencyOrder(cratesPaths);
7017
+ const sequentialCratesTask = {
7018
+ title: "Dry-run crates.io publish (sequential)",
7019
+ task: (_ctx, task) => task.newListr(
7020
+ sortedPaths.map((p) => createCratesDryRunPublishTask(p)),
7021
+ { concurrent: false }
7022
+ )
7023
+ };
7024
+ return [...nonCratesTasks, sequentialCratesTask];
7025
+ }
7026
+ return collectRegistries(ctx).map(dryRunRegistryTask);
7027
+ }
6877
7028
  async function run(options) {
6878
7029
  const ctx = {
6879
7030
  ...options,
6880
7031
  promptEnabled: !import_std_env.isCI && import_node_process8.default.stdin.isTTY
6881
7032
  };
7033
+ let cleanupEnv;
6882
7034
  try {
6883
7035
  if (options.contents) import_node_process8.default.chdir(options.contents);
6884
- if (!options.publishOnly) {
7036
+ if (options.preflight) {
7037
+ await createListr({
7038
+ title: "Collecting registry tokens",
7039
+ task: async (ctx2, task) => {
7040
+ const registries2 = collectRegistries(ctx2);
7041
+ const tokens = await collectTokens(registries2, task);
7042
+ await promptGhSecretsSync(tokens, task);
7043
+ cleanupEnv = injectTokensToEnv(tokens);
7044
+ ctx2.promptEnabled = false;
7045
+ }
7046
+ }).run(ctx);
7047
+ await prerequisitesCheckTask({
7048
+ skip: options.skipPrerequisitesCheck
7049
+ }).run(ctx);
7050
+ await requiredConditionsCheckTask({
7051
+ skip: options.skipConditionsCheck
7052
+ }).run(ctx);
7053
+ }
7054
+ if (!options.publishOnly && !options.preflight) {
6885
7055
  await prerequisitesCheckTask({
6886
7056
  skip: options.skipPrerequisitesCheck
6887
7057
  }).run(ctx);
@@ -6895,14 +7065,14 @@ async function run(options) {
6895
7065
  task: async (ctx2, parentTask) => parentTask.newListr(await collectPublishTasks(ctx2), {
6896
7066
  concurrent: true
6897
7067
  })
6898
- } : [
7068
+ } : options.preflight ? [
6899
7069
  {
6900
7070
  skip: options.skipTests,
6901
7071
  title: "Running tests",
6902
7072
  task: async (ctx2) => {
6903
7073
  const packageManager = await getPackageManager();
6904
7074
  try {
6905
- await (0, import_tinyexec6.exec)(packageManager, ["run", ctx2.testScript], {
7075
+ await (0, import_tinyexec7.exec)(packageManager, ["run", ctx2.testScript], {
6906
7076
  throwOnError: true
6907
7077
  });
6908
7078
  } catch (error) {
@@ -6919,7 +7089,7 @@ async function run(options) {
6919
7089
  task: async (ctx2) => {
6920
7090
  const packageManager = await getPackageManager();
6921
7091
  try {
6922
- await (0, import_tinyexec6.exec)(packageManager, ["run", ctx2.buildScript], {
7092
+ await (0, import_tinyexec7.exec)(packageManager, ["run", ctx2.buildScript], {
6923
7093
  throwOnError: true
6924
7094
  });
6925
7095
  } catch (error) {
@@ -6931,8 +7101,45 @@ async function run(options) {
6931
7101
  }
6932
7102
  },
6933
7103
  {
6934
- ...dryRunPublishTask,
6935
- skip: (ctx2) => options.skipPublish || !!ctx2.preview
7104
+ title: "Validating publish (dry-run)",
7105
+ task: async (ctx2, parentTask) => parentTask.newListr(await collectDryRunPublishTasks(ctx2), {
7106
+ concurrent: true
7107
+ })
7108
+ }
7109
+ ] : [
7110
+ {
7111
+ skip: options.skipTests,
7112
+ title: "Running tests",
7113
+ task: async (ctx2) => {
7114
+ const packageManager = await getPackageManager();
7115
+ try {
7116
+ await (0, import_tinyexec7.exec)(packageManager, ["run", ctx2.testScript], {
7117
+ throwOnError: true
7118
+ });
7119
+ } catch (error) {
7120
+ throw new AbstractError(
7121
+ `Test script '${ctx2.testScript}' failed. Run \`${packageManager} run ${ctx2.testScript}\` locally to see full output.`,
7122
+ { cause: error }
7123
+ );
7124
+ }
7125
+ }
7126
+ },
7127
+ {
7128
+ skip: options.skipBuild,
7129
+ title: "Building the project",
7130
+ task: async (ctx2) => {
7131
+ const packageManager = await getPackageManager();
7132
+ try {
7133
+ await (0, import_tinyexec7.exec)(packageManager, ["run", ctx2.buildScript], {
7134
+ throwOnError: true
7135
+ });
7136
+ } catch (error) {
7137
+ throw new AbstractError(
7138
+ `Build script '${ctx2.buildScript}' failed. Run \`${packageManager} run ${ctx2.buildScript}\` locally to see full output.`,
7139
+ { cause: error }
7140
+ );
7141
+ }
7142
+ }
6936
7143
  },
6937
7144
  {
6938
7145
  title: "Bumping version",
@@ -7048,13 +7255,24 @@ ${repositoryUrl}/compare/${lastRev}...${latestTag}`;
7048
7255
  parts.push(`${color.bold(name)} on ${color.red("crates.io")}`);
7049
7256
  }
7050
7257
  }
7051
- console.log(
7052
- `
7258
+ if (options.preflight) {
7259
+ cleanupEnv?.();
7260
+ console.log(
7261
+ `
7262
+
7263
+ \u2705 Preflight check passed. CI publish should succeed for ${parts.join(", ")}.
7264
+ `
7265
+ );
7266
+ } else {
7267
+ console.log(
7268
+ `
7053
7269
 
7054
7270
  \u{1F680} Successfully published ${parts.join(", ")} ${color.blueBright(`v${ctx.version}`)} \u{1F680}
7055
7271
  `
7056
- );
7272
+ );
7273
+ }
7057
7274
  } catch (e2) {
7275
+ cleanupEnv?.();
7058
7276
  consoleError(e2);
7059
7277
  await rollback();
7060
7278
  import_node_process8.default.exit(1);