@dbx-tools/projen 0.6.153 → 0.6.160

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/README.md CHANGED
@@ -61,38 +61,38 @@ headers.
61
61
  import { project, projectPy } from "@dbx-tools/projen";
62
62
 
63
63
  const root = new project.DBXToolsNodeProject({ name: "my-apps" });
64
- const repository = {
65
- url: "https://github.com/example/my-apps.git",
66
- ref: "main",
67
- root: "python/packages",
68
- } as const;
69
64
 
70
65
  new projectPy.DBXToolsPythonWorkspace(root, {
71
- repository,
66
+ root: "python/packages",
72
67
  packages: [
73
68
  {
74
69
  directory: "core",
75
- name: "my-apps-core",
76
- module: "my_apps.core",
77
70
  description: "Shared Python helpers",
78
71
  },
72
+ {
73
+ directory: "service",
74
+ description: "Python service",
75
+ internalDependencies: ["core"],
76
+ },
79
77
  ],
80
- interpreterPath: "${workspaceFolder}/python/.venv/bin/python",
81
78
  release: true,
82
79
  });
83
80
  ```
84
81
 
85
- Use `projectPy.pythonGitDependency(repository, name, directory)` for an internal
86
- dependency that must also resolve when a package is installed directly through a
87
- Git `#subdirectory=` URL. `root.vscode` is projen's existing VS Code component;
88
- dbx-tools reuses it rather than constructing a second `.vscode/settings.json`
89
- owner.
82
+ Distribution names and modules come from the parent scope plus each package
83
+ directory. The repository comes from the parent project metadata or Git remote.
84
+ `internalDependencies` renders standalone Git `#subdirectory=` requirements
85
+ without repeating repository coordinates. Pass `repository`, `name`, or
86
+ `module` only to override those conventions. `root.vscode` is projen's existing
87
+ VS Code component; dbx-tools reuses it rather than constructing a second
88
+ `.vscode/settings.json` owner.
90
89
 
91
90
  ## Add A Rust Workspace
92
91
 
93
- `DBXToolsRustWorkspace` discovers every source-bearing folder under
94
- `packages/rs`, generates its Cargo manifest, and derives the crate name from the
95
- root scope plus folder name. A crate containing `uniffi::setup_scaffolding!()`
92
+ `DBXToolsRustWorkspace` discovers every source-bearing folder under its
93
+ configurable `root` (default `packages/rs`), generates its Cargo manifest, and
94
+ derives the crate name and repository from the parent project. A crate containing
95
+ `uniffi::setup_scaffolding!()`
96
96
  automatically wires matching private Node and Python binding packages using the
97
97
  same capability name. Repository-specific dependencies and features remain
98
98
  declarative options in `.projenrc.ts`; generated bindings are built separately
@@ -105,16 +105,28 @@ without Rust crates start no Rust watcher. When Rust projects are detected,
105
105
  Cargo is required and the focused task fails immediately if it is unavailable.
106
106
 
107
107
  The workspace also generates a `rust-release` workflow from discovered crates,
108
- UniFFI bindings, and release-enabled binaries. Its matrix has one row per
109
- target. Each row installs native dependencies and restores Cargo/sccache once,
108
+ UniFFI bindings, and release-enabled binaries. The root `release-dispatch`
109
+ workflow resolves the annotated release tag and sends the tag plus commit SHA to
110
+ `rust-release` when Rust is present. The Rust workflow runs in the configured release
111
+ branch cache scope, checks out the supplied SHA, fetches the tag, and requires
112
+ both the tag target and `HEAD` to equal that SHA before building. Its matrix has
113
+ one row per target. Each row installs native dependencies and restores
114
+ Cargo/sccache once,
110
115
  builds the Rust workspace once, then packages every discovered output from that
111
116
  shared build. Bun and the workspace install are present only when a Node binding
112
117
  needs TypeScript generation; uv is present only when a Python wheel is needed.
