@dbx-tools/projen 0.6.163 → 0.6.174

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/src/project-py.ts CHANGED
@@ -7,8 +7,8 @@ import { GithubWorkflow } from "projen/lib/github";
7
7
  import { JobPermission } from "projen/lib/github/workflows-model";
8
8
  import { parse, stringify } from "smol-toml";
9
9
  import { projectReleaseBranch, projectRepositoryUrl } from "./project-js.ts";
10
- import type { DBXToolsProject, DBXToolsProjectOptions } from "./project.ts";
11
10
  import { isDBXToolsJavaScriptProject } from "./project-predicate.ts";
11
+ import type { DBXToolsProject, DBXToolsProjectOptions } from "./project.ts";
12
12
  import { DOWNSTREAM_RELEASE_EVENT, RELEASE_TAG, releaseSourceSteps } from "./release-dispatch.ts";
13
13
  import { readWorkspaceVersion } from "./workspace-version.ts";
14
14
 
@@ -29,8 +29,8 @@ export interface PythonPackageOptions extends DBXToolsProjectOptions {
29
29
  /** Workspace package directories rendered as standalone Git dependencies. */
30
30
  readonly internalDependencies?: readonly string[];
31
31
  readonly scripts?: Readonly<Record<string, string>>;
32
- /** Keep this package unpublished and out of public docs and releases. */
33
- readonly private?: boolean;
32
+ /** Publish this package through the Rust UniFFI release flow instead of the Python workflow. */
33
+ readonly uniffi?: boolean;
34
34
  /** Generated source files excluded from strict static analysis. Package-relative. */
35
35
  readonly generatedSources?: readonly string[];
36
36
  /** Trusted publisher used outside the standard Python release workflow. */
@@ -75,6 +75,7 @@ interface PythonPublication {
75
75
  readonly environment: string;
76
76
  readonly workflowName: string;
77
77
  readonly artifacts?: string;
78
+ readonly dependencies?: readonly string[];
78
79
  }
79
80
 
80
81
  /** Options for {@link DBXToolsPythonWorkspace}. */
@@ -214,8 +215,8 @@ export class DBXToolsPythonProject extends python.PythonProject implements DBXTo
214
215
  if (pkg.scripts) {
215
216
  this.uv.file.addOverride("project.scripts", pkg.scripts);
216
217
  }
217
- if (pkg.private) {
218
- this.uv.file.addOverride("tool.dbx-tools.private", true);
218
+ if (pkg.uniffi !== undefined) {
219
+ this.uv.file.addOverride("tool.dbx_tools.config.uniffi", pkg.uniffi);
219
220
  }
220
221
  formatPyproject(this.uv.file);
221
222
  this.uv.file.readonly = true;
@@ -319,7 +320,7 @@ export class DBXToolsPythonWorkspace extends Component {
319
320
  projectVscode(project)?.settings.addSetting("python.defaultInterpreterPath", interpreterPath);
320
321
  }
321
322
 
322
- if (options.release && this.packages.some((pkg) => !pkg.packageOptions.private)) {
323
+ if (options.release && this.packages.length > 0) {
323
324
  if (isDBXToolsJavaScriptProject()(project)) {
324
325
  project.dbxToolsConfig.pythonReleaseWorkflow =
325
326
  releaseOptions.workflowName ?? "python-release";
@@ -428,7 +429,10 @@ export class DBXToolsPythonWorkspace extends Component {
428
429
  private addReleaseWorkflow(project: javascript.NodeProject, options: PythonReleaseOptions): void {
429
430
  if (!project.github) return;
430
431
  const publications = this.publications(options);
431
- if (publications.length === 0) return;
432
+ const uniffiPublications = this.uniffiPublications(options);
433
+ const allPublications = [...publications, ...uniffiPublications];
434
+ if (allPublications.length === 0) return;
435
+ const usesRustArtifacts = uniffiPublications.length > 0;
432
436
  const workflowName = options.workflowName ?? "python-release";
433
437
  const workflow = new GithubWorkflow(project.github, workflowName, {
434
438
  limitConcurrency: true,
@@ -436,7 +440,7 @@ export class DBXToolsPythonWorkspace extends Component {
436
440
  });
437
441
  workflow.file?.addOverride("permissions", {
438
442
  contents: "read",
439
- ...(options.upstreamWorkflow ? { actions: "read" } : {}),
443
+ ...(options.upstreamWorkflow || usesRustArtifacts ? { actions: "read" } : {}),
440
444
  });
441
445
  const upstreamWorkflow = options.upstreamWorkflow;
442
446
  const branchDispatch = upstreamWorkflow === undefined && isDBXToolsJavaScriptProject()(project);
@@ -487,7 +491,7 @@ export class DBXToolsPythonWorkspace extends Component {
487
491
  runsOn: ["ubuntu-latest"],
488
492
  permissions: {
489
493
  contents: JobPermission.READ,
490
- ...(upstreamWorkflow ? { actions: JobPermission.READ } : {}),
494
+ ...(upstreamWorkflow || usesRustArtifacts ? { actions: JobPermission.READ } : {}),
491
495
  },
492
496
  timeoutMinutes: 20,
493
497
  steps: [
@@ -555,6 +559,35 @@ export class DBXToolsPythonWorkspace extends Component {
555
559
  ]
556
560
  : []),
557
561
  { name: "Setup uv", uses: "astral-sh/setup-uv@v7" },
562
+ ...(usesRustArtifacts
563
+ ? [
564
+ {
565
+ name: "Require Rust artifact handoff",
566
+ if: "${{ github.event_name == 'repository_dispatch' || github.event_name == 'workflow_run' }}",
567
+ shell: "bash",
568
+ env: {
569
+ RUST_RUN_ID:
570
+ "${{ github.event_name == 'repository_dispatch' && github.event.client_payload.rust_run_id || github.event.workflow_run.id }}",
571
+ RUST_RUN_ATTEMPT:
572
+ "${{ github.event_name == 'repository_dispatch' && github.event.client_payload.rust_run_attempt || github.event.workflow_run.run_attempt }}",
573
+ },
574
+ run: ['test -n "$RUST_RUN_ID"', 'test -n "$RUST_RUN_ATTEMPT"'].join("\n"),
575
+ },
576
+ ]
577
+ : []),
578
+ ...uniffiPublications.map((publication) => ({
579
+ name: `Download ${publication.distribution} native wheels`,
580
+ if: "${{ github.event_name == 'repository_dispatch' || github.event_name == 'workflow_run' }}",
581
+ uses: "actions/download-artifact@v8",
582
+ with: {
583
+ pattern: `${publication.distribution}--*--python-wheel`,
584
+ path: `dist/${publication.directory}`,
585
+ "merge-multiple": true,
586
+ "run-id":
587
+ "${{ github.event_name == 'repository_dispatch' && github.event.client_payload.rust_run_id || github.event.workflow_run.id }}",
588
+ "github-token": "${{ github.token }}",
589
+ },
590
+ })),
558
591
  {
559
592
  name: "Stamp workspace versions",
560
593
  env: {
@@ -594,8 +627,15 @@ export class DBXToolsPythonWorkspace extends Component {
594
627
  {
595
628
  name: "Validate distributions",
596
629
  run: [
597
- `test "$(find dist -type f \\( -name '*.whl' -o -name '*.tar.gz' \\) | wc -l | tr -d ' ')" -eq ${publications.length * 2}`,
598
- "uvx twine check dist/*/*.whl dist/*/*.tar.gz",
630
+ ...publications.map(
631
+ (publication) =>
632
+ `test "$(find dist/${publication.directory} -maxdepth 1 -type f \\( -name '*.whl' -o -name '*.tar.gz' \\) | wc -l | tr -d ' ')" -eq 2`,
633
+ ),
634
+ ...uniffiPublications.map(
635
+ (publication) =>
636
+ `if [ "$GITHUB_EVENT_NAME" = "repository_dispatch" ] || [ "$GITHUB_EVENT_NAME" = "workflow_run" ]; then find dist/${publication.directory} -maxdepth 1 -name '*.whl' -print -quit | grep -q .; fi`,
637
+ ),
638
+ "find dist -type f \\( -name '*.whl' -o -name '*.tar.gz' \\) -print0 | xargs -0 uvx twine check",
599
639
  ].join("\n"),
600
640
  },
601
641
  {
@@ -610,12 +650,15 @@ export class DBXToolsPythonWorkspace extends Component {
610
650
  },
611
651
  ],
612
652
  });
613
- for (const publication of publications) {
653
+ for (const publication of allPublications) {
614
654
  workflow.addJob(`publish-${publication.directory}`, {
615
655
  if: branchDispatch
616
656
  ? "${{ github.event_name == 'repository_dispatch' }}"
617
657
  : "${{ github.event_name == 'push' || github.event_name == 'workflow_run' }}",
618
- needs: ["build"],
658
+ needs: [
659
+ "build",
660
+ ...(publication.dependencies ?? []).map((dependency) => `publish-${dependency}`),
661
+ ],
619
662
  environment: {
620
663
  name: publication.environment,
621
664
  url:
@@ -643,7 +686,7 @@ export class DBXToolsPythonWorkspace extends Component {
643
686
 
644
687
  private publications(options: PythonReleaseOptions): readonly PythonPublication[] {
645
688
  return this.packages
646
- .filter((pkg) => !pkg.packageOptions.private)
689
+ .filter((pkg) => pkg.packageOptions.uniffi !== true)
647
690
  .map((pkg) => ({
648
691
  directory: pkg.packageOptions.directory,
649
692
  distribution: pkg.packageOptions.name,
@@ -653,6 +696,22 @@ export class DBXToolsPythonWorkspace extends Component {
653
696
  }));
654
697
  }
655
698
 
699
+ private uniffiPublications(options: PythonReleaseOptions): readonly PythonPublication[] {
700
+ return this.packages
701
+ .filter((pkg) => pkg.packageOptions.uniffi === true)
702
+ .map((pkg) => ({
703
+ directory: pkg.packageOptions.directory,
704
+ distribution: pkg.packageOptions.name,
705
+ environment:
706
+ pkg.packageOptions.trustedPublisher?.environment ??
707
+ options.environments?.[pkg.packageOptions.name] ??
708
+ `pypi-${pkg.packageOptions.name}`,
709
+ workflowName: options.workflowName ?? "python-release",
710
+ artifacts: pkg.packageOptions.trustedPublisher?.artifacts,
711
+ dependencies: pkg.packageOptions.internalDependencies,
712
+ }));
713
+ }
714
+
656
715
  private trustedPublisherPublications(
657
716
  options: PythonReleaseOptions,
658
717
  ): readonly PythonPublication[] {
@@ -683,7 +742,10 @@ export class DBXToolsPythonWorkspace extends Component {
683
742
  const linesBeforeAuthentication = [
684
743
  "# PyPI Trusted Publisher Setup Instructions",
685
744
  "",
686
- "Use a browser to audit and configure the PyPI trusted publishers below.",
745
+ "Use the system browser to audit and configure the PyPI trusted publishers below.",
746
+ "Do not use an in-app browser or embedded webview.",
747
+ "Do not visit GitHub or use the GitHub API or CLI. Every required GitHub owner, repository, workflow, environment, and branch value is provided below and is authoritative.",
748
+ "Use only PyPI pages for this task.",
687
749
  "",
688
750
  "## Audit and confirmation",
689
751
  "",
@@ -691,7 +753,7 @@ export class DBXToolsPythonWorkspace extends Component {
691
753
  "",
692
754
  "- Confirm that the active PyPI account can administer the listed projects and pending publishers.",
693
755
  "- Start at https://pypi.org/manage/projects/ and determine which listed projects exist.",
694
- "- Inspect every existing GitHub publisher for each project and compare its owner, repository name, workflow name, and environment name with the desired values below.",
756
+ "- On PyPI, inspect every existing GitHub Actions publisher for each project and compare its owner, repository name, workflow name, and environment name with the desired values below.",
695
757
  "- Identify duplicate and mismatched trusted publishers that must be replaced by removing the publisher entry and adding the correct one.",
696
758
  "- For projects that do not exist, identify the pending publisher that must be created.",
697
759
  "- Report the active account and a proposed reconciliation plan grouped by publishers that will be left unchanged, updated, replaced, created, or removed.",
@@ -700,9 +762,9 @@ export class DBXToolsPythonWorkspace extends Component {
700
762
  "",
701
763
  "## GitHub environment policy",
702
764
  "",
703
- `- Configure every environment listed below to permit deployments from the ${releaseBranch} branch.`,
704
- "- Branch-scoped release workflows use the environment names below; tag-only deployment policies reject their trusted-publishing jobs.",
705
- "- Keep the GitHub environment name synchronized with the PyPI trusted publisher environment.",
765
+ `- The supplied branch policy value is ${releaseBranch}.`,
766
+ "- Do not inspect or configure GitHub environments during this task.",
767
+ "- GitHub environment administration is a separate task; use the supplied values only when comparing PyPI publisher entries.",
706
768
  "",
707
769
  "## Authentication",
708
770
  "",
@@ -714,7 +776,7 @@ export class DBXToolsPythonWorkspace extends Component {
714
776
  "",
715
777
  "## Efficient browser workflow",
716
778
  "",
717
- "- Reuse or claim an existing PyPI browser tab when available.",
779
+ "- Reuse an existing PyPI tab in the system browser when available.",
718
780
  "- For an existing project, open its publisher page directly at https://pypi.org/manage/project/<project-name>/settings/publishing/.",
719
781
  "- For a project that does not exist, use the pending-publisher page at https://pypi.org/manage/account/publishing/.",
720
782
  "- Treat the publisher table shown after submission as authoritative confirmation of success, even if the navigation header unexpectedly appears signed out.",
@@ -769,7 +831,7 @@ export class DBXToolsPythonWorkspace extends Component {
769
831
  ],
770
832
  });
771
833
  project.root.addTask("pypiTrustedPublisherInstructions", {
772
- description: "Print browser-agent instructions for PyPI trusted publishers",
834
+ description: "Print system-browser instructions for PyPI trusted publishers",
773
835
  exec: `node ${helper}`,
774
836
  receiveArgs: true,
775
837
  });