@dbx-tools/projen 0.6.77 → 0.6.78

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 CHANGED
@@ -36,11 +36,11 @@ export { repoRoot, DEFAULT_PACKAGE_ROOTS, DiscoveredPackage } from "./src/packag
36
36
  export type { RecordedPackage } from "./src/packages.ts";
37
37
  export { PnpmWorkspaceState } from "./src/pnpm-workspace.ts";
38
38
  export type { Catalog, AllowBuilds, DBXToolsPNPMWorkspaceOptions } from "./src/pnpm-workspace.ts";
39
- export type { ApplyToProjectsOptions } from "./src/project.ts";
39
+ export type { DBXToolsProjectLanguage, DBXToolsProjectOptions, DBXToolsProject, ApplyToProjectsOptions } from "./src/project.ts";
40
40
  export { PackageIdentifier, PROJEN_VERSION, DBXToolsNodeProject, ROOT_INSTALL_ONLY_MIXIN, DBXToolsTypeScriptProject } from "./src/project-js.ts";
41
- export type { DBXToolsProject, DBXToolsProjectOptions, DBXToolsTypeScriptProjectOptions } from "./src/project-js.ts";
42
- export { DBXToolsPythonWorkspace } from "./src/project-py.ts";
43
- export type { PythonRepositoryOptions, PythonPackageOptions, PythonReleaseOptions, DBXToolsPythonWorkspaceOptions } from "./src/project-py.ts";
41
+ export type { DBXToolsJavaScriptProject, DBXToolsJavaScriptProjectOptions, DBXToolsTypeScriptProjectOptions } from "./src/project-js.ts";
42
+ export { DBXToolsPythonProject, DBXToolsPythonWorkspace } from "./src/project-py.ts";
43
+ export type { PythonRepositoryOptions, PythonPackageOptions, DBXToolsPythonProjectOptions, PythonReleaseOptions, DBXToolsPythonWorkspaceOptions } from "./src/project-py.ts";
44
44
  export { COMPILED_DIR, COMPILED_COMPILER_OPTIONS } from "./src/publish.ts";
45
45
  export { DBXToolsRelease } from "./src/release.ts";
46
46
  export type { StandaloneRelease, DBXToolsReleaseOptions } from "./src/release.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.77",
30
- "@dbx-tools/path": "0.6.77",
31
- "@dbx-tools/shared-core": "0.6.77",
29
+ "@dbx-tools/core": "0.6.78",
30
+ "@dbx-tools/path": "0.6.78",
31
+ "@dbx-tools/shared-core": "0.6.78",
32
32
  "commander": "^15.0.0",
33
33
  "concurrently": "^10.0.3",
34
34
  "constructs": "^10.6.0",
@@ -47,7 +47,7 @@
47
47
  },
48
48
  "main": "index.ts",
49
49
  "license": "Apache-2.0",
50
- "version": "0.6.77",
50
+ "version": "0.6.78",
51
51
  "types": "index.ts",
52
52
  "type": "module",
