@dbx-tools/projen 0.9.43 → 0.9.46
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 +28 -34
- package/index.ts +6 -7
- package/package.json +9 -10
- package/shims/python-node/bootstrap.ts +129 -0
- package/shims/python-node/child-process.ts +96 -0
- package/shims/python-node/crypto.ts +60 -0
- package/shims/python-node/fs-promises.ts +179 -0
- package/shims/python-node/fs.ts +173 -0
- package/shims/python-node/headers.ts +75 -0
- package/shims/python-node/host.ts +341 -0
- package/shims/python-node/os.ts +20 -0
- package/shims/python-node/process.ts +13 -0
- package/shims/python-node/readline.ts +37 -0
- package/shims/python-node/tsconfig.json +24 -0
- package/src/_lazy-require.ts +7 -25
- package/src/bun-app.ts +2 -2
- package/src/bun-workflow.ts +1 -9
- package/src/codegen.ts +20 -17
- package/src/dbx-tools-config.ts +52 -9
- package/src/generated.ts +4 -17
- package/src/mixin.ts +1 -5
- package/src/packages.ts +18 -15
- package/src/pnpm-workspace.ts +1 -4
- package/src/project-js.ts +65 -157
- package/src/project-predicate.ts +7 -3
- package/src/project-py.ts +120 -43
- package/src/publish.ts +13 -10
- package/src/python-node-bindings.ts +2 -0
- package/src/python-node-bundle.ts +6 -0
- package/src/release-options.ts +17 -17
- package/src/release.ts +30 -9
- package/src/scaffold.ts +4 -7
- package/src/tags.ts +6 -16
- package/src/vscode.ts +1 -1
- package/src/watch.ts +5 -5
- package/src/workspace-lock.ts +1 -0
- package/src/workspace-version.ts +4 -4
- package/tasks/barrels.ts +3 -5
- package/tasks/clean.ts +1 -0
- package/tasks/lib/compile-workspace.ts +41 -19
- package/tasks/lib/local-publish.ts +0 -28
- package/tasks/lib/publish-npm.ts +8 -0
- package/tasks/lib/publish-python.ts +4 -0
- package/tasks/lib/python-node-bindings.ts +77 -21
- package/tasks/lib/python-registry.ts +4 -0
- package/tasks/lib/python-release.ts +2 -0
- package/tasks/lib/release.ts +1 -1
- package/tasks/projenrc.ts +1 -0
- package/tasks/sync.ts +4 -4
- package/src/openapi.ts +0 -293
- package/tasks/local-publish.ts +0 -6
- package/tasks/openapi.ts +0 -35
package/src/project-py.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
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
3
|
import { stringUtils, type OneOrMany } from "@dbx-tools/shared-core";
|
|
4
|
-
import { Component, License, TextFile, type Project, javascript, python } from "projen";
|
|
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 {
|
|
6
|
+
import { LICENSE, projectRepositoryUrl } from "./project-js.ts";
|
|
7
7
|
import { isDBXToolsJavaScriptProject } from "./project-predicate.ts";
|
|
8
8
|
import type { DBXToolsProject, DBXToolsProjectOptions } from "./project.ts";
|
|
9
9
|
import { PythonNodeBundle, type PythonNodeBindingsOptions } from "./python-node-bundle.ts";
|
|
@@ -29,17 +29,17 @@ export interface PythonPackageOptions extends DBXToolsProjectOptions {
|
|
|
29
29
|
readonly directory: string;
|
|
30
30
|
readonly name?: string;
|
|
31
31
|
readonly module?: string;
|
|
32
|
-
/** Python
|
|
32
|
+
/** Python source root passed to uv_build. Defaults to `src`. */
|
|
33
33
|
readonly moduleRoot?: string;
|
|
34
34
|
readonly description: string;
|
|
35
35
|
readonly dependencies?: readonly string[];
|
|
36
36
|
/** Workspace package directories rendered as standalone Git dependencies. */
|
|
37
37
|
readonly internalDependencies?: readonly string[];
|
|
38
38
|
readonly scripts?: Readonly<Record<string, string>>;
|
|
39
|
+
/** GitHub environment used to publish this distribution to PyPI. */
|
|
40
|
+
readonly releaseEnvironment?: string;
|
|
39
41
|
/** One or more build-time Node packages embedded through PythonMonkey. */
|
|
40
42
|
readonly nodeBindings?: OneOrMany<PythonNodeBindingsOptions>;
|
|
41
|
-
/** Generated source files excluded from strict static analysis. Package-relative. */
|
|
42
|
-
readonly generatedSources?: readonly string[];
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
interface ResolvedPythonPackageOptions extends PythonPackageOptions {
|
|
@@ -59,8 +59,6 @@ export interface DBXToolsPythonProjectOptions extends DBXToolsProjectOptions {
|
|
|
59
59
|
|
|
60
60
|
/** Python release workflow configuration. */
|
|
61
61
|
export interface PythonReleaseOptions {
|
|
62
|
-
/** GitHub environment by Python distribution name. Defaults to `pypi-<name>`. */
|
|
63
|
-
readonly environments?: Readonly<Record<string, string>>;
|
|
64
62
|
readonly environmentUrl?: string;
|
|
65
63
|
}
|
|
66
64
|
|
|
@@ -83,6 +81,8 @@ export interface DBXToolsPythonWorkspaceOptions {
|
|
|
83
81
|
/** uv strategy for repositories that intentionally use multiple trusted indexes. */
|
|
84
82
|
readonly indexStrategy?: "first-index" | "unsafe-first-match" | "unsafe-best-match";
|
|
85
83
|
readonly ruffTarget?: string;
|
|
84
|
+
/** Python version installed in validation, documentation, and release workflows. */
|
|
85
|
+
readonly workflowPythonVersion?: string;
|
|
86
86
|
readonly workspaceName?: string;
|
|
87
87
|
readonly devDependencies?: readonly string[];
|
|
88
88
|
readonly testPaths?: readonly string[];
|
|
@@ -90,8 +90,6 @@ export interface DBXToolsPythonWorkspaceOptions {
|
|
|
90
90
|
/** Repository-relative paths Ruff must not lint or format. */
|
|
91
91
|
readonly ruffExcludes?: readonly string[];
|
|
92
92
|
readonly ruffPerFileIgnores?: Readonly<Record<string, readonly string[]>>;
|
|
93
|
-
/** Generated Python implementation files Pyrefly should resolve but not type-check. */
|
|
94
|
-
readonly pyreflyProjectExcludes?: readonly string[];
|
|
95
93
|
readonly interpreterPath?: string | false;
|
|
96
94
|
readonly release?: boolean | PythonReleaseOptions;
|
|
97
95
|
}
|
|
@@ -102,6 +100,72 @@ const DEFAULT_DEV_DEPENDENCIES = [
|
|
|
102
100
|
"pyyaml>=6.0,<7",
|
|
103
101
|
"ruff>=0.12,<1",
|
|
104
102
|
] as const;
|
|
103
|
+
const DEFAULT_RUFF_TARGET = "py310";
|
|
104
|
+
const RELEASE_SETUP_CONDITION =
|
|
105
|
+
"${{ steps.release.outputs.validation == 'true' || steps.release.outputs.docs == 'true' || steps.release.outputs.pypi == 'true' }}";
|
|
106
|
+
|
|
107
|
+
/** Convert Ruff's `py311` target spelling to the setup-python `3.11` spelling. */
|
|
108
|
+
function pythonVersionFromRuffTarget(target: string): string {
|
|
109
|
+
const match = /^py(\d)(\d{1,2})$/.exec(target);
|
|
110
|
+
if (!match) throw new Error(`Cannot derive a workflow Python version from Ruff target ${target}`);
|
|
111
|
+
return `${match[1]}.${match[2]}`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Python and uv setup steps, optionally gated by a workflow expression. */
|
|
115
|
+
function pythonSetupSteps(pythonVersion: string, condition?: string): JobStep[] {
|
|
116
|
+
return [
|
|
117
|
+
{
|
|
118
|
+
name: "Setup Python",
|
|
119
|
+
uses: github.ActionRefs.ACTIONS_SETUP_PYTHON,
|
|
120
|
+
...(condition ? { if: condition } : {}),
|
|
121
|
+
with: { "python-version": pythonVersion },
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
...uvSetupStep(),
|
|
125
|
+
...(condition ? { if: condition } : {}),
|
|
126
|
+
},
|
|
127
|
+
];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Materialize Projen's lazily rendered native build steps before extending them. */
|
|
131
|
+
function resolvedJobSteps(job: Job): JobStep[] {
|
|
132
|
+
const steps = (job as unknown as { steps: JobStep[] | (() => JobStep[]) }).steps;
|
|
133
|
+
return typeof steps === "function" ? steps() : steps;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function missingSetupSteps(job: Job, setupSteps: readonly JobStep[]): JobStep[] {
|
|
137
|
+
const actionName = (step: JobStep): string | undefined => step.uses?.split("@", 1)[0];
|
|
138
|
+
const existing = new Set(resolvedJobSteps(job).map(actionName));
|
|
139
|
+
return setupSteps.filter((step) => !existing.has(actionName(step)));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Ensure Python and uv are available before validation, docs, and packaging.
|
|
144
|
+
*
|
|
145
|
+
* The Python workspace owns these prerequisites. Existing action steps are
|
|
146
|
+
* reused so repository-specific setup can override a version without causing a
|
|
147
|
+
* duplicate installation.
|
|
148
|
+
*/
|
|
149
|
+
function withPythonBuildSetup(job: Job, pythonVersion: string): Job {
|
|
150
|
+
const setupSteps = missingSetupSteps(
|
|
151
|
+
job,
|
|
152
|
+
pythonSetupSteps(pythonVersion, RELEASE_SETUP_CONDITION),
|
|
153
|
+
);
|
|
154
|
+
if (setupSteps.length === 0) return job;
|
|
155
|
+
const steps = resolvedJobSteps(job);
|
|
156
|
+
const verificationIndex = steps.findIndex((step) => step.name === "Verify release context");
|
|
157
|
+
const validationIndex = steps.findIndex((step) => step.name?.startsWith("Validate "));
|
|
158
|
+
const insertionIndex =
|
|
159
|
+
verificationIndex >= 0
|
|
160
|
+
? verificationIndex + 1
|
|
161
|
+
: validationIndex < 0
|
|
162
|
+
? steps.length
|
|
163
|
+
: validationIndex;
|
|
164
|
+
return {
|
|
165
|
+
...job,
|
|
166
|
+
steps: [...steps.slice(0, insertionIndex), ...setupSteps, ...steps.slice(insertionIndex)],
|
|
167
|
+
};
|
|
168
|
+
}
|
|
105
169
|
|
|
106
170
|
/** Repository-relative path for a Python package directory. */
|
|
107
171
|
export function pythonPackagePath(repository: PythonRepositoryOptions, directory: string): string {
|
|
@@ -131,7 +195,7 @@ export class DBXToolsPythonProject extends python.PythonProject implements DBXTo
|
|
|
131
195
|
authorEmail: "",
|
|
132
196
|
version: options.version,
|
|
133
197
|
description: pkg.description,
|
|
134
|
-
license:
|
|
198
|
+
license: LICENSE,
|
|
135
199
|
github: false,
|
|
136
200
|
sample: false,
|
|
137
201
|
pytest: false,
|
|
@@ -171,7 +235,7 @@ export class DBXToolsPythonProject extends python.PythonProject implements DBXTo
|
|
|
171
235
|
},
|
|
172
236
|
},
|
|
173
237
|
});
|
|
174
|
-
new License(this, { spdx:
|
|
238
|
+
new License(this, { spdx: LICENSE });
|
|
175
239
|
this.packageOptions = pkg;
|
|
176
240
|
if (!(this.packagingManager instanceof python.Uv)) {
|
|
177
241
|
throw new Error(`Expected uv packaging for ${pkg.name}`);
|
|
@@ -202,12 +266,19 @@ export class DBXToolsPythonProject extends python.PythonProject implements DBXTo
|
|
|
202
266
|
/**
|
|
203
267
|
* Generates a root uv workspace, projen-native Python member projects, Python
|
|
204
268
|
* tasks, editor interpreter selection, and an optional publishing workflow.
|
|
269
|
+
*
|
|
270
|
+
* Native `PythonProject` and `PyprojectTomlFile` continue to own each package
|
|
271
|
+
* and TOML rendering. This component owns only the unsupported remainder:
|
|
272
|
+
* attaching several uv members to an existing JavaScript root and adding their
|
|
273
|
+
* dependency-ordered artifacts to the repository's shared tag release.
|
|
205
274
|
*/
|
|
206
275
|
export class DBXToolsPythonWorkspace extends Component {
|
|
207
276
|
readonly packages: readonly DBXToolsPythonProject[];
|
|
208
277
|
readonly repository: Required<PythonRepositoryOptions>;
|
|
209
278
|
readonly requiresPython: string;
|
|
210
279
|
readonly version: string;
|
|
280
|
+
/** Exact Python version used by the shared release build. */
|
|
281
|
+
readonly workflowPythonVersion: string;
|
|
211
282
|
readonly file: python.PyprojectTomlFile;
|
|
212
283
|
|
|
213
284
|
constructor(project: javascript.NodeProject, options: DBXToolsPythonWorkspaceOptions) {
|
|
@@ -250,6 +321,9 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
250
321
|
}));
|
|
251
322
|
const resolvedOptions = { ...options, packages };
|
|
252
323
|
this.requiresPython = options.requiresPython ?? ">=3.10";
|
|
324
|
+
this.workflowPythonVersion =
|
|
325
|
+
options.workflowPythonVersion ??
|
|
326
|
+
pythonVersionFromRuffTarget(options.ruffTarget ?? DEFAULT_RUFF_TARGET);
|
|
253
327
|
this.version = readWorkspaceVersion(project.outdir);
|
|
254
328
|
this.file = this.emitWorkspace(project, resolvedOptions, scope);
|
|
255
329
|
this.packages = packages.map(
|
|
@@ -285,10 +359,11 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
285
359
|
`${this.repository.root}/**/dist/`,
|
|
286
360
|
);
|
|
287
361
|
this.addTasks(project, resolvedOptions);
|
|
362
|
+
this.addBuildWorkflowSetup(project);
|
|
288
363
|
|
|
289
364
|
const configuredReleaseOptions = options.release === true ? {} : options.release || {};
|
|
290
365
|
const releaseOptions = { ...configuredReleaseOptions };
|
|
291
|
-
this.addTrustedPublisherInstructionsTask(project
|
|
366
|
+
this.addTrustedPublisherInstructionsTask(project);
|
|
292
367
|
|
|
293
368
|
const interpreterPath = options.interpreterPath ?? "${workspaceFolder}/.venv/bin/python";
|
|
294
369
|
if (interpreterPath !== false) {
|
|
@@ -338,7 +413,7 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
338
413
|
},
|
|
339
414
|
},
|
|
340
415
|
ruff: {
|
|
341
|
-
"target-version": options.ruffTarget ??
|
|
416
|
+
"target-version": options.ruffTarget ?? DEFAULT_RUFF_TARGET,
|
|
342
417
|
"line-length": 100,
|
|
343
418
|
lint: {
|
|
344
419
|
"per-file-ignores": perFileIgnores,
|
|
@@ -360,18 +435,6 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
360
435
|
if (ruffExcludes.length) {
|
|
361
436
|
file.addOverride("tool.ruff.exclude", [...new Set(ruffExcludes)]);
|
|
362
437
|
}
|
|
363
|
-
file.addOverride("tool.pyrefly.ignore-errors-in-generated-code", true);
|
|
364
|
-
const projectExcludes = [
|
|
365
|
-
...(options.pyreflyProjectExcludes ?? []),
|
|
366
|
-
...options.packages.flatMap((pkg) =>
|
|
367
|
-
[...(pkg.generatedSources ?? []), ...pythonNodeGeneratedSources(pkg)].map(
|
|
368
|
-
(source) => `${this.repository.root}/${pkg.directory}/${source}`,
|
|
369
|
-
),
|
|
370
|
-
),
|
|
371
|
-
];
|
|
372
|
-
if (projectExcludes.length) {
|
|
373
|
-
file.addOverride("tool.pyrefly.project-excludes", [...new Set(projectExcludes)]);
|
|
374
|
-
}
|
|
375
438
|
file.addOverride(
|
|
376
439
|
"tool.uv.sources",
|
|
377
440
|
Object.fromEntries(options.packages.map((pkg) => [pkg.name, { workspace: true }])),
|
|
@@ -390,10 +453,11 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
390
453
|
exec: "uv run pytest",
|
|
391
454
|
description: "Run Python workspace tests",
|
|
392
455
|
});
|
|
393
|
-
project.addTask("py:lint", {
|
|
456
|
+
const lint = project.addTask("py:lint", {
|
|
394
457
|
exec: `uv run ruff check ${lintPaths.join(" ")}`,
|
|
395
458
|
description: "Lint Python workspace packages",
|
|
396
459
|
});
|
|
460
|
+
lint.exec(`uv run ruff format --check ${lintPaths.join(" ")}`);
|
|
397
461
|
project.addTask("py:format", {
|
|
398
462
|
exec: `uv run ruff format ${lintPaths.join(" ")}`,
|
|
399
463
|
description: "Format Python workspace packages",
|
|
@@ -404,19 +468,37 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
404
468
|
});
|
|
405
469
|
}
|
|
406
470
|
|
|
471
|
+
/** Add Python prerequisites to Projen's native build workflow when enabled. */
|
|
472
|
+
private addBuildWorkflowSetup(project: javascript.NodeProject): void {
|
|
473
|
+
const workflow = project.buildWorkflow?.workflow;
|
|
474
|
+
const jobId = project.buildWorkflowJobId;
|
|
475
|
+
if (!workflow || !jobId) return;
|
|
476
|
+
const job = workflow.getJob(jobId) as Job | undefined;
|
|
477
|
+
if (!job) return;
|
|
478
|
+
const setupSteps = missingSetupSteps(job, pythonSetupSteps(this.workflowPythonVersion));
|
|
479
|
+
if (setupSteps.length === 0) return;
|
|
480
|
+
const steps = resolvedJobSteps(job);
|
|
481
|
+
const buildIndex = steps.findIndex((step) => step.name === "build");
|
|
482
|
+
const insertionIndex = buildIndex < 0 ? steps.length : buildIndex;
|
|
483
|
+
workflow.updateJob(jobId, {
|
|
484
|
+
...job,
|
|
485
|
+
steps: [...steps.slice(0, insertionIndex), ...setupSteps, ...steps.slice(insertionIndex)],
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
|
|
407
489
|
private addReleaseWorkflow(project: javascript.NodeProject, options: PythonReleaseOptions): void {
|
|
408
490
|
if (!project.github || !isDBXToolsJavaScriptProject()(project)) return;
|
|
409
|
-
const publications = this.publications(
|
|
491
|
+
const publications = this.publications();
|
|
410
492
|
const allPublications = publications;
|
|
411
493
|
if (allPublications.length === 0) return;
|
|
412
494
|
const workflow = tryReleaseWorkflow(project);
|
|
413
495
|
if (!workflow) {
|
|
414
496
|
throw new Error("Python release requires the root dbx-tools release mode");
|
|
415
497
|
}
|
|
416
|
-
const
|
|
417
|
-
if (!
|
|
498
|
+
const configuredBuild = workflow.getJob("build-release") as Job | undefined;
|
|
499
|
+
if (!configuredBuild) throw new Error("Python release requires the shared release build job");
|
|
500
|
+
const build = withPythonBuildSetup(configuredBuild, this.workflowPythonVersion);
|
|
418
501
|
const pythonSteps: JobStep[] = [
|
|
419
|
-
uvSetupStep(),
|
|
420
502
|
{
|
|
421
503
|
name: "Build Python distributions",
|
|
422
504
|
env: { RELEASE_VERSION: "${{ steps.release.outputs.release_version }}" },
|
|
@@ -429,7 +511,7 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
429
511
|
},
|
|
430
512
|
...allPublications.map((publication) => ({
|
|
431
513
|
name: `Upload ${publication.distribution} distributions`,
|
|
432
|
-
uses:
|
|
514
|
+
uses: github.ActionRefs.ACTIONS_UPLOAD_ARTIFACT,
|
|
433
515
|
with: {
|
|
434
516
|
name: `release-python-${publication.directory}`,
|
|
435
517
|
path: `.release/python/${publication.directory}`,
|
|
@@ -464,7 +546,7 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
464
546
|
steps: [
|
|
465
547
|
{
|
|
466
548
|
name: `Download ${publication.distribution} distributions`,
|
|
467
|
-
uses:
|
|
549
|
+
uses: github.ActionRefs.ACTIONS_DOWNLOAD_ARTIFACT,
|
|
468
550
|
with: {
|
|
469
551
|
name: `release-python-${publication.directory}`,
|
|
470
552
|
path: `dist/${publication.directory}`,
|
|
@@ -475,7 +557,6 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
475
557
|
uses: "pypa/gh-action-pypi-publish@release/v1",
|
|
476
558
|
with: {
|
|
477
559
|
"packages-dir": `dist/${publication.directory}`,
|
|
478
|
-
"skip-existing": true,
|
|
479
560
|
},
|
|
480
561
|
},
|
|
481
562
|
],
|
|
@@ -484,22 +565,18 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
484
565
|
refreshReleaseDocsDependencies(project);
|
|
485
566
|
}
|
|
486
567
|
|
|
487
|
-
private publications(
|
|
568
|
+
private publications(): readonly PythonPublication[] {
|
|
488
569
|
return this.packages.map((pkg) => ({
|
|
489
570
|
directory: pkg.packageOptions.directory,
|
|
490
571
|
distribution: pkg.packageOptions.name,
|
|
491
|
-
environment:
|
|
492
|
-
options.environments?.[pkg.packageOptions.name] ?? `pypi-${pkg.packageOptions.name}`,
|
|
572
|
+
environment: pkg.packageOptions.releaseEnvironment ?? `pypi-${pkg.packageOptions.name}`,
|
|
493
573
|
dependencies: pkg.packageOptions.internalDependencies,
|
|
494
574
|
}));
|
|
495
575
|
}
|
|
496
576
|
|
|
497
|
-
private addTrustedPublisherInstructionsTask(
|
|
498
|
-
project: javascript.NodeProject,
|
|
499
|
-
options: PythonReleaseOptions,
|
|
500
|
-
): void {
|
|
577
|
+
private addTrustedPublisherInstructionsTask(project: javascript.NodeProject): void {
|
|
501
578
|
const repository = this.githubRepository();
|
|
502
|
-
const publications = this.publications(
|
|
579
|
+
const publications = this.publications();
|
|
503
580
|
const releaseTag =
|
|
504
581
|
isDBXToolsJavaScriptProject()(project) && tryReleaseWorkflow(project)
|
|
505
582
|
? releaseTagPattern(project)
|
|
@@ -579,7 +656,7 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
579
656
|
lines: stringUtils.dedent(
|
|
580
657
|
// ============================================================================
|
|
581
658
|
/*js*/`
|
|
582
|
-
#!/usr/bin/env
|
|
659
|
+
#!/usr/bin/env bun
|
|
583
660
|
import { parseArgs } from "node:util";
|
|
584
661
|
|
|
585
662
|
const { values } = parseArgs({
|
|
@@ -600,7 +677,7 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
600
677
|
});
|
|
601
678
|
project.root.addTask("pypiTrustedPublisherInstructions", {
|
|
602
679
|
description: "Print system-browser instructions for PyPI trusted publishers",
|
|
603
|
-
exec: `
|
|
680
|
+
exec: `bun ${helper}`,
|
|
604
681
|
receiveArgs: true,
|
|
605
682
|
});
|
|
606
683
|
}
|
package/src/publish.ts
CHANGED
|
@@ -9,11 +9,10 @@
|
|
|
9
9
|
* (`ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`), so a published package whose
|
|
10
10
|
* entry point is `index.ts` is unloadable by anything that is not a bundler.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* type-checks; it only changes what `pnpm pack` writes.
|
|
12
|
+
* `publishConfig` records the compiled `main`/`types`/`bin`/`exports` tree while
|
|
13
|
+
* the workspace keeps its source entry points. The release packer applies that
|
|
14
|
+
* record inside each archive because Bun does not perform npm's publishConfig
|
|
15
|
+
* substitution itself. The checkout remains source-first.
|
|
17
16
|
*
|
|
18
17
|
* Two compiler options make the emitted tree loadable, both native `tsc`:
|
|
19
18
|
*
|
|
@@ -56,14 +55,18 @@ export const COMPILED_COMPILER_OPTIONS: javascript.TypeScriptCompilerOptions = {
|
|
|
56
55
|
* Everything does EXCEPT the `ui` tag, and the exclusion is about consumers
|
|
57
56
|
* rather than convenience. The problem being solved is that Node cannot load raw
|
|
58
57
|
* TypeScript - but a browser package is never loaded by Node. UI packages reach
|
|
59
|
-
* their consumer through
|
|
60
|
-
* `./styles.css` plus raw SVG assets that `tsc` does not copy and could
|
|
61
|
-
* rewrite. Compiling them would
|
|
62
|
-
*
|
|
58
|
+
* their consumer through a browser bundler, which reads their source, and they
|
|
59
|
+
* export `./styles.css` plus raw SVG assets that `tsc` does not copy and could
|
|
60
|
+
* not rewrite. Compiling them would require an asset-aware library pipeline to
|
|
61
|
+
* solve a problem they do not have.
|
|
63
62
|
*/
|
|
64
63
|
export function publishesCompiled(pkg: javascript.NodeProject): boolean {
|
|
65
64
|
if (!(pkg instanceof typescript.TypeScriptProject) || !pkg.parent) return false;
|
|
66
|
-
return
|
|
65
|
+
return (
|
|
66
|
+
isDBXToolsJavaScriptProject()(pkg) &&
|
|
67
|
+
pkg.dbxToolsConfig.publishable &&
|
|
68
|
+
!pkg.dbxToolsConfig.tags.includes("ui")
|
|
69
|
+
);
|
|
67
70
|
}
|
|
68
71
|
|
|
69
72
|
/** The `./lib/...` stem of a source path, or `undefined` if it is not TypeScript. */
|
|
@@ -8,6 +8,7 @@ import { parse } from "smol-toml";
|
|
|
8
8
|
import { runTaskCommand } from "./_task-command.ts";
|
|
9
9
|
import { resolveRepoRoot, workspaceDependencyDirectories } from "./packages.ts";
|
|
10
10
|
|
|
11
|
+
/** Resolved function replacement consumed while generating one PythonMonkey bundle. */
|
|
11
12
|
export interface ResolvedPythonNodeFunctionOverride {
|
|
12
13
|
readonly handlerExport: string;
|
|
13
14
|
readonly handlerFile: string;
|
|
@@ -15,6 +16,7 @@ export interface ResolvedPythonNodeFunctionOverride {
|
|
|
15
16
|
readonly targetModule: string;
|
|
16
17
|
}
|
|
17
18
|
|
|
19
|
+
/** Fully resolved pyproject binding configuration consumed by the runtime generator. */
|
|
18
20
|
export interface ResolvedPythonNodeBindings {
|
|
19
21
|
readonly bindingDirectory: string;
|
|
20
22
|
readonly bindingName: string;
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Projen task wiring for pyproject-driven PythonMonkey runtime generation.
|
|
3
|
+
*
|
|
4
|
+
* Type extraction and bundle emission remain in the executable generator; this
|
|
5
|
+
* component owns only typed configuration and generate/check task registration.
|
|
6
|
+
*/
|
|
1
7
|
import { Component, type Project, type Task } from "projen";
|
|
2
8
|
import { taskCommand } from "./project-js.ts";
|
|
3
9
|
|
package/src/release-options.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* @module
|
|
9
9
|
*/
|
|
10
10
|
import { json } from "@dbx-tools/shared-core";
|
|
11
|
+
import { z } from "zod";
|
|
11
12
|
|
|
12
13
|
/** Publication scopes accepted by the release task; auto preserves normal publication. */
|
|
13
14
|
export const RELEASE_PUBLISH_TARGETS = ["auto", "npm", "pypi", "local", "none"] as const;
|
|
@@ -22,12 +23,19 @@ export const RELEASE_INSTALL_MODES = ["auto", "always", "never"] as const;
|
|
|
22
23
|
export type ReleaseInstallMode = (typeof RELEASE_INSTALL_MODES)[number];
|
|
23
24
|
|
|
24
25
|
/** CI steps selected by an immutable release annotation. */
|
|
25
|
-
export
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
export const ReleaseStepSelectionSchema = z
|
|
27
|
+
.object({
|
|
28
|
+
npm: z.boolean().describe("Build and publish npm artifacts."),
|
|
29
|
+
pypi: z.boolean().describe("Build and publish Python artifacts."),
|
|
30
|
+
docs: z.boolean().describe("Build and deploy documentation."),
|
|
31
|
+
validation: z.boolean().describe("Run repository validation tasks."),
|
|
32
|
+
})
|
|
33
|
+
.strict()
|
|
34
|
+
.readonly()
|
|
35
|
+
.describe("Immutable CI steps recorded in an annotated release tag.");
|
|
36
|
+
|
|
37
|
+
/** CI steps selected by an immutable release annotation. */
|
|
38
|
+
export type ReleaseStepSelection = z.infer<typeof ReleaseStepSelectionSchema>;
|
|
31
39
|
|
|
32
40
|
/** Flags consumed by the shared release-selection policy. */
|
|
33
41
|
export interface ReleaseSelectionOptions {
|
|
@@ -63,17 +71,9 @@ export function parseReleaseTagAnnotation(annotation: string): ReleaseStepSelect
|
|
|
63
71
|
if (selections.length === 0) return releaseStepSelection();
|
|
64
72
|
if (selections.length !== 1) throw new Error("Release tag contains multiple step selections");
|
|
65
73
|
const value = json.parseRecord(selections[0]!.slice(ANNOTATION_MARKER.length));
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
typeof value.npm !== "boolean" ||
|
|
70
|
-
typeof value.pypi !== "boolean" ||
|
|
71
|
-
typeof value.docs !== "boolean" ||
|
|
72
|
-
typeof value.validation !== "boolean"
|
|
73
|
-
) {
|
|
74
|
-
throw new Error("Invalid release tag step selection");
|
|
75
|
-
}
|
|
76
|
-
return { npm: value.npm, pypi: value.pypi, docs: value.docs, validation: value.validation };
|
|
74
|
+
const parsed = ReleaseStepSelectionSchema.safeParse(value);
|
|
75
|
+
if (!parsed.success) throw new Error("Invalid release tag step selection");
|
|
76
|
+
return parsed.data;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
/** Whether the selected release should inspect and publish configured local registries. */
|
package/src/release.ts
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Unified annotated-tag release workflow generation.
|
|
3
|
+
*
|
|
4
|
+
* Projen's native `Release` owns a different lifecycle: it calculates versions
|
|
5
|
+
* from commits, generates changelogs and GitHub Releases, and publishes Publib's
|
|
6
|
+
* ecosystem-specific artifact tree from branch workflows. This repository
|
|
7
|
+
* instead creates one reviewed `VERSION` commit and annotated `v*` tag locally,
|
|
8
|
+
* requires that tag to equal `origin/main`, omits GitHub Releases, and builds
|
|
9
|
+
* npm, Python, and documentation artifacts once before isolated publication
|
|
10
|
+
* jobs download them. `DBXToolsRelease` therefore remains separate while native
|
|
11
|
+
* workflow and project primitives own the YAML model and setup steps.
|
|
12
|
+
*/
|
|
2
13
|
import { stringUtils } from "@dbx-tools/shared-core";
|
|
3
14
|
import { Component, github } from "projen";
|
|
4
15
|
import { GithubWorkflow } from "projen/lib/github";
|
|
@@ -40,6 +51,8 @@ export interface DBXToolsReleaseOptions {
|
|
|
40
51
|
readonly validationTasks?: readonly string[];
|
|
41
52
|
/** Repository prerequisites installed before release validation and artifact builds. */
|
|
42
53
|
readonly setupSteps?: readonly JobStep[];
|
|
54
|
+
/** Repository-specific synthesis commands run before the root Projen synthesis check. */
|
|
55
|
+
readonly synthesisCommands?: readonly string[];
|
|
43
56
|
}
|
|
44
57
|
|
|
45
58
|
/** Locate the unified workflow when release generation is enabled. */
|
|
@@ -76,12 +89,12 @@ export function nodeReleaseSetupSteps(): readonly JobStep[] {
|
|
|
76
89
|
return [
|
|
77
90
|
{
|
|
78
91
|
name: "Setup Bun",
|
|
79
|
-
uses:
|
|
92
|
+
uses: github.ActionRefs.OVEN_SH_SETUP_BUN,
|
|
80
93
|
with: { "bun-version": BUN_VERSION },
|
|
81
94
|
},
|
|
82
95
|
{
|
|
83
96
|
name: "Setup Node.js",
|
|
84
|
-
uses:
|
|
97
|
+
uses: github.ActionRefs.ACTIONS_SETUP_NODE,
|
|
85
98
|
with: {
|
|
86
99
|
"node-version": NODE_VERSION,
|
|
87
100
|
"registry-url": NPM_REGISTRY_URL,
|
|
@@ -140,7 +153,6 @@ function releaseBuildJob(
|
|
|
140
153
|
permissions: { contents: JobPermission.READ },
|
|
141
154
|
timeoutMinutes: 60,
|
|
142
155
|
env: {
|
|
143
|
-
BUN_VERSION,
|
|
144
156
|
CI: "true",
|
|
145
157
|
...(options.docs
|
|
146
158
|
? { DOCS_SITE_URL: options.docs.siteUrl, DOCS_BASE: options.docs.base ?? "/" }
|
|
@@ -162,7 +174,7 @@ function releaseBuildJob(
|
|
|
162
174
|
fetchDepth: 0,
|
|
163
175
|
},
|
|
164
176
|
}),
|
|
165
|
-
...project.renderWorkflowSetup({ mutable:
|
|
177
|
+
...project.renderWorkflowSetup({ mutable: true }),
|
|
166
178
|
{
|
|
167
179
|
name: "Verify release context",
|
|
168
180
|
id: "release",
|
|
@@ -192,6 +204,15 @@ function releaseBuildJob(
|
|
|
192
204
|
// ============================================================================
|
|
193
205
|
),
|
|
194
206
|
},
|
|
207
|
+
{
|
|
208
|
+
name: "Verify generated sources",
|
|
209
|
+
run: [
|
|
210
|
+
...(options.synthesisCommands ?? []),
|
|
211
|
+
"bunx projen",
|
|
212
|
+
"git diff --ignore-space-at-eol --exit-code",
|
|
213
|
+
].join("\n"),
|
|
214
|
+
},
|
|
215
|
+
{ name: "Verify workspace versions", run: "bun run version:check" },
|
|
195
216
|
...(options.setupSteps ?? []),
|
|
196
217
|
...releaseBuildSteps(
|
|
197
218
|
"validation",
|
|
@@ -214,7 +235,7 @@ function releaseBuildJob(
|
|
|
214
235
|
},
|
|
215
236
|
{
|
|
216
237
|
name: "Upload npm archives",
|
|
217
|
-
uses:
|
|
238
|
+
uses: github.ActionRefs.ACTIONS_UPLOAD_ARTIFACT,
|
|
218
239
|
with: { name: "release-npm", path: ".release/npm", "if-no-files-found": "error" },
|
|
219
240
|
},
|
|
220
241
|
])),
|
|
@@ -243,11 +264,11 @@ function nodePublishJob(): Job {
|
|
|
243
264
|
runsOn: ["ubuntu-latest"],
|
|
244
265
|
permissions: { contents: JobPermission.READ, idToken: JobPermission.WRITE },
|
|
245
266
|
timeoutMinutes: 30,
|
|
246
|
-
env: {
|
|
267
|
+
env: { CI: "true" },
|
|
247
268
|
steps: [
|
|
248
269
|
{
|
|
249
270
|
name: "Download npm archives",
|
|
250
|
-
uses:
|
|
271
|
+
uses: github.ActionRefs.ACTIONS_DOWNLOAD_ARTIFACT,
|
|
251
272
|
with: { name: "release-npm", path: ".release/npm" },
|
|
252
273
|
},
|
|
253
274
|
...nodeReleaseSetupSteps(),
|
|
@@ -295,7 +316,7 @@ export class DBXToolsRelease extends Component {
|
|
|
295
316
|
super(project);
|
|
296
317
|
project.addGitIgnore(".release/");
|
|
297
318
|
const installCondition =
|
|
298
|
-
"
|
|
319
|
+
"bun -e \"process.exit(process.env.DBX_TOOLS_RELEASE_INSTALL === 'never' ? 1 : 0)\"";
|
|
299
320
|
project.package.installTask.addCondition(installCondition);
|
|
300
321
|
project.package.installCiTask.addCondition(installCondition);
|
|
301
322
|
const tagPrefix = options.tagPrefix ?? "v";
|
package/src/scaffold.ts
CHANGED
|
@@ -9,11 +9,9 @@ import { repoRoot } from "./packages.ts";
|
|
|
9
9
|
* Re-run projen synth by executing `.projenrc.ts` with the CURRENT runtime (no
|
|
10
10
|
* projen network re-exec).
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* `sync` once the repo moved onto bun - `process.execPath` is bun here, so the
|
|
16
|
-
* flag was aimed at the one runtime that cannot take it.
|
|
12
|
+
* dbx-tools tasks are Bun entrypoints, so the current runtime executes the
|
|
13
|
+
* TypeScript definition directly. A Node fallback would require an additional
|
|
14
|
+
* loader that no generated task or supported workflow uses.
|
|
17
15
|
*
|
|
18
16
|
* `post: true` runs the full flow - projen's post-synth `bun install` AND the
|
|
19
17
|
* post-synth barrels component - which is what the one-shot `sync` task
|
|
@@ -33,6 +31,5 @@ export function runSynth(options: { post?: boolean } = {}): void {
|
|
|
33
31
|
if (options.post) delete env.PROJEN_DISABLE_POST;
|
|
34
32
|
else env.PROJEN_DISABLE_POST = "true";
|
|
35
33
|
const projenrc = join(repoRoot, ".projenrc.ts");
|
|
36
|
-
|
|
37
|
-
runTaskCommand(repoRoot, process.execPath, args, { env });
|
|
34
|
+
runTaskCommand(repoRoot, process.execPath, [projenrc], { env });
|
|
38
35
|
}
|
package/src/tags.ts
CHANGED
|
@@ -34,7 +34,7 @@ const NODE_COMPILER_OPTIONS: javascript.TypeScriptCompilerOptions = {
|
|
|
34
34
|
types: ["node"],
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
-
/**
|
|
37
|
+
/** DOM-capable libraries shared by browser packages. */
|
|
38
38
|
const DOM_LIB = ["ES2022", "DOM", "DOM.Iterable"];
|
|
39
39
|
|
|
40
40
|
function removeNodeTypes(project: javascript.NodeProject): void {
|
|
@@ -144,7 +144,7 @@ export const PACKAGE_TAG_MIXINS = {
|
|
|
144
144
|
// register and no launcher shim to generate. In a WORKSPACE checkout the
|
|
145
145
|
// manifest still points at the `.ts` entry, which Node type-strips on its own
|
|
146
146
|
// because the package is not under `node_modules`.
|
|
147
|
-
p.addDeps("commander@catalog:");
|
|
147
|
+
p.addDeps("commander@catalog:", "zod@catalog:");
|
|
148
148
|
p.addDevDeps("@types/node@catalog:");
|
|
149
149
|
// A CLI compiles code OUTSIDE `src/` - its root `index.ts` barrel and the
|
|
150
150
|
// `bin/` entries - which the src-only tag default doesn't reach.
|
|
@@ -163,15 +163,11 @@ export const PACKAGE_TAG_MIXINS = {
|
|
|
163
163
|
addPackageFiles(p, "bin");
|
|
164
164
|
}),
|
|
165
165
|
server: create(projectPredicate.hasTag("server"), (p) => {
|
|
166
|
-
// A Node/Express service.
|
|
167
|
-
//
|
|
168
|
-
|
|
169
|
-
p.addDeps("express@catalog:", "tsoa@catalog:");
|
|
166
|
+
// A Node/Express service. `dev` and `start` run `src/server.ts` directly
|
|
167
|
+
// through Bun, while package compilation remains native TypeScript.
|
|
168
|
+
p.addDeps("express@catalog:");
|
|
170
169
|
p.addDevDeps("@types/node@catalog:", "@types/express@catalog:");
|
|
171
|
-
applyCompilerOptions(p,
|
|
172
|
-
...NODE_COMPILER_OPTIONS,
|
|
173
|
-
experimentalDecorators: true,
|
|
174
|
-
});
|
|
170
|
+
applyCompilerOptions(p, NODE_COMPILER_OPTIONS);
|
|
175
171
|
// bun runs the server `.ts` directly (native TS, no tsx). `--watch` restarts
|
|
176
172
|
// on change - the tsx-watch replacement.
|
|
177
173
|
(p.tasks.tryFind("dev") ?? p.addTask("dev")).reset("bun --watch src/server.ts");
|
|
@@ -185,12 +181,6 @@ export const PACKAGE_TAG_MIXINS = {
|
|
|
185
181
|
removeNodeTypes(p);
|
|
186
182
|
applyCompilerOptions(p, AGNOSTIC_COMPILER_OPTIONS);
|
|
187
183
|
}),
|
|
188
|
-
openapi: create(projectPredicate.hasTag("openapi"), (p) => {
|
|
189
|
-
removeNodeTypes(p);
|
|
190
|
-
p.addDeps("openapi-fetch@catalog:");
|
|
191
|
-
applyCompilerOptions(p, { target: "ES2022", lib: [...DOM_LIB], types: [] });
|
|
192
|
-
addPackageFiles(p, "openapi.json");
|
|
193
|
-
}),
|
|
194
184
|
} satisfies Record<string, ConstructsMixin>;
|
|
195
185
|
|
|
196
186
|
/** A known tag name (a key of {@link PACKAGE_TAG_MIXINS}). */
|
package/src/vscode.ts
CHANGED
|
@@ -33,7 +33,7 @@ export function configureVsCode(scope: javascript.NodeProject): void {
|
|
|
33
33
|
{
|
|
34
34
|
label: "sync",
|
|
35
35
|
detail:
|
|
36
|
-
"projen sync --watch - projenrc (.projenrc.ts + syncResynthPaths re-synth) +
|
|
36
|
+
"projen sync --watch - projenrc (.projenrc.ts + syncResynthPaths re-synth) + barrel watcher",
|
|
37
37
|
type: "shell",
|
|
38
38
|
command: "bun run sync -- --watch",
|
|
39
39
|
isBackground: true,
|