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/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
@@ -5217,6 +5340,8 @@ var Registry = class {
5217
5340
  this.packageName = packageName;
5218
5341
  this.registry = registry;
5219
5342
  }
5343
+ async dryRunPublish(_manifestDir) {
5344
+ }
5220
5345
  };
5221
5346
 
5222
5347
  // src/registry/crates.ts
@@ -5286,7 +5411,7 @@ var CratesRegistry = class extends Registry {
5286
5411
  try {
5287
5412
  const args = ["publish"];
5288
5413
  if (manifestDir) {
5289
- 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"));
5290
5415
  }
5291
5416
  await (0, import_tinyexec2.exec)("cargo", args, { throwOnError: true });
5292
5417
  return true;
@@ -5297,6 +5422,20 @@ ${stderr}` : "Failed to run `cargo publish`";
5297
5422
  throw new CratesError(message, { cause: error });
5298
5423
  }
5299
5424
  }
5425
+ async dryRunPublish(manifestDir) {
5426
+ try {
5427
+ const args = ["publish", "--dry-run"];
5428
+ if (manifestDir) {
5429
+ args.push("--manifest-path", import_node_path5.default.join(manifestDir, "Cargo.toml"));
5430
+ }
5431
+ await (0, import_tinyexec2.exec)("cargo", args, { throwOnError: true });
5432
+ } catch (error) {
5433
+ const stderr = error instanceof import_tinyexec2.NonZeroExitError ? error.output?.stderr : void 0;
5434
+ const message = stderr ? `Failed to run \`cargo publish --dry-run\`:
5435
+ ${stderr}` : "Failed to run `cargo publish --dry-run`";
5436
+ throw new CratesError(message, { cause: error });
5437
+ }
5438
+ }
5300
5439
  async isPublished() {
5301
5440
  try {
5302
5441
  const response = await fetch(
@@ -5383,86 +5522,12 @@ function createCratesPublishTask(packagePath) {
5383
5522
  var cratesAvailableCheckTasks = createCratesAvailableCheckTask();
5384
5523
  var cratesPublishTasks = createCratesPublishTask();
5385
5524
 
5386
- // src/tasks/jsr.ts
5387
- var import_node_process6 = __toESM(require("process"), 1);
5525
+ // src/tasks/dry-run-publish.ts
5388
5526
  var import_prompt_adapter_enquirer = require("@listr2/prompt-adapter-enquirer");
5389
- var import_promise_spawn = __toESM(require("@npmcli/promise-spawn"), 1);
5390
5527
 
5391
5528
  // src/registry/jsr.ts
5392
5529
  var import_tinyexec3 = require("tinyexec");
5393
5530
 
5394
- // src/utils/db.ts
5395
- var import_node_crypto = require("crypto");
5396
- var import_node_fs = require("fs");
5397
- var import_node_path5 = __toESM(require("path"), 1);
5398
- var import_meta = {};
5399
- var a = "aes-256-cbc";
5400
- var n = (0, import_node_fs.statSync)(import_meta.dirname);
5401
- var k = `${n.rdev}${n.birthtimeMs}${n.nlink}${n.gid}`;
5402
- var l = (0, import_node_crypto.createHash)("md5").update(k).digest();
5403
- function e(e2, f) {
5404
- const c = (0, import_node_crypto.createCipheriv)(a, (0, import_node_crypto.createHash)("sha-256").update(f).digest(), l);
5405
- return c.update(e2, "utf8", "hex") + c.final("hex");
5406
- }
5407
- function d(g, h) {
5408
- const d2 = (0, import_node_crypto.createDecipheriv)(a, (0, import_node_crypto.createHash)("sha-256").update(h).digest(), l);
5409
- return d2.update(g, "hex", "utf8") + d2.final("utf8");
5410
- }
5411
- var Db = class {
5412
- constructor() {
5413
- __publicField(this, "path", import_node_path5.default.resolve(import_meta.dirname, ".pubm"));
5414
- try {
5415
- if (!(0, import_node_fs.statSync)(this.path).isDirectory()) {
5416
- (0, import_node_fs.mkdirSync)(this.path);
5417
- }
5418
- } catch {
5419
- try {
5420
- (0, import_node_fs.mkdirSync)(this.path);
5421
- } catch (error) {
5422
- throw new Error(
5423
- `Failed to create token storage directory at '${this.path}': ${error instanceof Error ? error.message : error}`
5424
- );
5425
- }
5426
- }
5427
- }
5428
- set(field, value) {
5429
- try {
5430
- (0, import_node_fs.writeFileSync)(
5431
- import_node_path5.default.resolve(
5432
- this.path,
5433
- Buffer.from(e(field, field)).toString("base64")
5434
- ),
5435
- Buffer.from(e(`${value}`, field)),
5436
- { encoding: "binary" }
5437
- );
5438
- } catch (error) {
5439
- throw new Error(
5440
- `Failed to save token for '${field}': ${error instanceof Error ? error.message : error}`
5441
- );
5442
- }
5443
- }
5444
- get(field) {
5445
- const filePath = import_node_path5.default.resolve(
5446
- this.path,
5447
- Buffer.from(e(field, field)).toString("base64")
5448
- );
5449
- let raw;
5450
- try {
5451
- raw = (0, import_node_fs.readFileSync)(filePath);
5452
- } catch {
5453
- return null;
5454
- }
5455
- try {
5456
- return d(Buffer.from(raw).toString(), field);
5457
- } catch {
5458
- console.warn(
5459
- `Stored token for '${field}' appears corrupted. It will be re-requested.`
5460
- );
5461
- return null;
5462
- }
5463
- }
5464
- };
5465
-
5466
5531
  // src/utils/package-name.ts
5467
5532
  var import_node_module = require("module");
5468
5533
  function isScopedPackage(packageName) {
@@ -5589,6 +5654,25 @@ ${stderr}` : ""}`,
5589
5654
  );
5590
5655
  }
5591
5656
  }
5657
+ async dryRunPublish() {
5658
+ try {
5659
+ await (0, import_tinyexec3.exec)(
5660
+ "jsr",
5661
+ [
5662
+ "publish",
5663
+ "--dry-run",
5664
+ "--allow-dirty",
5665
+ "--token",
5666
+ `${JsrClient.token}`
5667
+ ],
5668
+ { throwOnError: true }
5669
+ );
5670
+ } catch (error) {
5671
+ throw new JsrError("Failed to run `jsr publish --dry-run`", {
5672
+ cause: error
5673
+ });
5674
+ }
5675
+ }
5592
5676
  async version() {
5593
5677
  return await this.jsr(["--version"]);
5594
5678
  }
@@ -5983,6 +6067,24 @@ var NpmRegistry = class extends Registry {
5983
6067
  throw this.classifyPublishError(error);
5984
6068
  }
5985
6069
  }
6070
+ async dryRunPublish() {
6071
+ try {
6072
+ await this.npm(["publish", "--dry-run"]);
6073
+ } catch (error) {
6074
+ throw new NpmError("Failed to run `npm publish --dry-run`", {
6075
+ cause: error
6076
+ });
6077
+ }
6078
+ }
6079
+ async twoFactorAuthMode() {
6080
+ try {
6081
+ const output = await this.npm(["profile", "get", "--json"]);
6082
+ const profile = JSON.parse(output);
6083
+ return profile?.tfa?.mode ?? null;
6084
+ } catch {
6085
+ return null;
6086
+ }
6087
+ }
5986
6088
  async isPackageNameAvaliable() {
5987
6089
  return isValidPackageName(this.packageName);
5988
6090
  }
@@ -6019,7 +6121,80 @@ async function npmRegistry() {
6019
6121
  return new NpmRegistry(packageJson.name);
6020
6122
  }
6021
6123
 
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
+ }
6154
+ var npmDryRunPublishTask = {
6155
+ title: "Dry-run npm publish",
6156
+ task: async (_, task) => {
6157
+ task.output = "Running npm publish --dry-run...";
6158
+ await withTokenRetry("npm", task, async () => {
6159
+ const npm = await npmRegistry();
6160
+ await npm.dryRunPublish();
6161
+ });
6162
+ }
6163
+ };
6164
+ var jsrDryRunPublishTask = {
6165
+ title: "Dry-run jsr publish",
6166
+ task: async (_, task) => {
6167
+ task.output = "Running jsr publish --dry-run...";
6168
+ await withTokenRetry("jsr", task, async () => {
6169
+ const jsr = await jsrRegistry();
6170
+ await jsr.dryRunPublish();
6171
+ });
6172
+ }
6173
+ };
6174
+ async function getCrateName2(packagePath) {
6175
+ const eco = new RustEcosystem(packagePath ?? process.cwd());
6176
+ return await eco.packageName();
6177
+ }
6178
+ function createCratesDryRunPublishTask(packagePath) {
6179
+ const label = packagePath ? ` (${packagePath})` : "";
6180
+ return {
6181
+ title: `Dry-run crates.io publish${label}`,
6182
+ task: async (_, task) => {
6183
+ task.output = "Running cargo publish --dry-run...";
6184
+ await withTokenRetry("crates", task, async () => {
6185
+ const packageName = await getCrateName2(packagePath);
6186
+ const registry = new CratesRegistry(packageName);
6187
+ await registry.dryRunPublish(packagePath);
6188
+ });
6189
+ }
6190
+ };
6191
+ }
6192
+ var cratesDryRunPublishTask = createCratesDryRunPublishTask();
6193
+
6022
6194
  // src/tasks/jsr.ts
