@dbx-tools/projen 0.9.154 → 0.9.158

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.
Files changed (49) hide show
  1. package/README.md +36 -47
  2. package/index.ts +42 -20
  3. package/package.json +8 -8
  4. package/src/_task-command.ts +7 -7
  5. package/src/appkit-workspace.ts +278 -104
  6. package/src/barrels.ts +147 -259
  7. package/src/bun-app.ts +54 -10
  8. package/src/clean.ts +6 -6
  9. package/src/engine-root.ts +4 -4
  10. package/src/mixin.ts +0 -2
  11. package/src/module-exports.ts +4 -5
  12. package/src/npm-packaging.ts +128 -0
  13. package/src/npm-release.ts +200 -0
  14. package/src/packages.ts +11 -11
  15. package/src/project-js.ts +209 -103
  16. package/src/project-predicate.ts +5 -12
  17. package/src/project-py.ts +6 -6
  18. package/src/project.ts +7 -14
  19. package/src/publish.ts +3 -3
  20. package/src/python-node-bindings.ts +16 -12
  21. package/src/python-packaging.ts +177 -0
  22. package/src/release-options.ts +2 -2
  23. package/src/release.ts +3 -3
  24. package/src/shared-core-usage.ts +17 -4
  25. package/src/tags.ts +10 -9
  26. package/src/watch.ts +7 -9
  27. package/src/workspace-lock.ts +2 -2
  28. package/src/workspace-manifest.ts +2 -2
  29. package/tasks/appkit-deploy.ts +35 -15
  30. package/tasks/barrels.ts +6 -9
  31. package/tasks/bump.ts +1 -1
  32. package/tasks/clean.ts +3 -3
  33. package/tasks/cli.ts +3 -3
  34. package/tasks/compile-workspace.ts +8 -8
  35. package/tasks/dev-watch.ts +11 -11
  36. package/tasks/local-publish.ts +6 -6
  37. package/tasks/publish-npm.ts +11 -319
  38. package/tasks/publish-python.ts +2 -180
  39. package/tasks/publish.ts +6 -11
  40. package/tasks/python-node-bindings.ts +2 -2
  41. package/tasks/python-registry.ts +6 -6
  42. package/tasks/python-sync.ts +2 -3
  43. package/tasks/release-version.ts +1 -1
  44. package/tasks/release.ts +11 -11
  45. package/tasks/sync.ts +2 -2
  46. package/tasks/test-workspace.ts +9 -9
  47. package/tasks/verify-publications.ts +9 -5
  48. package/tasks/version-check.ts +5 -5
  49. /package/{tasks → src}/python-release.ts +0 -0
@@ -1,57 +1,73 @@
1
1
  /**
2
- * AppKit workspace conventions for paired browser and server packages.
2
+ * One Bun-first AppKit application rooted at a conventional workspace folder.
3
3
  *
4
4
  * @module
5
5
  */
6
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";
7
+ import { join, relative, sep } from "node:path";
8
+ import { Component, JsonFile, type Task, TextFile, javascript, typescript } from "projen";
9
+ import { BunBuildFile, BunfigFile } from "./bun-app.ts";
10
+ import { BUN_VERSION } from "./bun-workflow.ts";
9
11
  import { headerLines } from "./generated.ts";
10
12
 
11
13
  /** AppKit workspace discovery and validation mode. */
12
14
  export type AppKitWorkspaceMode = "auto" | boolean;
13
15
 
14
- /** Projects configured by the AppKit workspace owner. */
16
+ /** Minimal generated Databricks bundle settings. */
17
+ export interface AppKitBundleOptions {
18
+ readonly description?: string;
19
+ readonly name?: string;
20
+ }
21
+
22
+ /** Application project and generated bundle behavior. */
15
23
  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. */
24
+ readonly application: javascript.NodeProject;
25
+ readonly bundle?: false | AppKitBundleOptions;
21
26
  readonly validate?: boolean;
22
27
  }
23
28
 