53
53
  "exports": {
@@ -250,6 +250,7 @@ export class PnpmWorkspaceState {
250
250
  */
251
251
  public resolveMembers(project: Project, extraMembers: readonly string[] = []): void {
252
252
  const members = project.subprojects
253
+ .filter((sub): sub is javascript.NodeProject => sub instanceof javascript.NodeProject)
253
254
  .map((sub) => toPosix(relative(project.outdir, sub.outdir)))
254
255
  .filter(Boolean);
255
256
  // `extraMembers` are workspace siblings NOT attached as subprojects (e.g. the
package/src/project-js.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * The dbx-tools project surface plus package tooling: the single
3
- * {@link DBXToolsProject} interface, the projen Node/TypeScript project classes,
3
+ * {@link DBXToolsJavaScriptProject} interface, the projen Node/TypeScript project classes,
4
4
  * naming, guards, manifest fields, and the shared root init.
5
5
  *
6
6
  * {@link DBXToolsNodeProject} (monorepo root) and {@link DBXToolsTypeScriptProject}
7
- * (a package, or a standalone compiling root) both implement {@link DBXToolsProject}.
7
+ * (a package, or a standalone compiling root) both implement {@link DBXToolsJavaScriptProject}.
8
8
  */
9
9
  import { existsSync, readdirSync } from "node:fs";
10
10
  import { dirname, join, relative, resolve } from "node:path";
@@ -41,13 +41,14 @@ import { DBXToolsRelease, type StandaloneRelease } from "./release.ts";
41
41
  import { AGNOSTIC_COMPILER_OPTIONS, PACKAGE_TAG_MIXINS, type PackageTag } from "./tags.ts";
42
42
  import { DBXToolsRootTsconfig } from "./tsconfig.ts";
43
43
  import { DBXToolsVsCode } from "./vscode.ts";
44
+ import type { DBXToolsProject, DBXToolsProjectOptions as CommonProjectOptions } from "./project.ts";
44
45
 
45
46
  /**
46
47
  * The dbx-tools project surface, backed by projen's Node toolchain. A single
47
48
  * interface for both the monorepo root and each package: it carries the
48
49
  * `dbxToolsConfig` component plus the npm-naming and root-only file components.
49
50
  */
50
- export interface DBXToolsProject extends javascript.NodeProject {
51
+ export interface DBXToolsJavaScriptProject extends DBXToolsProject, javascript.NodeProject {
51
52
  /** The package's `dbxToolsConfig` component (tags + `package.json` config). */
52
53
  readonly dbxToolsConfig: DBXToolsConfig;
53
54
  /** npm scope (the `@scope` in `@scope/pkg`), without the leading `@`. */
@@ -342,7 +343,9 @@ export const PROJEN_VERSION = "^0.101.16";
342
343
  * inherits the root's config rather than emitting its own. `name`/`defaultReleaseBranch`
343
344
  * are resolved/applied by the caller.
344
345
  */
345
- function defaultProjectOptions(options: DBXToolsProjectOptions): DBXToolsProjectOptions {
346
+ function defaultProjectOptions(
347
+ options: DBXToolsJavaScriptProjectOptions,
348
+ ): DBXToolsJavaScriptProjectOptions {
346
349
  const isRoot = options.parent === undefined;
347
350
  return {
348
351
  // Bun owns install/run/build/test locally and in CI. projen renders
@@ -420,7 +423,7 @@ function defaultProjectOptions(options: DBXToolsProjectOptions): DBXToolsProject
420
423
  * pristine array to seed a child's fresh one. Spread AFTER `...options`.
421
424
  */
422
425
  function copiedGitIgnoreOptions(
423
- options: DBXToolsProjectOptions,
426
+ options: DBXToolsJavaScriptProjectOptions,
424
427
  ): Pick<javascript.NodeProjectOptions, "gitIgnoreOptions"> {
425
428
  if (!options.gitIgnoreOptions?.ignorePatterns) return {};
426
429
  return {
@@ -460,8 +463,9 @@ function defaultTypeScriptProjectOptions(
460
463
  const DEV_DEPS_ROOT: string[] = ["typescript@^5.9.3", "@types/bun@^1.3.14"];
461
464
 
462
465
  /** Options for {@link DBXToolsNodeProject} (the monorepo root). */
463
- export interface DBXToolsProjectOptions
466
+ export interface DBXToolsJavaScriptProjectOptions
464
467
  extends
468
+ CommonProjectOptions,
465
469
  Partial<javascript.NodeProjectOptions>,
466
470
  DBXToolsConfigOptions,
467
471
  DBXToolsPNPMWorkspaceOptions {
@@ -529,7 +533,7 @@ export interface DBXToolsProjectOptions
529
533
 
530
534
  /** Options for {@link DBXToolsTypeScriptProject} (a package, or a compiling root). */
531
535
  export interface DBXToolsTypeScriptProjectOptions
532
- extends Partial<typescript.TypeScriptProjectOptions>, DBXToolsProjectOptions {
536
+ extends Partial<typescript.TypeScriptProjectOptions>, DBXToolsJavaScriptProjectOptions {
533
537
  /** Emit the projen-owned bun app scaffolding (`bunfig.toml`/`dev.ts`/`build.ts`). */
534
538
  readonly bunApp?: boolean;
535
539
  }
@@ -539,7 +543,11 @@ export interface DBXToolsTypeScriptProjectOptions
539
543
  * {@link DBXToolsTypeScriptProject} per `src`-bearing folder, then emits the
540
544
  * shared config, tasks, `pnpm-workspace.yaml`, and barrels-on-synth.
541
545
  */
542
- export class DBXToolsNodeProject extends javascript.NodeProject implements DBXToolsProject {
546
+ export class DBXToolsNodeProject
547
+ extends javascript.NodeProject
548
+ implements DBXToolsJavaScriptProject
549
+ {
550
+ readonly language = "javascript" as const;
543
551
  readonly scope: string;
544
552
  readonly dbxToolsConfig: DBXToolsConfig;
545
553
  pnpmWorkspace?: PnpmWorkspaceState;
@@ -548,7 +556,7 @@ export class DBXToolsNodeProject extends javascript.NodeProject implements DBXTo
548
556
  private readonly extraWorkspaceMembers: readonly string[];
549
557
  private readonly rootInstallOnly: boolean;
550
558
 
551
- constructor(options: DBXToolsProjectOptions = {}) {
559
+ constructor(options: DBXToolsJavaScriptProjectOptions = {}) {
552
560
  const { name, scope } = resolveIdentity(options);
553
561
  const releaseDefaults =
554
562
  options.release && options.releaseTrigger === undefined
@@ -622,8 +630,9 @@ export const ROOT_INSTALL_ONLY_MIXIN = mixin.create(
622
630
  */
623
631
  export class DBXToolsTypeScriptProject
624
632
  extends typescript.TypeScriptProject
625
- implements DBXToolsProject
633
+ implements DBXToolsJavaScriptProject
626
634
  {
635
+ readonly language = "javascript" as const;
627
636
  readonly scope: string;
628
637
  readonly dbxToolsConfig: DBXToolsConfig;
629
638
  pnpmWorkspace?: PnpmWorkspaceState;
@@ -816,7 +825,7 @@ class PrettierIgnoreGenerated extends Component {
816
825
  /** Default leading path segment stripped from a package's name (not its tag). */
817
826
  const DEFAULT_OMIT_RELATIVE_PREFIX = ["node"];
818
827
 
819
- /** Normalize the {@link DBXToolsProjectOptions.omitRelativePrefix} option to a slug list. */
828
+ /** Normalize the {@link DBXToolsJavaScriptProjectOptions.omitRelativePrefix} option to a slug list. */
820
829
  function resolveOmitRelativePrefix(option: OneOrMany<string> | undefined): string[] {
821
830
  const raw = option === undefined ? DEFAULT_OMIT_RELATIVE_PREFIX : option;
822
831
  const list = Array.isArray(raw) ? raw : [raw];
@@ -943,7 +952,7 @@ export function taskScript(_project: javascript.NodeProject, script: string, arg
943
952
  */
944
953
  function initProject(
945
954
  project: DBXToolsNodeProject | DBXToolsTypeScriptProject,
946
- options: DBXToolsProjectOptions,
955
+ options: DBXToolsJavaScriptProjectOptions,
947
956
  ): void {
948
957
  // projen's GithubProject seeds a `# replace this` SampleReadme on every
949
958
  // project. READMEs are hand-written and owned outside projen, so drop the
@@ -1101,7 +1110,7 @@ function initProject(
1101
1110
 
1102
1111
  // Already-attached subprojects, keyed by repo-relative member path.
1103
1112
  const rootAbs = resolve(project.outdir);
1104
- const existing = new Map<string, DBXToolsProject>();
1113
+ const existing = new Map<string, DBXToolsJavaScriptProject>();
1105
1114
  for (const sub of project.subprojects) {
1106
1115
  if (sub instanceof DBXToolsNodeProject || sub instanceof DBXToolsTypeScriptProject) {
1107
1116
  existing.set(toPosix(relative(rootAbs, sub.outdir)), sub);
@@ -1182,7 +1191,7 @@ class ChildGitignore extends IgnoreFile {
1182
1191
  */
1183
1192
  function swapChildGitignore(
1184
1193
  project: javascript.NodeProject,
1185
- options: DBXToolsProjectOptions,
1194
+ options: DBXToolsJavaScriptProjectOptions,
1186
1195
  ): void {
1187
1196
  project.tryRemoveFile(".gitignore");
1188
1197
  const fresh = new ChildGitignore(project, ".gitignore", {
@@ -11,7 +11,8 @@ import { IConstruct } from "constructs";
11
11
  import { Project } from "projen";
12
12
  import { project } from "..";
13
13
  import { toPosix } from "./packages.ts";
14
- import { DBXToolsProject, DBXToolsNodeProject, DBXToolsTypeScriptProject } from "./project-js.ts";
14
+ import type { DBXToolsProject } from "./project.ts";
15
+ import type { DBXToolsJavaScriptProject } from "./project-js.ts";
15
16
 
16
17
  /**
17
18
  * Guard: the construct is a projen {@link Project} - the base every builder here
@@ -27,11 +28,19 @@ export function isProject(): Predicate<IConstruct, Project> {
27
28
  return predicate.create((c: IConstruct): c is Project => Project.isProject(c));
28
29
  }
29
30
 
30
- /** Guard: the construct is a {@link DBXToolsProject} (a DBXTools Node or TypeScript project). */
31
+ /** Guard: the construct implements the language-agnostic {@link DBXToolsProject} contract. */
31
32
  export function isDBXToolsProject(): Predicate<IConstruct, DBXToolsProject> {
32
33
  return isProject().and(
33
34
  (project): project is DBXToolsProject =>
34
- project instanceof DBXToolsNodeProject || project instanceof DBXToolsTypeScriptProject,
35
+ (project as Partial<DBXToolsProject>).language === "javascript" ||
36
+ (project as Partial<DBXToolsProject>).language === "python",
37
+ );
38
+ }
39
+
40
+ /** Guard: the construct is a dbx-tools JavaScript/TypeScript project. */
41
+ export function isDBXToolsJavaScriptProject(): Predicate<IConstruct, DBXToolsJavaScriptProject> {
42
+ return isDBXToolsProject().and(
43
+ (project): project is DBXToolsJavaScriptProject => project.language === "javascript",
35
44
  );
36
45
  }
37
46
 
@@ -97,14 +106,16 @@ export function hasIdentifierScope(
97
106
 
98
107
  /**
99
108
  * Matches DBXTools packages carrying every listed tag (`dbxToolsConfig.tags`), narrowing
100
- * {@link Project} to {@link DBXToolsProject} (tags live only on DBXTools packages). Also the
109
+ * {@link Project} to {@link DBXToolsJavaScriptProject} (tags live only on JavaScript packages). Also the
101
110
  * guard backing each built-in {@link PACKAGE_TAG_MIXINS} entry. Keep it in the SAME `.and(...)`
102
111
  * as any name/path filter (or last when chaining) - a later non-tag `.and` re-widens to
103
112
  * {@link Project} and drops the narrowing.
104
113
  */
105
- export function hasTag(...tags: OneOrMany<PathMatchInput>): Predicate<IConstruct, DBXToolsProject> {
114
+ export function hasTag(
115
+ ...tags: OneOrMany<PathMatchInput>
116
+ ): Predicate<IConstruct, DBXToolsJavaScriptProject> {
106
117
  const matchers = projectMatchers(...tags);
107
- return isDBXToolsProject().and((project) =>
118
+ return isDBXToolsJavaScriptProject().and((project) =>
108
119
  matchers.every((matcher) => project.dbxToolsConfig.tags.some((tag) => matcher(tag))),
109
120
  );
110
121
  }
package/src/project-py.ts CHANGED
@@ -1,8 +1,9 @@
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, TextFile, type Project, javascript, vscode } from "projen";
3
+ import { Component, type Project, javascript, python, vscode } from "projen";
4
4
  import { GithubWorkflow } from "projen/lib/github";
5
5
  import { JobPermission } from "projen/lib/github/workflows-model";
6
+ import type { DBXToolsProject, DBXToolsProjectOptions } from "./project.ts";
6
7
 
7
8
  /** Git location used by direct `#subdirectory=` package dependencies. */
8
9
  export interface PythonRepositoryOptions {
@@ -12,7 +13,7 @@ export interface PythonRepositoryOptions {
12
13
  }
13
14
 
14
15
  /** One independently installable Python package in the uv workspace. */
15
- export interface PythonPackageOptions {
16
+ export interface PythonPackageOptions extends DBXToolsProjectOptions {
16
17
  readonly directory: string;
17
18
  readonly name: string;
18
19
  readonly module: string;
@@ -20,10 +21,19 @@ export interface PythonPackageOptions {
20
21
  readonly dependencies?: readonly string[];
21
22
  }
22
23
 
24
+ /** Options for one projen-native Python workspace member. */
25
+ export interface DBXToolsPythonProjectOptions extends DBXToolsProjectOptions {
26
+ readonly parent: Project;
27
+ readonly package: PythonPackageOptions;
28
+ readonly repository: Required<PythonRepositoryOptions>;
29
+ readonly requiresPython: string;
30
+ }
31
+
23
32
  /** Python release workflow configuration. */
24
33
  export interface PythonReleaseOptions {
25
34
  readonly workflowName?: string;
26
- readonly environment?: string;
35
+ /** GitHub environment by Python distribution name. Defaults to `pypi-<name>`. */
36
+ readonly environments?: Readonly<Record<string, string>>;
27
37
  readonly environmentUrl?: string;
28
38
  }
29
39
 
@@ -69,27 +79,112 @@ function projectVscode(project: Project): vscode.VsCode | undefined {
69
79
  return (project as Project & { readonly vscode?: vscode.VsCode }).vscode;
70
80
  }
71
81
 
82
+ /** A Python package implemented with projen's `PythonProject` and uv backend. */
83
+ export class DBXToolsPythonProject extends python.PythonProject implements DBXToolsProject {
84
+ readonly language = "python" as const;
85
+ readonly packageOptions: PythonPackageOptions;
86
+ readonly uv: python.Uv;
87
+
88
+ constructor(options: DBXToolsPythonProjectOptions) {
89
+ const pkg = options.package;
90
+ super({
91
+ parent: options.parent,
92
+ outdir: pythonPackagePath(options.repository, pkg.directory),
93
+ name: pkg.name,
94
+ moduleName: pkg.module,
95
+ authorName: "",
96
+ authorEmail: "",
97
+ version: "0.0.0",
98
+ description: pkg.description,
99
+ github: false,
100
+ sample: false,
101
+ pytest: false,
102
+ projenrcPython: false,
103
+ projenrcJs: false,
104
+ projenrcTs: false,
105
+ pip: false,
106
+ venv: false,
107
+ setuptools: false,
108
+ poetry: false,
109
+ uv: true,
110
+ projenCommand: options.parent.projenCommand,
111
+ uvOptions: {
112
+ project: {
113
+ name: pkg.name,
114
+ version: "0.0.0",
115
+ description: pkg.description,
116
+ readme: "README.md",
117
+ requiresPython: options.requiresPython,
118
+ dependencies: [...(pkg.dependencies ?? [])],
119
+ urls: {
120
+ Source: `${options.repository.url.replace(/\.git$/, "")}/tree/${options.repository.ref}/${pythonPackagePath(options.repository, pkg.directory)}`,
121
+ },
122
+ },
123
+ buildSystem: {
124
+ requires: ["uv_build>=0.11.28,<0.12.0"],
125
+ buildBackend: "uv_build",
126
+ },
127
+ uv: {
128
+ buildBackend: {
129
+ moduleName: pkg.module,
130
+ moduleRoot: "src",
131
+ namespace: true,
132
+ },
133
+ },
134
+ },
135
+ });
136
+ this.packageOptions = pkg;
137
+ if (!(this.packagingManager instanceof python.Uv)) {
138
+ throw new Error(`Expected uv packaging for ${pkg.name}`);
139
+ }
140
+ this.uv = this.packagingManager;
141
+ this.uv.file.addDeletionOverride("project.authors");
142
+ this.uv.file.addDeletionOverride("dependency-groups");
143
+ this.uv.file.readonly = true;
144
+
145
+ for (const path of [".gitattributes", ".gitignore"]) {
146
+ this.tryRemoveFile(path);
147
+ }
148
+ }
149
+
150
+ /** The root workspace owns dependency installation for every member. */
151
+ public override postSynthesize(): void {}
152
+ }
153
+
72
154
  /**
73
- * Generates a root uv workspace, member package metadata, Python tasks, editor
74
- * interpreter selection, and an optional trusted-publishing workflow.
155
+ * Generates a root uv workspace, projen-native Python member projects, Python
156
+ * tasks, editor interpreter selection, and an optional publishing workflow.
75
157
  */
76
158
  export class DBXToolsPythonWorkspace extends Component {
77
- readonly packages: readonly PythonPackageOptions[];
159
+ readonly packages: readonly DBXToolsPythonProject[];
78
160
  readonly repository: Required<PythonRepositoryOptions>;
79
161
  readonly requiresPython: string;
162
+ readonly file: python.PyprojectTomlFile;
80
163
 
81
164
  constructor(project: javascript.NodeProject, options: DBXToolsPythonWorkspaceOptions) {
82
165
  super(project);
83
- this.packages = options.packages;
84
166
  this.repository = {
85
167
  url: options.repository.url,
86
168
  ref: options.repository.ref ?? "main",
87
169
  root: options.repository.root ?? "packages/py",
88
170
  };
89
171
  this.requiresPython = options.requiresPython ?? ">=3.10";
90
-
91
- this.emitWorkspace(project, options);
92
- this.emitPackages(project);
172
+ this.file = this.emitWorkspace(project, options);
173
+ this.packages = options.packages.map(
174
+ (pkg) =>
175
+ new DBXToolsPythonProject({
176
+ parent: project,
177
+ package: pkg,
178
+ repository: this.repository,
179
+ requiresPython: this.requiresPython,
180
+ }),
181
+ );
182
+ for (const pkg of this.packages) {
183
+ const pyproject = `/${pythonPackagePath(this.repository, pkg.packageOptions.directory)}/pyproject.toml`;
184
+ project.gitignore.include(pyproject);
185
+ project.gitattributes.addAttributes(pyproject, "linguist-generated");
186
+ project.prettier?.addIgnorePattern(pyproject.slice(1));
187
+ }
93
188
  this.addTasks(project, options);
94
189
 
95
190
  const interpreterPath = options.interpreterPath ?? "${workspaceFolder}/.venv/bin/python";
@@ -115,81 +210,45 @@ export class DBXToolsPythonWorkspace extends Component {
115
210
  private emitWorkspace(
116
211
  project: javascript.NodeProject,
117
212
  options: DBXToolsPythonWorkspaceOptions,
118
- ): void {
119
- const devDependencies = options.devDependencies ?? DEFAULT_DEV_DEPENDENCIES;
213
+ ): python.PyprojectTomlFile {
120
214
  const testPaths = options.testPaths ?? [this.repository.root];
121
- const ruffTarget = options.ruffTarget ?? "py310";
122
215
  const perFileIgnores = options.ruffPerFileIgnores ?? {};
123
- new TextFile(project, "pyproject.toml", {
124
- marker: false,
125
- readonly: true,
126
- lines: [
127
- '# ~~ Generated by projen. To modify, edit .projenrc.ts and run "bunx projen".',
128
- "[project]",
129
- `name = ${quote(options.workspaceName ?? `${string.toSlug(project.name)}-python-workspace`)}`,
130
- 'version = "0.0.0"',
131
- `requires-python = ${quote(this.requiresPython)}`,
132
- "dependencies = []",
133
- "",
134
- "[dependency-groups]",
135
- `dev = [${devDependencies.map(quote).join(", ")}]`,
136
- "",
137
- "[tool.uv]",
138
- "package = false",
139
- "",
140
- "[tool.uv.workspace]",
141
- `members = [${quote(`${this.repository.root}/*`)}]`,
142
- "",
143
- "[tool.uv.sources]",
144
- ...this.packages.map((pkg) => `${pkg.name} = { workspace = true }`),
145
- "",
146
- "[tool.pytest.ini_options]",
147
- 'asyncio_mode = "auto"',
148
- `testpaths = [${testPaths.map(quote).join(", ")}]`,
149
- "",
150
- "[tool.ruff]",
151
- `target-version = ${quote(ruffTarget)}`,
152
- "line-length = 100",
153
- "",
154
- "[tool.ruff.lint.per-file-ignores]",
155
- ...Object.entries(perFileIgnores).map(
156
- ([path, rules]) => `${quote(path)} = [${rules.map(quote).join(", ")}]`,
157
- ),
158
- "",
159
- ],
216
+ const file = new python.PyprojectTomlFile(project, {
217
+ project: {
218
+ name: options.workspaceName ?? `${string.toSlug(project.name)}-python-workspace`,
219
+ version: "0.0.0",
220
+ requiresPython: this.requiresPython,
221
+ dependencies: [],
222
+ },
223
+ dependencyGroups: {
224
+ dev: [...(options.devDependencies ?? DEFAULT_DEV_DEPENDENCIES)],
225
+ },
226
+ tool: {
227
+ uv: python.uvConfig.toJson_UvConfiguration({
228
+ package: false,
229
+ workspace: { members: [`${this.repository.root}/*`] },
230
+ }),
231
+ pytest: {
232
+ ini_options: {
233
+ asyncio_mode: "auto",
234
+ testpaths: testPaths,
235
+ },
236
+ },
237
+ ruff: {
238
+ "target-version": options.ruffTarget ?? "py310",
239
+ "line-length": 100,
240
+ lint: {
241
+ "per-file-ignores": perFileIgnores,
242
+ },
243
+ },
244
+ },
160
245
  });
161
- }
162
-
163
- private emitPackages(project: javascript.NodeProject): void {
164
- for (const pkg of this.packages) {
165
- new TextFile(project, `${this.packagePath(pkg.directory)}/pyproject.toml`, {
166
- marker: false,
167
- readonly: true,
168
- lines: [
169
- '# ~~ Generated by projen. To modify, edit .projenrc.ts and run "bunx projen".',
170
- "[project]",
171
- `name = ${quote(pkg.name)}`,
172
- 'version = "0.0.0"',
173
- `description = ${quote(pkg.description)}`,
174
- 'readme = "README.md"',
175
- `requires-python = ${quote(this.requiresPython)}`,
176
- `dependencies = [${(pkg.dependencies ?? []).map(quote).join(", ")}]`,
177
- "",
178
- "[project.urls]",
179
- `Source = ${quote(`${this.repository.url.replace(/\.git$/, "")}/tree/${this.repository.ref}/${this.packagePath(pkg.directory)}`)}`,
180
- "",
181
- "[build-system]",
182
- 'requires = ["uv_build>=0.11.28,<0.12.0"]',
183
- 'build-backend = "uv_build"',
184
- "",
185
- "[tool.uv.build-backend]",
186
- `module-name = ${quote(pkg.module)}`,
187
- 'module-root = "src"',
188
- "namespace = true",
189
- "",
190
- ],
191
- });
192
- }
246
+ file.addOverride(
247
+ "tool.uv.sources",
248
+ Object.fromEntries(options.packages.map((pkg) => [pkg.name, { workspace: true }])),
249
+ );
250
+ file.readonly = true;
251
+ return file;
193
252
  }
194
253
 
195
254
  private addTasks(project: javascript.NodeProject, options: DBXToolsPythonWorkspaceOptions): void {
@@ -247,12 +306,20 @@ export class DBXToolsPythonWorkspace extends Component {
247
306
  env: { VERSION: "${{ inputs.version }}" },
248
307
  run: this.renderVersionStampScript(),
249
308
  },
250
- { name: "Build distributions", run: "uv build --all-packages" },
309
+ {
310
+ name: "Build distributions",
311
+ run: this.packages
312
+ .map(
313
+ (pkg) =>
314
+ `uv build --package ${pkg.packageOptions.name} --out-dir dist/${pkg.packageOptions.directory}`,
315
+ )
316
+ .join("\n"),
317
+ },
251
318
  {
252
319
  name: "Validate distributions",
253
320
  run: [
254
- `test "$(find dist -maxdepth 1 -type f | wc -l | tr -d ' ')" -eq ${this.packages.length * 2}`,
255
- "uvx twine check dist/*",
321
+ `test "$(find dist -type f \\( -name '*.whl' -o -name '*.tar.gz' \\) | wc -l | tr -d ' ')" -eq ${this.packages.length * 2}`,
322
+ "uvx twine check dist/*/*.whl dist/*/*.tar.gz",
256
323
  ].join("\n"),
257
324
  },
258
325
  {
@@ -262,29 +329,34 @@ export class DBXToolsPythonWorkspace extends Component {
262
329
  },
263
330
  ],
264
331
  });
265
- workflow.addJob("publish", {
266
- if: "${{ inputs.publish }}",
267
- needs: ["build"],
268
- environment: {
269
- name: options.environment ?? "pypi",
270
- url: options.environmentUrl ?? "https://pypi.org/",
271
- },
272
- runsOn: ["ubuntu-latest"],
273
- permissions: { idToken: JobPermission.WRITE },
274
- timeoutMinutes: 10,
275
- steps: [
276
- {
277
- name: "Download distributions",
278
- uses: "actions/download-artifact@v8",
279
- with: { name: "python-distributions", path: "dist" },
280
- },
281
- {
282
- name: "Publish to PyPI",
283
- uses: "pypa/gh-action-pypi-publish@release/v1",
284
- with: { "packages-dir": "dist" },
332
+ for (const pkg of this.packages) {
333
+ workflow.addJob(`publish-${pkg.packageOptions.directory}`, {
334
+ if: "${{ inputs.publish }}",
335
+ needs: ["build"],
336
+ environment: {
337
+ name:
338
+ options.environments?.[pkg.packageOptions.name] ?? `pypi-${pkg.packageOptions.name}`,
339
+ url:
340
+ options.environmentUrl ??
341
+ `https://pypi.org/project/${pkg.packageOptions.name.replaceAll("_", "-")}/`,
285
342
  },
286
- ],
287
- });
343
+ runsOn: ["ubuntu-latest"],
344
+ permissions: { idToken: JobPermission.WRITE },
345
+ timeoutMinutes: 10,
346
+ steps: [
347
+ {
348
+ name: "Download distributions",
349
+ uses: "actions/download-artifact@v8",
350
+ with: { name: "python-distributions", path: "dist" },
351
+ },
352
+ {
353
+ name: `Publish ${pkg.packageOptions.name} to PyPI`,
354
+ uses: "pypa/gh-action-pypi-publish@release/v1",
355
+ with: { "packages-dir": `dist/${pkg.packageOptions.directory}` },
356
+ },
357
+ ],
358
+ });
359
+ }
288
360
  }
289
361
 
290
362
  private renderVersionStampScript(): string {
package/src/project.ts CHANGED
@@ -5,14 +5,27 @@
5
5
  import { type PathMatchInput } from "@dbx-tools/path";
6
6
  import { object, type OneOrMany } from "@dbx-tools/shared-core";
7
7
  import { type IConstruct } from "constructs";
8
- import { Project } from "projen";
8
+ import { Project, type ProjectOptions } from "projen";
9
9
  import * as mixin from "./mixin.ts";
10
10
  import * as projectPredicate from "./project-predicate.ts";
11
- import type { DBXToolsProject } from "./project-js.ts";
11
+ import type { DBXToolsJavaScriptProject } from "./project-js.ts";
12
12
 
13
13
  export * from "./project-js.ts";
14
14
  export * from "./project-py.ts";
15
15
 
16
+ /** Runtime family implemented by a dbx-tools project. */
17
+ export type DBXToolsProjectLanguage = "javascript" | "python";
18
+
19
+ /** Options shared by every dbx-tools project implementation. */
20
+ export interface DBXToolsProjectOptions extends Partial<
21
+ Pick<ProjectOptions, "name" | "parent" | "outdir">
22
+ > {}
23
+
24
+ /** Minimal language-agnostic project contract. */
25
+ export interface DBXToolsProject extends Project {
26
+ readonly language: DBXToolsProjectLanguage;
27
+ }
28
+
16
29
  /** Filters selecting which projects an {@link applyToProjects} call runs against. */
17
30
  export interface ApplyToProjectsOptions {
18
31
  /** Include plain projen projects. Defaults to DBXTools projects only. */
@@ -45,8 +58,8 @@ type ApplyToAllProjectsOptions = Omit<ApplyToProjectsOptions, "includeNonDBXTool
45
58
  export function applyToProjects(
46
59
  construct: IConstruct,
47
60
  ...args:
48
- | [ApplyToDBXToolsProjectsOptions, ...OneOrMany<(project: DBXToolsProject) => void>]
49
- | OneOrMany<(project: DBXToolsProject) => void>
61
+ | [ApplyToDBXToolsProjectsOptions, ...OneOrMany<(project: DBXToolsJavaScriptProject) => void>]
62
+ | OneOrMany<(project: DBXToolsJavaScriptProject) => void>
50
63
  ): void;
51
64
 
52
65
  export function applyToProjects(
@@ -71,7 +84,7 @@ export function applyToProjects<P extends Project>(
71
84
  const callbacks = (hasOptions ? rest : args) as OneOrMany<(project: Project) => void>;
72
85
  let predicate = projectPredicate.isProject();
73
86
  if (!options?.includeNonDBXToolsProjects) {
74
- predicate = predicate.and(projectPredicate.isDBXToolsProject());
87
+ predicate = predicate.and(projectPredicate.isDBXToolsJavaScriptProject());
75
88
  }
76
89
  if (!options?.includeRoots) predicate = predicate.and((project) => project.parent != null);
77
90
  if (options?.identifierPackageName) {
package/src/publish.ts CHANGED
@@ -29,7 +29,7 @@
29
29
  */
30
30
  import type { javascript } from "projen";
31
31
  import { typescript } from "projen";
32
- import { isDBXToolsProject } from "./project-predicate.ts";
32
+ import { isDBXToolsJavaScriptProject } from "./project-predicate.ts";
33
33
  import { addPackageFiles, applyCompilerOptions, applyIncludes } from "./project.ts";
34
34
 
35
35
  /** Directory `tsc` emits into, and the root of every published entry point. */
@@ -63,7 +63,7 @@ export const COMPILED_COMPILER_OPTIONS: javascript.TypeScriptCompilerOptions = {
63
63
  */
64
64
  export function publishesCompiled(pkg: javascript.NodeProject): boolean {
65
65
  if (!(pkg instanceof typescript.TypeScriptProject) || !pkg.parent) return false;
66
- return isDBXToolsProject()(pkg) && !pkg.dbxToolsConfig.tags.includes("ui");
66
+ return isDBXToolsJavaScriptProject()(pkg) && !pkg.dbxToolsConfig.tags.includes("ui");
67
67
  }
68
68
 
69
69
  /** The `./lib/...` stem of a source path, or `undefined` if it is not TypeScript. */