@dbx-tools/projen 0.9.38 → 0.9.43
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 +73 -9
- package/index.ts +5 -3
- package/package.json +4 -4
- package/src/bun-workflow.ts +6 -166
- package/src/codegen.ts +3 -3
- package/src/project-js.ts +8 -44
- package/src/project-py.ts +52 -17
- package/src/release-context.ts +2 -33
- package/src/release-options.ts +82 -0
- package/src/release.ts +114 -63
- package/tasks/lib/publish-python.ts +16 -2
- package/tasks/lib/python-node-bindings.ts +30 -5
- package/tasks/lib/release.ts +97 -31
- package/tasks/release-options.ts +32 -0
- package/tasks/release.ts +1 -1
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-run release policy shared by the Projen task and tag-triggered workflow.
|
|
3
|
+
*
|
|
4
|
+
* This module owns defaults and the annotated-tag contract. Reuse these values
|
|
5
|
+
* when adding release steps so local flags and CI cannot drift. Registry URLs
|
|
6
|
+
* and local installation choices never belong in the public tag annotation.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import { json } from "@dbx-tools/shared-core";
|
|
11
|
+
|
|
12
|
+
/** Publication scopes accepted by the release task; auto preserves normal publication. */
|
|
13
|
+
export const RELEASE_PUBLISH_TARGETS = ["auto", "npm", "pypi", "local", "none"] as const;
|
|
14
|
+
|
|
15
|
+
/** One publication scope accepted by the release task. */
|
|
16
|
+
export type ReleasePublishTarget = (typeof RELEASE_PUBLISH_TARGETS)[number];
|
|
17
|
+
|
|
18
|
+
/** Native Projen dependency-install modes available during local release preparation. */
|
|
19
|
+
export const RELEASE_INSTALL_MODES = ["auto", "always", "never"] as const;
|
|
20
|
+
|
|
21
|
+
/** Local dependency-install behavior; auto retains Projen's normal trigger policy. */
|
|
22
|
+
export type ReleaseInstallMode = (typeof RELEASE_INSTALL_MODES)[number];
|
|
23
|
+
|
|
24
|
+
/** CI steps selected by an immutable release annotation. */
|
|
25
|
+
export interface ReleaseStepSelection {
|
|
26
|
+
readonly npm: boolean;
|
|
27
|
+
readonly pypi: boolean;
|
|
28
|
+
readonly docs: boolean;
|
|
29
|
+
readonly validation: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Flags consumed by the shared release-selection policy. */
|
|
33
|
+
export interface ReleaseSelectionOptions {
|
|
34
|
+
readonly publish?: ReleasePublishTarget;
|
|
35
|
+
readonly npm?: boolean;
|
|
36
|
+
readonly pypi?: boolean;
|
|
37
|
+
readonly docs?: boolean;
|
|
38
|
+
readonly validation?: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const ANNOTATION_MARKER = "dbx-tools-release:";
|
|
42
|
+
|
|
43
|
+
/** Derive public build/publication steps without mutating repository configuration. */
|
|
44
|
+
export function releaseStepSelection(options: ReleaseSelectionOptions = {}): ReleaseStepSelection {
|
|
45
|
+
const target = options.publish ?? "auto";
|
|
46
|
+
return {
|
|
47
|
+
npm: (target === "auto" || target === "npm") && options.npm !== false,
|
|
48
|
+
pypi: (target === "auto" || target === "pypi") && options.pypi !== false,
|
|
49
|
+
docs: options.docs ?? (target !== "local" && target !== "none"),
|
|
50
|
+
validation: options.validation ?? true,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Record non-default CI selections alongside the existing annotated release tag. */
|
|
55
|
+
export function releaseTagAnnotation(tag: string, selection: ReleaseStepSelection): string {
|
|
56
|
+
if (selection.npm && selection.pypi && selection.docs && selection.validation) return tag;
|
|
57
|
+
return `${tag}\n\n${ANNOTATION_MARKER} ${JSON.stringify(selection)}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Read the release selection, rejecting malformed or ambiguous annotated policy. */
|
|
61
|
+
export function parseReleaseTagAnnotation(annotation: string): ReleaseStepSelection {
|
|
62
|
+
const selections = annotation.split("\n").filter((line) => line.startsWith(ANNOTATION_MARKER));
|
|
63
|
+
if (selections.length === 0) return releaseStepSelection();
|
|
64
|
+
if (selections.length !== 1) throw new Error("Release tag contains multiple step selections");
|
|
65
|
+
const value = json.parseRecord(selections[0]!.slice(ANNOTATION_MARKER.length));
|
|
66
|
+
if (
|
|
67
|
+
!value ||
|
|
68
|
+
Object.keys(value).length !== 4 ||
|
|
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 };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Whether the selected release should inspect and publish configured local registries. */
|
|
80
|
+
export function releasePublishesLocally(target: ReleasePublishTarget = "auto"): boolean {
|
|
81
|
+
return target === "auto" || target === "local";
|
|
82
|
+
}
|
package/src/release.ts
CHANGED
|
@@ -5,7 +5,8 @@ import { GithubWorkflow } from "projen/lib/github";
|
|
|
5
5
|
import { JobPermission, type Job, type JobStep } from "projen/lib/github/workflows-model";
|
|
6
6
|
import { BUN_VERSION } from "./bun-workflow.ts";
|
|
7
7
|
import { projectReleaseBranch, taskCommand, type DBXToolsJavaScriptProject } from "./project-js.ts";
|
|
8
|
-
import { RELEASE_VERSION
|
|
8
|
+
import { RELEASE_VERSION } from "./release-context.ts";
|
|
9
|
+
import type { ReleaseStepSelection } from "./release-options.ts";
|
|
9
10
|
|
|
10
11
|
const NODE_VERSION = "24";
|
|
11
12
|
const NPM_VERSION = "11.4.2";
|
|
@@ -17,7 +18,7 @@ const releaseWorkflows = new WeakMap<DBXToolsJavaScriptProject, GithubWorkflow>(
|
|
|
17
18
|
export interface ReleaseDocsOptions {
|
|
18
19
|
readonly siteUrl: string;
|
|
19
20
|
readonly base?: string;
|
|
20
|
-
/**
|
|
21
|
+
/** Documentation generation steps run in the shared release build after validation. */
|
|
21
22
|
readonly prepareSteps: readonly JobStep[];
|
|
22
23
|
/** Repository-defined documentation build and validation steps. */
|
|
23
24
|
readonly buildSteps: readonly JobStep[];
|
|
@@ -37,6 +38,8 @@ export interface DBXToolsReleaseOptions {
|
|
|
37
38
|
readonly pythonRoot?: string;
|
|
38
39
|
/** Repository task names run after VERSION verification and before publication. */
|
|
39
40
|
readonly validationTasks?: readonly string[];
|
|
41
|
+
/** Repository prerequisites installed before release validation and artifact builds. */
|
|
42
|
+
readonly setupSteps?: readonly JobStep[];
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
/** Locate the unified workflow when release generation is enabled. */
|
|
@@ -44,7 +47,7 @@ export function tryReleaseWorkflow(project: DBXToolsJavaScriptProject): GithubWo
|
|
|
44
47
|
return releaseWorkflows.get(project);
|
|
45
48
|
}
|
|
46
49
|
|
|
47
|
-
/** Tag pattern
|
|
50
|
+
/** Tag pattern accepted by the unified release workflow. */
|
|
48
51
|
export function releaseTagPattern(project: DBXToolsJavaScriptProject): string {
|
|
49
52
|
const prefix = releaseTagPrefixes.get(project);
|
|
50
53
|
if (!prefix) throw new Error("Release workflow is not configured");
|
|
@@ -57,11 +60,25 @@ export function releaseCondition(prerequisites: readonly string[] = []): string
|
|
|
57
60
|
return `\${{ always() && ${prerequisites.map((condition) => `(${condition})`).join(" && ")} }}`;
|
|
58
61
|
}
|
|
59
62
|
|
|
60
|
-
/**
|
|
61
|
-
export function
|
|
63
|
+
/** Gate shared-build steps using the same per-run flag, preserving their existing conditions. */
|
|
64
|
+
export function releaseBuildSteps(
|
|
65
|
+
flag: keyof ReleaseStepSelection,
|
|
66
|
+
steps: readonly JobStep[],
|
|
67
|
+
): JobStep[] {
|
|
68
|
+
return steps.map((step) => ({
|
|
69
|
+
...step,
|
|
70
|
+
if: `\${{ success() && steps.release.outputs.${flag} == 'true'${step.if ? ` && (${step.if.replace(/^\s*\$\{\{\s*|\s*\}\}\s*$/g, "")})` : ""} }}`,
|
|
71
|
+
}));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Artifact-only publication runtime and npm authentication; no workspace installation. */
|
|
75
|
+
export function nodeReleaseSetupSteps(): readonly JobStep[] {
|
|
62
76
|
return [
|
|
63
|
-
|
|
64
|
-
|
|
77
|
+
{
|
|
78
|
+
name: "Setup Bun",
|
|
79
|
+
uses: "oven-sh/setup-bun@v2",
|
|
80
|
+
with: { "bun-version": BUN_VERSION },
|
|
81
|
+
},
|
|
65
82
|
{
|
|
66
83
|
name: "Setup Node.js",
|
|
67
84
|
uses: "actions/setup-node@v6",
|
|
@@ -75,26 +92,32 @@ export function nodeReleaseSetupSteps(project: DBXToolsJavaScriptProject): reado
|
|
|
75
92
|
];
|
|
76
93
|
}
|
|
77
94
|
|
|
78
|
-
/** Install uv
|
|
95
|
+
/** Install uv for Python release builds. */
|
|
79
96
|
export function uvSetupStep(): JobStep {
|
|
80
97
|
return {
|
|
81
98
|
name: "Setup uv",
|
|
82
99
|
uses: "astral-sh/setup-uv@v7",
|
|
83
|
-
with: {
|
|
84
|
-
"enable-cache": true,
|
|
85
|
-
"cache-dependency-glob": "**/pyproject.toml",
|
|
86
|
-
},
|
|
87
100
|
};
|
|
88
101
|
}
|
|
89
102
|
|
|
90
103
|
function refreshDocsRegistryDependencies(workflow: GithubWorkflow): void {
|
|
91
|
-
const docs = workflow.getJob("
|
|
104
|
+
const docs = workflow.getJob("deploy-docs") as Job | undefined;
|
|
92
105
|
if (!docs) return;
|
|
93
106
|
const registryJobs = Object.keys(workflow.jobs).filter(
|
|
94
107
|
(name) => name === "publish-node" || name.startsWith("publish-pypi-"),
|
|
95
108
|
);
|
|
96
109
|
if (registryJobs.length > 0) {
|
|
97
|
-
workflow.updateJob("
|
|
110
|
+
workflow.updateJob("deploy-docs", {
|
|
111
|
+
...docs,
|
|
112
|
+
needs: ["build-release", ...registryJobs],
|
|
113
|
+
if: releaseCondition([
|
|
114
|
+
"needs['build-release'].result == 'success'",
|
|
115
|
+
"needs['build-release'].outputs.docs == 'true'",
|
|
116
|
+
...registryJobs.map(
|
|
117
|
+
(name) => `needs['${name}'].result == 'success' || needs['${name}'].result == 'skipped'`,
|
|
118
|
+
),
|
|
119
|
+
]),
|
|
120
|
+
});
|
|
98
121
|
}
|
|
99
122
|
}
|
|
100
123
|
|
|
@@ -106,7 +129,7 @@ export function refreshReleaseDocsDependencies(project: DBXToolsJavaScriptProjec
|
|
|
106
129
|
}
|
|
107
130
|
}
|
|
108
131
|
|
|
109
|
-
function
|
|
132
|
+
function releaseBuildJob(
|
|
110
133
|
project: DBXToolsJavaScriptProject,
|
|
111
134
|
tagPrefix: string,
|
|
112
135
|
releaseBranch: string,
|
|
@@ -115,12 +138,21 @@ function verifyContextJob(
|
|
|
115
138
|
return {
|
|
116
139
|
runsOn: ["ubuntu-latest"],
|
|
117
140
|
permissions: { contents: JobPermission.READ },
|
|
118
|
-
timeoutMinutes:
|
|
119
|
-
env: {
|
|
141
|
+
timeoutMinutes: 60,
|
|
142
|
+
env: {
|
|
143
|
+
BUN_VERSION,
|
|
144
|
+
CI: "true",
|
|
145
|
+
...(options.docs
|
|
146
|
+
? { DOCS_SITE_URL: options.docs.siteUrl, DOCS_BASE: options.docs.base ?? "/" }
|
|
147
|
+
: {}),
|
|
148
|
+
},
|
|
120
149
|
outputs: {
|
|
121
150
|
release_tag: { stepId: "release", outputName: "release_tag" },
|
|
122
151
|
expected_sha: { stepId: "release", outputName: "expected_sha" },
|
|
123
152
|
release_version: { stepId: "release", outputName: "release_version" },
|
|
153
|
+
npm: { stepId: "release", outputName: "npm" },
|
|
154
|
+
pypi: { stepId: "release", outputName: "pypi" },
|
|
155
|
+
docs: { stepId: "release", outputName: "docs" },
|
|
124
156
|
},
|
|
125
157
|
steps: [
|
|
126
158
|
github.WorkflowSteps.checkout({
|
|
@@ -155,28 +187,70 @@ function verifyContextJob(
|
|
|
155
187
|
echo "release_tag=$RELEASE_TAG" >> "$GITHUB_OUTPUT"
|
|
156
188
|
echo "expected_sha=$RELEASE_SHA" >> "$GITHUB_OUTPUT"
|
|
157
189
|
echo "release_version=$RELEASE_VERSION" >> "$GITHUB_OUTPUT"
|
|
190
|
+
bun node_modules/@dbx-tools/projen/tasks/release-options.ts --tag "$RELEASE_TAG" --output "$GITHUB_OUTPUT"
|
|
158
191
|
`
|
|
159
192
|
// ============================================================================
|
|
160
193
|
),
|
|
161
194
|
},
|
|
162
|
-
...(options.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
195
|
+
...(options.setupSteps ?? []),
|
|
196
|
+
...releaseBuildSteps(
|
|
197
|
+
"validation",
|
|
198
|
+
(options.validationTasks ?? []).map((task) => ({
|
|
199
|
+
name: `Validate ${task}`,
|
|
200
|
+
run: `bun run ${task}`,
|
|
201
|
+
})),
|
|
202
|
+
),
|
|
203
|
+
...(options.nodeRelease === false
|
|
204
|
+
? []
|
|
205
|
+
: releaseBuildSteps("npm", [
|
|
206
|
+
{
|
|
207
|
+
name: "Build npm archives",
|
|
208
|
+
env: { RELEASE_VERSION: "${{ steps.release.outputs.release_version }}" },
|
|
209
|
+
run: [
|
|
210
|
+
"bun run compile",
|
|
211
|
+
'bun node_modules/@dbx-tools/projen/tasks/publish.ts "$RELEASE_VERSION" --skip-compile --output .release/npm',
|
|
212
|
+
"bun build node_modules/@dbx-tools/projen/tasks/publish-npm.ts --target=bun --outfile=.release/npm/publish-npm.mjs",
|
|
213
|
+
].join("\n"),
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
name: "Upload npm archives",
|
|
217
|
+
uses: "actions/upload-artifact@v4",
|
|
218
|
+
with: { name: "release-npm", path: ".release/npm", "if-no-files-found": "error" },
|
|
219
|
+
},
|
|
220
|
+
])),
|
|
221
|
+
...(options.docs
|
|
222
|
+
? releaseBuildSteps("docs", [
|
|
223
|
+
...options.docs.prepareSteps,
|
|
224
|
+
...options.docs.buildSteps,
|
|
225
|
+
{
|
|
226
|
+
name: "Upload Pages artifact",
|
|
227
|
+
uses: "actions/upload-pages-artifact@v4",
|
|
228
|
+
with: { path: options.docs.artifactPath },
|
|
229
|
+
},
|
|
230
|
+
])
|
|
231
|
+
: []),
|
|
166
232
|
],
|
|
167
233
|
};
|
|
168
234
|
}
|
|
169
235
|
|
|
170
|
-
function nodePublishJob(
|
|
236
|
+
function nodePublishJob(): Job {
|
|
171
237
|
return {
|
|
172
|
-
if: releaseCondition(
|
|
173
|
-
|
|
238
|
+
if: releaseCondition([
|
|
239
|
+
"needs['build-release'].result == 'success'",
|
|
240
|
+
"needs['build-release'].outputs.npm == 'true'",
|
|
241
|
+
]),
|
|
242
|
+
needs: ["build-release"],
|
|
174
243
|
runsOn: ["ubuntu-latest"],
|
|
175
244
|
permissions: { contents: JobPermission.READ, idToken: JobPermission.WRITE },
|
|
176
245
|
timeoutMinutes: 30,
|
|
177
246
|
env: { BUN_VERSION, CI: "true" },
|
|
178
247
|
steps: [
|
|
179
|
-
|
|
248
|
+
{
|
|
249
|
+
name: "Download npm archives",
|
|
250
|
+
uses: "actions/download-artifact@v4",
|
|
251
|
+
with: { name: "release-npm", path: ".release/npm" },
|
|
252
|
+
},
|
|
253
|
+
...nodeReleaseSetupSteps(),
|
|
180
254
|
{
|
|
181
255
|
name: "Publish npm workspace",
|
|
182
256
|
env: {
|
|
@@ -184,47 +258,19 @@ function nodePublishJob(project: DBXToolsJavaScriptProject): Job {
|
|
|
184
258
|
NODE_AUTH_TOKEN: "${{ secrets.NPM_TOKEN }}",
|
|
185
259
|
NPM_CONFIG_PROVENANCE: "true",
|
|
186
260
|
},
|
|
187
|
-
run: 'bun
|
|
261
|
+
run: 'bun .release/npm/publish-npm.mjs --directory .release/npm --version "$RELEASE_VERSION"',
|
|
188
262
|
},
|
|
189
263
|
],
|
|
190
264
|
};
|
|
191
265
|
}
|
|
192
266
|
|
|
193
|
-
function addDocsJobs(
|
|
194
|
-
workflow: GithubWorkflow,
|
|
195
|
-
project: DBXToolsJavaScriptProject,
|
|
196
|
-
options: ReleaseDocsOptions,
|
|
197
|
-
): void {
|
|
198
|
-
workflow.addJob("build-docs", {
|
|
199
|
-
if: releaseCondition(),
|
|
200
|
-
needs: ["verify-context"],
|
|
201
|
-
runsOn: ["ubuntu-latest"],
|
|
202
|
-
permissions: {
|
|
203
|
-
contents: JobPermission.READ,
|
|
204
|
-
pages: JobPermission.WRITE,
|
|
205
|
-
idToken: JobPermission.WRITE,
|
|
206
|
-
},
|
|
207
|
-
timeoutMinutes: 30,
|
|
208
|
-
env: {
|
|
209
|
-
BUN_VERSION,
|
|
210
|
-
DOCS_SITE_URL: options.siteUrl,
|
|
211
|
-
DOCS_BASE: options.base ?? "/",
|
|
212
|
-
},
|
|
213
|
-
steps: [
|
|
214
|
-
...releaseSourceSteps(),
|
|
215
|
-
...project.renderWorkflowSetup({ mutable: false }),
|
|
216
|
-
...options.prepareSteps,
|
|
217
|
-
...options.buildSteps,
|
|
218
|
-
{
|
|
219
|
-
name: "Upload Pages artifact",
|
|
220
|
-
uses: "actions/upload-pages-artifact@v4",
|
|
221
|
-
with: { path: options.artifactPath },
|
|
222
|
-
},
|
|
223
|
-
],
|
|
224
|
-
});
|
|
267
|
+
function addDocsJobs(workflow: GithubWorkflow): void {
|
|
225
268
|
workflow.addJob("deploy-docs", {
|
|
226
|
-
if: releaseCondition(
|
|
227
|
-
|
|
269
|
+
if: releaseCondition([
|
|
270
|
+
"needs['build-release'].result == 'success'",
|
|
271
|
+
"needs['build-release'].outputs.docs == 'true'",
|
|
272
|
+
]),
|
|
273
|
+
needs: ["build-release"],
|
|
228
274
|
environment: {
|
|
229
275
|
name: "github-pages",
|
|
230
276
|
url: "${{ steps.deployment.outputs.page_url }}",
|
|
@@ -247,6 +293,11 @@ function addDocsJobs(
|
|
|
247
293
|
export class DBXToolsRelease extends Component {
|
|
248
294
|
constructor(project: DBXToolsJavaScriptProject, options: DBXToolsReleaseOptions = {}) {
|
|
249
295
|
super(project);
|
|
296
|
+
project.addGitIgnore(".release/");
|
|
297
|
+
const installCondition =
|
|
298
|
+
"node -e \"process.exit(process.env.DBX_TOOLS_RELEASE_INSTALL === 'never' ? 1 : 0)\"";
|
|
299
|
+
project.package.installTask.addCondition(installCondition);
|
|
300
|
+
project.package.installCiTask.addCondition(installCondition);
|
|
250
301
|
const tagPrefix = options.tagPrefix ?? "v";
|
|
251
302
|
const releaseBranch = projectReleaseBranch(project);
|
|
252
303
|
releaseTagPrefixes.set(project, tagPrefix);
|
|
@@ -290,12 +341,12 @@ export class DBXToolsRelease extends Component {
|
|
|
290
341
|
push: { tags: [`${tagPrefix}*`] },
|
|
291
342
|
});
|
|
292
343
|
workflow.file?.addOverride("permissions.contents", "read");
|
|
293
|
-
workflow.addJob("
|
|
344
|
+
workflow.addJob("build-release", releaseBuildJob(project, tagPrefix, releaseBranch, options));
|
|
294
345
|
if (options.nodeRelease !== false) {
|
|
295
|
-
workflow.addJob("publish-node", nodePublishJob(
|
|
346
|
+
workflow.addJob("publish-node", nodePublishJob());
|
|
296
347
|
}
|
|
297
348
|
if (options.docs) {
|
|
298
|
-
addDocsJobs(workflow
|
|
349
|
+
addDocsJobs(workflow);
|
|
299
350
|
}
|
|
300
351
|
refreshReleaseDocsDependencies(project);
|
|
301
352
|
}
|
|
@@ -94,6 +94,8 @@ export function buildPythonProjects(options: {
|
|
|
94
94
|
readonly allowEmpty?: boolean;
|
|
95
95
|
readonly output: string;
|
|
96
96
|
readonly packages?: readonly string[];
|
|
97
|
+
/** Place each package's artifacts in its directory beneath output. */
|
|
98
|
+
readonly packageDirectories?: boolean;
|
|
97
99
|
readonly root: string;
|
|
98
100
|
readonly version: string;
|
|
99
101
|
}): void {
|
|
@@ -136,9 +138,15 @@ export function buildPythonProjects(options: {
|
|
|
136
138
|
version: options.version,
|
|
137
139
|
}),
|
|
138
140
|
);
|
|
139
|
-
|
|
141
|
+
const packageOutput = options.packageDirectories ? join(output, project.directory) : output;
|
|
142
|
+
runTaskCommand(root, "uv", ["build", "--out-dir", packageOutput, packageRoot]);
|
|
143
|
+
if (pythonDistributionPaths(packageOutput).length === 0) {
|
|
144
|
+
throw new Error(`No Python distributions found for ${project.name}`);
|
|
145
|
+
}
|
|
140
146
|
}
|
|
141
|
-
const distributions =
|
|
147
|
+
const distributions = options.packageDirectories
|
|
148
|
+
? projects.flatMap((project) => pythonDistributionPaths(join(output, project.directory)))
|
|
149
|
+
: pythonDistributionPaths(output);
|
|
142
150
|
if (distributions.length === 0) {
|
|
143
151
|
throw new Error(`No Python distributions found in ${output}`);
|
|
144
152
|
}
|
|
@@ -157,6 +165,10 @@ export async function main(): Promise<void> {
|
|
|
157
165
|
.option("--root <path>", "Python workspace package root", "packages/py")
|
|
158
166
|
.option("--package <directory...>", "Build only selected package directories")
|
|
159
167
|
.option("--output <path>", "Build distributions into a directory without publishing")
|
|
168
|
+
.option(
|
|
169
|
+
"--package-directories",
|
|
170
|
+
"Keep each package's distributions in its own output directory",
|
|
171
|
+
)
|
|
160
172
|
.option("--dry-run", "build and inspect distributions without uploading")
|
|
161
173
|
.action(
|
|
162
174
|
(
|
|
@@ -165,6 +177,7 @@ export async function main(): Promise<void> {
|
|
|
165
177
|
dryRun?: boolean;
|
|
166
178
|
indexUrl?: string;
|
|
167
179
|
package?: string[];
|
|
180
|
+
packageDirectories?: boolean;
|
|
168
181
|
publishUrl?: string;
|
|
169
182
|
root: string;
|
|
170
183
|
output?: string;
|
|
@@ -174,6 +187,7 @@ export async function main(): Promise<void> {
|
|
|
174
187
|
buildPythonProjects({
|
|
175
188
|
output: options.output,
|
|
176
189
|
packages: options.package,
|
|
190
|
+
packageDirectories: options.packageDirectories,
|
|
177
191
|
root: options.root,
|
|
178
192
|
version,
|
|
179
193
|
});
|
|
@@ -270,6 +270,11 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
270
270
|
};
|
|
271
271
|
});
|
|
272
272
|
build.onResolve({ filter: /.*/ }, ({ path }) => {
|
|
273
|
+
if (path === "zod") {
|
|
274
|
+
return {
|
|
275
|
+
path: resolve(dirname(Bun.resolveSync("zod/package.json", root)), "index.cjs"),
|
|
276
|
+
};
|
|
277
|
+
}
|
|
273
278
|
if (overridesByModule.has(path)) {
|
|
274
279
|
return { path, namespace: functionOverrideNamespace };
|
|
275
280
|
}
|
|
@@ -586,7 +591,13 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
586
591
|
}
|
|
587
592
|
const parameterType = withoutUndefined(checker.getTypeAtLocation(parameter));
|
|
588
593
|
const path = `${exported.sourceName}.${parameter.name.text}`;
|
|
589
|
-
const record = pythonRecordType(
|
|
594
|
+
const record = pythonRecordType(
|
|
595
|
+
checker,
|
|
596
|
+
parameterType,
|
|
597
|
+
records,
|
|
598
|
+
path,
|
|
599
|
+
declaredTypeName(parameter.type),
|
|
600
|
+
);
|
|
590
601
|
const required = !parameter.questionToken && !parameter.initializer;
|
|
591
602
|
return {
|
|
592
603
|
javascriptName: parameter.name.text,
|
|
@@ -606,6 +617,7 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
606
617
|
responses,
|
|
607
618
|
protocols,
|
|
608
619
|
`${exported.sourceName}.return`,
|
|
620
|
+
declaredTypeName(declaration.type),
|
|
609
621
|
);
|
|
610
622
|
typesByFunction.set(`${exported.sourceFile}#${exported.sourceName}`, {
|
|
611
623
|
parameters,
|
|
@@ -636,8 +648,10 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
636
648
|
type: ts.Type,
|
|
637
649
|
records: Map<string, PythonRecord>,
|
|
638
650
|
path: string,
|
|
651
|
+
preferredName?: string,
|
|
639
652
|
): PythonRecord {
|
|
640
|
-
const
|
|
653
|
+
const symbolName = type.aliasSymbol?.getName() ?? type.getSymbol()?.getName();
|
|
654
|
+
const name = symbolName && symbolName !== "__type" ? symbolName : preferredName;
|
|
641
655
|
if (!name || name === "__type") throw new Error(`${path} must reference a named object type`);
|
|
642
656
|
const existing = records.get(name);
|
|
643
657
|
if (existing) return existing;
|
|
@@ -665,13 +679,23 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
665
679
|
type: ts.Type,
|
|
666
680
|
records: Map<string, PythonRecord>,
|
|
667
681
|
path: string,
|
|
682
|
+
preferredName?: string,
|
|
668
683
|
): PythonRecord | undefined {
|
|
669
684
|
if (!(type.flags & ts.TypeFlags.Object)) return undefined;
|
|
670
685
|
if (checker.isArrayType(type) || type.getCallSignatures().length > 0) return undefined;
|
|
671
686
|
if (checker.getIndexTypeOfType(type, ts.IndexKind.String)) return undefined;
|
|
672
|
-
const
|
|
687
|
+
const symbolName = type.aliasSymbol?.getName() ?? type.getSymbol()?.getName();
|
|
688
|
+
const name = symbolName && symbolName !== "__type" ? symbolName : preferredName;
|
|
673
689
|
if (!name || name === "__type") return undefined;
|
|
674
|
-
return pythonRecord(checker, type, records, path);
|
|
690
|
+
return pythonRecord(checker, type, records, path, preferredName);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
function declaredTypeName(node: ts.TypeNode | undefined): string | undefined {
|
|
694
|
+
if (!node) return undefined;
|
|
695
|
+
if (ts.isTypeReferenceNode(node) && ts.isIdentifier(node.typeName)) {
|
|
696
|
+
return node.typeName.text;
|
|
697
|
+
}
|
|
698
|
+
return undefined;
|
|
675
699
|
}
|
|
676
700
|
|
|
677
701
|
function pythonType(
|
|
@@ -715,6 +739,7 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
715
739
|
responses: Map<string, PythonResponse>,
|
|
716
740
|
protocols: Map<string, PythonProtocol>,
|
|
717
741
|
path: string,
|
|
742
|
+
preferredName?: string,
|
|
718
743
|
): string {
|
|
719
744
|
if (type.isUnion()) {
|
|
720
745
|
return pythonUnion(
|
|
@@ -839,7 +864,7 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
839
864
|
protocols.set(name, { name, methods: protocolMethods, properties: protocolProperties });
|
|
840
865
|
return name;
|
|
841
866
|
}
|
|
842
|
-
const name = `${pythonObjectName(type, path, "Result")}Response`;
|
|
867
|
+
const name = `${preferredName ?? pythonObjectName(type, path, "Result")}Response`;
|
|
843
868
|
if (responses.has(name)) return name;
|
|
844
869
|
responses.set(name, { name, fields: [] });
|
|
845
870
|
const fields = properties.map((property) => {
|