@dbx-tools/projen 0.9.31 → 0.9.36
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 +71 -65
- package/index.ts +5 -8
- package/package.json +8 -7
- package/src/_task-command.ts +0 -75
- package/src/barrels.ts +15 -23
- package/src/bun-app.ts +5 -6
- package/src/clean.ts +41 -15
- package/src/codegen.ts +2 -3
- package/src/dbx-tools-config.ts +23 -64
- package/src/generated.ts +7 -1
- package/src/module-exports.ts +35 -3
- package/src/openapi.ts +34 -38
- package/src/packages.ts +3 -30
- package/src/pnpm-workspace.ts +21 -26
- package/src/project-js.ts +152 -365
- package/src/project-predicate.ts +4 -5
- package/src/project-py.ts +55 -144
- package/src/publish.ts +1 -1
- package/src/python-node-bindings.ts +140 -67
- package/src/python-node-bundle.ts +6 -42
- package/src/release-context.ts +4 -4
- package/src/release.ts +88 -86
- package/src/tags.ts +20 -18
- package/src/tsconfig.ts +9 -5
- package/src/vscode.ts +47 -58
- package/src/watch.ts +2 -8
- package/src/workspace-version.ts +1 -6
- package/tasks/barrels.ts +43 -38
- package/tasks/bump.ts +24 -20
- package/tasks/clean.ts +54 -50
- package/tasks/compile-workspace.ts +2 -117
- package/tasks/lib/compile-workspace.ts +119 -0
- package/tasks/lib/local-publish.ts +122 -0
- package/tasks/lib/publish-npm.ts +458 -0
- package/tasks/lib/publish-python.ts +195 -0
- package/tasks/lib/publish.ts +264 -0
- package/tasks/lib/python-node-bindings.ts +1262 -0
- package/tasks/{python-registry.ts → lib/python-registry.ts} +21 -30
- package/tasks/{python-release.ts → lib/python-release.ts} +1 -0
- package/tasks/lib/release-version.ts +66 -0
- package/tasks/lib/release.ts +212 -0
- package/tasks/local-publish.ts +4 -105
- package/tasks/openapi.ts +25 -21
- package/tasks/projenrc.ts +14 -12
- package/tasks/publish-npm.ts +3 -364
- package/tasks/publish-python.ts +3 -184
- package/tasks/publish.ts +3 -279
- package/tasks/python-node-bindings.ts +3 -1014
- package/tasks/release-version.ts +3 -62
- package/tasks/release.ts +3 -96
- package/tasks/sync.ts +71 -70
- package/tasks/version-check.ts +3 -10
- package/src/release-catalog.ts +0 -684
- package/tasks/databricks-cli-assets.ts +0 -129
- package/tasks/python-node-bindings-watch.ts +0 -47
package/src/watch.ts
CHANGED
|
@@ -90,19 +90,13 @@ export interface WatchLoopOptions {
|
|
|
90
90
|
check?: (changed: string[]) => boolean | Promise<boolean>;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
function resolveWatchLoopOptions(config?: IgnoreGroupOptions | WatchLoopOptions): WatchLoopOptions {
|
|
94
|
-
if (!config) return {};
|
|
95
|
-
if ("check" in config || "ignoreOptions" in config) return config as WatchLoopOptions;
|
|
96
|
-
return { ignoreOptions: config as IgnoreGroupOptions };
|
|
97
|
-
}
|
|
98
|
-
|
|
99
93
|
export function watchLoop(
|
|
100
94
|
tag: string,
|
|
101
95
|
paths: string[],
|
|
102
96
|
onBatch: (changed: string[]) => void | Promise<void>,
|
|
103
|
-
config
|
|
97
|
+
config: WatchLoopOptions = {},
|
|
104
98
|
): void {
|
|
105
|
-
const { ignoreOptions, check } =
|
|
99
|
+
const { ignoreOptions, check } = config;
|
|
106
100
|
const pending = new Set<string>();
|
|
107
101
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
108
102
|
let running = false;
|
package/src/workspace-version.ts
CHANGED
|
@@ -110,17 +110,12 @@ export function syncWorkspaceManifestVersion(manifestPath: string, version: stri
|
|
|
110
110
|
return true;
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
/** The checked-in version a release increment starts from. */
|
|
114
|
-
export function resolveBaseVersion(root: string): string {
|
|
115
|
-
return readWorkspaceVersion(root);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
113
|
/** Resolve the next release version without mutating the workspace. */
|
|
119
114
|
export function resolveNextVersion(
|
|
120
115
|
root: string,
|
|
121
116
|
level: VersionLevel,
|
|
122
117
|
): { base: string; version: string } {
|
|
123
|
-
const base =
|
|
118
|
+
const base = readWorkspaceVersion(root);
|
|
124
119
|
const parsed = parseSemver(base) ?? [0, 0, 1];
|
|
125
120
|
return {
|
|
126
121
|
base: parsed.join("."),
|
package/tasks/barrels.ts
CHANGED
|
@@ -7,14 +7,6 @@ import { recordedPackages } from "../src/packages.ts";
|
|
|
7
7
|
import { watchLoop, watchRoots } from "../src/watch.ts";
|
|
8
8
|
|
|
9
9
|
const logger = log.logger("projen:barrels");
|
|
10
|
-
const { values } = parseArgs({
|
|
11
|
-
args: process.argv.slice(2),
|
|
12
|
-
options: {
|
|
13
|
-
watch: { type: "boolean" },
|
|
14
|
-
dir: { type: "string", multiple: true },
|
|
15
|
-
},
|
|
16
|
-
strict: false,
|
|
17
|
-
});
|
|
18
10
|
|
|
19
11
|
/** The recorded package dir that owns `abs`, if any (for a targeted barrel rebuild). */
|
|
20
12
|
function ownerPackageDir(
|
|
@@ -45,36 +37,49 @@ function warnUnownedChanges(changed: readonly string[]): void {
|
|
|
45
37
|
}
|
|
46
38
|
}
|
|
47
39
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
watchRoots(),
|
|
55
|
-
(changed) => {
|
|
56
|
-
const n = generateBarrels({ dirs: [...changedBarrelTargets(changed)] });
|
|
57
|
-
if (n) logger.success(`rebuilt ${stringUtils.pluralize(n, "barrel")}`);
|
|
40
|
+
export function main(args: string[] = process.argv.slice(2)): void {
|
|
41
|
+
const { values } = parseArgs({
|
|
42
|
+
args,
|
|
43
|
+
options: {
|
|
44
|
+
watch: { type: "boolean" },
|
|
45
|
+
dir: { type: "string", multiple: true },
|
|
58
46
|
},
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
47
|
+
strict: false,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
if (values.watch) {
|
|
51
|
+
// Watch the package roots; a source edit inside a package rebuilds just that
|
|
52
|
+
// package's `index.ts` barrel (no re-synth - the projenrc watcher owns that).
|
|
53
|
+
// watchLoop already drops generated paths, so a barrel write never re-triggers us.
|
|
54
|
+
watchLoop(
|
|
55
|
+
"barrels",
|
|
56
|
+
watchRoots(),
|
|
57
|
+
(changed) => {
|
|
58
|
+
const n = generateBarrels({ dirs: [...changedBarrelTargets(changed)] });
|
|
59
|
+
if (n) logger.success(`rebuilt ${stringUtils.pluralize(n, "barrel")}`);
|
|
69
60
|
},
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
61
|
+
{
|
|
62
|
+
check: (changed) => {
|
|
63
|
+
const targets = changedBarrelTargets(changed);
|
|
64
|
+
// A change under a package root that no recorded package owns is a new
|
|
65
|
+
// folder: only a re-synth can create its barrel, so this watcher skips.
|
|
66
|
+
if (!targets.some(() => true)) {
|
|
67
|
+
warnUnownedChanges(changed);
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
return true;
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
);
|
|
74
|
+
} else {
|
|
75
|
+
const dirs = Array.isArray(values.dir)
|
|
76
|
+
? values.dir.filter((value): value is string => typeof value === "string")
|
|
77
|
+
: [];
|
|
78
|
+
const n = generateBarrels(dirs.length ? { dirs } : undefined);
|
|
79
|
+
logger.success(
|
|
80
|
+
n === 0 ? "barrels already up to date" : `updated ${stringUtils.pluralize(n, "barrel")}`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
80
83
|
}
|
|
84
|
+
|
|
85
|
+
if (import.meta.main) main();
|
package/tasks/bump.ts
CHANGED
|
@@ -16,26 +16,30 @@ import {
|
|
|
16
16
|
|
|
17
17
|
const logger = log.logger("projen:bump");
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
program
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
19
|
+
export async function main(): Promise<void> {
|
|
20
|
+
const program = new Command();
|
|
21
|
+
program
|
|
22
|
+
.description("Increment VERSION and synchronize generated workspace versions")
|
|
23
|
+
.addOption(
|
|
24
|
+
new Option("-l, --level <level>", "semver increment")
|
|
25
|
+
.choices(["patch", "minor", "major"])
|
|
26
|
+
.default("patch"),
|
|
27
|
+
)
|
|
28
|
+
.option("--no-synth", "write VERSION without synchronizing generated files")
|
|
29
|
+
.action((opts: { level: VersionLevel; synth: boolean }) => {
|
|
30
|
+
const root = projectUtils.root() ?? process.cwd();
|
|
31
|
+
const next = resolveNextVersion(root, opts.level);
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
logger.info(`bump ${next.base} -> ${next.version} (${opts.level})`);
|
|
34
|
+
writeWorkspaceVersion(root, next.version);
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
if (opts.synth) {
|
|
37
|
+
runTaskCommand(root, process.execPath, [".projenrc.ts"]);
|
|
38
|
+
}
|
|
39
|
+
logger.success(`workspace version synchronized at ${next.version}`);
|
|
40
|
+
});
|
|
40
41
|
|
|
41
|
-
await program.parseAsync();
|
|
42
|
+
await program.parseAsync();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (import.meta.main) await main();
|
package/tasks/clean.ts
CHANGED
|
@@ -5,63 +5,67 @@ import { listGeneratedFiles, listNodeModulesDirs, removePaths } from "../src/cle
|
|
|
5
5
|
import { repoRoot, toPosix } from "../src/packages.ts";
|
|
6
6
|
|
|
7
7
|
const logger = log.logger("projen:clean");
|
|
8
|
-
|
|
8
|
+
export async function main(args: string[] = process.argv.slice(2)): Promise<void> {
|
|
9
|
+
const yes = args.includes("-y") || args.includes("--yes");
|
|
9
10
|
|
|
10
|
-
const files = listGeneratedFiles();
|
|
11
|
-
const nodeModules = listNodeModulesDirs();
|
|
12
|
-
const targets = [...files, ...nodeModules];
|
|
11
|
+
const files = listGeneratedFiles();
|
|
12
|
+
const nodeModules = listNodeModulesDirs();
|
|
13
|
+
const targets = [...files, ...nodeModules];
|
|
13
14
|
|
|
14
|
-
if (targets.length === 0) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
15
|
+
if (targets.length === 0) {
|
|
16
|
+
logger.success("nothing to remove (no generated files or node_modules)");
|
|
17
|
+
process.exit(0);
|
|
18
|
+
}
|
|
18
19
|
|
|
19
|
-
const regenHint = (removedNodeModules: boolean): string =>
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
const regenHint = (removedNodeModules: boolean): string =>
|
|
21
|
+
removedNodeModules
|
|
22
|
+
? "reinstall with `bun install`, then `bun run default`"
|
|
23
|
+
: "regenerate with `bun run default`";
|
|
23
24
|
|
|
24
|
-
if (yes) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
25
|
+
if (yes) {
|
|
26
|
+
const n = removePaths(targets);
|
|
27
|
+
logger.success(
|
|
28
|
+
`removed ${stringUtils.pluralize(n, "path")} (${files.length} generated + ${nodeModules.length} node_modules) - ${regenHint(nodeModules.length > 0)}`,
|
|
29
|
+
);
|
|
30
|
+
process.exit(0);
|
|
31
|
+
}
|
|
31
32
|
|
|
32
|
-
if (!process.stdin.isTTY) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
33
|
+
if (!process.stdin.isTTY) {
|
|
34
|
+
logger.warn(
|
|
35
|
+
`non-interactive shell: re-run with -y to remove all ${targets.length} paths (${files.length} generated + ${nodeModules.length} node_modules), or run in a terminal to pick`,
|
|
36
|
+
);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
38
39
|
|
|
39
|
-
const clack = await import("@clack/prompts");
|
|
40
|
-
clack.intro("projen clean");
|
|
41
|
-
const label = (f: string): string => toPosix(relative(repoRoot, f));
|
|
42
|
-
const picked = await clack.multiselect<string>({
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
});
|
|
40
|
+
const clack = await import("@clack/prompts");
|
|
41
|
+
clack.intro("projen clean");
|
|
42
|
+
const label = (f: string): string => toPosix(relative(repoRoot, f));
|
|
43
|
+
const picked = await clack.multiselect<string>({
|
|
44
|
+
message: `Select paths to remove (${files.length} generated + ${nodeModules.length} node_modules, all preselected)`,
|
|
45
|
+
options: [
|
|
46
|
+
...files.map((f) => ({ value: f, label: label(f) })),
|
|
47
|
+
...nodeModules.map((d) => ({
|
|
48
|
+
value: d,
|
|
49
|
+
label: `${label(d)} (directory)`,
|
|
50
|
+
})),
|
|
51
|
+
],
|
|
52
|
+
initialValues: [...targets],
|
|
53
|
+
required: false,
|
|
54
|
+
});
|
|
54
55
|
|
|
55
|
-
if (typeof picked === "symbol") {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
56
|
+
if (typeof picked === "symbol") {
|
|
57
|
+
clack.cancel("clean cancelled - nothing removed");
|
|
58
|
+
process.exit(0);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (picked.length === 0) {
|
|
62
|
+
clack.outro("nothing selected - nothing removed");
|
|
63
|
+
process.exit(0);
|
|
64
|
+
}
|
|
59
65
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
66
|
+
const removedNodeModules = picked.some((p) => nodeModules.includes(p));
|
|
67
|
+
const n = removePaths(picked);
|
|
68
|
+
clack.outro(`removed ${stringUtils.pluralize(n, "path")} - ${regenHint(removedNodeModules)}`);
|
|
63
69
|
}
|
|
64
70
|
|
|
65
|
-
|
|
66
|
-
const n = removePaths(picked);
|
|
67
|
-
clack.outro(`removed ${stringUtils.pluralize(n, "path")} - ${regenHint(removedNodeModules)}`);
|
|
71
|
+
if (import.meta.main) await main();
|
|
@@ -1,121 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env -S bun
|
|
2
|
-
|
|
3
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
-
import { availableParallelism } from "node:os";
|
|
5
|
-
import { join, resolve } from "node:path";
|
|
6
|
-
import * as exec from "@dbx-tools/core/exec";
|
|
2
|
+
import { main } from "./lib/compile-workspace.ts";
|
|
7
3
|
|
|
8
|
-
|
|
9
|
-
readonly name?: string;
|
|
10
|
-
readonly scripts?: Record<string, string>;
|
|
11
|
-
readonly workspaces?: string[];
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
interface TaskManifest {
|
|
15
|
-
readonly tasks?: {
|
|
16
|
-
readonly compile?: {
|
|
17
|
-
readonly steps?: Array<{ readonly execArgs?: string[] }>;
|
|
18
|
-
};
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface CompilePlan {
|
|
23
|
-
readonly typescriptConfigs: string[];
|
|
24
|
-
readonly customPackages: Array<{ readonly name: string; readonly directory: string }>;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function readJson<T>(path: string): T {
|
|
28
|
-
return JSON.parse(readFileSync(path, "utf8")) as T;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function plainTypeScriptBuild(steps: unknown): boolean {
|
|
32
|
-
if (!Array.isArray(steps) || steps.length !== 1) return false;
|
|
33
|
-
const step = steps[0] as { execArgs?: string[] };
|
|
34
|
-
if (!step || typeof step !== "object") return false;
|
|
35
|
-
return (
|
|
36
|
-
Object.keys(step).length === 1 &&
|
|
37
|
-
step.execArgs?.length === 2 &&
|
|
38
|
-
step.execArgs[0] === "tsc" &&
|
|
39
|
-
step.execArgs[1] === "--build"
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/** Read the root's concrete workspace paths on every run, including newly added members. */
|
|
44
|
-
export function compilePlan(root: string): CompilePlan {
|
|
45
|
-
const workspace = readJson<PackageManifest>(join(root, "package.json"));
|
|
46
|
-
const typescriptConfigs: string[] = [];
|
|
47
|
-
const customPackages: CompilePlan["customPackages"] = [];
|
|
48
|
-
for (const member of workspace.workspaces ?? []) {
|
|
49
|
-
const directory = resolve(root, member);
|
|
50
|
-
const manifest = readJson<PackageManifest>(join(directory, "package.json"));
|
|
51
|
-
const script = manifest.scripts?.compile;
|
|
52
|
-
if (!script) continue;
|
|
53
|
-
const taskPath = join(directory, ".projen", "tasks.json");
|
|
54
|
-
const steps = existsSync(taskPath)
|
|
55
|
-
? readJson<TaskManifest>(taskPath).tasks?.compile?.steps
|
|
56
|
-
: undefined;
|
|
57
|
-
if (
|
|
58
|
-
script === "projen compile" &&
|
|
59
|
-
!manifest.scripts?.precompile &&
|
|
60
|
-
!manifest.scripts?.postcompile &&
|
|
61
|
-
plainTypeScriptBuild(steps)
|
|
62
|
-
) {
|
|
63
|
-
typescriptConfigs.push(join(directory, "tsconfig.json"));
|
|
64
|
-
} else {
|
|
65
|
-
customPackages.push({ name: manifest.name ?? member, directory });
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return { typescriptConfigs, customPackages };
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const emoji = /[\p{Extended_Pictographic}\uFE0F\u200D]/gu;
|
|
72
|
-
|
|
73
|
-
async function main(): Promise<void> {
|
|
74
|
-
const root = process.cwd();
|
|
75
|
-
const plan = compilePlan(root);
|
|
76
|
-
const jobs: Array<{ name: string; result: Promise<exec.ExecResult> }> = [];
|
|
77
|
-
if (plan.typescriptConfigs.length > 0) {
|
|
78
|
-
const batchCount = Math.min(4, availableParallelism(), plan.typescriptConfigs.length);
|
|
79
|
-
const batches = Array.from({ length: batchCount }, () => [] as string[]);
|
|
80
|
-
plan.typescriptConfigs.forEach((config, index) => batches[index % batchCount]!.push(config));
|
|
81
|
-
console.log(
|
|
82
|
-
`Type-checking ${plan.typescriptConfigs.length} workspaces in ${batchCount} batches`,
|
|
83
|
-
);
|
|
84
|
-
for (const [index, batch] of batches.entries()) {
|
|
85
|
-
jobs.push({
|
|
86
|
-
name: `TypeScript batch ${index + 1}`,
|
|
87
|
-
result: exec.spawn("bunx", ["tsc", "--build", ...batch], {
|
|
88
|
-
cwd: root,
|
|
89
|
-
stdin: "ignore",
|
|
90
|
-
stdout: "inherit",
|
|
91
|
-
stderr: "inherit",
|
|
92
|
-
}),
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
for (const pkg of plan.customPackages) {
|
|
97
|
-
jobs.push({
|
|
98
|
-
name: pkg.name,
|
|
99
|
-
result: exec.spawn("bun", ["run", "compile"], {
|
|
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
|
-
}),
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
const results = await Promise.allSettled(jobs.map((job) => job.result));
|
|
108
|
-
let failed = false;
|
|
109
|
-
for (const [index, result] of results.entries()) {
|
|
110
|
-
if (result.status === "rejected") {
|
|
111
|
-
console.error(`${jobs[index]!.name} compile failed:`, result.reason);
|
|
112
|
-
failed = true;
|
|
113
|
-
} else if (result.value.exitCode !== 0) {
|
|
114
|
-
console.error(`${jobs[index]!.name} compile exited with ${result.value.exitCode}`);
|
|
115
|
-
failed = true;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
if (failed) process.exitCode = 1;
|
|
119
|
-
}
|
|
4
|
+
export { compilePlan, main } from "./lib/compile-workspace.ts";
|
|
120
5
|
|
|
121
6
|
if (import.meta.main) await main();
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
#!/usr/bin/env -S bun
|
|
2
|
+
/** Compile source-first workspace packages in a few TypeScript processes. */
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
+
import { availableParallelism } from "node:os";
|
|
5
|
+
import { join, resolve } from "node:path";
|
|
6
|
+
import * as exec from "@dbx-tools/core/exec";
|
|
7
|
+
|
|
8
|
+
interface PackageManifest {
|
|
9
|
+
readonly name?: string;
|
|
10
|
+
readonly scripts?: Record<string, string>;
|
|
11
|
+
readonly workspaces?: string[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface TaskManifest {
|
|
15
|
+
readonly tasks?: {
|
|
16
|
+
readonly compile?: {
|
|
17
|
+
readonly steps?: Array<{ readonly execArgs?: string[] }>;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface CompilePlan {
|
|
23
|
+
readonly typescriptConfigs: string[];
|
|
24
|
+
readonly customPackages: Array<{ readonly name: string; readonly directory: string }>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function readJson<T>(path: string): T {
|
|
28
|
+
return JSON.parse(readFileSync(path, "utf8")) as T;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function plainTypeScriptBuild(steps: unknown): boolean {
|
|
32
|
+
if (!Array.isArray(steps) || steps.length !== 1) return false;
|
|
33
|
+
const step = steps[0] as { execArgs?: string[] };
|
|
34
|
+
if (!step || typeof step !== "object") return false;
|
|
35
|
+
return (
|
|
36
|
+
Object.keys(step).length === 1 &&
|
|
37
|
+
step.execArgs?.length === 2 &&
|
|
38
|
+
step.execArgs[0] === "tsc" &&
|
|
39
|
+
step.execArgs[1] === "--build"
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Read the root's concrete workspace paths on every run, including newly added members. */
|
|
44
|
+
export function compilePlan(root: string): CompilePlan {
|
|
45
|
+
const workspace = readJson<PackageManifest>(join(root, "package.json"));
|
|
46
|
+
const typescriptConfigs: string[] = [];
|
|
47
|
+
const customPackages: CompilePlan["customPackages"] = [];
|
|
48
|
+
for (const member of workspace.workspaces ?? []) {
|
|
49
|
+
const directory = resolve(root, member);
|
|
50
|
+
const manifest = readJson<PackageManifest>(join(directory, "package.json"));
|
|
51
|
+
const script = manifest.scripts?.compile;
|
|
52
|
+
if (!script) continue;
|
|
53
|
+
const taskPath = join(directory, ".projen", "tasks.json");
|
|
54
|
+
const steps = existsSync(taskPath)
|
|
55
|
+
? readJson<TaskManifest>(taskPath).tasks?.compile?.steps
|
|
56
|
+
: undefined;
|
|
57
|
+
if (
|
|
58
|
+
script === "projen compile" &&
|
|
59
|
+
!manifest.scripts?.precompile &&
|
|
60
|
+
!manifest.scripts?.postcompile &&
|
|
61
|
+
plainTypeScriptBuild(steps)
|
|
62
|
+
) {
|
|
63
|
+
typescriptConfigs.push(join(directory, "tsconfig.json"));
|
|
64
|
+
} else {
|
|
65
|
+
customPackages.push({ name: manifest.name ?? member, directory });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return { typescriptConfigs, customPackages };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const emoji = /[\p{Extended_Pictographic}\uFE0F\u200D]/gu;
|
|
72
|
+
|
|
73
|
+
export async function main(): Promise<void> {
|
|
74
|
+
const root = process.cwd();
|
|
75
|
+
const plan = compilePlan(root);
|
|
76
|
+
const jobs: Array<{ name: string; result: Promise<exec.ExecResult> }> = [];
|
|
77
|
+
if (plan.typescriptConfigs.length > 0) {
|
|
78
|
+
const batchCount = Math.min(4, availableParallelism(), plan.typescriptConfigs.length);
|
|
79
|
+
const batches = Array.from({ length: batchCount }, () => [] as string[]);
|
|
80
|
+
plan.typescriptConfigs.forEach((config, index) => batches[index % batchCount]!.push(config));
|
|
81
|
+
console.log(
|
|
82
|
+
`Type-checking ${plan.typescriptConfigs.length} workspaces in ${batchCount} batches`,
|
|
83
|
+
);
|
|
84
|
+
for (const [index, batch] of batches.entries()) {
|
|
85
|
+
jobs.push({
|
|
86
|
+
name: `TypeScript batch ${index + 1}`,
|
|
87
|
+
result: exec.spawn("bunx", ["tsc", "--build", ...batch], {
|
|
88
|
+
cwd: root,
|
|
89
|
+
stdin: "ignore",
|
|
90
|
+
stdout: "inherit",
|
|
91
|
+
stderr: "inherit",
|
|
92
|
+
}),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
for (const pkg of plan.customPackages) {
|
|
97
|
+
jobs.push({
|
|
98
|
+
name: pkg.name,
|
|
99
|
+
result: exec.spawn("bun", ["run", "compile"], {
|
|
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
|
+
}),
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
const results = await Promise.allSettled(jobs.map((job) => job.result));
|
|
108
|
+
let failed = false;
|
|
109
|
+
for (const [index, result] of results.entries()) {
|
|
110
|
+
if (result.status === "rejected") {
|
|
111
|
+
console.error(`${jobs[index]!.name} compile failed:`, result.reason);
|
|
112
|
+
failed = true;
|
|
113
|
+
} else if (result.value.exitCode !== 0) {
|
|
114
|
+
console.error(`${jobs[index]!.name} compile exited with ${result.value.exitCode}`);
|
|
115
|
+
failed = true;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (failed) process.exitCode = 1;
|
|
119
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/** Build and publish the current workspace to configured loopback registries. */
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import * as projectUtils from "@dbx-tools/core/project-utils";
|
|
5
|
+
import { log, net } from "@dbx-tools/shared-core";
|
|
6
|
+
import { Command } from "commander";
|
|
7
|
+
import { activePythonIndexes, resolveLocalPypi } from "./python-registry.ts";
|
|
8
|
+
import { runTaskCommandAsync } from "../../src/_task-command.ts";
|
|
9
|
+
|
|
10
|
+
const logger = log.logger("projen:local-publish");
|
|
11
|
+
|
|
12
|
+
/** Options for publishing the current workspace to local registries. */
|
|
13
|
+
export interface LocalPublishOptions {
|
|
14
|
+
readonly localPypi: string;
|
|
15
|
+
readonly localRegistry: string;
|
|
16
|
+
readonly pythonRoot: string;
|
|
17
|
+
readonly root: string;
|
|
18
|
+
readonly version: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function resolveLocalRegistry(value: string): string | undefined {
|
|
22
|
+
const trimmed = value.trim();
|
|
23
|
+
if (!trimmed || trimmed.toLowerCase() === "false") return undefined;
|
|
24
|
+
if (trimmed.toLowerCase() === "auto") {
|
|
25
|
+
const registry = projectUtils.npmRegistry();
|
|
26
|
+
return registry && net.isLoopbackHost(registry) ? registry.href : undefined;
|
|
27
|
+
}
|
|
28
|
+
return trimmed;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Outcome of a local publish: which ecosystems were deployed to a local registry. */
|
|
32
|
+
export interface LocalPublishResult {
|
|
33
|
+
readonly npm: boolean;
|
|
34
|
+
readonly python: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Build and publish the current workspace directly to local registries. */
|
|
38
|
+
export async function publishLocalRelease(
|
|
39
|
+
options: LocalPublishOptions,
|
|
40
|
+
): Promise<LocalPublishResult> {
|
|
41
|
+
const root = resolve(options.root);
|
|
42
|
+
const localRegistry = resolveLocalRegistry(options.localRegistry);
|
|
43
|
+
const activeIndexes = activePythonIndexes();
|
|
44
|
+
const localPypi = resolveLocalPypi(options.localPypi, activeIndexes);
|
|
45
|
+
const publishes: Promise<unknown>[] = [];
|
|
46
|
+
|
|
47
|
+
if (
|
|
48
|
+
options.localPypi.toLowerCase() === "auto" &&
|
|
49
|
+
activeIndexes.some((index) => net.isLoopbackHost(index)) &&
|
|
50
|
+
!localPypi
|
|
51
|
+
) {
|
|
52
|
+
logger.info(
|
|
53
|
+
`skipped local Python publish: no active index (${activeIndexes.join(", ")}) is a devpi +simple index`,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (localRegistry) {
|
|
58
|
+
const publishNpmScript = fileURLToPath(new URL("../publish.ts", import.meta.url));
|
|
59
|
+
logger.info(`building and publishing npm workspace to ${localRegistry}`);
|
|
60
|
+
publishes.push(
|
|
61
|
+
runTaskCommandAsync(root, process.execPath, [
|
|
62
|
+
publishNpmScript,
|
|
63
|
+
options.version,
|
|
64
|
+
"--registry",
|
|
65
|
+
localRegistry,
|
|
66
|
+
]),
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (localPypi) {
|
|
71
|
+
const publishPythonScript = fileURLToPath(new URL("../publish-python.ts", import.meta.url));
|
|
72
|
+
logger.info(`building and publishing Python workspace to ${localPypi.publishUrl}`);
|
|
73
|
+
publishes.push(
|
|
74
|
+
runTaskCommandAsync(root, process.execPath, [
|
|
75
|
+
publishPythonScript,
|
|
76
|
+
options.version,
|
|
77
|
+
"--root",
|
|
78
|
+
options.pythonRoot,
|
|
79
|
+
"--index-url",
|
|
80
|
+
localPypi.indexUrl,
|
|
81
|
+
"--publish-url",
|
|
82
|
+
localPypi.publishUrl,
|
|
83
|
+
]),
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (publishes.length === 0) {
|
|
88
|
+
logger.info("no local registries configured; skipping local publish");
|
|
89
|
+
return { npm: false, python: false };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
await Promise.all(publishes);
|
|
93
|
+
logger.success(`published local workspace ${options.version}`);
|
|
94
|
+
return { npm: Boolean(localRegistry), python: Boolean(localPypi) };
|
|
95
|
+
}
|
|
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
|
+
}
|