@dbx-tools/projen 0.9.19 → 0.9.28

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.
Files changed (56) hide show
  1. package/README.md +83 -229
  2. package/index.ts +11 -18
  3. package/package.json +9 -5
  4. package/src/_task-command.ts +75 -0
  5. package/src/barrels.ts +1 -74
  6. package/src/module-exports.ts +175 -1
  7. package/src/openapi.ts +5 -72
  8. package/src/packages.ts +45 -0
  9. package/src/project-js.ts +5 -48
  10. package/src/project-predicate.ts +1 -2
  11. package/src/project-py.ts +87 -199
  12. package/src/project.ts +2 -2
  13. package/src/python-node-bindings.ts +138 -0
  14. package/src/python-node-bundle.ts +92 -0
  15. package/src/release-catalog.ts +40 -208
  16. package/src/{release-dispatch.ts → release-context.ts} +0 -11
  17. package/src/release.ts +74 -778
  18. package/src/scaffold.ts +2 -6
  19. package/src/workspace-version.ts +9 -121
  20. package/tasks/bump.ts +17 -34
  21. package/tasks/databricks-cli-assets.ts +129 -0
  22. package/tasks/local-publish.ts +61 -138
  23. package/tasks/publish-npm.ts +71 -30
  24. package/tasks/publish-python.ts +61 -148
  25. package/tasks/publish.ts +32 -182
  26. package/tasks/python-node-bindings-watch.ts +47 -0
  27. package/tasks/python-node-bindings.ts +1017 -0
  28. package/tasks/python-registry.ts +2 -9
  29. package/tasks/python-release.ts +74 -0
  30. package/tasks/release-version.ts +23 -2
  31. package/tasks/release.ts +99 -0
  32. package/tasks/sync.ts +13 -18
  33. package/tasks/version-check.ts +13 -72
  34. package/src/_rust-project.ts +0 -429
  35. package/src/_rust-release-workflow.ts +0 -698
  36. package/src/project-rs.ts +0 -1127
  37. package/src/release-plan.ts +0 -269
  38. package/src/release-please.ts +0 -242
  39. package/src/uniffi.ts +0 -65
  40. package/tasks/publish-uniffi-local.ts +0 -314
  41. package/tasks/release-assets.ts +0 -476
  42. package/tasks/release-bootstrap.ts +0 -59
  43. package/tasks/release-candidate.ts +0 -383
  44. package/tasks/release-manifest.ts +0 -273
  45. package/tasks/release-plan.ts +0 -89
  46. package/tasks/release-please.ts +0 -162
  47. package/tasks/release-pr.ts +0 -563
  48. package/tasks/release-request.ts +0 -166
  49. package/tasks/release-summary-units.ts +0 -75
  50. package/tasks/release-summary.ts +0 -381
  51. package/tasks/rust.ts +0 -178
  52. package/tasks/stamp-python.ts +0 -13
  53. package/tasks/uniffi-python.d.ts +0 -61
  54. package/tasks/uniffi-python.js +0 -191
  55. package/tasks/uniffi-release.mjs +0 -573
  56. package/tasks/uniffi.ts +0 -271
@@ -1,30 +1,26 @@
1
1
  /**
2
2
  * Cross-language release-unit ownership and dependency graph.
3
3
  *
4
- * The catalog is the version lookup and release-planning seam shared by every
5
- * generated project. Fixed mode preserves the root VERSION contract while the
6
- * same registrations and graph are exercised before independent versions are
7
- * enabled.
4
+ * The catalog owns cross-language package identity, release grouping, and
5
+ * publication order. Every registered project reads its version from the root
6
+ * VERSION file.
8
7
  *
9
8
  * @module
10
9
  */
11
10
 
12
- import { execFileSync } from "node:child_process";
13
11
  import { createHash } from "node:crypto";
14
12
  import { existsSync, readFileSync, readdirSync } from "node:fs";
15
13
  import { basename, join, relative, resolve } from "node:path";
16
- import { Component, JsonFile, type Project } from "projen";
14
+ import { Component, type Project } from "projen";
15
+ import { probeTaskCommand } from "./_task-command.ts";
17
16
  import { toPosix } from "./packages.ts";
18
17
  import { readWorkspaceVersion } from "./workspace-version.ts";
19
18
 
20
- /** Version source used by the generated workspace. */
21
- export type DBXToolsVersioningMode = "fixed" | "independent";
22
-
23
19
  /** Runtime family represented by a release project. */
