@dbx-tools/projen 0.9.151 → 0.9.153
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 +49 -4
- package/index.ts +6 -1
- package/package.json +7 -5
- package/src/appkit-workspace.ts +181 -0
- package/src/bun-app.ts +7 -1
- package/src/dbx-tools-config.ts +1 -0
- package/src/generated.ts +16 -7
- package/src/packages.ts +6 -1
- package/src/pnpm-workspace.ts +4 -0
- package/src/project-js.ts +65 -10
- package/src/project-py.ts +50 -19
- package/src/tsconfig.ts +10 -2
- package/src/workspace-manifest.ts +182 -0
- package/tasks/appkit-deploy.ts +245 -0
- package/tasks/compile-workspace.ts +2 -1
- package/tasks/publish-npm.ts +3 -54
- package/tasks/pypi-trusted-publisher-instructions.ts +54 -0
- package/tasks/python-node-bindings.ts +11 -7
- package/tasks/test-workspace.ts +2 -0
package/README.md
CHANGED
|
@@ -24,7 +24,13 @@ packages. It can also generate Zod schemas from declarations and coordinate
|
|
|
24
24
|
generated-file cleanup and watch tasks.
|
|
25
25
|
|
|
26
26
|
Every `src`-bearing folder under the configured roots becomes a
|
|
27
|
-
`DBXToolsTypeScriptProject`.
|
|
27
|
+
`DBXToolsTypeScriptProject`. `packages/js` is selected automatically when it
|
|
28
|
+
exists; otherwise discovery uses `packages`. Folder path drives package name and
|
|
29
|
+
runtime tags.
|
|
30
|
+
Use `packageDefaults` for native options shared by every discovered package,
|
|
31
|
+
such as `{ publishable: false }`; reserve `resolvePackageOptions` for genuine
|
|
32
|
+
per-package differences. Use `rootTsconfig` only for repository-wide compiler
|
|
33
|
+
differences; root tests, Bun/Node types, and React JSX are already included.
|
|
28
34
|
|
|
29
35
|
Use `DBXToolsTypeScriptProject` itself as the root when one package needs the
|
|
30
36
|
same Bun workspace, repository, task, and release defaults without a separate
|
|
@@ -104,7 +110,11 @@ new projectPy.DBXToolsPythonWorkspace(root, {
|
|
|
104
110
|
```
|
|
105
111
|
|
|
106
112
|
Distribution names and modules come from the parent scope plus each package
|
|
107
|
-
directory.
|
|
113
|
+
directory. Direct children of the Python root that contain `src/` are discovered
|
|
114
|
+
automatically; `packages` only needs entries whose metadata differs from the
|
|
115
|
+
generated defaults. Set `discoverPackages: false` for a fully explicit list.
|
|
116
|
+
The repository comes from the parent project metadata, existing generated root
|
|
117
|
+
manifest, or Git remote.
|
|
108
118
|
`internalDependencies` renders standalone Git `#subdirectory=` requirements
|
|
109
119
|
without repeating repository coordinates. Pass `repository`, `name`, or
|
|
110
120
|
`module` only to override those conventions. `root.vscode` is Projen's existing
|
|
@@ -310,8 +320,43 @@ parameter to `Project`). Drop to `mixin.create(predicate, fn)` +
|
|
|
310
320
|
`project.with(...)` only when you need a predicate the filters cannot express.
|
|
311
321
|
|
|
312
322
|
Built-in tag mixins set runtime defaults for `shared`, `node`, `cli`, `server`,
|
|
313
|
-
and `
|
|
314
|
-
scripts, and generated files on top.
|
|
323
|
+
`ui`, and `app`. Repo-specific mixins layer package-specific dependencies,
|
|
324
|
+
scripts, and generated files on top. `ui` is for reusable React component
|
|
325
|
+
libraries. `app` owns a Bun-built browser application. Terminal
|
|
326
|
+
`packages/**/app` and `packages/**/server` directories select those specific
|
|
327
|
+
roles instead of inheriting a generic `ui` or `node` ancestor.
|
|
328
|
+
|
|
329
|
+
AppKit mode defaults to `auto`: when the workspace has both `app` and `server`
|
|
330
|
+
packages, the engine adds the AppKit, Vite, React plugin, and Tailwind
|
|
331
|
+
dependencies to the package that uses each one. It also disables AppKit's
|
|
332
|
+
package migration, includes the Vite and generated-type inputs in the browser
|
|
333
|
+
tsconfig, generates a read-only monorepo-safe `vite.config.ts`, adds
|
|
334
|
+
`appkit:sync`, and ignores local Databricks state. Put repository-specific Vite
|
|
335
|
+
settings in the optional unmanaged `vite.config.override.ts`; the generated
|
|
336
|
+
config merges it over the engine defaults. Set
|
|
337
|
+
`appkit: false` to disable this ownership. Set `appkit: true` to require AppKit
|
|
338
|
+
and fail synthesis when either package role or its required entry files are
|
|
339
|
+
missing. Keep application-specific UI and server dependencies in the consuming
|
|
340
|
+
`.projenrc.ts`. A workspace with one `server` package also receives standard
|
|
341
|
+
`check`, `dev`, and `start` tasks unless the repository defines them.
|
|
342
|
+
|
|
343
|
+
The generated `deploy` task validates with the native Bun build, then delegates
|
|
344
|
+
to `appkit:deploy` unless the repository already owns `deploy`. The AppKit task
|
|
345
|
+
runs `databricks apps deploy --skip-validation` with all forwarded arguments
|
|
346
|
+
from an isolated source snapshot, so the Databricks CLI never runs npm locally.
|
|
347
|
+
Because the remote builder selects npm when no portable pnpm lockfile is
|
|
348
|
+
uploaded, the snapshot replaces `catalog:` and `workspace:` dependency protocols
|
|
349
|
+
with concrete versions and keeps every manifest stable through upload. The
|
|
350
|
+
generated working tree is never modified. The task passes an explicit profile,
|
|
351
|
+
using the supplied flag or resolving one through `@dbx-tools/auth`, and fails
|
|
352
|
+
before staging when neither profile nor complete environment credentials are
|
|
353
|
+
available. It validates the bundle strictly, removes only stale direct-engine
|
|
354
|
+
state from the temporary snapshot, and defaults to active-run protection while
|
|
355
|
+
skipping duplicate npm validation and tests. If deployment or app startup
|
|
356
|
+
fails, the task prints the current Databricks App logs before returning the
|
|
357
|
+
original failure. A repository-owned deployment flow can run its own Bun checks
|
|
358
|
+
and spawn `appkit:deploy` for the upload step instead of maintaining staging or
|
|
359
|
+
materialization code.
|
|
315
360
|
|
|
316
361
|
Every workspace root exposes a generic development watcher:
|
|
317
362
|
|
package/index.ts
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
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.153";
|
|
7
|
+
export * as appkitWorkspace from "./src/appkit-workspace.ts";
|
|
7
8
|
export * as barrels from "./src/barrels.ts";
|
|
8
9
|
export * as bunApp from "./src/bun-app.ts";
|
|
9
10
|
export * as bunWorkflow from "./src/bun-workflow.ts";
|
|
@@ -36,7 +37,10 @@ export * as tsconfig from "./src/tsconfig.ts";
|
|
|
36
37
|
export * as vscode from "./src/vscode.ts";
|
|
37
38
|
export * as watch from "./src/watch.ts";
|
|
38
39
|
export * as workspaceLock from "./src/workspace-lock.ts";
|
|
40
|
+
export * as workspaceManifest from "./src/workspace-manifest.ts";
|
|
39
41
|
export * as workspaceVersion from "./src/workspace-version.ts";
|
|
42
|
+
export { AppKitWorkspace } from "./src/appkit-workspace.ts";
|
|
43
|
+
export type { AppKitWorkspaceMode, AppKitWorkspaceOptions } from "./src/appkit-workspace.ts";
|
|
40
44
|
export { BUN_DEV_OVERRIDE, BUN_BUILD_OVERRIDE, BUN_APP_OVERRIDES, RootBunfigFile, BunfigFile, BunDevServerFile, BunBuildFile } from "./src/bun-app.ts";
|
|
41
45
|
export { BUN_VERSION, MINIMUM_BUN_VERSION, BUN_VERSION_CHECK_SCRIPT } from "./src/bun-workflow.ts";
|
|
42
46
|
export { DBXToolsConfigDataSchema, DBXToolsConfig } from "./src/dbx-tools-config.ts";
|
|
@@ -73,6 +77,7 @@ export type { SharedCoreUsageKind, SharedCoreExportUsage } from "./src/shared-co
|
|
|
73
77
|
export { AGNOSTIC_COMPILER_OPTIONS, PACKAGE_TAG_MIXINS } from "./src/tags.ts";
|
|
74
78
|
export type { PackageTag } from "./src/tags.ts";
|
|
75
79
|
export { DBXToolsRootTsconfig } from "./src/tsconfig.ts";
|
|
80
|
+
export type { DBXToolsRootTsconfigOptions } from "./src/tsconfig.ts";
|
|
76
81
|
export type { IgnoreGroupOptions, WatchLoopOptions } from "./src/watch.ts";
|
|
77
82
|
export type { WorkspaceMutationLockOptions } from "./src/workspace-lock.ts";
|
|
78
83
|
export { VERSION_FILE, DEFAULT_VERSION } from "./src/workspace-version.ts";
|
package/package.json
CHANGED
|
@@ -30,10 +30,11 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@clack/prompts": "^1.7.0",
|
|
33
|
-
"@dbx-tools/
|
|
34
|
-
"@dbx-tools/
|
|
35
|
-
"@dbx-tools/
|
|
36
|
-
"@dbx-tools/
|
|
33
|
+
"@dbx-tools/auth": "^0.9.153",
|
|
34
|
+
"@dbx-tools/cli-args": "^0.9.153",
|
|
35
|
+
"@dbx-tools/core": "^0.9.153",
|
|
36
|
+
"@dbx-tools/path": "^0.9.153",
|
|
37
|
+
"@dbx-tools/shared-core": "^0.9.153",
|
|
37
38
|
"commander": "^15.0.0",
|
|
38
39
|
"concurrently": "^10.0.3",
|
|
39
40
|
"constructs": "^10.6.0",
|
|
@@ -55,11 +56,12 @@
|
|
|
55
56
|
},
|
|
56
57
|
"main": "index.ts",
|
|
57
58
|
"license": "Apache-2.0",
|
|
58
|
-
"version": "0.9.
|
|
59
|
+
"version": "0.9.153",
|
|
59
60
|
"types": "index.ts",
|
|
60
61
|
"type": "module",
|
|
61
62
|
"exports": {
|
|
62
63
|
".": "./index.ts",
|
|
64
|
+
"./pypi-trusted-publisher-instructions": "./tasks/pypi-trusted-publisher-instructions.ts",
|
|
63
65
|
"./python-release-packaging": "./tasks/publish-python.ts",
|
|
64
66
|
"./release-packaging": "./tasks/publish-npm.ts",
|
|
65
67
|
"./package.json": "./package.json"
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AppKit workspace conventions for paired browser and server packages.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import { existsSync } from "node:fs";
|
|
7
|
+
import { basename, join, relative, sep } from "node:path";
|
|
8
|
+
import { Component, type Task, TextFile, javascript, typescript } from "projen";
|
|
9
|
+
import { headerLines } from "./generated.ts";
|
|
10
|
+
|
|
11
|
+
/** AppKit workspace discovery and validation mode. */
|
|
12
|
+
export type AppKitWorkspaceMode = "auto" | boolean;
|
|
13
|
+
|
|
14
|
+
/** Projects configured by the AppKit workspace owner. */
|
|
15
|
+
export interface AppKitWorkspaceOptions {
|
|
16
|
+
/** Browser application packages carrying the `app` tag. */
|
|
17
|
+
readonly apps: readonly javascript.NodeProject[];
|
|
18
|
+
/** AppKit backend packages carrying the `server` tag. */
|
|
19
|
+
readonly servers: readonly javascript.NodeProject[];
|
|
20
|
+
/** Fail synthesis when an AppKit package or required entry file is missing. */
|
|
21
|
+
readonly validate?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Own the dependencies and generated configuration shared by AppKit workspaces.
|
|
26
|
+
*/
|
|
27
|
+
export class AppKitWorkspace extends Component {
|
|
28
|
+
private readonly deployTask: Task;
|
|
29
|
+
private readonly serverEntrypoint?: string;
|
|
30
|
+
|
|
31
|
+
constructor(project: javascript.NodeProject, options: AppKitWorkspaceOptions) {
|
|
32
|
+
super(project);
|
|
33
|
+
|
|
34
|
+
if (options.validate) validateWorkspace(options);
|
|
35
|
+
project.gitignore.addPatterns(".databricks");
|
|
36
|
+
project.addTask("appkit:sync", {
|
|
37
|
+
description: "Synchronize installed AppKit plugin metadata",
|
|
38
|
+
exec: "appkit plugin sync --write --silent",
|
|
39
|
+
});
|
|
40
|
+
this.deployTask = project.addTask("appkit:deploy", {
|
|
41
|
+
description: "Deploy the AppKit workspace from an npm-compatible source snapshot",
|
|
42
|
+
execArgs: ["bun", "node_modules/@dbx-tools/projen/tasks/appkit-deploy.ts"],
|
|
43
|
+
receiveArgs: true,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
for (const app of options.apps) configureApp(app);
|
|
47
|
+
for (const server of options.servers) configureServer(server);
|
|
48
|
+
if (options.servers.length === 1) {
|
|
49
|
+
const server = options.servers[0]!;
|
|
50
|
+
const entrypoint = serverEntrypoint(server);
|
|
51
|
+
this.serverEntrypoint = relative(project.outdir, join(server.outdir, entrypoint))
|
|
52
|
+
.split(sep)
|
|
53
|
+
.join("/");
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public override preSynthesize(): void {
|
|
58
|
+
let check = this.project.tasks.tryFind("check");
|
|
59
|
+
if (!check) {
|
|
60
|
+
check = this.project.addTask("check", {
|
|
61
|
+
description: "Validate the generated workspace with Bun",
|
|
62
|
+
});
|
|
63
|
+
check.spawn(this.project.buildTask);
|
|
64
|
+
}
|
|
65
|
+
if (!this.project.tasks.tryFind("deploy")) {
|
|
66
|
+
const deploy = this.project.addTask("deploy", {
|
|
67
|
+
description: "Validate with Bun and deploy the AppKit workspace",
|
|
68
|
+
});
|
|
69
|
+
deploy.spawn(check);
|
|
70
|
+
deploy.spawn(this.deployTask);
|
|
71
|
+
}
|
|
72
|
+
if (!this.serverEntrypoint) return;
|
|
73
|
+
if (!this.project.tasks.tryFind("dev")) {
|
|
74
|
+
this.project.addTask("dev", {
|
|
75
|
+
description: "Run the AppKit server in development mode",
|
|
76
|
+
env: { NODE_ENV: "development" },
|
|
77
|
+
exec: `bun --watch ${this.serverEntrypoint}`,
|
|
78
|
+
receiveArgs: true,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
if (!this.project.tasks.tryFind("start")) {
|
|
82
|
+
this.project.addTask("start", {
|
|
83
|
+
description: "Run the AppKit server in production mode",
|
|
84
|
+
env: { NODE_ENV: "production" },
|
|
85
|
+
exec: `bun ${this.serverEntrypoint}`,
|
|
86
|
+
receiveArgs: true,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Validate the explicit AppKit convention before generating dependent config. */
|
|
93
|
+
function validateWorkspace(options: AppKitWorkspaceOptions): void {
|
|
94
|
+
const missing: string[] = [];
|
|
95
|
+
if (options.apps.length === 0) missing.push("an `app` package");
|
|
96
|
+
if (options.servers.length === 0) missing.push("a `server` package");
|
|
97
|
+
for (const app of options.apps) {
|
|
98
|
+
if (!existsSync(join(app.outdir, "index.html"))) missing.push(`${app.name}/index.html`);
|
|
99
|
+
}
|
|
100
|
+
for (const server of options.servers) {
|
|
101
|
+
const entrypoint = serverEntrypoint(server);
|
|
102
|
+
if (!existsSync(join(server.outdir, entrypoint))) {
|
|
103
|
+
missing.push(`${server.name}/${entrypoint}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (missing.length > 0) {
|
|
107
|
+
throw new Error(`appkit: true requires ${missing.join(", ")}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Configure one AppKit browser application without broadening React libraries. */
|
|
112
|
+
function configureApp(project: javascript.NodeProject): void {
|
|
113
|
+
project.package.addField("appkit", { autoMigrate: false });
|
|
114
|
+
project.addDeps(
|
|
115
|
+
"@databricks/appkit-ui@catalog:",
|
|
116
|
+
"@tailwindcss/vite@catalog:",
|
|
117
|
+
"tailwindcss@catalog:",
|
|
118
|
+
);
|
|
119
|
+
project.addDevDeps("@databricks/appkit@catalog:", "vite@catalog:");
|
|
120
|
+
const typesDirectory = `${basename(project.outdir)}/shared/appkit-types`;
|
|
121
|
+
new TextFile(project, "vite.config.ts", {
|
|
122
|
+
marker: true,
|
|
123
|
+
readonly: true,
|
|
124
|
+
lines: [
|
|
125
|
+
...headerLines({ tool: "projen", source: ".projenrc.ts" }),
|
|
126
|
+
'import { existsSync } from "node:fs";',
|
|
127
|
+
'import { appKitServingTypesPlugin, appKitTypesPlugin } from "@databricks/appkit";',
|
|
128
|
+
'import tailwindcss from "@tailwindcss/vite";',
|
|
129
|
+
'import { defineConfig, mergeConfig, type UserConfig } from "vite";',
|
|
130
|
+
"",
|
|
131
|
+
`const appKitTypesDirectory = ${JSON.stringify(typesDirectory)};`,
|
|
132
|
+
'const overrideUrl = new URL("./vite.config.override.ts", import.meta.url);',
|
|
133
|
+
"const override = existsSync(overrideUrl)",
|
|
134
|
+
" ? ((await import(overrideUrl.href)).default as UserConfig)",
|
|
135
|
+
" : {};",
|
|
136
|
+
"",
|
|
137
|
+
"export default defineConfig(",
|
|
138
|
+
" mergeConfig(",
|
|
139
|
+
" {",
|
|
140
|
+
" plugins: [",
|
|
141
|
+
" tailwindcss(),",
|
|
142
|
+
" appKitTypesPlugin({",
|
|
143
|
+
" outFile: `${appKitTypesDirectory}/analytics.d.ts`,",
|
|
144
|
+
" }),",
|
|
145
|
+
" appKitServingTypesPlugin({",
|
|
146
|
+
" outFile: `${appKitTypesDirectory}/serving.d.ts`,",
|
|
147
|
+
" }),",
|
|
148
|
+
" ],",
|
|
149
|
+
" },",
|
|
150
|
+
" override,",
|
|
151
|
+
" ),",
|
|
152
|
+
");",
|
|
153
|
+
],
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
if (!(project instanceof typescript.TypeScriptProject)) return;
|
|
157
|
+
project.tsconfig?.file.addOverride("compilerOptions.rootDir", ".");
|
|
158
|
+
project.tsconfig?.addInclude("vite.config.ts");
|
|
159
|
+
project.tsconfig?.addInclude("vite.config.override.ts");
|
|
160
|
+
project.tsconfig?.addInclude("shared/appkit-types");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Configure one AppKit server with the runtime dependency its Vite loader imports. */
|
|
164
|
+
function configureServer(project: javascript.NodeProject): void {
|
|
165
|
+
const entrypoint = serverEntrypoint(project);
|
|
166
|
+
if (entrypoint !== project.package.manifest.main) {
|
|
167
|
+
project.package.addField("main", entrypoint);
|
|
168
|
+
project.package.addField("types", entrypoint);
|
|
169
|
+
}
|
|
170
|
+
project.addDeps("@databricks/appkit@catalog:", "@vitejs/plugin-react@catalog:");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** Prefer the canonical AppKit server entry when a package still has its default barrel. */
|
|
174
|
+
function serverEntrypoint(project: javascript.NodeProject): string {
|
|
175
|
+
const configured =
|
|
176
|
+
typeof project.package.manifest.main === "string" ? project.package.manifest.main : "index.ts";
|
|
177
|
+
const canonical = "src/server.ts";
|
|
178
|
+
return configured === "index.ts" && existsSync(join(project.outdir, canonical))
|
|
179
|
+
? canonical
|
|
180
|
+
: configured;
|
|
181
|
+
}
|
package/src/bun-app.ts
CHANGED
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
*/
|
|
25
25
|
import { stringUtils } from "@dbx-tools/shared-core";
|
|
26
26
|
import { type Project, TextFile } from "projen";
|
|
27
|
+
import { header, headerLines } from "./generated.ts";
|
|
27
28
|
|
|
28
29
|
/** Unmanaged dev-server override module, merged over the generated options if present. */
|
|
29
30
|
export const BUN_DEV_OVERRIDE = "bun-dev.override.ts";
|
|
@@ -31,6 +32,8 @@ export const BUN_DEV_OVERRIDE = "bun-dev.override.ts";
|
|
|
31
32
|
export const BUN_BUILD_OVERRIDE = "bun-build.override.ts";
|
|
32
33
|
/** Unmanaged generated-script overrides excluded from formatting and linting. */
|
|
33
34
|
export const BUN_APP_OVERRIDES = [BUN_DEV_OVERRIDE, BUN_BUILD_OVERRIDE];
|
|
35
|
+
const PROJEN_HEADER = { tool: "projen", source: ".projenrc.ts" } as const;
|
|
36
|
+
const PROJEN_TS_HEADER = header(PROJEN_HEADER).trimEnd().replaceAll("\n", "\n ");
|
|
34
37
|
|
|
35
38
|
/**
|
|
36
39
|
* Root `bunfig.toml`: pin bun's HOISTED linker workspace-wide.
|
|
@@ -49,7 +52,7 @@ export class RootBunfigFile extends TextFile {
|
|
|
49
52
|
marker: false,
|
|
50
53
|
readonly: true,
|
|
51
54
|
lines: [
|
|
52
|
-
|
|
55
|
+
...headerLines({ ...PROJEN_HEADER, comment: "#" }),
|
|
53
56
|
"# Hoisted (flat) node_modules so a peer dep like @mastra/core resolves to ONE",
|
|
54
57
|
"# copy - the isolated default splits it into peer-hash variants and breaks",
|
|
55
58
|
"# identity-based singletons + cross-package types.",
|
|
@@ -76,6 +79,7 @@ export class BunfigFile extends TextFile {
|
|
|
76
79
|
marker: true,
|
|
77
80
|
readonly: true,
|
|
78
81
|
lines: [
|
|
82
|
+
...headerLines({ ...PROJEN_HEADER, comment: "#" }),
|
|
79
83
|
"# Tailwind v4 for the dev server (Bun.serve reads [serve.static] plugins).",
|
|
80
84
|
"# Production build applies the plugin via Bun.build's API in build.ts - the",
|
|
81
85
|
"# `bun build` CLI does not load bunfig plugins, so there is no [build] here.",
|
|
@@ -94,6 +98,7 @@ export class BunDevServerFile extends TextFile {
|
|
|
94
98
|
const source = (
|
|
95
99
|
// ============================================================================
|
|
96
100
|
/*ts*/`
|
|
101
|
+
${PROJEN_TS_HEADER}
|
|
97
102
|
import { existsSync } from "node:fs";
|
|
98
103
|
import index from "./index.html";
|
|
99
104
|
|
|
@@ -135,6 +140,7 @@ export class BunBuildFile extends TextFile {
|
|
|
135
140
|
String.raw
|
|
136
141
|
// ============================================================================
|
|
137
142
|
/*ts*/`
|
|
143
|
+
${PROJEN_TS_HEADER}
|
|
138
144
|
import { existsSync } from "node:fs";
|
|
139
145
|
import { cp, rm } from "node:fs/promises";
|
|
140
146
|
import { basename } from "node:path";
|
package/src/dbx-tools-config.ts
CHANGED
|
@@ -73,6 +73,7 @@ export class DBXToolsConfig {
|
|
|
73
73
|
this.tags = [...(options.tags ?? [])];
|
|
74
74
|
this.codegenInputs = [...(options.codegenInputs ?? [])];
|
|
75
75
|
this.publishable = options.publishable ?? true;
|
|
76
|
+
project.package.addField("private", () => (this.publishable ? undefined : true));
|
|
76
77
|
project.package.addField(CONFIG_KEY, () => {
|
|
77
78
|
const data = this.data();
|
|
78
79
|
return Object.keys(data).length === 0 ? undefined : data;
|
package/src/generated.ts
CHANGED
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { chmodSync, existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
12
12
|
|
|
13
|
-
/**
|
|
14
|
-
const MARKER = "
|
|
13
|
+
/** Tool-owned marker text shared by every supported comment style. */
|
|
14
|
+
const MARKER = "GENERATED by";
|
|
15
15
|
|
|
16
16
|
/** Python subpackage, under a package's module directory, that holds all toolchain output. */
|
|
17
17
|
export const PYTHON_GENERATED_PACKAGE = "_generated";
|
|
@@ -34,6 +34,8 @@ export function makeReadonly(file: string): void {
|
|
|
34
34
|
|
|
35
35
|
/** Inputs for the durable generated-file banner. */
|
|
36
36
|
export interface HeaderOpts {
|
|
37
|
+
/** Line-comment prefix. Defaults to `//`. */
|
|
38
|
+
readonly comment?: "//" | "#";
|
|
37
39
|
/** What produced the file, e.g. `"projen watch"`. */
|
|
38
40
|
readonly tool: string;
|
|
39
41
|
/** What the file is derived from, e.g. `"the exporting modules beside it"`. */
|
|
@@ -42,12 +44,18 @@ export interface HeaderOpts {
|
|
|
42
44
|
|
|
43
45
|
/** Build the do-not-edit banner (line comments, for TS/JS). */
|
|
44
46
|
export function header(opts: HeaderOpts): string {
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
lines.push(
|
|
47
|
+
const comment = opts.comment ?? "//";
|
|
48
|
+
const lines = [`${comment} ${MARKER} ${opts.tool} - DO NOT EDIT.`];
|
|
49
|
+
if (opts.source) lines.push(`${comment} Regenerated from ${opts.source}.`);
|
|
50
|
+
lines.push(`${comment} Hand edits are overwritten on the next watch; this file is read-only.`);
|
|
48
51
|
return `${lines.join("\n")}\n`;
|
|
49
52
|
}
|
|
50
53
|
|
|
54
|
+
/** Build a generated banner as lines for Projen file components. */
|
|
55
|
+
export function headerLines(opts: HeaderOpts): string[] {
|
|
56
|
+
return header(opts).trimEnd().split("\n");
|
|
57
|
+
}
|
|
58
|
+
|
|
51
59
|
/**
|
|
52
60
|
* True if the file exists and carries the do-not-edit banner {@link header}
|
|
53
61
|
* writes - i.e. a toolchain wrote it and a hand edit would be overwritten.
|
|
@@ -61,7 +69,8 @@ export function isGenerated(file: string): boolean {
|
|
|
61
69
|
try {
|
|
62
70
|
const source = readFileSync(file, "utf8");
|
|
63
71
|
return (
|
|
64
|
-
source.startsWith(MARKER) ||
|
|
72
|
+
source.startsWith(`// ${MARKER}`) ||
|
|
73
|
+
source.startsWith(`# ${MARKER}`) ||
|
|
65
74
|
source.startsWith("// ~~ Generated by projen") ||
|
|
66
75
|
source.startsWith("# ~~ Generated by projen") ||
|
|
67
76
|
/"\/\/"\s*:\s*"~~ Generated by projen/.test(source)
|
|
@@ -78,7 +87,7 @@ export function isGenerated(file: string): boolean {
|
|
|
78
87
|
export function stampGenerated(file: string, opts: HeaderOpts): void {
|
|
79
88
|
makeWritable(file);
|
|
80
89
|
const body = readFileSync(file, "utf8");
|
|
81
|
-
const next =
|
|
90
|
+
const next = isGenerated(file) ? body : `${header(opts)}\n${body}`;
|
|
82
91
|
writeFileSync(file, next);
|
|
83
92
|
makeReadonly(file);
|
|
84
93
|
}
|
package/src/packages.ts
CHANGED
|
@@ -146,11 +146,16 @@ export class DiscoveredPackage {
|
|
|
146
146
|
|
|
147
147
|
/**
|
|
148
148
|
* Tag candidates from nesting under the discovery root: cumulative kebab-case join
|
|
149
|
-
* of every ancestor folder, excluding the leaf package folder when depth >= 2
|
|
149
|
+
* of every ancestor folder, excluding the leaf package folder when depth >= 2.
|
|
150
|
+
* Terminal `app` and `server` directories are runtime roles and replace their
|
|
151
|
+
* generic ancestor (`ui/app` -> `app`, `node/server` -> `server`).
|
|
152
|
+
* Other paths keep cumulative tags
|
|
150
153
|
* (`shared/path/coolDude/another` -> `[shared, shared-path, shared-path-cool-dude]`).
|
|
151
154
|
* Matched against `packageTagPaths` to resolve applied mixin tags.
|
|
152
155
|
*/
|
|
153
156
|
get tagCandidates(): string[] {
|
|
157
|
+
const leaf = stringUtils.toSlug(this.name);
|
|
158
|
+
if (leaf === "app" || leaf === "server") return [leaf];
|
|
154
159
|
return nestingTagsFromSegments(this.relSegments);
|
|
155
160
|
}
|
|
156
161
|
}
|
package/src/pnpm-workspace.ts
CHANGED
|
@@ -62,6 +62,10 @@ const DEFAULT_CATALOG: Catalog = {
|
|
|
62
62
|
"react-dom": "~19.2.4",
|
|
63
63
|
"@types/react": "^19.2.2",
|
|
64
64
|
"@types/react-dom": "^19.2.2",
|
|
65
|
+
tailwindcss: "^4.3.2",
|
|
66
|
+
"@tailwindcss/vite": "^4.3.2",
|
|
67
|
+
vite: "npm:rolldown-vite@7.1.14",
|
|
68
|
+
"@vitejs/plugin-react": "^5.1.1",
|
|
65
69
|
// Tailwind v4 compiler for bun's dev server + `Bun.build` (the `app` tag).
|
|
66
70
|
"bun-plugin-tailwind": "^0.1.2",
|
|
67
71
|
"@types/node": "^24.6.0",
|
package/src/project-js.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { type IConstruct } from "constructs";
|
|
|
15
15
|
import { Component, LogLevel, Project, type Task, TextFile, javascript, typescript } from "projen";
|
|
16
16
|
import { JobPermission, type JobStep } from "projen/lib/github/workflows-model";
|
|
17
17
|
import type { ReleaseProjectOptions } from "projen/lib/release";
|
|
18
|
+
import { AppKitWorkspace, type AppKitWorkspaceMode } from "./appkit-workspace.ts";
|
|
18
19
|
import { generateBarrels, isGeneratedBarrel } from "./barrels.ts";
|
|
19
20
|
|
|
20
21
|
import { BUN_APP_OVERRIDES, RootBunfigFile } from "./bun-app.ts";
|
|
@@ -39,7 +40,7 @@ import { applyCompiledPublish } from "./publish.ts";
|
|
|
39
40
|
import { generatePythonNodeBindings } from "./python-node-bindings.ts";
|
|
40
41
|
import { DBXToolsRelease, type ReleaseDocsOptions } from "./release.ts";
|
|
41
42
|
import { AGNOSTIC_COMPILER_OPTIONS, PACKAGE_TAG_MIXINS, type PackageTag } from "./tags.ts";
|
|
42
|
-
import { DBXToolsRootTsconfig } from "./tsconfig.ts";
|
|
43
|
+
import { DBXToolsRootTsconfig, type DBXToolsRootTsconfigOptions } from "./tsconfig.ts";
|
|
43
44
|
import { configureVsCode } from "./vscode.ts";
|
|
44
45
|
import { readWorkspaceVersion, syncWorkspaceManifestVersion } from "./workspace-version.ts";
|
|
45
46
|
|
|
@@ -139,7 +140,15 @@ function repositoryOptions(
|
|
|
139
140
|
options: DBXToolsJavaScriptProjectOptions,
|
|
140
141
|
): Pick<javascript.NodeProjectOptions, "repository" | "repositoryDirectory"> {
|
|
141
142
|
const workspaceRoot = resolve(options.parent?.root.outdir ?? options.outdir ?? process.cwd());
|
|
142
|
-
const
|
|
143
|
+
const existingRepository = readPackageManifest(workspaceRoot)?.repository;
|
|
144
|
+
const existingRepositoryUrl =
|
|
145
|
+
typeof existingRepository === "string"
|
|
146
|
+
? existingRepository
|
|
147
|
+
: object.isRecord(existingRepository)
|
|
148
|
+
? stringUtils.trimToNull(existingRepository.url)
|
|
149
|
+
: undefined;
|
|
150
|
+
const repository =
|
|
151
|
+
options.repository ?? projectUtils.repositoryUrl(workspaceRoot, "npm") ?? existingRepositoryUrl;
|
|
143
152
|
const projectRoot = options.parent
|
|
144
153
|
? resolve(options.parent.outdir, options.outdir ?? ".")
|
|
145
154
|
: resolve(options.outdir ?? process.cwd());
|
|
@@ -603,6 +612,8 @@ export type DBXToolsJavaScriptProjectOptions = CommonProjectOptions &
|
|
|
603
612
|
* disable. Defaults to `"node"`.
|
|
604
613
|
*/
|
|
605
614
|
readonly omitRelativePrefix?: OneOrMany<string>;
|
|
615
|
+
/** Native defaults merged into every filesystem-discovered package. */
|
|
616
|
+
readonly packageDefaults?: Omit<DiscoveredTypeScriptPackageOptions, "name">;
|
|
606
617
|
/** Resolve native constructor fields before a discovered package is constructed. */
|
|
607
618
|
readonly resolvePackageOptions?: (
|
|
608
619
|
pkg: DiscoveredPackage,
|
|
@@ -620,6 +631,13 @@ export type DBXToolsJavaScriptProjectOptions = CommonProjectOptions &
|
|
|
620
631
|
* a list = only those tags.
|
|
621
632
|
*/
|
|
622
633
|
readonly defaultTagMixins?: false | PackageTag[];
|
|
634
|
+
/** Overrides for the generated repository-root TypeScript program. */
|
|
635
|
+
readonly rootTsconfig?: DBXToolsRootTsconfigOptions;
|
|
636
|
+
/**
|
|
637
|
+
* Apply AppKit conventions to `app` and `server` packages. Defaults to
|
|
638
|
+
* `auto`; `true` also validates the required package roles and entry files.
|
|
639
|
+
*/
|
|
640
|
+
readonly appkit?: AppKitWorkspaceMode;
|
|
623
641
|
/**
|
|
624
642
|
* Extra repo-root paths that trigger a full re-synth during `sync --watch`
|
|
625
643
|
* (alongside `.projenrc.ts`). Repo-relative, e.g. `".example.projenrc.ts"`.
|
|
@@ -679,7 +697,7 @@ export class DBXToolsNodeProject
|
|
|
679
697
|
readonly language = "javascript" as const;
|
|
680
698
|
readonly scope: string;
|
|
681
699
|
readonly dbxToolsConfig: DBXToolsConfig;
|
|
682
|
-
pnpmWorkspace
|
|
700
|
+
pnpmWorkspace: PnpmWorkspaceState;
|
|
683
701
|
rootTsconfig?: DBXToolsRootTsconfig;
|
|
684
702
|
readonly extraWorkspaceMembers: readonly string[];
|
|
685
703
|
readonly releaseBranch: string;
|
|
@@ -1007,7 +1025,10 @@ class WorkspaceValidationTasks extends Component {
|
|
|
1007
1025
|
return;
|
|
1008
1026
|
}
|
|
1009
1027
|
project.compileTask.execArgs(taskCommand("compile-workspace.ts"));
|
|
1010
|
-
project.
|
|
1028
|
+
const testAll = project.tasks.tryFind("test:all")!;
|
|
1029
|
+
const pythonTests = project.tasks.tryFind("py:test");
|
|
1030
|
+
if (pythonTests) testAll.spawn(pythonTests);
|
|
1031
|
+
project.testTask.spawn(testAll);
|
|
1011
1032
|
}
|
|
1012
1033
|
}
|
|
1013
1034
|
|
|
@@ -1054,6 +1075,7 @@ class PrettierIgnoreGenerated extends Component {
|
|
|
1054
1075
|
if (!(sub instanceof DBXToolsTypeScriptProject)) continue;
|
|
1055
1076
|
const rel = toPosix(relative(rootAbs, sub.outdir));
|
|
1056
1077
|
prettier.addIgnorePattern(`${rel}/index.ts`);
|
|
1078
|
+
prettier.addIgnorePattern(`${rel}/src/**/index.ts`);
|
|
1057
1079
|
for (const module of codegenModulePaths(sub.dbxToolsConfig.codegenInputs)) {
|
|
1058
1080
|
prettier.addIgnorePattern(`${rel}/${module}`);
|
|
1059
1081
|
}
|
|
@@ -1225,7 +1247,7 @@ function initProject(
|
|
|
1225
1247
|
}
|
|
1226
1248
|
|
|
1227
1249
|
if (project instanceof DBXToolsNodeProject) {
|
|
1228
|
-
project.rootTsconfig = new DBXToolsRootTsconfig(project);
|
|
1250
|
+
project.rootTsconfig = new DBXToolsRootTsconfig(project, options.rootTsconfig);
|
|
1229
1251
|
}
|
|
1230
1252
|
const projenrc = new typescript.ProjenrcTs(project, {
|
|
1231
1253
|
runner: typescript.TypeScriptRunner.nodejs(),
|
|
@@ -1253,7 +1275,9 @@ function initProject(
|
|
|
1253
1275
|
// lockfiles, so the CI-named install task must resolve from manifests as well.
|
|
1254
1276
|
project.package.installCiTask.reset("bun install");
|
|
1255
1277
|
configureRootBunRuntime(project);
|
|
1256
|
-
const roots =
|
|
1278
|
+
const roots =
|
|
1279
|
+
options.packageRoots ??
|
|
1280
|
+
(existsSync(resolve(project.outdir, "packages/js")) ? ["packages/js"] : DEFAULT_PACKAGE_ROOTS);
|
|
1257
1281
|
project.dbxToolsConfig.packageRoots = [...roots];
|
|
1258
1282
|
if (options.syncResynthPaths?.length) {
|
|
1259
1283
|
project.dbxToolsConfig.syncResynthPaths = [...options.syncResynthPaths];
|
|
@@ -1278,10 +1302,20 @@ function initProject(
|
|
|
1278
1302
|
project.gitignore.addPatterns(
|
|
1279
1303
|
...[...ignore.ignorePatterns({ test: false, dot: false, lock: true })],
|
|
1280
1304
|
);
|
|
1305
|
+
// The generic compiled-output pattern `**/lib/**` must not hide authored
|
|
1306
|
+
// modules beneath a package's `src/lib/`.
|
|
1307
|
+
project.gitignore.include("**/src/lib/**");
|
|
1281
1308
|
// What the dot group was actually earning here, named explicitly: secrets and
|
|
1282
1309
|
// local editor state. Both ignore CONTENTS (`.idea/*`) rather than the
|
|
1283
1310
|
// directory, so a later `!` negation can still reach a file inside.
|
|
1284
|
-
project.gitignore.addPatterns(
|
|
1311
|
+
project.gitignore.addPatterns(
|
|
1312
|
+
".env",
|
|
1313
|
+
".env.*",
|
|
1314
|
+
"!.env.example",
|
|
1315
|
+
"!.env.sample",
|
|
1316
|
+
".idea/*",
|
|
1317
|
+
".npmrc",
|
|
1318
|
+
);
|
|
1285
1319
|
for (const root of roots) {
|
|
1286
1320
|
project.annotateGenerated(`/${root}/**/index.ts`);
|
|
1287
1321
|
project.annotateGenerated(`/${root}/**/src/generated/**`);
|
|
@@ -1396,9 +1430,11 @@ function initProject(
|
|
|
1396
1430
|
continue;
|
|
1397
1431
|
}
|
|
1398
1432
|
const defaultPackageName = packageNameFor(project.scope, p.relPath, omitPrefixes);
|
|
1399
|
-
const
|
|
1400
|
-
|
|
1401
|
-
|
|
1433
|
+
const defaults = {
|
|
1434
|
+
name: defaultPackageName,
|
|
1435
|
+
...options.packageDefaults,
|
|
1436
|
+
} satisfies DiscoveredTypeScriptPackageOptions;
|
|
1437
|
+
const packageOptions = options.resolvePackageOptions?.(p, defaults) ?? defaults;
|
|
1402
1438
|
new DBXToolsTypeScriptProject({
|
|
1403
1439
|
parent: project,
|
|
1404
1440
|
outdir: p.memberPath,
|
|
@@ -1417,6 +1453,25 @@ function initProject(
|
|
|
1417
1453
|
if (enabledTagMixins.length) {
|
|
1418
1454
|
project.with(...enabledTagMixins.map((t) => PACKAGE_TAG_MIXINS[t]));
|
|
1419
1455
|
}
|
|
1456
|
+
const appkitMode = options.appkit ?? "auto";
|
|
1457
|
+
if (appkitMode !== false) {
|
|
1458
|
+
const projects = [project, ...project.subprojects].filter(
|
|
1459
|
+
(candidate): candidate is DBXToolsNodeProject | DBXToolsTypeScriptProject =>
|
|
1460
|
+
candidate instanceof DBXToolsNodeProject || candidate instanceof DBXToolsTypeScriptProject,
|
|
1461
|
+
);
|
|
1462
|
+
const apps = projects.filter((candidate) => candidate.dbxToolsConfig.tags.includes("app"));
|
|
1463
|
+
const servers = projects.filter((candidate) =>
|
|
1464
|
+
candidate.dbxToolsConfig.tags.includes("server"),
|
|
1465
|
+
);
|
|
1466
|
+
const enabled = appkitMode === true || (apps.length > 0 && servers.length > 0);
|
|
1467
|
+
if (enabled) {
|
|
1468
|
+
new AppKitWorkspace(project, {
|
|
1469
|
+
apps,
|
|
1470
|
+
servers,
|
|
1471
|
+
validate: appkitMode === true,
|
|
1472
|
+
});
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1420
1475
|
|
|
1421
1476
|
new WorkspaceValidationTasks(project);
|
|
1422
1477
|
new PrettierIgnoreGenerated(project);
|