@dbx-tools/projen 0.9.5 → 0.9.6

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 (47) hide show
  1. package/README.md +64 -16
  2. package/index.ts +7 -5
  3. package/package.json +9 -6
  4. package/src/_rust-project.ts +356 -0
  5. package/src/_rust-release-workflow.ts +901 -0
  6. package/src/_rust-release.ts +605 -0
  7. package/src/_task-command.ts +89 -0
  8. package/src/barrels.ts +9 -7
  9. package/src/engine-root.ts +5 -5
  10. package/src/openapi.ts +1 -1
  11. package/src/packages.ts +9 -9
  12. package/src/pnpm-workspace.ts +1 -1
  13. package/src/project-js.ts +19 -25
  14. package/src/project-py.ts +6 -6
  15. package/src/project-rs.ts +100 -1098
  16. package/src/projen-version.ts +9 -0
  17. package/src/publish.ts +3 -4
  18. package/src/release-catalog.ts +1 -1
  19. package/src/release.ts +11 -12
  20. package/src/scaffold.ts +1 -1
  21. package/src/uniffi.ts +7 -0
  22. package/src/watch.ts +2 -2
  23. package/src/workspace-version.ts +20 -15
  24. package/tasks/barrels.ts +4 -4
  25. package/tasks/build-rust-release.ts +68 -0
  26. package/tasks/bump.ts +3 -2
  27. package/tasks/clean.ts +3 -3
  28. package/tasks/local-publish.ts +21 -3
  29. package/tasks/openapi.ts +2 -2
  30. package/tasks/publish-npm.ts +12 -6
  31. package/tasks/publish-python.ts +2 -2
  32. package/tasks/publish-uniffi-local.ts +11 -15
  33. package/tasks/publish.ts +88 -88
  34. package/tasks/python-registry.ts +1 -1
  35. package/tasks/release-bootstrap.ts +2 -2
  36. package/tasks/release-plan.ts +1 -1
  37. package/tasks/release-please.ts +1 -1
  38. package/tasks/release-pr.ts +60 -115
  39. package/tasks/release-request.ts +8 -29
  40. package/tasks/release-summary-units.ts +2 -9
  41. package/tasks/release-summary.ts +35 -24
  42. package/tasks/release-version.ts +44 -0
  43. package/tasks/rust-release.d.ts +41 -0
  44. package/tasks/rust-release.mjs +1717 -0
  45. package/tasks/uniffi-python.js +1 -1
  46. package/tasks/uniffi.ts +7 -1
  47. package/tasks/version-check.ts +6 -5
package/src/barrels.ts CHANGED
@@ -59,7 +59,7 @@
59
59
  import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
60
60
  import { join, relative } from "node:path";
61
61
  import { find } from "@dbx-tools/path";
62
- import { json, string } from "@dbx-tools/shared-core";
62
+ import { json, stringUtils } from "@dbx-tools/shared-core";
63
63
  import isIdentifier from "is-identifier";
64
64
  import { header, isGenerated, makeReadonly, makeWritable, type HeaderOpts } from "./generated.ts";
65
65
  import { moduleExports, moduleStatements, type ModuleExport } from "./module-exports.ts";
@@ -125,9 +125,11 @@ const UNIFFI_BINDINGS_FILE = "bindings.ts";
125
125
  const UNIFFI_GENERATED_FILE = "_bindings.ts";
126
126
  const UNIFFI_FFI_FILE = "_bindings-ffi.ts";
127
127
 
