@dbx-tools/projen 0.6.152 → 0.6.158

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
@@ -104,26 +104,63 @@ removing a crate or `setup_scaffolding!()` marker triggers a full synth. Repos
104
104
  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
- The workspace also generates a `rust-release` workflow from those discovered
108
- UniFFI crates. It builds Linux x64/arm64, macOS x64/arm64, and Windows x64.
109
- Node publishes a small facade plus optional OS/CPU packages, so npm installs
110
- only the matching native library. Python publishes one platform-tagged wheel
111
- per target, so pip follows the same thin-install model.
112
-
113
- Set the repository variable `LOCAL_REPOSITORIES=true` to enable the generated
114
- self-hosted mirror job. Configure `LOCAL_NPM_REGISTRY` and
107
+ The workspace also generates a `rust-release` workflow from discovered crates,
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,
115
+ builds the Rust workspace once, then packages every discovered output from that
116
+ shared build. Bun and the workspace install are present only when a Node binding
117
+ needs TypeScript generation; uv is present only when a Python wheel is needed.
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
130
+ `target/<triple>/release/uniffi-bindgen` directly. Artifact packaging therefore
131
+ does no Rust compilation after the main workspace build.
132
+
133
+ Artifacts identify their crate, target, and type. Download-only publication
134
+ jobs publish native npm platform archives before the facade and publish
135
+ platform-tagged Python wheels without checkout, Bun, or `bun install`.
136
+ Non-private Cargo crates publish from a source-only job with
137
+ `cargo publish --no-verify`; GitHub Release uploads likewise consume prebuilt
138
+ binary artifacts without reinstalling a toolchain.
139
+
140
+ Set the repository variable `LOCAL_REPOSITORIES=true` to enable generated
141
+ self-hosted publication from the prebuilt artifacts. Configure `LOCAL_NPM_REGISTRY` and
115
142
  `LOCAL_PYPI_PUBLISH_URL` with their matching `LOCAL_*` credentials. Set
116
143
  `LOCAL_CARGO_REGISTRY` to a named Cargo registry such as a loopback
