@dbx-tools/projen 0.9.161 → 0.9.165

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/index.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  // Hand edits are overwritten on the next watch; this file is read-only.
4
4
 
5
5
  export const PACKAGE_IDENTIFIER = "@dbx-tools/projen";
6
- export const PACKAGE_VERSION = "0.9.161";
6
+ export const PACKAGE_VERSION = "0.9.165";
7
7
  export * as appkitWorkspace from "./src/appkit-workspace.ts";
8
8
  export * as barrels from "./src/barrels.ts";
9
9
  export * as bunApp from "./src/bun-app.ts";
@@ -85,7 +85,7 @@ export { PYTHON_NODE_RUNTIME_DISTRIBUTION, PYTHON_NODE_RUNTIME_MODULE, PYTHON_NO
85
85
  export type { PythonNodeRuntimeOptions } from "./src/python-node-runtime.ts";
86
86
  export { pythonDistributionPaths, publishPythonProjects, buildPythonProjects } from "./src/python-packaging.ts";
87
87
  export { pythonProjectInfo, preparePythonProjectForPublication } from "./src/python-release.ts";
88
- export { RELEASE_VERSION, RELEASE_WORKFLOW_FILE, PUBLIC_NPM_REGISTRY_URL, PUBLIC_PYPI_REGISTRY_URL, PUBLICATION_WAIT_TIMEOUT_SECONDS, PUBLICATION_WAIT_INTERVAL_SECONDS } from "./src/release-context.ts";
88
+ export { RELEASE_VERSION, RELEASE_WORKFLOW_FILE, RELEASE_INSTALL_ENV, PUBLIC_NPM_REGISTRY_URL, PUBLIC_PYPI_REGISTRY_URL, PUBLICATION_WAIT_TIMEOUT_SECONDS, PUBLICATION_WAIT_INTERVAL_SECONDS, PUBLICATION_REQUEST_TIMEOUT_SECONDS } from "./src/release-context.ts";
89
89
  export { RELEASE_PUBLISH_TARGETS, RELEASE_INSTALL_MODES, ReleaseStepSelectionSchema, releaseStepSelection, releaseTagAnnotation, parseReleaseTagAnnotation, releasePublishesLocally } from "./src/release-options.ts";
90
90
  export type { ReleasePublishTarget, ReleaseInstallMode, ReleaseStepSelection, ReleaseSelectionOptions } from "./src/release-options.ts";
91
91
  export { tryReleaseWorkflow, releaseTagPattern, releaseCondition, releaseBuildSteps, nodeReleaseSetupSteps, uvSetupStep, refreshReleaseDocsDependencies, refreshPublicationVerification, DBXToolsRelease } from "./src/release.ts";
package/package.json CHANGED
@@ -30,11 +30,11 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "@clack/prompts": "^1.7.0",
33
- "@dbx-tools/auth": "^0.9.161",
34
- "@dbx-tools/cli-args": "^0.9.161",
35
- "@dbx-tools/core": "^0.9.161",
36
- "@dbx-tools/path": "^0.9.161",
37
- "@dbx-tools/shared-core": "^0.9.161",
33
+ "@dbx-tools/auth": "^0.9.165",
34
+ "@dbx-tools/cli-args": "^0.9.165",
35
+ "@dbx-tools/core": "^0.9.165",
36
+ "@dbx-tools/path": "^0.9.165",
37
+ "@dbx-tools/shared-core": "^0.9.165",
38
38
  "commander": "^15.0.0",
39
39
  "concurrently": "^10.0.3",
40
40
  "constructs": "^10.6.0",
@@ -56,7 +56,7 @@
56
56
  },
57
57
  "main": "index.ts",
58
58
  "license": "Apache-2.0",
59
- "version": "0.9.161",
59
+ "version": "0.9.165",
60
60
  "types": "index.ts",
61
61
  "type": "module",
