@dbx-tools/projen 0.9.51 → 0.9.58
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 +56 -16
- package/index.ts +9 -6
- package/package.json +5 -6
- package/src/dbx-tools-config.ts +8 -0
- package/src/generated.ts +6 -0
- package/src/packages.ts +6 -0
- package/src/project-py.ts +89 -18
- package/src/project.ts +2 -1
- package/src/python-node-bindings-component.ts +71 -0
- package/src/python-node-bindings.ts +38 -14
- package/src/python-node-runtime.ts +56 -0
- package/src/vscode.ts +17 -1
- package/src/workspace-lock.ts +31 -13
- package/tasks/publish-python.ts +14 -6
- package/tasks/python-localize-imports.py +188 -0
- package/tasks/python-node-bindings-watch.ts +96 -0
- package/tasks/python-node-bindings.ts +42 -130
- package/tasks/python-node-runtime-watch.ts +40 -0
- package/tasks/python-sync.ts +85 -3
- package/tasks/release.ts +2 -1
- package/tasks/sync.ts +15 -3
- package/shims/python-node/bootstrap.ts +0 -129
- package/shims/python-node/child-process.ts +0 -96
- package/shims/python-node/crypto.ts +0 -60
- package/shims/python-node/fs-promises.ts +0 -179
- package/shims/python-node/fs.ts +0 -173
- package/shims/python-node/headers.ts +0 -75
- package/shims/python-node/host.ts +0 -341
- package/shims/python-node/os.ts +0 -20
- package/shims/python-node/process.ts +0 -13
- package/shims/python-node/readline.ts +0 -37
- package/shims/python-node/tsconfig.json +0 -24
- package/src/python-node-bundle.ts +0 -67
package/README.md
CHANGED
|
@@ -135,6 +135,13 @@ in consuming repositories after `@dbx-tools/projen` is installed.
|
|
|
135
135
|
new project.DBXToolsPythonWorkspace(root, {
|
|
136
136
|
root: "packages/py",
|
|
137
137
|
packages: [
|
|
138
|
+
{
|
|
139
|
+
directory: "node-runtime",
|
|
140
|
+
name: "dbx-tools-node-runtime",
|
|
141
|
+
module: "dbx_tools.node_runtime",
|
|
142
|
+
description: "Shared PythonMonkey runtime and lazy Node.js bootstrap",
|
|
143
|
+
nodeRuntime: true,
|
|
144
|
+
},
|
|
138
145
|
{
|
|
139
146
|
directory: "postgres",
|
|
140
147
|
description: "Python bindings for example Postgres helpers",
|
|
@@ -180,18 +187,40 @@ types with typed async methods, and primitives, arrays, maps, promises, and
|
|
|
180
187
|
optional values retain their corresponding Python annotations.
|
|
181
188
|
|
|
182
189
|
The generator resolves portable Node built-ins through standard browser
|
|
183
|
-
polyfills. Python-host-backed
|
|
184
|
-
|
|
185
|
-
configure a shim
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
`
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
190
|
+
polyfills. Generated bundles proxy Python-host-backed built-ins to
|
|
191
|
+
`dbx-tools-node-runtime`, which owns the process, file, crypto, OS, abort,
|
|
192
|
+
headers, and runtime bootstrap shims. Packages do not configure or copy a shim
|
|
193
|
+
directory, and their generated Python loader imports the shared runtime package.
|
|
194
|
+
|
|
195
|
+
`nodeRuntime: true` identifies the one workspace package that owns
|
|
196
|
+
`build-runtime.ts`, `shims/`, the generated `runtime.js`, PythonMonkey bootstrap,
|
|
197
|
+
and bundle loading. Its distribution and module must be
|
|
198
|
+
`dbx-tools-node-runtime` and `dbx_tools.node_runtime`. Packages with
|
|
199
|
+
`nodeBindings` automatically depend on that workspace member. When the workspace
|
|
200
|
+
does not publish the runtime itself, they receive the matching external
|
|
201
|
+
`dbx-tools-node-runtime` dependency instead.
|
|
202
|
+
|
|
203
|
+
The workspace creates one repository-wide binding lifecycle:
|
|
204
|
+
|
|
205
|
+
- `python-node-bindings` generates every configured bridge.
|
|
206
|
+
- `python-node-bindings:check` verifies generated files and fails on stale
|
|
207
|
+
`_generated/node` directories.
|
|
208
|
+
- `python-node-bindings:watch` watches all configured bridges and regenerates
|
|
209
|
+
only Python projects affected by a Node package, transitive workspace
|
|
210
|
+
dependency, binding manifest, or function override change.
|
|
211
|
+
|
|
212
|
+
When a runtime owner is configured, the workspace also creates one shared
|
|
213
|
+
runtime lifecycle:
|
|
214
|
+
|
|
215
|
+
- `python-node-runtime` runs the package-owned `build-runtime.ts`.
|
|
216
|
+
- `python-node-runtime:check` verifies the committed `runtime.js`.
|
|
217
|
+
- `python-node-runtime:watch` watches `build-runtime.ts` and `shims/` and runs
|
|
218
|
+
the same package-owned builder.
|
|
219
|
+
|
|
220
|
+
`bun run sync --watch` supervises both generic watchers alongside the existing
|
|
221
|
+
`projenrc` and `barrels` watchers. No per-Python-package watcher tasks are
|
|
222
|
+
created. Full synthesis regenerates every binding and reconciles stale
|
|
223
|
+
`_generated/node` trees with the current `pyproject.toml` files.
|
|
195
224
|
Built-in shims can make ordinary Node imports work under PythonMonkey.
|
|
196
225
|
`functionOverrides` replaces named exports only in the generated runtime, so the
|
|
197
226
|
Node package does not gain Python callbacks or alternate source files. A
|
|
@@ -215,7 +244,7 @@ subset of a Git repository instead of exposing a direct URL dependency:
|
|
|
215
244
|
include: ["driver.py", "operations/**/*.py"],
|
|
216
245
|
exclude: ["**/test_*.py"],
|
|
217
246
|
replace: {
|
|
218
|
-
"
|
|
247
|
+
"DEFAULT_TIMEOUT = 30": "DEFAULT_TIMEOUT = 120",
|
|
219
248
|
},
|
|
220
249
|
},
|
|
221
250
|
],
|
|
@@ -225,8 +254,18 @@ subset of a Git repository instead of exposing a direct URL dependency:
|
|
|
225
254
|
Each source is written beneath
|
|
226
255
|
`<python-module>/_generated/sync/<name-or-owner-repository-hash>`. The generated
|
|
227
256
|
package task parses pip-style Git sources, filters files with the configured
|
|
228
|
-
globs, verifies every
|
|
229
|
-
commit in `.sync.json`, and recursively marks the result read-only.
|
|
257
|
+
globs, applies and verifies every literal `replace` patch, records the options
|
|
258
|
+
and resolved commit in `.sync.json`, and recursively marks the result read-only.
|
|
259
|
+
|
|
260
|
+
Do not hard-code the generated package path in `replace`. `localizeImports`
|
|
261
|
+
(on by default for identifier names) parses each file with the Python AST and
|
|
262
|
+
rewrites absolute imports of synchronized modules, whether upstream imports them
|
|
263
|
+
bare (`from operations.base import X`) or under the subdirectory's dotted path
|
|
264
|
+
(`from src.driver.operations.base import X`). It also rewrites constant
|
|
265
|
+
`__import__` and `importlib.import_module` arguments. Pass a list of upstream
|
|
266
|
+
module names to rewrite only those (each must match), or `false` to keep upstream
|
|
267
|
+
imports. The import prefix comes from the same constants as the output path, so
|
|
268
|
+
renaming the module or generated directory flows through without config edits.
|
|
230
269
|
|
|
231
270
|
Synchronization uses a check-lock-check sequence and an atomic directory
|
|
232
271
|
replacement. A current pinned commit performs no network request. Branches and
|
|
@@ -390,7 +429,8 @@ file contract as the CLI.
|
|
|
390
429
|
- `engineRoot` - engine package root resolution for bootstrapped repos.
|
|
391
430
|
|
|
392
431
|
The engine registers its commands as projen tasks on the workspace root, so run
|
|
393
|
-
them with `bun run <task>` - `sync` (add `--watch`), `barrels`, and
|
|
432
|
+
them with `bun run <task>` - `sync` (add `--watch`), `barrels`, `clean`, and the
|
|
433
|
+
configured `python-node-bindings:*` and `python-node-runtime:*` lifecycles.
|
|
394
434
|
[`@dbx-tools/cli`](../packages/js/cli/dbx-tools) is only needed to
|
|
395
435
|
bootstrap a folder that has no `.projenrc.ts` or toolchain yet.
|
|
396
436
|
|
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.58";
|
|
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";
|
|
@@ -23,7 +23,8 @@ export * as projectPy from "./src/project-py.ts";
|
|
|
23
23
|
export * as projenVersion from "./src/projen-version.ts";
|
|
24
24
|
export * as publish from "./src/publish.ts";
|
|
25
25
|
export * as pythonNodeBindings from "./src/python-node-bindings.ts";
|
|
26
|
-
export * as
|
|
26
|
+
export * as pythonNodeBindingsComponent from "./src/python-node-bindings-component.ts";
|
|
27
|
+
export * as pythonNodeRuntime from "./src/python-node-runtime.ts";
|
|
27
28
|
export * as release from "./src/release.ts";
|
|
28
29
|
export * as releaseContext from "./src/release-context.ts";
|
|
29
30
|
export * as releaseOptions from "./src/release-options.ts";
|
|
@@ -39,6 +40,7 @@ export { BUN_VERSION } from "./src/bun-workflow.ts";
|
|
|
39
40
|
export { DBXToolsConfigDataSchema, DBXToolsConfig } from "./src/dbx-tools-config.ts";
|
|
40
41
|
export type { DBXToolsConfigOptions, DBXToolsConfigData } from "./src/dbx-tools-config.ts";
|
|
41
42
|
export { resolvePkgRoot } from "./src/engine-root.ts";
|
|
43
|
+
export { PYTHON_GENERATED_PACKAGE, PYTHON_SYNC_PACKAGE } from "./src/generated.ts";
|
|
42
44
|
export type { HeaderOpts } from "./src/generated.ts";
|
|
43
45
|
export type { ConstructsMixin } from "./src/mixin.ts";
|
|
44
46
|
export type { ModuleStatement, ModuleExport, PublicFunctionExport } from "./src/module-exports.ts";
|
|
@@ -53,10 +55,11 @@ export { DBXToolsPythonProject, DBXToolsPythonWorkspace } from "./src/project-py
|
|
|
53
55
|
export type { PythonRepositoryOptions, PythonPackageOptions, PythonSourceSyncOptions, DBXToolsPythonProjectOptions, PythonReleaseOptions, DBXToolsPythonWorkspaceOptions } from "./src/project-py.ts";
|
|
54
56
|
export { PROJEN_VERSION } from "./src/projen-version.ts";
|
|
55
57
|
export { COMPILED_DIR, COMPILED_COMPILER_OPTIONS } from "./src/publish.ts";
|
|
56
|
-
export {
|
|
57
|
-
export
|
|
58
|
-
export {
|
|
59
|
-
export
|
|
58
|
+
export type { ResolvedPythonNodeFunctionOverride, ResolvedPythonNodeBindings, GeneratePythonNodeBindingsOptions } from "./src/python-node-bindings.ts";
|
|
59
|
+
export { PYTHON_NODE_BINDINGS_TASK, PYTHON_NODE_BINDINGS_CHECK_TASK, PYTHON_NODE_BINDINGS_WATCH_TASK, PythonNodeBindings } from "./src/python-node-bindings-component.ts";
|
|
60
|
+
export type { PythonNodeFunctionOverride, PythonNodeBindingsOptions, PythonNodeBindingsComponentOptions } from "./src/python-node-bindings-component.ts";
|
|
61
|
+
export { PYTHON_NODE_RUNTIME_DISTRIBUTION, PYTHON_NODE_RUNTIME_MODULE, PYTHON_NODE_RUNTIME_TASK, PYTHON_NODE_RUNTIME_CHECK_TASK, PYTHON_NODE_RUNTIME_WATCH_TASK, PythonNodeRuntime } from "./src/python-node-runtime.ts";
|
|
62
|
+
export type { PythonNodeRuntimeOptions } from "./src/python-node-runtime.ts";
|
|
60
63
|
export { DBXToolsRelease } from "./src/release.ts";
|
|
61
64
|
export type { ReleaseDocsOptions, DBXToolsReleaseOptions } from "./src/release.ts";
|
|
62
65
|
export { RELEASE_VERSION } from "./src/release-context.ts";
|
package/package.json
CHANGED
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@clack/prompts": "^1.7.0",
|
|
33
|
-
"@dbx-tools/core": "^0.9.
|
|
34
|
-
"@dbx-tools/path": "^0.9.
|
|
35
|
-
"@dbx-tools/shared-core": "^0.9.
|
|
33
|
+
"@dbx-tools/core": "^0.9.58",
|
|
34
|
+
"@dbx-tools/path": "^0.9.58",
|
|
35
|
+
"@dbx-tools/shared-core": "^0.9.58",
|
|
36
36
|
"commander": "^15.0.0",
|
|
37
37
|
"concurrently": "^10.0.3",
|
|
38
38
|
"constructs": "^10.6.0",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
},
|
|
55
55
|
"main": "index.ts",
|
|
56
56
|
"license": "Apache-2.0",
|
|
57
|
-
"version": "0.9.
|
|
57
|
+
"version": "0.9.58",
|
|
58
58
|
"types": "index.ts",
|
|
59
59
|
"type": "module",
|
|
60
60
|
"exports": {
|
|
@@ -65,8 +65,7 @@
|
|
|
65
65
|
"files": [
|
|
66
66
|
"index.ts",
|
|
67
67
|
"src",
|
|
68
|
-
"tasks"
|
|
69
|
-
"shims/python-node"
|
|
68
|
+
"tasks"
|
|
70
69
|
],
|
|
71
70
|
"//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"bunx projen\"."
|
|
72
71
|
}
|
package/src/dbx-tools-config.ts
CHANGED
|
@@ -38,6 +38,11 @@ export const DBXToolsConfigDataSchema = z
|
|
|
38
38
|
.readonly()
|
|
39
39
|
.optional()
|
|
40
40
|
.describe("Additional paths that trigger watcher resynthesis."),
|
|
41
|
+
syncWatchTasks: z
|
|
42
|
+
.array(z.string().min(1))
|
|
43
|
+
.readonly()
|
|
44
|
+
.optional()
|
|
45
|
+
.describe("Projen tasks supervised by `sync --watch`."),
|
|
41
46
|
})
|
|
42
47
|
.strict()
|
|
43
48
|
.readonly()
|
|
@@ -58,6 +63,8 @@ export class DBXToolsConfig {
|
|
|
58
63
|
publishable: boolean;
|
|
59
64
|
/** Additional repository paths that trigger watcher resynthesis. */
|
|
60
65
|
syncResynthPaths?: string[];
|
|
66
|
+
/** Projen tasks supervised by `sync --watch`. */
|
|
67
|
+
readonly syncWatchTasks: string[] = [];
|
|
61
68
|
|
|
62
69
|
constructor(
|
|
63
70
|
readonly project: javascript.NodeProject,
|
|
@@ -82,6 +89,7 @@ export class DBXToolsConfig {
|
|
|
82
89
|
...(!this.publishable ? { publishable: false } : {}),
|
|
83
90
|
...(this.packageRoots?.length ? { packageRoots: this.packageRoots } : {}),
|
|
84
91
|
...(this.syncResynthPaths?.length ? { syncResynthPaths: this.syncResynthPaths } : {}),
|
|
92
|
+
...(this.syncWatchTasks.length ? { syncWatchTasks: [...new Set(this.syncWatchTasks)] } : {}),
|
|
85
93
|
});
|
|
86
94
|
}
|
|
87
95
|
}
|
package/src/generated.ts
CHANGED
|
@@ -13,6 +13,12 @@ import { chmodSync, existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
13
13
|
/** First line of every banner {@link header} builds. */
|
|
14
14
|
const MARKER = "// GENERATED by";
|
|
15
15
|
|
|
16
|
+
/** Python subpackage, under a package's module directory, that holds all toolchain output. */
|
|
17
|
+
export const PYTHON_GENERATED_PACKAGE = "_generated";
|
|
18
|
+
|
|
19
|
+
/** {@link PYTHON_GENERATED_PACKAGE} child holding upstream sources written by `python-sync`. */
|
|
20
|
+
export const PYTHON_SYNC_PACKAGE = "sync";
|
|
21
|
+
|
|
16
22
|
const READONLY = 0o444;
|
|
17
23
|
const WRITABLE = 0o644;
|
|
18
24
|
|
package/src/packages.ts
CHANGED
|
@@ -280,6 +280,12 @@ export function syncResynthPaths(projectRoot: string = resolveRepoRoot()): strin
|
|
|
280
280
|
return Array.isArray(paths) ? stringUtils.parseList(paths.map((p) => String(p))) : [];
|
|
281
281
|
}
|
|
282
282
|
|
|
283
|
+
/** Projen task names supervised as focused processes by `sync --watch`. */
|
|
284
|
+
export function syncWatchTasks(projectRoot: string = resolveRepoRoot()): string[] {
|
|
285
|
+
const tasks = readDbxToolsConfig(projectRoot)?.syncWatchTasks;
|
|
286
|
+
return Array.isArray(tasks) ? stringUtils.parseList(tasks.map((task) => String(task))) : [];
|
|
287
|
+
}
|
|
288
|
+
|
|
283
289
|
/**
|
|
284
290
|
* The recorded workspace members from `pnpm-workspace.yaml` (the source of truth),
|
|
285
291
|
* each augmented with the `tags` read back from its `package.json`. This is what
|
package/src/project-py.ts
CHANGED
|
@@ -3,10 +3,19 @@ import * as projectUtils from "@dbx-tools/core/project-utils";
|
|
|
3
3
|
import { stringUtils, type OneOrMany } from "@dbx-tools/shared-core";
|
|
4
4
|
import { Component, License, TextFile, type Project, github, javascript, python } from "projen";
|
|
5
5
|
import { JobPermission, type Job, type JobStep } from "projen/lib/github/workflows-model";
|
|
6
|
+
import { PYTHON_GENERATED_PACKAGE } from "./generated.ts";
|
|
6
7
|
import { LICENSE, projectRepositoryUrl, taskCommand } from "./project-js.ts";
|
|
7
8
|
import { isDBXToolsJavaScriptProject } from "./project-predicate.ts";
|
|
8
9
|
import type { DBXToolsProject, DBXToolsProjectOptions } from "./project.ts";
|
|
9
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
PythonNodeBindings,
|
|
12
|
+
type PythonNodeBindingsOptions,
|
|
13
|
+
} from "./python-node-bindings-component.ts";
|
|
14
|
+
import {
|
|
15
|
+
PYTHON_NODE_RUNTIME_DISTRIBUTION,
|
|
16
|
+
PYTHON_NODE_RUNTIME_MODULE,
|
|
17
|
+
PythonNodeRuntime,
|
|
18
|
+
} from "./python-node-runtime.ts";
|
|
10
19
|
import {
|
|
11
20
|
refreshReleaseDocsDependencies,
|
|
12
21
|
releaseCondition,
|
|
@@ -33,6 +42,8 @@ export interface PythonPackageOptions extends DBXToolsProjectOptions {
|
|
|
33
42
|
readonly moduleRoot?: string;
|
|
34
43
|
readonly description: string;
|
|
35
44
|
readonly dependencies?: readonly string[];
|
|
45
|
+
/** Named PEP 621 optional dependency groups emitted under `project.optional-dependencies`. */
|
|
46
|
+
readonly optionalDependencies?: Readonly<Record<string, readonly string[]>>;
|
|
36
47
|
/** Development dependencies emitted only in this package's dependency group. */
|
|
37
48
|
readonly devDependencies?: readonly string[];
|
|
38
49
|
/** Workspace package directories rendered as standalone Git dependencies. */
|
|
@@ -40,6 +51,8 @@ export interface PythonPackageOptions extends DBXToolsProjectOptions {
|
|
|
40
51
|
readonly scripts?: Readonly<Record<string, string>>;
|
|
41
52
|
/** GitHub environment used to publish this distribution to PyPI. */
|
|
42
53
|
readonly releaseEnvironment?: string;
|
|
54
|
+
/** Mark this package as the shared PythonMonkey runtime and Node shim owner. */
|
|
55
|
+
readonly nodeRuntime?: boolean;
|
|
43
56
|
/** One or more build-time Node packages embedded through PythonMonkey. */
|
|
44
57
|
readonly nodeBindings?: OneOrMany<PythonNodeBindingsOptions>;
|
|
45
58
|
/** Pinned Git source subsets synchronized into this package's generated tree. */
|
|
@@ -52,7 +65,17 @@ export interface PythonSourceSyncOptions {
|
|
|
52
65
|
readonly source: string;
|
|
53
66
|
readonly include?: readonly string[];
|
|
54
67
|
readonly exclude?: readonly string[];
|
|
68
|
+
/** Literal text patches applied to each synchronized `.py` file before import localization. */
|
|
55
69
|
readonly replace?: Readonly<Record<string, string>>;
|
|
70
|
+
/**
|
|
71
|
+
* Rewrite absolute imports of synchronized modules to the generated package they now
|
|
72
|
+
* live in, using the Python AST. `true` detects every top-level synchronized module,
|
|
73
|
+
* importable either bare (`config.schema`) or under the source subdirectory's dotted
|
|
74
|
+
* path (`graphiti_core.driver.postgraph`). A list names the upstream modules to rewrite
|
|
75
|
+
* instead, and each entry must match at least one import. Defaults to `true` when the
|
|
76
|
+
* sync `name` is a Python identifier; set `false` to keep upstream imports unchanged.
|
|
77
|
+
*/
|
|
78
|
+
readonly localizeImports?: boolean | readonly string[];
|
|
56
79
|
}
|
|
57
80
|
|
|
58
81
|
interface ResolvedPythonPackageOptions extends PythonPackageOptions {
|
|
@@ -231,6 +254,7 @@ export class DBXToolsPythonProject extends python.PythonProject implements DBXTo
|
|
|
231
254
|
licenseFiles: ["LICENSE"],
|
|
232
255
|
requiresPython: options.requiresPython,
|
|
233
256
|
dependencies: [...(pkg.dependencies ?? [])],
|
|
257
|
+
optionalDependencies: pkg.optionalDependencies,
|
|
234
258
|
scripts: pkg.scripts,
|
|
235
259
|
urls: {
|
|
236
260
|
Source: `${options.repository.url.replace(/\.git$/, "")}/tree/${options.repository.ref}/${pythonPackagePath(options.repository, pkg.directory)}`,
|
|
@@ -271,7 +295,13 @@ export class DBXToolsPythonProject extends python.PythonProject implements DBXTo
|
|
|
271
295
|
);
|
|
272
296
|
}
|
|
273
297
|
if (pkg.sync?.length) {
|
|
274
|
-
this.uv.file.addOverride(
|
|
298
|
+
this.uv.file.addOverride(
|
|
299
|
+
"tool.dbx_tools.sync",
|
|
300
|
+
pkg.sync.map(({ localizeImports, ...sync }) => ({
|
|
301
|
+
...sync,
|
|
302
|
+
...(localizeImports === undefined ? {} : { localize_imports: localizeImports }),
|
|
303
|
+
})),
|
|
304
|
+
);
|
|
275
305
|
}
|
|
276
306
|
this.uv.file.readonly = true;
|
|
277
307
|
|
|
@@ -324,28 +354,64 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
324
354
|
name: pkg.name ?? `${scope}-${stringUtils.toSlug(pkg.directory)}`,
|
|
325
355
|
module: pkg.module ?? pythonModuleName(scope, pkg.directory),
|
|
326
356
|
}));
|
|
357
|
+
const configuredNodeRuntimes = packageIdentities.filter(({ nodeRuntime }) => nodeRuntime);
|
|
358
|
+
if (configuredNodeRuntimes.length > 1) {
|
|
359
|
+
throw new Error("A Python workspace can configure only one shared Node runtime package");
|
|
360
|
+
}
|
|
361
|
+
const configuredNodeRuntime = configuredNodeRuntimes[0];
|
|
362
|
+
if (
|
|
363
|
+
configuredNodeRuntime &&
|
|
364
|
+
(configuredNodeRuntime.name !== PYTHON_NODE_RUNTIME_DISTRIBUTION ||
|
|
365
|
+
configuredNodeRuntime.module !== PYTHON_NODE_RUNTIME_MODULE)
|
|
366
|
+
) {
|
|
367
|
+
throw new Error(
|
|
368
|
+
`The shared Node runtime package must use distribution ${PYTHON_NODE_RUNTIME_DISTRIBUTION} ` +
|
|
369
|
+
`and module ${PYTHON_NODE_RUNTIME_MODULE}`,
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
this.version = readWorkspaceVersion(project.outdir);
|
|
327
373
|
const packagesByDirectory = new Map(packageIdentities.map((pkg) => [pkg.directory, pkg]));
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
|
|
374
|
+
const nodeRuntimePackage = configuredNodeRuntime;
|
|
375
|
+
const packages = packageIdentities.map((pkg) => {
|
|
376
|
+
const usesNodeRuntime = pythonNodeBindings(pkg).length > 0;
|
|
377
|
+
const internalDependencies = [
|
|
378
|
+
...new Set([
|
|
379
|
+
...(pkg.internalDependencies ?? []),
|
|
380
|
+
...(usesNodeRuntime &&
|
|
381
|
+
nodeRuntimePackage &&
|
|
382
|
+
nodeRuntimePackage.directory !== pkg.directory
|
|
383
|
+
? [nodeRuntimePackage.directory]
|
|
384
|
+
: []),
|
|
385
|
+
]),
|
|
386
|
+
];
|
|
387
|
+
const dependencies = [
|
|
331
388
|
...(pkg.dependencies ?? []),
|
|
332
|
-
...
|
|
389
|
+
...internalDependencies.map((directory) => {
|
|
333
390
|
const dependency = packagesByDirectory.get(directory);
|
|
334
391
|
if (!dependency) {
|
|
335
392
|
throw new Error(
|
|
336
393
|
`Python package ${pkg.directory} references unknown internal package ${directory}`,
|
|
337
394
|
);
|
|
338
395
|
}
|
|
339
|
-
return dependency.name
|
|
396
|
+
return usesNodeRuntime && dependency.name === PYTHON_NODE_RUNTIME_DISTRIBUTION
|
|
397
|
+
? `${dependency.name}==${this.version}`
|
|
398
|
+
: dependency.name;
|
|
340
399
|
}),
|
|
341
|
-
|
|
342
|
-
|
|
400
|
+
...(usesNodeRuntime && !nodeRuntimePackage
|
|
401
|
+
? [`${PYTHON_NODE_RUNTIME_DISTRIBUTION}==${this.version}`]
|
|
402
|
+
: []),
|
|
403
|
+
];
|
|
404
|
+
return {
|
|
405
|
+
...pkg,
|
|
406
|
+
dependencies: [...new Set(dependencies)],
|
|
407
|
+
internalDependencies,
|
|
408
|
+
};
|
|
409
|
+
});
|
|
343
410
|
const resolvedOptions = { ...options, packages };
|
|
344
411
|
this.requiresPython = options.requiresPython ?? ">=3.10";
|
|
345
412
|
this.workflowPythonVersion =
|
|
346
413
|
options.workflowPythonVersion ??
|
|
347
414
|
pythonVersionFromRuffTarget(options.ruffTarget ?? DEFAULT_RUFF_TARGET);
|
|
348
|
-
this.version = readWorkspaceVersion(project.outdir);
|
|
349
415
|
this.file = this.emitWorkspace(project, resolvedOptions, scope);
|
|
350
416
|
this.packages = packages.map(
|
|
351
417
|
(pkg) =>
|
|
@@ -357,13 +423,19 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
357
423
|
version: this.version,
|
|
358
424
|
}),
|
|
359
425
|
);
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
name: pkg.packageOptions.directory,
|
|
365
|
-
projectDirectory: pythonPackagePath(this.repository, pkg.packageOptions.directory),
|
|
426
|
+
const syncWatchTasks: string[] = [];
|
|
427
|
+
if (configuredNodeRuntime) {
|
|
428
|
+
const runtime = new PythonNodeRuntime(project, {
|
|
429
|
+
projectDirectory: pythonPackagePath(this.repository, configuredNodeRuntime.directory),
|
|
366
430
|
});
|
|
431
|
+
syncWatchTasks.push(runtime.watchTask.name);
|
|
432
|
+
}
|
|
433
|
+
if (this.packages.some((pkg) => pythonNodeBindings(pkg.packageOptions).length > 0)) {
|
|
434
|
+
const bindings = new PythonNodeBindings(project);
|
|
435
|
+
syncWatchTasks.push(bindings.watchTask.name);
|
|
436
|
+
}
|
|
437
|
+
if (syncWatchTasks.length && isDBXToolsJavaScriptProject()(project)) {
|
|
438
|
+
project.dbxToolsConfig.syncWatchTasks.push(...syncWatchTasks);
|
|
367
439
|
}
|
|
368
440
|
for (const pkg of this.packages) {
|
|
369
441
|
if (!pkg.packageOptions.sync?.length) continue;
|
|
@@ -681,7 +753,6 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
681
753
|
`- Repository name: ${repository.name}`,
|
|
682
754
|
"- Workflow name: release.yml",
|
|
683
755
|
`- Environment name: ${publication.environment}`,
|
|
684
|
-
`- GitHub environment tag: ${releaseTag}`,
|
|
685
756
|
"",
|
|
686
757
|
];
|
|
687
758
|
}),
|
|
@@ -759,5 +830,5 @@ function pythonNodeGeneratedSources(pkg: ResolvedPythonPackageOptions): string[]
|
|
|
759
830
|
if (bindings.length === 0) return [];
|
|
760
831
|
const moduleRoot = pkg.moduleRoot ?? "src";
|
|
761
832
|
const moduleDirectory = `${moduleRoot}/${pkg.module.replaceAll(".", "/")}`;
|
|
762
|
-
return [`${moduleDirectory}
|
|
833
|
+
return [`${moduleDirectory}/${PYTHON_GENERATED_PACKAGE}/**`];
|
|
763
834
|
}
|
package/src/project.ts
CHANGED
|
@@ -12,7 +12,8 @@ import * as projectPredicate from "./project-predicate.ts";
|
|
|
12
12
|
|
|
13
13
|
export * from "./project-js.ts";
|
|
14
14
|
export * from "./project-py.ts";
|
|
15
|
-
export * from "./python-node-
|
|
15
|
+
export * from "./python-node-bindings-component.ts";
|
|
16
|
+
export * from "./python-node-runtime.ts";
|
|
16
17
|
|
|
17
18
|
/** Runtime family implemented by a dbx-tools project. */
|
|
18
19
|
export type DBXToolsProjectLanguage = "javascript" | "python";
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/** Projen task wiring for repository-wide PythonMonkey Node binding generation. */
|
|
2
|
+
import { Component, type Project, type Task } from "projen";
|
|
3
|
+
import { taskCommand } from "./project-js.ts";
|
|
4
|
+
|
|
5
|
+
/** Repository-wide task that regenerates every configured Python Node bridge. */
|
|
6
|
+
export const PYTHON_NODE_BINDINGS_TASK = "python-node-bindings";
|
|
7
|
+
|
|
8
|
+
/** Repository-wide task that verifies every generated Python Node bridge. */
|
|
9
|
+
export const PYTHON_NODE_BINDINGS_CHECK_TASK = `${PYTHON_NODE_BINDINGS_TASK}:check`;
|
|
10
|
+
|
|
11
|
+
/** Repository-wide task that watches every configured Python Node bridge. */
|
|
12
|
+
export const PYTHON_NODE_BINDINGS_WATCH_TASK = `${PYTHON_NODE_BINDINGS_TASK}:watch`;
|
|
13
|
+
|
|
14
|
+
/** One function replaced only in a generated PythonMonkey bundle. */
|
|
15
|
+
export interface PythonNodeFunctionOverride {
|
|
16
|
+
/** Module specifier imported by the ordinary Node source. */
|
|
17
|
+
readonly module: string;
|
|
18
|
+
/** Named export to replace. */
|
|
19
|
+
readonly export: string;
|
|
20
|
+
/** Repository-relative TypeScript file containing the replacement. */
|
|
21
|
+
readonly handler: string;
|
|
22
|
+
/** Named export in {@link handler}. Defaults to {@link export}. */
|
|
23
|
+
readonly handlerExport?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Node package embedded into one Python distribution through PythonMonkey. */
|
|
27
|
+
export interface PythonNodeBindingsOptions {
|
|
28
|
+
/** Public Node package specifier, such as `@dbx-tools/auth`. */
|
|
29
|
+
readonly package: string;
|
|
30
|
+
/** Optional portable package subpath to bind. Defaults to {@link package}. */
|
|
31
|
+
readonly entrypoint?: string;
|
|
32
|
+
/** Automatically generated root namespace modules to bind independently. */
|
|
33
|
+
readonly modules?: readonly string[];
|
|
34
|
+
/** Functions replaced only while generating the Python runtime. */
|
|
35
|
+
readonly functionOverrides?: readonly PythonNodeFunctionOverride[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Options for repository-wide PythonMonkey Node binding tasks. */
|
|
39
|
+
export interface PythonNodeBindingsComponentOptions {
|
|
40
|
+
/** Add the repository-wide freshness check to the test task. Defaults to true. */
|
|
41
|
+
readonly test?: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Registers one build, freshness, and watch lifecycle for every configured
|
|
46
|
+
* `[tool.dbx_tools.node_bindings]` table in the repository.
|
|
47
|
+
*/
|
|
48
|
+
export class PythonNodeBindings extends Component {
|
|
49
|
+
readonly buildTask: Task;
|
|
50
|
+
readonly checkTask: Task;
|
|
51
|
+
readonly watchTask: Task;
|
|
52
|
+
|
|
53
|
+
constructor(project: Project, options: PythonNodeBindingsComponentOptions = {}) {
|
|
54
|
+
super(project);
|
|
55
|
+
const command = taskCommand("python-node-bindings.ts");
|
|
56
|
+
this.buildTask = project.addTask(PYTHON_NODE_BINDINGS_TASK, {
|
|
57
|
+
description: "Generate every configured Python Node binding",
|
|
58
|
+
execArgs: command,
|
|
59
|
+
});
|
|
60
|
+
this.checkTask = project.addTask(PYTHON_NODE_BINDINGS_CHECK_TASK, {
|
|
61
|
+
description: "Verify every configured Python Node binding is current",
|
|
62
|
+
execArgs: [...command, "--check"],
|
|
63
|
+
});
|
|
64
|
+
this.watchTask = project.addTask(PYTHON_NODE_BINDINGS_WATCH_TASK, {
|
|
65
|
+
description: "Watch and regenerate affected Python Node bindings",
|
|
66
|
+
execArgs: taskCommand("python-node-bindings-watch.ts"),
|
|
67
|
+
});
|
|
68
|
+
project.preCompileTask.spawn(this.buildTask);
|
|
69
|
+
if (options.test ?? true) project.testTask.spawn(this.checkTask);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -32,14 +32,17 @@ export interface ResolvedPythonNodeBindings {
|
|
|
32
32
|
readonly workspaceDirectories: readonly string[];
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
/**
|
|
36
|
-
export
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
export function generatePythonNodeBindings(
|
|
35
|
+
/** Options for repository-wide Python Node binding generation. */
|
|
36
|
+
export interface GeneratePythonNodeBindingsOptions {
|
|
37
|
+
/** Verify generated output without changing it. */
|
|
38
|
+
readonly check?: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Generate or verify every pyproject-configured Node binding in a repository. */
|
|
42
|
+
export function generatePythonNodeBindings(
|
|
43
|
+
projectRoot: string = resolveRepoRoot(),
|
|
44
|
+
options: GeneratePythonNodeBindingsOptions = {},
|
|
45
|
+
): void {
|
|
43
46
|
const task = resolve(dirname(fileURLToPath(import.meta.url)), "../tasks/python-node-bindings.ts");
|
|
44
47
|
const configuredProjects = pythonNodeBindingProjects(projectRoot);
|
|
45
48
|
const directories = new Set<string>();
|
|
@@ -48,16 +51,38 @@ export function generatePythonNodeBindings(projectRoot: string = resolveRepoRoot
|
|
|
48
51
|
directories.add(relative(projectRoot, join(config.moduleDirectory, "_generated", "node")));
|
|
49
52
|
}
|
|
50
53
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
const stale = existingBindingDirectories(projectRoot).filter(
|
|
55
|
+
(directory) => !directories.has(directory),
|
|
56
|
+
);
|
|
57
|
+
if (stale.length > 0 && options.check) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
`Generated Node binding directories are stale:\n${stale.map((directory) => ` ${directory}`).join("\n")}`,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
for (const directory of stale) {
|
|
63
|
+
rmSync(resolve(projectRoot, directory), { recursive: true, force: true });
|
|
55
64
|
}
|
|
56
65
|
for (const project of configuredProjects) {
|
|
57
|
-
runTaskCommand(projectRoot, "bun", [
|
|
66
|
+
runTaskCommand(projectRoot, "bun", [
|
|
67
|
+
task,
|
|
68
|
+
"--root",
|
|
69
|
+
projectRoot,
|
|
70
|
+
"--project",
|
|
71
|
+
project,
|
|
72
|
+
...(options.check ? ["--check"] : []),
|
|
73
|
+
]);
|
|
58
74
|
}
|
|
59
75
|
}
|
|
60
76
|
|
|
77
|
+
/** Resolve every configured binding in a repository. */
|
|
78
|
+
export function resolveAllPythonNodeBindings(
|
|
79
|
+
projectRoot: string = resolveRepoRoot(),
|
|
80
|
+
): ResolvedPythonNodeBindings[] {
|
|
81
|
+
return pythonNodeBindingProjects(projectRoot).flatMap((project) =>
|
|
82
|
+
resolvePythonNodeBindings(projectRoot, project),
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
61
86
|
/** Repository-relative Python projects that configure generated Node bindings. */
|
|
62
87
|
export function pythonNodeBindingProjects(projectRoot: string): string[] {
|
|
63
88
|
return [
|
|
@@ -165,7 +190,6 @@ export function pythonNodeBindingWatchInputs(config: ResolvedPythonNodeBindings)
|
|
|
165
190
|
return [
|
|
166
191
|
config.pyproject,
|
|
167
192
|
...config.workspaceDirectories,
|
|
168
|
-
PYTHON_NODE_SHIM_ROOT,
|
|
169
193
|
...config.functionOverrides.map(({ handlerFile }) => handlerFile),
|
|
170
194
|
];
|
|
171
195
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/** Projen task wiring for the shared PythonMonkey Node runtime package. */
|
|
2
|
+
import { Component, type Project, type Task } from "projen";
|
|
3
|
+
import { taskCommand } from "./project-js.ts";
|
|
4
|
+
|
|
5
|
+
/** Published Python distribution imported by every generated Node binding loader. */
|
|
6
|
+
export const PYTHON_NODE_RUNTIME_DISTRIBUTION = "dbx-tools-node-runtime";
|
|
7
|
+
|
|
8
|
+
/** Python module that owns the shared PythonMonkey runtime and Node shims. */
|
|
9
|
+
export const PYTHON_NODE_RUNTIME_MODULE = "dbx_tools.node_runtime";
|
|
10
|
+
|
|
11
|
+
/** Repository-wide task that builds the shared Python Node runtime. */
|
|
12
|
+
export const PYTHON_NODE_RUNTIME_TASK = "python-node-runtime";
|
|
13
|
+
|
|
14
|
+
/** Repository-wide task that verifies the shared Python Node runtime. */
|
|
15
|
+
export const PYTHON_NODE_RUNTIME_CHECK_TASK = `${PYTHON_NODE_RUNTIME_TASK}:check`;
|
|
16
|
+
|
|
17
|
+
/** Repository-wide task that watches the shared Python Node runtime. */
|
|
18
|
+
export const PYTHON_NODE_RUNTIME_WATCH_TASK = `${PYTHON_NODE_RUNTIME_TASK}:watch`;
|
|
19
|
+
|
|
20
|
+
/** Options for the package that owns the shared PythonMonkey runtime. */
|
|
21
|
+
export interface PythonNodeRuntimeOptions {
|
|
22
|
+
/** Repository-relative Python project containing `build-runtime.ts` and `shims/`. */
|
|
23
|
+
readonly projectDirectory: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Registers the shared runtime's build, freshness, and watch lifecycle.
|
|
28
|
+
*
|
|
29
|
+
* The runtime package owns the build script, shim sources, generated `runtime.js`,
|
|
30
|
+
* PythonMonkey bootstrap, and bundle loader. This component only connects those
|
|
31
|
+
* package-owned operations to Projen's native task graph.
|
|
32
|
+
*/
|
|
33
|
+
export class PythonNodeRuntime extends Component {
|
|
34
|
+
readonly buildTask: Task;
|
|
35
|
+
readonly checkTask: Task;
|
|
36
|
+
readonly watchTask: Task;
|
|
37
|
+
|
|
38
|
+
constructor(project: Project, options: PythonNodeRuntimeOptions) {
|
|
39
|
+
super(project);
|
|
40
|
+
const buildCommand = ["bun", `${options.projectDirectory}/build-runtime.ts`];
|
|
41
|
+
this.buildTask = project.addTask(PYTHON_NODE_RUNTIME_TASK, {
|
|
42
|
+
description: "Build the shared PythonMonkey Node runtime",
|
|
43
|
+
execArgs: buildCommand,
|
|
44
|
+
});
|
|
45
|
+
this.checkTask = project.addTask(PYTHON_NODE_RUNTIME_CHECK_TASK, {
|
|
46
|
+
description: "Verify the shared PythonMonkey Node runtime is current",
|
|
47
|
+
execArgs: [...buildCommand, "--check"],
|
|
48
|
+
});
|
|
49
|
+
this.watchTask = project.addTask(PYTHON_NODE_RUNTIME_WATCH_TASK, {
|
|
50
|
+
description: "Watch and rebuild the shared PythonMonkey Node runtime",
|
|
51
|
+
execArgs: taskCommand("python-node-runtime-watch.ts", "--project", options.projectDirectory),
|
|
52
|
+
});
|
|
53
|
+
project.preCompileTask.spawn(this.buildTask);
|
|
54
|
+
project.testTask.spawn(this.checkTask);
|
|
55
|
+
}
|
|
56
|
+
}
|