117
- [Kellnr](https://kellnr.io/) instance and provide `LOCAL_CARGO_TOKEN`. The runner
118
- detects its OS, architecture, libc, Rust target, and Python wheel tag and builds
119
- 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
120
146
  with `CARGO_REGISTRY_TOKEN`. Override `releaseTargets` only when a consumer has
121
147
  additional native runners; ordinary projects inherit the maintained matrix
122
- automatically.
123
-
124
- Private Python projects are marked with `[tool.dbx-tools] private = true`. They
125
- are excluded from the uv workspace, documentation, and Python release workflow
126
- until their native artifact publishing matrix is enabled.
148
+ automatically. `bun run bump` also accepts repeatable `--os` and `--arch`
149
+ selectors; every selected operating system is crossed with every selected
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.
153
+
154
+ Private Python binding projects are marked with `[tool.dbx-tools] private =
155
+ true`. They stay out of the standard uv/Python release and docs surfaces;
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.
127
164
 
128
165
  ## Customize Packages With Mixins
129
166
 
@@ -139,6 +176,10 @@ projenProject.applyToProjects(project, { tags: "shared" }, (pkg) => {
139
176
  project.synth();
140
177
  ```
141
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
+
142
183
  `applyToProjects` AND-s its globs (prefix a glob with `!` to negate) into one
143
184
  predicate over the DBXTools child packages, then applies it as a `constructs`
144
185
  mixin across the subtree. Filter on the folder (`path`), the tags (`tags`), or
@@ -318,10 +359,12 @@ Use `--local-registry false` or `--local-pypi false` to disable either local
318
359
  publish. An explicit `--local-pypi http://localhost:3141/user/index/` overrides
319
360
  auto-detection; `--python-root` defaults to `packages/py`.
320
361
 
321
- The pushed `v*` tag is also the public release boundary: it triggers npm
322
- publishing, stamps and publishes every Python distribution to PyPI, and rebuilds
323
- and deploys the documentation site. Ordinary pushes to `main` publish none of
324
- those surfaces.
362
+ The pushed `v*` tag is also the public release boundary. Available workflow
363
+ stages form the generated chain Rust -> Python -> Node -> docs: Rust builds and
364
+ publishes native artifacts and Cargo crates, Python publishes standard
365
+ distributions, Node publishes standard workspace packages, and docs deploys
366
+ after publication. A stage with no corresponding outputs is omitted. Ordinary
367
+ pushes to `main` publish none of those surfaces.
325
368
 
326
369
  Members intentionally keep only the tasks that something OTHER than a human
327
370
  invokes, so there is no second place to run the same thing:
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.152",
30
- "@dbx-tools/path": "0.6.152",
31
- "@dbx-tools/shared-core": "0.6.152",
29
+ "@dbx-tools/core": "0.6.158",
30
+ "@dbx-tools/path": "0.6.158",
31
+ "@dbx-tools/shared-core": "0.6.158",
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.152",
51
+ "version": "0.6.158",
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`,
@@ -379,6 +419,7 @@ function defaultProjectOptions(
379
419
  // provider. See {@link DBXToolsRelease}.
380
420
  ...(isRoot ? {} : { npmAccess: javascript.NpmAccess.PUBLIC }),
381
421
  buildWorkflow: false,
422
+ workflowPackageCache: false,
382
423
  release: false,
383
424
  // The root build validates the whole workspace and must not also pack every
384
425
  // member into unused `dist/js` tarballs. Child projects keep projen's package
@@ -518,7 +559,7 @@ export interface DBXToolsJavaScriptProjectOptions
518
559
  /** Workflow that must finish successfully before the main Node release runs. */
519
560
  readonly releaseUpstreamWorkflow?: string;
520
561
  /** Main Node release workflow name. Defaults to `node-release`. */
521
- readonly releaseWorkflowName?: string | false;
562
+ readonly nodeReleaseWorkflowName?: string | false;
522
563
  /**
523
564
  * Extra workspace member paths (repo-relative, POSIX) to list in the workspace
524
565
  * config ALONGSIDE the discovered `packageRoots` members - for a package that
@@ -558,7 +599,8 @@ export class DBXToolsNodeProject
558
599
  pnpmWorkspace?: PnpmWorkspaceState;
559
600
  rootTsconfig?: DBXToolsRootTsconfig;
560
601
  vsCode?: DBXToolsVsCode;
561
- private readonly extraWorkspaceMembers: readonly string[];
602
+ readonly extraWorkspaceMembers: readonly string[];
603
+ readonly releaseBranch: string;
562
604
  private readonly rootInstallOnly: boolean;
563
605
 
564
606
  constructor(options: DBXToolsJavaScriptProjectOptions = {}) {
@@ -594,6 +636,7 @@ export class DBXToolsNodeProject
594
636
  this.package.addField("version", readWorkspaceVersion(this.outdir));
595
637
  this.scope = scope;
596
638
  this.extraWorkspaceMembers = options.extraWorkspaceMembers ?? [];
639
+ this.releaseBranch = options.defaultReleaseBranch ?? "main";
597
640
  this.rootInstallOnly = options.rootInstallOnly !== false;
598
641
  this.dbxToolsConfig = new DBXToolsConfig(this, options);
599
642
  initProject(this, options);
@@ -772,13 +815,13 @@ class WorkspaceValidationTasks extends Component {
772
815
  }
773
816
 
774
817
  /**
775
- * Bound the default validation workflows when a root opts into them.
818
+ * Apply bounded, read-only, supersedable defaults to validation workflows.
776
819
  *
777
820
  * Projen otherwise leaves jobs at GitHub's six-hour ceiling. Missing workflows
778
821
  * are a no-op, so roots that keep the engine defaults (`github`/build workflow
779
822
  * off) do not gain new files.
780
823
  */
781
- class WorkflowTimeouts extends Component {
824
+ class WorkflowDefaults extends Component {
782
825
  public override preSynthesize(): void {
783
826
  const build = this.project.tryFindObjectFile(".github/workflows/build.yml");
784
827
  for (const job of ["build", "self-mutation"]) {
@@ -787,6 +830,14 @@ class WorkflowTimeouts extends Component {
787
830
  this.project
788
831
  .tryFindObjectFile(".github/workflows/pull-request-lint.yml")
789
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
+ }
790
841
  }
791
842
  }
792
843
 
@@ -838,6 +889,10 @@ class PrettierIgnoreGenerated extends Component {
838
889
  prettier.addIgnorePattern(`${rel}/${module}`);
839
890
  }
840
891
  }
892
+ const extraMembers = (this.project as DBXToolsNodeProject).extraWorkspaceMembers ?? [];
893
+ for (const member of extraMembers) {
894
+ prettier.addIgnorePattern(`${member}/index.ts`);
895
+ }
841
896
  }
842
897
  }
843
898
 
@@ -1020,7 +1075,7 @@ function initProject(
1020
1075
  // always apply; the self-dep is added only when the engine is an installed pkg.
1021
1076
  const selfDep = engineSelfDependency(project);
1022
1077
  if (selfDep) project.addDevDeps(selfDep);
1023
- project.addDevDeps(...DEV_DEPS_ROOT);
1078
+ project.addDevDeps(...DEV_DEPS_ROOT, "concurrently@catalog:");
1024
1079
  configureRootPackage(project);
1025
1080
  // Root carries the bare `repository` (no `directory`); children add their subpath.
1026
1081
  applyRepository(project, options.repository);
@@ -1057,6 +1112,11 @@ function initProject(
1057
1112
  project.annotateGenerated(`/${root}/**/index.ts`);
1058
1113
  project.annotateGenerated(`/${root}/openapi/**`);
1059
1114
  }
1115
+ const extraWorkspaceMembers =
1116
+ project instanceof DBXToolsNodeProject ? project.extraWorkspaceMembers : [];
1117
+ for (const member of extraWorkspaceMembers) {
1118
+ project.annotateGenerated(`/${member}/index.ts`);
1119
+ }
1060
1120
 
1061
1121
  // ESLint lives ONLY on the root and lints every package. `projectService` resolves
1062
1122
  // each file to its own package tsconfig (so type-aware rules work tree-wide), and
@@ -1177,7 +1237,7 @@ function initProject(
1177
1237
  }
1178
1238
 
1179
1239
  new WorkspaceValidationTasks(project);
1180
- new WorkflowTimeouts(project);
1240
+ new WorkflowDefaults(project);
1181
1241
  new PrettierIgnoreGenerated(project);
1182
1242
 
1183
1243
  new GeneratedSource(project);
@@ -1188,7 +1248,7 @@ function initProject(
1188
1248
  tagPrefix: options.releaseTagPrefix,
1189
1249
  standaloneReleases: options.standaloneReleases,
1190
1250
  upstreamWorkflow: options.releaseUpstreamWorkflow,
1191
- workflowName: options.releaseWorkflowName,
1251
+ workflowName: options.nodeReleaseWorkflowName ?? options.releaseWorkflowName,
1192
1252
  });
1193
1253
  }
1194
1254