128
- /** `pnpm-workspace` -> `pnpmWorkspace`; `local-fs` -> `localFS` (`fs` -> `FS`). */
129
- function kebabToCamel(segment: string): string {
130
- const tokens = [...string.tokenizeWithOptions({ lowerCase: true, capitalize: true }, segment)];
128
+ /** `config-tools` / `config_tools` -> `configTools`; `local-fs` -> `localFS`. */
129
+ function moduleSegmentToCamel(segment: string): string {
130
+ const tokens = [
131
+ ...stringUtils.tokenizeWithOptions({ lowerCase: true, capitalize: true }, segment),
132
+ ];
131
133
  if (tokens.length === 0) return segment;
132
134
  const [first, ...rest] = tokens;
133
135
  // Standalone acronym modules (`fs`, `ai`) stay lowercase so they match
@@ -145,14 +147,14 @@ function modulePathToNamespace(modulePath: string): string {
145
147
  const rel = modulePath.replace(/^\.\/src\//, "").replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
146
148
  const segments = rel.split("/");
147
149
  if (segments.at(-1) === "index" && segments.length > 1) segments.pop();
148
- const names = segments.map(kebabToCamel);
150
+ const names = segments.map(moduleSegmentToCamel);
149
151
  let name =
150
152
  names.length === 1
151
153
  ? names[0]!
152
154
  : names[0]! +
153
155
  names
154
156
  .slice(1)
155
- .map((s) => string.capitalize(s))
157
+ .map((s) => stringUtils.capitalize(s))
156
158
  .join("");
157
159
  if (!isIdentifier(name)) {
158
160
  name = `${name}Module`;
@@ -576,7 +578,7 @@ export function generateBarrels(
576
578
  const names = failures.map((f) => relative(projectRoot, f.dir) || f.dir);
577
579
  throw new AggregateError(
578
580
  failures.map((f) => f.err),
579
- `${string.pluralize(failures.length, "barrel")} failed: ${names.join(", ")}`,
581
+ `${stringUtils.pluralize(failures.length, "barrel")} failed: ${names.join(", ")}`,
580
582
  );
581
583
  }
582
584
  return total;
@@ -6,8 +6,8 @@
6
6
  */
7
7
  import { dirname } from "node:path";
8
8
  import { fileURLToPath } from "node:url";
9
- import { project } from "@dbx-tools/core";
10
- import { functionModule } from "@dbx-tools/shared-core";
9
+ import * as projectUtils from "@dbx-tools/core/project-utils";
10
+ import { functionUtils } from "@dbx-tools/shared-core";
11
11
 
12
12
  const ENGINE_PKG = "@dbx-tools/projen";
13
13
 
@@ -17,11 +17,11 @@ const ENGINE_PKG = "@dbx-tools/projen";
17
17
  * Walks up from this module with shared-core's {@link project.root} (the nearest
18
18
  * package bounded by the enclosing npm/git root), so it resolves both in-repo and
19
19
  * when installed as a dependency. Memoized with shared-core's
20
- * {@link functionModule.memoize}, which caches only a successful result - a
20
+ * {@link functionUtils.memoize}, which caches only a successful result - a
21
21
  * throw is retried on the next call.
22
22
  */
23
- export const resolvePkgRoot = functionModule.memoize((): string => {
24
- const found = project.root(dirname(fileURLToPath(import.meta.url)));
23
+ export const resolvePkgRoot = functionUtils.memoize((): string => {
24
+ const found = projectUtils.root(dirname(fileURLToPath(import.meta.url)));
25
25
  if (!found) throw new Error(`${ENGINE_PKG} package root not found`);
26
26
  return found;
27
27
  });
package/src/openapi.ts CHANGED
@@ -35,7 +35,7 @@ import {
35
35
  import { createRequire } from "node:module";
36
36
  import { join } from "node:path";
37
37
  import { promisify } from "node:util";
38
- import { bin } from "@dbx-tools/core";
38
+ import * as bin from "@dbx-tools/core/bin";
39
39
  import { find } from "@dbx-tools/path";
40
40
  import { log } from "@dbx-tools/shared-core";
41
41
  import type * as ts from "typescript";
package/src/packages.ts CHANGED
@@ -25,14 +25,14 @@
25
25
  */
26
26
  import { existsSync, readFileSync } from "node:fs";
27
27
  import { extname, relative, resolve, sep } from "node:path";
28
- import { project as coreProject } from "@dbx-tools/core";
28
+ import * as projectUtils from "@dbx-tools/core/project-utils";
29
29
  import { find } from "@dbx-tools/path";
30
- import { json, object, string } from "@dbx-tools/shared-core";
30
+ import { json, object, stringUtils } from "@dbx-tools/shared-core";
31
31
  import { parse } from "yaml";
32
32
 
33
33
  /** Resolve the nearest package/projenrc root at call time. */
34
34
  export function resolveRepoRoot(cwd: string = process.cwd()): string {
35
- return coreProject.root(cwd) ?? cwd;
35
+ return projectUtils.root(cwd) ?? cwd;
36
36
  }
37
37
 
38
38
  /** Current-process repository root for executable task entrypoints. */
@@ -47,11 +47,11 @@ export const DEFAULT_PACKAGE_ROOTS = ["packages"] as const;
47
47
  /**
48
48
  * A project name: the root `package.json` name, else the git remote's repo
49
49
  * name, else the root folder name. Delegates to `@dbx-tools/core`'s
50
- * {@link coreProject.name}, which is also what a consuming repo's own tooling
50
+ * {@link projectUtils.name}, which is also what a consuming repo's own tooling
51
51
  * sees, so the engine and its host agree on the name.
52
52
  */
53
53
  export function projectName(projectRoot: string = resolveRepoRoot()): string {
54
- return coreProject.name(projectRoot);
54
+ return projectUtils.name(projectRoot);
55
55
  }
56
56
 
57
57
  const MODULE_EXTS = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
@@ -65,7 +65,7 @@ export function toPosix(p: string): string {
65
65
 
66
66
  /**
67
67
  * Cumulative nesting tags from a package's path segments relative to its discovery
68
- * root. Each segment is kebab-cased with {@link string.toSlug} (`coolDude` ->
68
+ * root. Each segment is kebab-cased with {@link stringUtils.toSlug} (`coolDude` ->
69
69
  * `cool-dude`). The leaf folder (the package name) is excluded when there are two
70
70
  * or more segments; a lone segment tags itself.
71
71
  */
@@ -75,7 +75,7 @@ function nestingTagsFromSegments(segments: readonly string[]): string[] {
75
75
  const out: string[] = [];
76
76
  let acc = "";
77
77
  for (const segment of prefix) {
78
- const token = string.toSlug(segment);
78
+ const token = stringUtils.toSlug(segment);
79
79
  if (!token) continue;
80
80
  acc = acc ? `${acc}-${token}` : token;
81
81
  out.push(acc);
@@ -288,7 +288,7 @@ function readManifestTags(dir: string): string[] | undefined {
288
288
  */
289
289
  export function syncResynthPaths(projectRoot: string = resolveRepoRoot()): string[] {
290
290
  const paths = readDbxToolsConfig(projectRoot)?.syncResynthPaths;
291
- return Array.isArray(paths) ? string.parseList(paths.map((p) => String(p))) : [];
291
+ return Array.isArray(paths) ? stringUtils.parseList(paths.map((p) => String(p))) : [];
292
292
  }
293
293
 
294
294
  /**
@@ -322,7 +322,7 @@ export function recordedPackages(projectRoot: string = resolveRepoRoot()): Recor
322
322
  export function recordedRoots(projectRoot: string = resolveRepoRoot()): string[] {
323
323
  const configured = readDbxToolsConfig(projectRoot)?.packageRoots;
324
324
  if (Array.isArray(configured)) {
325
- const roots = string.parseList(configured.map((root) => String(root)));
325
+ const roots = stringUtils.parseList(configured.map((root) => String(root)));
326
326
  if (roots.length) return roots;
327
327
  }
328
328
  const roots = new Set<string>(DEFAULT_PACKAGE_ROOTS);
@@ -2,7 +2,7 @@
2
2
  * Inputs for `pnpm-workspace.yaml`, which projen's NATIVE
3
3
  * `javascript.PnpmWorkspaceYaml` owns.
4
4
  *
5
- * projen (>= 0.101.16) writes this file itself: every pnpm `NodeProject` gets a
5
+ * projen writes this file itself: every pnpm `NodeProject` gets a
6
6
  * `PnpmWorkspaceYaml` component typed by `PnpmWorkspaceYamlSchema`, fed from
7
7
  * `pnpmOptions.workspaceYamlOptions`. Nothing here writes a file; this only
8
8
  * supplies the options object it renders, so the whole pnpm schema (`overrides`,
package/src/project-js.ts CHANGED
@@ -8,9 +8,9 @@
8
8
  */
9
9
  import { existsSync, readdirSync } from "node:fs";
10
10
  import { dirname, join, relative, resolve } from "node:path";
11
- import { project as coreProject } from "@dbx-tools/core";
11
+ import * as projectUtils from "@dbx-tools/core/project-utils";
12
12
  import { ignore, match } from "@dbx-tools/path";
13
- import { object, string, type OneOrMany } from "@dbx-tools/shared-core";
13
+ import { object, stringUtils, type OneOrMany } from "@dbx-tools/shared-core";
14
14
  import { type IConstruct } from "constructs";
15
15
  import { Component, IgnoreFile, Project, type TaskOptions, javascript, typescript } from "projen";
16
16
  import { BuildWorkflow } from "projen/lib/build";
@@ -40,6 +40,7 @@ import {
40
40
  } from "./packages.ts";
41
41
  import { PnpmWorkspaceState, type DBXToolsPNPMWorkspaceOptions } from "./pnpm-workspace.ts";
42
42
  import type { DBXToolsProject, DBXToolsProjectOptions as CommonProjectOptions } from "./project.ts";
43
+ import { PROJEN_VERSION } from "./projen-version.ts";
43
44
  import { applyCompiledPublish } from "./publish.ts";
44
45
  import {
45
46
  DBXToolsReleaseCatalog,
@@ -120,17 +121,17 @@ export class PackageIdentifier {
120
121
  * `@<first>/<rest joined by ->`.
121
122
  *
122
123
  * The leading segment is the npm `@scope`, kebab-cased with
123
- * {@link string.toSlug} so a multi-word scope survives intact
124
+ * {@link stringUtils.toSlug} so a multi-word scope survives intact
124
125
  * (`dbx-tools` -> `dbx-tools`, not `dbx`/`tools`). Every later path
125
- * segment is tokenized with {@link string.tokenize}, so nested folders
126
+ * segment is tokenized with {@link stringUtils.tokenize}, so nested folders
126
127
  * split into their own dash-joined name parts.
127
128
  */
128
129
  static of(...names: OneOrMany<string>): PackageIdentifier {
129
130
  const segments = names.flatMap((part) => part.split("/")).filter(Boolean);
130
- const scope = segments.length ? string.toSlug(segments[0]!) : "";
131
+ const scope = segments.length ? stringUtils.toSlug(segments[0]!) : "";
131
132
  const nameParts = [
132
133
  scope,
133
- ...segments.slice(1).flatMap((segment) => [...string.tokenize(segment)]),
134
+ ...segments.slice(1).flatMap((segment) => [...stringUtils.tokenize(segment)]),
134
135
  ].filter(Boolean);
135
136
  if (!nameParts.length) throw new Error(`Invalid name: ${names.join(", ")}`);
136
137
  if (nameParts.length === 1) return new PackageIdentifier(undefined, nameParts[0]!);
@@ -154,13 +155,13 @@ function configureRootPackage(project: javascript.NodeProject): void {
154
155
  * published source (without it, publish fails with E422). A child also carries the
155
156
  * monorepo `directory` subpath (its path relative to the root); the root omits it.
156
157
  * No-op when no git remote is detected and no `repository` override was supplied.
157
- * The URL is auto-detected + cached by {@link coreProject.repositoryUrl} (gh, then
158
+ * The URL is auto-detected + cached by {@link projectUtils.repositoryUrl} (gh, then
158
159
  * a normalized git remote), in npm's `git+https://.../repo.git` form.
159
160
  */
160
161
  function applyRepository(project: javascript.NodeProject, override?: string): void {
161
162
  const workspaceRoot = resolve(project.root.outdir);
162
163
  const url =
163
- override && override.length ? override : coreProject.repositoryUrl(workspaceRoot, "npm");
164
+ override && override.length ? override : projectUtils.repositoryUrl(workspaceRoot, "npm");
164
165
  if (!url) return;
165
166
  const directory = toPosix(relative(workspaceRoot, resolve(project.outdir)));
166
167
  project.package.addField("repository", {
@@ -278,7 +279,7 @@ export function projectRepositoryUrl(project: javascript.NodeProject): string |
278
279
  typeof repository === "string"
279
280
  ? repository
280
281
  : object.isRecord(repository)
281
- ? (string.trimToNull(repository.url) ?? undefined)
282
+ ? (stringUtils.trimToNull(repository.url) ?? undefined)
282
283
  : undefined;
283
284
  return configured?.replace(/^git\+/, "").replace(/\.git$/, "");
284
285
  }
@@ -382,14 +383,7 @@ const PRETTIER_SETTINGS: javascript.PrettierSettings = {
382
383
  endOfLine: javascript.EndOfLine.LF,
383
384
  };
384
385
 
385
- /**
386
- * The `projen` version every generated manifest pins.
387
- *
388
- * Kept as one constant so the root's devDependency and this engine's own
389
- * dependency can never drift apart - a synth run loads the engine from one copy
390
- * of projen and the tasks execute against another otherwise.
391
- */
392
- export const PROJEN_VERSION = "^0.101.16";
386
+ export { PROJEN_VERSION } from "./projen-version.ts";
393
387
 
394
388
  /** SPDX license shared by generated JavaScript, Python, and Rust packages. */
395
389
  export const DBX_TOOLS_LICENSE = "Apache-2.0";
@@ -412,7 +406,7 @@ function defaultProjectOptions(options: DBXToolsJavaScriptProjectOptions) {
412
406
  // phase installs with pnpm - so a deployed app keeps its catalog + build
413
407
  // allowances even though the local/CI manager is bun.
414
408
  packageManager: javascript.NodePackageManager.BUN,
415
- // Pinned rather than left to projen's "latest": 0.101.16 is the first release
409
+ // Pinned rather than left to projen's "latest": this is the co-tested release
416
410
  // whose `NodePackage` renders bun's `trustedDependencies` natively. Under bun,
417
411
  // projen does NOT create the `pnpm-workspace.yaml` component itself (that call
418
412
  // site is gated to pnpm), so the engine constructs it directly ({@link
@@ -750,7 +744,7 @@ export class DBXToolsNodeProject
750
744
  const steps = super.renderWorkflowSetup(options);
751
745
  if (this.parent) return steps;
752
746
  return steps.flatMap((step) => {
753
- if (step.uses === "oven-sh/setup-bun@v2") {
747
+ if (step.uses?.startsWith("oven-sh/setup-bun@")) {
754
748
  return [...bunCacheRestoreSteps(this, { ignorePaths: this.workflowCacheIgnorePaths })];
755
749
  }
756
750
  if (step.run === "bun install") {
@@ -963,7 +957,7 @@ function configureBuildWorkflow(
963
957
  "!**/package.json",
964
958
  "!**/pyproject.toml",
965
959
  "!**/index.ts",
966
- "!packages/js/node/rust-binary/src/_rust-release-binaries.ts",
960
+ "!packages/js/node/rust-binary/src/_release-binaries.ts",
967
961
  "!packages/js/node/appkit-graphiti/src/_python-runtime.ts",
968
962
  ],
969
963
  },
@@ -1216,7 +1210,7 @@ const DEFAULT_OMIT_RELATIVE_PREFIX = ["node"];
1216
1210
  function resolveOmitRelativePrefix(option: OneOrMany<string> | undefined): string[] {
1217
1211
  const raw = option === undefined ? DEFAULT_OMIT_RELATIVE_PREFIX : option;
1218
1212
  const list = Array.isArray(raw) ? raw : [raw];
1219
- return list.map((segment) => string.toSlug(segment)).filter(Boolean);
1213
+ return list.map((segment) => stringUtils.toSlug(segment)).filter(Boolean);
1220
1214
  }
1221
1215
 
1222
1216
  /**
@@ -1226,7 +1220,7 @@ function resolveOmitRelativePrefix(option: OneOrMany<string> | undefined): strin
1226
1220
  */
1227
1221
  function packageNameFor(scope: string, relPath: string, omitPrefixes: string[]): string {
1228
1222
  const segments = relPath.split("/").filter(Boolean);
1229
- if (segments.length > 1 && omitPrefixes.includes(string.toSlug(segments[0]!))) {
1223
+ if (segments.length > 1 && omitPrefixes.includes(stringUtils.toSlug(segments[0]!))) {
1230
1224
  segments.shift();
1231
1225
  }
1232
1226
  return PackageIdentifier.of(scope, segments.join("/")).packageName;
@@ -1267,14 +1261,14 @@ function engineSelfDependency(project: javascript.NodeProject): string | undefin
1267
1261
  const enginePkgJson = join(resolvePkgRoot(), "package.json");
1268
1262
  if (!toPosix(enginePkgJson).includes("/node_modules/")) return undefined;
1269
1263
  const engine = readPackageManifest(dirname(enginePkgJson));
1270
- const name = string.trimToNull(engine?.name);
1264
+ const name = stringUtils.trimToNull(engine?.name);
1271
1265
  if (!name) return undefined;
1272
- const version = string.trimToNull(engine?.version);
1266
+ const version = stringUtils.trimToNull(engine?.version);
1273
1267
 
1274
1268
  // No existing consumer manifest (or no entry) falls through to a computed pin.
1275
1269
  const consumer = readPackageManifest(resolve(project.outdir));
1276
1270
  const dependencyOf = (field: unknown): string | undefined =>
1277
- object.isRecord(field) ? (string.trimToNull(field[name]) ?? undefined) : undefined;
1271
+ object.isRecord(field) ? (stringUtils.trimToNull(field[name]) ?? undefined) : undefined;
1278
1272
  const existing = dependencyOf(consumer?.devDependencies) ?? dependencyOf(consumer?.dependencies);
1279
1273
  if (existing) return `${name}@${existing}`;
1280
1274
  return `${name}@^${version}`;
package/src/project-py.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /** Reusable uv workspace generation for Python packages hosted in a projen tree. */
2
- import { project as coreProject } from "@dbx-tools/core";
3
- import { string } from "@dbx-tools/shared-core";
2
+ import * as projectUtils from "@dbx-tools/core/project-utils";
3
+ import { stringUtils } from "@dbx-tools/shared-core";
4
4
  import { Component, License, TextFile, type Project, javascript, python, vscode } from "projen";
5
5
  import { JobPermission } from "projen/lib/github/workflows-model";
6
6
  import { BUN_VERSION, bunCacheRestoreSteps, bunCacheSaveStep } from "./bun-workflow.ts";
@@ -237,12 +237,12 @@ export class DBXToolsPythonWorkspace extends Component {
237
237
  constructor(project: javascript.NodeProject, options: DBXToolsPythonWorkspaceOptions) {
238
238
  super(project);
239
239
  const scope = isDBXToolsJavaScriptProject()(project)
240
- ? string.toSlug(project.scope)
241
- : string.toSlug(project.name).replace(/-root$/, "");
240
+ ? stringUtils.toSlug(project.scope)
241
+ : stringUtils.toSlug(project.name).replace(/-root$/, "");
242
242
  const repositoryUrl =
243
243
  options.repository?.url ??
244
244
  projectRepositoryUrl(project) ??
245
- coreProject.repositoryUrl(project.outdir);
245
+ projectUtils.repositoryUrl(project.outdir);
246
246
  if (!repositoryUrl) {
247
247
  throw new Error("Python workspace repository URL was not configured or detected");
248
248
  }
@@ -253,7 +253,7 @@ export class DBXToolsPythonWorkspace extends Component {
253
253
  };
254
254
  const packageIdentities: ResolvedPythonPackageOptions[] = options.packages.map((pkg) => ({
255
255
  ...pkg,
256
- name: pkg.name ?? `${scope}-${string.toSlug(pkg.directory)}`,
256
+ name: pkg.name ?? `${scope}-${stringUtils.toSlug(pkg.directory)}`,
257
257
  module: pkg.module ?? pythonModuleName(scope, pkg.directory),
258
258
  }));
259
259
  const packagesByDirectory = new Map(packageIdentities.map((pkg) => [pkg.directory, pkg]));