@dbx-tools/projen 0.9.5 → 0.9.7
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 +69 -17
- package/index.ts +7 -5
- package/package.json +9 -6
- package/src/_rust-project.ts +356 -0
- package/src/_rust-release-workflow.ts +954 -0
- package/src/_rust-release.ts +605 -0
- package/src/_task-command.ts +89 -0
- package/src/barrels.ts +12 -11
- package/src/bun-app.ts +96 -85
- package/src/bun-workflow.ts +60 -52
- package/src/engine-root.ts +5 -5
- package/src/openapi.ts +16 -9
- package/src/packages.ts +9 -9
- package/src/pnpm-workspace.ts +1 -1
- package/src/project-js.ts +19 -25
- package/src/project-py.ts +28 -23
- package/src/project-rs.ts +100 -1098
- package/src/projen-version.ts +9 -0
- package/src/publish.ts +3 -4
- package/src/release-catalog.ts +1 -1
- package/src/release.ts +212 -177
- package/src/scaffold.ts +1 -1
- package/src/uniffi.ts +7 -0
- package/src/watch.ts +2 -2
- package/src/workspace-version.ts +20 -15
- package/tasks/barrels.ts +4 -4
- package/tasks/build-rust-release.ts +68 -0
- package/tasks/bump.ts +3 -2
- package/tasks/clean.ts +3 -3
- package/tasks/local-publish.ts +21 -3
- package/tasks/openapi.ts +2 -2
- package/tasks/publish-npm.ts +12 -6
- package/tasks/publish-python.ts +2 -2
- package/tasks/publish-uniffi-local.ts +11 -15
- package/tasks/publish.ts +88 -88
- package/tasks/python-registry.ts +1 -1
- package/tasks/release-bootstrap.ts +2 -2
- package/tasks/release-plan.ts +1 -1
- package/tasks/release-please.ts +1 -1
- package/tasks/release-pr.ts +60 -115
- package/tasks/release-request.ts +8 -29
- package/tasks/release-summary-units.ts +2 -9
- package/tasks/release-summary.ts +35 -24
- package/tasks/release-version.ts +44 -0
- package/tasks/rust-release.d.ts +41 -0
- package/tasks/rust-release.mjs +1717 -0
- package/tasks/sync.ts +3 -3
- package/tasks/uniffi-python.js +1 -1
- package/tasks/uniffi.ts +7 -1
- package/tasks/version-check.ts +6 -5
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @dbx-tools/projen
|
|
2
2
|
|
|
3
|
-
Projen engine for dbx-tools
|
|
3
|
+
Projen engine for Bun-first dbx-tools workspaces.
|
|
4
4
|
|
|
5
5
|
Import this package from `.projenrc.ts` when a repository should discover
|
|
6
6
|
packages from the filesystem and generate manifests, tsconfigs,
|
|
@@ -11,9 +11,10 @@ Key features:
|
|
|
11
11
|
- Filesystem package discovery: every `src`-bearing folder under configured
|
|
12
12
|
workspace roots becomes a TypeScript package.
|
|
13
13
|
- Tag-driven runtime defaults for shared libraries, Node packages, CLIs,
|
|
14
|
-
servers, OpenAPI clients, and React/
|
|
15
|
-
- Generated package manifests, tsconfigs, package-root barrels,
|
|
16
|
-
|
|
14
|
+
servers, OpenAPI clients, and React/browser UI packages.
|
|
15
|
+
- Generated package manifests, tsconfigs, package-root barrels, Bun app configs,
|
|
16
|
+
VS Code settings, and a committed `pnpm-workspace.yaml` retained for
|
|
17
|
+
Databricks Apps deployment.
|
|
17
18
|
- Extensible mixin system so repositories can add deps, tasks, or generated
|
|
18
19
|
files based on package predicates.
|
|
19
20
|
- OpenAPI client generation from tsoa controllers and zod schema generation from
|
|
@@ -23,15 +24,15 @@ Key features:
|
|
|
23
24
|
## Define A Workspace Root
|
|
24
25
|
|
|
25
26
|
```ts
|
|
26
|
-
import { project
|
|
27
|
+
import { project } from "@dbx-tools/projen";
|
|
27
28
|
|
|
28
|
-
const
|
|
29
|
+
const rootProject = new project.DBXToolsNodeProject({
|
|
29
30
|
name: "my-apps",
|
|
30
31
|
scope: "my-apps",
|
|
31
32
|
packageRoots: ["packages", "examples"],
|
|
32
33
|
});
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
rootProject.synth();
|
|
35
36
|
```
|
|
36
37
|
|
|
37
38
|
Every `src`-bearing folder under the configured roots becomes a
|
|
@@ -130,8 +131,8 @@ derives the crate name and repository from the parent project. A crate containin
|
|
|
130
131
|
`uniffi::setup_scaffolding!()`
|
|
131
132
|
automatically wires matching public Node and Python binding packages using the
|
|
132
133
|
`<name>-rs` folder suffix. Node packages are named `@<scope>/<name>-rs`;
|
|
133
|
-
Python distributions are named `<scope>-<name>-rs` and
|
|
134
|
-
from `<scope>.<name>_rs
|
|
134
|
+
Python distributions are named `<scope>-<name>-rs` and export generated values
|
|
135
|
+
from `<scope>.<name>_rs`. Binding packages are always dedicated and
|
|
135
136
|
never merge generated code into handwritten Node or Python packages.
|
|
136
137
|
Repository-specific dependencies and features remain
|
|
137
138
|
declarative options in `.projenrc.ts`; generated bindings are built separately
|
|
@@ -141,9 +142,38 @@ facades compile to `lib/` and publish JavaScript entry points that plain Node
|
|
|
141
142
|
can load from `node_modules`. A complete `bindings.ts` / `_bindings.ts` /
|
|
142
143
|
`_bindings-ffi.ts` triplet is exported directly from the generated package
|
|
143
144
|
barrel. Python keeps `bindings.py` as the generated implementation and leaves
|
|
144
|
-
`__init__.py`
|
|
145
|
+
`__init__.py` as its generated package-root export. Node generation fails when
|
|
146
|
+
direct binding names conflict.
|
|
145
147
|
Do not create a `nodeExports` binding subpath or a handwritten type facade.
|
|
146
148
|
|
|
149
|
+
Each discovered member is a native Projen `Project`, exposed as
|
|
150
|
+
`DBXToolsRustProject`. The same class also owns standalone Cargo projects with
|
|
151
|
+
the flat object-style options used by the Node and Python project classes:
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
import { project } from "@dbx-tools/projen";
|
|
155
|
+
|
|
156
|
+
new project.DBXToolsRustProject({
|
|
157
|
+
name: "my-apps-core",
|
|
158
|
+
outdir: "native/core",
|
|
159
|
+
version: "0.1.0",
|
|
160
|
+
description: "Shared native runtime",
|
|
161
|
+
repository: "https://github.com/example/my-apps",
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The project emits concrete standalone Cargo metadata plus `compile`, `test`,
|
|
166
|
+
`package`, `lint`, `format`, and `format:check` tasks. A nested standalone
|
|
167
|
+
project declares its own empty Cargo workspace so a surrounding repository
|
|
168
|
+
workspace does not absorb it accidentally. The `examples` option emits explicit
|
|
169
|
+
Cargo example targets and `required-features`, so an optional code generator is
|
|
170
|
+
excluded from ordinary workspace tests until its feature is enabled.
|
|
171
|
+
`DBXToolsRustWorkspace` constructs the same class with workspace-owned metadata
|
|
172
|
+
and keeps aggregate binding and release coordination.
|
|
173
|
+
`DBXToolsRustWorkspaceOptions.private` supplies the default Cargo publication
|
|
174
|
+
policy for every discovered crate; a package-level `private` value overrides it.
|
|
175
|
+
Cargo projects use the flat object-style constructor exclusively.
|
|
176
|
+
|
|
147
177
|
Rust dependencies between binding-enabled workspace crates become Node
|
|
148
178
|
`workspace:*` and Python `internalDependencies` automatically. Python generation
|
|
149
179
|
selects the owning crate and supplies `external_packages` imports. UBRN currently
|
|
@@ -159,7 +189,8 @@ build has no colliding binary outputs. Packaging runs that prebuilt executable.
|
|
|
159
189
|
Cargo manifests, target config, and UniFFI config are generated from structured
|
|
160
190
|
Projen `TomlFile` objects. Local and release Python generation share one
|
|
161
191
|
dependency-free helper for generator arguments, target-specific executable
|
|
162
|
-
names, generated headers,
|
|
192
|
+
names, generated headers, generated package-root exports, and native-library
|
|
193
|
+
placement.
|
|
163
194
|
|
|
164
195
|
`sync --watch` runs a focused Rust watcher beside the OpenAPI watcher. Changes
|
|
165
196
|
inside an existing UniFFI crate regenerate that crate and its dependent bindings
|
|
@@ -173,6 +204,12 @@ a host Rust build. Regenerate bindings through the focused watcher or explicit
|
|
|
173
204
|
task while changing a UniFFI API. Release preparation runs Cargo workspace tests
|
|
174
205
|
without invoking UBRN.
|
|
175
206
|
|
|
207
|
+
Rust fingerprinting and binary version stamping use the dependency-free Node
|
|
208
|
+
helper generated at `.projen/rust-release.mjs`. The implementation ships with
|
|
209
|
+
`@dbx-tools/projen`, so consumers do not need a repository-specific helper
|
|
210
|
+
crate, a crates.io publication, or an extra Cargo build before release
|
|
211
|
+
orchestration can start.
|
|
212
|
+
|
|
176
213
|
The workspace generates one `release.yml` workflow for every ecosystem. One
|
|
177
214
|
root `VERSION` drives every Node, Python, Cargo, native, and GitHub artifact.
|
|
178
215
|
`bun run release` prepares one reviewed PR into `main`; it enables automatic
|
|
@@ -217,7 +254,11 @@ and select `rust-lld` for the workspace build. Rust release rows restore and
|
|
|
217
254
|
update dependency-only Cargo caches in the release branch's cache scope. The
|
|
218
255
|
cache key excludes workspace version changes, and manual tag recovery restores
|
|
219
256
|
without saving a tag-scoped copy. There is no separate cache workflow or
|
|
220
|
-
sccache layer.
|
|
257
|
+
sccache layer. After publishing the current raw Rust bundles and build manifests,
|
|
258
|
+
the GitHub release job deletes those internal assets from published
|
|
259
|
+
`vMAJOR.MINOR.PATCH` releases strictly lower than the current version while
|
|
260
|
+
leaving versioned consumer binaries in place. Phase timings are written to each
|
|
261
|
+
build log. Python generation executes the already-built
|
|
221
262
|
`target/<triple>/release/<crate>-uniffi-bindgen` directly. Artifact packaging therefore
|
|
222
263
|
does no Rust compilation after the main workspace build.
|
|
223
264
|
|
|
@@ -253,18 +294,28 @@ from the same workflow. When a conventional Node binding path already belongs
|
|
|
253
294
|
to a root subproject, Rust mapping reuses that project and adds binding
|
|
254
295
|
dependencies and metadata to its existing manifest.
|
|
255
296
|
|
|
297
|
+
The workspace npm publisher owns compilation for normal release publication. It
|
|
298
|
+
selects every publishable package with compiled entry points, invokes one
|
|
299
|
+
root-level filtered compile, then packs each package exactly once with lifecycle
|
|
300
|
+
scripts disabled. It validates the archive identity, configured access,
|
|
301
|
+
integrity, and repository metadata before publishing those same bytes. Local
|
|
302
|
+
release preparation passes `--skip-compile` only after its immediately preceding
|
|
303
|
+
validation compile and verifies every expected output exists before reuse.
|
|
304
|
+
Package `prepack` tasks remain available for standalone publishes without
|
|
305
|
+
multiplying `tsc --build` across the monorepo release flow.
|
|
306
|
+
|
|
256
307
|
## Customize Packages With Mixins
|
|
257
308
|
|
|
258
309
|
```ts
|
|
259
|
-
import { project
|
|
310
|
+
import { project } from "@dbx-tools/projen";
|
|
260
311
|
|
|
261
|
-
const
|
|
312
|
+
const rootProject = new project.DBXToolsNodeProject();
|
|
262
313
|
|
|
263
|
-
|
|
314
|
+
project.applyToProjects(rootProject, { tags: "shared" }, (pkg) => {
|
|
264
315
|
pkg.addDeps("zod@catalog:");
|
|
265
316
|
});
|
|
266
317
|
|
|
267
|
-
|
|
318
|
+
rootProject.synth();
|
|
268
319
|
```
|
|
269
320
|
|
|
270
321
|
Use `projectJs.addOptionalPeer(pkg, specifier)` for an optional peer that must
|
|
@@ -482,7 +533,8 @@ invokes, so there is no second place to run the same thing:
|
|
|
482
533
|
edit/compile loop to one package.
|
|
483
534
|
- `build` / `package` - a complete compile/test/pack lifecycle when invoked in
|
|
484
535
|
one package. Release preparation validates through the root's filtered tasks,
|
|
485
|
-
and publication
|
|
536
|
+
and publication concurrently uploads archives that were each packed and
|
|
537
|
+
validated once with lifecycle scripts disabled. The package phase also packs
|
|
486
538
|
with `--ignore-scripts` because its build already compiled; `prepack` remains
|
|
487
539
|
available for a standalone publish that did not run `build` first.
|
|
488
540
|
- `install` / `install:ci` / `default` / `pre-compile` / `post-compile` -
|
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.
|
|
6
|
+
export const PACKAGE_VERSION = "0.9.7";
|
|
7
7
|
export * as barrels from "./src/barrels.ts";
|
|
8
8
|
export * as bunApp from "./src/bun-app.ts";
|
|
9
9
|
export * as bunWorkflow from "./src/bun-workflow.ts";
|
|
@@ -22,6 +22,7 @@ export * as projectJs from "./src/project-js.ts";
|
|
|
22
22
|
export * as projectPredicate from "./src/project-predicate.ts";
|
|
23
23
|
export * as projectPy from "./src/project-py.ts";
|
|
24
24
|
export * as projectRs from "./src/project-rs.ts";
|
|
25
|
+
export * as projenVersion from "./src/projen-version.ts";
|
|
25
26
|
export * as publish from "./src/publish.ts";
|
|
26
27
|
export * as release from "./src/release.ts";
|
|
27
28
|
export * as releaseCatalog from "./src/release-catalog.ts";
|
|
@@ -50,12 +51,13 @@ export type { RecordedPackage } from "./src/packages.ts";
|
|
|
50
51
|
export { PnpmWorkspaceState } from "./src/pnpm-workspace.ts";
|
|
51
52
|
export type { Catalog, AllowBuilds, DBXToolsPNPMWorkspaceOptions } from "./src/pnpm-workspace.ts";
|
|
52
53
|
export type { DBXToolsProjectLanguage, DBXToolsProjectOptions, DBXToolsProject, ApplyToProjectsOptions } from "./src/project.ts";
|
|
53
|
-
export { PackageIdentifier,
|
|
54
|
+
export { PackageIdentifier, DBX_TOOLS_LICENSE, DBXToolsNodeProject, ROOT_INSTALL_ONLY_MIXIN, DBXToolsTypeScriptProject } from "./src/project-js.ts";
|
|
54
55
|
export type { DBXToolsJavaScriptProject, DBXToolsReleaseMode, PullRequestTitlePolicyOptions, DBXToolsJavaScriptProjectOptions, DBXToolsTypeScriptProjectOptions } from "./src/project-js.ts";
|
|
55
56
|
export { DBXToolsPythonProject, DBXToolsPythonWorkspace } from "./src/project-py.ts";
|
|
56
57
|
export type { PythonRepositoryOptions, PythonPackageOptions, PythonTrustedPublisherOptions, DBXToolsPythonProjectOptions, PythonReleaseOptions, DBXToolsPythonWorkspaceOptions } from "./src/project-py.ts";
|
|
57
|
-
export { RustReleaseOs, RustReleaseCpu, UNIFFI_RELEASE_TARGETS,
|
|
58
|
-
export type { CargoDependencyOptions,
|
|
58
|
+
export { DBXToolsRustProject, RustReleaseOs, RustReleaseCpu, UNIFFI_RELEASE_TARGETS, DBXToolsRustWorkspace } from "./src/project-rs.ts";
|
|
59
|
+
export type { CargoDependency, CargoDependencyOptions, CargoExampleOptions, DBXToolsRustProjectOptions, RustCrateOptions, RustCliOptions, DBXToolsRustWorkspaceOptions, RustReleasePlatform, UniFFIReleaseTarget, RustBindingMapping, RustWorkspaceMapping, RustReleaseBinaryAssetMapping, RustReleaseBinaryMapping } from "./src/project-rs.ts";
|
|
60
|
+
export { PROJEN_VERSION } from "./src/projen-version.ts";
|
|
59
61
|
export { COMPILED_DIR, COMPILED_COMPILER_OPTIONS } from "./src/publish.ts";
|
|
60
62
|
export { DBXToolsRelease } from "./src/release.ts";
|
|
61
63
|
export type { ReleaseStage, ReleaseDocsOptions, ReleaseSummaryOptions, DBXToolsReleaseOptions } from "./src/release.ts";
|
|
@@ -70,7 +72,7 @@ export type { ReleaseUnitWorkspacePluginOptions } from "./src/release-please.ts"
|
|
|
70
72
|
export { AGNOSTIC_COMPILER_OPTIONS, PACKAGE_TAG_MIXINS } from "./src/tags.ts";
|
|
71
73
|
export type { PackageTag } from "./src/tags.ts";
|
|
72
74
|
export { DBXToolsRootTsconfig } from "./src/tsconfig.ts";
|
|
73
|
-
export { makeDefaultedInterfaceParametersOptional, addTypeScriptExtensionsToBindingImports, addExplicitInterfaceReexports } from "./src/uniffi.ts";
|
|
75
|
+
export { makeDefaultedInterfaceParametersOptional, removeObsoleteInterfaceAliases, addTypeScriptExtensionsToBindingImports, addExplicitInterfaceReexports } from "./src/uniffi.ts";
|
|
74
76
|
export type { TypeScriptBindingModule } from "./src/uniffi.ts";
|
|
75
77
|
export { DBXToolsVsCode } from "./src/vscode.ts";
|
|
76
78
|
export type { IgnoreGroupOptions } from "./src/watch.ts";
|
package/package.json
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
"name": "@dbx-tools/projen",
|
|
3
3
|
"scripts": {
|
|
4
4
|
"build": "projen build",
|
|
5
|
+
"bundle:rust-release": "projen bundle:rust-release",
|
|
5
6
|
"clobber": "projen clobber",
|
|
6
7
|
"compile": "projen compile",
|
|
7
8
|
"default": "projen default",
|
|
@@ -20,22 +21,24 @@
|
|
|
20
21
|
"@types/node": "^24.6.0",
|
|
21
22
|
"@types/semver": "^7.7.1",
|
|
22
23
|
"constructs": "^10.0.0",
|
|
23
|
-
"projen": "
|
|
24
|
+
"projen": "0.103.27",
|
|
24
25
|
"tsoa": "^6.6.0",
|
|
25
26
|
"typescript": "^5.9.3"
|
|
26
27
|
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"projen": "^0.103.27"
|
|
30
|
+
},
|
|
27
31
|
"dependencies": {
|
|
28
32
|
"@clack/prompts": "^1.7.0",
|
|
29
|
-
"@dbx-tools/core": "^0.9.
|
|
30
|
-
"@dbx-tools/path": "^0.9.
|
|
31
|
-
"@dbx-tools/shared-core": "^0.9.
|
|
33
|
+
"@dbx-tools/core": "^0.9.7",
|
|
34
|
+
"@dbx-tools/path": "^0.9.7",
|
|
35
|
+
"@dbx-tools/shared-core": "^0.9.7",
|
|
32
36
|
"commander": "^15.0.0",
|
|
33
37
|
"concurrently": "^10.0.3",
|
|
34
38
|
"constructs": "^10.6.0",
|
|
35
39
|
"is-identifier": "^1",
|
|
36
40
|
"openapi-typescript": "^7.13.0",
|
|
37
41
|
"oxc-parser": "^0.90.0",
|
|
38
|
-
"projen": "^0.101.16",
|
|
39
42
|
"release-please": "17.11.2",
|
|
40
43
|
"semver": "^7.7.3",
|
|
41
44
|
"smol-toml": "1.8.0",
|
|
@@ -50,7 +53,7 @@
|
|
|
50
53
|
},
|
|
51
54
|
"main": "index.ts",
|
|
52
55
|
"license": "Apache-2.0",
|
|
53
|
-
"version": "0.9.
|
|
56
|
+
"version": "0.9.7",
|
|
54
57
|
"types": "index.ts",
|
|
55
58
|
"type": "module",
|
|
56
59
|
"exports": {
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
/** Projen-native standalone and workspace-owned Rust project implementation. */
|
|
2
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
3
|
+
import { basename, join, resolve } from "node:path";
|
|
4
|
+
import * as projectUtils from "@dbx-tools/core/project-utils";
|
|
5
|
+
import { stringUtils } from "@dbx-tools/shared-core";
|
|
6
|
+
import { License, Project, TextFile, TomlFile, javascript, type Task } from "projen";
|
|
7
|
+
import { DBX_TOOLS_LICENSE, projectRepositoryUrl } from "./project-js.ts";
|
|
8
|
+
import type { RustReleaseOs } from "./project-rs.ts";
|
|
9
|
+
import type { DBXToolsProject, DBXToolsProjectOptions } from "./project.ts";
|
|
10
|
+
import { readWorkspaceVersion } from "./workspace-version.ts";
|
|
11
|
+
|
|
12
|
+
export interface CargoDependencyOptions {
|
|
13
|
+
readonly version?: string;
|
|
14
|
+
readonly workspace?: boolean;
|
|
15
|
+
readonly path?: string;
|
|
16
|
+
readonly optional?: boolean;
|
|
17
|
+
readonly package?: string;
|
|
18
|
+
readonly defaultFeatures?: boolean;
|
|
19
|
+
readonly features?: readonly string[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type CargoDependency = string | CargoDependencyOptions;
|
|
23
|
+
|
|
24
|
+
export interface RustCliOptions {
|
|
25
|
+
/** `dbx` subcommand. Defaults to the Rust package directory. */
|
|
26
|
+
readonly command?: string;
|
|
27
|
+
/** Root CLI help text. Defaults to the Rust package description. */
|
|
28
|
+
readonly description?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Explicit Cargo example target and the feature gate required to compile it. */
|
|
32
|
+
export interface CargoExampleOptions {
|
|
33
|
+
readonly name: string;
|
|
34
|
+
/** Source path relative to the crate root. Defaults to Cargo's conventional path. */
|
|
35
|
+
readonly path?: string;
|
|
36
|
+
/** Features Cargo must enable before compiling this example. */
|
|
37
|
+
readonly requiredFeatures?: readonly string[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Cargo package, target, binding, and release behavior shared by every Rust project. */
|
|
41
|
+
export interface RustCrateOptions {
|
|
42
|
+
readonly description?: string;
|
|
43
|
+
/** Keep this crate unpublished and out of public docs. */
|
|
44
|
+
readonly private?: boolean;
|
|
45
|
+
/** Build this crate's binary for each target and attach it to the GitHub release. */
|
|
46
|
+
readonly release?: boolean;
|
|
47
|
+
/** Omit this crate and any release binary artifact from these operating systems. */
|
|
48
|
+
readonly releaseExcludeOs?: readonly RustReleaseOs[];
|
|
49
|
+
readonly dependencies?: Readonly<Record<string, CargoDependency>>;
|
|
50
|
+
readonly devDependencies?: Readonly<Record<string, CargoDependency>>;
|
|
51
|
+
readonly features?: Readonly<Record<string, readonly string[]>>;
|
|
52
|
+
readonly defaultFeatures?: readonly string[];
|
|
53
|
+
/** Explicit example targets, including optional Cargo feature gates. */
|
|
54
|
+
readonly examples?: readonly CargoExampleOptions[];
|
|
55
|
+
/** Cargo and release executable name. Defaults to the generated package name. */
|
|
56
|
+
readonly binaryName?: string;
|
|
57
|
+
/** Publish this release binary through the generated `dbx` command registry. */
|
|
58
|
+
readonly cli?: boolean | RustCliOptions;
|
|
59
|
+
readonly bindings?: readonly ("node" | "python")[];
|
|
60
|
+
readonly uniffiConfig?: Readonly<Record<string, unknown>>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Options for a standalone or workspace-owned Projen-native Cargo project. */
|
|
64
|
+
export interface DBXToolsRustProjectOptions extends DBXToolsProjectOptions, RustCrateOptions {
|
|
65
|
+
/** Cargo package name. */
|
|
66
|
+
readonly name: string;
|
|
67
|
+
readonly parent?: Project;
|
|
68
|
+
/** Package directory relative to its workspace root; inferred from `outdir` when omitted. */
|
|
69
|
+
readonly directory?: string;
|
|
70
|
+
/** Cargo workspace root used for generated workspace mappings. Defaults to `.`. */
|
|
71
|
+
readonly workspaceRoot?: string;
|
|
72
|
+
/** Inherit unspecified Cargo package metadata from `[workspace.package]`. */
|
|
73
|
+
readonly workspace?: boolean;
|
|
74
|
+
/** Crate-name scope retained for workspace package and binding naming. */
|
|
75
|
+
readonly scope?: string;
|
|
76
|
+
/** Concrete standalone package version. Defaults to `0.1.0`. */
|
|
77
|
+
readonly version?: string;
|
|
78
|
+
/** Rust edition. Defaults to `2021` for standalone projects. */
|
|
79
|
+
readonly edition?: string;
|
|
80
|
+
/** Minimum supported Rust version. Defaults to `1.82` for standalone projects. */
|
|
81
|
+
readonly rustVersion?: string;
|
|
82
|
+
/** SPDX license identifier. Defaults to Apache-2.0 for standalone projects. */
|
|
83
|
+
readonly license?: string;
|
|
84
|
+
/** Copyright owner written to a standalone license file. Defaults to the package name. */
|
|
85
|
+
readonly copyrightOwner?: string;
|
|
86
|
+
/** Source repository URL. Auto-detected for standalone projects when possible. */
|
|
87
|
+
readonly repository?: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function rustSources(directory: string): string[] {
|
|
91
|
+
if (!existsSync(directory)) return [];
|
|
92
|
+
const files: string[] = [];
|
|
93
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
94
|
+
const path = join(directory, entry.name);
|
|
95
|
+
if (entry.isDirectory()) files.push(...rustSources(path));
|
|
96
|
+
else if (entry.isFile() && entry.name.endsWith(".rs")) files.push(path);
|
|
97
|
+
}
|
|
98
|
+
return files;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Whether a Rust crate embeds the UniFFI proc-macro scaffolding marker. */
|
|
102
|
+
export function hasUniFFIBindings(directory: string): boolean {
|
|
103
|
+
return rustSources(join(directory, "src")).some((path) =>
|
|
104
|
+
/\buniffi\s*::\s*setup_scaffolding\s*!\s*\(/.test(readFileSync(path, "utf8")),
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function discoverRustCrates(root: string): string[] {
|
|
109
|
+
if (!existsSync(root)) return [];
|
|
110
|
+
return readdirSync(root, { withFileTypes: true })
|
|
111
|
+
.filter((entry) => entry.isDirectory())
|
|
112
|
+
.filter((entry) => rustSources(join(root, entry.name, "src")).length > 0)
|
|
113
|
+
.map((entry) => entry.name)
|
|
114
|
+
.sort();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function cargoDependency(
|
|
118
|
+
value: CargoDependency,
|
|
119
|
+
workspaceVersion?: string,
|
|
120
|
+
): string | Record<string, unknown> {
|
|
121
|
+
if (typeof value === "string") return value;
|
|
122
|
+
return {
|
|
123
|
+
...(value.version ? { version: value.version } : {}),
|
|
124
|
+
...(!value.version && value.path && workspaceVersion ? { version: workspaceVersion } : {}),
|
|
125
|
+
...(value.workspace ? { workspace: true } : {}),
|
|
126
|
+
...(value.path ? { path: value.path } : {}),
|
|
127
|
+
...(value.package ? { package: value.package } : {}),
|
|
128
|
+
...(value.optional ? { optional: true } : {}),
|
|
129
|
+
...(value.defaultFeatures === false ? { "default-features": false } : {}),
|
|
130
|
+
...(value.features?.length ? { features: [...value.features] } : {}),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function structuredTomlSections(
|
|
135
|
+
value: Readonly<Record<string, unknown>>,
|
|
136
|
+
): Record<string, unknown> {
|
|
137
|
+
const result: Record<string, unknown> = {};
|
|
138
|
+
for (const [section, contents] of Object.entries(value)) {
|
|
139
|
+
const path = section.split(".");
|
|
140
|
+
let target = result;
|
|
141
|
+
for (const part of path.slice(0, -1)) {
|
|
142
|
+
const current = target[part];
|
|
143
|
+
if (current === undefined) {
|
|
144
|
+
target[part] = {};
|
|
145
|
+
} else if (!current || typeof current !== "object" || Array.isArray(current)) {
|
|
146
|
+
throw new Error(`Conflicting TOML section ${section}`);
|
|
147
|
+
}
|
|
148
|
+
target = target[part] as Record<string, unknown>;
|
|
149
|
+
}
|
|
150
|
+
target[path.at(-1)!] = contents;
|
|
151
|
+
}
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
interface ResolvedRustProjectOptions extends DBXToolsRustProjectOptions {
|
|
156
|
+
readonly directory: string;
|
|
157
|
+
readonly outdir: string;
|
|
158
|
+
readonly workspaceRoot: string;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function resolveRustProjectOptions(
|
|
162
|
+
options: DBXToolsRustProjectOptions,
|
|
163
|
+
): ResolvedRustProjectOptions {
|
|
164
|
+
if (!/^[A-Za-z0-9_][A-Za-z0-9_-]*$/.test(options.name)) {
|
|
165
|
+
throw new Error(`Invalid Cargo package name: ${options.name}`);
|
|
166
|
+
}
|
|
167
|
+
const directory =
|
|
168
|
+
options.directory ??
|
|
169
|
+
(options.outdir && options.outdir !== "."
|
|
170
|
+
? basename(options.outdir)
|
|
171
|
+
: stringUtils.toSlug(options.name));
|
|
172
|
+
return {
|
|
173
|
+
...options,
|
|
174
|
+
directory,
|
|
175
|
+
outdir: options.outdir ?? (options.parent ? directory : "."),
|
|
176
|
+
workspaceRoot: options.workspaceRoot ?? ".",
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function packageOptions(
|
|
181
|
+
options: ResolvedRustProjectOptions,
|
|
182
|
+
): RustCrateOptions & { readonly directory: string } {
|
|
183
|
+
return {
|
|
184
|
+
directory: options.directory,
|
|
185
|
+
...(options.description !== undefined ? { description: options.description } : {}),
|
|
186
|
+
...(options.private !== undefined ? { private: options.private } : {}),
|
|
187
|
+
...(options.release !== undefined ? { release: options.release } : {}),
|
|
188
|
+
...(options.releaseExcludeOs ? { releaseExcludeOs: options.releaseExcludeOs } : {}),
|
|
189
|
+
...(options.dependencies ? { dependencies: options.dependencies } : {}),
|
|
190
|
+
...(options.devDependencies ? { devDependencies: options.devDependencies } : {}),
|
|
191
|
+
...(options.features ? { features: options.features } : {}),
|
|
192
|
+
...(options.defaultFeatures ? { defaultFeatures: options.defaultFeatures } : {}),
|
|
193
|
+
...(options.binaryName ? { binaryName: options.binaryName } : {}),
|
|
194
|
+
...(options.cli !== undefined ? { cli: options.cli } : {}),
|
|
195
|
+
...(options.bindings ? { bindings: options.bindings } : {}),
|
|
196
|
+
...(options.uniffiConfig ? { uniffiConfig: options.uniffiConfig } : {}),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function inheritedCargoValue(
|
|
201
|
+
workspace: boolean,
|
|
202
|
+
configured: string | undefined,
|
|
203
|
+
fallback: string,
|
|
204
|
+
): string | { workspace: true } {
|
|
205
|
+
return workspace && configured === undefined ? { workspace: true } : (configured ?? fallback);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** One standalone or workspace-owned Cargo project. */
|
|
209
|
+
export class RustProject extends Project implements DBXToolsProject {
|
|
210
|
+
readonly language = "rust" as const;
|
|
211
|
+
readonly crateName: string;
|
|
212
|
+
readonly workspaceRoot: string;
|
|
213
|
+
readonly scope: string;
|
|
214
|
+
readonly packageOptions: RustCrateOptions & { readonly directory: string };
|
|
215
|
+
readonly uniffi: boolean;
|
|
216
|
+
readonly manifestFile: TomlFile;
|
|
217
|
+
readonly lintTask: Task;
|
|
218
|
+
readonly formatTask: Task;
|
|
219
|
+
readonly formatCheckTask: Task;
|
|
220
|
+
|
|
221
|
+
constructor(projectOptions: DBXToolsRustProjectOptions) {
|
|
222
|
+
const options = resolveRustProjectOptions(projectOptions);
|
|
223
|
+
super({ parent: options.parent, outdir: options.outdir, name: options.name });
|
|
224
|
+
this.crateName = options.name;
|
|
225
|
+
this.workspaceRoot = options.workspaceRoot;
|
|
226
|
+
this.scope = stringUtils.toSlug(options.scope ?? options.name.split("-")[0]!);
|
|
227
|
+
this.packageOptions = packageOptions(options);
|
|
228
|
+
this.uniffi = hasUniFFIBindings(this.outdir);
|
|
229
|
+
const library = existsSync(join(this.outdir, "src/lib.rs"));
|
|
230
|
+
const binary = existsSync(join(this.outdir, "src/main.rs"));
|
|
231
|
+
const binaryName = options.binaryName ?? this.crateName;
|
|
232
|
+
const workspace = options.workspace === true;
|
|
233
|
+
const projectDirectory = options.parent
|
|
234
|
+
? resolve(options.parent.outdir, options.outdir)
|
|
235
|
+
: resolve(options.outdir);
|
|
236
|
+
const repository =
|
|
237
|
+
options.repository ??
|
|
238
|
+
(options.parent instanceof javascript.NodeProject
|
|
239
|
+
? projectRepositoryUrl(options.parent)
|
|
240
|
+
: projectUtils.repositoryUrl(projectDirectory));
|
|
241
|
+
const dependencyVersion =
|
|
242
|
+
options.version ?? (options.parent ? readWorkspaceVersion(options.parent.outdir) : "0.1.0");
|
|
243
|
+
const manifest: Record<string, unknown> = {
|
|
244
|
+
...(!workspace ? { workspace: {} } : {}),
|
|
245
|
+
package: {
|
|
246
|
+
name: this.crateName,
|
|
247
|
+
version: inheritedCargoValue(workspace, options.version, "0.1.0"),
|
|
248
|
+
edition: inheritedCargoValue(workspace, options.edition, "2021"),
|
|
249
|
+
"rust-version": inheritedCargoValue(workspace, options.rustVersion, "1.82"),
|
|
250
|
+
description: options.description ?? this.crateName,
|
|
251
|
+
license: inheritedCargoValue(workspace, options.license, DBX_TOOLS_LICENSE),
|
|
252
|
+
...(workspace && options.repository === undefined
|
|
253
|
+
? { repository: { workspace: true } }
|
|
254
|
+
: repository
|
|
255
|
+
? { repository }
|
|
256
|
+
: {}),
|
|
257
|
+
...(options.private ? { publish: false } : {}),
|
|
258
|
+
},
|
|
259
|
+
...(library
|
|
260
|
+
? {
|
|
261
|
+
lib: {
|
|
262
|
+
name: this.crateName.replaceAll("-", "_"),
|
|
263
|
+
path: "src/lib.rs",
|
|
264
|
+
...(this.uniffi ? { "crate-type": ["lib", "cdylib"] } : {}),
|
|
265
|
+
},
|
|
266
|
+
}
|
|
267
|
+
: {}),
|
|
268
|
+
...(this.uniffi || binary
|
|
269
|
+
? {
|
|
270
|
+
bin: [
|
|
271
|
+
...(this.uniffi
|
|
272
|
+
? [{ name: `${this.crateName}-uniffi-bindgen`, path: "uniffi-bindgen.rs" }]
|
|
273
|
+
: []),
|
|
274
|
+
...(binary ? [{ name: binaryName, path: "src/main.rs" }] : []),
|
|
275
|
+
],
|
|
276
|
+
}
|
|
277
|
+
: {}),
|
|
278
|
+
...(options.features || options.defaultFeatures
|
|
279
|
+
? {
|
|
280
|
+
features: {
|
|
281
|
+
...(options.defaultFeatures ? { default: [...options.defaultFeatures] } : {}),
|
|
282
|
+
...options.features,
|
|
283
|
+
},
|
|
284
|
+
}
|
|
285
|
+
: {}),
|
|
286
|
+
...(options.examples?.length
|
|
287
|
+
? {
|
|
288
|
+
example: options.examples.map((example) => ({
|
|
289
|
+
name: example.name,
|
|
290
|
+
...(example.path ? { path: example.path } : {}),
|
|
291
|
+
...(example.requiredFeatures?.length
|
|
292
|
+
? { "required-features": [...example.requiredFeatures] }
|
|
293
|
+
: {}),
|
|
294
|
+
})),
|
|
295
|
+
}
|
|
296
|
+
: {}),
|
|
297
|
+
...(options.dependencies
|
|
298
|
+
? {
|
|
299
|
+
dependencies: Object.fromEntries(
|
|
300
|
+
Object.entries(options.dependencies).map(([name, value]) => [
|
|
301
|
+
name,
|
|
302
|
+
cargoDependency(value, dependencyVersion),
|
|
303
|
+
]),
|
|
304
|
+
),
|
|
305
|
+
}
|
|
306
|
+
: {}),
|
|
307
|
+
...(options.devDependencies
|
|
308
|
+
? {
|
|
309
|
+
"dev-dependencies": Object.fromEntries(
|
|
310
|
+
Object.entries(options.devDependencies).map(([name, value]) => [
|
|
311
|
+
name,
|
|
312
|
+
cargoDependency(value, dependencyVersion),
|
|
313
|
+
]),
|
|
314
|
+
),
|
|
315
|
+
}
|
|
316
|
+
: {}),
|
|
317
|
+
};
|
|
318
|
+
this.manifestFile = new TomlFile(this, "Cargo.toml", { marker: false, obj: manifest });
|
|
319
|
+
if (this.uniffi) {
|
|
320
|
+
new TextFile(this, "uniffi-bindgen.rs", {
|
|
321
|
+
lines: ["fn main() {", " uniffi::uniffi_bindgen_main();", "}", ""],
|
|
322
|
+
});
|
|
323
|
+
new TomlFile(this, "uniffi.toml", {
|
|
324
|
+
marker: false,
|
|
325
|
+
obj: structuredTomlSections(
|
|
326
|
+
options.uniffiConfig ?? {
|
|
327
|
+
"bindings.python": { cdylib_name: this.crateName.replaceAll("-", "_") },
|
|
328
|
+
"bindings.typescript": { strictTypeChecking: true },
|
|
329
|
+
},
|
|
330
|
+
),
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
this.compileTask.reset("cargo build");
|
|
334
|
+
this.testTask.reset("cargo test");
|
|
335
|
+
this.packageTask.reset("cargo package");
|
|
336
|
+
this.lintTask = this.addTask("lint", {
|
|
337
|
+
description: "Lint every Rust target and feature",
|
|
338
|
+
exec: "cargo clippy --all-targets --all-features",
|
|
339
|
+
});
|
|
340
|
+
this.formatTask = this.addTask("format", {
|
|
341
|
+
description: "Format Rust sources",
|
|
342
|
+
exec: "cargo fmt",
|
|
343
|
+
});
|
|
344
|
+
this.formatCheckTask = this.addTask("format:check", {
|
|
345
|
+
description: "Check Rust source formatting",
|
|
346
|
+
exec: "cargo fmt -- --check",
|
|
347
|
+
});
|
|
348
|
+
if (!workspace) {
|
|
349
|
+
new License(this, {
|
|
350
|
+
spdx: options.license ?? DBX_TOOLS_LICENSE,
|
|
351
|
+
copyrightOwner: options.copyrightOwner ?? options.name,
|
|
352
|
+
});
|
|
353
|
+
this.gitignore.addPatterns("target/");
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|