@dbx-tools/projen 0.9.40 → 0.9.46
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 +58 -67
- package/index.ts +6 -8
- package/package.json +9 -10
- package/shims/python-node/bootstrap.ts +129 -0
- package/shims/python-node/child-process.ts +96 -0
- package/shims/python-node/crypto.ts +60 -0
- package/shims/python-node/fs-promises.ts +179 -0
- package/shims/python-node/fs.ts +173 -0
- package/shims/python-node/headers.ts +75 -0
- package/shims/python-node/host.ts +341 -0
- package/shims/python-node/os.ts +20 -0
- package/shims/python-node/process.ts +13 -0
- package/shims/python-node/readline.ts +37 -0
- package/shims/python-node/tsconfig.json +24 -0
- package/src/_lazy-require.ts +7 -25
- package/src/bun-app.ts +2 -2
- package/src/bun-workflow.ts +2 -170
- package/src/codegen.ts +23 -20
- package/src/dbx-tools-config.ts +52 -9
- package/src/generated.ts +4 -17
- package/src/mixin.ts +1 -5
- package/src/packages.ts +18 -15
- package/src/pnpm-workspace.ts +1 -4
- package/src/project-js.ts +65 -196
- package/src/project-predicate.ts +7 -3
- package/src/project-py.ts +127 -37
- package/src/publish.ts +13 -10
- package/src/python-node-bindings.ts +2 -0
- package/src/python-node-bundle.ts +6 -0
- package/src/release-options.ts +17 -17
- package/src/release.ts +31 -14
- package/src/scaffold.ts +4 -7
- package/src/tags.ts +6 -16
- package/src/vscode.ts +1 -1
- package/src/watch.ts +5 -5
- package/src/workspace-lock.ts +1 -0
- package/src/workspace-version.ts +4 -4
- package/tasks/barrels.ts +3 -5
- package/tasks/clean.ts +1 -0
- package/tasks/lib/compile-workspace.ts +41 -19
- package/tasks/lib/local-publish.ts +0 -28
- package/tasks/lib/publish-npm.ts +8 -0
- package/tasks/lib/publish-python.ts +4 -0
- package/tasks/lib/python-node-bindings.ts +107 -26
- package/tasks/lib/python-registry.ts +4 -0
- package/tasks/lib/python-release.ts +2 -0
- package/tasks/lib/release.ts +2 -2
- package/tasks/projenrc.ts +1 -0
- package/tasks/sync.ts +4 -4
- package/src/openapi.ts +0 -293
- package/tasks/local-publish.ts +0 -6
- package/tasks/openapi.ts +0 -35
package/src/vscode.ts
CHANGED
|
@@ -33,7 +33,7 @@ export function configureVsCode(scope: javascript.NodeProject): void {
|
|
|
33
33
|
{
|
|
34
34
|
label: "sync",
|
|
35
35
|
detail:
|
|
36
|
-
"projen sync --watch - projenrc (.projenrc.ts + syncResynthPaths re-synth) +
|
|
36
|
+
"projen sync --watch - projenrc (.projenrc.ts + syncResynthPaths re-synth) + barrel watcher",
|
|
37
37
|
type: "shell",
|
|
38
38
|
command: "bun run sync -- --watch",
|
|
39
39
|
isBackground: true,
|
package/src/watch.ts
CHANGED
|
@@ -7,13 +7,13 @@
|
|
|
7
7
|
* decls - reacting to our own output would loop), exits on a watcher error chokidar
|
|
8
8
|
* cannot recover from ({@link FATAL_WATCH_ERROR_CODES}), and shuts down on SIGINT. Callers
|
|
9
9
|
* pass the paths to watch and an `onBatch` handler; the concern-specific glue - which
|
|
10
|
-
* barrels to rebuild
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* barrels to rebuild or when to re-synth - lives in the task that owns it
|
|
11
|
+
* (`tasks/barrels.ts` or `tasks/projenrc.ts`), each forwarding here rather than
|
|
12
|
+
* duplicating the watch machinery.
|
|
13
13
|
*
|
|
14
14
|
* `watchRoots()` is the one shared input - the package roots where every
|
|
15
|
-
* watchable source file lives
|
|
16
|
-
*
|
|
15
|
+
* watchable source file lives. {@link watchFiles} owns the chokidar wiring; this
|
|
16
|
+
* is thin glue.
|
|
17
17
|
*/
|
|
18
18
|
import { isAbsolute, resolve } from "node:path";
|
|
19
19
|
import { watch as pathWatch } from "@dbx-tools/path";
|
package/src/workspace-lock.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { log } from "@dbx-tools/shared-core";
|
|
|
6
6
|
const MUTATION_LOCK_SCOPE = "dbx-tools-workspace-mutation";
|
|
7
7
|
const logger = log.logger("projen:workspace-lock");
|
|
8
8
|
|
|
9
|
+
/** Optional preflight used before and after acquiring the workspace mutation lock. */
|
|
9
10
|
export interface WorkspaceMutationLockOptions {
|
|
10
11
|
/**
|
|
11
12
|
* When this returns false, skip both the lock and the callback. After the
|
package/src/workspace-version.ts
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* The repo-root `VERSION` file holds one plain `x.y.z` string that every
|
|
5
5
|
* generated manifest copies at synth: the root and `projen/` package.json, every
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* JavaScript member, every Python `pyproject.toml`, and the example apps. Synth
|
|
7
|
+
* only READS this file (defaulting to {@link DEFAULT_VERSION} when it is absent
|
|
8
|
+
* on a fresh tree); it never rewrites it, so an ordinary `bunx projen` cannot
|
|
9
|
+
* move a package version up or down.
|
|
10
10
|
*
|
|
11
11
|
* Only the pure `bump` task changes the number. It increments the checked-in
|
|
12
12
|
* file while release preparation owns the surrounding Git transaction.
|
package/tasks/barrels.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env -S bun
|
|
2
|
+
/** Generate package barrels once or keep affected barrels current in watch mode. */
|
|
2
3
|
import { sep } from "node:path";
|
|
3
4
|
import { parseArgs } from "node:util";
|
|
4
5
|
import { log, object, stringUtils } from "@dbx-tools/shared-core";
|
|
@@ -11,12 +12,9 @@ const logger = log.logger("projen:barrels");
|
|
|
11
12
|
/** The recorded package dir that owns `abs`, if any (for a targeted barrel rebuild). */
|
|
12
13
|
function ownerPackageDir(
|
|
13
14
|
abs: string,
|
|
14
|
-
packages: readonly { readonly dir: string
|
|
15
|
+
packages: readonly { readonly dir: string }[],
|
|
15
16
|
): string | undefined {
|
|
16
|
-
|
|
17
|
-
return packages.find(
|
|
18
|
-
({ dir, tags }) => !tags.includes("openapi") && (abs === dir || abs.startsWith(dir + sep)),
|
|
19
|
-
)?.dir;
|
|
17
|
+
return packages.find(({ dir }) => abs === dir || abs.startsWith(dir + sep))?.dir;
|
|
20
18
|
}
|
|
21
19
|
|
|
22
20
|
function changedBarrelTargets(changed: readonly string[]): object.Sequence<string> {
|
package/tasks/clean.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env -S bun
|
|
2
|
+
/** Interactively remove generated files and installed dependency directories. */
|
|
2
3
|
import { relative } from "node:path";
|
|
3
4
|
import { log, stringUtils } from "@dbx-tools/shared-core";
|
|
4
5
|
import { listGeneratedFiles, listNodeModulesDirs, removePaths } from "../src/clean.ts";
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env -S bun
|
|
2
|
-
/**
|
|
2
|
+
/** Batch package type-checks and run package-owned compile lifecycles. */
|
|
3
3
|
import { existsSync, readFileSync } from "node:fs";
|
|
4
4
|
import { availableParallelism } from "node:os";
|
|
5
5
|
import { join, resolve } from "node:path";
|
|
6
6
|
import * as exec from "@dbx-tools/core/exec";
|
|
7
|
+
import { log } from "@dbx-tools/shared-core";
|
|
8
|
+
|
|
9
|
+
const logger = log.logger("projen:compile");
|
|
7
10
|
|
|
8
11
|
interface PackageManifest {
|
|
9
12
|
readonly name?: string;
|
|
@@ -13,8 +16,14 @@ interface PackageManifest {
|
|
|
13
16
|
|
|
14
17
|
interface TaskManifest {
|
|
15
18
|
readonly tasks?: {
|
|
19
|
+
readonly "pre-compile"?: {
|
|
20
|
+
readonly steps?: Array<{ readonly exec?: string; readonly execArgs?: string[] }>;
|
|
21
|
+
};
|
|
16
22
|
readonly compile?: {
|
|
17
|
-
readonly steps?: Array<{ readonly execArgs?: string[] }>;
|
|
23
|
+
readonly steps?: Array<{ readonly exec?: string; readonly execArgs?: string[] }>;
|
|
24
|
+
};
|
|
25
|
+
readonly "post-compile"?: {
|
|
26
|
+
readonly steps?: Array<{ readonly exec?: string; readonly execArgs?: string[] }>;
|
|
18
27
|
};
|
|
19
28
|
};
|
|
20
29
|
}
|
|
@@ -40,6 +49,10 @@ function plainTypeScriptBuild(steps: unknown): boolean {
|
|
|
40
49
|
);
|
|
41
50
|
}
|
|
42
51
|
|
|
52
|
+
function hasSteps(steps: unknown): boolean {
|
|
53
|
+
return Array.isArray(steps) && steps.length > 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
43
56
|
/** Read the root's concrete workspace paths on every run, including newly added members. */
|
|
44
57
|
export function compilePlan(root: string): CompilePlan {
|
|
45
58
|
const workspace = readJson<PackageManifest>(join(root, "package.json"));
|
|
@@ -51,14 +64,13 @@ export function compilePlan(root: string): CompilePlan {
|
|
|
51
64
|
const script = manifest.scripts?.compile;
|
|
52
65
|
if (!script) continue;
|
|
53
66
|
const taskPath = join(directory, ".projen", "tasks.json");
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
: undefined;
|
|
67
|
+
const tasks = existsSync(taskPath) ? readJson<TaskManifest>(taskPath).tasks : undefined;
|
|
68
|
+
const plainCompile = plainTypeScriptBuild(tasks?.compile?.steps);
|
|
57
69
|
if (
|
|
58
70
|
script === "projen compile" &&
|
|
59
|
-
|
|
60
|
-
!
|
|
61
|
-
|
|
71
|
+
plainCompile &&
|
|
72
|
+
!hasSteps(tasks?.["pre-compile"]?.steps) &&
|
|
73
|
+
!hasSteps(tasks?.["post-compile"]?.steps)
|
|
62
74
|
) {
|
|
63
75
|
typescriptConfigs.push(join(directory, "tsconfig.json"));
|
|
64
76
|
} else {
|
|
@@ -78,9 +90,7 @@ export async function main(): Promise<void> {
|
|
|
78
90
|
const batchCount = Math.min(4, availableParallelism(), plan.typescriptConfigs.length);
|
|
79
91
|
const batches = Array.from({ length: batchCount }, () => [] as string[]);
|
|
80
92
|
plan.typescriptConfigs.forEach((config, index) => batches[index % batchCount]!.push(config));
|
|
81
|
-
|
|
82
|
-
`Type-checking ${plan.typescriptConfigs.length} workspaces in ${batchCount} batches`,
|
|
83
|
-
);
|
|
93
|
+
logger.info(`type-checking ${plan.typescriptConfigs.length} workspaces in ${batchCount} batches`);
|
|
84
94
|
for (const [index, batch] of batches.entries()) {
|
|
85
95
|
jobs.push({
|
|
86
96
|
name: `TypeScript batch ${index + 1}`,
|
|
@@ -96,24 +106,36 @@ export async function main(): Promise<void> {
|
|
|
96
106
|
for (const pkg of plan.customPackages) {
|
|
97
107
|
jobs.push({
|
|
98
108
|
name: pkg.name,
|
|
99
|
-
result:
|
|
100
|
-
cwd: pkg.directory,
|
|
101
|
-
stdin: "ignore",
|
|
102
|
-
stdout: (line) => console.log(`${pkg.name}: ${line.replace(emoji, "")}`),
|
|
103
|
-
stderr: (line) => console.error(`${pkg.name}: ${line.replace(emoji, "")}`),
|
|
104
|
-
}),
|
|
109
|
+
result: compileCustomPackage(pkg),
|
|
105
110
|
});
|
|
106
111
|
}
|
|
107
112
|
const results = await Promise.allSettled(jobs.map((job) => job.result));
|
|
108
113
|
let failed = false;
|
|
109
114
|
for (const [index, result] of results.entries()) {
|
|
110
115
|
if (result.status === "rejected") {
|
|
111
|
-
|
|
116
|
+
logger.error(`${jobs[index]!.name} compile failed:`, result.reason);
|
|
112
117
|
failed = true;
|
|
113
118
|
} else if (result.value.exitCode !== 0) {
|
|
114
|
-
|
|
119
|
+
logger.error(`${jobs[index]!.name} compile exited with ${result.value.exitCode}`);
|
|
115
120
|
failed = true;
|
|
116
121
|
}
|
|
117
122
|
}
|
|
118
123
|
if (failed) process.exitCode = 1;
|
|
119
124
|
}
|
|
125
|
+
|
|
126
|
+
async function compileCustomPackage(pkg: {
|
|
127
|
+
readonly name: string;
|
|
128
|
+
readonly directory: string;
|
|
129
|
+
}): Promise<exec.ExecResult> {
|
|
130
|
+
let result: exec.ExecResult | undefined;
|
|
131
|
+
for (const task of ["pre-compile", "compile", "post-compile"]) {
|
|
132
|
+
result = await exec.spawn("bun", ["run", task], {
|
|
133
|
+
cwd: pkg.directory,
|
|
134
|
+
stdin: "ignore",
|
|
135
|
+
stdout: (line) => logger.info(`${pkg.name}: ${line.replace(emoji, "")}`),
|
|
136
|
+
stderr: (line) => logger.error(`${pkg.name}: ${line.replace(emoji, "")}`),
|
|
137
|
+
});
|
|
138
|
+
if (result.exitCode !== 0) return result;
|
|
139
|
+
}
|
|
140
|
+
return result!;
|
|
141
|
+
}
|
|
@@ -3,7 +3,6 @@ import { resolve } from "node:path";
|
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import * as projectUtils from "@dbx-tools/core/project-utils";
|
|
5
5
|
import { log, net } from "@dbx-tools/shared-core";
|
|
6
|
-
import { Command } from "commander";
|
|
7
6
|
import { activePythonIndexes, resolveLocalPypi } from "./python-registry.ts";
|
|
8
7
|
import { runTaskCommandAsync } from "../../src/_task-command.ts";
|
|
9
8
|
|
|
@@ -93,30 +92,3 @@ export async function publishLocalRelease(
|
|
|
93
92
|
logger.success(`published local workspace ${options.version}`);
|
|
94
93
|
return { npm: Boolean(localRegistry), python: Boolean(localPypi) };
|
|
95
94
|
}
|
|
96
|
-
|
|
97
|
-
export async function main(): Promise<void> {
|
|
98
|
-
await new Command()
|
|
99
|
-
.requiredOption("--version <version>", "release version")
|
|
100
|
-
.option("--root <path>", "repository root")
|
|
101
|
-
.option("--python-root <path>", "Python package root", "packages/py")
|
|
102
|
-
.option("--local-registry <url>", "local npm registry", "auto")
|
|
103
|
-
.option("--local-pypi <url>", "local devpi index", "auto")
|
|
104
|
-
.action(
|
|
105
|
-
async (options: {
|
|
106
|
-
version: string;
|
|
107
|
-
root?: string;
|
|
108
|
-
pythonRoot: string;
|
|
109
|
-
localRegistry: string;
|
|
110
|
-
localPypi: string;
|
|
111
|
-
}) => {
|
|
112
|
-
await publishLocalRelease({
|
|
113
|
-
localPypi: options.localPypi,
|
|
114
|
-
localRegistry: options.localRegistry,
|
|
115
|
-
pythonRoot: options.pythonRoot,
|
|
116
|
-
root: options.root ?? projectUtils.root() ?? process.cwd(),
|
|
117
|
-
version: options.version,
|
|
118
|
-
});
|
|
119
|
-
},
|
|
120
|
-
)
|
|
121
|
-
.parseAsync();
|
|
122
|
-
}
|
package/tasks/lib/publish-npm.ts
CHANGED
|
@@ -22,6 +22,7 @@ import { runTaskCommand } from "../../src/_task-command.ts";
|
|
|
22
22
|
const DEFAULT_REGISTRY = "https://registry.npmjs.org";
|
|
23
23
|
const logger = log.logger("projen:publish-npm");
|
|
24
24
|
|
|
25
|
+
/** Registry and archive identity used to verify idempotent npm publication. */
|
|
25
26
|
export interface NpmReleaseIdentity {
|
|
26
27
|
readonly access?: "public" | "restricted";
|
|
27
28
|
readonly contentDigest?: string;
|
|
@@ -41,6 +42,7 @@ interface NpmArchiveManifest {
|
|
|
41
42
|
readonly version?: string;
|
|
42
43
|
}
|
|
43
44
|
|
|
45
|
+
/** Transform applied to a packed npm manifest before archive verification. */
|
|
44
46
|
export type NpmArchiveManifestTransform = (
|
|
45
47
|
manifest: NpmArchiveManifest,
|
|
46
48
|
) => Readonly<Record<string, unknown>>;
|
|
@@ -143,6 +145,7 @@ function registryUrl(registry: string, name: string, version: string): string {
|
|
|
143
145
|
return `${registry.replace(/\/$/, "")}/${encodeURIComponent(name)}/${encodeURIComponent(version)}`;
|
|
144
146
|
}
|
|
145
147
|
|
|
148
|
+
/** Verify that registry metadata represents the exact locally packed release. */
|
|
146
149
|
export function npmReleaseMatches(
|
|
147
150
|
local: NpmReleaseIdentity,
|
|
148
151
|
published: NpmReleaseIdentity | undefined,
|
|
@@ -173,6 +176,7 @@ export function npmReleaseMatches(
|
|
|
173
176
|
return true;
|
|
174
177
|
}
|
|
175
178
|
|
|
179
|
+
/** Read one published npm release identity, or `undefined` when it is absent. */
|
|
176
180
|
export async function publishedNpmRelease(
|
|
177
181
|
name: string,
|
|
178
182
|
version: string,
|
|
@@ -232,6 +236,7 @@ function readNpmArchiveManifest(path: string): NpmArchiveManifest {
|
|
|
232
236
|
return JSON.parse(result.stdout) as NpmArchiveManifest;
|
|
233
237
|
}
|
|
234
238
|
|
|
239
|
+
/** Read and hash the release identity embedded in an npm archive. */
|
|
235
240
|
export function readNpmArchiveIdentity(path: string): NpmReleaseIdentity {
|
|
236
241
|
const manifest = readNpmArchiveManifest(path);
|
|
237
242
|
if (!manifest.name || !manifest.version) {
|
|
@@ -326,6 +331,7 @@ export function npmArchiveContentDigest(path: string): string {
|
|
|
326
331
|
}
|
|
327
332
|
}
|
|
328
333
|
|
|
334
|
+
/** Pack one workspace package and optionally transform only its archived manifest. */
|
|
329
335
|
export function packNpmPackage(
|
|
330
336
|
directory: string,
|
|
331
337
|
destination: string,
|
|
@@ -404,6 +410,7 @@ export function packNpmPackage(
|
|
|
404
410
|
}
|
|
405
411
|
}
|
|
406
412
|
|
|
413
|
+
/** Publish dependency-ordered npm archives, skipping only exact registry matches. */
|
|
407
414
|
export async function publishNpmArchives(options: {
|
|
408
415
|
readonly directory: string;
|
|
409
416
|
readonly dryRun?: boolean;
|
|
@@ -443,6 +450,7 @@ export async function publishNpmArchives(options: {
|
|
|
443
450
|
}
|
|
444
451
|
}
|
|
445
452
|
|
|
453
|
+
/** Parse npm archive publication options and execute the publisher. */
|
|
446
454
|
export async function main(): Promise<void> {
|
|
447
455
|
const program = new Command();
|
|
448
456
|
program
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env -S bun
|
|
2
|
+
/**
|
|
3
|
+
* Generate a shared PythonMonkey runtime and typed Python wrappers from each
|
|
4
|
+
* package's serialized node-binding configuration.
|
|
5
|
+
*/
|
|
2
6
|
import {
|
|
3
7
|
existsSync,
|
|
4
8
|
mkdirSync,
|
|
@@ -10,7 +14,7 @@ import {
|
|
|
10
14
|
import { dirname, join, relative, resolve } from "node:path";
|
|
11
15
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
12
16
|
import { parseArgs } from "node:util";
|
|
13
|
-
import { stringUtils } from "@dbx-tools/shared-core";
|
|
17
|
+
import { log, stringUtils } from "@dbx-tools/shared-core";
|
|
14
18
|
import type { BunPlugin } from "bun";
|
|
15
19
|
import stdLibBrowser from "node-stdlib-browser";
|
|
16
20
|
import ts from "typescript";
|
|
@@ -23,6 +27,8 @@ import {
|
|
|
23
27
|
type ResolvedPythonNodeFunctionOverride,
|
|
24
28
|
} from "../../src/python-node-bindings.ts";
|
|
25
29
|
|
|
30
|
+
const logger = log.logger("projen:python-node-bindings");
|
|
31
|
+
|
|
26
32
|
type FunctionOverride = ResolvedPythonNodeFunctionOverride;
|
|
27
33
|
|
|
28
34
|
function bindingIndex(value: string, count: number, project: string): number {
|
|
@@ -180,6 +186,7 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
180
186
|
["fs/promises", resolve(shimRoot, "fs-promises.ts")],
|
|
181
187
|
["os", resolve(shimRoot, "os.ts")],
|
|
182
188
|
["process", resolve(shimRoot, "process.ts")],
|
|
189
|
+
["readline", resolve(shimRoot, "readline.ts")],
|
|
183
190
|
]);
|
|
184
191
|
const bridgeSource = [
|
|
185
192
|
"export const __pythonGet = (target, name) => target[name];",
|
|
@@ -270,6 +277,11 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
270
277
|
};
|
|
271
278
|
});
|
|
272
279
|
build.onResolve({ filter: /.*/ }, ({ path }) => {
|
|
280
|
+
if (path === "zod") {
|
|
281
|
+
return {
|
|
282
|
+
path: resolve(dirname(Bun.resolveSync("zod/package.json", root)), "index.cjs"),
|
|
283
|
+
};
|
|
284
|
+
}
|
|
273
285
|
if (overridesByModule.has(path)) {
|
|
274
286
|
return { path, namespace: functionOverrideNamespace };
|
|
275
287
|
}
|
|
@@ -311,7 +323,7 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
311
323
|
target: "browser",
|
|
312
324
|
});
|
|
313
325
|
if (!result.success) {
|
|
314
|
-
for (const message of result.logs)
|
|
326
|
+
for (const message of result.logs) logger.error(message);
|
|
315
327
|
throw new Error(`Could not bundle ${config.entrypoint}`);
|
|
316
328
|
}
|
|
317
329
|
if (result.outputs.length !== 1) {
|
|
@@ -433,7 +445,7 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
433
445
|
.map((name) => ` ${JSON.stringify(name)},`)
|
|
434
446
|
.join("\n");
|
|
435
447
|
const dataclasses = records.map(pythonDataclass).join("\n\n\n");
|
|
436
|
-
const responseTypes = responses.map(pythonResponse).join("\n\n\n");
|
|
448
|
+
const responseTypes = orderPythonResponses(responses).map(pythonResponse).join("\n\n\n");
|
|
437
449
|
const protocolTypes = protocols.map(pythonProtocol).join("\n\n\n");
|
|
438
450
|
const wrappers = functions
|
|
439
451
|
.map(({ async, javascriptName, pythonName, parameters, returnType }) =>
|
|
@@ -521,6 +533,39 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
521
533
|
readonly type: string;
|
|
522
534
|
}
|
|
523
535
|
|
|
536
|
+
/**
|
|
537
|
+
* Emit nested response types before the responses that reference them.
|
|
538
|
+
*
|
|
539
|
+
* Functional TypedDict declarations evaluate their field types immediately,
|
|
540
|
+
* unlike class annotations protected by `from __future__ import annotations`.
|
|
541
|
+
*/
|
|
542
|
+
function orderPythonResponses(responses: readonly PythonResponse[]): PythonResponse[] {
|
|
543
|
+
const ordered: PythonResponse[] = [];
|
|
544
|
+
const visited = new Set<string>();
|
|
545
|
+
const visiting = new Set<string>();
|
|
546
|
+
|
|
547
|
+
const visit = (response: PythonResponse): void => {
|
|
548
|
+
if (visited.has(response.name) || visiting.has(response.name)) return;
|
|
549
|
+
visiting.add(response.name);
|
|
550
|
+
for (const dependency of responses) {
|
|
551
|
+
if (
|
|
552
|
+
dependency.name !== response.name &&
|
|
553
|
+
response.fields.some(({ type }) =>
|
|
554
|
+
new RegExp(`\\b${dependency.name}\\b`).test(type),
|
|
555
|
+
)
|
|
556
|
+
) {
|
|
557
|
+
visit(dependency);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
visiting.delete(response.name);
|
|
561
|
+
visited.add(response.name);
|
|
562
|
+
ordered.push(response);
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
for (const response of responses) visit(response);
|
|
566
|
+
return ordered;
|
|
567
|
+
}
|
|
568
|
+
|
|
524
569
|
interface PythonProtocol {
|
|
525
570
|
readonly methods: readonly PythonProtocolMethod[];
|
|
526
571
|
readonly name: string;
|
|
@@ -586,7 +631,13 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
586
631
|
}
|
|
587
632
|
const parameterType = withoutUndefined(checker.getTypeAtLocation(parameter));
|
|
588
633
|
const path = `${exported.sourceName}.${parameter.name.text}`;
|
|
589
|
-
const record = pythonRecordType(
|
|
634
|
+
const record = pythonRecordType(
|
|
635
|
+
checker,
|
|
636
|
+
parameterType,
|
|
637
|
+
records,
|
|
638
|
+
path,
|
|
639
|
+
declaredTypeName(parameter.type),
|
|
640
|
+
);
|
|
590
641
|
const required = !parameter.questionToken && !parameter.initializer;
|
|
591
642
|
return {
|
|
592
643
|
javascriptName: parameter.name.text,
|
|
@@ -606,6 +657,7 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
606
657
|
responses,
|
|
607
658
|
protocols,
|
|
608
659
|
`${exported.sourceName}.return`,
|
|
660
|
+
declaredTypeName(declaration.type),
|
|
609
661
|
);
|
|
610
662
|
typesByFunction.set(`${exported.sourceFile}#${exported.sourceName}`, {
|
|
611
663
|
parameters,
|
|
@@ -636,18 +688,18 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
636
688
|
type: ts.Type,
|
|
637
689
|
records: Map<string, PythonRecord>,
|
|
638
690
|
path: string,
|
|
691
|
+
preferredName?: string,
|
|
639
692
|
): PythonRecord {
|
|
640
|
-
const
|
|
693
|
+
const symbolName = type.aliasSymbol?.getName() ?? type.getSymbol()?.getName();
|
|
694
|
+
const name = symbolName && symbolName !== "__type" ? symbolName : preferredName;
|
|
641
695
|
if (!name || name === "__type") throw new Error(`${path} must reference a named object type`);
|
|
642
696
|
const existing = records.get(name);
|
|
643
697
|
if (existing) return existing;
|
|
644
698
|
const placeholder: PythonRecord = { name, fields: [] };
|
|
645
699
|
records.set(name, placeholder);
|
|
646
700
|
const fields = checker.getPropertiesOfType(type).map((property) => {
|
|
647
|
-
const declaration = property.valueDeclaration ?? property.declarations?.[0];
|
|
648
|
-
if (!declaration) throw new Error(`${path}.${property.name} has no TypeScript declaration`);
|
|
649
701
|
const propertyType = withoutUndefined(
|
|
650
|
-
checker
|
|
702
|
+
resolvedPropertyType(checker, type, property, `${path}.${property.name}`),
|
|
651
703
|
);
|
|
652
704
|
return {
|
|
653
705
|
javascriptName: property.name,
|
|
@@ -665,13 +717,23 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
665
717
|
type: ts.Type,
|
|
666
718
|
records: Map<string, PythonRecord>,
|
|
667
719
|
path: string,
|
|
720
|
+
preferredName?: string,
|
|
668
721
|
): PythonRecord | undefined {
|
|
669
722
|
if (!(type.flags & ts.TypeFlags.Object)) return undefined;
|
|
670
723
|
if (checker.isArrayType(type) || type.getCallSignatures().length > 0) return undefined;
|
|
671
724
|
if (checker.getIndexTypeOfType(type, ts.IndexKind.String)) return undefined;
|
|
672
|
-
const
|
|
725
|
+
const symbolName = type.aliasSymbol?.getName() ?? type.getSymbol()?.getName();
|
|
726
|
+
const name = symbolName && symbolName !== "__type" ? symbolName : preferredName;
|
|
673
727
|
if (!name || name === "__type") return undefined;
|
|
674
|
-
return pythonRecord(checker, type, records, path);
|
|
728
|
+
return pythonRecord(checker, type, records, path, preferredName);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
function declaredTypeName(node: ts.TypeNode | undefined): string | undefined {
|
|
732
|
+
if (!node) return undefined;
|
|
733
|
+
if (ts.isTypeReferenceNode(node) && ts.isIdentifier(node.typeName)) {
|
|
734
|
+
return node.typeName.text;
|
|
735
|
+
}
|
|
736
|
+
return undefined;
|
|
675
737
|
}
|
|
676
738
|
|
|
677
739
|
function pythonType(
|
|
@@ -715,6 +777,7 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
715
777
|
responses: Map<string, PythonResponse>,
|
|
716
778
|
protocols: Map<string, PythonProtocol>,
|
|
717
779
|
path: string,
|
|
780
|
+
preferredName?: string,
|
|
718
781
|
): string {
|
|
719
782
|
if (type.isUnion()) {
|
|
720
783
|
return pythonUnion(
|
|
@@ -757,13 +820,15 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
757
820
|
);
|
|
758
821
|
}
|
|
759
822
|
const properties = checker.getPropertiesOfType(type).filter(publicProperty);
|
|
760
|
-
const methods = properties.filter(
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
823
|
+
const methods = properties.filter(
|
|
824
|
+
(property) =>
|
|
825
|
+
resolvedPropertyType(
|
|
826
|
+
checker,
|
|
827
|
+
type,
|
|
828
|
+
property,
|
|
829
|
+
`${path}.${property.name}`,
|
|
830
|
+
).getCallSignatures().length > 0,
|
|
831
|
+
);
|
|
767
832
|
if (methods.length > 0) {
|
|
768
833
|
const name = pythonObjectName(type, path, "Result");
|
|
769
834
|
if (protocols.has(name)) return name;
|
|
@@ -822,14 +887,14 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
822
887
|
const protocolProperties = properties
|
|
823
888
|
.filter(({ name }) => !methodNames.has(name))
|
|
824
889
|
.map((property) => {
|
|
825
|
-
const declaration = property.valueDeclaration ?? property.declarations?.[0];
|
|
826
|
-
if (!declaration) throw new Error(`${path}.${property.name} has no declaration`);
|
|
827
890
|
return {
|
|
828
891
|
name: pythonFunctionName(property.name),
|
|
829
892
|
required: !(property.flags & ts.SymbolFlags.Optional),
|
|
830
893
|
type: pythonReturnType(
|
|
831
894
|
checker,
|
|
832
|
-
withoutUndefined(
|
|
895
|
+
withoutUndefined(
|
|
896
|
+
resolvedPropertyType(checker, type, property, `${path}.${property.name}`),
|
|
897
|
+
),
|
|
833
898
|
responses,
|
|
834
899
|
protocols,
|
|
835
900
|
`${path}.${property.name}`,
|
|
@@ -839,18 +904,18 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
839
904
|
protocols.set(name, { name, methods: protocolMethods, properties: protocolProperties });
|
|
840
905
|
return name;
|
|
841
906
|
}
|
|
842
|
-
const name = `${pythonObjectName(type, path, "Result")}Response`;
|
|
907
|
+
const name = `${preferredName ?? pythonObjectName(type, path, "Result")}Response`;
|
|
843
908
|
if (responses.has(name)) return name;
|
|
844
909
|
responses.set(name, { name, fields: [] });
|
|
845
910
|
const fields = properties.map((property) => {
|
|
846
|
-
const declaration = property.valueDeclaration ?? property.declarations?.[0];
|
|
847
|
-
if (!declaration) throw new Error(`${path}.${property.name} has no TypeScript declaration`);
|
|
848
911
|
return {
|
|
849
912
|
name: property.name,
|
|
850
913
|
required: !(property.flags & ts.SymbolFlags.Optional),
|
|
851
914
|
type: pythonReturnType(
|
|
852
915
|
checker,
|
|
853
|
-
withoutUndefined(
|
|
916
|
+
withoutUndefined(
|
|
917
|
+
resolvedPropertyType(checker, type, property, `${path}.${property.name}`),
|
|
918
|
+
),
|
|
854
919
|
responses,
|
|
855
920
|
protocols,
|
|
856
921
|
`${path}.${property.name}`,
|
|
@@ -883,12 +948,28 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
883
948
|
}
|
|
884
949
|
|
|
885
950
|
function publicProperty(property: ts.Symbol): boolean {
|
|
951
|
+
if (!property.declarations?.length) return true;
|
|
886
952
|
return (property.declarations ?? []).some((declaration) => {
|
|
887
953
|
const flags = ts.getCombinedModifierFlags(declaration as ts.Declaration);
|
|
888
954
|
return !(flags & (ts.ModifierFlags.Private | ts.ModifierFlags.Protected));
|
|
889
955
|
});
|
|
890
956
|
}
|
|
891
957
|
|
|
958
|
+
function resolvedPropertyType(
|
|
959
|
+
checker: ts.TypeChecker,
|
|
960
|
+
owner: ts.Type,
|
|
961
|
+
property: ts.Symbol,
|
|
962
|
+
path: string,
|
|
963
|
+
): ts.Type {
|
|
964
|
+
const location =
|
|
965
|
+
property.valueDeclaration ??
|
|
966
|
+
property.declarations?.[0] ??
|
|
967
|
+
owner.aliasSymbol?.declarations?.[0] ??
|
|
968
|
+
owner.getSymbol()?.declarations?.[0];
|
|
969
|
+
if (!location) throw new Error(`${path} has no TypeScript type location`);
|
|
970
|
+
return checker.getTypeOfSymbolAtLocation(property, location);
|
|
971
|
+
}
|
|
972
|
+
|
|
892
973
|
function pythonObjectName(type: ts.Type, path: string, suffix: string): string {
|
|
893
974
|
const name = type.aliasSymbol?.getName() ?? type.getSymbol()?.getName();
|
|
894
975
|
if (name && name !== "__type") return name;
|
|
@@ -1071,7 +1152,7 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
1071
1152
|
function writeGenerated(output: string, contents: string, kind: string): void {
|
|
1072
1153
|
const destination = relative(root, output);
|
|
1073
1154
|
if (existsSync(output) && readFileSync(output, "utf8") === contents) {
|
|
1074
|
-
if (values.check)
|
|
1155
|
+
if (values.check) logger.info(`verified ${destination}`);
|
|
1075
1156
|
return;
|
|
1076
1157
|
}
|
|
1077
1158
|
if (values.check) {
|
|
@@ -1081,7 +1162,7 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
1081
1162
|
makeWritable(output);
|
|
1082
1163
|
writeFileSync(output, contents);
|
|
1083
1164
|
makeReadonly(output);
|
|
1084
|
-
|
|
1165
|
+
logger.info(`generated ${destination}`);
|
|
1085
1166
|
}
|
|
1086
1167
|
}
|
|
1087
1168
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** Resolve active uv indexes and writable loopback devpi publication targets. */
|
|
1
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
3
|
import { homedir } from "node:os";
|
|
3
4
|
import { resolve } from "node:path";
|
|
@@ -5,8 +6,11 @@ import { net } from "@dbx-tools/shared-core";
|
|
|
5
6
|
import { parse } from "smol-toml";
|
|
6
7
|
import { captureTaskCommand } from "../../src/_task-command.ts";
|
|
7
8
|
|
|
9
|
+
/** Paired Python package index and upload endpoints for a local registry. */
|
|
8
10
|
export interface LocalPythonRegistry {
|
|
11
|
+
/** PEP 503 package index URL used for dependency resolution. */
|
|
9
12
|
readonly indexUrl: string;
|
|
13
|
+
/** Registry upload URL used for publication. */
|
|
10
14
|
readonly publishUrl: string;
|
|
11
15
|
}
|
|
12
16
|
|
|
@@ -22,6 +22,7 @@ const directSibling = (
|
|
|
22
22
|
return directory === sibling.directory || directory.endsWith(`/${sibling.directory}`);
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
+
/** Parse the publication identity and private flag from one Python manifest. */
|
|
25
26
|
export function pythonProjectInfo(
|
|
26
27
|
source: string,
|
|
27
28
|
toml: { parse(source: string): unknown },
|
|
@@ -41,6 +42,7 @@ export function pythonProjectInfo(
|
|
|
41
42
|
};
|
|
42
43
|
}
|
|
43
44
|
|
|
45
|
+
/** Project sibling dependencies and the release version into an isolated manifest copy. */
|
|
44
46
|
export function preparePythonProjectForPublication(
|
|
45
47
|
source: string,
|
|
46
48
|
options: {
|
package/tasks/lib/release.ts
CHANGED
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
runTaskCommand,
|
|
11
11
|
taskCommandSucceeds,
|
|
12
12
|
} from "../../src/_task-command.ts";
|
|
13
|
-
import { readWorkspaceVersion } from "../../src/workspace-version.ts";
|
|
14
13
|
import {
|
|
15
14
|
RELEASE_INSTALL_MODES,
|
|
16
15
|
RELEASE_PUBLISH_TARGETS,
|
|
@@ -20,6 +19,7 @@ import {
|
|
|
20
19
|
type ReleaseInstallMode,
|
|
21
20
|
type ReleaseSelectionOptions,
|
|
22
21
|
} from "../../src/release-options.ts";
|
|
22
|
+
import { readWorkspaceVersion } from "../../src/workspace-version.ts";
|
|
23
23
|
|
|
24
24
|
const logger = log.logger("projen:release");
|
|
25
25
|
|
|
@@ -115,7 +115,7 @@ export async function runRelease(
|
|
|
115
115
|
const selection = releaseStepSelection(options);
|
|
116
116
|
const install = options.install ?? "auto";
|
|
117
117
|
|
|
118
|
-
if (install === "always") runTaskCommand(root, "bun", ["install"
|
|
118
|
+
if (install === "always") runTaskCommand(root, "bun", ["install"]);
|
|
119
119
|
|
|
120
120
|
prepareReleaseBranch({ branch, remote, root });
|
|
121
121
|
|
package/tasks/projenrc.ts
CHANGED