113
- A binary-only workspace therefore installs neither.
114
-
115
- The Node generator's Rust CLI is cached under one target directory keyed by its
116
- pinned UBRN version and runner architecture, then prepared before the workspace
117
- build. Python generation executes the already-built
118
+ A binary-only workspace therefore installs neither. `rustVersion` remains the
119
+ MSRV recorded in package manifests, while `releaseRustVersion` independently
120
+ defaults release compilation to `stable`. UBRN uses that same release toolchain
121
+ unless `ubrnRustVersion` explicitly selects another one.
122
+
123
+ The Node generator's Rust CLI can be cached under one target directory keyed by
124
+ its pinned UBRN version, Rust version, runner OS, and runner architecture. Set
125
+ the repository variable `CACHE_UBRN_TARGET=true` to enable that archive while
126
+ comparing its transfer cost with the default sccache-only path. Cargo registry
127
+ caches and the `SCCACHE_GHA_VERSION` namespace stay stable per target/toolchain
128
+ across version tags. Cache keys, restore results, sccache statistics, and phase
129
+ timings are written to each build log. Python generation executes the already-built
118
130
  `target/<triple>/release/uniffi-bindgen` directly. Artifact packaging therefore
119
131
  does no Rust compilation after the main workspace build.
120
132
 
@@ -125,22 +137,30 @@ Non-private Cargo crates publish from a source-only job with
125
137
  `cargo publish --no-verify`; GitHub Release uploads likewise consume prebuilt
126
138
  binary artifacts without reinstalling a toolchain.
127
139
 
128
- Set the repository variable `LOCAL_REPOSITORIES=true` to enable the generated
129
- self-hosted mirror job. Configure `LOCAL_NPM_REGISTRY` and
140
+ Set the repository variable `LOCAL_REPOSITORIES=true` to enable generated
141
+ self-hosted publication from the prebuilt artifacts. Configure `LOCAL_NPM_REGISTRY` and
130
142
  `LOCAL_PYPI_PUBLISH_URL` with their matching `LOCAL_*` credentials. Set
131
143
  `LOCAL_CARGO_REGISTRY` to a named Cargo registry such as a loopback
