@dbx-tools/projen 0.9.93 → 0.9.95
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 +17 -4
- package/index.ts +3 -1
- package/package.json +4 -4
- package/src/dev-watch.ts +15 -0
- package/src/pnpm-workspace.ts +1 -0
- package/src/project-js.ts +6 -0
- package/src/tags.ts +2 -6
- package/tasks/dev-watch.ts +339 -0
- package/tasks/release.ts +15 -3
package/README.md
CHANGED
|
@@ -308,6 +308,18 @@ Built-in tag mixins set runtime defaults for `shared`, `node`, `cli`, `server`,
|
|
|
308
308
|
and `ui`. Repo-specific mixins layer package-specific dependencies,
|
|
309
309
|
scripts, and generated files on top.
|
|
310
310
|
|
|
311
|
+
Every workspace root exposes a generic development watcher:
|
|
312
|
+
|
|
313
|
+
```sh
|
|
314
|
+
bun run dev:watch [--debounce-ms 15000] [--restart-key r] <command> [args...]
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
It restarts after watched writes have been quiet for 15 seconds. Command paths,
|
|
318
|
+
`--cwd`, and `--filter` identify the owning package; its transitive workspace
|
|
319
|
+
dependencies are watched with the shared generated/dependency/test/temp ignores.
|
|
320
|
+
Press the configured key for an immediate restart. The demo uses this task to
|
|
321
|
+
run its concrete server entrypoint; the `server` tag does not assume one.
|
|
322
|
+
|
|
311
323
|
AppKit 0.81's `appkitServerConfig()` is the preferred tsdown preset for a
|
|
312
324
|
consumer app using conventional compiled `server/agents/*/agent.ts` entries. It
|
|
313
325
|
does not replace this engine's package discovery, Bun browser build, source-run
|
|
@@ -488,10 +500,10 @@ bun run release --no-release-notes
|
|
|
488
500
|
bun run release --release-notes-instructions "Focus on operator-visible changes."
|
|
489
501
|
```
|
|
490
502
|
|
|
491
|
-
`--demo-deploy` is off by default
|
|
492
|
-
publication it runs `bun run demo:deploy`, which stages
|
|
493
|
-
from locally compiled Node packages and locally built Python
|
|
494
|
-
the
|
|
503
|
+
`--demo-deploy` is off by default and requires `--profile <name>`. After tagging
|
|
504
|
+
and any local registry publication it runs `bun run demo:deploy`, which stages
|
|
505
|
+
the AppKit demo from locally compiled Node packages and locally built Python
|
|
506
|
+
wheels, resolves only the selected workspace profile through `@dbx-tools/auth`,
|
|
495
507
|
force-refreshes its token with interactive login fallback, and deploys it. That
|
|
496
508
|
step is local only and is not recorded in the annotated tag.
|
|
497
509
|
|
|
@@ -580,6 +592,7 @@ release [options]
|
|
|
580
592
|
| `--no-release-notes` | skip writing docs/releases notes (Genie and git-log fallback) |
|
|
581
593
|
| `--release-notes-instructions <text>` | append custom instructions to the Genie release-notes prompt |
|
|
582
594
|
| `--demo-deploy` | after tagging, stage and deploy the AppKit demo app (off by default) |
|
|
595
|
+
| `--profile <name>` | Databricks CLI profile for --demo-deploy |
|
|
583
596
|
| `--no-local-publish` | skip publishing to configured local registries |
|
|
584
597
|
| `--local-registry <auto\|false\|url>` | local npm registry selection (default: "auto") |
|
|
585
598
|
| `--local-pypi <auto\|false\|url>` | local devpi registry selection (default: "auto") |
|
package/index.ts
CHANGED
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
// Hand edits are overwritten on the next watch; this file is read-only.
|
|
4
4
|
|
|
5
5
|
export const PACKAGE_IDENTIFIER = "@dbx-tools/projen";
|
|
6
|
-
export const PACKAGE_VERSION = "0.9.
|
|
6
|
+
export const PACKAGE_VERSION = "0.9.95";
|
|
7
7
|
export * as barrels from "./src/barrels.ts";
|
|
8
8
|
export * as bunApp from "./src/bun-app.ts";
|
|
9
9
|
export * as bunWorkflow from "./src/bun-workflow.ts";
|
|
10
10
|
export * as clean from "./src/clean.ts";
|
|
11
11
|
export * as codegen from "./src/codegen.ts";
|
|
12
12
|
export * as dbxToolsConfig from "./src/dbx-tools-config.ts";
|
|
13
|
+
export * as devWatch from "./src/dev-watch.ts";
|
|
13
14
|
export * as engineRoot from "./src/engine-root.ts";
|
|
14
15
|
export * as generated from "./src/generated.ts";
|
|
15
16
|
export * as mixin from "./src/mixin.ts";
|
|
@@ -40,6 +41,7 @@ export { BUN_DEV_OVERRIDE, BUN_BUILD_OVERRIDE, BUN_APP_OVERRIDES, RootBunfigFile
|
|
|
40
41
|
export { BUN_VERSION } from "./src/bun-workflow.ts";
|
|
41
42
|
export { DBXToolsConfigDataSchema, DBXToolsConfig } from "./src/dbx-tools-config.ts";
|
|
42
43
|
export type { DBXToolsConfigOptions, DBXToolsConfigData } from "./src/dbx-tools-config.ts";
|
|
44
|
+
export { DEV_WATCH_TASK, DEV_WATCH_SCRIPT, DEV_RESTART_DEBOUNCE_MS, DEV_RESTART_KEY, SERVER_WATCH_DISABLED_ENV } from "./src/dev-watch.ts";
|
|
43
45
|
export { resolvePkgRoot } from "./src/engine-root.ts";
|
|
44
46
|
export { PYTHON_GENERATED_PACKAGE, PYTHON_SYNC_PACKAGE } from "./src/generated.ts";
|
|
45
47
|
export type { HeaderOpts } from "./src/generated.ts";
|
package/package.json
CHANGED
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@clack/prompts": "^1.7.0",
|
|
33
|
-
"@dbx-tools/core": "^0.9.
|
|
34
|
-
"@dbx-tools/path": "^0.9.
|
|
35
|
-
"@dbx-tools/shared-core": "^0.9.
|
|
33
|
+
"@dbx-tools/core": "^0.9.95",
|
|
34
|
+
"@dbx-tools/path": "^0.9.95",
|
|
35
|
+
"@dbx-tools/shared-core": "^0.9.95",
|
|
36
36
|
"commander": "^15.0.0",
|
|
37
37
|
"concurrently": "^10.0.3",
|
|
38
38
|
"constructs": "^10.6.0",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
},
|
|
55
55
|
"main": "index.ts",
|
|
56
56
|
"license": "Apache-2.0",
|
|
57
|
-
"version": "0.9.
|
|
57
|
+
"version": "0.9.95",
|
|
58
58
|
"types": "index.ts",
|
|
59
59
|
"type": "module",
|
|
60
60
|
"exports": {
|
package/src/dev-watch.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Root task and CLI defaults for the generic development command watcher. */
|
|
2
|
+
|
|
3
|
+
/** Projen task exposed as `bun run dev:watch`. */
|
|
4
|
+
export const DEV_WATCH_TASK = "dev:watch";
|
|
5
|
+
/** Shipped task entrypoint invoked by {@link DEV_WATCH_TASK}. */
|
|
6
|
+
export const DEV_WATCH_SCRIPT = "dev-watch.ts";
|
|
7
|
+
/** Quiet period after the last watched change before restarting. */
|
|
8
|
+
export const DEV_RESTART_DEBOUNCE_MS = 15_000;
|
|
9
|
+
/** Interactive key that requests an immediate restart. */
|
|
10
|
+
export const DEV_RESTART_KEY = "r";
|
|
11
|
+
/**
|
|
12
|
+
* Process environment that skips wrapping or running the file watcher.
|
|
13
|
+
* Parsed with `configUtils.boolean` (`1` / `true` / `yes` disable watch).
|
|
14
|
+
*/
|
|
15
|
+
export const SERVER_WATCH_DISABLED_ENV = "SERVER_WATCH_DISABLED";
|
package/src/pnpm-workspace.ts
CHANGED
package/src/project-js.ts
CHANGED
|
@@ -21,6 +21,7 @@ import { BUN_APP_OVERRIDES, RootBunfigFile } from "./bun-app.ts";
|
|
|
21
21
|
import { BUN_VERSION } from "./bun-workflow.ts";
|
|
22
22
|
import { codegenModulePaths, generateCodegen } from "./codegen.ts";
|
|
23
23
|
import { DBXToolsConfig, type DBXToolsConfigOptions } from "./dbx-tools-config.ts";
|
|
24
|
+
import { DEV_WATCH_SCRIPT, DEV_WATCH_TASK } from "./dev-watch.ts";
|
|
24
25
|
import { resolvePkgRoot } from "./engine-root.ts";
|
|
25
26
|
import * as mixin from "./mixin.ts";
|
|
26
27
|
import {
|
|
@@ -1070,6 +1071,11 @@ function resolveTags(p: DiscoveredPackage, tagPaths: Record<string, string[]>):
|
|
|
1070
1071
|
function registerRootTasks(project: javascript.NodeProject): void {
|
|
1071
1072
|
project.addTask("barrels", { execArgs: taskCommand("barrels.ts") });
|
|
1072
1073
|
project.addTask("clean", { execArgs: taskCommand("clean.ts"), receiveArgs: true });
|
|
1074
|
+
project.addTask(DEV_WATCH_TASK, {
|
|
1075
|
+
description: "Restart a development command after watched changes settle",
|
|
1076
|
+
execArgs: taskCommand(DEV_WATCH_SCRIPT),
|
|
1077
|
+
receiveArgs: true,
|
|
1078
|
+
});
|
|
1073
1079
|
project.addTask("sync", {
|
|
1074
1080
|
execArgs: taskCommand("sync.ts"),
|
|
1075
1081
|
receiveArgs: true,
|
package/src/tags.ts
CHANGED
|
@@ -163,15 +163,11 @@ export const PACKAGE_TAG_MIXINS = {
|
|
|
163
163
|
addPackageFiles(p, "bin");
|
|
164
164
|
}),
|
|
165
165
|
server: create(projectPredicate.hasTag("server"), (p) => {
|
|
166
|
-
// A Node/Express service.
|
|
167
|
-
//
|
|
166
|
+
// A Node/Express service. The root's generic `dev:watch` task owns watched
|
|
167
|
+
// execution because this tag cannot assume an entrypoint or command shape.
|
|
168
168
|
p.addDeps("express@catalog:");
|
|
169
169
|
p.addDevDeps("@types/node@catalog:", "@types/express@catalog:");
|
|
170
170
|
applyCompilerOptions(p, NODE_COMPILER_OPTIONS);
|
|
171
|
-
// bun runs the server `.ts` directly (native TS, no tsx). `--watch` restarts
|
|
172
|
-
// on change - the tsx-watch replacement.
|
|
173
|
-
(p.tasks.tryFind("dev") ?? p.addTask("dev")).reset("bun --watch src/server.ts");
|
|
174
|
-
(p.tasks.tryFind("start") ?? p.addTask("start")).reset("bun src/server.ts");
|
|
175
171
|
}),
|
|
176
172
|
node: create(projectPredicate.hasTag("node"), (p) => {
|
|
177
173
|
p.addDevDeps("@types/node@catalog:");
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
#!/usr/bin/env -S bun
|
|
2
|
+
/** Restart a development command after relevant workspace changes settle. */
|
|
3
|
+
import { isAbsolute, relative, resolve } from "node:path";
|
|
4
|
+
import { configUtils } from "@dbx-tools/core";
|
|
5
|
+
import * as exec from "@dbx-tools/core/exec";
|
|
6
|
+
import { watch as pathWatch } from "@dbx-tools/path";
|
|
7
|
+
import { log } from "@dbx-tools/shared-core";
|
|
8
|
+
import { Command, CommanderError, InvalidArgumentError, Option } from "commander";
|
|
9
|
+
import {
|
|
10
|
+
DEV_RESTART_DEBOUNCE_MS,
|
|
11
|
+
DEV_RESTART_KEY,
|
|
12
|
+
DEV_WATCH_TASK,
|
|
13
|
+
SERVER_WATCH_DISABLED_ENV,
|
|
14
|
+
} from "../src/dev-watch.ts";
|
|
15
|
+
import {
|
|
16
|
+
readPackageManifest,
|
|
17
|
+
recordedPackages,
|
|
18
|
+
resolveRepoRoot,
|
|
19
|
+
workspaceDependencyDirectories,
|
|
20
|
+
} from "../src/packages.ts";
|
|
21
|
+
|
|
22
|
+
const logger = log.logger("projen:dev-watch");
|
|
23
|
+
const SHUTDOWN_TIMEOUT_MS = 2_000;
|
|
24
|
+
|
|
25
|
+
export interface DevWatchOptions {
|
|
26
|
+
readonly command: readonly string[];
|
|
27
|
+
readonly debounceMs: number;
|
|
28
|
+
readonly restartKey: string;
|
|
29
|
+
/** When true, run the command once and skip file-change restarts. */
|
|
30
|
+
readonly serverWatchDisabled: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface WorkspacePackage {
|
|
34
|
+
readonly dir: string;
|
|
35
|
+
readonly name?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function debounceMilliseconds(value: string): number {
|
|
39
|
+
const parsed = configUtils.toNumber(value);
|
|
40
|
+
if (parsed === undefined || parsed < 0) {
|
|
41
|
+
throw new InvalidArgumentError("must be a non-negative number of milliseconds");
|
|
42
|
+
}
|
|
43
|
+
return parsed;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function singleKey(value: string): string {
|
|
47
|
+
if (Array.from(value).length !== 1) {
|
|
48
|
+
throw new InvalidArgumentError("must be exactly one character");
|
|
49
|
+
}
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Parse watcher flags while passing every token after the command through unchanged. */
|
|
54
|
+
export function parseDevWatchOptions(args: string[]): DevWatchOptions {
|
|
55
|
+
const program = new Command()
|
|
56
|
+
.name(DEV_WATCH_TASK)
|
|
57
|
+
.description("Restart a development command after watched changes settle")
|
|
58
|
+
.exitOverride()
|
|
59
|
+
.configureOutput({
|
|
60
|
+
writeErr: (message) => {
|
|
61
|
+
if (import.meta.main) process.stderr.write(message);
|
|
62
|
+
},
|
|
63
|
+
})
|
|
64
|
+
.enablePositionalOptions()
|
|
65
|
+
.passThroughOptions()
|
|
66
|
+
.addOption(
|
|
67
|
+
new Option("--debounce-ms <milliseconds>", "quiet period before restart")
|
|
68
|
+
.default(DEV_RESTART_DEBOUNCE_MS)
|
|
69
|
+
.argParser(debounceMilliseconds),
|
|
70
|
+
)
|
|
71
|
+
.addOption(
|
|
72
|
+
new Option("--restart-key <key>", "interactive immediate-restart key")
|
|
73
|
+
.default(DEV_RESTART_KEY)
|
|
74
|
+
.argParser(singleKey),
|
|
75
|
+
)
|
|
76
|
+
.addOption(
|
|
77
|
+
new Option(
|
|
78
|
+
"--server-watch-disabled",
|
|
79
|
+
`run the command once without watching (${SERVER_WATCH_DISABLED_ENV})`,
|
|
80
|
+
),
|
|
81
|
+
)
|
|
82
|
+
.argument("<command>")
|
|
83
|
+
.argument("[args...]")
|
|
84
|
+
.parse(["bun", DEV_WATCH_TASK, ...args]);
|
|
85
|
+
const options = program.opts<{
|
|
86
|
+
debounceMs: number;
|
|
87
|
+
restartKey: string;
|
|
88
|
+
serverWatchDisabled?: boolean;
|
|
89
|
+
}>();
|
|
90
|
+
const [command, commandArgs = []] = program.processedArgs as [string, string[]?];
|
|
91
|
+
const serverWatchDisabled =
|
|
92
|
+
options.serverWatchDisabled === true ||
|
|
93
|
+
configUtils.boolean(undefined, SERVER_WATCH_DISABLED_ENV, configUtils.ENV_ONLY) === true;
|
|
94
|
+
if (serverWatchDisabled) process.env[SERVER_WATCH_DISABLED_ENV] = "1";
|
|
95
|
+
return {
|
|
96
|
+
command: [command, ...commandArgs],
|
|
97
|
+
debounceMs: options.debounceMs,
|
|
98
|
+
restartKey: options.restartKey,
|
|
99
|
+
serverWatchDisabled,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function containsPath(parent: string, candidate: string): boolean {
|
|
104
|
+
const rel = relative(parent, candidate);
|
|
105
|
+
return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function workspacePackages(root: string): WorkspacePackage[] {
|
|
109
|
+
return recordedPackages(root)
|
|
110
|
+
.map(({ dir }) => {
|
|
111
|
+
const name = readPackageManifest(dir)?.name;
|
|
112
|
+
return { dir, name: typeof name === "string" ? name : undefined };
|
|
113
|
+
})
|
|
114
|
+
.sort((left, right) => right.dir.length - left.dir.length);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function optionArgument(
|
|
118
|
+
command: readonly string[],
|
|
119
|
+
longName: string,
|
|
120
|
+
shortName?: string,
|
|
121
|
+
): string | undefined {
|
|
122
|
+
for (let index = 0; index < command.length; index++) {
|
|
123
|
+
const token = command[index]!;
|
|
124
|
+
if (token === longName || (shortName !== undefined && token === shortName)) {
|
|
125
|
+
return command[index + 1];
|
|
126
|
+
}
|
|
127
|
+
if (token.startsWith(`${longName}=`)) return token.slice(longName.length + 1);
|
|
128
|
+
if (shortName !== undefined && token.startsWith(`${shortName}=`)) {
|
|
129
|
+
return token.slice(shortName.length + 1);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function commandWorkingDirectory(command: readonly string[], cwd: string): string {
|
|
136
|
+
const configured = optionArgument(command, "--cwd");
|
|
137
|
+
return configured ? resolve(cwd, configured) : cwd;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function commandPackage(
|
|
141
|
+
command: readonly string[],
|
|
142
|
+
cwd: string,
|
|
143
|
+
packages: readonly WorkspacePackage[],
|
|
144
|
+
): WorkspacePackage | undefined {
|
|
145
|
+
const selected = optionArgument(command, "--filter", "-F");
|
|
146
|
+
if (selected) {
|
|
147
|
+
const filtered = packages.find(({ name }) => name === selected);
|
|
148
|
+
if (filtered) return filtered;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const commandCwd = commandWorkingDirectory(command, cwd);
|
|
152
|
+
for (const token of command) {
|
|
153
|
+
if (token.startsWith("-")) continue;
|
|
154
|
+
const candidate = resolve(commandCwd, token);
|
|
155
|
+
const owner = packages.find(({ dir }) => containsPath(dir, candidate));
|
|
156
|
+
if (owner) return owner;
|
|
157
|
+
}
|
|
158
|
+
return packages.find(({ dir }) => containsPath(dir, commandCwd));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Source directories most likely to affect the command, using workspace dependency metadata. */
|
|
162
|
+
export function devWatchDirectories(
|
|
163
|
+
command: readonly string[],
|
|
164
|
+
cwd: string = process.cwd(),
|
|
165
|
+
): string[] {
|
|
166
|
+
const root = resolveRepoRoot(cwd);
|
|
167
|
+
const packages = workspacePackages(root);
|
|
168
|
+
const owner = commandPackage(command, cwd, packages);
|
|
169
|
+
if (!owner) return [commandWorkingDirectory(command, cwd)];
|
|
170
|
+
const dependencies = owner.name ? workspaceDependencyDirectories(owner.name, root) : [];
|
|
171
|
+
return dependencies.length > 0 ? dependencies : [owner.dir];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Compare one raw terminal character with the configured restart key. */
|
|
175
|
+
export function isRestartKey(key: string, restartKey: string): boolean {
|
|
176
|
+
return key === restartKey || key.toLocaleLowerCase() === restartKey.toLocaleLowerCase();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Supervise one command until the user or host stops the task. */
|
|
180
|
+
export async function main(args: string[] = process.argv.slice(2)): Promise<void> {
|
|
181
|
+
let options: DevWatchOptions;
|
|
182
|
+
try {
|
|
183
|
+
options = parseDevWatchOptions(args);
|
|
184
|
+
} catch (err) {
|
|
185
|
+
if (!(err instanceof CommanderError)) throw err;
|
|
186
|
+
if (err.exitCode !== 0) process.exitCode = err.exitCode;
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const cwd = process.cwd();
|
|
190
|
+
if (options.serverWatchDisabled) {
|
|
191
|
+
logger.info(`${SERVER_WATCH_DISABLED_ENV} is set; running without file watching`);
|
|
192
|
+
const [command, ...commandArgs] = options.command;
|
|
193
|
+
const { exitCode } = await exec.spawn(command!, commandArgs, {
|
|
194
|
+
cwd,
|
|
195
|
+
stdin: "ignore",
|
|
196
|
+
stdout: "inherit",
|
|
197
|
+
stderr: "inherit",
|
|
198
|
+
check: false,
|
|
199
|
+
});
|
|
200
|
+
if (exitCode !== 0) process.exitCode = exitCode;
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const root = resolveRepoRoot(cwd);
|
|
205
|
+
const watchDirectories = devWatchDirectories(options.command, cwd);
|
|
206
|
+
const commandLabel = options.command.map((part) => JSON.stringify(part)).join(" ");
|
|
207
|
+
let child: exec.ChildProcessResult | undefined;
|
|
208
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
209
|
+
let restartReason: string | undefined;
|
|
210
|
+
let restarting = false;
|
|
211
|
+
let stopping = false;
|
|
212
|
+
|
|
213
|
+
const watcher = pathWatch.watchFiles(watchDirectories, {
|
|
214
|
+
cwd: root,
|
|
215
|
+
ignoreInitial: true,
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
async function stopChild(): Promise<void> {
|
|
219
|
+
const current = child;
|
|
220
|
+
if (!current) return;
|
|
221
|
+
child = undefined;
|
|
222
|
+
try {
|
|
223
|
+
await exec.kill(current, {
|
|
224
|
+
gracefulTimeoutMs: SHUTDOWN_TIMEOUT_MS,
|
|
225
|
+
forceTimeoutMs: SHUTDOWN_TIMEOUT_MS,
|
|
226
|
+
});
|
|
227
|
+
} catch (err) {
|
|
228
|
+
logger.error("failed to stop command process tree:", err);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function startChild(reason: string): void {
|
|
233
|
+
logger.info(`${reason}; starting ${commandLabel}`);
|
|
234
|
+
const [command, ...commandArgs] = options.command;
|
|
235
|
+
const current = exec.spawn(command!, commandArgs, {
|
|
236
|
+
cwd,
|
|
237
|
+
stdin: "ignore",
|
|
238
|
+
stdout: "inherit",
|
|
239
|
+
stderr: "inherit",
|
|
240
|
+
check: false,
|
|
241
|
+
});
|
|
242
|
+
child = current;
|
|
243
|
+
void current
|
|
244
|
+
.then(({ exitCode }) => {
|
|
245
|
+
if (child !== current || stopping) return;
|
|
246
|
+
child = undefined;
|
|
247
|
+
logger.warn(
|
|
248
|
+
`command exited with code ${exitCode}; press ${JSON.stringify(options.restartKey)} or change a watched file to restart`,
|
|
249
|
+
);
|
|
250
|
+
})
|
|
251
|
+
.catch((err) => {
|
|
252
|
+
if (child !== current || stopping) return;
|
|
253
|
+
child = undefined;
|
|
254
|
+
logger.error("command failed:", err);
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async function drainRestarts(): Promise<void> {
|
|
259
|
+
if (restarting) return;
|
|
260
|
+
restarting = true;
|
|
261
|
+
try {
|
|
262
|
+
while (restartReason !== undefined && !stopping) {
|
|
263
|
+
const reason = restartReason;
|
|
264
|
+
restartReason = undefined;
|
|
265
|
+
await stopChild();
|
|
266
|
+
if (!stopping) startChild(reason);
|
|
267
|
+
}
|
|
268
|
+
} finally {
|
|
269
|
+
restarting = false;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function requestRestart(reason: string): void {
|
|
274
|
+
restartReason = reason;
|
|
275
|
+
void drainRestarts();
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function scheduleRestart(): void {
|
|
279
|
+
clearTimeout(timer);
|
|
280
|
+
timer = setTimeout(
|
|
281
|
+
() => requestRestart(`changes settled for ${options.debounceMs}ms`),
|
|
282
|
+
options.debounceMs,
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
watcher.on("all", scheduleRestart);
|
|
287
|
+
watcher.on("error", (err) => logger.error("watcher error:", err));
|
|
288
|
+
watcher.on("ready", () => {
|
|
289
|
+
const packages = watchDirectories.length;
|
|
290
|
+
logger.info(
|
|
291
|
+
`watching ${packages} workspace ${packages === 1 ? "directory" : "directories"} with ${options.debounceMs}ms restart debounce`,
|
|
292
|
+
);
|
|
293
|
+
logger.info(`press ${JSON.stringify(options.restartKey)} to restart; Ctrl-C to stop`);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
const stdin = process.stdin;
|
|
297
|
+
const wasRaw = stdin.isTTY ? Boolean(stdin.isRaw) : false;
|
|
298
|
+
const onInput = (chunk: Buffer | string): void => {
|
|
299
|
+
for (const key of String(chunk)) {
|
|
300
|
+
if (key === "\u0003") {
|
|
301
|
+
void shutdown();
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
if (isRestartKey(key, options.restartKey)) {
|
|
305
|
+
clearTimeout(timer);
|
|
306
|
+
requestRestart("manual restart requested");
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
if (stdin.isTTY) stdin.setRawMode(true);
|
|
311
|
+
stdin.resume();
|
|
312
|
+
stdin.on("data", onInput);
|
|
313
|
+
|
|
314
|
+
let finish!: () => void;
|
|
315
|
+
const complete = new Promise<void>((resolveComplete) => {
|
|
316
|
+
finish = resolveComplete;
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
async function shutdown(): Promise<void> {
|
|
320
|
+
if (stopping) return;
|
|
321
|
+
stopping = true;
|
|
322
|
+
clearTimeout(timer);
|
|
323
|
+
restartReason = undefined;
|
|
324
|
+
stdin.off("data", onInput);
|
|
325
|
+
if (stdin.isTTY) stdin.setRawMode(wasRaw);
|
|
326
|
+
stdin.pause();
|
|
327
|
+
await Promise.allSettled([watcher.close(), stopChild()]);
|
|
328
|
+
finish();
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"] as const) {
|
|
332
|
+
process.once(signal, () => void shutdown());
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
requestRestart("initial start");
|
|
336
|
+
await complete;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (import.meta.main) await main();
|
package/tasks/release.ts
CHANGED
|
@@ -71,7 +71,9 @@ function pushAnnotatedReleaseTag(options: {
|
|
|
71
71
|
runTaskCommand(root, "git", ["tag", "--annotate", tag, "--message", annotation]);
|
|
72
72
|
const githubAccount = projectUtils.resolveProjectGhAccount(root, { remote });
|
|
73
73
|
if (!githubAccount) {
|
|
74
|
-
logger.warn(
|
|
74
|
+
logger.warn(
|
|
75
|
+
"no authenticated GitHub account can access the release repository; using git push",
|
|
76
|
+
);
|
|
75
77
|
runTaskCommand(root, "git", ["push", remote, `refs/tags/${tag}`]);
|
|
76
78
|
return;
|
|
77
79
|
}
|
|
@@ -228,6 +230,8 @@ export async function runRelease(
|
|
|
228
230
|
readonly validationTasks?: readonly string[];
|
|
229
231
|
/** After tagging, stage and deploy the AppKit demo app. Off by default. */
|
|
230
232
|
readonly demoDeploy?: boolean;
|
|
233
|
+
/** Explicit Databricks CLI profile used by the optional demo deployment. */
|
|
234
|
+
readonly demoProfile?: string;
|
|
231
235
|
/** Write `docs/releases/vX.Y.Z.md` via Genie, with a git-log fallback. On by default. */
|
|
232
236
|
readonly releaseNotes?: boolean;
|
|
233
237
|
/** Additional instructions appended to the standard Genie release-notes prompt. */
|
|
@@ -238,6 +242,11 @@ export async function runRelease(
|
|
|
238
242
|
const { branch, prefix, remote, root } = options;
|
|
239
243
|
const selection = releaseStepSelection(options);
|
|
240
244
|
const install = options.install ?? "auto";
|
|
245
|
+
const demoProfile = options.demoProfile?.trim();
|
|
246
|
+
|
|
247
|
+
if (options.demoDeploy && !demoProfile) {
|
|
248
|
+
throw new Error("--demo-deploy requires --profile <name>");
|
|
249
|
+
}
|
|
241
250
|
|
|
242
251
|
if (install === "always") runTaskCommand(root, "bun", ["install"]);
|
|
243
252
|
if (selection.validation && hasPackageScript(root, "eslint:fix")) {
|
|
@@ -331,8 +340,8 @@ export async function runRelease(
|
|
|
331
340
|
});
|
|
332
341
|
}
|
|
333
342
|
|
|
334
|
-
if (options.demoDeploy) {
|
|
335
|
-
runTaskCommand(root, "bun", ["run", "demo:deploy"]);
|
|
343
|
+
if (options.demoDeploy && demoProfile) {
|
|
344
|
+
runTaskCommand(root, "bun", ["run", "demo:deploy", "--profile", demoProfile]);
|
|
336
345
|
}
|
|
337
346
|
|
|
338
347
|
return tag;
|
|
@@ -379,6 +388,7 @@ export function createReleaseCommand(): Command {
|
|
|
379
388
|
"append custom instructions to the Genie release-notes prompt",
|
|
380
389
|
)
|
|
381
390
|
.option("--demo-deploy", "after tagging, stage and deploy the AppKit demo app (off by default)")
|
|
391
|
+
.option("--profile <name>", "Databricks CLI profile for --demo-deploy")
|
|
382
392
|
.option("--no-local-publish", "skip publishing to configured local registries")
|
|
383
393
|
.option("--local-registry <auto|false|url>", "local npm registry selection", "auto")
|
|
384
394
|
.option("--local-pypi <auto|false|url>", "local devpi registry selection", "auto")
|
|
@@ -397,6 +407,7 @@ export function createReleaseCommand(): Command {
|
|
|
397
407
|
install: ReleaseInstallMode;
|
|
398
408
|
validate: string[];
|
|
399
409
|
demoDeploy?: boolean;
|
|
410
|
+
profile?: string;
|
|
400
411
|
releaseNotes?: boolean;
|
|
401
412
|
releaseNotesInstructions?: string;
|
|
402
413
|
},
|
|
@@ -419,6 +430,7 @@ export function createReleaseCommand(): Command {
|
|
|
419
430
|
validation: options.validation,
|
|
420
431
|
validationTasks: options.validate,
|
|
421
432
|
demoDeploy: options.demoDeploy,
|
|
433
|
+
demoProfile: options.profile,
|
|
422
434
|
releaseNotes: options.releaseNotes,
|
|
423
435
|
releaseNotesInstructions: options.releaseNotesInstructions,
|
|
424
436
|
});
|