@dbx-tools/projen 0.9.40 → 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 +58 -67
- package/index.ts +6 -8
- 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 +2 -170
- package/src/codegen.ts +23 -20
- 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 -196
- package/src/project-predicate.ts +7 -3
- package/src/project-py.ts +127 -37
- 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 +31 -14
- 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 +107 -26
- package/tasks/lib/python-registry.ts +4 -0
- package/tasks/lib/python-release.ts +2 -0
- package/tasks/lib/release.ts +2 -2
- 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,13 +81,15 @@ 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[];
|
|
89
89
|
readonly lintPaths?: readonly string[];
|
|
90
|
+
/** Repository-relative paths Ruff must not lint or format. */
|
|
91
|
+
readonly ruffExcludes?: readonly string[];
|
|
90
92
|
readonly ruffPerFileIgnores?: Readonly<Record<string, readonly string[]>>;
|
|
91
|
-
/** Generated Python implementation files Pyrefly should resolve but not type-check. */
|
|
92
|
-
readonly pyreflyProjectExcludes?: readonly string[];
|
|
93
93
|
readonly interpreterPath?: string | false;
|
|
94
94
|
readonly release?: boolean | PythonReleaseOptions;
|
|
95
95
|
}
|
|
@@ -100,6 +100,72 @@ const DEFAULT_DEV_DEPENDENCIES = [
|
|
|
100
100
|
"pyyaml>=6.0,<7",
|
|
101
101
|
"ruff>=0.12,<1",
|
|
102
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
|
+
}
|
|
103
169
|
|
|
104
170
|
/** Repository-relative path for a Python package directory. */
|
|
105
171
|
export function pythonPackagePath(repository: PythonRepositoryOptions, directory: string): string {
|
|
@@ -129,7 +195,7 @@ export class DBXToolsPythonProject extends python.PythonProject implements DBXTo
|
|
|
129
195
|
authorEmail: "",
|
|
130
196
|
version: options.version,
|
|
131
197
|
description: pkg.description,
|
|
132
|
-
license:
|
|
198
|
+
license: LICENSE,
|
|
133
199
|
github: false,
|
|
134
200
|
sample: false,
|
|
135
201
|
pytest: false,
|
|
@@ -169,7 +235,7 @@ export class DBXToolsPythonProject extends python.PythonProject implements DBXTo
|
|
|
169
235
|
},
|
|
170
236
|
},
|
|
171
237
|
});
|
|
172
|
-
new License(this, { spdx:
|
|
238
|
+
new License(this, { spdx: LICENSE });
|
|
173
239
|
this.packageOptions = pkg;
|
|
174
240
|
if (!(this.packagingManager instanceof python.Uv)) {
|
|
175
241
|
throw new Error(`Expected uv packaging for ${pkg.name}`);
|
|
@@ -200,12 +266,19 @@ export class DBXToolsPythonProject extends python.PythonProject implements DBXTo
|
|
|
200
266
|
/**
|
|
201
267
|
* Generates a root uv workspace, projen-native Python member projects, Python
|
|
202
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.
|
|
203
274
|
*/
|
|
204
275
|
export class DBXToolsPythonWorkspace extends Component {
|
|
205
276
|
readonly packages: readonly DBXToolsPythonProject[];
|
|
206
277
|
readonly repository: Required<PythonRepositoryOptions>;
|
|
207
278
|
readonly requiresPython: string;
|
|
208
279
|
readonly version: string;
|
|
280
|
+
/** Exact Python version used by the shared release build. */
|
|
281
|
+
readonly workflowPythonVersion: string;
|
|
209
282
|
readonly file: python.PyprojectTomlFile;
|
|
210
283
|
|
|
211
284
|
constructor(project: javascript.NodeProject, options: DBXToolsPythonWorkspaceOptions) {
|
|
@@ -248,6 +321,9 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
248
321
|
}));
|
|
249
322
|
const resolvedOptions = { ...options, packages };
|
|
250
323
|
this.requiresPython = options.requiresPython ?? ">=3.10";
|
|
324
|
+
this.workflowPythonVersion =
|
|
325
|
+
options.workflowPythonVersion ??
|
|
326
|
+
pythonVersionFromRuffTarget(options.ruffTarget ?? DEFAULT_RUFF_TARGET);
|
|
251
327
|
this.version = readWorkspaceVersion(project.outdir);
|
|
252
328
|
this.file = this.emitWorkspace(project, resolvedOptions, scope);
|
|
253
329
|
this.packages = packages.map(
|
|
@@ -283,10 +359,11 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
283
359
|
`${this.repository.root}/**/dist/`,
|
|
284
360
|
);
|
|
285
361
|
this.addTasks(project, resolvedOptions);
|
|
362
|
+
this.addBuildWorkflowSetup(project);
|
|
286
363
|
|
|
287
364
|
const configuredReleaseOptions = options.release === true ? {} : options.release || {};
|
|
288
365
|
const releaseOptions = { ...configuredReleaseOptions };
|
|
289
|
-
this.addTrustedPublisherInstructionsTask(project
|
|
366
|
+
this.addTrustedPublisherInstructionsTask(project);
|
|
290
367
|
|
|
291
368
|
const interpreterPath = options.interpreterPath ?? "${workspaceFolder}/.venv/bin/python";
|
|
292
369
|
if (interpreterPath !== false) {
|
|
@@ -336,7 +413,7 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
336
413
|
},
|
|
337
414
|
},
|
|
338
415
|
ruff: {
|
|
339
|
-
"target-version": options.ruffTarget ??
|
|
416
|
+
"target-version": options.ruffTarget ?? DEFAULT_RUFF_TARGET,
|
|
340
417
|
"line-length": 100,
|
|
341
418
|
lint: {
|
|
342
419
|
"per-file-ignores": perFileIgnores,
|
|
@@ -347,17 +424,16 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
347
424
|
if (options.indexStrategy) {
|
|
348
425
|
file.addOverride("tool.uv.index-strategy", options.indexStrategy);
|
|
349
426
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
...(options.pyreflyProjectExcludes ?? []),
|
|
427
|
+
const ruffExcludes = [
|
|
428
|
+
...(options.ruffExcludes ?? []),
|
|
353
429
|
...options.packages.flatMap((pkg) =>
|
|
354
|
-
|
|
430
|
+
pythonNodeGeneratedSources(pkg).map(
|
|
355
431
|
(source) => `${this.repository.root}/${pkg.directory}/${source}`,
|
|
356
432
|
),
|
|
357
433
|
),
|
|
358
434
|
];
|
|
359
|
-
if (
|
|
360
|
-
file.addOverride("tool.
|
|
435
|
+
if (ruffExcludes.length) {
|
|
436
|
+
file.addOverride("tool.ruff.exclude", [...new Set(ruffExcludes)]);
|
|
361
437
|
}
|
|
362
438
|
file.addOverride(
|
|
363
439
|
"tool.uv.sources",
|
|
@@ -377,10 +453,11 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
377
453
|
exec: "uv run pytest",
|
|
378
454
|
description: "Run Python workspace tests",
|
|
379
455
|
});
|
|
380
|
-
project.addTask("py:lint", {
|
|
456
|
+
const lint = project.addTask("py:lint", {
|
|
381
457
|
exec: `uv run ruff check ${lintPaths.join(" ")}`,
|
|
382
458
|
description: "Lint Python workspace packages",
|
|
383
459
|
});
|
|
460
|
+
lint.exec(`uv run ruff format --check ${lintPaths.join(" ")}`);
|
|
384
461
|
project.addTask("py:format", {
|
|
385
462
|
exec: `uv run ruff format ${lintPaths.join(" ")}`,
|
|
386
463
|
description: "Format Python workspace packages",
|
|
@@ -391,19 +468,37 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
391
468
|
});
|
|
392
469
|
}
|
|
393
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
|
+
|
|
394
489
|
private addReleaseWorkflow(project: javascript.NodeProject, options: PythonReleaseOptions): void {
|
|
395
490
|
if (!project.github || !isDBXToolsJavaScriptProject()(project)) return;
|
|
396
|
-
const publications = this.publications(
|
|
491
|
+
const publications = this.publications();
|
|
397
492
|
const allPublications = publications;
|
|
398
493
|
if (allPublications.length === 0) return;
|
|
399
494
|
const workflow = tryReleaseWorkflow(project);
|
|
400
495
|
if (!workflow) {
|
|
401
496
|
throw new Error("Python release requires the root dbx-tools release mode");
|
|
402
497
|
}
|
|
403
|
-
const
|
|
404
|
-
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);
|
|
405
501
|
const pythonSteps: JobStep[] = [
|
|
406
|
-
uvSetupStep(),
|
|
407
502
|
{
|
|
408
503
|
name: "Build Python distributions",
|
|
409
504
|
env: { RELEASE_VERSION: "${{ steps.release.outputs.release_version }}" },
|
|
@@ -416,7 +511,7 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
416
511
|
},
|
|
417
512
|
...allPublications.map((publication) => ({
|
|
418
513
|
name: `Upload ${publication.distribution} distributions`,
|
|
419
|
-
uses:
|
|
514
|
+
uses: github.ActionRefs.ACTIONS_UPLOAD_ARTIFACT,
|
|
420
515
|
with: {
|
|
421
516
|
name: `release-python-${publication.directory}`,
|
|
422
517
|
path: `.release/python/${publication.directory}`,
|
|
@@ -451,7 +546,7 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
451
546
|
steps: [
|
|
452
547
|
{
|
|
453
548
|
name: `Download ${publication.distribution} distributions`,
|
|
454
|
-
uses:
|
|
549
|
+
uses: github.ActionRefs.ACTIONS_DOWNLOAD_ARTIFACT,
|
|
455
550
|
with: {
|
|
456
551
|
name: `release-python-${publication.directory}`,
|
|
457
552
|
path: `dist/${publication.directory}`,
|
|
@@ -462,7 +557,6 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
462
557
|
uses: "pypa/gh-action-pypi-publish@release/v1",
|
|
463
558
|
with: {
|
|
464
559
|
"packages-dir": `dist/${publication.directory}`,
|
|
465
|
-
"skip-existing": true,
|
|
466
560
|
},
|
|
467
561
|
},
|
|
468
562
|
],
|
|
@@ -471,22 +565,18 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
471
565
|
refreshReleaseDocsDependencies(project);
|
|
472
566
|
}
|
|
473
567
|
|
|
474
|
-
private publications(
|
|
568
|
+
private publications(): readonly PythonPublication[] {
|
|
475
569
|
return this.packages.map((pkg) => ({
|
|
476
570
|
directory: pkg.packageOptions.directory,
|
|
477
571
|
distribution: pkg.packageOptions.name,
|
|
478
|
-
environment:
|
|
479
|
-
options.environments?.[pkg.packageOptions.name] ?? `pypi-${pkg.packageOptions.name}`,
|
|
572
|
+
environment: pkg.packageOptions.releaseEnvironment ?? `pypi-${pkg.packageOptions.name}`,
|
|
480
573
|
dependencies: pkg.packageOptions.internalDependencies,
|
|
481
574
|
}));
|
|
482
575
|
}
|
|
483
576
|
|
|
484
|
-
private addTrustedPublisherInstructionsTask(
|
|
485
|
-
project: javascript.NodeProject,
|
|
486
|
-
options: PythonReleaseOptions,
|
|
487
|
-
): void {
|
|
577
|
+
private addTrustedPublisherInstructionsTask(project: javascript.NodeProject): void {
|
|
488
578
|
const repository = this.githubRepository();
|
|
489
|
-
const publications = this.publications(
|
|
579
|
+
const publications = this.publications();
|
|
490
580
|
const releaseTag =
|
|
491
581
|
isDBXToolsJavaScriptProject()(project) && tryReleaseWorkflow(project)
|
|
492
582
|
? releaseTagPattern(project)
|
|
@@ -566,7 +656,7 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
566
656
|
lines: stringUtils.dedent(
|
|
567
657
|
// ============================================================================
|
|
568
658
|
/*js*/`
|
|
569
|
-
#!/usr/bin/env
|
|
659
|
+
#!/usr/bin/env bun
|
|
570
660
|
import { parseArgs } from "node:util";
|
|
571
661
|
|
|
572
662
|
const { values } = parseArgs({
|
|
@@ -587,7 +677,7 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
587
677
|
});
|
|
588
678
|
project.root.addTask("pypiTrustedPublisherInstructions", {
|
|
589
679
|
description: "Print system-browser instructions for PyPI trusted publishers",
|
|
590
|
-
exec: `
|
|
680
|
+
exec: `bun ${helper}`,
|
|
591
681
|
receiveArgs: true,
|
|
592
682
|
});
|
|
593
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,
|
|
@@ -92,15 +105,11 @@ export function nodeReleaseSetupSteps(): readonly JobStep[] {
|
|
|
92
105
|
];
|
|
93
106
|
}
|
|
94
107
|
|
|
95
|
-
/** Install uv
|
|
108
|
+
/** Install uv for Python release builds. */
|
|
96
109
|
export function uvSetupStep(): JobStep {
|
|
97
110
|
return {
|
|
98
111
|
name: "Setup uv",
|
|
99
112
|
uses: "astral-sh/setup-uv@v7",
|
|
100
|
-
with: {
|
|
101
|
-
"enable-cache": true,
|
|
102
|
-
"cache-dependency-glob": "**/pyproject.toml",
|
|
103
|
-
},
|
|
104
113
|
};
|
|
105
114
|
}
|
|
106
115
|
|
|
@@ -144,7 +153,6 @@ function releaseBuildJob(
|
|
|
144
153
|
permissions: { contents: JobPermission.READ },
|
|
145
154
|
timeoutMinutes: 60,
|
|
146
155
|
env: {
|
|
147
|
-
BUN_VERSION,
|
|
148
156
|
CI: "true",
|
|
149
157
|
...(options.docs
|
|
150
158
|
? { DOCS_SITE_URL: options.docs.siteUrl, DOCS_BASE: options.docs.base ?? "/" }
|
|
@@ -166,7 +174,7 @@ function releaseBuildJob(
|
|
|
166
174
|
fetchDepth: 0,
|
|
167
175
|
},
|
|
168
176
|
}),
|
|
169
|
-
...project.renderWorkflowSetup({ mutable:
|
|
177
|
+
...project.renderWorkflowSetup({ mutable: true }),
|
|
170
178
|
{
|
|
171
179
|
name: "Verify release context",
|
|
172
180
|
id: "release",
|
|
@@ -196,6 +204,15 @@ function releaseBuildJob(
|
|
|
196
204
|
// ============================================================================
|
|
197
205
|
),
|
|
198
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" },
|
|
199
216
|
...(options.setupSteps ?? []),
|
|
200
217
|
...releaseBuildSteps(
|
|
201
218
|
"validation",
|
|
@@ -218,7 +235,7 @@ function releaseBuildJob(
|
|
|
218
235
|
},
|
|
219
236
|
{
|
|
220
237
|
name: "Upload npm archives",
|
|
221
|
-
uses:
|
|
238
|
+
uses: github.ActionRefs.ACTIONS_UPLOAD_ARTIFACT,
|
|
222
239
|
with: { name: "release-npm", path: ".release/npm", "if-no-files-found": "error" },
|
|
223
240
|
},
|
|
224
241
|
])),
|
|
@@ -247,11 +264,11 @@ function nodePublishJob(): Job {
|
|
|
247
264
|
runsOn: ["ubuntu-latest"],
|
|
248
265
|
permissions: { contents: JobPermission.READ, idToken: JobPermission.WRITE },
|
|
249
266
|
timeoutMinutes: 30,
|
|
250
|
-
env: {
|
|
267
|
+
env: { CI: "true" },
|
|
251
268
|
steps: [
|
|
252
269
|
{
|
|
253
270
|
name: "Download npm archives",
|
|
254
|
-
uses:
|
|
271
|
+
uses: github.ActionRefs.ACTIONS_DOWNLOAD_ARTIFACT,
|
|
255
272
|
with: { name: "release-npm", path: ".release/npm" },
|
|
256
273
|
},
|
|
257
274
|
...nodeReleaseSetupSteps(),
|
|
@@ -299,7 +316,7 @@ export class DBXToolsRelease extends Component {
|
|
|
299
316
|
super(project);
|
|
300
317
|
project.addGitIgnore(".release/");
|
|
301
318
|
const installCondition =
|
|
302
|
-
|
|
319
|
+
"bun -e \"process.exit(process.env.DBX_TOOLS_RELEASE_INSTALL === 'never' ? 1 : 0)\"";
|
|
303
320
|
project.package.installTask.addCondition(installCondition);
|
|
304
321
|
project.package.installCiTask.addCondition(installCondition);
|
|
305
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}). */
|