132
- [Kellnr](https://kellnr.io/) instance and provide `LOCAL_CARGO_TOKEN`. The runner
133
- detects its OS, architecture, libc, Rust target, and Python wheel tag and builds
134
- only that native target. Public Cargo crates also publish directly to crates.io
144
+ [Kellnr](https://kellnr.io/) instance and provide `LOCAL_CARGO_TOKEN`. Public
145
+ Cargo crates also publish directly to crates.io
135
146
  with `CARGO_REGISTRY_TOKEN`. Override `releaseTargets` only when a consumer has
136
147
  additional native runners; ordinary projects inherit the maintained matrix
137
148
  automatically. `bun run bump` also accepts repeatable `--os` and `--arch`
138
149
  selectors; every selected operating system is crossed with every selected
139
150
  architecture. Omit both filters to regenerate the complete maintained matrix.
151
+ `DBX_TOOLS_RELEASE_PLATFORMS` can select the generated matrix without repeating
152
+ environment parsing in a consumer.
140
153
 
141
154
  Private Python binding projects are marked with `[tool.dbx-tools] private =
142
155
  true`. They stay out of the standard uv/Python release and docs surfaces;
143
156
  `rust-release` publishes their prebuilt native wheels directly.
157
+ After Rust's public artifacts finish, it dispatches the downstream `release`
158
+ event. Python, Node, standalone Node, and docs consume that event independently
159
+ and start together with the same verified tag and SHA. Without Rust,
160
+ `release-dispatch` sends that event immediately. Configure release GitHub
161
+ environments to permit the generated release branch; the trusted-publisher
162
+ instructions list that branch with the workflow and environment identity PyPI
163
+ verifies.
144
164
 
145
165
  ## Customize Packages With Mixins
146
166
 
@@ -156,6 +176,10 @@ projenProject.applyToProjects(project, { tags: "shared" }, (pkg) => {
156
176
  project.synth();
157
177
  ```
158
178
 
179
+ Use `projectJs.addOptionalPeer(pkg, specifier)` for an optional peer that must
180
+ also resolve during local development. It writes the peer metadata and matching
181
+ development dependency without turning the peer into a runtime dependency.
182
+
159
183
  `applyToProjects` AND-s its globs (prefix a glob with `!` to negate) into one
160
184
  predicate over the DBXTools child packages, then applies it as a `constructs`
161
185
  mixin across the subtree. Filter on the folder (`path`), the tags (`tags`), or
package/index.ts CHANGED
@@ -22,6 +22,7 @@ export * as projectPy from "./src/project-py.ts";
22
22
  export * as projectRs from "./src/project-rs.ts";
23
23
  export * as publish from "./src/publish.ts";
24
24
  export * as release from "./src/release.ts";
25
+ export * as releaseDispatch from "./src/release-dispatch.ts";
25
26
  export * as scaffold from "./src/scaffold.ts";
26
27
  export * as tags from "./src/tags.ts";
27
28
  export * as tsconfig from "./src/tsconfig.ts";
@@ -50,6 +51,7 @@ export type { CargoDependencyOptions, CargoDependency, RustPackageOptions, DBXTo
50
51
  export { COMPILED_DIR, COMPILED_COMPILER_OPTIONS } from "./src/publish.ts";
51
52
  export { DBXToolsRelease } from "./src/release.ts";
52
53
  export type { StandaloneRelease, DBXToolsReleaseOptions } from "./src/release.ts";
54
+ export { DOWNSTREAM_RELEASE_EVENT, RUST_RELEASE_EVENT, RELEASE_TAG, RELEASE_SHA } from "./src/release-dispatch.ts";
53
55
  export { AGNOSTIC_COMPILER_OPTIONS, PACKAGE_TAG_MIXINS } from "./src/tags.ts";
54
56
  export type { PackageTag } from "./src/tags.ts";
55
57
  export { DBXToolsRootTsconfig } from "./src/tsconfig.ts";
package/package.json CHANGED
@@ -26,9 +26,9 @@
26
26
  },
27
27
  "dependencies": {
28
28
  "@clack/prompts": "^1.7.0",
29
- "@dbx-tools/core": "0.6.153",
30
- "@dbx-tools/path": "0.6.153",
31
- "@dbx-tools/shared-core": "0.6.153",
29
+ "@dbx-tools/core": "0.6.160",
30
+ "@dbx-tools/path": "0.6.160",
31
+ "@dbx-tools/shared-core": "0.6.160",
32
32
  "commander": "^15.0.0",
33
33
  "concurrently": "^10.0.3",
34
34
  "constructs": "^10.6.0",
@@ -48,7 +48,7 @@
48
48
  },
49
49
  "main": "index.ts",
50
50
  "license": "Apache-2.0",
51
- "version": "0.6.153",
51
+ "version": "0.6.160",
52
52
  "types": "index.ts",
53
53
  "type": "module",
54
54
  "exports": {
@@ -101,7 +101,10 @@ const DEFAULT_CATALOG: Catalog = {
101
101
  * workspace with an "Ignored build scripts" warning on its first install.
102
102
  */
103
103
  const DEFAULT_ALLOW_BUILDS: AllowBuilds = {
104
+ "@databricks/appkit": true,
105
+ "@databricks/appkit-ui": true,
104
106
  esbuild: true,
107
+ protobufjs: true,
105
108
  "unrs-resolver": true,
106
109
  // The `bun` npm package (a peer of `bun-plugin-tailwind`) ships a `bun.exe`
107
110
  // placeholder and downloads the real platform binary in its postinstall. Left
@@ -113,6 +116,10 @@ const DEFAULT_ALLOW_BUILDS: AllowBuilds = {
113
116
  "onnxruntime-node": true,
114
117
  };
115
118
 
119
+ const DEFAULT_OVERRIDES: Readonly<Record<string, string>> = {
120
+ glob: "^13.0.0",
121
+ };
122
+
116
123
  /**
117
124
  * pnpm settings this engine applies to every workspace, beyond members, catalog,
118
125
  * and allowances. Each is stated because pnpm's own default is the weaker choice
@@ -173,7 +180,7 @@ export class PnpmWorkspaceState {
173
180
  this.allowBuilds = { ...DEFAULT_ALLOW_BUILDS, ...options.allowBuilds };
174
181
  // Seeded even when empty so `addOverride` has a reference projen already
175
182
  // captured; projen's `omitEmpty` drops the key while it stays empty.
176
- this.overrides = { ...options.workspaceYaml?.overrides };
183
+ this.overrides = { ...DEFAULT_OVERRIDES, ...options.workspaceYaml?.overrides };
177
184
  this.options = {
178
185
  ...DEFAULT_WORKSPACE_YAML,
179
186
  ...options.workspaceYaml,
package/src/project-js.ts CHANGED
@@ -236,6 +236,46 @@ export function addExports(pkg: javascript.NodeProject, exports: Record<string,
236
236
  });
237
237
  }
238
238
 
239
+ function dependencyName(spec: string): string {
240
+ if (!spec.startsWith("@")) return spec.split("@", 1)[0]!;
241
+ const versionSeparator = spec.indexOf("@", spec.indexOf("/") + 1);
242
+ return versionSeparator < 0 ? spec : spec.slice(0, versionSeparator);
243
+ }
244
+
245
+ /** Add an optional peer and make it available while developing the package. */
246
+ export function addOptionalPeer(pkg: javascript.NodeProject, spec: string): void {
247
+ const name = dependencyName(spec);
248
+ const current = object.isRecord(pkg.package.manifest.peerDependenciesMeta)
249
+ ? pkg.package.manifest.peerDependenciesMeta
250
+ : {};
251
+ pkg.addPeerDeps(spec);
252
+ pkg.package.addField("peerDependenciesMeta", {
253
+ ...current,
254
+ [name]: { optional: true },
255
+ });
256
+ pkg.addDevDeps(spec);
257
+ }
258
+
259
+ /** Resolve repository metadata already configured on a Node project. */
260
+ export function projectRepositoryUrl(project: javascript.NodeProject): string | undefined {
261
+ const repository = project.package.manifest.repository;
262
+ const configured =
263
+ typeof repository === "string"
264
+ ? repository
265
+ : object.isRecord(repository)
266
+ ? (string.trimToNull(repository.url) ?? undefined)
267
+ : undefined;
268
+ return configured?.replace(/^git\+/, "").replace(/\.git$/, "");
269
+ }
270
+
271
+ /** Resolve the branch release workflows use for a generated project. */
272
+ export function projectReleaseBranch(project: javascript.NodeProject): string {
273
+ return (
274
+ (project as javascript.NodeProject & { readonly releaseBranch?: string }).releaseBranch ??
275
+ "main"
276
+ );
277
+ }
278
+
239
279
  /**
240
280
  * MERGE entries onto a package's npm `files` allowlist - the only paths that
241
281
  * ship in the published tarball. npm always includes `package.json`, `README`,
@@ -559,7 +599,8 @@ export class DBXToolsNodeProject
559
599
  pnpmWorkspace?: PnpmWorkspaceState;
560
600
  rootTsconfig?: DBXToolsRootTsconfig;
561
601
  vsCode?: DBXToolsVsCode;
562
- private readonly extraWorkspaceMembers: readonly string[];
602
+ readonly extraWorkspaceMembers: readonly string[];
603
+ readonly releaseBranch: string;
563
604
  private readonly rootInstallOnly: boolean;
564
605
 
565
606
  constructor(options: DBXToolsJavaScriptProjectOptions = {}) {
@@ -595,6 +636,7 @@ export class DBXToolsNodeProject
595
636
  this.package.addField("version", readWorkspaceVersion(this.outdir));
596
637
  this.scope = scope;
597
638
  this.extraWorkspaceMembers = options.extraWorkspaceMembers ?? [];
639
+ this.releaseBranch = options.defaultReleaseBranch ?? "main";
598
640
  this.rootInstallOnly = options.rootInstallOnly !== false;
599
641
  this.dbxToolsConfig = new DBXToolsConfig(this, options);
600
642
  initProject(this, options);
@@ -773,13 +815,13 @@ class WorkspaceValidationTasks extends Component {
773
815
  }
774
816
 
775
817
  /**
776
- * Bound the default validation workflows when a root opts into them.
818
+ * Apply bounded, read-only, supersedable defaults to validation workflows.
777
819
  *
778
820
  * Projen otherwise leaves jobs at GitHub's six-hour ceiling. Missing workflows
779
821
  * are a no-op, so roots that keep the engine defaults (`github`/build workflow
780
822
  * off) do not gain new files.
781
823
  */
782
- class WorkflowTimeouts extends Component {
824
+ class WorkflowDefaults extends Component {
783
825
  public override preSynthesize(): void {
784
826
  const build = this.project.tryFindObjectFile(".github/workflows/build.yml");
785
827
  for (const job of ["build", "self-mutation"]) {
@@ -788,6 +830,14 @@ class WorkflowTimeouts extends Component {
788
830
  this.project
789
831
  .tryFindObjectFile(".github/workflows/pull-request-lint.yml")
790
832
  ?.addOverride("jobs.validate.timeout-minutes", 10);
833
+ for (const name of ["build", "pull-request-lint"]) {
834
+ const workflow = this.project.tryFindObjectFile(`.github/workflows/${name}.yml`);
835
+ workflow?.addOverride("permissions", { contents: "read" });
836
+ workflow?.addOverride("concurrency", {
837
+ group: "${{ github.workflow }}-${{ github.ref }}",
838
+ "cancel-in-progress": true,
839
+ });
840
+ }
791
841
  }
792
842
  }
793
843
 
@@ -839,6 +889,10 @@ class PrettierIgnoreGenerated extends Component {
839
889
  prettier.addIgnorePattern(`${rel}/${module}`);
840
890
  }
841
891
  }
892
+ const extraMembers = (this.project as DBXToolsNodeProject).extraWorkspaceMembers ?? [];
893
+ for (const member of extraMembers) {
894
+ prettier.addIgnorePattern(`${member}/index.ts`);
895
+ }
842
896
  }
843
897
  }
844
898
 
@@ -1021,7 +1075,7 @@ function initProject(
1021
1075
  // always apply; the self-dep is added only when the engine is an installed pkg.
1022
1076
  const selfDep = engineSelfDependency(project);
1023
1077
  if (selfDep) project.addDevDeps(selfDep);
1024
- project.addDevDeps(...DEV_DEPS_ROOT);
1078
+ project.addDevDeps(...DEV_DEPS_ROOT, "concurrently@catalog:");
1025
1079
  configureRootPackage(project);
1026
1080
  // Root carries the bare `repository` (no `directory`); children add their subpath.
1027
1081
  applyRepository(project, options.repository);
@@ -1058,6 +1112,11 @@ function initProject(
1058
1112
  project.annotateGenerated(`/${root}/**/index.ts`);
1059
1113
  project.annotateGenerated(`/${root}/openapi/**`);
1060
1114
  }
1115
+ const extraWorkspaceMembers =
1116
+ project instanceof DBXToolsNodeProject ? project.extraWorkspaceMembers : [];
1117
+ for (const member of extraWorkspaceMembers) {
1118
+ project.annotateGenerated(`/${member}/index.ts`);
1119
+ }
1061
1120
 
1062
1121
  // ESLint lives ONLY on the root and lints every package. `projectService` resolves
1063
1122
  // each file to its own package tsconfig (so type-aware rules work tree-wide), and
@@ -1178,7 +1237,7 @@ function initProject(
1178
1237
  }
1179
1238
 
1180
1239
  new WorkspaceValidationTasks(project);
1181
- new WorkflowTimeouts(project);
1240
+ new WorkflowDefaults(project);
1182
1241
  new PrettierIgnoreGenerated(project);
1183
1242
 
1184
1243
  new GeneratedSource(project);