@dbx-tools/projen 0.9.43 → 0.9.47
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 +28 -34
- package/index.ts +6 -7
- 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 +1 -9
- package/src/codegen.ts +20 -17
- 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 -157
- package/src/project-predicate.ts +7 -3
- package/src/project-py.ts +122 -44
- 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 +30 -9
- 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 +77 -21
- package/tasks/lib/python-registry.ts +4 -0
- package/tasks/lib/python-release.ts +2 -0
- package/tasks/lib/release.ts +1 -1
- 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/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];",
|
|
@@ -316,7 +323,7 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
316
323
|
target: "browser",
|
|
317
324
|
});
|
|
318
325
|
if (!result.success) {
|
|
319
|
-
for (const message of result.logs)
|
|
326
|
+
for (const message of result.logs) logger.error(message);
|
|
320
327
|
throw new Error(`Could not bundle ${config.entrypoint}`);
|
|
321
328
|
}
|
|
322
329
|
if (result.outputs.length !== 1) {
|
|
@@ -438,7 +445,7 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
438
445
|
.map((name) => ` ${JSON.stringify(name)},`)
|
|
439
446
|
.join("\n");
|
|
440
447
|
const dataclasses = records.map(pythonDataclass).join("\n\n\n");
|
|
441
|
-
const responseTypes = responses.map(pythonResponse).join("\n\n\n");
|
|
448
|
+
const responseTypes = orderPythonResponses(responses).map(pythonResponse).join("\n\n\n");
|
|
442
449
|
const protocolTypes = protocols.map(pythonProtocol).join("\n\n\n");
|
|
443
450
|
const wrappers = functions
|
|
444
451
|
.map(({ async, javascriptName, pythonName, parameters, returnType }) =>
|
|
@@ -526,6 +533,39 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
526
533
|
readonly type: string;
|
|
527
534
|
}
|
|
528
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
|
+
|
|
529
569
|
interface PythonProtocol {
|
|
530
570
|
readonly methods: readonly PythonProtocolMethod[];
|
|
531
571
|
readonly name: string;
|
|
@@ -658,10 +698,8 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
658
698
|
const placeholder: PythonRecord = { name, fields: [] };
|
|
659
699
|
records.set(name, placeholder);
|
|
660
700
|
const fields = checker.getPropertiesOfType(type).map((property) => {
|
|
661
|
-
const declaration = property.valueDeclaration ?? property.declarations?.[0];
|
|
662
|
-
if (!declaration) throw new Error(`${path}.${property.name} has no TypeScript declaration`);
|
|
663
701
|
const propertyType = withoutUndefined(
|
|
664
|
-
checker
|
|
702
|
+
resolvedPropertyType(checker, type, property, `${path}.${property.name}`),
|
|
665
703
|
);
|
|
666
704
|
return {
|
|
667
705
|
javascriptName: property.name,
|
|
@@ -782,13 +820,15 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
782
820
|
);
|
|
783
821
|
}
|
|
784
822
|
const properties = checker.getPropertiesOfType(type).filter(publicProperty);
|
|
785
|
-
const methods = properties.filter(
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
823
|
+
const methods = properties.filter(
|
|
824
|
+
(property) =>
|
|
825
|
+
resolvedPropertyType(
|
|
826
|
+
checker,
|
|
827
|
+
type,
|
|
828
|
+
property,
|
|
829
|
+
`${path}.${property.name}`,
|
|
830
|
+
).getCallSignatures().length > 0,
|
|
831
|
+
);
|
|
792
832
|
if (methods.length > 0) {
|
|
793
833
|
const name = pythonObjectName(type, path, "Result");
|
|
794
834
|
if (protocols.has(name)) return name;
|
|
@@ -847,14 +887,14 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
847
887
|
const protocolProperties = properties
|
|
848
888
|
.filter(({ name }) => !methodNames.has(name))
|
|
849
889
|
.map((property) => {
|
|
850
|
-
const declaration = property.valueDeclaration ?? property.declarations?.[0];
|
|
851
|
-
if (!declaration) throw new Error(`${path}.${property.name} has no declaration`);
|
|
852
890
|
return {
|
|
853
891
|
name: pythonFunctionName(property.name),
|
|
854
892
|
required: !(property.flags & ts.SymbolFlags.Optional),
|
|
855
893
|
type: pythonReturnType(
|
|
856
894
|
checker,
|
|
857
|
-
withoutUndefined(
|
|
895
|
+
withoutUndefined(
|
|
896
|
+
resolvedPropertyType(checker, type, property, `${path}.${property.name}`),
|
|
897
|
+
),
|
|
858
898
|
responses,
|
|
859
899
|
protocols,
|
|
860
900
|
`${path}.${property.name}`,
|
|
@@ -868,14 +908,14 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
868
908
|
if (responses.has(name)) return name;
|
|
869
909
|
responses.set(name, { name, fields: [] });
|
|
870
910
|
const fields = properties.map((property) => {
|
|
871
|
-
const declaration = property.valueDeclaration ?? property.declarations?.[0];
|
|
872
|
-
if (!declaration) throw new Error(`${path}.${property.name} has no TypeScript declaration`);
|
|
873
911
|
return {
|
|
874
912
|
name: property.name,
|
|
875
913
|
required: !(property.flags & ts.SymbolFlags.Optional),
|
|
876
914
|
type: pythonReturnType(
|
|
877
915
|
checker,
|
|
878
|
-
withoutUndefined(
|
|
916
|
+
withoutUndefined(
|
|
917
|
+
resolvedPropertyType(checker, type, property, `${path}.${property.name}`),
|
|
918
|
+
),
|
|
879
919
|
responses,
|
|
880
920
|
protocols,
|
|
881
921
|
`${path}.${property.name}`,
|
|
@@ -908,12 +948,28 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
908
948
|
}
|
|
909
949
|
|
|
910
950
|
function publicProperty(property: ts.Symbol): boolean {
|
|
951
|
+
if (!property.declarations?.length) return true;
|
|
911
952
|
return (property.declarations ?? []).some((declaration) => {
|
|
912
953
|
const flags = ts.getCombinedModifierFlags(declaration as ts.Declaration);
|
|
913
954
|
return !(flags & (ts.ModifierFlags.Private | ts.ModifierFlags.Protected));
|
|
914
955
|
});
|
|
915
956
|
}
|
|
916
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
|
+
|
|
917
973
|
function pythonObjectName(type: ts.Type, path: string, suffix: string): string {
|
|
918
974
|
const name = type.aliasSymbol?.getName() ?? type.getSymbol()?.getName();
|
|
919
975
|
if (name && name !== "__type") return name;
|
|
@@ -1096,7 +1152,7 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
1096
1152
|
function writeGenerated(output: string, contents: string, kind: string): void {
|
|
1097
1153
|
const destination = relative(root, output);
|
|
1098
1154
|
if (existsSync(output) && readFileSync(output, "utf8") === contents) {
|
|
1099
|
-
if (values.check)
|
|
1155
|
+
if (values.check) logger.info(`verified ${destination}`);
|
|
1100
1156
|
return;
|
|
1101
1157
|
}
|
|
1102
1158
|
if (values.check) {
|
|
@@ -1106,7 +1162,7 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
1106
1162
|
makeWritable(output);
|
|
1107
1163
|
writeFileSync(output, contents);
|
|
1108
1164
|
makeReadonly(output);
|
|
1109
|
-
|
|
1165
|
+
logger.info(`generated ${destination}`);
|
|
1110
1166
|
}
|
|
1111
1167
|
}
|
|
1112
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
|
@@ -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
package/tasks/sync.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env -S bun
|
|
2
|
+
/** Synthesize once or supervise the focused Projen and barrel watch loops. */
|
|
2
3
|
import { fileURLToPath } from "node:url";
|
|
3
4
|
import { log } from "@dbx-tools/shared-core";
|
|
4
5
|
import concurrently from "concurrently";
|
|
@@ -46,8 +47,8 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
46
47
|
// Watch: one initial full synth to bring the tree up to date, then focused
|
|
47
48
|
// watchers under `concurrently`. The projenrc watcher is the intelligent stand-in
|
|
48
49
|
// for stock `projen --watch` - it re-synths (+install) ONLY when `.projenrc.ts` or
|
|
49
|
-
// a configured `syncResynthPaths` entry changes, while
|
|
50
|
-
//
|
|
50
|
+
// a configured `syncResynthPaths` entry changes, while the barrel watcher keeps
|
|
51
|
+
// generated package exports fresh on source edits with no full synth.
|
|
51
52
|
//
|
|
52
53
|
// VS Code auto-runs this on folder open, so from here down nothing is allowed to be
|
|
53
54
|
// fatal: errors are logged and retried, and only a stop signal ends the task.
|
|
@@ -67,11 +68,10 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
67
68
|
const watchers = [
|
|
68
69
|
watcher("projenrc.ts", "projenrc", "magenta"),
|
|
69
70
|
watcher("barrels.ts", "barrels", "cyan", "--watch"),
|
|
70
|
-
watcher("openapi.ts", "openapi", "green", "--watch"),
|
|
71
71
|
];
|
|
72
72
|
const { result } = concurrently(watchers, {
|
|
73
73
|
prefix: "name",
|
|
74
|
-
// No `killOthersOn`: one watcher falling over is no reason to tear the other
|
|
74
|
+
// No `killOthersOn`: one watcher falling over is no reason to tear the other
|
|
75
75
|
// down. `-1` is concurrently's spelling for "restart forever", so a crashed
|
|
76
76
|
// watcher comes back instead of silently leaving its outputs stale.
|
|
77
77
|
restartTries: -1,
|