6195
+ var import_node_process6 = __toESM(require("process"), 1);
6196
+ var import_prompt_adapter_enquirer2 = require("@listr2/prompt-adapter-enquirer");
6197
+ var import_promise_spawn = __toESM(require("@npmcli/promise-spawn"), 1);
6023
6198
  var { open } = import_promise_spawn.default;
6024
6199
  var JsrAvailableError = class extends AbstractError {
6025
6200
  constructor(message, { cause } = {}) {
@@ -6047,7 +6222,7 @@ var jsrAvailableCheckTasks = {
6047
6222
  if (ctx.promptEnabled) {
6048
6223
  const maxAttempts = 3;
6049
6224
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
6050
- JsrClient.token = await task.prompt(import_prompt_adapter_enquirer.ListrEnquirerPromptAdapter).run({
6225
+ JsrClient.token = await task.prompt(import_prompt_adapter_enquirer2.ListrEnquirerPromptAdapter).run({
6051
6226
  type: "password",
6052
6227
  message: `Please enter the jsr ${color.bold("API token")}${attempt > 1 ? ` (attempt ${attempt}/${maxAttempts})` : ""}`,
6053
6228
  footer: `
@@ -6097,7 +6272,7 @@ Generate a token from ${color.bold(link2("jsr.io", "https://jsr.io/account/token
6097
6272
  )
6098
6273
  )).filter((v) => v !== null);
6099
6274
  if (searchResults.length > 0) {
6100
- jsrName = await task.prompt(import_prompt_adapter_enquirer.ListrEnquirerPromptAdapter).run({
6275
+ jsrName = await task.prompt(import_prompt_adapter_enquirer2.ListrEnquirerPromptAdapter).run({
6101
6276
  type: "select",
6102
6277
  message: "Is there a scoped package you want to publish in the already published list?",
6103
6278
  choices: [
@@ -6115,7 +6290,7 @@ Generate a token from ${color.bold(link2("jsr.io", "https://jsr.io/account/token
6115
6290
  }
6116
6291
  const userName = await new Git().userName();
6117
6292
  task.output = "Select the scope of the package to publish";
6118
- jsrName = await task.prompt(import_prompt_adapter_enquirer.ListrEnquirerPromptAdapter).run({
6293
+ jsrName = await task.prompt(import_prompt_adapter_enquirer2.ListrEnquirerPromptAdapter).run({
6119
6294
  type: "select",
6120
6295
  message: "jsr.json does not exist, and the package name is not scoped. Please select a scope for the 'jsr' package",
6121
6296
  choices: [
@@ -6143,7 +6318,7 @@ Generate a token from ${color.bold(link2("jsr.io", "https://jsr.io/account/token
6143
6318
  });
6144
6319
  if (jsrName === "specify") {
6145
6320
  while (!isScopedPackage(jsrName)) {
6146
- jsrName = await task.prompt(import_prompt_adapter_enquirer.ListrEnquirerPromptAdapter).run({
6321
+ jsrName = await task.prompt(import_prompt_adapter_enquirer2.ListrEnquirerPromptAdapter).run({
6147
6322
  type: "input",
6148
6323
  message: "Package name"
6149
6324
  });
@@ -6211,7 +6386,7 @@ var jsrPublishTasks = {
6211
6386
  ${urls.map((url) => ` ${color.cyan(url)}`).join("\n")}`;
6212
6387
  open(urls[0]);
6213
6388
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
6214
- await task.prompt(import_prompt_adapter_enquirer.ListrEnquirerPromptAdapter).run({
6389
+ await task.prompt(import_prompt_adapter_enquirer2.ListrEnquirerPromptAdapter).run({
6215
6390
  type: "input",
6216
6391
  message: `Press ${color.bold("enter")} after creating the package on jsr.io${attempt > 1 ? ` (attempt ${attempt}/${maxAttempts})` : ""}`
6217
6392
  });
@@ -6240,7 +6415,7 @@ ${jsr.packageCreationUrls.join("\n")}`
6240
6415
  // src/tasks/npm.ts
6241
6416
  var import_node_child_process = require("child_process");
6242
6417
  var import_node_process7 = __toESM(require("process"), 1);
6243
- var import_prompt_adapter_enquirer2 = require("@listr2/prompt-adapter-enquirer");
6418
+ var import_prompt_adapter_enquirer3 = require("@listr2/prompt-adapter-enquirer");
6244
6419
  var import_promise_spawn2 = __toESM(require("@npmcli/promise-spawn"), 1);
6245
6420
  var { open: open2 } = import_promise_spawn2.default;
6246
6421
  var NpmAvailableError = class extends AbstractError {
@@ -6252,7 +6427,6 @@ var NpmAvailableError = class extends AbstractError {
6252
6427
  };
6253
6428
  var npmAvailableCheckTasks = {
6254
6429
  title: "Checking npm avaliable for publising",
6255
- skip: (ctx) => !!ctx.preview,
6256
6430
  task: async (ctx, task) => {
6257
6431
  const npm = await npmRegistry();
6258
6432
  if (!await npm.isLoggedIn()) {
@@ -6315,6 +6489,14 @@ var npmAvailableCheckTasks = {
6315
6489
  More information: ${link2("npm naming rules", "https://github.com/npm/validate-npm-package-name?tab=readme-ov-file#naming-rules")}`
6316
6490
  );
6317
6491
  }
6492
+ if (!ctx.promptEnabled) {
6493
+ const tfaMode = await npm.twoFactorAuthMode();
6494
+ if (tfaMode === "auth-and-writes") {
6495
+ throw new NpmAvailableError(
6496
+ `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`
6497
+ );
6498
+ }
6499
+ }
6318
6500
  }
6319
6501
  };
6320
6502
  var npmPublishTasks = {
@@ -6330,7 +6512,7 @@ var npmPublishTasks = {
6330
6512
  const maxAttempts = 3;
6331
6513
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
6332
6514
  result = await npm.publish(
6333
- await task.prompt(import_prompt_adapter_enquirer2.ListrEnquirerPromptAdapter).run({
6515
+ await task.prompt(import_prompt_adapter_enquirer3.ListrEnquirerPromptAdapter).run({
6334
6516
  type: "password",
6335
6517
  message: `npm OTP code${attempt > 1 ? ` (attempt ${attempt}/${maxAttempts})` : ""}`
6336
6518
  })
@@ -6364,8 +6546,64 @@ var npmPublishTasks = {
6364
6546
  }
6365
6547
  };
6366
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
+
6367
6605
  // src/tasks/prerequisites-check.ts
6368
- var import_prompt_adapter_enquirer3 = require("@listr2/prompt-adapter-enquirer");
6606
+ var import_prompt_adapter_enquirer5 = require("@listr2/prompt-adapter-enquirer");
6369
6607
  var PrerequisitesCheckError = class extends AbstractError {
6370
6608
  constructor(message, { cause } = {}) {
6371
6609
  super(message, { cause });
@@ -6385,7 +6623,7 @@ var prerequisitesCheckTask = (options) => {
6385
6623
  title: "Verifying current branch is a release branch",
6386
6624
  task: async (ctx, task) => {
6387
6625
  if (await git.branch() !== ctx.branch) {
6388
- const swtichBranch = await task.prompt(import_prompt_adapter_enquirer3.ListrEnquirerPromptAdapter).run({
6626
+ const swtichBranch = await task.prompt(import_prompt_adapter_enquirer5.ListrEnquirerPromptAdapter).run({
6389
6627
  type: "toggle",
6390
6628
  message: `${warningBadge} The current HEAD branch is not the release target branch. Do you want to switch branch to ${ctx.branch}?`,
6391
6629
  enabled: "Yes",
@@ -6407,7 +6645,7 @@ var prerequisitesCheckTask = (options) => {
6407
6645
  task: async (_2, task) => {
6408
6646
  task.output = "Checking for updates with `git fetch`";
6409
6647
  if ((await git.dryFetch()).trim()) {
6410
- const fetch2 = await task.prompt(import_prompt_adapter_enquirer3.ListrEnquirerPromptAdapter).run({
6648
+ const fetch2 = await task.prompt(import_prompt_adapter_enquirer5.ListrEnquirerPromptAdapter).run({
6411
6649
  type: "toggle",
6412
6650
  message: `${warningBadge} Local history is outdated. Do you want to run \`git fetch\`?`,
6413
6651
  enabled: "Yes",
@@ -6424,7 +6662,7 @@ var prerequisitesCheckTask = (options) => {
6424
6662
  }
6425
6663
  task.output = "Checking for updates with `git pull`";
6426
6664
  if (await git.revisionDiffsCount()) {
6427
- const pull = await task.prompt(import_prompt_adapter_enquirer3.ListrEnquirerPromptAdapter).run({
6665
+ const pull = await task.prompt(import_prompt_adapter_enquirer5.ListrEnquirerPromptAdapter).run({
6428
6666
  type: "toggle",
6429
6667
  message: `${warningBadge} Local history is outdated. Do you want to run \`git pull\`?`,
6430
6668
  enabled: "Yes",
@@ -6446,7 +6684,7 @@ var prerequisitesCheckTask = (options) => {
6446
6684
  task: async (ctx, task) => {
6447
6685
  if (await git.status()) {
6448
6686
  task.output = "Local working tree is not clean.";
6449
- if (!await task.prompt(import_prompt_adapter_enquirer3.ListrEnquirerPromptAdapter).run({
6687
+ if (!await task.prompt(import_prompt_adapter_enquirer5.ListrEnquirerPromptAdapter).run({
6450
6688
  type: "toggle",
6451
6689
  message: `${warningBadge} Local working tree is not clean. Do you want to skip?`,
6452
6690
  enabled: "Yes",
@@ -6471,7 +6709,7 @@ var prerequisitesCheckTask = (options) => {
6471
6709
  return void 0;
6472
6710
  }
6473
6711
  if ((await git.commits(latestTag, "HEAD")).length <= 0) {
6474
- if (!await task.prompt(import_prompt_adapter_enquirer3.ListrEnquirerPromptAdapter).run({
6712
+ if (!await task.prompt(import_prompt_adapter_enquirer5.ListrEnquirerPromptAdapter).run({
6475
6713
  type: "toggle",
6476
6714
  message: `${warningBadge} No commits exist from the latest tag. Do you want to skip?`,
6477
6715
  enabled: "Yes",
@@ -6489,7 +6727,7 @@ var prerequisitesCheckTask = (options) => {
6489
6727
  task: async (ctx, task) => {
6490
6728
  const gitTag = `v${ctx.version}`;
6491
6729
  if (await git.checkTagExist(gitTag)) {
6492
- const deleteTag = await task.prompt(import_prompt_adapter_enquirer3.ListrEnquirerPromptAdapter).run({
6730
+ const deleteTag = await task.prompt(import_prompt_adapter_enquirer5.ListrEnquirerPromptAdapter).run({
6493
6731
  type: "toggle",
6494
6732
  message: `${warningBadge} The Git tag '${gitTag}' already exists. Do you want to delete tag?`,
6495
6733
  enabled: "Yes",
@@ -6511,13 +6749,13 @@ var prerequisitesCheckTask = (options) => {
6511
6749
  };
6512
6750
 
6513
6751
  // src/tasks/required-conditions-check.ts
6514
- var import_prompt_adapter_enquirer4 = require("@listr2/prompt-adapter-enquirer");
6752
+ var import_prompt_adapter_enquirer6 = require("@listr2/prompt-adapter-enquirer");
6515
6753
 
6516
6754
  // src/registry/custom-registry.ts
6517
- var import_tinyexec5 = require("tinyexec");
6755
+ var import_tinyexec6 = require("tinyexec");
6518
6756
  var CustomRegistry = class extends NpmRegistry {
6519
6757
  async npm(args) {
6520
- const { stdout } = await (0, import_tinyexec5.exec)(
6758
+ const { stdout } = await (0, import_tinyexec6.exec)(
6521
6759
  "npm",
6522
6760
  args.concat("--registry", this.registry),
6523
6761
  { throwOnError: true }
@@ -6620,7 +6858,7 @@ var requiredConditionsCheckTask = (options) => createListr({
6620
6858
  task: async (_3, task) => {
6621
6859
  const jsr = await jsrRegistry();
6622
6860
  if (!await jsr.isInstalled()) {
6623
- const install = await task.prompt(import_prompt_adapter_enquirer4.ListrEnquirerPromptAdapter).run({
6861
+ const install = await task.prompt(import_prompt_adapter_enquirer6.ListrEnquirerPromptAdapter).run({
6624
6862
  type: "toggle",
6625
6863
  message: `${warningBadge} jsr is not installed. Do you want to install jsr?`,
6626
6864
  enabled: "Yes",
@@ -6754,14 +6992,66 @@ async function collectPublishTasks(ctx) {
6754
6992
  }
6755
6993
  return collectRegistries(ctx).map(registryTask);
6756
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
+ }
6757
7028
  async function run(options) {
6758
7029
  const ctx = {
6759
7030
  ...options,
6760
7031
  promptEnabled: !import_std_env.isCI && import_node_process8.default.stdin.isTTY
6761
7032
  };
7033
+ let cleanupEnv;
6762
7034
  try {
6763
7035
  if (options.contents) import_node_process8.default.chdir(options.contents);
6764
- 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) {
6765
7055
  await prerequisitesCheckTask({
6766
7056
  skip: options.skipPrerequisitesCheck
6767
7057
  }).run(ctx);
@@ -6775,14 +7065,55 @@ async function run(options) {
6775
7065
  task: async (ctx2, parentTask) => parentTask.newListr(await collectPublishTasks(ctx2), {
6776
7066
  concurrent: true
6777
7067
  })
6778
- } : [
7068
+ } : options.preflight ? [
7069
+ {
7070
+ skip: options.skipTests,
7071
+ title: "Running tests",
7072
+ task: async (ctx2) => {
7073
+ const packageManager = await getPackageManager();
7074
+ try {
7075
+ await (0, import_tinyexec7.exec)(packageManager, ["run", ctx2.testScript], {
7076
+ throwOnError: true
7077
+ });
7078
+ } catch (error) {
7079
+ throw new AbstractError(
7080
+ `Test script '${ctx2.testScript}' failed. Run \`${packageManager} run ${ctx2.testScript}\` locally to see full output.`,
7081
+ { cause: error }
7082
+ );
7083
+ }
7084
+ }
7085
+ },
7086
+ {
7087
+ skip: options.skipBuild,
7088
+ title: "Building the project",
7089
+ task: async (ctx2) => {
7090
+ const packageManager = await getPackageManager();
7091
+ try {
7092
+ await (0, import_tinyexec7.exec)(packageManager, ["run", ctx2.buildScript], {
7093
+ throwOnError: true
7094
+ });
7095
+ } catch (error) {
7096
+ throw new AbstractError(
7097
+ `Build script '${ctx2.buildScript}' failed. Run \`${packageManager} run ${ctx2.buildScript}\` locally to see full output.`,
7098
+ { cause: error }
7099
+ );
7100
+ }
7101
+ }
7102
+ },
7103
+ {
7104
+ title: "Validating publish (dry-run)",
7105
+ task: async (ctx2, parentTask) => parentTask.newListr(await collectDryRunPublishTasks(ctx2), {
7106
+ concurrent: true
7107
+ })
7108
+ }
7109
+ ] : [
6779
7110
  {
6780
7111
  skip: options.skipTests,
6781
7112
  title: "Running tests",
6782
7113
  task: async (ctx2) => {
6783
7114
  const packageManager = await getPackageManager();
6784
7115
  try {
6785
- await (0, import_tinyexec6.exec)(packageManager, ["run", ctx2.testScript], {
7116
+ await (0, import_tinyexec7.exec)(packageManager, ["run", ctx2.testScript], {
6786
7117
  throwOnError: true
6787
7118
  });
6788
7119
  } catch (error) {
@@ -6799,7 +7130,7 @@ async function run(options) {
6799
7130
  task: async (ctx2) => {
6800
7131
  const packageManager = await getPackageManager();
6801
7132
  try {
6802
- await (0, import_tinyexec6.exec)(packageManager, ["run", ctx2.buildScript], {
7133
+ await (0, import_tinyexec7.exec)(packageManager, ["run", ctx2.buildScript], {
6803
7134
  throwOnError: true
6804
7135
  });
6805
7136
  } catch (error) {
@@ -6924,13 +7255,24 @@ ${repositoryUrl}/compare/${lastRev}...${latestTag}`;
6924
7255
  parts.push(`${color.bold(name)} on ${color.red("crates.io")}`);
6925
7256
  }
6926
7257
  }
6927
- console.log(
6928
- `
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
+ `
6929
7269
 
6930
7270
  \u{1F680} Successfully published ${parts.join(", ")} ${color.blueBright(`v${ctx.version}`)} \u{1F680}
6931
7271
  `
6932
- );
7272
+ );
7273
+ }
6933
7274
  } catch (e2) {
7275
+ cleanupEnv?.();
6934
7276
  consoleError(e2);
6935
7277
  await rollback();
6936
7278
  import_node_process8.default.exit(1);