@dbx-tools/projen 0.6.60 → 0.6.61

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
@@ -124,35 +124,6 @@ package is attempted even if one fails; the failures are re-thrown together as a
124
124
  `AggregateError` naming each package, rather than the first one abandoning the
125
125
  rest of the sweep.
126
126
 
127
- ### Generate Node And Python From Rosetta Sources
128
-
129
- The experimental `rs-packages` convention keeps TypeScript as the canonical
130
- Node implementation while colocating an explicit Python implementation in the
131
- same file:
132
-
133
- ```ts
134
- export function capitalize(value: string): string {
135
- return value ? value[0]!.toUpperCase() + value.slice(1) : value;
136
- }
137
-
138
- /* @rs-python
139
- def capitalize(value: str) -> str:
140
- return value[:1].upper() + value[1:] if value else value
141
- @rs-end */
142
- ```
143
-
144
- By default, `rs-packages/shared/core/src/string.ts` generates:
145
-
146
- - `packages/shared/core/src/string.ts`
147
- - `python-packages/shared-core/src/shared_core/string.py`
148
-
149
- Use `// @rs-node <path>` or `// @rs-python-path <path>` only when the inferred
150
- paths are unsuitable. Every Rosetta source must live below a package's `src/`
151
- folder and include at least one Python block; generation fails rather than
152
- silently emitting an empty Python module. `DBXToolsNodeProject` enables the
153
- `rs-packages` root by default, accepts `rsPackageRoots` for custom roots, and
154
- accepts `rsPackageRoots: false` to disable the experiment.
155
-
156
127
  ## Generate OpenAPI Clients
157
128
 
