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.js CHANGED
@@ -4453,7 +4453,7 @@ var Listr = (_a23 = class {
4453
4453
  // src/tasks/runner.ts
4454
4454
  import SemVer from "semver";
4455
4455
  import { isCI as isCI2 } from "std-env";
4456
- import { exec as exec7 } from "tinyexec";
4456
+ import { exec as exec8 } from "tinyexec";
4457
4457
 
4458
4458
  // src/error.ts
4459
4459
  var AbstractError = class extends Error {
@@ -5174,8 +5174,130 @@ function collectRegistries(ctx) {
5174
5174
  return ctx.registries;
5175
5175
  }
5176
5176
 
5177
- // src/registry/crates.ts
5177
+ // src/utils/db.ts
5178
+ import { createCipheriv, createDecipheriv, createHash } from "node:crypto";
5179
+ import { mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
5178
5180
  import path4 from "node:path";
5181
+ var a = "aes-256-cbc";
5182
+ var n = statSync(import.meta.dirname);
5183
+ var k = `${n.rdev}${n.birthtimeMs}${n.nlink}${n.gid}`;
5184
+ var l = createHash("md5").update(k).digest();
5185
+ function e(e2, f) {
5186
+ const c = createCipheriv(a, createHash("sha-256").update(f).digest(), l);
5187
+ return c.update(e2, "utf8", "hex") + c.final("hex");
5188
+ }
5189
+ function d(g, h) {
5190
+ const d2 = createDecipheriv(a, createHash("sha-256").update(h).digest(), l);
5191
+ return d2.update(g, "hex", "utf8") + d2.final("utf8");
5192
+ }
5193
+ var Db = class {
5194
+ constructor() {
5195
+ __publicField(this, "path", path4.resolve(import.meta.dirname, ".pubm"));
5196
+ try {
5197
+ if (!statSync(this.path).isDirectory()) {
5198
+ mkdirSync(this.path);
5199
+ }
5200
+ } catch {
5201
+ try {
5202
+ mkdirSync(this.path);
5203
+ } catch (error) {
5204
+ throw new Error(
5205
+ `Failed to create token storage directory at '${this.path}': ${error instanceof Error ? error.message : error}`
5206
+ );
5207
+ }
5208
+ }
5209
+ }
5210
+ set(field, value) {
5211
+ try {
5212
+ writeFileSync(
5213
+ path4.resolve(
5214
+ this.path,
5215
+ Buffer.from(e(field, field)).toString("base64")
5216
+ ),
5217
+ Buffer.from(e(`${value}`, field)),
5218
+ { encoding: "binary" }
5219
+ );
5220
+ } catch (error) {
5221
+ throw new Error(
5222
+ `Failed to save token for '${field}': ${error instanceof Error ? error.message : error}`
5223
+ );
5224
+ }
5225
+ }
5226
+ get(field) {
5227
+ const filePath = path4.resolve(
5228
+ this.path,
5229
+ Buffer.from(e(field, field)).toString("base64")
5230
+ );
5231
+ let raw;
5232
+ try {
5233
+ raw = readFileSync(filePath);
5234
+ } catch {
5235
+ return null;
5236
+ }
5237
+ try {
5238
+ return d(Buffer.from(raw).toString(), field);
5239
+ } catch {
5240
+ console.warn(
5241
+ `Stored token for '${field}' appears corrupted. It will be re-requested.`
5242
+ );
5243
+ return null;
5244
+ }
5245
+ }
5246
+ };
5247
+
5248
+ // src/utils/token.ts
5249
+ var TOKEN_CONFIG = {
5250
+ npm: {
5251
+ envVar: "NODE_AUTH_TOKEN",
5252
+ dbKey: "npm-token",
5253
+ ghSecretName: "NODE_AUTH_TOKEN",
5254
+ promptLabel: "npm access token"
5255
+ },
5256
+ jsr: {
5257
+ envVar: "JSR_TOKEN",
5258
+ dbKey: "jsr-token",
5259
+ ghSecretName: "JSR_TOKEN",
5260
+ promptLabel: "jsr API token"
5261
+ },
5262
+ crates: {
5263
+ envVar: "CARGO_REGISTRY_TOKEN",
5264
+ dbKey: "cargo-token",
5265
+ ghSecretName: "CARGO_REGISTRY_TOKEN",
5266
+ promptLabel: "crates.io API token"
5267
+ }
5268
+ };
5269
+ function loadTokensFromDb(registries) {
5270
+ const db = new Db();
5271
+ const tokens = {};
5272
+ for (const registry of registries) {
5273
+ const config = TOKEN_CONFIG[registry];
5274
+ if (!config) continue;
5275
+ const token = db.get(config.dbKey);
5276
+ if (token) tokens[registry] = token;
5277
+ }
5278
+ return tokens;
5279
+ }
5280
+ function injectTokensToEnv(tokens) {
5281
+ const originals = {};
5282
+ for (const [registry, token] of Object.entries(tokens)) {
5283
+ const config = TOKEN_CONFIG[registry];
5284
+ if (!config) continue;
5285
+ originals[config.envVar] = process.env[config.envVar];
5286
+ process.env[config.envVar] = token;
5287
+ }
5288
+ return () => {
5289
+ for (const [envVar, original] of Object.entries(originals)) {
5290
+ if (original === void 0) {
5291
+ delete process.env[envVar];
5292
+ } else {
5293
+ process.env[envVar] = original;
5294
+ }
5295
+ }
5296
+ };
5297
+ }
5298
+
5299
+ // src/registry/crates.ts
5300
+ import path5 from "node:path";
5179
5301
  import { exec as exec3, NonZeroExitError } from "tinyexec";
5180
5302
 
5181
5303
  // src/registry/registry.ts
@@ -5184,6 +5306,8 @@ var Registry = class {
5184
5306
  this.packageName = packageName;
5185
5307
  this.registry = registry;
5186
5308
  }
5309
+ async dryRunPublish(_manifestDir) {
5310
+ }
5187
5311
  };
5188
5312
 
5189
5313
  // src/registry/crates.ts
@@ -5253,7 +5377,7 @@ var CratesRegistry = class extends Registry {
5253
5377
  try {
5254
5378
  const args = ["publish"];
5255
5379
  if (manifestDir) {
5256
- args.push("--manifest-path", path4.join(manifestDir, "Cargo.toml"));
5380
+ args.push("--manifest-path", path5.join(manifestDir, "Cargo.toml"));
5257
5381
  }
5258
5382
  await exec3("cargo", args, { throwOnError: true });
5259
5383
  return true;
@@ -5264,6 +5388,20 @@ ${stderr}` : "Failed to run `cargo publish`";
5264
5388
  throw new CratesError(message, { cause: error });
5265
5389
  }
5266
5390
  }
5391
+ async dryRunPublish(manifestDir) {
5392
+ try {
5393
+ const args = ["publish", "--dry-run"];
5394
+ if (manifestDir) {
5395
+ args.push("--manifest-path", path5.join(manifestDir, "Cargo.toml"));
5396
+ }
5397
+ await exec3("cargo", args, { throwOnError: true });
5398
+ } catch (error) {
5399
+ const stderr = error instanceof NonZeroExitError ? error.output?.stderr : void 0;
5400
+ const message = stderr ? `Failed to run \`cargo publish --dry-run\`:
5401
+ ${stderr}` : "Failed to run `cargo publish --dry-run`";
5402
+ throw new CratesError(message, { cause: error });
5403
+ }
5404
+ }
5267
5405
  async isPublished() {
5268
5406
  try {
5269
5407
  const response = await fetch(
@@ -5350,85 +5488,12 @@ function createCratesPublishTask(packagePath) {
5350
5488
  var cratesAvailableCheckTasks = createCratesAvailableCheckTask();
5351
5489
  var cratesPublishTasks = createCratesPublishTask();
5352
5490
 
5353
- // src/tasks/jsr.ts
5354
- import process8 from "node:process";
5491
+ // src/tasks/dry-run-publish.ts
5355
5492
  import { ListrEnquirerPromptAdapter } from "@listr2/prompt-adapter-enquirer";
5356
- import npmCli from "@npmcli/promise-spawn";
5357
5493
 
5358
5494
  // src/registry/jsr.ts
5359
5495
  import { exec as exec4, NonZeroExitError as NonZeroExitError2 } from "tinyexec";
5360
5496
 
5361
- // src/utils/db.ts
5362
- import { createCipheriv, createDecipheriv, createHash } from "node:crypto";
5363
- import { mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
5364
- import path5 from "node:path";
5365
- var a = "aes-256-cbc";
5366
- var n = statSync(import.meta.dirname);
5367
- var k = `${n.rdev}${n.birthtimeMs}${n.nlink}${n.gid}`;
5368
- var l = createHash("md5").update(k).digest();
5369
- function e(e2, f) {
5370
- const c = createCipheriv(a, createHash("sha-256").update(f).digest(), l);
5371
- return c.update(e2, "utf8", "hex") + c.final("hex");
5372
- }
5373
- function d(g, h) {
5374
- const d2 = createDecipheriv(a, createHash("sha-256").update(h).digest(), l);
5375
- return d2.update(g, "hex", "utf8") + d2.final("utf8");
5376
- }
5377
- var Db = class {
5378
- constructor() {
5379
- __publicField(this, "path", path5.resolve(import.meta.dirname, ".pubm"));
5380
- try {
5381
- if (!statSync(this.path).isDirectory()) {
5382
- mkdirSync(this.path);
5383
- }
5384
- } catch {
5385
- try {
5386
- mkdirSync(this.path);
5387
- } catch (error) {
5388
- throw new Error(
5389
- `Failed to create token storage directory at '${this.path}': ${error instanceof Error ? error.message : error}`
5390
- );
5391
- }
5392
- }
5393
- }
5394
- set(field, value) {
5395
- try {
5396
- writeFileSync(
5397
- path5.resolve(
5398
- this.path,
5399
- Buffer.from(e(field, field)).toString("base64")
5400
- ),
5401
- Buffer.from(e(`${value}`, field)),
5402
- { encoding: "binary" }
5403
- );
5404
- } catch (error) {
5405
- throw new Error(
5406
- `Failed to save token for '${field}': ${error instanceof Error ? error.message : error}`
5407
- );
5408
- }
5409
- }
5410
- get(field) {
5411
- const filePath = path5.resolve(
5412
- this.path,
5413
- Buffer.from(e(field, field)).toString("base64")
5414
- );
5415
- let raw;
5416
- try {
5417
- raw = readFileSync(filePath);
5418
- } catch {
5419
- return null;
5420
- }
5421
- try {
5422
- return d(Buffer.from(raw).toString(), field);
5423
- } catch {
5424
- console.warn(
5425
- `Stored token for '${field}' appears corrupted. It will be re-requested.`
5426
- );
5427
- return null;
5428
- }
5429
- }
5430
- };
5431
-
5432
5497
  // src/utils/package-name.ts
5433
5498
  import { builtinModules } from "node:module";
5434
5499
  function isScopedPackage(packageName) {
@@ -5554,6 +5619,25 @@ ${stderr}` : ""}`,
5554
5619
  );
5555
5620
  }
5556
5621
  }
5622
+ async dryRunPublish() {
5623
+ try {
5624
+ await exec4(
5625
+ "jsr",
5626
+ [
5627
+ "publish",
5628
+ "--dry-run",
5629
+ "--allow-dirty",
5630
+ "--token",
5631
+ `${JsrClient.token}`
5632
+ ],
5633
+ { throwOnError: true }
5634
+ );
5635
+ } catch (error) {
5636
+ throw new JsrError("Failed to run `jsr publish --dry-run`", {
5637
+ cause: error
5638
+ });
5639
+ }
5640
+ }
5557
5641
  async version() {
5558
5642
  return await this.jsr(["--version"]);
5559
5643
  }
@@ -5948,6 +6032,24 @@ var NpmRegistry = class extends Registry {
5948
6032
  throw this.classifyPublishError(error);
5949
6033
  }
5950
6034
  }
6035
+ async dryRunPublish() {
6036
+ try {
6037
+ await this.npm(["publish", "--dry-run"]);
6038
+ } catch (error) {
6039
+ throw new NpmError("Failed to run `npm publish --dry-run`", {
6040
+ cause: error
6041
+ });
6042
+ }
6043
+ }
6044
+ async twoFactorAuthMode() {
6045
+ try {
6046
+ const output = await this.npm(["profile", "get", "--json"]);
6047
+ const profile = JSON.parse(output);
6048
+ return profile?.tfa?.mode ?? null;
6049
+ } catch {
6050
+ return null;
6051
+ }
6052
+ }
5951
6053
  async isPackageNameAvaliable() {
5952
6054
  return isValidPackageName(this.packageName);
5953
6055
  }
@@ -5984,7 +6086,80 @@ async function npmRegistry() {
5984
6086
  return new NpmRegistry(packageJson.name);
5985
6087
  }
5986
6088
 
6089
+ // src/tasks/dry-run-publish.ts
6090
+ var AUTH_ERROR_PATTERNS = [
6091
+ /401/i,
6092
+ /403/i,
6093
+ /unauthorized/i,
6094
+ /forbidden/i,
6095
+ /invalid.token/i,
6096
+ /eotp/i
6097
+ ];
6098
+ function isAuthError(error) {
6099
+ const message = error instanceof Error ? error.message : String(error);
6100
+ return AUTH_ERROR_PATTERNS.some((pattern) => pattern.test(message));
6101
+ }
6102
+ async function withTokenRetry(registryKey, task, action) {
6103
+ try {
6104
+ await action();
6105
+ } catch (error) {
6106
+ if (!isAuthError(error)) throw error;
6107
+ const config = TOKEN_CONFIG[registryKey];
6108
+ if (!config) throw error;
6109
+ task.output = `Auth failed. Re-enter ${config.promptLabel}`;
6110
+ const newToken = await task.prompt(ListrEnquirerPromptAdapter).run({
6111
+ type: "password",
6112
+ message: `Re-enter ${config.promptLabel}`
6113
+ });
6114
+ new Db().set(config.dbKey, newToken);
6115
+ process.env[config.envVar] = newToken;
6116
+ await action();
6117
+ }
6118
+ }
6119
+ var npmDryRunPublishTask = {
6120
+ title: "Dry-run npm publish",
6121
+ task: async (_, task) => {
6122
+ task.output = "Running npm publish --dry-run...";
6123
+ await withTokenRetry("npm", task, async () => {
6124
+ const npm = await npmRegistry();
6125
+ await npm.dryRunPublish();
6126
+ });
6127
+ }
6128
+ };
6129
+ var jsrDryRunPublishTask = {
6130
+ title: "Dry-run jsr publish",
6131
+ task: async (_, task) => {
6132
+ task.output = "Running jsr publish --dry-run...";
6133
+ await withTokenRetry("jsr", task, async () => {
6134
+ const jsr = await jsrRegistry();
6135
+ await jsr.dryRunPublish();
6136
+ });
6137
+ }
6138
+ };
6139
+ async function getCrateName2(packagePath) {
6140
+ const eco = new RustEcosystem(packagePath ?? process.cwd());
6141
+ return await eco.packageName();
6142
+ }
6143
+ function createCratesDryRunPublishTask(packagePath) {
6144
+ const label = packagePath ? ` (${packagePath})` : "";
6145
+ return {
6146
+ title: `Dry-run crates.io publish${label}`,
6147
+ task: async (_, task) => {
6148
+ task.output = "Running cargo publish --dry-run...";
6149
+ await withTokenRetry("crates", task, async () => {
6150
+ const packageName = await getCrateName2(packagePath);
6151
+ const registry = new CratesRegistry(packageName);
6152
+ await registry.dryRunPublish(packagePath);
6153
+ });
6154
+ }
6155
+ };
6156
+ }
6157
+ var cratesDryRunPublishTask = createCratesDryRunPublishTask();
6158
+
5987
6159
  // src/tasks/jsr.ts
6160
+ import process8 from "node:process";
6161
+ import { ListrEnquirerPromptAdapter as ListrEnquirerPromptAdapter2 } from "@listr2/prompt-adapter-enquirer";
6162
+ import npmCli from "@npmcli/promise-spawn";
5988
6163
  var { open } = npmCli;
5989
6164
  var JsrAvailableError = class extends AbstractError {
5990
6165
  constructor(message, { cause } = {}) {
@@ -6012,7 +6187,7 @@ var jsrAvailableCheckTasks = {
6012
6187
  if (ctx.promptEnabled) {
6013
6188
  const maxAttempts = 3;
6014
6189
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
6015
- JsrClient.token = await task.prompt(ListrEnquirerPromptAdapter).run({
6190
+ JsrClient.token = await task.prompt(ListrEnquirerPromptAdapter2).run({
6016
6191
  type: "password",
6017
6192
  message: `Please enter the jsr ${color.bold("API token")}${attempt > 1 ? ` (attempt ${attempt}/${maxAttempts})` : ""}`,
6018
6193
  footer: `
@@ -6062,7 +6237,7 @@ Generate a token from ${color.bold(link2("jsr.io", "https://jsr.io/account/token
6062
6237
  )
6063
6238
  )).filter((v) => v !== null);
6064
6239
  if (searchResults.length > 0) {
6065
- jsrName = await task.prompt(ListrEnquirerPromptAdapter).run({
6240
+ jsrName = await task.prompt(ListrEnquirerPromptAdapter2).run({
6066
6241
  type: "select",
6067
6242
  message: "Is there a scoped package you want to publish in the already published list?",
6068
6243
  choices: [
@@ -6080,7 +6255,7 @@ Generate a token from ${color.bold(link2("jsr.io", "https://jsr.io/account/token
6080
6255
  }
6081
6256
  const userName = await new Git().userName();
6082
6257
  task.output = "Select the scope of the package to publish";
6083
- jsrName = await task.prompt(ListrEnquirerPromptAdapter).run({
6258
+ jsrName = await task.prompt(ListrEnquirerPromptAdapter2).run({
6084
6259
  type: "select",
6085
6260
  message: "jsr.json does not exist, and the package name is not scoped. Please select a scope for the 'jsr' package",
6086
6261
  choices: [
@@ -6108,7 +6283,7 @@ Generate a token from ${color.bold(link2("jsr.io", "https://jsr.io/account/token
6108
6283
  });
6109
6284
  if (jsrName === "specify") {
6110
6285
  while (!isScopedPackage(jsrName)) {
6111
- jsrName = await task.prompt(ListrEnquirerPromptAdapter).run({
6286
+ jsrName = await task.prompt(ListrEnquirerPromptAdapter2).run({
6112
6287
  type: "input",
6113
6288
  message: "Package name"
6114
6289
  });
@@ -6176,7 +6351,7 @@ var jsrPublishTasks = {
6176
6351
  ${urls.map((url) => ` ${color.cyan(url)}`).join("\n")}`;
6177
6352
  open(urls[0]);
6178
6353
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
6179
- await task.prompt(ListrEnquirerPromptAdapter).run({
6354
+ await task.prompt(ListrEnquirerPromptAdapter2).run({
6180
6355
  type: "input",
6181
6356
  message: `Press ${color.bold("enter")} after creating the package on jsr.io${attempt > 1 ? ` (attempt ${attempt}/${maxAttempts})` : ""}`
6182
6357
  });
@@ -6205,7 +6380,7 @@ ${jsr.packageCreationUrls.join("\n")}`
6205
6380
  // src/tasks/npm.ts
6206
6381
  import { spawn } from "node:child_process";
6207
6382
  import process9 from "node:process";
6208
- import { ListrEnquirerPromptAdapter as ListrEnquirerPromptAdapter2 } from "@listr2/prompt-adapter-enquirer";
6383
+ import { ListrEnquirerPromptAdapter as ListrEnquirerPromptAdapter3 } from "@listr2/prompt-adapter-enquirer";
6209
6384
  import npmCli2 from "@npmcli/promise-spawn";
6210
6385
  var { open: open2 } = npmCli2;
6211
6386
  var NpmAvailableError = class extends AbstractError {
@@ -6217,7 +6392,6 @@ var NpmAvailableError = class extends AbstractError {
6217
6392
  };
6218
6393
  var npmAvailableCheckTasks = {
6219
6394
  title: "Checking npm avaliable for publising",
6220
- skip: (ctx) => !!ctx.preview,
6221
6395
  task: async (ctx, task) => {
6222
6396
  const npm = await npmRegistry();
6223
6397
  if (!await npm.isLoggedIn()) {
@@ -6280,6 +6454,14 @@ var npmAvailableCheckTasks = {
6280
6454
  More information: ${link2("npm naming rules", "https://github.com/npm/validate-npm-package-name?tab=readme-ov-file#naming-rules")}`
6281
6455
  );
6282
6456
  }
6457
+ if (!ctx.promptEnabled) {
6458
+ const tfaMode = await npm.twoFactorAuthMode();
6459
+ if (tfaMode === "auth-and-writes") {
6460
+ throw new NpmAvailableError(
6461
+ `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`
6462
+ );
6463
+ }
6464
+ }
6283
6465
  }
6284
6466
  };
6285
6467
  var npmPublishTasks = {
@@ -6295,7 +6477,7 @@ var npmPublishTasks = {
6295
6477
  const maxAttempts = 3;
6296
6478
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
6297
6479
  result = await npm.publish(
6298
- await task.prompt(ListrEnquirerPromptAdapter2).run({
6480
+ await task.prompt(ListrEnquirerPromptAdapter3).run({
6299
6481
  type: "password",
6300
6482
  message: `npm OTP code${attempt > 1 ? ` (attempt ${attempt}/${maxAttempts})` : ""}`
6301
6483
  })
@@ -6329,8 +6511,64 @@ var npmPublishTasks = {
6329
6511
  }
6330
6512
  };
6331
6513
 
6514
+ // src/tasks/preflight.ts
6515
+ import { ListrEnquirerPromptAdapter as ListrEnquirerPromptAdapter4 } from "@listr2/prompt-adapter-enquirer";
6516
+ import { exec as exec6 } from "tinyexec";
6517
+ var PreflightError = class extends AbstractError {
6518
+ constructor() {
6519
+ super(...arguments);
6520
+ __publicField(this, "name", "Preflight Error");
6521
+ }
6522
+ };
6523
+ async function collectTokens(registries, task) {
6524
+ const existing = loadTokensFromDb(registries);
6525
+ const tokens = { ...existing };
6526
+ for (const registry of registries) {
6527
+ const config = TOKEN_CONFIG[registry];
6528
+ if (!config || tokens[registry]) continue;
6529
+ task.output = `Enter ${config.promptLabel}`;
6530
+ const token = await task.prompt(ListrEnquirerPromptAdapter4).run({
6531
+ type: "password",
6532
+ message: `Enter ${config.promptLabel}`
6533
+ });
6534
+ tokens[registry] = token;
6535
+ new Db().set(config.dbKey, token);
6536
+ }
6537
+ return tokens;
6538
+ }
6539
+ async function syncGhSecrets(tokens) {
6540
+ for (const [registry, token] of Object.entries(tokens)) {
6541
+ const config = TOKEN_CONFIG[registry];
6542
+ if (!config) continue;
6543
+ await exec6("gh", ["secret", "set", config.ghSecretName], {
6544
+ throwOnError: true,
6545
+ nodeOptions: { input: token }
6546
+ });
6547
+ }
6548
+ }
6549
+ async function promptGhSecretsSync(tokens, task) {
6550
+ const shouldSync = await task.prompt(ListrEnquirerPromptAdapter4).run({
6551
+ type: "toggle",
6552
+ message: "Sync tokens to GitHub Secrets?",
6553
+ enabled: "Yes",
6554
+ disabled: "No"
6555
+ });
6556
+ if (shouldSync) {
6557
+ task.output = "Syncing tokens to GitHub Secrets...";
6558
+ try {
6559
+ await syncGhSecrets(tokens);
6560
+ task.output = "Tokens synced to GitHub Secrets.";
6561
+ } catch (error) {
6562
+ throw new PreflightError(
6563
+ "Failed to sync tokens to GitHub Secrets. Ensure `gh` CLI is installed and authenticated (`gh auth login`).",
6564
+ { cause: error }
6565
+ );
6566
+ }
6567
+ }
6568
+ }
6569
+
6332
6570
  // src/tasks/prerequisites-check.ts
6333
- import { ListrEnquirerPromptAdapter as ListrEnquirerPromptAdapter3 } from "@listr2/prompt-adapter-enquirer";
6571
+ import { ListrEnquirerPromptAdapter as ListrEnquirerPromptAdapter5 } from "@listr2/prompt-adapter-enquirer";
6334
6572
  var PrerequisitesCheckError = class extends AbstractError {
6335
6573
  constructor(message, { cause } = {}) {
6336
6574
  super(message, { cause });
@@ -6350,7 +6588,7 @@ var prerequisitesCheckTask = (options) => {
6350
6588
  title: "Verifying current branch is a release branch",
6351
6589
  task: async (ctx, task) => {
6352
6590
  if (await git.branch() !== ctx.branch) {
6353
- const swtichBranch = await task.prompt(ListrEnquirerPromptAdapter3).run({
6591
+ const swtichBranch = await task.prompt(ListrEnquirerPromptAdapter5).run({
6354
6592
  type: "toggle",
6355
6593
  message: `${warningBadge} The current HEAD branch is not the release target branch. Do you want to switch branch to ${ctx.branch}?`,
6356
6594
  enabled: "Yes",
@@ -6372,7 +6610,7 @@ var prerequisitesCheckTask = (options) => {
6372
6610
  task: async (_2, task) => {
6373
6611
  task.output = "Checking for updates with `git fetch`";
6374
6612
  if ((await git.dryFetch()).trim()) {
6375
- const fetch2 = await task.prompt(ListrEnquirerPromptAdapter3).run({
6613
+ const fetch2 = await task.prompt(ListrEnquirerPromptAdapter5).run({
6376
6614
  type: "toggle",
6377
6615
  message: `${warningBadge} Local history is outdated. Do you want to run \`git fetch\`?`,
6378
6616
  enabled: "Yes",
@@ -6389,7 +6627,7 @@ var prerequisitesCheckTask = (options) => {
6389
6627
  }
6390
6628
  task.output = "Checking for updates with `git pull`";
6391
6629
  if (await git.revisionDiffsCount()) {
6392
- const pull = await task.prompt(ListrEnquirerPromptAdapter3).run({
6630
+ const pull = await task.prompt(ListrEnquirerPromptAdapter5).run({
6393
6631
  type: "toggle",
6394
6632
  message: `${warningBadge} Local history is outdated. Do you want to run \`git pull\`?`,
6395
6633
  enabled: "Yes",
@@ -6411,7 +6649,7 @@ var prerequisitesCheckTask = (options) => {
6411
6649
  task: async (ctx, task) => {
6412
6650
  if (await git.status()) {
6413
6651
  task.output = "Local working tree is not clean.";
6414
- if (!await task.prompt(ListrEnquirerPromptAdapter3).run({
6652
+ if (!await task.prompt(ListrEnquirerPromptAdapter5).run({
6415
6653
  type: "toggle",
6416
6654
  message: `${warningBadge} Local working tree is not clean. Do you want to skip?`,
6417
6655
  enabled: "Yes",
@@ -6436,7 +6674,7 @@ var prerequisitesCheckTask = (options) => {
6436
6674
  return void 0;
6437
6675
  }
6438
6676
  if ((await git.commits(latestTag, "HEAD")).length <= 0) {
6439
- if (!await task.prompt(ListrEnquirerPromptAdapter3).run({
6677
+ if (!await task.prompt(ListrEnquirerPromptAdapter5).run({
6440
6678
  type: "toggle",
6441
6679
  message: `${warningBadge} No commits exist from the latest tag. Do you want to skip?`,
6442
6680
  enabled: "Yes",
@@ -6454,7 +6692,7 @@ var prerequisitesCheckTask = (options) => {
6454
6692
  task: async (ctx, task) => {
6455
6693
  const gitTag = `v${ctx.version}`;
6456
6694
  if (await git.checkTagExist(gitTag)) {
6457
- const deleteTag = await task.prompt(ListrEnquirerPromptAdapter3).run({
6695
+ const deleteTag = await task.prompt(ListrEnquirerPromptAdapter5).run({
6458
6696
  type: "toggle",
6459
6697
  message: `${warningBadge} The Git tag '${gitTag}' already exists. Do you want to delete tag?`,
6460
6698
  enabled: "Yes",
@@ -6476,13 +6714,13 @@ var prerequisitesCheckTask = (options) => {
6476
6714
  };
6477
6715
 
6478
6716
  // src/tasks/required-conditions-check.ts
6479
- import { ListrEnquirerPromptAdapter as ListrEnquirerPromptAdapter4 } from "@listr2/prompt-adapter-enquirer";
6717
+ import { ListrEnquirerPromptAdapter as ListrEnquirerPromptAdapter6 } from "@listr2/prompt-adapter-enquirer";
6480
6718
 
6481
6719
  // src/registry/custom-registry.ts
6482
- import { exec as exec6 } from "tinyexec";
6720
+ import { exec as exec7 } from "tinyexec";
6483
6721
  var CustomRegistry = class extends NpmRegistry {
6484
6722
  async npm(args) {
6485
- const { stdout } = await exec6(
6723
+ const { stdout } = await exec7(
6486
6724
  "npm",
6487
6725
  args.concat("--registry", this.registry),
6488
6726
  { throwOnError: true }
@@ -6584,7 +6822,7 @@ var requiredConditionsCheckTask = (options) => createListr({
6584
6822
  task: async (_3, task) => {
6585
6823
  const jsr = await jsrRegistry();
6586
6824
  if (!await jsr.isInstalled()) {
6587
- const install = await task.prompt(ListrEnquirerPromptAdapter4).run({
6825
+ const install = await task.prompt(ListrEnquirerPromptAdapter6).run({
6588
6826
  type: "toggle",
6589
6827
  message: `${warningBadge} jsr is not installed. Do you want to install jsr?`,
6590
6828
  enabled: "Yes",
@@ -6718,14 +6956,66 @@ async function collectPublishTasks(ctx) {
6718
6956
  }
6719
6957
  return collectRegistries(ctx).map(registryTask);
6720
6958
  }
6959
+ function dryRunRegistryTask(registry) {
6960
+ switch (registry) {
6961
+ case "npm":
6962
+ return npmDryRunPublishTask;
6963
+ case "jsr":
6964
+ return jsrDryRunPublishTask;
6965
+ case "crates":
6966
+ return cratesDryRunPublishTask;
6967
+ default:
6968
+ return npmDryRunPublishTask;
6969
+ }
6970
+ }
6971
+ async function collectDryRunPublishTasks(ctx) {
6972
+ if (ctx.packages?.length) {
6973
+ const nonCratesTasks = ctx.packages.flatMap(
6974
+ (pkg) => pkg.registries.filter((reg) => reg !== "crates").map((reg) => dryRunRegistryTask(reg))
6975
+ );
6976
+ const cratesPaths = ctx.packages.filter((pkg) => pkg.registries.includes("crates")).map((pkg) => pkg.path);
6977
+ if (cratesPaths.length === 0) {
6978
+ return nonCratesTasks;
6979
+ }
6980
+ const sortedPaths = await sortCratesByDependencyOrder(cratesPaths);
6981
+ const sequentialCratesTask = {
6982
+ title: "Dry-run crates.io publish (sequential)",
6983
+ task: (_ctx, task) => task.newListr(
6984
+ sortedPaths.map((p) => createCratesDryRunPublishTask(p)),
6985
+ { concurrent: false }
6986
+ )
6987
+ };
6988
+ return [...nonCratesTasks, sequentialCratesTask];
6989
+ }
6990
+ return collectRegistries(ctx).map(dryRunRegistryTask);
6991
+ }
6721
6992
  async function run(options) {
6722
6993
  const ctx = {
6723
6994
  ...options,
6724
6995
  promptEnabled: !isCI2 && process10.stdin.isTTY
6725
6996
  };
6997
+ let cleanupEnv;
6726
6998
  try {
6727
6999
  if (options.contents) process10.chdir(options.contents);
6728
- if (!options.publishOnly) {
7000
+ if (options.preflight) {
7001
+ await createListr({
7002
+ title: "Collecting registry tokens",
7003
+ task: async (ctx2, task) => {
7004
+ const registries2 = collectRegistries(ctx2);
7005
+ const tokens = await collectTokens(registries2, task);
7006
+ await promptGhSecretsSync(tokens, task);
7007
+ cleanupEnv = injectTokensToEnv(tokens);
7008
+ ctx2.promptEnabled = false;
7009
+ }
7010
+ }).run(ctx);
7011
+ await prerequisitesCheckTask({
7012
+ skip: options.skipPrerequisitesCheck
7013
+ }).run(ctx);
7014
+ await requiredConditionsCheckTask({
7015
+ skip: options.skipConditionsCheck
7016
+ }).run(ctx);
7017
+ }
7018
+ if (!options.publishOnly && !options.preflight) {
6729
7019
  await prerequisitesCheckTask({
6730
7020
  skip: options.skipPrerequisitesCheck
6731
7021
  }).run(ctx);
@@ -6739,14 +7029,55 @@ async function run(options) {
6739
7029
  task: async (ctx2, parentTask) => parentTask.newListr(await collectPublishTasks(ctx2), {
6740
7030
  concurrent: true
6741
7031
  })
6742
- } : [
7032
+ } : options.preflight ? [
7033
+ {
7034
+ skip: options.skipTests,
7035
+ title: "Running tests",
7036
+ task: async (ctx2) => {
7037
+ const packageManager = await getPackageManager();
7038
+ try {
7039
+ await exec8(packageManager, ["run", ctx2.testScript], {
7040
+ throwOnError: true
7041
+ });
7042
+ } catch (error) {
7043
+ throw new AbstractError(
7044
+ `Test script '${ctx2.testScript}' failed. Run \`${packageManager} run ${ctx2.testScript}\` locally to see full output.`,
7045
+ { cause: error }
7046
+ );
7047
+ }
7048
+ }
7049
+ },
7050
+ {
7051
+ skip: options.skipBuild,
7052
+ title: "Building the project",
7053
+ task: async (ctx2) => {
7054
+ const packageManager = await getPackageManager();
7055
+ try {
7056
+ await exec8(packageManager, ["run", ctx2.buildScript], {
7057
+ throwOnError: true
7058
+ });
7059
+ } catch (error) {
7060
+ throw new AbstractError(
7061
+ `Build script '${ctx2.buildScript}' failed. Run \`${packageManager} run ${ctx2.buildScript}\` locally to see full output.`,
7062
+ { cause: error }
7063
+ );
7064
+ }
7065
+ }
7066
+ },
7067
+ {
7068
+ title: "Validating publish (dry-run)",
7069
+ task: async (ctx2, parentTask) => parentTask.newListr(await collectDryRunPublishTasks(ctx2), {
7070
+ concurrent: true
7071
+ })
7072
+ }
7073
+ ] : [
6743
7074
  {
6744
7075
  skip: options.skipTests,
6745
7076
  title: "Running tests",
6746
7077
  task: async (ctx2) => {
6747
7078
  const packageManager = await getPackageManager();
6748
7079
  try {
6749
- await exec7(packageManager, ["run", ctx2.testScript], {
7080
+ await exec8(packageManager, ["run", ctx2.testScript], {
6750
7081
  throwOnError: true
6751
7082
  });
6752
7083
  } catch (error) {
@@ -6763,7 +7094,7 @@ async function run(options) {
6763
7094
  task: async (ctx2) => {
6764
7095
  const packageManager = await getPackageManager();
6765
7096
  try {
6766
- await exec7(packageManager, ["run", ctx2.buildScript], {
7097
+ await exec8(packageManager, ["run", ctx2.buildScript], {
6767
7098
  throwOnError: true
6768
7099
  });
6769
7100
  } catch (error) {
@@ -6888,13 +7219,24 @@ ${repositoryUrl}/compare/${lastRev}...${latestTag}`;
6888
7219
  parts.push(`${color.bold(name)} on ${color.red("crates.io")}`);
6889
7220
  }
6890
7221
  }
6891
- console.log(
6892
- `
7222
+ if (options.preflight) {
7223
+ cleanupEnv?.();
7224
+ console.log(
7225
+ `
7226
+
7227
+ \u2705 Preflight check passed. CI publish should succeed for ${parts.join(", ")}.
7228
+ `
7229
+ );
7230
+ } else {
7231
+ console.log(
7232
+ `
6893
7233
 
6894
7234
  \u{1F680} Successfully published ${parts.join(", ")} ${color.blueBright(`v${ctx.version}`)} \u{1F680}
6895
7235
  `
6896
- );
7236
+ );
7237
+ }
6897
7238
  } catch (e2) {
7239
+ cleanupEnv?.();
6898
7240
  consoleError(e2);
6899
7241
  await rollback();
6900
7242
  process10.exit(1);