24
- /**
25
- * Own the dependencies and generated configuration shared by AppKit workspaces.
26
- */
29
+ const REQUIRED_APPLICATION_FILES = ["client/index.html", "server/server.ts"] as const;
30
+
31
+ /** Whether the application root contains the entries required by AppKit. */
32
+ export function isAppKitApplication(project: javascript.NodeProject): boolean {
33
+ return REQUIRED_APPLICATION_FILES.every((path) => existsSync(join(project.outdir, path)));
34
+ }
35
+
36
+ /** Own the generated configuration and root tasks for one AppKit application. */
27
37
  export class AppKitWorkspace extends Component {
38
+ private readonly applicationDirectory: string;
28
39
  private readonly deployTask: Task;
29
- private readonly serverEntrypoint?: string;
30
40
 
31
41
  constructor(project: javascript.NodeProject, options: AppKitWorkspaceOptions) {
32
42
  super(project);
43
+ this.applicationDirectory = relative(project.outdir, options.application.outdir)
44
+ .split(sep)
45
+ .join("/");
46
+
47
+ if (options.validate) validateApplication(options.application);
48
+ configureApplication(options.application, options.bundle);
33
49
 
34
- if (options.validate) validateWorkspace(options);
35
- project.gitignore.addPatterns(".databricks");
36
- project.addTask("appkit:sync", {
50
+ project.gitignore.addPatterns(`${this.applicationDirectory}/.databricks`);
51
+ const sync = project.addTask("appkit:sync", {
37
52
  description: "Synchronize installed AppKit plugin metadata",
38
53
  exec: "appkit plugin sync --write --silent",
39
54
  });
55
+ sync.cwd = this.applicationDirectory;
56
+ const typegen = project.addTask("appkit:typegen", {
57
+ description: "Generate AppKit client declarations",
58
+ exec: "appkit generate-types",
59
+ });
60
+ typegen.cwd = this.applicationDirectory;
40
61
  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"],
62
+ description: "Deploy the AppKit application from an npm-compatible source snapshot",
63
+ execArgs: [
64
+ "bun",
65
+ "node_modules/@dbx-tools/projen/tasks/appkit-deploy.ts",
66
+ "--directory",
67
+ this.applicationDirectory,
68
+ ],
43
69
  receiveArgs: true,
44
70
  });
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
71
  }
56
72
 
57
73
  public override preSynthesize(): void {
@@ -64,118 +80,276 @@ export class AppKitWorkspace extends Component {
64
80
  }
65
81
  if (!this.project.tasks.tryFind("deploy")) {
66
82
  const deploy = this.project.addTask("deploy", {
67
- description: "Validate with Bun and deploy the AppKit workspace",
83
+ description: "Validate with Bun and deploy the AppKit application",
68
84
  });
69
85
  deploy.spawn(check);
70
86
  deploy.spawn(this.deployTask);
71
87
  }
72
- if (!this.serverEntrypoint) return;
73
88
  if (!this.project.tasks.tryFind("dev")) {
74
- this.project.addTask("dev", {
89
+ const dev = this.project.addTask("dev", {
75
90
  description: "Run the AppKit server in development mode",
76
91
  env: { NODE_ENV: "development" },
77
- exec: `bun --watch ${this.serverEntrypoint}`,
92
+ exec: "bun --watch server/server.ts",
78
93
  receiveArgs: true,
79
94
  });
95
+ dev.cwd = this.applicationDirectory;
80
96
  }
81
97
  if (!this.project.tasks.tryFind("start")) {
82
- this.project.addTask("start", {
98
+ const start = this.project.addTask("start", {
83
99
  description: "Run the AppKit server in production mode",
84
100
  env: { NODE_ENV: "production" },
85
- exec: `bun ${this.serverEntrypoint}`,
101
+ exec: "bun server/server.ts",
86
102
  receiveArgs: true,
87
103
  });
104
+ start.cwd = this.applicationDirectory;
88
105
  }
89
106
  }
90
107
  }
91
108
 
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
- }
109
+ function validateApplication(project: javascript.NodeProject): void {
110
+ const missing = REQUIRED_APPLICATION_FILES.filter(
111
+ (path) => !existsSync(join(project.outdir, path)),
112
+ );
106
113
  if (missing.length > 0) {
107
- throw new Error(`appkit: true requires ${missing.join(", ")}`);
114
+ throw new Error(
115
+ `appkit: true requires ${missing.map((path) => `${project.name}/${path}`).join(", ")}`,
116
+ );
108
117
  }
109
118
  }
110
119
 
111
- /** Configure one AppKit browser application without broadening React libraries. */
112
- function configureApp(project: javascript.NodeProject): void {
120
+ function configureApplication(
121
+ project: javascript.NodeProject,
122
+ bundle: false | AppKitBundleOptions | undefined,
123
+ ): void {
124
+ project.package.addField("private", true);
113
125
  project.package.addField("appkit", { autoMigrate: false });
126
+ project.package.addField("main", "server/server.ts");
127
+ project.package.addField("types", "server/server.ts");
128
+ project.package.addField("exports", { "./package.json": "./package.json" });
129
+ project.setScript("build", "bun build.ts");
114
130
  project.addDeps(
131
+ `bun@${BUN_VERSION}`,
132
+ "@databricks/appkit@catalog:",
115
133
  "@databricks/appkit-ui@catalog:",
134
+ "@databricks/sdk-experimental@catalog:",
116
135
  "@tailwindcss/vite@catalog:",
136
+ "@vitejs/plugin-react@catalog:",
137
+ "react@catalog:",
138
+ "react-dom@catalog:",
117
139
  "tailwindcss@catalog:",
140
+ "tw-animate-css@catalog:",
118
141
  );
119
- project.addDevDeps("@databricks/appkit@catalog:", "vite@catalog:");
120
- const typesDirectory = `${basename(project.outdir)}/shared/appkit-types`;
121
- new TextFile(project, "vite.config.ts", {
142
+ project.addDevDeps(
143
+ `@types/bun@${BUN_VERSION}`,
144
+ "@types/node@catalog:",
145
+ "@types/react@catalog:",
146
+ "@types/react-dom@catalog:",
147
+ "bun-plugin-tailwind@catalog:",
148
+ "vite@catalog:",
149
+ );
150
+ project.gitignore.addPatterns("/client/dist", "/server/lib");
151
+ project.tryRemoveFile("test/tsconfig.json");
152
+ project.testTask.reset("bun test server/test --pass-with-no-tests");
153
+ project.addTask("dev", {
154
+ description: "Run the AppKit server in development mode",
155
+ env: { NODE_ENV: "development" },
156
+ exec: "bun --watch server/server.ts",
157
+ receiveArgs: true,
158
+ });
159
+ project.addTask("start", {
160
+ description: "Run the AppKit server in production mode",
161
+ env: { NODE_ENV: "production" },
162
+ exec: "bun server/server.ts",
163
+ receiveArgs: true,
164
+ });
165
+ project.addTask("appkit:sync", {
166
+ description: "Synchronize installed AppKit plugin metadata",
167
+ exec: "appkit plugin sync --write --silent",
168
+ });
169
+ project.addTask("appkit:typegen", {
170
+ description: "Generate AppKit client declarations",
171
+ exec: "appkit generate-types",
172
+ });
173
+
174
+ configureTypeScript(project);
175
+ new BunfigFile(project);
176
+ new BunBuildFile(project, {
177
+ entrypoint: "client/index.html",
178
+ outdir: "client/dist",
179
+ publicDir: "client/public",
180
+ sourceRoot: "client/src",
181
+ tsconfig: "tsconfig.client.json",
182
+ });
183
+ new AppKitViteConfig(project);
184
+ new AppYaml(project);
185
+ if (bundle !== false) new DatabricksBundleFile(project, bundle);
186
+
187
+ project.compileTask.reset("tsc -p tsconfig.server.json --noEmit");
188
+ project.compileTask.exec("tsc -p tsconfig.client.json --noEmit");
189
+ project.compileTask.exec("bun build.ts");
190
+ }
191
+
192
+ function configureTypeScript(project: javascript.NodeProject): void {
193
+ new JsonFile(project, "tsconfig.shared.json", {
122
194
  marker: true,
123
195
  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
- ],
196
+ obj: {
197
+ compilerOptions: {
198
+ strict: true,
199
+ noUnusedLocals: true,
200
+ noUnusedParameters: true,
201
+ noFallthroughCasesInSwitch: true,
202
+ noImplicitReturns: true,
203
+ module: "ESNext",
204
+ moduleResolution: "bundler",
205
+ resolveJsonModule: true,
206
+ esModuleInterop: true,
207
+ jsx: "react-jsx",
208
+ skipLibCheck: true,
209
+ forceConsistentCasingInFileNames: true,
210
+ },
211
+ },
212
+ });
213
+ new JsonFile(project, "tsconfig.client.json", {
214
+ marker: true,
215
+ readonly: true,
216
+ obj: {
217
+ extends: "./tsconfig.shared.json",
218
+ compilerOptions: {
219
+ target: "ES2022",
220
+ useDefineForClassFields: true,
221
+ lib: ["ES2022", "DOM", "DOM.Iterable"],
222
+ types: ["vite/client", "node"],
223
+ allowImportingTsExtensions: true,
224
+ verbatimModuleSyntax: true,
225
+ moduleDetection: "force",
226
+ noEmit: true,
227
+ jsx: "react-jsx",
228
+ noUncheckedSideEffectImports: true,
229
+ baseUrl: "./client",
230
+ paths: { "@/*": ["./src/*"] },
231
+ },
232
+ include: ["client/src", "client/vite.config.override.ts", "shared/appkit-types"],
233
+ },
234
+ });
235
+ new JsonFile(project, "tsconfig.server.json", {
236
+ marker: true,
237
+ readonly: true,
238
+ obj: {
239
+ extends: "./tsconfig.shared.json",
240
+ compilerOptions: {
241
+ target: "ES2022",
242
+ lib: ["ES2022"],
243
+ types: ["node"],
244
+ allowImportingTsExtensions: true,
245
+ noEmit: true,
246
+ rootDir: ".",
247
+ },
248
+ include: ["server/**/*.ts", "shared/**/*.ts"],
249
+ exclude: ["node_modules", "client"],
250
+ },
154
251
  });
155
252
 
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");
253
+ if (!(project instanceof typescript.TypeScriptProject) || !project.tsconfig) return;
254
+ project.tsconfig.file.addDeletionOverride("compilerOptions");
255
+ project.tsconfig.file.addDeletionOverride("include");
256
+ project.tsconfig.file.addOverride("files", []);
257
+ project.tsconfig.file.addOverride("references", [
258
+ { path: "./tsconfig.client.json" },
259
+ { path: "./tsconfig.server.json" },
260
+ ]);
161
261
  }
162
262
 
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);
263
+ class AppKitViteConfig extends TextFile {
264
+ constructor(project: javascript.NodeProject) {
265
+ super(project, "client/vite.config.ts", {
266
+ marker: true,
267
+ readonly: true,
268
+ lines: [
269
+ ...headerLines({ tool: "projen", source: ".projenrc.ts" }),
270
+ 'import { existsSync } from "node:fs";',
271
+ 'import path from "node:path";',
272
+ 'import { appKitServingTypesPlugin, appKitTypesPlugin } from "@databricks/appkit";',
273
+ 'import react from "@vitejs/plugin-react";',
274
+ 'import tailwindcss from "@tailwindcss/vite";',
275
+ 'import { defineConfig, mergeConfig, type UserConfig } from "vite";',
276
+ "",
277
+ 'const overrideUrl = new URL("./vite.config.override.ts", import.meta.url);',
278
+ "const override = existsSync(overrideUrl)",
279
+ " ? ((await import(overrideUrl.href)).default as UserConfig)",
280
+ " : {};",
281
+ "",
282
+ "export default defineConfig(",
283
+ " mergeConfig(",
284
+ " {",
285
+ " root: import.meta.dirname,",
286
+ " plugins: [",
287
+ " react(),",
288
+ " tailwindcss(),",
289
+ ' appKitTypesPlugin({ outFile: "shared/appkit-types/analytics.d.ts" }),',
290
+ ' appKitServingTypesPlugin({ outFile: "shared/appkit-types/serving.d.ts" }),',
291
+ " ],",
292
+ " server: { middlewareMode: true },",
293
+ " build: {",
294
+ ' outDir: path.resolve(import.meta.dirname, "dist"),',
295
+ " emptyOutDir: true,",
296
+ ' sourcemap: process.env.NODE_ENV === "development",',
297
+ " },",
298
+ ' optimizeDeps: { include: ["react", "react-dom", "react/jsx-dev-runtime", "react/jsx-runtime"] },',
299
+ ' resolve: { alias: { "@": path.resolve(import.meta.dirname, "src") } },',
300
+ " },",
301
+ " override,",
302
+ " ),",
303
+ ");",
304
+ ],
305
+ });
169
306
  }
170
- project.addDeps("@databricks/appkit@catalog:", "@vitejs/plugin-react@catalog:");
171
307
  }
172
308
 
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;
309
+ class AppYaml extends TextFile {
310
+ constructor(project: javascript.NodeProject) {
311
+ super(project, "app.yaml", {
312
+ marker: false,
313
+ readonly: true,
314
+ lines: [
315
+ ...headerLines({ tool: "projen", source: ".projenrc.ts", comment: "#" }),
316
+ "command:",
317
+ " - ./node_modules/.bin/bun",
318
+ " - server/server.ts",
319
+ "",
320
+ ],
321
+ });
322
+ }
323
+ }
324
+
325
+ class DatabricksBundleFile extends TextFile {
326
+ constructor(project: javascript.NodeProject, options: AppKitBundleOptions = {}) {
327
+ const name = options.name ?? project.name.replace(/^@[^/]+\//, "");
328
+ const description = options.description ?? "A Databricks App powered by AppKit";
329
+ super(project, "databricks.yml", {
330
+ marker: false,
331
+ readonly: true,
332
+ lines: [
333
+ ...headerLines({ tool: "projen", source: ".projenrc.ts", comment: "#" }),
334
+ "bundle:",
335
+ ` name: ${JSON.stringify(name)}`,
336
+ " engine: direct",
337
+ "",
338
+ "resources:",
339
+ " apps:",
340
+ " app:",
341
+ ` name: ${JSON.stringify(name)}`,
342
+ ` description: ${JSON.stringify(description)}`,
343
+ " source_code_path: ./",
344
+ " lifecycle:",
345
+ " started: true",
346
+ "",
347
+ "targets:",
348
+ " dev:",
349
+ " default: true",
350
+ " mode: development",
351
+ "",
352
+ ],
353
+ });
354
+ }
181
355
  }