@dbx-tools/projen 0.9.31 → 0.9.36
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 +71 -65
- package/index.ts +5 -8
- package/package.json +8 -7
- package/src/_task-command.ts +0 -75
- package/src/barrels.ts +15 -23
- package/src/bun-app.ts +5 -6
- package/src/clean.ts +41 -15
- package/src/codegen.ts +2 -3
- package/src/dbx-tools-config.ts +23 -64
- package/src/generated.ts +7 -1
- package/src/module-exports.ts +35 -3
- package/src/openapi.ts +34 -38
- package/src/packages.ts +3 -30
- package/src/pnpm-workspace.ts +21 -26
- package/src/project-js.ts +152 -365
- package/src/project-predicate.ts +4 -5
- package/src/project-py.ts +55 -144
- package/src/publish.ts +1 -1
- package/src/python-node-bindings.ts +140 -67
- package/src/python-node-bundle.ts +6 -42
- package/src/release-context.ts +4 -4
- package/src/release.ts +88 -86
- package/src/tags.ts +20 -18
- package/src/tsconfig.ts +9 -5
- package/src/vscode.ts +47 -58
- package/src/watch.ts +2 -8
- package/src/workspace-version.ts +1 -6
- package/tasks/barrels.ts +43 -38
- package/tasks/bump.ts +24 -20
- package/tasks/clean.ts +54 -50
- package/tasks/compile-workspace.ts +2 -117
- package/tasks/lib/compile-workspace.ts +119 -0
- package/tasks/lib/local-publish.ts +122 -0
- package/tasks/lib/publish-npm.ts +458 -0
- package/tasks/lib/publish-python.ts +195 -0
- package/tasks/lib/publish.ts +264 -0
- package/tasks/lib/python-node-bindings.ts +1262 -0
- package/tasks/{python-registry.ts → lib/python-registry.ts} +21 -30
- package/tasks/{python-release.ts → lib/python-release.ts} +1 -0
- package/tasks/lib/release-version.ts +66 -0
- package/tasks/lib/release.ts +212 -0
- package/tasks/local-publish.ts +4 -105
- package/tasks/openapi.ts +25 -21
- package/tasks/projenrc.ts +14 -12
- package/tasks/publish-npm.ts +3 -364
- package/tasks/publish-python.ts +3 -184
- package/tasks/publish.ts +3 -279
- package/tasks/python-node-bindings.ts +3 -1014
- package/tasks/release-version.ts +3 -62
- package/tasks/release.ts +3 -96
- package/tasks/sync.ts +71 -70
- package/tasks/version-check.ts +3 -10
- package/src/release-catalog.ts +0 -684
- package/tasks/databricks-cli-assets.ts +0 -129
- package/tasks/python-node-bindings-watch.ts +0 -47
package/src/project-predicate.ts
CHANGED
|
@@ -9,9 +9,8 @@ import {
|
|
|
9
9
|
} from "@dbx-tools/shared-core";
|
|
10
10
|
import { IConstruct } from "constructs";
|
|
11
11
|
import { Project } from "projen";
|
|
12
|
-
import { project } from "..";
|
|
13
12
|
import { toPosix } from "./packages.ts";
|
|
14
|
-
import type
|
|
13
|
+
import { identifier, type DBXToolsJavaScriptProject } from "./project-js.ts";
|
|
15
14
|
import type { DBXToolsProject } from "./project.ts";
|
|
16
15
|
|
|
17
16
|
/**
|
|
@@ -77,7 +76,7 @@ export function hasIdentifierPackageName(
|
|
|
77
76
|
): Predicate<IConstruct, Project> {
|
|
78
77
|
const matchers = projectMatchers(...patterns);
|
|
79
78
|
return isProject().and((p) => {
|
|
80
|
-
const packageName =
|
|
79
|
+
const packageName = identifier(p).packageName;
|
|
81
80
|
return matchers.every((matcher) => matcher(packageName));
|
|
82
81
|
});
|
|
83
82
|
}
|
|
@@ -88,7 +87,7 @@ export function hasIdentifierName(
|
|
|
88
87
|
): Predicate<IConstruct, Project> {
|
|
89
88
|
const matchers = projectMatchers(...patterns);
|
|
90
89
|
return isProject().and((p) => {
|
|
91
|
-
const name =
|
|
90
|
+
const name = identifier(p).name;
|
|
92
91
|
return matchers.every((matcher) => matcher(name));
|
|
93
92
|
});
|
|
94
93
|
}
|
|
@@ -99,7 +98,7 @@ export function hasIdentifierScope(
|
|
|
99
98
|
): Predicate<IConstruct, Project> {
|
|
100
99
|
const matchers = projectMatchers(...patterns);
|
|
101
100
|
return isProject().and((p) => {
|
|
102
|
-
const scope =
|
|
101
|
+
const scope = identifier(p).scope;
|
|
103
102
|
return scope && matchers.every((matcher) => matcher(scope));
|
|
104
103
|
});
|
|
105
104
|
}
|
package/src/project-py.ts
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
/** Reusable uv workspace generation for Python packages hosted in a projen tree. */
|
|
2
2
|
import * as projectUtils from "@dbx-tools/core/project-utils";
|
|
3
|
-
import { stringUtils } from "@dbx-tools/shared-core";
|
|
4
|
-
import { Component, License, TextFile, type Project, javascript, python
|
|
3
|
+
import { stringUtils, type OneOrMany } from "@dbx-tools/shared-core";
|
|
4
|
+
import { Component, License, TextFile, type Project, javascript, python } from "projen";
|
|
5
5
|
import { JobPermission } from "projen/lib/github/workflows-model";
|
|
6
|
-
import { BUN_VERSION
|
|
6
|
+
import { BUN_VERSION } from "./bun-workflow.ts";
|
|
7
7
|
import { DBX_TOOLS_LICENSE, projectRepositoryUrl } from "./project-js.ts";
|
|
8
8
|
import { isDBXToolsJavaScriptProject } from "./project-predicate.ts";
|
|
9
|
-
import { hasWorkspaceNodePackage } from "./python-node-bindings.ts";
|
|
10
|
-
import { PythonNodeBundle, type PythonNodeBindingsOptions } from "./python-node-bundle.ts";
|
|
11
9
|
import type { DBXToolsProject, DBXToolsProjectOptions } from "./project.ts";
|
|
10
|
+
import { PythonNodeBundle, type PythonNodeBindingsOptions } from "./python-node-bundle.ts";
|
|
12
11
|
import { RELEASE_VERSION, releaseSourceSteps } from "./release-context.ts";
|
|
13
12
|
import {
|
|
14
13
|
refreshReleaseDocsDependencies,
|
|
14
|
+
releaseCondition,
|
|
15
15
|
releaseTagPattern,
|
|
16
|
-
releasePublishCondition,
|
|
17
16
|
tryReleaseWorkflow,
|
|
18
17
|
uvSetupStep,
|
|
19
18
|
} from "./release.ts";
|
|
@@ -38,12 +37,10 @@ export interface PythonPackageOptions extends DBXToolsProjectOptions {
|
|
|
38
37
|
/** Workspace package directories rendered as standalone Git dependencies. */
|
|
39
38
|
readonly internalDependencies?: readonly string[];
|
|
40
39
|
readonly scripts?: Readonly<Record<string, string>>;
|
|
41
|
-
/**
|
|
42
|
-
readonly nodeBindings?: PythonNodeBindingsOptions
|
|
40
|
+
/** One or more build-time Node packages embedded through PythonMonkey. */
|
|
41
|
+
readonly nodeBindings?: OneOrMany<PythonNodeBindingsOptions>;
|
|
43
42
|
/** Generated source files excluded from strict static analysis. Package-relative. */
|
|
44
43
|
readonly generatedSources?: readonly string[];
|
|
45
|
-
/** Trusted publisher used outside the standard Python release workflow. */
|
|
46
|
-
readonly trustedPublisher?: PythonTrustedPublisherOptions;
|
|
47
44
|
}
|
|
48
45
|
|
|
49
46
|
interface ResolvedPythonPackageOptions extends PythonPackageOptions {
|
|
@@ -51,12 +48,6 @@ interface ResolvedPythonPackageOptions extends PythonPackageOptions {
|
|
|
51
48
|
readonly module: string;
|
|
52
49
|
}
|
|
53
50
|
|
|
54
|
-
/** GitHub Actions publisher for a Python package released by another workflow. */
|
|
55
|
-
export interface PythonTrustedPublisherOptions {
|
|
56
|
-
readonly environment: string;
|
|
57
|
-
readonly artifacts?: string;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
51
|
/** Options for one projen-native Python workspace member. */
|
|
61
52
|
export interface DBXToolsPythonProjectOptions extends DBXToolsProjectOptions {
|
|
62
53
|
readonly parent: Project;
|
|
@@ -78,7 +69,6 @@ interface PythonPublication {
|
|
|
78
69
|
readonly directory: string;
|
|
79
70
|
readonly distribution: string;
|
|
80
71
|
readonly environment: string;
|
|
81
|
-
readonly artifacts?: string;
|
|
82
72
|
readonly dependencies?: readonly string[];
|
|
83
73
|
}
|
|
84
74
|
|
|
@@ -117,32 +107,11 @@ export function pythonPackagePath(repository: PythonRepositoryOptions, directory
|
|
|
117
107
|
return `${repository.root ?? "packages/py"}/${directory}`;
|
|
118
108
|
}
|
|
119
109
|
|
|
120
|
-
/** PEP 508 dependency pointing at a sibling package in a Git repository. */
|
|
121
|
-
export function pythonGitDependency(
|
|
122
|
-
repository: PythonRepositoryOptions,
|
|
123
|
-
name: string,
|
|
124
|
-
directory: string,
|
|
125
|
-
): string {
|
|
126
|
-
return `${name} @ git+${repository.url}@${repository.ref ?? "main"}#subdirectory=${pythonPackagePath(repository, directory)}`;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
110
|
/** Derive a dotted Python module from an npm-style scope and package directory. */
|
|
130
111
|
export function pythonModuleName(scope: string, directory: string): string {
|
|
131
112
|
return [scope, ...directory.split("/")].map((part) => part.replaceAll("-", "_")).join(".");
|
|
132
113
|
}
|
|
133
114
|
|
|
134
|
-
/** Published requirement for one internal Python dependency. */
|
|
135
|
-
export function pythonReleaseRequirement(version: string, exact = false): string {
|
|
136
|
-
if (exact) return `==${version}`;
|
|
137
|
-
const [major, minor] = version.split(".").map(Number);
|
|
138
|
-
const upper = major === 0 ? `0.${minor + 1}.0` : `${major + 1}.0.0`;
|
|
139
|
-
return `>=${version},<${upper}`;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function projectVscode(project: Project): vscode.VsCode | undefined {
|
|
143
|
-
return (project as Project & { readonly vscode?: vscode.VsCode }).vscode;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
115
|
/** A Python package implemented with projen's `PythonProject` and uv backend. */
|
|
147
116
|
export class DBXToolsPythonProject extends python.PythonProject implements DBXToolsProject {
|
|
148
117
|
readonly language = "python" as const;
|
|
@@ -151,6 +120,7 @@ export class DBXToolsPythonProject extends python.PythonProject implements DBXTo
|
|
|
151
120
|
|
|
152
121
|
constructor(options: DBXToolsPythonProjectOptions) {
|
|
153
122
|
const pkg = options.package;
|
|
123
|
+
const nodeBindings = pythonNodeBindings(pkg);
|
|
154
124
|
super({
|
|
155
125
|
parent: options.parent,
|
|
156
126
|
outdir: pythonPackagePath(options.repository, pkg.directory),
|
|
@@ -182,6 +152,7 @@ export class DBXToolsPythonProject extends python.PythonProject implements DBXTo
|
|
|
182
152
|
licenseFiles: ["LICENSE"],
|
|
183
153
|
requiresPython: options.requiresPython,
|
|
184
154
|
dependencies: [...(pkg.dependencies ?? [])],
|
|
155
|
+
scripts: pkg.scripts,
|
|
185
156
|
urls: {
|
|
186
157
|
Source: `${options.repository.url.replace(/\.git$/, "")}/tree/${options.repository.ref}/${pythonPackagePath(options.repository, pkg.directory)}`,
|
|
187
158
|
},
|
|
@@ -193,8 +164,7 @@ export class DBXToolsPythonProject extends python.PythonProject implements DBXTo
|
|
|
193
164
|
uv: {
|
|
194
165
|
buildBackend: {
|
|
195
166
|
moduleName: pkg.module,
|
|
196
|
-
moduleRoot:
|
|
197
|
-
pkg.moduleRoot ?? (pkg.nodeBindings?.layout === "package" ? "generated-src" : "src"),
|
|
167
|
+
moduleRoot: pkg.moduleRoot ?? "src",
|
|
198
168
|
namespace: true,
|
|
199
169
|
},
|
|
200
170
|
},
|
|
@@ -206,29 +176,16 @@ export class DBXToolsPythonProject extends python.PythonProject implements DBXTo
|
|
|
206
176
|
throw new Error(`Expected uv packaging for ${pkg.name}`);
|
|
207
177
|
}
|
|
208
178
|
this.uv = this.packagingManager;
|
|
179
|
+
this.tasks.removeTask("publish");
|
|
180
|
+
this.tasks.removeTask("publish:test");
|
|
209
181
|
this.uv.file.addDeletionOverride("project.authors");
|
|
210
182
|
this.uv.file.addDeletionOverride("dependency-groups");
|
|
211
|
-
if (
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
...(pkg.nodeBindings.entrypoint ? { entrypoint: pkg.nodeBindings.entrypoint } : {}),
|
|
218
|
-
...(pkg.nodeBindings.layout ? { layout: pkg.nodeBindings.layout } : {}),
|
|
219
|
-
...(pkg.nodeBindings.private ? { private: true } : {}),
|
|
220
|
-
...(pkg.nodeBindings.shimRoot ? { shim_root: pkg.nodeBindings.shimRoot } : {}),
|
|
221
|
-
...(pkg.nodeBindings.functionOverrides?.length
|
|
222
|
-
? {
|
|
223
|
-
function_overrides: pkg.nodeBindings.functionOverrides.map((override) => ({
|
|
224
|
-
module: override.module,
|
|
225
|
-
export: override.export,
|
|
226
|
-
handler: override.handler,
|
|
227
|
-
...(override.handlerExport ? { handler_export: override.handlerExport } : {}),
|
|
228
|
-
})),
|
|
229
|
-
}
|
|
230
|
-
: {}),
|
|
231
|
-
});
|
|
183
|
+
if (nodeBindings.length > 0) {
|
|
184
|
+
const rendered = nodeBindings.map(renderPythonNodeBindings);
|
|
185
|
+
this.uv.file.addOverride(
|
|
186
|
+
"tool.dbx_tools.node_bindings",
|
|
187
|
+
rendered.length === 1 ? rendered[0] : rendered,
|
|
188
|
+
);
|
|
232
189
|
}
|
|
233
190
|
this.uv.file.readonly = true;
|
|
234
191
|
|
|
@@ -286,7 +243,7 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
286
243
|
`Python package ${pkg.directory} references unknown internal package ${directory}`,
|
|
287
244
|
);
|
|
288
245
|
}
|
|
289
|
-
return
|
|
246
|
+
return dependency.name;
|
|
290
247
|
}),
|
|
291
248
|
],
|
|
292
249
|
}));
|
|
@@ -301,53 +258,17 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
301
258
|
package: pkg,
|
|
302
259
|
repository: this.repository,
|
|
303
260
|
requiresPython: this.requiresPython,
|
|
304
|
-
version:
|
|
305
|
-
? project.releaseCatalog.versionForRegistration(
|
|
306
|
-
"python",
|
|
307
|
-
pkg.name,
|
|
308
|
-
pythonPackagePath(this.repository, pkg.directory),
|
|
309
|
-
)
|
|
310
|
-
: this.version,
|
|
261
|
+
version: this.version,
|
|
311
262
|
}),
|
|
312
263
|
);
|
|
313
264
|
for (const pkg of this.packages) {
|
|
314
|
-
|
|
265
|
+
const bindings = pythonNodeBindings(pkg.packageOptions);
|
|
266
|
+
if (bindings.length === 0) continue;
|
|
315
267
|
new PythonNodeBundle(project, {
|
|
316
268
|
name: pkg.packageOptions.directory,
|
|
317
269
|
projectDirectory: pythonPackagePath(this.repository, pkg.packageOptions.directory),
|
|
318
|
-
watch: hasWorkspaceNodePackage(project, pkg.packageOptions.nodeBindings.package),
|
|
319
270
|
});
|
|
320
271
|
}
|
|
321
|
-
if (isDBXToolsJavaScriptProject()(project)) {
|
|
322
|
-
for (const pkg of this.packages) {
|
|
323
|
-
project.releaseCatalog.registerProject(pkg, {
|
|
324
|
-
language: "python",
|
|
325
|
-
identity: pkg.packageOptions.name,
|
|
326
|
-
sourcePaths: [`${pythonPackagePath(this.repository, pkg.packageOptions.directory)}/src`],
|
|
327
|
-
dependencies: (pkg.packageOptions.internalDependencies ?? []).map((directory) => {
|
|
328
|
-
const dependency = packagesByDirectory.get(directory);
|
|
329
|
-
if (!dependency) {
|
|
330
|
-
throw new Error(
|
|
331
|
-
`Python package ${pkg.packageOptions.directory} references unknown internal package ${directory}`,
|
|
332
|
-
);
|
|
333
|
-
}
|
|
334
|
-
const version = project.releaseCatalog.versionForRegistration(
|
|
335
|
-
"python",
|
|
336
|
-
dependency.name,
|
|
337
|
-
pythonPackagePath(this.repository, dependency.directory),
|
|
338
|
-
);
|
|
339
|
-
return {
|
|
340
|
-
target: dependency.name,
|
|
341
|
-
kind: "runtime" as const,
|
|
342
|
-
requirement: pythonReleaseRequirement(version),
|
|
343
|
-
propagation: "outside-range" as const,
|
|
344
|
-
publishOrder: true,
|
|
345
|
-
internal: true,
|
|
346
|
-
};
|
|
347
|
-
}),
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
272
|
for (const pkg of this.packages) {
|
|
352
273
|
const pyproject = `/${pythonPackagePath(this.repository, pkg.packageOptions.directory)}/pyproject.toml`;
|
|
353
274
|
project.gitignore.include(pyproject);
|
|
@@ -370,7 +291,7 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
370
291
|
|
|
371
292
|
const interpreterPath = options.interpreterPath ?? "${workspaceFolder}/.venv/bin/python";
|
|
372
293
|
if (interpreterPath !== false) {
|
|
373
|
-
|
|
294
|
+
project.vscode?.settings.addSetting("python.defaultInterpreterPath", interpreterPath);
|
|
374
295
|
}
|
|
375
296
|
|
|
376
297
|
if (options.release && this.packages.length > 0) {
|
|
@@ -383,11 +304,6 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
383
304
|
return pythonPackagePath(this.repository, directory);
|
|
384
305
|
}
|
|
385
306
|
|
|
386
|
-
/** PEP 508 dependency pointing at a sibling package in the configured repository. */
|
|
387
|
-
gitDependency(name: string, directory: string): string {
|
|
388
|
-
return pythonGitDependency(this.repository, name, directory);
|
|
389
|
-
}
|
|
390
|
-
|
|
391
307
|
private emitWorkspace(
|
|
392
308
|
project: javascript.NodeProject,
|
|
393
309
|
options: Omit<DBXToolsPythonWorkspaceOptions, "packages"> & {
|
|
@@ -490,7 +406,7 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
490
406
|
(dependency) => `publish-pypi-${dependency}`,
|
|
491
407
|
);
|
|
492
408
|
workflow.addJob(`publish-pypi-${publication.directory}`, {
|
|
493
|
-
if:
|
|
409
|
+
if: releaseCondition([
|
|
494
410
|
"needs.verify-context.result == 'success'",
|
|
495
411
|
...dependencyJobs.map((job) => `needs['${job}'].result == 'success'`),
|
|
496
412
|
]),
|
|
@@ -507,12 +423,8 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
507
423
|
env: { BUN_VERSION },
|
|
508
424
|
steps: [
|
|
509
425
|
...releaseSourceSteps(),
|
|
510
|
-
...
|
|
511
|
-
ignorePaths: project.workflowCacheIgnorePaths,
|
|
512
|
-
}),
|
|
426
|
+
...project.renderWorkflowSetup({ mutable: false }),
|
|
513
427
|
uvSetupStep(),
|
|
514
|
-
{ name: "Install release helpers", run: "bun install --frozen-lockfile" },
|
|
515
|
-
bunCacheSaveStep(),
|
|
516
428
|
{
|
|
517
429
|
name: `Build ${publication.distribution} distributions`,
|
|
518
430
|
env: { RELEASE_VERSION },
|
|
@@ -547,31 +459,12 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
547
459
|
}));
|
|
548
460
|
}
|
|
549
461
|
|
|
550
|
-
private trustedPublisherPublications(
|
|
551
|
-
options: PythonReleaseOptions,
|
|
552
|
-
): readonly PythonPublication[] {
|
|
553
|
-
const standard = new Map(
|
|
554
|
-
this.publications(options).map((publication) => [publication.distribution, publication]),
|
|
555
|
-
);
|
|
556
|
-
for (const pkg of this.packages) {
|
|
557
|
-
const publisher = pkg.packageOptions.trustedPublisher;
|
|
558
|
-
if (!publisher || standard.has(pkg.packageOptions.name)) continue;
|
|
559
|
-
standard.set(pkg.packageOptions.name, {
|
|
560
|
-
directory: pkg.packageOptions.directory,
|
|
561
|
-
distribution: pkg.packageOptions.name,
|
|
562
|
-
environment: publisher.environment,
|
|
563
|
-
artifacts: publisher.artifacts,
|
|
564
|
-
});
|
|
565
|
-
}
|
|
566
|
-
return [...standard.values()];
|
|
567
|
-
}
|
|
568
|
-
|
|
569
462
|
private addTrustedPublisherInstructionsTask(
|
|
570
463
|
project: javascript.NodeProject,
|
|
571
464
|
options: PythonReleaseOptions,
|
|
572
465
|
): void {
|
|
573
466
|
const repository = this.githubRepository();
|
|
574
|
-
const publications = this.
|
|
467
|
+
const publications = this.publications(options);
|
|
575
468
|
const releaseTag =
|
|
576
469
|
isDBXToolsJavaScriptProject()(project) && tryReleaseWorkflow(project)
|
|
577
470
|
? releaseTagPattern(project)
|
|
@@ -641,7 +534,6 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
641
534
|
"- Workflow name: release.yml",
|
|
642
535
|
`- Environment name: ${publication.environment}`,
|
|
643
536
|
`- GitHub environment tag: ${releaseTag}`,
|
|
644
|
-
...(publication.artifacts ? [`- Artifacts: ${publication.artifacts}`] : []),
|
|
645
537
|
"",
|
|
646
538
|
];
|
|
647
539
|
}),
|
|
@@ -689,16 +581,35 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
689
581
|
}
|
|
690
582
|
}
|
|
691
583
|
|
|
692
|
-
function
|
|
584
|
+
function pythonNodeBindings(
|
|
585
|
+
pkg: Pick<PythonPackageOptions, "nodeBindings">,
|
|
586
|
+
): readonly PythonNodeBindingsOptions[] {
|
|
693
587
|
if (!pkg.nodeBindings) return [];
|
|
694
|
-
|
|
695
|
-
|
|
588
|
+
return Array.isArray(pkg.nodeBindings) ? pkg.nodeBindings : [pkg.nodeBindings];
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function renderPythonNodeBindings(binding: PythonNodeBindingsOptions): Record<string, unknown> {
|
|
592
|
+
return {
|
|
593
|
+
package: binding.package,
|
|
594
|
+
...(binding.entrypoint ? { entrypoint: binding.entrypoint } : {}),
|
|
595
|
+
...(binding.modules?.length ? { modules: binding.modules } : {}),
|
|
596
|
+
...(binding.functionOverrides?.length
|
|
597
|
+
? {
|
|
598
|
+
function_overrides: binding.functionOverrides.map((override) => ({
|
|
599
|
+
module: override.module,
|
|
600
|
+
export: override.export,
|
|
601
|
+
handler: override.handler,
|
|
602
|
+
...(override.handlerExport ? { handler_export: override.handlerExport } : {}),
|
|
603
|
+
})),
|
|
604
|
+
}
|
|
605
|
+
: {}),
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function pythonNodeGeneratedSources(pkg: ResolvedPythonPackageOptions): string[] {
|
|
610
|
+
const bindings = pythonNodeBindings(pkg);
|
|
611
|
+
if (bindings.length === 0) return [];
|
|
612
|
+
const moduleRoot = pkg.moduleRoot ?? "src";
|
|
696
613
|
const moduleDirectory = `${moduleRoot}/${pkg.module.replaceAll(".", "/")}`;
|
|
697
|
-
|
|
698
|
-
pkg.nodeBindings.layout === "package" ? moduleDirectory : `${moduleDirectory}/_generated`;
|
|
699
|
-
return [
|
|
700
|
-
`${generatedDirectory}/_runtime.js`,
|
|
701
|
-
`${generatedDirectory}/node_bindings.py`,
|
|
702
|
-
`${generatedDirectory}/__init__.py`,
|
|
703
|
-
];
|
|
614
|
+
return [`${moduleDirectory}/_generated/**`];
|
|
704
615
|
}
|
package/src/publish.ts
CHANGED
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
*/
|
|
30
30
|
import type { javascript } from "projen";
|
|
31
31
|
import { typescript } from "projen";
|
|
32
|
+
import { addPackageFiles, applyCompilerOptions, applyIncludes } from "./project-js.ts";
|
|
32
33
|
import { isDBXToolsJavaScriptProject } from "./project-predicate.ts";
|
|
33
|
-
import { addPackageFiles, applyCompilerOptions, applyIncludes } from "./project.ts";
|
|
34
34
|
|
|
35
35
|
/** Directory `tsc` emits into, and the root of every published entry point. */
|
|
36
36
|
export const COMPILED_DIR = "lib";
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/** Shared pyproject-backed configuration for PythonMonkey Node bindings. */
|
|
2
|
-
import { readFileSync } from "node:fs";
|
|
3
|
-
import { join, resolve } from "node:path";
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
2
|
+
import { readFileSync, rmSync } from "node:fs";
|
|
3
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { find } from "@dbx-tools/path";
|
|
6
|
+
import { object, stringUtils } from "@dbx-tools/shared-core";
|
|
6
7
|
import { parse } from "smol-toml";
|
|
7
|
-
import {
|
|
8
|
+
import { runTaskCommand } from "./_task-command.ts";
|
|
9
|
+
import { resolveRepoRoot, workspaceDependencyDirectories } from "./packages.ts";
|
|
8
10
|
|
|
9
11
|
export interface ResolvedPythonNodeFunctionOverride {
|
|
10
12
|
readonly handlerExport: string;
|
|
@@ -14,82 +16,142 @@ export interface ResolvedPythonNodeFunctionOverride {
|
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
export interface ResolvedPythonNodeBindings {
|
|
17
|
-
readonly
|
|
18
|
-
readonly
|
|
19
|
+
readonly bindingDirectory: string;
|
|
20
|
+
readonly bindingName: string;
|
|
19
21
|
readonly functionOverrides: readonly ResolvedPythonNodeFunctionOverride[];
|
|
20
22
|
readonly entrypoint: string;
|
|
21
23
|
readonly moduleDirectory: string;
|
|
22
|
-
readonly
|
|
23
|
-
readonly private: boolean;
|
|
24
|
+
readonly modules: readonly string[];
|
|
24
25
|
readonly package: string;
|
|
25
26
|
readonly project: string;
|
|
26
27
|
readonly projectDirectory: string;
|
|
27
28
|
readonly pyproject: string;
|
|
28
29
|
readonly runtimeOutput: string;
|
|
29
|
-
readonly shimRoot?: string;
|
|
30
30
|
readonly workspaceDirectories: readonly string[];
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
/**
|
|
33
|
+
/** Standard PythonMonkey host adapters shipped with the binding generator. */
|
|
34
|
+
export const PYTHON_NODE_SHIM_ROOT = resolve(
|
|
35
|
+
dirname(fileURLToPath(import.meta.url)),
|
|
36
|
+
"../shims/python-node",
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
/** Regenerate every pyproject-configured Node binding in a repository. */
|
|
40
|
+
export function generatePythonNodeBindings(projectRoot: string = resolveRepoRoot()): void {
|
|
41
|
+
const task = resolve(dirname(fileURLToPath(import.meta.url)), "../tasks/python-node-bindings.ts");
|
|
42
|
+
const configuredProjects = pythonNodeBindingProjects(projectRoot);
|
|
43
|
+
const directories = new Set<string>();
|
|
44
|
+
for (const project of configuredProjects) {
|
|
45
|
+
for (const config of resolvePythonNodeBindings(projectRoot, project)) {
|
|
46
|
+
directories.add(relative(projectRoot, join(config.moduleDirectory, "_generated", "node")));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
for (const directory of existingBindingDirectories(projectRoot)) {
|
|
50
|
+
if (!directories.has(directory)) {
|
|
51
|
+
rmSync(resolve(projectRoot, directory), { recursive: true, force: true });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
for (const project of configuredProjects) {
|
|
55
|
+
runTaskCommand(projectRoot, "bun", [task, "--root", projectRoot, "--project", project]);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function pythonNodeBindingProjects(projectRoot: string): string[] {
|
|
60
|
+
return [
|
|
61
|
+
...find
|
|
62
|
+
.findFiles("**/pyproject.toml", { cwd: projectRoot })
|
|
63
|
+
.filter((file) => {
|
|
64
|
+
const manifest = parse(readFileSync(resolve(projectRoot, file), "utf8")) as {
|
|
65
|
+
tool?: { dbx_tools?: { node_bindings?: unknown } };
|
|
66
|
+
};
|
|
67
|
+
return manifest.tool?.dbx_tools?.node_bindings !== undefined;
|
|
68
|
+
})
|
|
69
|
+
.map((file) => dirname(file)),
|
|
70
|
+
].sort();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function existingBindingDirectories(projectRoot: string): string[] {
|
|
74
|
+
const directories = new Set<string>();
|
|
75
|
+
for (const file of find.findFiles("**/_generated/node/**/*", {
|
|
76
|
+
cwd: projectRoot,
|
|
77
|
+
ignore: () => false,
|
|
78
|
+
})) {
|
|
79
|
+
const segments = file.split("/");
|
|
80
|
+
const generated = segments.lastIndexOf("_generated");
|
|
81
|
+
if (generated >= 0 && segments[generated + 1] === "node") {
|
|
82
|
+
directories.add(segments.slice(0, generated + 2).join("/"));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return [...directories].sort();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Resolve one Python package's Node binding configurations and conventional outputs. */
|
|
34
89
|
export function resolvePythonNodeBindings(
|
|
35
90
|
root: string,
|
|
36
91
|
project: string,
|
|
37
|
-
): ResolvedPythonNodeBindings {
|
|
92
|
+
): ResolvedPythonNodeBindings[] {
|
|
38
93
|
const projectDirectory = resolve(root, project);
|
|
39
94
|
const pyproject = resolve(projectDirectory, "pyproject.toml");
|
|
40
95
|
const manifest = record(parse(readFileSync(pyproject, "utf8")), "pyproject.toml");
|
|
41
96
|
const tool = record(manifest.tool, "tool");
|
|
42
97
|
const dbxTools = record(tool.dbx_tools, "tool.dbx_tools");
|
|
43
|
-
const
|
|
98
|
+
const configuredBindings = Array.isArray(dbxTools.node_bindings)
|
|
99
|
+
? dbxTools.node_bindings
|
|
100
|
+
: [dbxTools.node_bindings];
|
|
101
|
+
if (configuredBindings.length === 0) {
|
|
102
|
+
throw new Error("tool.dbx_tools.node_bindings must contain at least one table");
|
|
103
|
+
}
|
|
44
104
|
const uv = record(tool.uv, "tool.uv");
|
|
45
105
|
const backend = record(uv["build-backend"], "tool.uv.build-backend");
|
|
46
|
-
const packageName = requiredString(bindings.package, "tool.dbx_tools.node_bindings.package");
|
|
47
|
-
const entrypoint =
|
|
48
|
-
optionalString(bindings.entrypoint, "tool.dbx_tools.node_bindings.entrypoint") ?? packageName;
|
|
49
|
-
const layout = nodeBindingsLayout(bindings.layout);
|
|
50
106
|
const moduleName = requiredString(backend["module-name"], "tool.uv.build-backend.module-name");
|
|
51
107
|
const moduleRoot = requiredString(backend["module-root"], "tool.uv.build-backend.module-root");
|
|
52
|
-
const configuredOverrides = bindings.function_overrides ?? [];
|
|
53
|
-
if (!Array.isArray(configuredOverrides)) {
|
|
54
|
-
throw new Error("tool.dbx_tools.node_bindings.function_overrides must be an array");
|
|
55
|
-
}
|
|
56
108
|
const moduleDirectory = resolve(projectDirectory, moduleRoot, ...moduleName.split("."));
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
layout
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
109
|
+
const configs = configuredBindings.map((candidate, bindingIndex) => {
|
|
110
|
+
const bindingPath = Array.isArray(dbxTools.node_bindings)
|
|
111
|
+
? `tool.dbx_tools.node_bindings[${bindingIndex}]`
|
|
112
|
+
: "tool.dbx_tools.node_bindings";
|
|
113
|
+
const bindings = record(candidate, bindingPath);
|
|
114
|
+
const packageName = requiredString(bindings.package, `${bindingPath}.package`);
|
|
115
|
+
const entrypoint =
|
|
116
|
+
optionalString(bindings.entrypoint, `${bindingPath}.entrypoint`) ?? packageName;
|
|
117
|
+
const modules = optionalStrings(bindings.modules, `${bindingPath}.modules`);
|
|
118
|
+
if (bindings.layout !== undefined || bindings.private !== undefined) {
|
|
119
|
+
throw new Error(`${bindingPath} no longer supports layout or private`);
|
|
120
|
+
}
|
|
121
|
+
const configuredOverrides = bindings.function_overrides ?? [];
|
|
122
|
+
if (!Array.isArray(configuredOverrides)) {
|
|
123
|
+
throw new Error(`${bindingPath}.function_overrides must be an array`);
|
|
124
|
+
}
|
|
125
|
+
const bindingName = nodePackageName(packageName);
|
|
126
|
+
const bindingDirectory = join(moduleDirectory, "_generated", "node", bindingName);
|
|
127
|
+
return {
|
|
128
|
+
project,
|
|
129
|
+
projectDirectory,
|
|
130
|
+
pyproject,
|
|
131
|
+
package: packageName,
|
|
132
|
+
entrypoint,
|
|
133
|
+
modules,
|
|
134
|
+
bindingName,
|
|
135
|
+
bindingDirectory,
|
|
136
|
+
moduleDirectory,
|
|
137
|
+
runtimeOutput: join(moduleDirectory, "_generated", "node", "_runtime.js"),
|
|
138
|
+
functionOverrides: configuredOverrides.map((overrideCandidate, overrideIndex) => {
|
|
139
|
+
const path = `${bindingPath}.function_overrides[${overrideIndex}]`;
|
|
140
|
+
const override = record(overrideCandidate, path);
|
|
141
|
+
const targetExport = requiredString(override.export, `${path}.export`);
|
|
142
|
+
return {
|
|
143
|
+
targetModule: requiredString(override.module, `${path}.module`),
|
|
144
|
+
targetExport,
|
|
145
|
+
handlerFile: resolve(root, requiredString(override.handler, `${path}.handler`)),
|
|
146
|
+
handlerExport:
|
|
147
|
+
optionalString(override.handler_export, `${path}.handler_export`) ?? targetExport,
|
|
148
|
+
};
|
|
149
|
+
}),
|
|
150
|
+
workspaceDirectories: workspaceDependencyDirectories(packageName, root),
|
|
151
|
+
};
|
|
152
|
+
});
|
|
153
|
+
validateBindings(configs);
|
|
154
|
+
return configs;
|
|
93
155
|
}
|
|
94
156
|
|
|
95
157
|
/** All paths that can change a workspace-backed generated Node runtime. */
|
|
@@ -100,16 +162,26 @@ export function pythonNodeBindingWatchInputs(config: ResolvedPythonNodeBindings)
|
|
|
100
162
|
return [
|
|
101
163
|
config.pyproject,
|
|
102
164
|
...config.workspaceDirectories,
|
|
103
|
-
|
|
165
|
+
PYTHON_NODE_SHIM_ROOT,
|
|
104
166
|
...config.functionOverrides.map(({ handlerFile }) => handlerFile),
|
|
105
167
|
];
|
|
106
168
|
}
|
|
107
169
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
170
|
+
function validateBindings(configs: readonly ResolvedPythonNodeBindings[]): void {
|
|
171
|
+
const names = configs.map(({ bindingName }) => bindingName);
|
|
172
|
+
if (new Set(names).size !== names.length) {
|
|
173
|
+
throw new Error("tool.dbx_tools.node_bindings package names must be unique");
|
|
174
|
+
}
|
|
175
|
+
for (const config of configs) {
|
|
176
|
+
if (new Set(config.modules).size !== config.modules.length) {
|
|
177
|
+
throw new Error(`${config.package} Node binding module names must be unique`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function nodePackageName(packageName: string): string {
|
|
183
|
+
const unscoped = packageName.split("/").at(-1) ?? packageName;
|
|
184
|
+
return stringUtils.toIdentifierWithOptions({ delimiter: "_" }, unscoped);
|
|
113
185
|
}
|
|
114
186
|
|
|
115
187
|
function record(value: unknown, path: string): Record<string, unknown> {
|
|
@@ -131,8 +203,9 @@ function optionalString(value: unknown, path: string): string | undefined {
|
|
|
131
203
|
return value.trim();
|
|
132
204
|
}
|
|
133
205
|
|
|
134
|
-
function
|
|
135
|
-
if (value === undefined) return
|
|
136
|
-
if (typeof value
|
|
137
|
-
|
|
206
|
+
function optionalStrings(value: unknown, path: string): string[] {
|
|
207
|
+
if (value === undefined) return [];
|
|
208
|
+
if (typeof value === "string") return [requiredString(value, path)];
|
|
209
|
+
if (!Array.isArray(value)) throw new Error(`${path} must be a string or array`);
|
|
210
|
+
return value.map((candidate, index) => requiredString(candidate, `${path}[${index}]`));
|
|
138
211
|
}
|