@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.
Files changed (50) hide show
  1. package/README.md +69 -17
  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 +954 -0
  6. package/src/_rust-release.ts +605 -0
  7. package/src/_task-command.ts +89 -0
  8. package/src/barrels.ts +12 -11
  9. package/src/bun-app.ts +96 -85
  10. package/src/bun-workflow.ts +60 -52
  11. package/src/engine-root.ts +5 -5
  12. package/src/openapi.ts +16 -9
  13. package/src/packages.ts +9 -9
  14. package/src/pnpm-workspace.ts +1 -1
  15. package/src/project-js.ts +19 -25
  16. package/src/project-py.ts +28 -23
  17. package/src/project-rs.ts +100 -1098
  18. package/src/projen-version.ts +9 -0
  19. package/src/publish.ts +3 -4
  20. package/src/release-catalog.ts +1 -1
  21. package/src/release.ts +212 -177
  22. package/src/scaffold.ts +1 -1
  23. package/src/uniffi.ts +7 -0
  24. package/src/watch.ts +2 -2
  25. package/src/workspace-version.ts +20 -15
  26. package/tasks/barrels.ts +4 -4
  27. package/tasks/build-rust-release.ts +68 -0
  28. package/tasks/bump.ts +3 -2
  29. package/tasks/clean.ts +3 -3
  30. package/tasks/local-publish.ts +21 -3
  31. package/tasks/openapi.ts +2 -2
  32. package/tasks/publish-npm.ts +12 -6
  33. package/tasks/publish-python.ts +2 -2
  34. package/tasks/publish-uniffi-local.ts +11 -15
  35. package/tasks/publish.ts +88 -88
  36. package/tasks/python-registry.ts +1 -1
  37. package/tasks/release-bootstrap.ts +2 -2
  38. package/tasks/release-plan.ts +1 -1
  39. package/tasks/release-please.ts +1 -1
  40. package/tasks/release-pr.ts +60 -115
  41. package/tasks/release-request.ts +8 -29
  42. package/tasks/release-summary-units.ts +2 -9
  43. package/tasks/release-summary.ts +35 -24
  44. package/tasks/release-version.ts +44 -0
  45. package/tasks/rust-release.d.ts +41 -0
  46. package/tasks/rust-release.mjs +1717 -0
  47. package/tasks/sync.ts +3 -3
  48. package/tasks/uniffi-python.js +1 -1
  49. package/tasks/uniffi.ts +7 -1
  50. package/tasks/version-check.ts +6 -5
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]));
@@ -766,23 +766,28 @@ export class DBXToolsPythonWorkspace extends Component {
766
766
  ];
767
767
  const helper = ".projen/pypi-trusted-publisher-instructions.mjs";
768
768
  new TextFile(project.root, helper, {
769
- lines: [
770
- "#!/usr/bin/env node",
771
- 'import { parseArgs } from "node:util";',
772
- "",
773
- "const { values } = parseArgs({",
774
- ' options: { secretFile: { type: "string" } },',
775
- "});",
776
- `const beforeAuthentication = ${JSON.stringify(linesBeforeAuthentication)};`,
777
- `const afterAuthentication = ${JSON.stringify(linesAfterAuthentication)};`,
778
- "const authentication = values.secretFile",
779
- " ? [",
780
- " `- If browser authentication is required, read credentials from ${values.secretFile} directly into the browser without printing, logging, or exposing secret values.`,",
781
- " `- If ${values.secretFile} is absent, invalid, or insufficient for authentication, pause and ask the user to complete authentication.`,",
782
- " ]",
783
- ' : ["- If browser authentication is required, pause and ask the user to complete authentication."];',
784
- 'process.stdout.write(`${[...beforeAuthentication, ...authentication, ...afterAuthentication].join("\\n").trimEnd()}\\n`);',
785
- ],
769
+ // prettier-ignore
770
+ lines: stringUtils.dedent(
771
+ // ============================================================================
772
+ /*js*/`
773
+ #!/usr/bin/env node
774
+ import { parseArgs } from "node:util";
775
+
776
+ const { values } = parseArgs({
777
+ options: { secretFile: { type: "string" } },
778
+ });
779
+ const beforeAuthentication = ${JSON.stringify(linesBeforeAuthentication)};
780
+ const afterAuthentication = ${JSON.stringify(linesAfterAuthentication)};
781
+ const authentication = values.secretFile
782
+ ? [
783
+ \`- If browser authentication is required, read credentials from \${values.secretFile} directly into the browser without printing, logging, or exposing secret values.\`,
784
+ \`- If \${values.secretFile} is absent, invalid, or insufficient for authentication, pause and ask the user to complete authentication.\`,
785
+ ]
786
+ : ["- If browser authentication is required, pause and ask the user to complete authentication."];
787
+ process.stdout.write(\`\${[...beforeAuthentication, ...authentication, ...afterAuthentication].join("\\n").trimEnd()}\\n\`);
788
+ `
789
+ // ============================================================================
790
+ ).split("\n"),
786
791
  });
787
792
  project.root.addTask("pypiTrustedPublisherInstructions", {
788
793
  description: "Print system-browser instructions for PyPI trusted publishers",