24
- export type ReleaseProjectLanguage = "javascript" | "python" | "rust";
20
+ export type ReleaseProjectLanguage = "javascript" | "python";
25
21
 
26
22
  /** Publishable output owned by one release unit. */
27
- export type ReleaseArtifactKind = "npm" | "pypi" | "cargo" | "github-binary" | "documentation";
23
+ export type ReleaseArtifactKind = "npm" | "pypi" | "documentation";
28
24
 
29
25
  /** Relationship between two projects or release units. */
30
26
  export type ReleaseEdgeKind =
@@ -51,11 +47,6 @@ export interface ReleaseUnitRule {
51
47
 
52
48
  /** Root catalog configuration. */
53
49
  export interface DBXToolsReleaseCatalogOptions {
54
- readonly mode?: DBXToolsVersioningMode;
55
- readonly manifestFile?: string;
56
- readonly graphFile?: string;
57
- readonly releasePleaseConfigFile?: string;
58
- readonly bootstrapSha?: string;
59
50
  readonly units?: readonly ReleaseUnitRule[];
60
51
  readonly externalProjects?: readonly ExternalReleaseProjectRegistration[];
61
52
  }
@@ -151,7 +142,6 @@ export interface ReleaseUnit {
151
142
  /** Generated execution graph consumed by validation and release tasks. */
152
143
  export interface ReleaseUnitGraph {
153
144
  readonly schemaVersion: 1;
154
- readonly mode: DBXToolsVersioningMode;
155
145
  readonly units: readonly ReleaseUnit[];
156
146
  readonly projects: readonly ReleaseProjectNode[];
157
147
  readonly artifacts: readonly ReleaseArtifact[];
@@ -182,8 +172,6 @@ const IGNORED_DIRECTORIES = new Set([
182
172
  ]);
183
173
 
184
174
  const IGNORED_FILES = new Set([
185
- "Cargo.lock",
186
- "Cargo.toml",
187
175
  "package.json",
188
176
  "pyproject.toml",
189
177
  "uv.lock",
@@ -194,7 +182,7 @@ const IGNORED_FILES = new Set([
194
182
 
195
183
  /** Stable default component id for one publishable project. */
196
184
  export function defaultReleaseUnitId(language: ReleaseProjectLanguage, identity: string): string {
197
- const prefix = language === "javascript" ? "node" : language === "python" ? "python" : "rs";
185
+ const prefix = language === "javascript" ? "node" : "python";
198
186
  const unscoped = identity.includes("/")
199
187
  ? identity.slice(identity.lastIndexOf("/") + 1)
200
188
  : identity;
@@ -219,22 +207,6 @@ export function defaultPublishOrder(kind: ReleaseEdgeKind): boolean {
219
207
  return !["test", "development"].includes(kind);
220
208
  }
221
209
 
222
- /** Read one component version, falling back to fixed workspace compatibility. */
223
- export function readReleaseUnitVersion(
224
- root: string,
225
- component: string,
226
- manifestFile = ".release-please-manifest.json",
227
- ): string {
228
- const path = resolve(root, manifestFile);
229
- if (!existsSync(path)) return readWorkspaceVersion(root);
230
- const parsed = JSON.parse(readFileSync(path, "utf8")) as Record<string, unknown>;
231
- const version = parsed[`.release-units/${component}`];
232
- if (typeof version !== "string") {
233
- throw new Error(`Release Please manifest is missing .release-units/${component}`);
234
- }
235
- return version;
236
- }
237
-
238
210
  /**
239
211
  * Root release catalog shared by every generated language project.
240
212
  *
@@ -243,12 +215,6 @@ export function readReleaseUnitVersion(
243
215
  * after project construction.
244
216
  */
245
217
  export class DBXToolsReleaseCatalog extends Component {
246
- readonly mode: DBXToolsVersioningMode;
247
- readonly manifestFile: string;
248
- readonly graphFile: string;
249
- readonly releasePleaseConfigFile: string;
250
- readonly bootstrapSha?: string;
251
-
252
218
  private readonly projects = new Map<Project, RegisteredProject>();
253
219
  private readonly externalProjects = new Map<string, ExternalReleaseProjectRegistration>();
254
220
  private readonly artifacts = new Map<string, RegisteredArtifact>();
@@ -257,37 +223,11 @@ export class DBXToolsReleaseCatalog extends Component {
257
223
  dependency: ReleaseDependencyInput;
258
224
  }> = [];
259
225
  private readonly rules: ReleaseUnitRule[];
260
- private readonly markerFiles = new Set<string>();
261
226
 
262
227
  constructor(project: Project, options: DBXToolsReleaseCatalogOptions = {}) {
263
228
  super(project);
264
- this.mode = options.mode ?? "fixed";
265
- this.manifestFile = options.manifestFile ?? ".release-please-manifest.json";
266
- this.graphFile = options.graphFile ?? ".projen/release-units.json";
267
- this.releasePleaseConfigFile = options.releasePleaseConfigFile ?? "release-please-config.json";
268
- this.bootstrapSha = options.bootstrapSha;
269
229
  this.rules = [...(options.units ?? [])];
270
230
  for (const external of options.externalProjects ?? []) this.registerExternalProject(external);
271
- if (this.mode === "independent") {
272
- new JsonFile(project, this.graphFile, {
273
- marker: false,
274
- readonly: true,
275
- obj: () => this.graph(),
276
- });
277
- new JsonFile(project, this.releasePleaseConfigFile, {
278
- marker: false,
279
- readonly: true,
280
- obj: () => this.releasePleaseConfig(),
281
- });
282
- project
283
- .addTask("release:bootstrap", {
284
- description: "Bootstrap Release Please state from release units",
285
- })
286
- .exec("bun node_modules/@dbx-tools/projen/tasks/release-bootstrap.ts");
287
- } else {
288
- project.tryRemoveFile(this.graphFile);
289
- project.tryRemoveFile(this.releasePleaseConfigFile);
290
- }
291
231
  }
292
232
 
293
233
  /** Register one attached Projen project. */
@@ -349,10 +289,7 @@ export class DBXToolsReleaseCatalog extends Component {
349
289
  versionFor(project: Project): string {
350
290
  const registered = this.projects.get(project);
351
291
  if (!registered) throw new Error(`Release project is not registered: ${project.outdir}`);
352
- if (!resolveValue(registered.registration.publish ?? true)) {
353
- return this.mode === "independent" ? "0.0.0" : readWorkspaceVersion(this.project.outdir);
354
- }
355
- return this.versionForUnit(this.unitFor(registered));
292
+ return this.workspaceVersion();
356
293
  }
357
294
 
358
295
  /** Stable unit, component, and version identity for a registered project. */
@@ -372,58 +309,23 @@ export class DBXToolsReleaseCatalog extends Component {
372
309
  }
373
310
 
374
311
  /** Current version for a project selected by its repository-relative path. */
375
- versionForPath(path: string): string {
376
- const normalized = toPosix(path).replace(/^\.\//, "") || ".";
377
- const attached = [...this.projects.values()].find(
378
- (registered) => this.projectId(registered.project) === normalized,
379
- );
380
- if (attached) return this.versionFor(attached.project);
381
- const external = this.externalProjects.get(normalized);
382
- if (!external) {
383
- if (this.mode === "fixed") return readWorkspaceVersion(this.project.outdir);
384
- throw new Error(`Release project path is not registered: ${normalized}`);
385
- }
386
- if (external.publish === false) {
387
- return this.mode === "independent" ? "0.0.0" : readWorkspaceVersion(this.project.outdir);
388
- }
389
- const unit =
390
- external.unit ??
391
- this.rules.find(
392
- (rule) =>
393
- rule.projectPaths?.includes(normalized) ||
394
- rule.projectIdentities?.includes(external.identity),
395
- )?.id ??
396
- defaultReleaseUnitId(external.language, external.identity);
397
- return this.versionForUnit(unit);
312
+ versionForPath(_path: string): string {
313
+ return this.workspaceVersion();
398
314
  }
399
315
 
400
316
  /** Resolve a version before the corresponding Projen project is constructed. */
401
317
  versionForRegistration(
402
- language: ReleaseProjectLanguage,
403
- identity: string,
404
- path: string,
405
- unit?: string,
318
+ _language: ReleaseProjectLanguage,
319
+ _identity: string,
320
+ _path: string,
321
+ _unit?: string,
406
322
  ): string {
407
- const normalized = toPosix(path).replace(/^\.\//, "") || ".";
408
- const resolvedUnit =
409
- unit ??
410
- this.rules.find(
411
- (rule) =>
412
- rule.projectPaths?.includes(normalized) || rule.projectIdentities?.includes(identity),
413
- )?.id ??
414
- defaultReleaseUnitId(language, identity);
415
- return this.versionForUnit(resolvedUnit);
323
+ return this.workspaceVersion();
416
324
  }
417
325
 
418
326
  /** Current version for a release unit. */
419
- versionForUnit(unit: string): string {
420
- if (this.mode === "fixed") return readWorkspaceVersion(this.project.outdir);
421
- const component = this.rules.find((rule) => rule.id === unit)?.component ?? unit;
422
- const versions = this.readIndependentVersions();
423
- const key = `.release-units/${component}`;
424
- const version = versions[key];
425
- if (!version) throw new Error(`Release Please manifest is missing ${key}`);
426
- return version;
327
+ versionForUnit(_unit: string): string {
328
+ return this.workspaceVersion();
427
329
  }
428
330
 
429
331
  /** Build and validate the normalized release graph. */
@@ -510,7 +412,7 @@ export class DBXToolsReleaseCatalog extends Component {
510
412
  return {
511
413
  id,
512
414
  component: unit.component,
513
- version: this.versionForResolvedUnit(id, unit.component),
415
+ version: this.workspaceVersion(),
514
416
  projects: [...unit.projects].sort(),
515
417
  artifacts: [...unit.artifacts].sort(),
516
418
  sourceHash: hashStrings(hashes),
@@ -520,7 +422,6 @@ export class DBXToolsReleaseCatalog extends Component {
520
422
 
521
423
  return {
522
424
  schemaVersion: 1,
523
- mode: this.mode,
524
425
  units: serializedUnits,
525
426
  projects,
526
427
  artifacts,
@@ -529,55 +430,6 @@ export class DBXToolsReleaseCatalog extends Component {
529
430
  };
530
431
  }
531
432
 
532
- /** Generated Release Please manifest-mode configuration. */
533
- releasePleaseConfig(): Record<string, unknown> {
534
- return {
535
- $schema:
536
- "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
537
- "release-type": "simple",
538
- "separate-pull-requests": false,
539
- "include-component-in-tag": true,
540
- "include-v-in-tag": true,
541
- ...(this.bootstrapSha ? { "bootstrap-sha": this.bootstrapSha } : {}),
542
- packages: Object.fromEntries(
543
- this.graph().units.map((unit) => [
544
- `.release-units/${unit.component}`,
545
- {
546
- "release-type": "simple",
547
- "package-name": unit.id,
548
- component: unit.component,
549
- "changelog-path": "CHANGELOG.md",
550
- "version-file": "version.txt",
551
- },
552
- ]),
553
- ),
554
- };
555
- }
556
-
557
- public override preSynthesize(): void {
558
- const graph = this.graph();
559
- if (this.mode === "fixed") return;
560
- for (const unit of graph.units) {
561
- const path = `.release-units/${unit.component}/source.json`;
562
- if (this.markerFiles.has(path)) continue;
563
- this.markerFiles.add(path);
564
- new JsonFile(this.project, path, {
565
- marker: false,
566
- readonly: true,
567
- obj: () => {
568
- const current = this.graph().units.find((candidate) => candidate.id === unit.id);
569
- if (!current) throw new Error(`Release unit disappeared during synthesis: ${unit.id}`);
570
- return {
571
- schemaVersion: 1,
572
- unit: current.id,
573
- component: current.component,
574
- sourceHash: current.sourceHash,
575
- };
576
- },
577
- });
578
- }
579
- }
580
-
581
433
  private projectNode(registered: RegisteredProject): ReleaseProjectNode {
582
434
  const identity = resolveValue(registered.registration.identity);
583
435
  const publish = resolveValue(registered.registration.publish ?? true);
@@ -651,6 +503,10 @@ export class DBXToolsReleaseCatalog extends Component {
651
503
  return toPosix(relative(this.project.outdir, project.outdir)) || ".";
652
504
  }
653
505
 
506
+ private workspaceVersion(): string {
507
+ return readWorkspaceVersion(this.project.outdir);
508
+ }
509
+
654
510
  private dependencies(
655
511
  byIdentity: ReadonlyMap<string, readonly ReleaseProjectNode[]>,
656
512
  byId: ReadonlyMap<string, ReleaseProjectNode>,
@@ -719,31 +575,6 @@ export class DBXToolsReleaseCatalog extends Component {
719
575
  [a.from, a.to, a.kind].join("\0").localeCompare([b.from, b.to, b.kind].join("\0")),
720
576
  );
721
577
  }
722
-
723
- private versionForResolvedUnit(unit: string, component: string): string {
724
- if (this.mode === "fixed") return readWorkspaceVersion(this.project.outdir);
725
- const key = `.release-units/${component}`;
726
- const version = this.readIndependentVersions()[key];
727
- if (!version) throw new Error(`Release Please manifest is missing ${key} for ${unit}`);
728
- return version;
729
- }
730
-
731
- private readIndependentVersions(): Record<string, string> {
732
- const path = resolve(this.project.outdir, this.manifestFile);
733
- if (!existsSync(path)) throw new Error(`Independent version manifest not found: ${path}`);
734
- const parsed = JSON.parse(readFileSync(path, "utf8")) as unknown;
735
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
736
- throw new Error(`Independent version manifest must contain an object: ${path}`);
737
- }
738
- return Object.fromEntries(
739
- Object.entries(parsed).map(([key, value]) => {
740
- if (typeof value !== "string" || !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(value)) {
741
- throw new Error(`Invalid independent version for ${key}: ${String(value)}`);
742
- }
743
- return [key, value];
744
- }),
745
- );
746
- }
747
578
  }
748
579
 
749
580
  /** Publish dependency batches ordered with dependencies before dependents. */
@@ -795,22 +626,23 @@ function hashPaths(root: string, paths: readonly string[]): string {
795
626
 
796
627
  function gitReleaseFiles(root: string, paths: readonly string[]): string[] | undefined {
797
628
  const relativePaths = paths.map((path) => toPosix(relative(root, path)));
798
- try {
799
- const output = execFileSync(
800
- "git",
801
- ["ls-files", "--cached", "--others", "--exclude-standard", "-z", "--", ...relativePaths],
802
- { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] },
803
- );
804
- return output
805
- .split("\0")
806
- .filter(Boolean)
807
- .map((path) => resolve(root, path))
808
- .filter((path) => existsSync(path))
809
- .filter((path) => paths.some((sourcePath) => trackedReleaseFile(sourcePath, path)))
810
- .sort();
811
- } catch {
812
- return undefined;
813
- }
629
+ const output = probeTaskCommand(root, "git", [
630
+ "ls-files",
631
+ "--cached",
632
+ "--others",
633
+ "--exclude-standard",
634
+ "-z",
635
+ "--",
636
+ ...relativePaths,
637
+ ]);
638
+ if (output === undefined) return undefined;
639
+ return output
640
+ .split("\0")
641
+ .filter(Boolean)
642
+ .map((path) => resolve(root, path))
643
+ .filter((path) => existsSync(path))
644
+ .filter((path) => paths.some((sourcePath) => trackedReleaseFile(sourcePath, path)))
645
+ .sort();
814
646
  }
815
647
 
816
648
  function trackedReleaseFile(sourcePath: string, path: string): boolean {
@@ -4,17 +4,6 @@ import type { JobStep } from "projen/lib/github/workflows-model";
4
4
  export const RELEASE_TAG = "${{ needs.verify-context.outputs.release_tag }}";
5
5
  export const RELEASE_SHA = "${{ needs.verify-context.outputs.expected_sha }}";
6
6
  export const RELEASE_VERSION = "${{ needs.verify-context.outputs.release_version }}";
7
- export const RELEASE_SUMMARY_DIRECTORY = "docs/releases";
8
- export const RELEASE_SUMMARY_PROVIDER_NAMES = ["cursor", "codex", "claude"] as const;
9
- export type ReleaseSummaryProviderName = (typeof RELEASE_SUMMARY_PROVIDER_NAMES)[number];
10
-
11
- /** Immutable repository path for one version's release summary. */
12
- export function releaseSummaryFile(version: string, component?: string): string {
13
- const prefix = component ? `${component.replace(/[^a-zA-Z0-9-]+/g, "-")}-` : "";
14
- return `${RELEASE_SUMMARY_DIRECTORY}/${prefix}v${version.replace(/^v/, "")}.md`;
15
- }
16
-
17
- export const RELEASE_SUMMARY_FILE = releaseSummaryFile(RELEASE_VERSION);
18
7
 
19
8
  /** Check out and verify the immutable commit selected by the release tag. */
20
9
  export function releaseSourceSteps(): readonly JobStep[] {