62
62
  "exports": {
@@ -12,9 +12,10 @@ import {
12
12
  import { tmpdir } from "node:os";
13
13
  import { join, relative } from "node:path";
14
14
  import { spawn, spawnSync } from "@dbx-tools/core";
15
- import { log, poll, toError } from "@dbx-tools/shared-core";
15
+ import { combineAbortSignals, log, poll, toError } from "@dbx-tools/shared-core";
16
16
  import {
17
17
  PUBLIC_NPM_REGISTRY_URL,
18
+ PUBLICATION_REQUEST_TIMEOUT_SECONDS,
18
19
  PUBLICATION_WAIT_INTERVAL_SECONDS,
19
20
  PUBLICATION_WAIT_TIMEOUT_SECONDS,
20
21
  } from "./release-context.ts";
@@ -79,11 +80,15 @@ export async function publishedNpmRelease(
79
80
  version: string,
80
81
  registry = process.env.NPM_CONFIG_REGISTRY ?? PUBLIC_NPM_REGISTRY_URL,
81
82
  signal?: AbortSignal,
82
- options: { readonly content?: boolean } = {},
83
+ options: { readonly content?: boolean; readonly timeoutMs?: number } = {},
83
84
  ): Promise<NpmReleaseIdentity | undefined> {
85
+ const requestSignal = combineAbortSignals(
86
+ signal,
87
+ AbortSignal.timeout(options.timeoutMs ?? PUBLICATION_REQUEST_TIMEOUT_SECONDS * 1_000),
88
+ );
84
89
  const response = await fetch(registryUrl(registry, name, version), {
85
90
  headers: { accept: "application/json" },
86
- signal,
91
+ signal: requestSignal,
87
92
  });
88
93
  if (response.status === 404) return undefined;
89
94
  if (!response.ok) {
@@ -100,7 +105,8 @@ export async function publishedNpmRelease(
100
105
  }
101
106
  let contentDigest: string | undefined;
102
107
  if (options.content !== false && metadata.dist?.tarball) {
103
- const archive = await fetch(metadata.dist.tarball, { signal });
108
+ const archive = await fetch(metadata.dist.tarball, { signal: requestSignal });
109
+ if (archive.status === 404) return undefined;
104
110
  if (!archive.ok) {
105
111
  throw new Error(`npm tarball lookup failed for ${name}@${version}: ${archive.status}`);
106
112
  }
@@ -132,24 +138,47 @@ export function isNpmStagedVersionConflict(error: unknown): boolean {
132
138
  export async function waitForMatchingNpmRelease(
133
139
  local: NpmReleaseIdentity,
134
140
  registry?: string,
135
- options: { readonly intervalMs?: number; readonly timeoutMs?: number } = {},
141
+ options: {
142
+ readonly intervalMs?: number;
143
+ readonly requestTimeoutMs?: number;
144
+ readonly timeoutMs?: number;
145
+ } = {},
136
146
  ): Promise<void> {
137
147
  const selectedRegistry = registry ?? process.env.NPM_CONFIG_REGISTRY ?? PUBLIC_NPM_REGISTRY_URL;
148
+ let lastError: Error | undefined;
138
149
  try {
139
150
  for await (const _available of poll(
140
151
  async ({ signal }) => {
141
- const published = await publishedNpmRelease(
142
- local.name,
143
- local.version,
144
- selectedRegistry,
145
- signal,
146
- { content: false },
147
- );
152
+ let published: NpmReleaseIdentity | undefined;
153
+ try {
154
+ published = await publishedNpmRelease(
155
+ local.name,
156
+ local.version,
157
+ selectedRegistry,
158
+ signal,
159
+ { content: false, timeoutMs: options.requestTimeoutMs },
160
+ );
161
+ } catch (error) {
162
+ lastError = toError(error);
163
+ return false;
164
+ }
148
165
  if (!published) return false;
149
- return npmReleaseMatches(
150
- local,
151
- await publishedNpmRelease(local.name, local.version, selectedRegistry, signal),
152
- );
166
+ let complete: NpmReleaseIdentity | undefined;
167
+ try {
168
+ complete = await publishedNpmRelease(
169
+ local.name,
170
+ local.version,
171
+ selectedRegistry,
172
+ signal,
173
+ { timeoutMs: options.requestTimeoutMs },
174
+ );
175
+ } catch (error) {
176
+ lastError = toError(error);
177
+ return false;
178
+ }
179
+ if (!complete) return false;
180
+ lastError = undefined;
181
+ return npmReleaseMatches(local, complete);
153
182
  },
154
183
  {
155
184
  intervalMs: options.intervalMs ?? PUBLICATION_WAIT_INTERVAL_SECONDS * 1_000,
@@ -160,10 +189,9 @@ export async function waitForMatchingNpmRelease(
160
189
  void _available;
161
190
  }
162
191
  } catch (error) {
163
- throw new Error(
164
- `Staged npm version ${local.name}@${local.version} did not become available`,
165
- { cause: error },
166
- );
192
+ throw new Error(`Staged npm version ${local.name}@${local.version} did not become available`, {
193
+ cause: lastError ?? error,
194
+ });
167
195
  }
168
196
  }
169
197
 
@@ -178,6 +206,7 @@ export async function publishNpmArchive(options: {
178
206
  readonly dryRun?: boolean;
179
207
  readonly env?: NodeJS.ProcessEnv;
180
208
  readonly intervalMs?: number;
209
+ readonly requestTimeoutMs?: number;
181
210
  readonly registry?: string;
182
211
  readonly timeoutMs?: number;
183
212
  }): Promise<NpmPublishOutcome> {
@@ -205,7 +234,9 @@ export async function publishNpmArchive(options: {
205
234
  check: false,
206
235
  },
207
236
  );
208
- if (result.stderr) process.stderr.write(result.stderr.endsWith("\n") ? result.stderr : `${result.stderr}\n`);
237
+ if (result.stderr) {
238
+ process.stderr.write(result.stderr.endsWith("\n") ? result.stderr : `${result.stderr}\n`);
239
+ }
209
240
  if (result.exitCode === 0) return "published";
210
241
  const error = new Error(
211
242
  `npm publish failed (exit ${result.exitCode})${result.stderr ? `: ${result.stderr}` : ""}`,
@@ -217,6 +248,7 @@ export async function publishNpmArchive(options: {
217
248
  logger.info(`waiting for staged ${identity.name}@${identity.version}`);
218
249
  await waitForMatchingNpmRelease(identity, options.registry, {
219
250
  intervalMs: options.intervalMs,
251
+ requestTimeoutMs: options.requestTimeoutMs,
220
252
  timeoutMs: options.timeoutMs,
221
253
  });
222
254
  return "staged";
@@ -3,6 +3,10 @@
3
3
  export const RELEASE_VERSION = "${{ needs.build-release.outputs.release_version }}";
4
4
  /** Generated GitHub Actions workflow triggered by release tags. */
5
5
  export const RELEASE_WORKFLOW_FILE = "release.yml";
6
+ /** Environment applied to release dependency installation and synthesis. */
7
+ export const RELEASE_INSTALL_ENV = {
8
+ ONNXRUNTIME_NODE_INSTALL: "skip",
9
+ } as const;
6
10
  /** Public npm registry queried after npm publication completes. */
7
11
  export const PUBLIC_NPM_REGISTRY_URL = "https://registry.npmjs.org";
8
12
  /** Public PyPI JSON API queried after Python publication completes. */
@@ -11,3 +15,5 @@ export const PUBLIC_PYPI_REGISTRY_URL = "https://pypi.org/pypi";
11
15
  export const PUBLICATION_WAIT_TIMEOUT_SECONDS = 20 * 60;
12
16
  /** Delay between public registry probes while a release is still invisible. */
13
17
  export const PUBLICATION_WAIT_INTERVAL_SECONDS = 5;
18
+ /** Maximum duration of one public registry request before the next poll can proceed. */
19
+ export const PUBLICATION_REQUEST_TIMEOUT_SECONDS = 10;
package/src/release.ts CHANGED
@@ -19,6 +19,7 @@ import { projectReleaseBranch, taskCommand, type DBXToolsJavaScriptProject } fro
19
19
  import {
20
20
  PUBLIC_NPM_REGISTRY_URL,
21
21
  PUBLICATION_WAIT_TIMEOUT_SECONDS,
22
+ RELEASE_INSTALL_ENV,
22
23
  RELEASE_VERSION,
23
24
  RELEASE_WORKFLOW_FILE,
24
25
  } from "./release-context.ts";
@@ -254,6 +255,7 @@ function releaseBuildJob(
254
255
  timeoutMinutes: 60,
255
256
  env: {
256
257
  CI: "true",
258
+ ...RELEASE_INSTALL_ENV,
257
259
  ...(options.docs
258
260
  ? { DOCS_SITE_URL: options.docs.siteUrl, DOCS_BASE: options.docs.base ?? "/" }
259
261
  : {}),
package/tasks/release.ts CHANGED
@@ -13,7 +13,7 @@ import { TaskDirectoriesOptionSchema, TaskRootOptionSchema } from "./options.ts"
13
13
  import { writeReleaseNotes } from "./release-notes.ts";
14
14
  import { assertReleaseVersion } from "./release-version.ts";
15
15
  import { captureTaskCommand, runTaskCommand, taskCommandSucceeds } from "../src/_task-command.ts";
16
- import { RELEASE_WORKFLOW_FILE } from "../src/release-context.ts";
16
+ import { RELEASE_INSTALL_ENV, RELEASE_WORKFLOW_FILE } from "../src/release-context.ts";
17
17
  import {
18
18
  RELEASE_INSTALL_MODES,
19
19
  RELEASE_PUBLISH_TARGETS,
@@ -504,7 +504,11 @@ export async function runRelease(
504
504
  throw new Error("--no-release-notes cannot be combined with supplied release notes");
505
505
  }
506
506
 
507
- if (install === "always") runTaskCommand(root, "bun", ["install"]);
507
+ if (install === "always") {
508
+ runTaskCommand(root, "bun", ["install"], {
509
+ env: { ...process.env, ...RELEASE_INSTALL_ENV },
510
+ });
511
+ }
508
512
  if (selection.validation && hasPackageScript(root, "eslint:fix")) {
509
513
  runTaskCommand(root, "bun", ["run", "eslint:fix"]);
510
514
  }
@@ -514,7 +518,11 @@ export async function runRelease(
514
518
  runTaskCommand(root, "git", ["fetch", remote, branch, "--tags"]);
515
519
  if (options.bump ?? true) {
516
520
  runTaskCommand(root, "bun", ["run", "bump"], {
517
- env: { ...process.env, DBX_TOOLS_RELEASE_INSTALL: install === "auto" ? "auto" : "never" },
521
+ env: {
522
+ ...process.env,
523
+ ...RELEASE_INSTALL_ENV,
524
+ DBX_TOOLS_RELEASE_INSTALL: install === "auto" ? "auto" : "never",
525
+ },
518
526
  });
519
527
  }
520
528
 
@@ -14,12 +14,14 @@ import {
14
14
  import {
15
15
  PUBLIC_NPM_REGISTRY_URL,
16
16
  PUBLIC_PYPI_REGISTRY_URL,
17
+ PUBLICATION_REQUEST_TIMEOUT_SECONDS,
17
18
  PUBLICATION_WAIT_INTERVAL_SECONDS,
18
19
  PUBLICATION_WAIT_TIMEOUT_SECONDS,
19
20
  } from "../src/release-context.ts";
20
21
 
21
22
  const DEFAULT_CONCURRENCY = 8;
22
23
  const DEFAULT_INTERVAL_SECONDS = PUBLICATION_WAIT_INTERVAL_SECONDS;
24
+ const DEFAULT_REQUEST_TIMEOUT_SECONDS = PUBLICATION_REQUEST_TIMEOUT_SECONDS;
23
25
  const DEFAULT_TIMEOUT_SECONDS = PUBLICATION_WAIT_TIMEOUT_SECONDS;
24
26
  const logger = log.logger("projen:verify-publications");
25
27
 
@@ -60,9 +62,16 @@ export interface PublicationProbe {
60
62
  export interface PublicationWaitOptions {
61
63
  readonly concurrency?: number;
62
64
  readonly intervalMs?: number;
65
+ readonly requestTimeoutMs?: number;
63
66
  readonly timeoutMs?: number;
64
67
  }
65
68
 
69
+ /** Human-readable state for one sorted set of unresolved publications. */
70
+ export function publicationWaitMessage(probes: readonly PublicationProbe[]): string {
71
+ const labels = probes.map(({ label }) => label).sort((left, right) => left.localeCompare(right));
72
+ return `waiting for ${labels.length} public publication${labels.length === 1 ? "" : "s"} - ${labels.join(", ")}`;
73
+ }
74
+
66
75
  /** Poll every unresolved publication concurrently and stop after one shared timeout. */
67
76
  export async function waitForPublications(
68
77
  probes: readonly PublicationProbe[],
@@ -70,9 +79,10 @@ export async function waitForPublications(
70
79
  ): Promise<void> {
71
80
  if (probes.length === 0) throw new Error("No public publications were provided");
72
81
  const concurrency = options.concurrency ?? DEFAULT_CONCURRENCY;
82
+ const requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_SECONDS * 1_000;
73
83
  const errors = new Map<string, Error>();
74
- let pending = [...probes];
75
- let previousCount: number | undefined;
84
+ let pending = [...probes].sort((left, right) => left.label.localeCompare(right.label));
85
+ let previousPending = "";
76
86
  try {
77
87
  for await (const unavailable of poll<PublicationProbe[]>(
78
88
  async ({ signal }) => {
@@ -80,7 +90,9 @@ export async function waitForPublications(
80
90
  pending,
81
91
  async (probe) => {
82
92
  try {
83
- const available = await probe.available(signal);
93
+ const available = await probe.available(
94
+ AbortSignal.any([signal, AbortSignal.timeout(requestTimeoutMs)]),
95
+ );
84
96
  if (available) errors.delete(probe.label);
85
97
  return { available, probe };
86
98
  } catch (error) {
@@ -90,7 +102,10 @@ export async function waitForPublications(
90
102
  },
91
103
  { concurrency },
92
104
  );
93
- return results.filter(({ available }) => !available).map(({ probe }) => probe);
105
+ return results
106
+ .filter(({ available }) => !available)
107
+ .map(({ probe }) => probe)
108
+ .sort((left, right) => left.label.localeCompare(right.label));
94
109
  },
95
110
  {
96
111
  intervalMs: options.intervalMs ?? DEFAULT_INTERVAL_SECONDS * 1_000,
@@ -99,12 +114,11 @@ export async function waitForPublications(
99
114
  },
100
115
  )) {
101
116
  pending = unavailable;
102
- if (pending.length > 0 && pending.length !== previousCount) {
103
- logger.info(
104
- `waiting for ${pending.length} public publication${pending.length === 1 ? "" : "s"}`,
105
- );
117
+ const pendingKey = pending.map(({ label }) => label).join("\0");
118
+ if (pending.length > 0 && pendingKey !== previousPending) {
119
+ logger.info(publicationWaitMessage(pending));
106
120
  }
107
- previousCount = pending.length;
121
+ previousPending = pendingKey;
108
122
  }
109
123
  } catch (error) {
110
124
  if (pending.length === 0) throw error;
@@ -243,6 +257,11 @@ export const VerifyPublicationsOptionsSchema = z
243
257
  .positive()
244
258
  .default(DEFAULT_TIMEOUT_SECONDS)
245
259
  .describe("Maximum time to wait for the complete publication batch"),
260
+ requestTimeoutSeconds: z.coerce
261
+ .number()
262
+ .positive()
263
+ .default(DEFAULT_REQUEST_TIMEOUT_SECONDS)
264
+ .describe("Maximum duration of one public registry request"),
246
265
  })
247
266
  .refine((options) => options.npmDirectory || options.pypiDirectory, {
248
267
  message: "At least one publication directory is required",
@@ -271,6 +290,7 @@ export async function main(args: readonly string[] = process.argv.slice(2)): Pro
271
290
  await waitForPublications(probes, {
272
291
  concurrency: options.concurrency,
273
292
  intervalMs: options.intervalSeconds * 1_000,
293
+ requestTimeoutMs: options.requestTimeoutSeconds * 1_000,
274
294
  timeoutMs: options.timeoutSeconds * 1_000,
275
295
  });
276
296
  }