158
129
  ```ts
@@ -217,7 +188,6 @@ file contract as the CLI.
217
188
  - `pnpmWorkspace` - generated pnpm workspace file and catalog model.
218
189
  - `barrels` / `moduleExports` - public entrypoint generation.
219
190
  - `codegen` - `.d.ts` to zod schema generation.
220
- - `rsPackages` - annotated TypeScript source to Node/Python generation.
221
191
  - `openapi` - tsoa/OpenAPI package generation.
222
192
  - `bunApp` / `tsconfig` / `vscode` - generated support files/components.
223
193
  - `generated` / `clean` / `watch` / `scaffold` - read-only file stamping,
@@ -226,8 +196,8 @@ file contract as the CLI.
226
196
  - `engineRoot` - engine package root resolution for bootstrapped repos.
227
197
 
228
198
  The engine registers its commands as projen tasks on the workspace root, so run
229
- them with `bun run <task>` - `sync` (add `--watch`), `barrels`, `openapi`,
230
- `rs-packages` (also supports `--watch`), and `clean`.
199
+ them with `bun run <task>` - `sync` (add `--watch`), `barrels`, `openapi`, and
200
+ `clean`.
231
201
  [`@dbx-tools/cli`](../packages/cli/dbx-tools) is only needed to
232
202
  bootstrap a folder that has no `.projenrc.ts` or toolchain yet.
233
203
 
@@ -237,15 +207,14 @@ Every repo-wide task lives on the root, and the root's `compile` / `test`
237
207
  delegate with `bun run --filter '*'` rather than emitting a step per member - so
238
208
  a new package is covered without a re-synth. Work from the root:
239
209
 
240
- | Task | What it does |
241
- | --------------------- | -------------------------------------------------- |
242
- | `bun run build` | `compile` + `test` + `package` across every member |
243
- | `bun run compile` | `tsc --build` in each member, in parallel |
244
- | `bun run test` | `eslint` once, then each member's tests |
245
- | `bun run sync` | re-synth (`--watch` to keep synthing) |
246
- | `bun run barrels` | regenerate the read-only `index.ts` barrels |
247
- | `bun run rs-packages` | regenerate Node/Python Rosetta outputs |
248
- | `bun run bump` | version, tag, and publish |
210
+ | Task | What it does |
211
+ | ----------------- | -------------------------------------------------- |
212
+ | `bun run build` | `compile` + `test` + `package` across every member |
213
+ | `bun run compile` | `tsc --build` in each member, in parallel |
214
+ | `bun run test` | `eslint` once, then each member's tests |
215
+ | `bun run sync` | re-synth (`--watch` to keep synthing) |
216
+ | `bun run barrels` | regenerate the read-only `index.ts` barrels |
217
+ | `bun run bump` | version, tag, and publish |
249
218
 
250
219
  Members intentionally keep only the tasks that something OTHER than a human
251
220
  invokes, so there is no second place to run the same thing:
package/index.ts CHANGED
@@ -18,13 +18,20 @@ export * as project from "./src/project.ts";
18
18
  export * as projectPredicate from "./src/project-predicate.ts";
19
19
  export * as publish from "./src/publish.ts";
20
20
  export * as release from "./src/release.ts";
21
- export * as rsPackages from "./src/rs-packages.ts";
22
21
  export * as scaffold from "./src/scaffold.ts";
23
22
  export * as tags from "./src/tags.ts";
24
23
  export * as tsconfig from "./src/tsconfig.ts";
25
24
  export * as vscode from "./src/vscode.ts";
26
25
  export * as watch from "./src/watch.ts";
27
- export { BUN_DEV_OVERRIDE, BUN_BUILD_OVERRIDE, BUN_APP_OVERRIDES, RootBunfigFile, BunfigFile, BunDevServerFile, BunBuildFile } from "./src/bun-app.ts";
26
+ export {
27
+ BUN_DEV_OVERRIDE,
28
+ BUN_BUILD_OVERRIDE,
29
+ BUN_APP_OVERRIDES,
30
+ RootBunfigFile,
31
+ BunfigFile,
32
+ BunDevServerFile,
33
+ BunBuildFile,
34
+ } from "./src/bun-app.ts";
28
35
  export { DBXToolsConfig } from "./src/dbx-tools-config.ts";
29
36
  export type { DBXToolsConfigOptions } from "./src/dbx-tools-config.ts";
30
37
  export { resolvePkgRoot } from "./src/engine-root.ts";
@@ -35,13 +42,21 @@ export { repoRoot, DEFAULT_PACKAGE_ROOTS, DiscoveredPackage } from "./src/packag
35
42
  export type { RecordedPackage } from "./src/packages.ts";
36
43
  export { PnpmWorkspaceState } from "./src/pnpm-workspace.ts";
37
44
  export type { Catalog, AllowBuilds, DBXToolsPNPMWorkspaceOptions } from "./src/pnpm-workspace.ts";
38
- export { PackageIdentifier, PROJEN_VERSION, DBXToolsNodeProject, DBXToolsTypeScriptProject } from "./src/project.ts";
39
- export type { DBXToolsProject, DBXToolsProjectOptions, DBXToolsTypeScriptProjectOptions, ApplyToProjectsOptions } from "./src/project.ts";
45
+ export {
46
+ PackageIdentifier,
47
+ PROJEN_VERSION,
48
+ DBXToolsNodeProject,
49
+ DBXToolsTypeScriptProject,
50
+ } from "./src/project.ts";
51
+ export type {
52
+ DBXToolsProject,
53
+ DBXToolsProjectOptions,
54
+ DBXToolsTypeScriptProjectOptions,
55
+ ApplyToProjectsOptions,
56
+ } from "./src/project.ts";
40
57
  export { COMPILED_DIR, COMPILED_COMPILER_OPTIONS } from "./src/publish.ts";
41
58
  export { DBXToolsRelease } from "./src/release.ts";
42
59
  export type { StandaloneRelease, DBXToolsReleaseOptions } from "./src/release.ts";
43
- export { DEFAULT_RS_PACKAGE_ROOTS, RsPackages } from "./src/rs-packages.ts";
44
- export type { RsPackageOutput, GenerateRsPackagesOptions } from "./src/rs-packages.ts";
45
60
  export { AGNOSTIC_COMPILER_OPTIONS, PACKAGE_TAG_MIXINS } from "./src/tags.ts";
46
61
  export type { PackageTag } from "./src/tags.ts";
47
62
  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.60",
30
- "@dbx-tools/path": "0.6.60",
31
- "@dbx-tools/shared-core": "0.6.60",
29
+ "@dbx-tools/core": "0.6.61",
30
+ "@dbx-tools/path": "0.6.61",
31
+ "@dbx-tools/shared-core": "0.6.61",
32
32
  "commander": "^15.0.0",
33
33
  "concurrently": "^10.0.3",
34
34
  "constructs": "^10.6.0",
@@ -47,7 +47,7 @@
47
47
  },
48
48
  "main": "index.ts",
49
49
  "license": "Apache-2.0",
50
- "version": "0.6.60",
50
+ "version": "0.6.61",
51
51
  "types": "index.ts",
52
52
  "type": "module",
53
53
  "exports": {
package/src/project.ts CHANGED
@@ -30,7 +30,6 @@ import {
30
30
  RootBunfigFile,
31
31
  } from "./bun-app.ts";
32
32
  import { DBXToolsVsCode } from "./vscode.ts";
33
- import { DEFAULT_RS_PACKAGE_ROOTS, RsPackages } from "./rs-packages.ts";
34
33
  import {
35
34
  DEFAULT_PACKAGE_ROOTS,
36
35
  type DiscoveredPackage,
@@ -520,11 +519,6 @@ export interface DBXToolsProjectOptions
520
519
  * is a member of the single bun workspace, so the root links it from source.
521
520
  */
522
521
  readonly extraWorkspaceMembers?: readonly string[];
523
- /**
524
- * Experimental TypeScript-to-Node/Python source roots. Defaults to
525
- * `["rs-packages"]`; set `false` to disable the generator.
526
- */
527
- readonly rsPackageRoots?: readonly string[] | false;
528
522
  }
529
523
 
530
524
  /** Options for {@link DBXToolsTypeScriptProject} (a package, or a compiling root). */
@@ -847,7 +841,6 @@ function registerRootTasks(project: javascript.NodeProject): void {
847
841
  applyTasks(project, {
848
842
  barrels: { exec: taskScript(project, "barrels.ts") },
849
843
  openapi: { exec: taskScript(project, "openapi.ts") },
850
- "rs-packages": { exec: taskScript(project, "rs-packages.ts"), receiveArgs: true },
851
844
  clean: { exec: taskScript(project, "clean.ts"), receiveArgs: true },
852
845
  // `receiveArgs` forwards `--watch`, so `bun run sync -- --watch` syncs once
853
846
  // then starts the single node-path watcher loop.
@@ -932,13 +925,6 @@ function initProject(
932
925
  project.vsCode = new DBXToolsVsCode(project);
933
926
 
934
927
  registerRootTasks(project);
935
- project.dbxToolsConfig.rsPackageRoots =
936
- options.rsPackageRoots === false
937
- ? false
938
- : [...(options.rsPackageRoots ?? DEFAULT_RS_PACKAGE_ROOTS)];
939
- if (options.rsPackageRoots !== false) {
940
- new RsPackages(project, options.rsPackageRoots ?? DEFAULT_RS_PACKAGE_ROOTS);
941
- }
942
928
  if (options.prettier || project.prettier) {
943
929
  const formatTask = project.tasks.tryFind("format") ?? project.addTask("format");
944
930
  formatTask.prependExec("prettier . --write", { receiveArgs: true });
package/tasks/sync.ts CHANGED
@@ -57,11 +57,6 @@ if (!process.argv.includes("--watch")) {
57
57
  { command: `bun "${taskPath("projenrc.ts")}"`, name: "projenrc", prefixColor: "magenta" },
58
58
  { command: `bun "${taskPath("barrels.ts")}" --watch`, name: "barrels", prefixColor: "cyan" },
59
59
  { command: `bun "${taskPath("openapi.ts")}" --watch`, name: "openapi", prefixColor: "green" },
60
- {
61
- command: `bun "${taskPath("rs-packages.ts")}" --watch`,
62
- name: "rs-packages",
63
- prefixColor: "yellow",
64
- },
65
60
  ],
66
61
  {
67
62
  prefix: "name",
@@ -1,237 +0,0 @@
1
- /**
2
- * Experimental Rosetta-source generation.
3
- *
4
- * A TypeScript file under `rs-packages/<package>/src/` is the canonical Node
5
- * implementation. Projen copies its TypeScript surface to the matching
6
- * `packages/<package>/src/` path and extracts one or more `@rs-python` blocks
7
- * into a conventional Python package path.
8
- */
9
- import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
10
- import { dirname, extname, join, relative, resolve } from "node:path";
11
- import { json, object } from "@dbx-tools/shared-core";
12
- import { Component, javascript } from "projen";
13
- import { header, makeReadonly, makeWritable } from "./generated.ts";
14
- import { toPosix } from "./packages.ts";
15
-
16
- export const DEFAULT_RS_PACKAGE_ROOTS = ["rs-packages"] as const;
17
-
18
- const SOURCE_EXTENSIONS = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
19
- const PYTHON_BLOCK = /\/\*\s*@rs-python\s*\n([\s\S]*?)\n\s*@rs-end\s*\*\//g;
20
- const NODE_OVERRIDE = /^\s*\/\/\s*@rs-node\s+(.+?)\s*$/m;
21
- const PYTHON_OVERRIDE = /^\s*\/\/\s*@rs-python-path\s+(.+?)\s*$/m;
22
- const RS_METADATA = /^\s*\/\/\s*@rs-(?:node|python-path)\s+.+?\s*$\n?/gm;
23
- const NODE_MARKER = "// GENERATED by projen synth (rs-packages)";
24
- const PYTHON_MARKER = "# GENERATED by projen synth (rs-packages)";
25
-
26
- export interface RsPackageOutput {
27
- readonly source: string;
28
- readonly node: string;
29
- readonly python: string;
30
- }
31
-
32
- export interface GenerateRsPackagesOptions {
33
- readonly projectRoot?: string;
34
- readonly roots?: readonly string[];
35
- }
36
-
37
- function sourceFiles(root: string): string[] {
38
- if (!existsSync(root)) return [];
39
- const files: string[] = [];
40
- const visit = (dir: string): void => {
41
- for (const entry of readdirSync(dir)) {
42
- const path = join(dir, entry);
43
- if (statSync(path).isDirectory()) visit(path);
44
- else if (SOURCE_EXTENSIONS.has(extname(entry))) files.push(path);
45
- }
46
- };
47
- visit(root);
48
- return files.sort();
49
- }
50
-
51
- function dedent(value: string): string {
52
- const lines = value.replace(/^\n+|\n+$/g, "").split("\n");
53
- const indents = lines
54
- .filter((line) => line.trim())
55
- .map((line) => line.match(/^\s*/)?.[0].length ?? 0);
56
- const width = indents.length ? Math.min(...indents) : 0;
57
- return lines.map((line) => line.slice(width)).join("\n");
58
- }
59
-
60
- function defaultOutputPaths(
61
- projectRoot: string,
62
- root: string,
63
- source: string,
64
- ): Pick<RsPackageOutput, "node" | "python"> {
65
- const sourceRelative = toPosix(relative(resolve(projectRoot, root), source));
66
- const segments = sourceRelative.split("/");
67
- const srcIndex = segments.indexOf("src");
68
- if (srcIndex < 1) {
69
- throw new Error(
70
- `Rosetta source must live under <package>/src/: ${toPosix(relative(projectRoot, source))}`,
71
- );
72
- }
73
-
74
- const packageSegments = segments.slice(0, srcIndex);
75
- const moduleSegments = segments.slice(srcIndex + 1);
76
- const moduleFile = moduleSegments.pop();
77
- if (!moduleFile) throw new Error(`Rosetta source has no module name: ${source}`);
78
-
79
- const pythonDistribution = packageSegments.join("-");
80
- const pythonImport = packageSegments.join("_").replace(/-/g, "_");
81
- const pythonModule = `${moduleFile.slice(0, -extname(moduleFile).length).replace(/-/g, "_")}.py`;
82
-
83
- return {
84
- node: resolve(
85
- projectRoot,
86
- "packages",
87
- ...packageSegments,
88
- "src",
89
- ...moduleSegments,
90
- moduleFile,
91
- ),
92
- python: resolve(
93
- projectRoot,
94
- "python-packages",
95
- pythonDistribution,
96
- "src",
97
- pythonImport,
98
- ...moduleSegments,
99
- pythonModule,
100
- ),
101
- };
102
- }
103
-
104
- function resolveOutput(
105
- projectRoot: string,
106
- override: RegExpMatchArray | null,
107
- fallback: string,
108
- ): string {
109
- const value = override?.[1]?.trim();
110
- return value ? resolve(projectRoot, value) : fallback;
111
- }
112
-
113
- function assertWritableOutput(projectRoot: string, path: string, marker: string): void {
114
- if (!existsSync(path)) return;
115
- if (readFileSync(path, "utf8").startsWith(marker)) return;
116
- throw new Error(
117
- `Refusing to overwrite hand-authored Rosetta output: ${toPosix(relative(projectRoot, path))}`,
118
- );
119
- }
120
-
121
- function renderSource(projectRoot: string, root: string, source: string): RsPackageOutput {
122
- const input = readFileSync(source, "utf8");
123
- const defaults = defaultOutputPaths(projectRoot, root, source);
124
- const node = resolveOutput(projectRoot, input.match(NODE_OVERRIDE), defaults.node);
125
- const python = resolveOutput(projectRoot, input.match(PYTHON_OVERRIDE), defaults.python);
126
- const pythonBlocks = [...input.matchAll(PYTHON_BLOCK)].map((match) => dedent(match[1] ?? ""));
127
-
128
- if (!pythonBlocks.length) {
129
- throw new Error(
130
- `Rosetta source needs at least one /* @rs-python ... @rs-end */ block: ${toPosix(relative(projectRoot, source))}`,
131
- );
132
- }
133
-
134
- const nodeBody = input.replace(PYTHON_BLOCK, "").replace(RS_METADATA, "").trim();
135
- const nodeContent = `${header({
136
- tool: "projen synth (rs-packages)",
137
- source: toPosix(relative(projectRoot, source)),
138
- })}\n${nodeBody}\n`;
139
- const pythonContent = [
140
- "# GENERATED by projen synth (rs-packages) - DO NOT EDIT.",
141
- `# Regenerated from ${toPosix(relative(projectRoot, source))}.`,
142
- "# Hand edits are overwritten; edit the Rosetta source instead.",
143
- "",
144
- pythonBlocks.join("\n\n"),
145
- "",
146
- ].join("\n");
147
-
148
- for (const [path, content] of [
149
- [node, nodeContent],
150
- [python, pythonContent],
151
- ] as const) {
152
- assertWritableOutput(projectRoot, path, path === node ? NODE_MARKER : PYTHON_MARKER);
153
- mkdirSync(dirname(path), { recursive: true });
154
- makeWritable(path);
155
- writeFileSync(path, content);
156
- makeReadonly(path);
157
- }
158
-
159
- return { source, node, python };
160
- }
161
-
162
- /** Read the roots recorded by synth for standalone tasks and watchers. */
163
- export function configuredRsPackageRoots(projectRoot: string): readonly string[] | false {
164
- const manifest = json.parseRecord(readFileSync(resolve(projectRoot, "package.json"), "utf8"));
165
- const config = object.isRecord(manifest?.dbxToolsConfig) ? manifest.dbxToolsConfig : undefined;
166
- const configured = config?.rsPackageRoots;
167
- if (configured === false) return false;
168
- if (Array.isArray(configured)) {
169
- const roots = configured.filter(
170
- (value): value is string => typeof value === "string" && !!value,
171
- );
172
- if (roots.length) return roots;
173
- }
174
- return DEFAULT_RS_PACKAGE_ROOTS;
175
- }
176
-
177
- export function discoverRsPackageOutputs(
178
- options: GenerateRsPackagesOptions = {},
179
- ): RsPackageOutput[] {
180
- const projectRoot = resolve(options.projectRoot ?? process.cwd());
181
- const roots = options.roots ?? DEFAULT_RS_PACKAGE_ROOTS;
182
- return roots.flatMap((root) =>
183
- sourceFiles(resolve(projectRoot, root)).map((source) => {
184
- const defaults = defaultOutputPaths(projectRoot, root, source);
185
- const input = readFileSync(source, "utf8");
186
- return {
187
- source,
188
- node: resolveOutput(projectRoot, input.match(NODE_OVERRIDE), defaults.node),
189
- python: resolveOutput(projectRoot, input.match(PYTHON_OVERRIDE), defaults.python),
190
- };
191
- }),
192
- );
193
- }
194
-
195
- export function generateRsPackages(options: GenerateRsPackagesOptions = {}): RsPackageOutput[] {
196
- const projectRoot = resolve(options.projectRoot ?? process.cwd());
197
- const roots = options.roots ?? DEFAULT_RS_PACKAGE_ROOTS;
198
- return roots.flatMap((root) =>
199
- sourceFiles(resolve(projectRoot, root)).map((source) =>
200
- renderSource(projectRoot, root, source),
201
- ),
202
- );
203
- }
204
-
205
- /** Projen lifecycle component that regenerates Rosetta outputs after synth. */
206
- export class RsPackages extends Component {
207
- private readonly outputs: RsPackageOutput[];
208
-
209
- constructor(
210
- project: javascript.NodeProject,
211
- private readonly roots: readonly string[] = DEFAULT_RS_PACKAGE_ROOTS,
212
- ) {
213
- super(project);
214
- // Generate during construction so a brand-new Node package exists before the
215
- // root's package scan later in the same synth. postSynthesize repeats this to
216
- // restore outputs after projen has written the rest of the tree.
217
- this.outputs = generateRsPackages({ projectRoot: project.outdir, roots });
218
- for (const output of this.outputs) {
219
- const node = `/${toPosix(relative(project.outdir, output.node))}`;
220
- const python = `/${toPosix(relative(project.outdir, output.python))}`;
221
- project.annotateGenerated(node);
222
- project.annotateGenerated(python);
223
- }
224
- }
225
-
226
- public override preSynthesize(): void {
227
- const eslint = javascript.Eslint.of(this.project);
228
- if (!eslint) return;
229
- for (const output of this.outputs) {
230
- eslint.addIgnorePattern(toPosix(relative(this.project.outdir, output.node)));
231
- }
232
- }
233
-
234
- public override postSynthesize(): void {
235
- generateRsPackages({ projectRoot: this.project.outdir, roots: this.roots });
236
- }
237
- }
@@ -1,26 +0,0 @@
1
- import { existsSync } from "node:fs";
2
- import { resolve } from "node:path";
3
- import { log } from "@dbx-tools/shared-core";
4
- import { configuredRsPackageRoots, generateRsPackages } from "../src/rs-packages.ts";
5
- import { repoRoot } from "../src/packages.ts";
6
- import { watchLoop } from "../src/watch.ts";
7
-
8
- const logger = log.logger("projen:rs-packages");
9
- const configuredRoots = configuredRsPackageRoots(repoRoot);
10
- const roots = configuredRoots === false ? [] : configuredRoots;
11
- const watchRoots = roots.map((root) => resolve(repoRoot, root)).filter(existsSync);
12
-
13
- function generate(): void {
14
- const outputs = generateRsPackages({ projectRoot: repoRoot, roots });
15
- logger.success(`generated ${outputs.length} Rosetta module${outputs.length === 1 ? "" : "s"}`);
16
- }
17
-
18
- generate();
19
-
20
- if (process.argv.includes("--watch")) {
21
- if (watchRoots.length) watchLoop("rs-packages", watchRoots, generate);
22
- else {
23
- logger.info("no Rosetta roots configured; watcher is idle");
24
- setInterval(() => {}, 2_147_483_647);
25
- }
26
- }