@dbx-tools/projen 0.6.145 → 0.6.148
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/index.ts +1 -1
- package/package.json +4 -4
- package/src/project-js.ts +6 -0
- package/src/project-py.ts +212 -19
- package/src/project-rs.ts +205 -114
- package/src/release.ts +60 -5
- package/tasks/uniffi.ts +7 -1
package/index.ts
CHANGED
|
@@ -44,7 +44,7 @@ export type { DBXToolsProjectLanguage, DBXToolsProjectOptions, DBXToolsProject,
|
|
|
44
44
|
export { PackageIdentifier, PROJEN_VERSION, DBXToolsNodeProject, ROOT_INSTALL_ONLY_MIXIN, DBXToolsTypeScriptProject } from "./src/project-js.ts";
|
|
45
45
|
export type { DBXToolsJavaScriptProject, DBXToolsJavaScriptProjectOptions, DBXToolsTypeScriptProjectOptions } from "./src/project-js.ts";
|
|
46
46
|
export { DBXToolsPythonProject, DBXToolsPythonWorkspace } from "./src/project-py.ts";
|
|
47
|
-
export type { PythonRepositoryOptions, PythonPackageOptions, DBXToolsPythonProjectOptions, PythonReleaseOptions, DBXToolsPythonWorkspaceOptions } from "./src/project-py.ts";
|
|
47
|
+
export type { PythonRepositoryOptions, PythonPackageOptions, PythonTrustedPublisherOptions, DBXToolsPythonProjectOptions, PythonReleaseOptions, DBXToolsPythonWorkspaceOptions } from "./src/project-py.ts";
|
|
48
48
|
export { UNIFFI_RELEASE_TARGETS, DBXToolsRustProject, DBXToolsRustWorkspace } from "./src/project-rs.ts";
|
|
49
49
|
export type { CargoDependencyOptions, CargoDependency, RustPackageOptions, DBXToolsRustWorkspaceOptions, UniFFIReleaseTarget, RustBindingMapping, RustWorkspaceMapping } from "./src/project-rs.ts";
|
|
50
50
|
export { COMPILED_DIR, COMPILED_COMPILER_OPTIONS } from "./src/publish.ts";
|
package/package.json
CHANGED
|
@@ -26,9 +26,9 @@
|
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@clack/prompts": "^1.7.0",
|
|
29
|
-
"@dbx-tools/core": "0.6.
|
|
30
|
-
"@dbx-tools/path": "0.6.
|
|
31
|
-
"@dbx-tools/shared-core": "0.6.
|
|
29
|
+
"@dbx-tools/core": "0.6.148",
|
|
30
|
+
"@dbx-tools/path": "0.6.148",
|
|
31
|
+
"@dbx-tools/shared-core": "0.6.148",
|
|
32
32
|
"commander": "^15.0.0",
|
|
33
33
|
"concurrently": "^10.0.3",
|
|
34
34
|
"constructs": "^10.6.0",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
},
|
|
49
49
|
"main": "index.ts",
|
|
50
50
|
"license": "Apache-2.0",
|
|
51
|
-
"version": "0.6.
|
|
51
|
+
"version": "0.6.148",
|
|
52
52
|
"types": "index.ts",
|
|
53
53
|
"type": "module",
|
|
54
54
|
"exports": {
|
package/src/project-js.ts
CHANGED
|
@@ -515,6 +515,10 @@ export interface DBXToolsJavaScriptProjectOptions
|
|
|
515
515
|
* `@dbx-tools/projen` engine in `projen/`, tagged `projen-v*`).
|
|
516
516
|
*/
|
|
517
517
|
readonly standaloneReleases?: readonly StandaloneRelease[];
|
|
518
|
+
/** Workflow that must finish successfully before the main Node release runs. */
|
|
519
|
+
readonly releaseUpstreamWorkflow?: string;
|
|
520
|
+
/** Main Node release workflow name. Defaults to `node-release`. */
|
|
521
|
+
readonly releaseWorkflowName?: string | false;
|
|
518
522
|
/**
|
|
519
523
|
* Extra workspace member paths (repo-relative, POSIX) to list in the workspace
|
|
520
524
|
* config ALONGSIDE the discovered `packageRoots` members - for a package that
|
|
@@ -1183,6 +1187,8 @@ function initProject(
|
|
|
1183
1187
|
new DBXToolsRelease(project as DBXToolsNodeProject, {
|
|
1184
1188
|
tagPrefix: options.releaseTagPrefix,
|
|
1185
1189
|
standaloneReleases: options.standaloneReleases,
|
|
1190
|
+
upstreamWorkflow: options.releaseUpstreamWorkflow,
|
|
1191
|
+
workflowName: options.releaseWorkflowName,
|
|
1186
1192
|
});
|
|
1187
1193
|
}
|
|
1188
1194
|
|
package/src/project-py.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Reusable uv workspace generation for Python packages hosted in a projen tree. */
|
|
2
2
|
import { string } from "@dbx-tools/shared-core";
|
|
3
|
-
import { Component, type Project, javascript, python, vscode } from "projen";
|
|
3
|
+
import { Component, TextFile, type Project, javascript, python, vscode } from "projen";
|
|
4
4
|
import type { IResolver } from "projen/lib/file";
|
|
5
5
|
import { GithubWorkflow } from "projen/lib/github";
|
|
6
6
|
import { JobPermission } from "projen/lib/github/workflows-model";
|
|
@@ -25,6 +25,15 @@ export interface PythonPackageOptions extends DBXToolsProjectOptions {
|
|
|
25
25
|
readonly scripts?: Readonly<Record<string, string>>;
|
|
26
26
|
/** Keep this package unpublished and out of public docs and releases. */
|
|
27
27
|
readonly private?: boolean;
|
|
28
|
+
/** Trusted publisher used outside the standard Python release workflow. */
|
|
29
|
+
readonly trustedPublisher?: PythonTrustedPublisherOptions;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** GitHub Actions publisher for a Python package released by another workflow. */
|
|
33
|
+
export interface PythonTrustedPublisherOptions {
|
|
34
|
+
readonly workflowName: string;
|
|
35
|
+
readonly environment: string;
|
|
36
|
+
readonly artifacts?: string;
|
|
28
37
|
}
|
|
29
38
|
|
|
30
39
|
/** Options for one projen-native Python workspace member. */
|
|
@@ -40,11 +49,21 @@ export interface DBXToolsPythonProjectOptions extends DBXToolsProjectOptions {
|
|
|
40
49
|
/** Python release workflow configuration. */
|
|
41
50
|
export interface PythonReleaseOptions {
|
|
42
51
|
readonly workflowName?: string;
|
|
52
|
+
/** Run after this workflow completes successfully instead of directly on tags. */
|
|
53
|
+
readonly upstreamWorkflow?: string;
|
|
43
54
|
/** GitHub environment by Python distribution name. Defaults to `pypi-<name>`. */
|
|
44
55
|
readonly environments?: Readonly<Record<string, string>>;
|
|
45
56
|
readonly environmentUrl?: string;
|
|
46
57
|
}
|
|
47
58
|
|
|
59
|
+
interface PythonPublication {
|
|
60
|
+
readonly directory: string;
|
|
61
|
+
readonly distribution: string;
|
|
62
|
+
readonly environment: string;
|
|
63
|
+
readonly workflowName: string;
|
|
64
|
+
readonly artifacts?: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
48
67
|
/** Options for {@link DBXToolsPythonWorkspace}. */
|
|
49
68
|
export interface DBXToolsPythonWorkspaceOptions {
|
|
50
69
|
readonly packages: readonly PythonPackageOptions[];
|
|
@@ -231,13 +250,16 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
231
250
|
}
|
|
232
251
|
this.addTasks(project, options);
|
|
233
252
|
|
|
253
|
+
const releaseOptions = options.release === true ? {} : options.release || {};
|
|
254
|
+
this.addTrustedPublisherInstructionsTask(project, releaseOptions);
|
|
255
|
+
|
|
234
256
|
const interpreterPath = options.interpreterPath ?? "${workspaceFolder}/.venv/bin/python";
|
|
235
257
|
if (interpreterPath !== false) {
|
|
236
258
|
projectVscode(project)?.settings.addSetting("python.defaultInterpreterPath", interpreterPath);
|
|
237
259
|
}
|
|
238
260
|
|
|
239
261
|
if (options.release) {
|
|
240
|
-
this.addReleaseWorkflow(project,
|
|
262
|
+
this.addReleaseWorkflow(project, releaseOptions);
|
|
241
263
|
}
|
|
242
264
|
}
|
|
243
265
|
|
|
@@ -331,11 +353,20 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
331
353
|
|
|
332
354
|
private addReleaseWorkflow(project: javascript.NodeProject, options: PythonReleaseOptions): void {
|
|
333
355
|
if (!project.github) return;
|
|
334
|
-
const
|
|
335
|
-
if (
|
|
356
|
+
const publications = this.publications(options);
|
|
357
|
+
if (publications.length === 0) return;
|
|
336
358
|
const workflow = new GithubWorkflow(project.github, options.workflowName ?? "python-release");
|
|
337
359
|
workflow.file?.addOverride("permissions", { contents: "read" });
|
|
338
|
-
|
|
360
|
+
const upstreamWorkflow = options.upstreamWorkflow;
|
|
361
|
+
if (upstreamWorkflow) {
|
|
362
|
+
workflow.file?.addOverride("on.workflow_run", {
|
|
363
|
+
workflows: [upstreamWorkflow],
|
|
364
|
+
types: ["completed"],
|
|
365
|
+
});
|
|
366
|
+
workflow.on({ workflowDispatch: {} });
|
|
367
|
+
} else {
|
|
368
|
+
workflow.on({ push: { tags: ["v*"] }, workflowDispatch: {} });
|
|
369
|
+
}
|
|
339
370
|
workflow.file?.addOverride("on.workflow_dispatch", {
|
|
340
371
|
inputs: {
|
|
341
372
|
version: {
|
|
@@ -346,32 +377,56 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
346
377
|
},
|
|
347
378
|
});
|
|
348
379
|
workflow.addJob("build", {
|
|
380
|
+
...(upstreamWorkflow
|
|
381
|
+
? {
|
|
382
|
+
if: "${{ github.event_name != 'workflow_run' || (github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push') }}",
|
|
383
|
+
}
|
|
384
|
+
: {}),
|
|
349
385
|
runsOn: ["ubuntu-latest"],
|
|
350
386
|
permissions: { contents: JobPermission.READ },
|
|
351
387
|
timeoutMinutes: 20,
|
|
352
388
|
steps: [
|
|
353
|
-
{
|
|
389
|
+
{
|
|
390
|
+
name: "Checkout",
|
|
391
|
+
uses: "actions/checkout@v6",
|
|
392
|
+
with: {
|
|
393
|
+
"fetch-depth": 0,
|
|
394
|
+
...(upstreamWorkflow
|
|
395
|
+
? {
|
|
396
|
+
ref: "${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || github.sha }}",
|
|
397
|
+
}
|
|
398
|
+
: {}),
|
|
399
|
+
},
|
|
400
|
+
},
|
|
354
401
|
{ name: "Setup uv", uses: "astral-sh/setup-uv@v7" },
|
|
355
402
|
{
|
|
356
403
|
name: "Stamp workspace versions",
|
|
357
404
|
env: {
|
|
358
405
|
VERSION: "${{ github.event_name == 'push' && github.ref_name || inputs.version }}",
|
|
359
406
|
},
|
|
360
|
-
run:
|
|
407
|
+
run: upstreamWorkflow
|
|
408
|
+
? [
|
|
409
|
+
'if [ "$GITHUB_EVENT_NAME" = "workflow_run" ]; then',
|
|
410
|
+
' VERSION="$(git tag --points-at HEAD --list "v*" | sort -V | tail -1)"',
|
|
411
|
+
' test -n "$VERSION"',
|
|
412
|
+
"fi",
|
|
413
|
+
this.renderVersionStampScript(),
|
|
414
|
+
].join("\n")
|
|
415
|
+
: this.renderVersionStampScript(),
|
|
361
416
|
},
|
|
362
417
|
{
|
|
363
418
|
name: "Build distributions",
|
|
364
|
-
run:
|
|
419
|
+
run: publications
|
|
365
420
|
.map(
|
|
366
|
-
(
|
|
367
|
-
`uv build --package ${
|
|
421
|
+
(publication) =>
|
|
422
|
+
`uv build --package ${publication.distribution} --out-dir dist/${publication.directory}`,
|
|
368
423
|
)
|
|
369
424
|
.join("\n"),
|
|
370
425
|
},
|
|
371
426
|
{
|
|
372
427
|
name: "Validate distributions",
|
|
373
428
|
run: [
|
|
374
|
-
`test "$(find dist -type f \\( -name '*.whl' -o -name '*.tar.gz' \\) | wc -l | tr -d ' ')" -eq ${
|
|
429
|
+
`test "$(find dist -type f \\( -name '*.whl' -o -name '*.tar.gz' \\) | wc -l | tr -d ' ')" -eq ${publications.length * 2}`,
|
|
375
430
|
"uvx twine check dist/*/*.whl dist/*/*.tar.gz",
|
|
376
431
|
].join("\n"),
|
|
377
432
|
},
|
|
@@ -382,16 +437,15 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
382
437
|
},
|
|
383
438
|
],
|
|
384
439
|
});
|
|
385
|
-
for (const
|
|
386
|
-
workflow.addJob(`publish-${
|
|
387
|
-
if: "${{ github.event_name == 'push' }}",
|
|
440
|
+
for (const publication of publications) {
|
|
441
|
+
workflow.addJob(`publish-${publication.directory}`, {
|
|
442
|
+
if: "${{ github.event_name == 'push' || github.event_name == 'workflow_run' }}",
|
|
388
443
|
needs: ["build"],
|
|
389
444
|
environment: {
|
|
390
|
-
name:
|
|
391
|
-
options.environments?.[pkg.packageOptions.name] ?? `pypi-${pkg.packageOptions.name}`,
|
|
445
|
+
name: publication.environment,
|
|
392
446
|
url:
|
|
393
447
|
options.environmentUrl ??
|
|
394
|
-
`https://pypi.org/project/${
|
|
448
|
+
`https://pypi.org/project/${publication.distribution.replaceAll("_", "-")}/`,
|
|
395
449
|
},
|
|
396
450
|
runsOn: ["ubuntu-latest"],
|
|
397
451
|
permissions: { idToken: JobPermission.WRITE },
|
|
@@ -403,15 +457,154 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
403
457
|
with: { name: "python-distributions", path: "dist" },
|
|
404
458
|
},
|
|
405
459
|
{
|
|
406
|
-
name: `Publish ${
|
|
460
|
+
name: `Publish ${publication.distribution} to PyPI`,
|
|
407
461
|
uses: "pypa/gh-action-pypi-publish@release/v1",
|
|
408
|
-
with: { "packages-dir": `dist/${
|
|
462
|
+
with: { "packages-dir": `dist/${publication.directory}` },
|
|
409
463
|
},
|
|
410
464
|
],
|
|
411
465
|
});
|
|
412
466
|
}
|
|
413
467
|
}
|
|
414
468
|
|
|
469
|
+
private publications(options: PythonReleaseOptions): readonly PythonPublication[] {
|
|
470
|
+
return this.packages
|
|
471
|
+
.filter((pkg) => !pkg.packageOptions.private)
|
|
472
|
+
.map((pkg) => ({
|
|
473
|
+
directory: pkg.packageOptions.directory,
|
|
474
|
+
distribution: pkg.packageOptions.name,
|
|
475
|
+
environment:
|
|
476
|
+
options.environments?.[pkg.packageOptions.name] ?? `pypi-${pkg.packageOptions.name}`,
|
|
477
|
+
workflowName: options.workflowName ?? "python-release",
|
|
478
|
+
}));
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
private trustedPublisherPublications(
|
|
482
|
+
options: PythonReleaseOptions,
|
|
483
|
+
): readonly PythonPublication[] {
|
|
484
|
+
const standard = new Map(
|
|
485
|
+
this.publications(options).map((publication) => [publication.distribution, publication]),
|
|
486
|
+
);
|
|
487
|
+
for (const pkg of this.packages) {
|
|
488
|
+
const publisher = pkg.packageOptions.trustedPublisher;
|
|
489
|
+
if (!publisher || standard.has(pkg.packageOptions.name)) continue;
|
|
490
|
+
standard.set(pkg.packageOptions.name, {
|
|
491
|
+
directory: pkg.packageOptions.directory,
|
|
492
|
+
distribution: pkg.packageOptions.name,
|
|
493
|
+
environment: publisher.environment,
|
|
494
|
+
workflowName: publisher.workflowName,
|
|
495
|
+
artifacts: publisher.artifacts,
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
return [...standard.values()];
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
private addTrustedPublisherInstructionsTask(
|
|
502
|
+
project: javascript.NodeProject,
|
|
503
|
+
options: PythonReleaseOptions,
|
|
504
|
+
): void {
|
|
505
|
+
const repository = this.githubRepository();
|
|
506
|
+
const publications = this.trustedPublisherPublications(options);
|
|
507
|
+
const linesBeforeAuthentication = [
|
|
508
|
+
"# PyPI Trusted Publisher Setup Instructions",
|
|
509
|
+
"",
|
|
510
|
+
"Use a browser to audit and configure the PyPI trusted publishers below.",
|
|
511
|
+
"",
|
|
512
|
+
"## Audit and confirmation",
|
|
513
|
+
"",
|
|
514
|
+
"Before making any changes, complete a read-only audit:",
|
|
515
|
+
"",
|
|
516
|
+
`- Confirm that the active PyPI account is ${repository.owner}.`,
|
|
517
|
+
"- Start at https://pypi.org/manage/projects/ and determine which listed projects exist.",
|
|
518
|
+
"- Inspect every existing GitHub publisher for each project and compare its repository owner, repository name, workflow filename, and environment with the desired values below.",
|
|
519
|
+
"- Identify duplicate and mismatched trusted publishers that must be replaced by removing the publisher entry and adding the correct one.",
|
|
520
|
+
"- For projects that do not exist, identify the pending publisher that must be created.",
|
|
521
|
+
"- Report the active account and a proposed reconciliation plan grouped by publishers that will be left unchanged, updated, replaced, created, or removed.",
|
|
522
|
+
"- Ask the user to confirm the complete proposed plan before submitting any change.",
|
|
523
|
+
"- After confirmation, perform the authorized plan without asking for additional confirmation unless authentication or a CAPTCHA requires user action.",
|
|
524
|
+
"",
|
|
525
|
+
"## Authentication",
|
|
526
|
+
"",
|
|
527
|
+
];
|
|
528
|
+
const linesAfterAuthentication = [
|
|
529
|
+
`- If the active account is not ${repository.owner}, pause and ask the user to sign in to the correct account.`,
|
|
530
|
+
"- Pause and ask the user to complete every CAPTCHA. Do not attempt to solve or bypass a CAPTCHA.",
|
|
531
|
+
"- After the user completes an authentication or CAPTCHA step, continue from the current browser session.",
|
|
532
|
+
"",
|
|
533
|
+
"## Efficient browser workflow",
|
|
534
|
+
"",
|
|
535
|
+
"- Reuse or claim an existing PyPI browser tab when available.",
|
|
536
|
+
"- For an existing project, open its publisher page directly at https://pypi.org/manage/project/<project-name>/settings/publishing/.",
|
|
537
|
+
"- For a project that does not exist, use the pending-publisher page at https://pypi.org/manage/account/publishing/.",
|
|
538
|
+
"- Treat the publisher table shown after submission as authoritative confirmation of success, even if the navigation header unexpectedly appears signed out.",
|
|
539
|
+
"- Platform-specific wheels for different operating systems and CPU architectures do not require separate PyPI projects or trusted publishers.",
|
|
540
|
+
"",
|
|
541
|
+
"## Reconciliation rules",
|
|
542
|
+
"",
|
|
543
|
+
"- Never delete a PyPI project or package. Every remove or delete action in these instructions applies only to a trusted publisher entry.",
|
|
544
|
+
"- Treat editing or updating a trusted publisher as replacing that publisher: remove the existing publisher entry, then add the correct one.",
|
|
545
|
+
"- If exactly one publisher matches, leave it unchanged.",
|
|
546
|
+
"- If a publisher is mismatched, remove that trusted publisher entry and add the correct one.",
|
|
547
|
+
"- If no publisher exists, create it.",
|
|
548
|
+
"- Remove duplicates so exactly one matching publisher remains for each PyPI project and workflow.",
|
|
549
|
+
"- Use a pending publisher only when the PyPI project does not exist.",
|
|
550
|
+
"- After every change, verify that the resulting table displays the exact desired configuration.",
|
|
551
|
+
"- At completion, report which publishers were unchanged, updated, replaced, created, or removed.",
|
|
552
|
+
"",
|
|
553
|
+
"Publisher type: GitHub Actions",
|
|
554
|
+
"",
|
|
555
|
+
...publications.flatMap((publication) => {
|
|
556
|
+
const workflowName = `${publication.workflowName}.yml`;
|
|
557
|
+
return [
|
|
558
|
+
`## ${publication.distribution}`,
|
|
559
|
+
`- PyPI project: ${publication.distribution}`,
|
|
560
|
+
`- GitHub repository: ${repository.owner}/${repository.name}`,
|
|
561
|
+
`- Repository owner: ${repository.owner}`,
|
|
562
|
+
`- Repository name: ${repository.name}`,
|
|
563
|
+
`- GitHub environment: ${publication.environment}`,
|
|
564
|
+
`- Workflow filename: ${workflowName}`,
|
|
565
|
+
`- Workflow path: .github/workflows/${workflowName}`,
|
|
566
|
+
...(publication.artifacts ? [`- Artifacts: ${publication.artifacts}`] : []),
|
|
567
|
+
"",
|
|
568
|
+
];
|
|
569
|
+
}),
|
|
570
|
+
];
|
|
571
|
+
const helper = ".projen/pypi-trusted-publisher-instructions.mjs";
|
|
572
|
+
new TextFile(project.root, helper, {
|
|
573
|
+
lines: [
|
|
574
|
+
"#!/usr/bin/env node",
|
|
575
|
+
'import { parseArgs } from "node:util";',
|
|
576
|
+
"",
|
|
577
|
+
"const { values } = parseArgs({",
|
|
578
|
+
' options: { secretFile: { type: "string" } },',
|
|
579
|
+
"});",
|
|
580
|
+
`const beforeAuthentication = ${JSON.stringify(linesBeforeAuthentication)};`,
|
|
581
|
+
`const afterAuthentication = ${JSON.stringify(linesAfterAuthentication)};`,
|
|
582
|
+
"const authentication = values.secretFile",
|
|
583
|
+
" ? [",
|
|
584
|
+
" `- If browser authentication is required, read credentials from ${values.secretFile} directly into the browser without printing, logging, or exposing secret values.`,",
|
|
585
|
+
" `- If ${values.secretFile} is absent, invalid, or insufficient for authentication, pause and ask the user to complete authentication.`,",
|
|
586
|
+
" ]",
|
|
587
|
+
' : ["- If browser authentication is required, pause and ask the user to complete authentication."];',
|
|
588
|
+
'process.stdout.write(`${[...beforeAuthentication, ...authentication, ...afterAuthentication].join("\\n").trimEnd()}\\n`);',
|
|
589
|
+
],
|
|
590
|
+
});
|
|
591
|
+
project.root.addTask("pypiTrustedPublisherInstructions", {
|
|
592
|
+
description: "Print browser-agent instructions for PyPI trusted publishers",
|
|
593
|
+
exec: `node ${helper}`,
|
|
594
|
+
receiveArgs: true,
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
private githubRepository(): { readonly owner: string; readonly name: string } {
|
|
599
|
+
const match = this.repository.url
|
|
600
|
+
.replace(/\.git$/, "")
|
|
601
|
+
.match(/github\.com(?:[/:])([^/]+)\/([^/]+)$/);
|
|
602
|
+
if (!match?.[1] || !match[2]) {
|
|
603
|
+
throw new Error(`Python repository must be hosted on GitHub: ${this.repository.url}`);
|
|
604
|
+
}
|
|
605
|
+
return { owner: match[1], name: match[2] };
|
|
606
|
+
}
|
|
607
|
+
|
|
415
608
|
private renderVersionStampScript(): string {
|
|
416
609
|
return [
|
|
417
610
|
`chmod -R u+w ${this.repository.root}`,
|
package/src/project-rs.ts
CHANGED
|
@@ -7,6 +7,8 @@ import type { DBXToolsProject } from "./project.ts";
|
|
|
7
7
|
import { DBXToolsTypeScriptProject } from "./project-js.ts";
|
|
8
8
|
import type { PythonPackageOptions } from "./project-py.ts";
|
|
9
9
|
import { readWorkspaceVersion } from "./workspace-version.ts";
|
|
10
|
+
import { mixin } from "../index.ts";
|
|
11
|
+
import { isDBXToolsJavaScriptProject, isDBXToolsProject } from "./project-predicate.ts";
|
|
10
12
|
|
|
11
13
|
export interface CargoDependencyOptions {
|
|
12
14
|
readonly version?: string;
|
|
@@ -24,6 +26,8 @@ export interface RustPackageOptions {
|
|
|
24
26
|
readonly description?: string;
|
|
25
27
|
/** Keep this crate unpublished and out of public docs. */
|
|
26
28
|
readonly private?: boolean;
|
|
29
|
+
/** Build this crate's binary for each target and attach it to the GitHub release. */
|
|
30
|
+
readonly release?: boolean;
|
|
27
31
|
readonly dependencies?: Readonly<Record<string, CargoDependency>>;
|
|
28
32
|
readonly devDependencies?: Readonly<Record<string, CargoDependency>>;
|
|
29
33
|
readonly features?: Readonly<Record<string, readonly string[]>>;
|
|
@@ -47,10 +51,13 @@ export interface DBXToolsRustWorkspaceOptions {
|
|
|
47
51
|
readonly nodeRoot?: string;
|
|
48
52
|
readonly pythonRoot?: string;
|
|
49
53
|
readonly pythonModulePrefix?: string;
|
|
54
|
+
readonly private?: boolean;
|
|
50
55
|
/** Generate tag-driven cross-platform UniFFI package releases. */
|
|
51
56
|
readonly release?: boolean;
|
|
52
57
|
/** Native release targets; defaults to the maintained GitHub-hosted matrix. */
|
|
53
58
|
readonly releaseTargets?: readonly UniFFIReleaseTarget[];
|
|
59
|
+
/** Workflow name used by downstream release stages. Defaults to `rust-release`. */
|
|
60
|
+
readonly releaseWorkflowName?: string;
|
|
54
61
|
}
|
|
55
62
|
|
|
56
63
|
export interface UniFFIReleaseTarget {
|
|
@@ -332,7 +339,9 @@ export class DBXToolsRustWorkspace {
|
|
|
332
339
|
crates: this.packages.map((pkg) => `${root}/${pkg.packageOptions.directory}`),
|
|
333
340
|
bindings: this.bindingMappings,
|
|
334
341
|
};
|
|
335
|
-
project
|
|
342
|
+
if (isDBXToolsJavaScriptProject()(project)) {
|
|
343
|
+
project.dbxToolsConfig.rust = this.workspaceMapping;
|
|
344
|
+
}
|
|
336
345
|
this.pythonPackages = bindings
|
|
337
346
|
.filter((pkg) => (pkg.packageOptions.bindings ?? ["node", "python"]).includes("python"))
|
|
338
347
|
.map((pkg) => ({
|
|
@@ -344,6 +353,15 @@ export class DBXToolsRustWorkspace {
|
|
|
344
353
|
module: `${options.pythonModulePrefix ?? options.scope.replaceAll("-", "_")}.${pkg.packageOptions.directory.replaceAll("-", "_")}`,
|
|
345
354
|
description: `Python bindings for ${pkg.crateName}`,
|
|
346
355
|
private: true,
|
|
356
|
+
trustedPublisher: {
|
|
357
|
+
workflowName: options.releaseWorkflowName ?? "rust-release",
|
|
358
|
+
environment: `native-${pkg.crateName}`,
|
|
359
|
+
artifacts: `platform-specific wheels for ${(
|
|
360
|
+
options.releaseTargets ?? UNIFFI_RELEASE_TARGETS
|
|
361
|
+
)
|
|
362
|
+
.map((target) => `${target.os}-${target.cpu}`)
|
|
363
|
+
.join(", ")}; all architectures publish to this one PyPI project`,
|
|
364
|
+
},
|
|
347
365
|
}));
|
|
348
366
|
|
|
349
367
|
const existing = new Map(
|
|
@@ -449,13 +467,17 @@ export class DBXToolsRustWorkspace {
|
|
|
449
467
|
]),
|
|
450
468
|
].join(" && "),
|
|
451
469
|
});
|
|
452
|
-
if (
|
|
453
|
-
|
|
470
|
+
if (
|
|
471
|
+
(options.release ?? true) &&
|
|
472
|
+
(this.bindingMappings.length > 0 || this.packages.some((pkg) => pkg.packageOptions.release))
|
|
473
|
+
) {
|
|
474
|
+
this.addReleaseWorkflow(project, options, options.releaseTargets ?? UNIFFI_RELEASE_TARGETS);
|
|
454
475
|
}
|
|
455
476
|
}
|
|
456
477
|
|
|
457
478
|
private addReleaseWorkflow(
|
|
458
479
|
project: javascript.NodeProject,
|
|
480
|
+
options: DBXToolsRustWorkspaceOptions,
|
|
459
481
|
targets: readonly UniFFIReleaseTarget[],
|
|
460
482
|
): void {
|
|
461
483
|
if (!project.github) return;
|
|
@@ -472,9 +494,104 @@ export class DBXToolsRustWorkspace {
|
|
|
472
494
|
target: { ...target, facade: index === 0 },
|
|
473
495
|
})),
|
|
474
496
|
);
|
|
475
|
-
|
|
497
|
+
const releaseBinaries = this.packages
|
|
498
|
+
.filter((pkg) => pkg.packageOptions.release)
|
|
499
|
+
.map((pkg) => ({
|
|
500
|
+
crate: pkg.crateName,
|
|
501
|
+
binary: pkg.packageOptions.binaryName ?? pkg.crateName,
|
|
502
|
+
}));
|
|
503
|
+
const binaryMatrix = releaseBinaries.flatMap((pkg) =>
|
|
504
|
+
targets.map((target) => ({ package: pkg, target })),
|
|
505
|
+
);
|
|
506
|
+
const buildJob = {
|
|
507
|
+
name: "${{ matrix.binding.crate }} / ${{ matrix.target.node }}",
|
|
508
|
+
"runs-on": "${{ matrix.target.runner }}",
|
|
509
|
+
strategy: {
|
|
510
|
+
"fail-fast": false,
|
|
511
|
+
matrix: { include: matrix },
|
|
512
|
+
},
|
|
513
|
+
steps: [
|
|
514
|
+
{ name: "Checkout", uses: "actions/checkout@v6" },
|
|
515
|
+
{
|
|
516
|
+
name: "Setup Bun",
|
|
517
|
+
uses: "oven-sh/setup-bun@v2",
|
|
518
|
+
with: { "bun-version": "1.3.14" },
|
|
519
|
+
},
|
|
520
|
+
{ name: "Setup uv", uses: "astral-sh/setup-uv@v7" },
|
|
521
|
+
{
|
|
522
|
+
name: "Setup Rust",
|
|
523
|
+
uses: "dtolnay/rust-toolchain@stable",
|
|
524
|
+
with: { targets: "${{ matrix.target.cargo }}" },
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
name: "Install Linux native dependencies",
|
|
528
|
+
if: "${{ matrix.target.os == 'linux' }}",
|
|
529
|
+
run: "sudo apt-get update && sudo apt-get install --yes libdbus-1-dev pkg-config",
|
|
530
|
+
},
|
|
531
|
+
{ name: "Install", run: "bun install" },
|
|
532
|
+
{
|
|
533
|
+
name: "Build thin native packages",
|
|
534
|
+
shell: "bash",
|
|
535
|
+
env: {
|
|
536
|
+
VERSION: "${{ github.event_name == 'push' && github.ref_name || inputs.version }}",
|
|
537
|
+
},
|
|
538
|
+
run: 'bun node_modules/@dbx-tools/projen/tasks/uniffi-release.ts build --crate "${{ matrix.binding.crate }}" --rust "${{ matrix.binding.rust }}" --node "${{ matrix.binding.node }}" --python "${{ matrix.binding.python }}" --node-package "${{ matrix.binding.nodePackage }}" --python-package "${{ matrix.binding.pythonPackage }}" --cargo-target "${{ matrix.target.cargo }}" --node-triple "${{ matrix.target.node }}" --python-tag "${{ matrix.target.python }}" --os "${{ matrix.target.os }}" --cpu "${{ matrix.target.cpu }}" --libc "${{ matrix.target.libc }}" --facade "${{ matrix.target.facade }}" --version "${VERSION#v}"',
|
|
539
|
+
},
|
|
540
|
+
{
|
|
541
|
+
name: "Upload native packages",
|
|
542
|
+
uses: "actions/upload-artifact@v7",
|
|
543
|
+
with: {
|
|
544
|
+
name: "${{ matrix.binding.crate }}-${{ matrix.target.node }}",
|
|
545
|
+
path: [
|
|
546
|
+
"dist/uniffi/npm/*.tgz",
|
|
547
|
+
"dist/uniffi/npm-facade/*.tgz",
|
|
548
|
+
"dist/uniffi/python/*.whl",
|
|
549
|
+
].join("\n"),
|
|
550
|
+
},
|
|
551
|
+
},
|
|
552
|
+
],
|
|
553
|
+
};
|
|
554
|
+
const binaryBuildJob = {
|
|
555
|
+
name: "${{ matrix.package.crate }} / ${{ matrix.target.node }}",
|
|
556
|
+
"runs-on": "${{ matrix.target.runner }}",
|
|
557
|
+
strategy: { "fail-fast": false, matrix: { include: binaryMatrix } },
|
|
558
|
+
steps: [
|
|
559
|
+
{ name: "Checkout", uses: "actions/checkout@v6" },
|
|
560
|
+
{
|
|
561
|
+
name: "Setup Rust",
|
|
562
|
+
uses: "dtolnay/rust-toolchain@stable",
|
|
563
|
+
with: { targets: "${{ matrix.target.cargo }}" },
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
name: "Install Linux native dependencies",
|
|
567
|
+
if: "${{ matrix.target.os == 'linux' }}",
|
|
568
|
+
run: "sudo apt-get update && sudo apt-get install --yes libdbus-1-dev pkg-config",
|
|
569
|
+
},
|
|
570
|
+
{
|
|
571
|
+
name: "Build release binary",
|
|
572
|
+
shell: "bash",
|
|
573
|
+
run: [
|
|
574
|
+
'cargo build --release --package "${{ matrix.package.crate }}" --bin "${{ matrix.package.binary }}" --target "${{ matrix.target.cargo }}"',
|
|
575
|
+
"mkdir -p dist/rust-release",
|
|
576
|
+
"SOURCE=\"target/${{ matrix.target.cargo }}/release/${{ matrix.package.binary }}${{ matrix.target.os == 'win32' && '.exe' || '' }}\"",
|
|
577
|
+
"DESTINATION=\"dist/rust-release/${{ matrix.package.binary }}-${{ matrix.target.node }}${{ matrix.target.os == 'win32' && '.exe' || '' }}\"",
|
|
578
|
+
'cp "$SOURCE" "$DESTINATION"',
|
|
579
|
+
].join("\n"),
|
|
580
|
+
},
|
|
581
|
+
{
|
|
582
|
+
name: "Upload release binary",
|
|
583
|
+
uses: "actions/upload-artifact@v7",
|
|
584
|
+
with: {
|
|
585
|
+
name: "release-${{ matrix.package.crate }}-${{ matrix.target.node }}",
|
|
586
|
+
path: "dist/rust-release/*",
|
|
587
|
+
},
|
|
588
|
+
},
|
|
589
|
+
],
|
|
590
|
+
};
|
|
591
|
+
const workflowName = options.releaseWorkflowName ?? "rust-release";
|
|
592
|
+
new YamlFile(project, `.github/workflows/${workflowName}.yml`, {
|
|
476
593
|
obj: {
|
|
477
|
-
name:
|
|
594
|
+
name: workflowName,
|
|
478
595
|
on: {
|
|
479
596
|
push: { tags: ["v*"] },
|
|
480
597
|
workflow_dispatch: {
|
|
@@ -489,118 +606,63 @@ export class DBXToolsRustWorkspace {
|
|
|
489
606
|
},
|
|
490
607
|
permissions: { contents: "read" },
|
|
491
608
|
jobs: {
|
|
492
|
-
build: {
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
609
|
+
...(matrix.length ? { build: buildJob } : {}),
|
|
610
|
+
...(binaryMatrix.length ? { "build-binaries": binaryBuildJob } : {}),
|
|
611
|
+
...(bindings.length
|
|
612
|
+
? {
|
|
613
|
+
publish: {
|
|
614
|
+
if: "${{ github.event_name == 'push' }}",
|
|
615
|
+
needs: ["build"],
|
|
616
|
+
"runs-on": "ubuntu-latest",
|
|
617
|
+
strategy: { matrix: { binding: bindings } },
|
|
618
|
+
permissions: { contents: "read", "id-token": "write" },
|
|
619
|
+
environment: { name: "native-${{ matrix.binding.crate }}" },
|
|
620
|
+
steps: [
|
|
621
|
+
{ name: "Checkout", uses: "actions/checkout@v6" },
|
|
622
|
+
{
|
|
623
|
+
name: "Setup Node.js",
|
|
624
|
+
uses: "actions/setup-node@v6",
|
|
625
|
+
with: { "registry-url": "https://registry.npmjs.org" },
|
|
626
|
+
},
|
|
627
|
+
{
|
|
628
|
+
name: "Setup Bun",
|
|
629
|
+
uses: "oven-sh/setup-bun@v2",
|
|
630
|
+
with: { "bun-version": "1.3.14" },
|
|
631
|
+
},
|
|
632
|
+
{ name: "Setup uv", uses: "astral-sh/setup-uv@v7" },
|
|
633
|
+
{ name: "Install", run: "bun install" },
|
|
634
|
+
{
|
|
635
|
+
name: "Download packages",
|
|
636
|
+
uses: "actions/download-artifact@v8",
|
|
637
|
+
with: {
|
|
638
|
+
pattern: "${{ matrix.binding.crate }}-*",
|
|
639
|
+
path: "dist/uniffi",
|
|
640
|
+
"merge-multiple": true,
|
|
641
|
+
},
|
|
642
|
+
},
|
|
643
|
+
{
|
|
644
|
+
name: "Publish npm packages",
|
|
645
|
+
env: { NODE_AUTH_TOKEN: "${{ secrets.NPM_TOKEN }}" },
|
|
646
|
+
run: 'for package in dist/uniffi/npm/*.tgz; do npm publish "$package" --access public; done',
|
|
647
|
+
},
|
|
648
|
+
{
|
|
649
|
+
name: "Publish npm facades",
|
|
650
|
+
if: "${{ matrix.binding.node != '' }}",
|
|
651
|
+
env: { NODE_AUTH_TOKEN: "${{ secrets.NPM_TOKEN }}" },
|
|
652
|
+
run: 'for package in dist/uniffi/npm-facade/*.tgz; do npm publish "$package" --access public; done',
|
|
653
|
+
},
|
|
654
|
+
{
|
|
655
|
+
name: "Publish Python wheels",
|
|
656
|
+
if: "${{ matrix.binding.python != '' }}",
|
|
657
|
+
run: "uv publish --trusted-publishing always dist/uniffi/python/*.whl",
|
|
658
|
+
},
|
|
659
|
+
],
|
|
524
660
|
},
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
{
|
|
528
|
-
name: "Upload native packages",
|
|
529
|
-
uses: "actions/upload-artifact@v7",
|
|
530
|
-
with: {
|
|
531
|
-
name: "${{ matrix.binding.crate }}-${{ matrix.target.node }}",
|
|
532
|
-
path: [
|
|
533
|
-
"dist/uniffi/npm/*.tgz",
|
|
534
|
-
"dist/uniffi/npm-facade/*.tgz",
|
|
535
|
-
"dist/uniffi/python/*.whl",
|
|
536
|
-
].join("\n"),
|
|
537
|
-
},
|
|
538
|
-
},
|
|
539
|
-
],
|
|
540
|
-
},
|
|
541
|
-
publish: {
|
|
542
|
-
if: "${{ github.event_name == 'push' }}",
|
|
543
|
-
needs: ["build"],
|
|
544
|
-
"runs-on": "ubuntu-latest",
|
|
545
|
-
strategy: { matrix: { binding: bindings } },
|
|
546
|
-
permissions: { contents: "read", "id-token": "write" },
|
|
547
|
-
environment: { name: "native-${{ matrix.binding.crate }}" },
|
|
548
|
-
steps: [
|
|
549
|
-
{ name: "Checkout", uses: "actions/checkout@v6" },
|
|
550
|
-
{
|
|
551
|
-
name: "Setup Node.js",
|
|
552
|
-
uses: "actions/setup-node@v6",
|
|
553
|
-
with: { "registry-url": "https://registry.npmjs.org" },
|
|
554
|
-
},
|
|
555
|
-
{
|
|
556
|
-
name: "Setup Bun",
|
|
557
|
-
uses: "oven-sh/setup-bun@v2",
|
|
558
|
-
with: { "bun-version": "1.3.14" },
|
|
559
|
-
},
|
|
560
|
-
{ name: "Setup uv", uses: "astral-sh/setup-uv@v7" },
|
|
561
|
-
{ name: "Install", run: "bun install" },
|
|
562
|
-
{
|
|
563
|
-
name: "Publish workspace npm dependencies",
|
|
564
|
-
if: "${{ matrix.binding.node != '' }}",
|
|
565
|
-
env: {
|
|
566
|
-
NPM_CONFIG_TOKEN: "${{ secrets.NPM_TOKEN }}",
|
|
567
|
-
NODE_AUTH_TOKEN: "${{ secrets.NPM_TOKEN }}",
|
|
568
|
-
},
|
|
569
|
-
run: [
|
|
570
|
-
'VERSION="${GITHUB_REF_NAME#v}"',
|
|
571
|
-
'bun node_modules/@dbx-tools/projen/tasks/publish.ts "$VERSION" --exclude projen --exclude "${{ matrix.binding.node }}"',
|
|
572
|
-
].join("\n"),
|
|
573
|
-
},
|
|
574
|
-
{
|
|
575
|
-
name: "Download packages",
|
|
576
|
-
uses: "actions/download-artifact@v8",
|
|
577
|
-
with: {
|
|
578
|
-
pattern: "${{ matrix.binding.crate }}-*",
|
|
579
|
-
path: "dist/uniffi",
|
|
580
|
-
"merge-multiple": true,
|
|
581
|
-
},
|
|
582
|
-
},
|
|
583
|
-
{
|
|
584
|
-
name: "Publish npm packages",
|
|
585
|
-
env: { NODE_AUTH_TOKEN: "${{ secrets.NPM_TOKEN }}" },
|
|
586
|
-
run: 'for package in dist/uniffi/npm/*.tgz; do npm publish "$package" --access public; done',
|
|
587
|
-
},
|
|
588
|
-
{
|
|
589
|
-
name: "Publish npm facades",
|
|
590
|
-
if: "${{ matrix.binding.node != '' }}",
|
|
591
|
-
env: { NODE_AUTH_TOKEN: "${{ secrets.NPM_TOKEN }}" },
|
|
592
|
-
run: 'for package in dist/uniffi/npm-facade/*.tgz; do npm publish "$package" --access public; done',
|
|
593
|
-
},
|
|
594
|
-
{
|
|
595
|
-
name: "Publish Python wheels",
|
|
596
|
-
if: "${{ matrix.binding.python != '' }}",
|
|
597
|
-
run: "uv publish --trusted-publishing always dist/uniffi/python/*.whl",
|
|
598
|
-
},
|
|
599
|
-
],
|
|
600
|
-
},
|
|
661
|
+
}
|
|
662
|
+
: {}),
|
|
601
663
|
"publish-cargo": {
|
|
602
664
|
if: "${{ github.event_name == 'push' }}",
|
|
603
|
-
needs: ["build"],
|
|
665
|
+
needs: [matrix.length ? "build" : "build-binaries"],
|
|
604
666
|
"runs-on": "ubuntu-latest",
|
|
605
667
|
permissions: { contents: "read" },
|
|
606
668
|
steps: [
|
|
@@ -642,6 +704,35 @@ export class DBXToolsRustWorkspace {
|
|
|
642
704
|
},
|
|
643
705
|
],
|
|
644
706
|
},
|
|
707
|
+
...(binaryMatrix.length
|
|
708
|
+
? {
|
|
709
|
+
"publish-github-release": {
|
|
710
|
+
if: "${{ github.event_name == 'push' }}",
|
|
711
|
+
needs: ["build-binaries"],
|
|
712
|
+
"runs-on": "ubuntu-latest",
|
|
713
|
+
permissions: { contents: "write" },
|
|
714
|
+
steps: [
|
|
715
|
+
{
|
|
716
|
+
name: "Download release binaries",
|
|
717
|
+
uses: "actions/download-artifact@v8",
|
|
718
|
+
with: {
|
|
719
|
+
pattern: "release-*",
|
|
720
|
+
path: "dist/rust-release",
|
|
721
|
+
"merge-multiple": true,
|
|
722
|
+
},
|
|
723
|
+
},
|
|
724
|
+
{
|
|
725
|
+
name: "Publish GitHub release assets",
|
|
726
|
+
uses: "softprops/action-gh-release@v2",
|
|
727
|
+
with: {
|
|
728
|
+
files: "dist/rust-release/*",
|
|
729
|
+
"generate-release-notes": true,
|
|
730
|
+
},
|
|
731
|
+
},
|
|
732
|
+
],
|
|
733
|
+
},
|
|
734
|
+
}
|
|
735
|
+
: {}),
|
|
645
736
|
},
|
|
646
737
|
},
|
|
647
738
|
});
|
package/src/release.ts
CHANGED
|
@@ -41,6 +41,9 @@ function BUN_PUBLISH_SCRIPT(tagPrefix: string, excludeDirs: readonly string[]):
|
|
|
41
41
|
// A manual run has no tag: use a throwaway version and never really publish.
|
|
42
42
|
' VERSION="0.0.0-dry.${GITHUB_RUN_NUMBER}"',
|
|
43
43
|
" DRY_RUN=--dry-run",
|
|
44
|
+
'elif [ -n "${RELEASE_VERSION:-}" ]; then',
|
|
45
|
+
' VERSION="$RELEASE_VERSION"',
|
|
46
|
+
" DRY_RUN=",
|
|
44
47
|
"else",
|
|
45
48
|
` VERSION="\${GITHUB_REF_NAME#${tagPrefix}}"`,
|
|
46
49
|
// A tag push honors the input too, so a dry-run tag can be tested if wanted.
|
|
@@ -56,6 +59,7 @@ interface PublishWorkflow {
|
|
|
56
59
|
readonly tagPrefix: string;
|
|
57
60
|
readonly steps: readonly JobStep[];
|
|
58
61
|
readonly workingDirectory?: string;
|
|
62
|
+
readonly upstreamWorkflow?: string;
|
|
59
63
|
}
|
|
60
64
|
|
|
61
65
|
/** Shared checkout and toolchain setup for every npm publish workflow. */
|
|
@@ -120,6 +124,10 @@ export interface DBXToolsReleaseOptions {
|
|
|
120
124
|
* component. Defaults to none.
|
|
121
125
|
*/
|
|
122
126
|
readonly standaloneReleases?: readonly StandaloneRelease[];
|
|
127
|
+
/** Run the main Node release after this workflow completes successfully. */
|
|
128
|
+
readonly upstreamWorkflow?: string;
|
|
129
|
+
/** Main Node release workflow name. Defaults to `node-release`. */
|
|
130
|
+
readonly workflowName?: string | false;
|
|
123
131
|
}
|
|
124
132
|
|
|
125
133
|
/**
|
|
@@ -151,11 +159,15 @@ export interface DBXToolsReleaseOptions {
|
|
|
151
159
|
export class DBXToolsRelease extends Component {
|
|
152
160
|
private readonly tagPrefix: string;
|
|
153
161
|
private readonly standaloneReleases: readonly StandaloneRelease[];
|
|
162
|
+
private readonly upstreamWorkflow?: string;
|
|
163
|
+
private readonly workflowName: string | false;
|
|
154
164
|
|
|
155
165
|
constructor(project: DBXToolsNodeProject, options: DBXToolsReleaseOptions = {}) {
|
|
156
166
|
super(project);
|
|
157
167
|
this.tagPrefix = options.tagPrefix ?? "v";
|
|
158
168
|
this.standaloneReleases = options.standaloneReleases ?? [];
|
|
169
|
+
this.upstreamWorkflow = options.upstreamWorkflow;
|
|
170
|
+
this.workflowName = options.workflowName ?? "node-release";
|
|
159
171
|
}
|
|
160
172
|
|
|
161
173
|
public override preSynthesize(): void {
|
|
@@ -176,7 +188,7 @@ export class DBXToolsRelease extends Component {
|
|
|
176
188
|
// Author the tag-driven publish workflows only when GitHub is enabled - they
|
|
177
189
|
// live in `.github/`, which requires projen's GitHub component.
|
|
178
190
|
if (project.github) {
|
|
179
|
-
this.authorReleaseWorkflow(project);
|
|
191
|
+
if (this.workflowName) this.authorReleaseWorkflow(project);
|
|
180
192
|
for (const standalone of this.standaloneReleases) {
|
|
181
193
|
this.authorStandaloneReleaseWorkflow(project, standalone);
|
|
182
194
|
}
|
|
@@ -186,7 +198,7 @@ export class DBXToolsRelease extends Component {
|
|
|
186
198
|
/** Author the common tag trigger, concurrency policy, permissions, and publish job. */
|
|
187
199
|
private authorPublishWorkflow(
|
|
188
200
|
project: DBXToolsNodeProject,
|
|
189
|
-
{ name, tagPrefix, steps, workingDirectory }: PublishWorkflow,
|
|
201
|
+
{ name, tagPrefix, steps, workingDirectory, upstreamWorkflow }: PublishWorkflow,
|
|
190
202
|
): void {
|
|
191
203
|
const workflow = new GithubWorkflow(project.github!, name, {
|
|
192
204
|
// Serialize publishes so two tags landing together cannot race to the
|
|
@@ -198,7 +210,14 @@ export class DBXToolsRelease extends Component {
|
|
|
198
210
|
// Read-only floor for any job that does not declare its own permissions;
|
|
199
211
|
// the publish job below overrides it with the `id-token` it needs.
|
|
200
212
|
workflow.file?.addOverride("permissions", { contents: "read" });
|
|
201
|
-
|
|
213
|
+
if (upstreamWorkflow) {
|
|
214
|
+
workflow.file?.addOverride("on.workflow_run", {
|
|
215
|
+
workflows: [upstreamWorkflow],
|
|
216
|
+
types: ["completed"],
|
|
217
|
+
});
|
|
218
|
+
} else {
|
|
219
|
+
workflow.on({ push: { tags: [`${tagPrefix}*`] } });
|
|
220
|
+
}
|
|
202
221
|
// Manual trigger for testing the workflow WITHOUT reaching npm: a
|
|
203
222
|
// `workflow_dispatch` run has no tag, so the publish script forces
|
|
204
223
|
// `--dry-run` (pack + validate only). The `dry_run` input (default true)
|
|
@@ -213,6 +232,11 @@ export class DBXToolsRelease extends Component {
|
|
|
213
232
|
},
|
|
214
233
|
});
|
|
215
234
|
workflow.addJob("publish", {
|
|
235
|
+
...(upstreamWorkflow
|
|
236
|
+
? {
|
|
237
|
+
if: "${{ github.event_name != 'workflow_run' || (github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'workflow_run') }}",
|
|
238
|
+
}
|
|
239
|
+
: {}),
|
|
216
240
|
runsOn: ["ubuntu-latest"],
|
|
217
241
|
// `id-token: write` lets npm mint the OIDC token for provenance attestation.
|
|
218
242
|
permissions: { contents: JobPermission.READ, idToken: JobPermission.WRITE },
|
|
@@ -223,7 +247,35 @@ export class DBXToolsRelease extends Component {
|
|
|
223
247
|
CI: "true",
|
|
224
248
|
DRY_RUN_INPUT: "${{ github.event.inputs.dry_run == 'true' && '--dry-run' || '' }}",
|
|
225
249
|
},
|
|
226
|
-
steps: [
|
|
250
|
+
steps: [
|
|
251
|
+
...publishSetupSteps().map((step) =>
|
|
252
|
+
step.name === "Checkout" && upstreamWorkflow
|
|
253
|
+
? {
|
|
254
|
+
...step,
|
|
255
|
+
with: {
|
|
256
|
+
...step.with,
|
|
257
|
+
ref: "${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || github.sha }}",
|
|
258
|
+
},
|
|
259
|
+
}
|
|
260
|
+
: step,
|
|
261
|
+
),
|
|
262
|
+
...(upstreamWorkflow
|
|
263
|
+
? [
|
|
264
|
+
{
|
|
265
|
+
name: "Resolve release tag",
|
|
266
|
+
run: [
|
|
267
|
+
'RELEASE_TAG="$(git tag --points-at HEAD --list "' +
|
|
268
|
+
tagPrefix +
|
|
269
|
+
'*" | sort -V | tail -1)"',
|
|
270
|
+
'test -n "$RELEASE_TAG"',
|
|
271
|
+
'echo "RELEASE_TAG=$RELEASE_TAG" >> "$GITHUB_ENV"',
|
|
272
|
+
`echo "RELEASE_VERSION=\${RELEASE_TAG#${tagPrefix}}" >> "$GITHUB_ENV"`,
|
|
273
|
+
].join("\n"),
|
|
274
|
+
} satisfies JobStep,
|
|
275
|
+
]
|
|
276
|
+
: []),
|
|
277
|
+
...steps,
|
|
278
|
+
],
|
|
227
279
|
...(workingDirectory ? { defaults: { run: { workingDirectory } } } : {}),
|
|
228
280
|
});
|
|
229
281
|
}
|
|
@@ -235,9 +287,12 @@ export class DBXToolsRelease extends Component {
|
|
|
235
287
|
* (no bump math).
|
|
236
288
|
*/
|
|
237
289
|
private authorReleaseWorkflow(project: DBXToolsNodeProject): void {
|
|
290
|
+
if (!this.workflowName) return;
|
|
291
|
+
if (this.workflowName !== "release") project.tryRemoveFile(".github/workflows/release.yml");
|
|
238
292
|
this.authorPublishWorkflow(project, {
|
|
239
|
-
name:
|
|
293
|
+
name: this.workflowName,
|
|
240
294
|
tagPrefix: this.tagPrefix,
|
|
295
|
+
upstreamWorkflow: this.upstreamWorkflow,
|
|
241
296
|
steps: [
|
|
242
297
|
// The pushed tag is the version: `<prefix>1.2.3` -> `1.2.3`. Stamp it on
|
|
243
298
|
// every workspace package (manifests are projen-readonly, so unlock
|
package/tasks/uniffi.ts
CHANGED
|
@@ -65,7 +65,13 @@ const replaceGenerated = (source: string, destination: string): void => {
|
|
|
65
65
|
mkdirSync(dirname(destination), { recursive: true });
|
|
66
66
|
makeWritable(destination);
|
|
67
67
|
rmSync(destination, { force: true });
|
|
68
|
-
|
|
68
|
+
try {
|
|
69
|
+
renameSync(source, destination);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
if (!(error instanceof Error) || !("code" in error) || error.code !== "EXDEV") throw error;
|
|
72
|
+
cpSync(source, destination);
|
|
73
|
+
rmSync(source, { force: true });
|
|
74
|
+
}
|
|
69
75
|
};
|
|
70
76
|
|
|
71
77
|
const stampGeneratedPython = (file: string): void => {
|