@dbx-tools/projen 0.9.32 → 0.9.37
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 +47 -85
- 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 -118
- 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 -121
- 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/tasks/release-version.ts
CHANGED
|
@@ -1,65 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env -S bun
|
|
2
|
-
|
|
3
|
-
import { resolve } from "node:path";
|
|
4
|
-
import { parseArgs } from "node:util";
|
|
5
|
-
import * as projectUtils from "@dbx-tools/core/project-utils";
|
|
6
|
-
import { captureGitTaskCommand } from "../src/_task-command.ts";
|
|
7
|
-
import { compareSemver, parseSemver, type Semver } from "../src/workspace-version.ts";
|
|
2
|
+
import { main } from "./lib/release-version.ts";
|
|
8
3
|
|
|
9
|
-
|
|
10
|
-
let best: Semver | undefined;
|
|
11
|
-
for (const prefix of prefixes) {
|
|
12
|
-
const tags = captureGitTaskCommand(root, [
|
|
13
|
-
"-c",
|
|
14
|
-
"versionsort.suffix=-",
|
|
15
|
-
"tag",
|
|
16
|
-
"--sort=-version:refname",
|
|
17
|
-
"--list",
|
|
18
|
-
`${prefix}*`,
|
|
19
|
-
]);
|
|
20
|
-
for (const tag of tags.split("\n")) {
|
|
21
|
-
if (!tag.startsWith(prefix)) continue;
|
|
22
|
-
const version = parseSemver(tag.slice(prefix.length));
|
|
23
|
-
if (version && (!best || compareSemver(version, best) > 0)) best = version;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
return best?.join(".");
|
|
27
|
-
}
|
|
4
|
+
export * from "./lib/release-version.ts";
|
|
28
5
|
|
|
29
|
-
|
|
30
|
-
version: string,
|
|
31
|
-
options: { root?: string; prefixes?: readonly string[]; assertNext?: boolean } = {},
|
|
32
|
-
): void {
|
|
33
|
-
const parsed = parseSemver(version);
|
|
34
|
-
if (!parsed) {
|
|
35
|
-
throw new Error(
|
|
36
|
-
`release version must be an exact stable x.y.z, got ${JSON.stringify(version)}`,
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
if (!options.assertNext) return;
|
|
40
|
-
const root = resolve(options.root ?? projectUtils.root() ?? process.cwd());
|
|
41
|
-
const prefixes = options.prefixes?.length ? options.prefixes : ["v"];
|
|
42
|
-
const current = publishedVersion(root, prefixes);
|
|
43
|
-
const currentParsed = current ? parseSemver(current) : undefined;
|
|
44
|
-
if (currentParsed && compareSemver(parsed, currentParsed) <= 0) {
|
|
45
|
-
throw new Error(`release version ${version} must be greater than published ${current}`);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (import.meta.main) {
|
|
50
|
-
const { values } = parseArgs({
|
|
51
|
-
options: {
|
|
52
|
-
version: { type: "string" },
|
|
53
|
-
root: { type: "string" },
|
|
54
|
-
prefix: { type: "string", multiple: true },
|
|
55
|
-
"assert-next": { type: "boolean", default: false },
|
|
56
|
-
},
|
|
57
|
-
strict: true,
|
|
58
|
-
});
|
|
59
|
-
if (!values.version) throw new Error("--version is required");
|
|
60
|
-
assertReleaseVersion(values.version, {
|
|
61
|
-
...(values.root ? { root: values.root } : {}),
|
|
62
|
-
...(values.prefix ? { prefixes: values.prefix } : {}),
|
|
63
|
-
assertNext: values["assert-next"],
|
|
64
|
-
});
|
|
65
|
-
}
|
|
6
|
+
if (import.meta.main) main();
|
package/tasks/release.ts
CHANGED
|
@@ -1,124 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env -S bun
|
|
2
|
-
|
|
3
|
-
import * as projectUtils from "@dbx-tools/core/project-utils";
|
|
4
|
-
import { log } from "@dbx-tools/shared-core";
|
|
5
|
-
import { Command } from "commander";
|
|
6
|
-
import {
|
|
7
|
-
captureGitTaskCommand,
|
|
8
|
-
gitTaskCommandSucceeds,
|
|
9
|
-
runGitTaskCommand,
|
|
10
|
-
runTaskCommand,
|
|
11
|
-
} from "../src/_task-command.ts";
|
|
12
|
-
import { assertReleaseVersion } from "./release-version.ts";
|
|
13
|
-
import { publishLocalRelease } from "./local-publish.ts";
|
|
14
|
-
import { readWorkspaceVersion } from "../src/workspace-version.ts";
|
|
2
|
+
import { main } from "./lib/release.ts";
|
|
15
3
|
|
|
16
|
-
|
|
4
|
+
export { main, runRelease } from "./lib/release.ts";
|
|
17
5
|
|
|
18
|
-
|
|
19
|
-
readonly root: string;
|
|
20
|
-
readonly branch: string;
|
|
21
|
-
readonly bump?: boolean;
|
|
22
|
-
readonly prefix: string;
|
|
23
|
-
readonly remote: string;
|
|
24
|
-
readonly localPublish?: boolean;
|
|
25
|
-
readonly pythonRoot?: string;
|
|
26
|
-
}): Promise<string> {
|
|
27
|
-
const { branch, prefix, remote, root } = options;
|
|
28
|
-
|
|
29
|
-
// Release runs from any branch: whatever is checked out is fast-forwarded onto
|
|
30
|
-
// `branch`. The working tree is committed into the release commit unless it is
|
|
31
|
-
// already clean - the clean tree is a convenience, not a precondition.
|
|
32
|
-
if (options.bump ?? true) {
|
|
33
|
-
runTaskCommand(root, "bun", ["run", "bump"]);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const version = readWorkspaceVersion(root);
|
|
37
|
-
const tag = `${prefix}${version}`;
|
|
38
|
-
runGitTaskCommand(root, ["fetch", remote, branch, "--tags"]);
|
|
39
|
-
assertReleaseVersion(version, { root, prefixes: [prefix], assertNext: true });
|
|
40
|
-
|
|
41
|
-
if (gitTaskCommandSucceeds(root, ["rev-parse", "--verify", `refs/tags/${tag}`])) {
|
|
42
|
-
throw new Error(`release tag ${tag} already exists`);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
runTaskCommand(root, "bun", ["run", "version:check"]);
|
|
46
|
-
const status = captureGitTaskCommand(
|
|
47
|
-
root,
|
|
48
|
-
["status", "--porcelain=v1", "--untracked-files=all"],
|
|
49
|
-
{ check: true },
|
|
50
|
-
);
|
|
51
|
-
if (status) {
|
|
52
|
-
if (gitTaskCommandSucceeds(root, ["diff", "--quiet", "HEAD", "--", "VERSION"])) {
|
|
53
|
-
throw new Error("release changes must include the local VERSION bump");
|
|
54
|
-
}
|
|
55
|
-
runGitTaskCommand(root, ["add", "--all"]);
|
|
56
|
-
runGitTaskCommand(root, ["commit", "-m", `chore(release): ${version}`]);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Fast-forward only: HEAD must already contain the remote branch tip, otherwise
|
|
60
|
-
// the push would be a non-fast-forward (an unclean merge) and we fail instead
|
|
61
|
-
// of force-pushing. This holds regardless of which branch is checked out.
|
|
62
|
-
const remoteHead = captureGitTaskCommand(root, ["rev-parse", `${remote}/${branch}`], {
|
|
63
|
-
check: true,
|
|
64
|
-
});
|
|
65
|
-
const head = captureGitTaskCommand(root, ["rev-parse", "HEAD"], { check: true });
|
|
66
|
-
if (!gitTaskCommandSucceeds(root, ["merge-base", "--is-ancestor", remoteHead, head])) {
|
|
67
|
-
throw new Error(
|
|
68
|
-
`cannot fast-forward ${remote}/${branch} to HEAD; ${branch} has diverged (rebase before release)`,
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
runGitTaskCommand(root, ["push", remote, `HEAD:${branch}`]);
|
|
73
|
-
runGitTaskCommand(root, ["tag", "--annotate", tag, "--message", tag]);
|
|
74
|
-
runGitTaskCommand(root, ["push", remote, `refs/tags/${tag}`]);
|
|
75
|
-
logger.success(`released ${tag}`, { sha: head });
|
|
76
|
-
|
|
77
|
-
// Restore local deploys: when a non-standard (loopback) npm or uv registry is
|
|
78
|
-
// configured, publish the freshly tagged version to it directly. Detection and
|
|
79
|
-
// publishing are owned by local-publish.ts; this no-ops on standard registries.
|
|
80
|
-
if (options.localPublish ?? true) {
|
|
81
|
-
await publishLocalRelease({
|
|
82
|
-
localPypi: "auto",
|
|
83
|
-
localRegistry: "auto",
|
|
84
|
-
pythonRoot: options.pythonRoot ?? "packages/py",
|
|
85
|
-
root,
|
|
86
|
-
version,
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return tag;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (import.meta.main) {
|
|
94
|
-
await new Command()
|
|
95
|
-
.option("--root <path>", "repository root")
|
|
96
|
-
.option("--branch <name>", "release branch", "main")
|
|
97
|
-
.option("--prefix <prefix>", "release tag prefix", "v")
|
|
98
|
-
.option("--remote <name>", "git remote", "origin")
|
|
99
|
-
.option("--python-root <path>", "Python package root for local publish", "packages/py")
|
|
100
|
-
.option("--no-bump", "use an existing synchronized local version bump")
|
|
101
|
-
.option("--no-local-publish", "skip publishing to configured local registries")
|
|
102
|
-
.action(
|
|
103
|
-
async (options: {
|
|
104
|
-
root?: string;
|
|
105
|
-
branch: string;
|
|
106
|
-
bump: boolean;
|
|
107
|
-
prefix: string;
|
|
108
|
-
remote: string;
|
|
109
|
-
pythonRoot: string;
|
|
110
|
-
localPublish: boolean;
|
|
111
|
-
}) => {
|
|
112
|
-
await runRelease({
|
|
113
|
-
root: options.root ?? projectUtils.root() ?? process.cwd(),
|
|
114
|
-
branch: options.branch,
|
|
115
|
-
bump: options.bump,
|
|
116
|
-
prefix: options.prefix,
|
|
117
|
-
remote: options.remote,
|
|
118
|
-
pythonRoot: options.pythonRoot,
|
|
119
|
-
localPublish: options.localPublish,
|
|
120
|
-
});
|
|
121
|
-
},
|
|
122
|
-
)
|
|
123
|
-
.parseAsync();
|
|
124
|
-
}
|
|
6
|
+
if (import.meta.main) await main();
|
package/tasks/sync.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
import { log } from "@dbx-tools/shared-core";
|
|
4
4
|
import concurrently from "concurrently";
|
|
5
|
-
import {
|
|
5
|
+
import { repoRoot } from "../src/packages.ts";
|
|
6
6
|
import { runSynth } from "../src/scaffold.ts";
|
|
7
7
|
import { withWorkspaceMutationLock } from "../src/workspace-lock.ts";
|
|
8
8
|
|
|
@@ -35,79 +35,80 @@ function watcher(script: string, name: string, prefixColor: string, ...args: str
|
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
logger.success("synced");
|
|
44
|
-
} else {
|
|
45
|
-
// Watch: one initial full synth to bring the tree up to date, then focused
|
|
46
|
-
// watchers under `concurrently`. The projenrc watcher is the intelligent stand-in
|
|
47
|
-
// for stock `projen --watch` - it re-synths (+install) ONLY when `.projenrc.ts` or
|
|
48
|
-
// a configured `syncResynthPaths` entry changes, while barrels/openapi keep generated
|
|
49
|
-
// OUTPUT fresh on source edits with no full synth.
|
|
50
|
-
//
|
|
51
|
-
// VS Code auto-runs this on folder open, so from here down nothing is allowed to be
|
|
52
|
-
// fatal: errors are logged and retried, and only a stop signal ends the task.
|
|
53
|
-
logger.start("initial sync");
|
|
54
|
-
try {
|
|
38
|
+
export async function main(args: string[] = process.argv.slice(2)): Promise<void> {
|
|
39
|
+
if (!args.includes("--watch")) {
|
|
40
|
+
// One-shot: full synth (+install + barrels via the post-synth component). This is
|
|
41
|
+
// the scriptable path, so a failed synth stays a failed exit code.
|
|
42
|
+
logger.start("synthesizing");
|
|
55
43
|
await withWorkspaceMutationLock(repoRoot, () => runSynth({ post: true }));
|
|
56
|
-
logger.success("synced
|
|
57
|
-
}
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
44
|
+
logger.success("synced");
|
|
45
|
+
} else {
|
|
46
|
+
// Watch: one initial full synth to bring the tree up to date, then focused
|
|
47
|
+
// watchers under `concurrently`. The projenrc watcher is the intelligent stand-in
|
|
48
|
+
// for stock `projen --watch` - it re-synths (+install) ONLY when `.projenrc.ts` or
|
|
49
|
+
// a configured `syncResynthPaths` entry changes, while barrels/openapi keep generated
|
|
50
|
+
// OUTPUT fresh on source edits with no full synth.
|
|
51
|
+
//
|
|
52
|
+
// VS Code auto-runs this on folder open, so from here down nothing is allowed to be
|
|
53
|
+
// fatal: errors are logged and retried, and only a stop signal ends the task.
|
|
54
|
+
logger.start("initial sync");
|
|
55
|
+
try {
|
|
56
|
+
await withWorkspaceMutationLock(repoRoot, () => runSynth({ post: true }));
|
|
57
|
+
logger.success("synced - watching (Ctrl-C to stop)");
|
|
58
|
+
} catch (err) {
|
|
59
|
+
// A tree that doesn't synth is precisely when the watcher is most useful: the edit
|
|
60
|
+
// that repairs it is the one the projenrc watcher is sitting there waiting for.
|
|
61
|
+
logger.error(
|
|
62
|
+
"initial sync failed - watching anyway:",
|
|
63
|
+
err instanceof Error ? err.message : err,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
65
66
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
restartTries: -1,
|
|
80
|
-
restartDelay: RESTART_DELAY_MS,
|
|
81
|
-
});
|
|
67
|
+
const watchers = [
|
|
68
|
+
watcher("projenrc.ts", "projenrc", "magenta"),
|
|
69
|
+
watcher("barrels.ts", "barrels", "cyan", "--watch"),
|
|
70
|
+
watcher("openapi.ts", "openapi", "green", "--watch"),
|
|
71
|
+
];
|
|
72
|
+
const { result } = concurrently(watchers, {
|
|
73
|
+
prefix: "name",
|
|
74
|
+
// No `killOthersOn`: one watcher falling over is no reason to tear the other two
|
|
75
|
+
// down. `-1` is concurrently's spelling for "restart forever", so a crashed
|
|
76
|
+
// watcher comes back instead of silently leaving its outputs stale.
|
|
77
|
+
restartTries: -1,
|
|
78
|
+
restartDelay: RESTART_DELAY_MS,
|
|
79
|
+
});
|
|
82
80
|
|
|
83
|
-
|
|
81
|
+
let stopping = false;
|
|
84
82
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Wind the watchers down for good.
|
|
85
|
+
*
|
|
86
|
+
* Restarting forever means an ordinary SIGTERM would be answered by respawning every
|
|
87
|
+
* watcher, so the task has to opt out explicitly. SIGINT is the one signal concurrently
|
|
88
|
+
* neutralizes - it rewrites that exit to 0 before the restart controller sees it - so
|
|
89
|
+
* re-emitting it is how we say "stop" in a language the supervisor understands.
|
|
90
|
+
*
|
|
91
|
+
* Killing is asynchronous (concurrently shells out to `ps` to walk each process tree)
|
|
92
|
+
* and can outlive `result`. The timer keeps the process open until the kills
|
|
93
|
+
* land, which prevents orphaned watchers after SIGTERM. It also stops waiting
|
|
94
|
+
* if one of them wedges.
|
|
95
|
+
*/
|
|
96
|
+
function stop(): void {
|
|
97
|
+
if (stopping) return;
|
|
98
|
+
stopping = true;
|
|
99
|
+
process.emit("SIGINT", "SIGINT");
|
|
100
|
+
setTimeout(() => process.exit(0), STOP_GRACE_MS);
|
|
101
|
+
}
|
|
104
102
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
103
|
+
// Registered after `concurrently()` so its own signal handler - the one that actually
|
|
104
|
+
// kills the children - is already in place by the time ours can fire.
|
|
105
|
+
for (const signal of ["SIGTERM", "SIGHUP"] as const) process.on(signal, stop);
|
|
108
106
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
107
|
+
// With infinite restarts this settles only once a stop signal has wound the watchers
|
|
108
|
+
// down, so there is no failure left to report. When `stop()` is driving, the pending
|
|
109
|
+
// grace timer keeps us alive past this point and owns the exit.
|
|
110
|
+
await result.catch(() => {});
|
|
111
|
+
}
|
|
113
112
|
}
|
|
113
|
+
|
|
114
|
+
if (import.meta.main) await main();
|
package/tasks/version-check.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env -S bun
|
|
2
2
|
/** Verify that every committed package version matches the root VERSION file. */
|
|
3
|
-
import {
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import * as projectUtils from "@dbx-tools/core/project-utils";
|
|
6
6
|
import { find } from "@dbx-tools/path";
|
|
@@ -23,7 +23,7 @@ function pythonVersion(path: string): string | undefined {
|
|
|
23
23
|
return typeof manifest.project?.version === "string" ? manifest.project.version : undefined;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
function main(): void {
|
|
26
|
+
export function main(): void {
|
|
27
27
|
const root = projectUtils.root() ?? process.cwd();
|
|
28
28
|
const version = readWorkspaceVersion(root);
|
|
29
29
|
const mismatches: string[] = [];
|
|
@@ -34,13 +34,6 @@ function main(): void {
|
|
|
34
34
|
check("package.json", manifestVersion(join(root, "package.json")));
|
|
35
35
|
for (const pkg of recordedPackages(root)) {
|
|
36
36
|
check(`${pkg.path}/package.json`, manifestVersion(join(pkg.dir, "package.json")));
|
|
37
|
-
const barrel = join(pkg.dir, "index.ts");
|
|
38
|
-
if (existsSync(barrel)) {
|
|
39
|
-
const actual = /export const PACKAGE_VERSION = "([^"]+)";/.exec(
|
|
40
|
-
readFileSync(barrel, "utf8"),
|
|
41
|
-
)?.[1];
|
|
42
|
-
check(`${pkg.path}/index.ts`, actual);
|
|
43
|
-
}
|
|
44
37
|
}
|
|
45
38
|
|
|
46
39
|
for (const path of find.findFiles("**/pyproject.toml", { cwd: root })) {
|
|
@@ -52,4 +45,4 @@ function main(): void {
|
|
|
52
45
|
logger.success(`all workspace package versions match ${version}`);
|
|
53
46
|
}
|
|
54
47
|
|
|
55
|
-
main();
|
|
48
|
+
if (import.meta.main) main();
|