@dbx-tools/projen 0.9.43 → 0.9.47
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 +122 -44
- 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,15 +81,16 @@ 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
|
+
/** Dependencies appended to the standard development group. */
|
|
87
88
|
readonly devDependencies?: readonly string[];
|
|
88
89
|
readonly testPaths?: readonly string[];
|
|
89
90
|
readonly lintPaths?: readonly string[];
|
|
90
91
|
/** Repository-relative paths Ruff must not lint or format. */
|
|
91
92
|
readonly ruffExcludes?: readonly string[];
|
|
92
93
|
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
94
|
readonly interpreterPath?: string | false;
|
|
96
95
|
readonly release?: boolean | PythonReleaseOptions;
|
|
97
96
|
}
|
|
@@ -102,6 +101,72 @@ const DEFAULT_DEV_DEPENDENCIES = [
|
|
|
102
101
|
"pyyaml>=6.0,<7",
|
|
103
102
|
"ruff>=0.12,<1",
|
|
104
103
|
] as const;
|
|
104
|
+
const DEFAULT_RUFF_TARGET = "py310";
|
|
105
|
+
const RELEASE_SETUP_CONDITION =
|
|
106
|
+
"${{ steps.release.outputs.validation == 'true' || steps.release.outputs.docs == 'true' || steps.release.outputs.pypi == 'true' }}";
|
|
107
|
+
|
|
108
|
+
/** Convert Ruff's `py311` target spelling to the setup-python `3.11` spelling. */
|
|
109
|
+
function pythonVersionFromRuffTarget(target: string): string {
|
|
110
|
+
const match = /^py(\d)(\d{1,2})$/.exec(target);
|
|
111
|
+
if (!match) throw new Error(`Cannot derive a workflow Python version from Ruff target ${target}`);
|
|
112
|
+
return `${match[1]}.${match[2]}`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Python and uv setup steps, optionally gated by a workflow expression. */
|
|
116
|
+
function pythonSetupSteps(pythonVersion: string, condition?: string): JobStep[] {
|
|
117
|
+
return [
|
|
118
|
+
{
|
|
119
|
+
name: "Setup Python",
|
|
120
|
+
uses: github.ActionRefs.ACTIONS_SETUP_PYTHON,
|
|
121
|
+
...(condition ? { if: condition } : {}),
|
|
122
|
+
with: { "python-version": pythonVersion },
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
...uvSetupStep(),
|
|
126
|
+
...(condition ? { if: condition } : {}),
|
|
127
|
+
},
|
|
128
|
+
];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Materialize Projen's lazily rendered native build steps before extending them. */
|
|
132
|
+
function resolvedJobSteps(job: Job): JobStep[] {
|
|
133
|
+
const steps = (job as unknown as { steps: JobStep[] | (() => JobStep[]) }).steps;
|
|
134
|
+
return typeof steps === "function" ? steps() : steps;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function missingSetupSteps(job: Job, setupSteps: readonly JobStep[]): JobStep[] {
|
|
138
|
+
const actionName = (step: JobStep): string | undefined => step.uses?.split("@", 1)[0];
|
|
139
|
+
const existing = new Set(resolvedJobSteps(job).map(actionName));
|
|
140
|
+
return setupSteps.filter((step) => !existing.has(actionName(step)));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Ensure Python and uv are available before validation, docs, and packaging.
|
|
145
|
+
*
|
|
146
|
+
* The Python workspace owns these prerequisites. Existing action steps are
|
|
147
|
+
* reused so repository-specific setup can override a version without causing a
|
|
148
|
+
* duplicate installation.
|
|
149
|
+
*/
|
|
150
|
+
function withPythonBuildSetup(job: Job, pythonVersion: string): Job {
|
|
151
|
+
const setupSteps = missingSetupSteps(
|
|
152
|
+
job,
|
|
153
|
+
pythonSetupSteps(pythonVersion, RELEASE_SETUP_CONDITION),
|
|
154
|
+
);
|
|
155
|
+
if (setupSteps.length === 0) return job;
|
|
156
|
+
const steps = resolvedJobSteps(job);
|
|
157
|
+
const verificationIndex = steps.findIndex((step) => step.name === "Verify release context");
|
|
158
|
+
const validationIndex = steps.findIndex((step) => step.name?.startsWith("Validate "));
|
|
159
|
+
const insertionIndex =
|
|
160
|
+
verificationIndex >= 0
|
|
161
|
+
? verificationIndex + 1
|
|
162
|
+
: validationIndex < 0
|
|
163
|
+
? steps.length
|
|
164
|
+
: validationIndex;
|
|
165
|
+
return {
|
|
166
|
+
...job,
|
|
167
|
+
steps: [...steps.slice(0, insertionIndex), ...setupSteps, ...steps.slice(insertionIndex)],
|
|
168
|
+
};
|
|
169
|
+
}
|
|
105
170
|
|
|
106
171
|
/** Repository-relative path for a Python package directory. */
|
|
107
172
|
export function pythonPackagePath(repository: PythonRepositoryOptions, directory: string): string {
|
|
@@ -131,7 +196,7 @@ export class DBXToolsPythonProject extends python.PythonProject implements DBXTo
|
|
|
131
196
|
authorEmail: "",
|
|
132
197
|
version: options.version,
|
|
133
198
|
description: pkg.description,
|
|
134
|
-
license:
|
|
199
|
+
license: LICENSE,
|
|
135
200
|
github: false,
|
|
136
201
|
sample: false,
|
|
137
202
|
pytest: false,
|
|
@@ -171,7 +236,7 @@ export class DBXToolsPythonProject extends python.PythonProject implements DBXTo
|
|
|
171
236
|
},
|
|
172
237
|
},
|
|
173
238
|
});
|
|
174
|
-
new License(this, { spdx:
|
|
239
|
+
new License(this, { spdx: LICENSE });
|
|
175
240
|
this.packageOptions = pkg;
|
|
176
241
|
if (!(this.packagingManager instanceof python.Uv)) {
|
|
177
242
|
throw new Error(`Expected uv packaging for ${pkg.name}`);
|
|
@@ -202,12 +267,19 @@ export class DBXToolsPythonProject extends python.PythonProject implements DBXTo
|
|
|
202
267
|
/**
|
|
203
268
|
* Generates a root uv workspace, projen-native Python member projects, Python
|
|
204
269
|
* tasks, editor interpreter selection, and an optional publishing workflow.
|
|
270
|
+
*
|
|
271
|
+
* Native `PythonProject` and `PyprojectTomlFile` continue to own each package
|
|
272
|
+
* and TOML rendering. This component owns only the unsupported remainder:
|
|
273
|
+
* attaching several uv members to an existing JavaScript root and adding their
|
|
274
|
+
* dependency-ordered artifacts to the repository's shared tag release.
|
|
205
275
|
*/
|
|
206
276
|
export class DBXToolsPythonWorkspace extends Component {
|
|
207
277
|
readonly packages: readonly DBXToolsPythonProject[];
|
|
208
278
|
readonly repository: Required<PythonRepositoryOptions>;
|
|
209
279
|
readonly requiresPython: string;
|
|
210
280
|
readonly version: string;
|
|
281
|
+
/** Exact Python version used by the shared release build. */
|
|
282
|
+
readonly workflowPythonVersion: string;
|
|
211
283
|
readonly file: python.PyprojectTomlFile;
|
|
212
284
|
|
|
213
285
|
constructor(project: javascript.NodeProject, options: DBXToolsPythonWorkspaceOptions) {
|
|
@@ -250,6 +322,9 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
250
322
|
}));
|
|
251
323
|
const resolvedOptions = { ...options, packages };
|
|
252
324
|
this.requiresPython = options.requiresPython ?? ">=3.10";
|
|
325
|
+
this.workflowPythonVersion =
|
|
326
|
+
options.workflowPythonVersion ??
|
|
327
|
+
pythonVersionFromRuffTarget(options.ruffTarget ?? DEFAULT_RUFF_TARGET);
|
|
253
328
|
this.version = readWorkspaceVersion(project.outdir);
|
|
254
329
|
this.file = this.emitWorkspace(project, resolvedOptions, scope);
|
|
255
330
|
this.packages = packages.map(
|
|
@@ -285,10 +360,11 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
285
360
|
`${this.repository.root}/**/dist/`,
|
|
286
361
|
);
|
|
287
362
|
this.addTasks(project, resolvedOptions);
|
|
363
|
+
this.addBuildWorkflowSetup(project);
|
|
288
364
|
|
|
289
365
|
const configuredReleaseOptions = options.release === true ? {} : options.release || {};
|
|
290
366
|
const releaseOptions = { ...configuredReleaseOptions };
|
|
291
|
-
this.addTrustedPublisherInstructionsTask(project
|
|
367
|
+
this.addTrustedPublisherInstructionsTask(project);
|
|
292
368
|
|
|
293
369
|
const interpreterPath = options.interpreterPath ?? "${workspaceFolder}/.venv/bin/python";
|
|
294
370
|
if (interpreterPath !== false) {
|
|
@@ -322,7 +398,7 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
322
398
|
dependencies: [...(options.dependencies ?? [])],
|
|
323
399
|
},
|
|
324
400
|
dependencyGroups: {
|
|
325
|
-
dev: [...(options.devDependencies ??
|
|
401
|
+
dev: [...DEFAULT_DEV_DEPENDENCIES, ...(options.devDependencies ?? [])],
|
|
326
402
|
},
|
|
327
403
|
tool: {
|
|
328
404
|
uv: python.uvConfig.toJson_UvConfiguration({
|
|
@@ -338,7 +414,7 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
338
414
|
},
|
|
339
415
|
},
|
|
340
416
|
ruff: {
|
|
341
|
-
"target-version": options.ruffTarget ??
|
|
417
|
+
"target-version": options.ruffTarget ?? DEFAULT_RUFF_TARGET,
|
|
342
418
|
"line-length": 100,
|
|
343
419
|
lint: {
|
|
344
420
|
"per-file-ignores": perFileIgnores,
|
|
@@ -360,18 +436,6 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
360
436
|
if (ruffExcludes.length) {
|
|
361
437
|
file.addOverride("tool.ruff.exclude", [...new Set(ruffExcludes)]);
|
|
362
438
|
}
|
|
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
439
|
file.addOverride(
|
|
376
440
|
"tool.uv.sources",
|
|
377
441
|
Object.fromEntries(options.packages.map((pkg) => [pkg.name, { workspace: true }])),
|
|
@@ -390,10 +454,11 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
390
454
|
exec: "uv run pytest",
|
|
391
455
|
description: "Run Python workspace tests",
|
|
392
456
|
});
|
|
393
|
-
project.addTask("py:lint", {
|
|
457
|
+
const lint = project.addTask("py:lint", {
|
|
394
458
|
exec: `uv run ruff check ${lintPaths.join(" ")}`,
|
|
395
459
|
description: "Lint Python workspace packages",
|
|
396
460
|
});
|
|
461
|
+
lint.exec(`uv run ruff format --check ${lintPaths.join(" ")}`);
|
|
397
462
|
project.addTask("py:format", {
|
|
398
463
|
exec: `uv run ruff format ${lintPaths.join(" ")}`,
|
|
399
464
|
description: "Format Python workspace packages",
|
|
@@ -404,19 +469,37 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
404
469
|
});
|
|
405
470
|
}
|
|
406
471
|
|
|
472
|
+
/** Add Python prerequisites to Projen's native build workflow when enabled. */
|
|
473
|
+
private addBuildWorkflowSetup(project: javascript.NodeProject): void {
|
|
474
|
+
const workflow = project.buildWorkflow?.workflow;
|
|
475
|
+
const jobId = project.buildWorkflowJobId;
|
|
476
|
+
if (!workflow || !jobId) return;
|
|
477
|
+
const job = workflow.getJob(jobId) as Job | undefined;
|
|
478
|
+
if (!job) return;
|
|
479
|
+
const setupSteps = missingSetupSteps(job, pythonSetupSteps(this.workflowPythonVersion));
|
|
480
|
+
if (setupSteps.length === 0) return;
|
|
481
|
+
const steps = resolvedJobSteps(job);
|
|
482
|
+
const buildIndex = steps.findIndex((step) => step.name === "build");
|
|
483
|
+
const insertionIndex = buildIndex < 0 ? steps.length : buildIndex;
|
|
484
|
+
workflow.updateJob(jobId, {
|
|
485
|
+
...job,
|
|
486
|
+
steps: [...steps.slice(0, insertionIndex), ...setupSteps, ...steps.slice(insertionIndex)],
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
|
|
407
490
|
private addReleaseWorkflow(project: javascript.NodeProject, options: PythonReleaseOptions): void {
|
|
408
491
|
if (!project.github || !isDBXToolsJavaScriptProject()(project)) return;
|
|
409
|
-
const publications = this.publications(
|
|
492
|
+
const publications = this.publications();
|
|
410
493
|
const allPublications = publications;
|
|
411
494
|
if (allPublications.length === 0) return;
|
|
412
495
|
const workflow = tryReleaseWorkflow(project);
|
|
413
496
|
if (!workflow) {
|
|
414
497
|
throw new Error("Python release requires the root dbx-tools release mode");
|
|
415
498
|
}
|
|
416
|
-
const
|
|
417
|
-
if (!
|
|
499
|
+
const configuredBuild = workflow.getJob("build-release") as Job | undefined;
|
|
500
|
+
if (!configuredBuild) throw new Error("Python release requires the shared release build job");
|
|
501
|
+
const build = withPythonBuildSetup(configuredBuild, this.workflowPythonVersion);
|
|
418
502
|
const pythonSteps: JobStep[] = [
|
|
419
|
-
uvSetupStep(),
|
|
420
503
|
{
|
|
421
504
|
name: "Build Python distributions",
|
|
422
505
|
env: { RELEASE_VERSION: "${{ steps.release.outputs.release_version }}" },
|
|
@@ -429,7 +512,7 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
429
512
|
},
|
|
430
513
|
...allPublications.map((publication) => ({
|
|
431
514
|
name: `Upload ${publication.distribution} distributions`,
|
|
432
|
-
uses:
|
|
515
|
+
uses: github.ActionRefs.ACTIONS_UPLOAD_ARTIFACT,
|
|
433
516
|
with: {
|
|
434
517
|
name: `release-python-${publication.directory}`,
|
|
435
518
|
path: `.release/python/${publication.directory}`,
|
|
@@ -464,7 +547,7 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
464
547
|
steps: [
|
|
465
548
|
{
|
|
466
549
|
name: `Download ${publication.distribution} distributions`,
|
|
467
|
-
uses:
|
|
550
|
+
uses: github.ActionRefs.ACTIONS_DOWNLOAD_ARTIFACT,
|
|
468
551
|
with: {
|
|
469
552
|
name: `release-python-${publication.directory}`,
|
|
470
553
|
path: `dist/${publication.directory}`,
|
|
@@ -475,7 +558,6 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
475
558
|
uses: "pypa/gh-action-pypi-publish@release/v1",
|
|
476
559
|
with: {
|
|
477
560
|
"packages-dir": `dist/${publication.directory}`,
|
|
478
|
-
"skip-existing": true,
|
|
479
561
|
},
|
|
480
562
|
},
|
|
481
563
|
],
|
|
@@ -484,22 +566,18 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
484
566
|
refreshReleaseDocsDependencies(project);
|
|
485
567
|
}
|
|
486
568
|
|
|
487
|
-
private publications(
|
|
569
|
+
private publications(): readonly PythonPublication[] {
|
|
488
570
|
return this.packages.map((pkg) => ({
|
|
489
571
|
directory: pkg.packageOptions.directory,
|
|
490
572
|
distribution: pkg.packageOptions.name,
|
|
491
|
-
environment:
|
|
492
|
-
options.environments?.[pkg.packageOptions.name] ?? `pypi-${pkg.packageOptions.name}`,
|
|
573
|
+
environment: pkg.packageOptions.releaseEnvironment ?? `pypi-${pkg.packageOptions.name}`,
|
|
493
574
|
dependencies: pkg.packageOptions.internalDependencies,
|
|
494
575
|
}));
|
|
495
576
|
}
|
|
496
577
|
|
|
497
|
-
private addTrustedPublisherInstructionsTask(
|
|
498
|
-
project: javascript.NodeProject,
|
|
499
|
-
options: PythonReleaseOptions,
|
|
500
|
-
): void {
|
|
578
|
+
private addTrustedPublisherInstructionsTask(project: javascript.NodeProject): void {
|
|
501
579
|
const repository = this.githubRepository();
|
|
502
|
-
const publications = this.publications(
|
|
580
|
+
const publications = this.publications();
|
|
503
581
|
const releaseTag =
|
|
504
582
|
isDBXToolsJavaScriptProject()(project) && tryReleaseWorkflow(project)
|
|
505
583
|
? releaseTagPattern(project)
|
|
@@ -579,7 +657,7 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
579
657
|
lines: stringUtils.dedent(
|
|
580
658
|
// ============================================================================
|
|
581
659
|
/*js*/`
|
|
582
|
-
#!/usr/bin/env
|
|
660
|
+
#!/usr/bin/env bun
|
|
583
661
|
import { parseArgs } from "node:util";
|
|
584
662
|
|
|
585
663
|
const { values } = parseArgs({
|
|
@@ -600,7 +678,7 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
600
678
|
});
|
|
601
679
|
project.root.addTask("pypiTrustedPublisherInstructions", {
|
|
602
680
|
description: "Print system-browser instructions for PyPI trusted publishers",
|
|
603
|
-
exec: `
|
|
681
|
+
exec: `bun ${helper}`,
|
|
604
682
|
receiveArgs: true,
|
|
605
683
|
});
|
|
606
684
|
}
|
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,
|