@dbx-tools/core 0.6.90 → 0.6.91

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 CHANGED
@@ -15,6 +15,8 @@ Key features:
15
15
  arrays.
16
16
  - Workspace/project root discovery from package-manager files, git metadata, and
17
17
  the current working directory.
18
+ - Asynchronous Databricks bundle App-resource streaming with native filesystem
19
+ traversal and CLI-resolved configuration.
18
20
  - Safe filesystem stat and project naming helpers for CLIs and projen synth.
19
21
  - Layered config from process env, environment-specific `.env` files, and the
20
22
  Databricks bundle's App `config.env`.
@@ -155,6 +157,29 @@ through one rule. `config.port()` accepts only TCP ports from 1 through 65535;
155
157
  `config.flattenBundleEnv()`, `flattenAppEnv()`, and `getBundlePath()` expose the
156
158
  same parsing logic for callers that already have parsed configuration data.
157
159
 
160
+ ## Discover Bundle App Resources
161
+
162
+ ```ts
163
+ import { bundle } from "@dbx-tools/core";
164
+
165
+ for await (const resource of bundle.appResources(projectRoot, cwd)) {
166
+ consume(resource);
167
+ }
168
+ ```
169
+
170
+ `appResources()` returns an async generator. It checks bundle files in the
171
+ optional working directory's ancestor chain first, then traverses the project
172
+ boundary with Node's asynchronous native filesystem APIs. It yields each `resources.apps` entry from
173
+ `databricks bundle validate --output json`, so includes, targets, variables,
174
+ and relative source paths are resolved by the Databricks CLI. It does not
175
+ require `app.yml` or `app.yaml`. Each resource includes its App `config` and the
176
+ complete resolved bundle `data`.
177
+
178
+ A failed validation that still returns partial App resources attaches
179
+ `bundleFailure` to those resources. Failed bundles with no App resources yield
180
+ nothing. Missing, invalid, or out-of-bound project paths also yield nothing.
181
+ Malformed successful CLI output throws while the generator is consumed.
182
+
158
183
  ## Run Commands
159
184
 
160
185
  ```ts
@@ -294,6 +319,7 @@ cached too.
294
319
  - `exec` - async/sync process spawning, stdio handling, abort wiring, and shlex.
295
320
  - `bin` - executable download, optional archive extraction, selection, and
296
321
  atomic installation.
322
+ - `bundle` - asynchronous streaming of CLI-resolved Databricks bundle App resources.
297
323
  - `project` - cwd normalization, root discovery, project naming, and git-remote
298
324
  parsing.
299
325
  - `file` - best-effort stat and parsed-record caching by caller-defined source key.
package/index.ts CHANGED
@@ -2,8 +2,10 @@
2
2
  // Regenerated from the exporting modules in ./src.
3
3
  // Hand edits are overwritten on the next watch; this file is read-only.
4
4
 
5
+ export const PACKAGE_IDENTIFIER = "@dbx-tools/core";
5
6
  export * as bin from "./src/bin.ts";
6
7
  export * as brand from "./src/brand.ts";
8
+ export * as bundle from "./src/bundle.ts";
7
9
  export * as config from "./src/config.ts";
8
10
  export * as exec from "./src/exec.ts";
9
11
  export * as file from "./src/file.ts";
package/lib/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
+ export declare const PACKAGE_IDENTIFIER = "@dbx-tools/core";
1
2
  export * as bin from "./src/bin.ts";
2
3
  export * as brand from "./src/brand.ts";
4
+ export * as bundle from "./src/bundle.ts";
3
5
  export * as config from "./src/config.ts";
4
6
  export * as exec from "./src/exec.ts";
5
7
  export * as file from "./src/file.ts";
package/lib/index.js CHANGED
@@ -1,8 +1,10 @@
1
1
  // GENERATED by projen watch - DO NOT EDIT.
2
2
  // Regenerated from the exporting modules in ./src.
3
3
  // Hand edits are overwritten on the next watch; this file is read-only.
4
+ export const PACKAGE_IDENTIFIER = "@dbx-tools/core";
4
5
  export * as bin from "./src/bin.js";
5
6
  export * as brand from "./src/brand.js";
7
+ export * as bundle from "./src/bundle.js";
6
8
  export * as config from "./src/config.js";
7
9
  export * as exec from "./src/exec.js";
8
10
  export * as file from "./src/file.js";
@@ -12,4 +14,4 @@ export * as project from "./src/project.js";
12
14
  export { BrandContextSchema, defaultBrandContext, parseBrandContext, brandContextJsonSchema, brandContextPrompt } from "./src/brand.js";
13
15
  export { MAX_TCP_PORT, ENV_ONLY, valueSchema, bundleResourceSchema, bundleEnvEntrySchema, bundleAppSchema, appEnvEntrySchema, appSchema } from "./src/config.js";
14
16
  export { COMMAND_NOT_FOUND_EXIT_CODE } from "./src/exec.js";
15
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwyQ0FBMkM7QUFDM0MsbURBQW1EO0FBQ25ELHdFQUF3RTtBQUV4RSxPQUFPLEtBQUssR0FBRyxNQUFNLGNBQWMsQ0FBQztBQUNwQyxPQUFPLEtBQUssS0FBSyxNQUFNLGdCQUFnQixDQUFDO0FBQ3hDLE9BQU8sS0FBSyxNQUFNLE1BQU0saUJBQWlCLENBQUM7QUFDMUMsT0FBTyxLQUFLLElBQUksTUFBTSxlQUFlLENBQUM7QUFDdEMsT0FBTyxLQUFLLElBQUksTUFBTSxlQUFlLENBQUM7QUFDdEMsT0FBTyxLQUFLLFFBQVEsTUFBTSxvQkFBb0IsQ0FBQztBQUMvQyxPQUFPLEtBQUssV0FBVyxNQUFNLHVCQUF1QixDQUFDO0FBQ3JELE9BQU8sS0FBSyxPQUFPLE1BQU0sa0JBQWtCLENBQUM7QUFFNUMsT0FBTyxFQUFFLGtCQUFrQixFQUFFLG1CQUFtQixFQUFFLGlCQUFpQixFQUFFLHNCQUFzQixFQUFFLGtCQUFrQixFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFFeEksT0FBTyxFQUFFLFlBQVksRUFBRSxRQUFRLEVBQUUsV0FBVyxFQUFFLG9CQUFvQixFQUFFLG9CQUFvQixFQUFFLGVBQWUsRUFBRSxpQkFBaUIsRUFBRSxTQUFTLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUVqSyxPQUFPLEVBQUUsMkJBQTJCLEVBQUUsTUFBTSxlQUFlLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBHRU5FUkFURUQgYnkgcHJvamVuIHdhdGNoIC0gRE8gTk9UIEVESVQuXG4vLyBSZWdlbmVyYXRlZCBmcm9tIHRoZSBleHBvcnRpbmcgbW9kdWxlcyBpbiAuL3NyYy5cbi8vIEhhbmQgZWRpdHMgYXJlIG92ZXJ3cml0dGVuIG9uIHRoZSBuZXh0IHdhdGNoOyB0aGlzIGZpbGUgaXMgcmVhZC1vbmx5LlxuXG5leHBvcnQgKiBhcyBiaW4gZnJvbSBcIi4vc3JjL2Jpbi50c1wiO1xuZXhwb3J0ICogYXMgYnJhbmQgZnJvbSBcIi4vc3JjL2JyYW5kLnRzXCI7XG5leHBvcnQgKiBhcyBjb25maWcgZnJvbSBcIi4vc3JjL2NvbmZpZy50c1wiO1xuZXhwb3J0ICogYXMgZXhlYyBmcm9tIFwiLi9zcmMvZXhlYy50c1wiO1xuZXhwb3J0ICogYXMgZmlsZSBmcm9tIFwiLi9zcmMvZmlsZS50c1wiO1xuZXhwb3J0ICogYXMgZmlsZUxvY2sgZnJvbSBcIi4vc3JjL2ZpbGUtbG9jay50c1wiO1xuZXhwb3J0ICogYXMgcHJvY2Vzc0xvY2sgZnJvbSBcIi4vc3JjL3Byb2Nlc3MtbG9jay50c1wiO1xuZXhwb3J0ICogYXMgcHJvamVjdCBmcm9tIFwiLi9zcmMvcHJvamVjdC50c1wiO1xuZXhwb3J0IHR5cGUgeyBCaW5Db250ZXh0LCBCaW5TZWxlY3Rpb25Db250ZXh0LCBCaW5TZWxlY3RvciwgQmluVmVyc2lvbk91dHB1dCwgQmluVmVyc2lvblBhcnNlciwgQmluT3B0aW9ucywgQmluVXJsIH0gZnJvbSBcIi4vc3JjL2Jpbi50c1wiO1xuZXhwb3J0IHsgQnJhbmRDb250ZXh0U2NoZW1hLCBkZWZhdWx0QnJhbmRDb250ZXh0LCBwYXJzZUJyYW5kQ29udGV4dCwgYnJhbmRDb250ZXh0SnNvblNjaGVtYSwgYnJhbmRDb250ZXh0UHJvbXB0IH0gZnJvbSBcIi4vc3JjL2JyYW5kLnRzXCI7XG5leHBvcnQgdHlwZSB7IEJyYW5kQ29udGV4dCwgQnJhbmRDb250ZXh0SW5wdXQgfSBmcm9tIFwiLi9zcmMvYnJhbmQudHNcIjtcbmV4cG9ydCB7IE1BWF9UQ1BfUE9SVCwgRU5WX09OTFksIHZhbHVlU2NoZW1hLCBidW5kbGVSZXNvdXJjZVNjaGVtYSwgYnVuZGxlRW52RW50cnlTY2hlbWEsIGJ1bmRsZUFwcFNjaGVtYSwgYXBwRW52RW50cnlTY2hlbWEsIGFwcFNjaGVtYSB9IGZyb20gXCIuL3NyYy9jb25maWcudHNcIjtcbmV4cG9ydCB0eXBlIHsgQ29uZmlnTWFwVmFsdWUsIENvbmZpZ0RhdGEsIENvbmZpZ1NvdXJjZSwgQ29uZmlnT3B0aW9ucywgQ29uZmlnRmlsZSB9IGZyb20gXCIuL3NyYy9jb25maWcudHNcIjtcbmV4cG9ydCB7IENPTU1BTkRfTk9UX0ZPVU5EX0VYSVRfQ09ERSB9IGZyb20gXCIuL3NyYy9leGVjLnRzXCI7XG5leHBvcnQgdHlwZSB7IEV4ZWNTdGRpbywgTGluZUhhbmRsZXIsIFN0ZGlvT3B0aW9uLCBFeGVjUmVzdWx0LCBDaGlsZFByb2Nlc3NSZXN1bHQsIEV4ZWNPcHRpb25zLCBTeW5jRXhlY1N0ZGlvLCBTeW5jRXhlY09wdGlvbnMsIFNwYXduQXJncyB9IGZyb20gXCIuL3NyYy9leGVjLnRzXCI7XG5leHBvcnQgdHlwZSB7IEZpbGVMb2NrQmFja2VuZCwgRmlsZUxvY2tBY3F1aXNpdGlvbiwgRmlsZUxvY2tPcHRpb25zIH0gZnJvbSBcIi4vc3JjL2ZpbGUtbG9jay50c1wiO1xuZXhwb3J0IHR5cGUgeyBQcm9qZWN0Q29udGV4dCB9IGZyb20gXCIuL3NyYy9wcm9qZWN0LnRzXCI7XG4iXX0=
17
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwyQ0FBMkM7QUFDM0MsbURBQW1EO0FBQ25ELHdFQUF3RTtBQUV4RSxNQUFNLENBQUMsTUFBTSxrQkFBa0IsR0FBRyxpQkFBaUIsQ0FBQztBQUNwRCxPQUFPLEtBQUssR0FBRyxNQUFNLGNBQWMsQ0FBQztBQUNwQyxPQUFPLEtBQUssS0FBSyxNQUFNLGdCQUFnQixDQUFDO0FBQ3hDLE9BQU8sS0FBSyxNQUFNLE1BQU0saUJBQWlCLENBQUM7QUFDMUMsT0FBTyxLQUFLLE1BQU0sTUFBTSxpQkFBaUIsQ0FBQztBQUMxQyxPQUFPLEtBQUssSUFBSSxNQUFNLGVBQWUsQ0FBQztBQUN0QyxPQUFPLEtBQUssSUFBSSxNQUFNLGVBQWUsQ0FBQztBQUN0QyxPQUFPLEtBQUssUUFBUSxNQUFNLG9CQUFvQixDQUFDO0FBQy9DLE9BQU8sS0FBSyxXQUFXLE1BQU0sdUJBQXVCLENBQUM7QUFDckQsT0FBTyxLQUFLLE9BQU8sTUFBTSxrQkFBa0IsQ0FBQztBQUU1QyxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsbUJBQW1CLEVBQUUsaUJBQWlCLEVBQUUsc0JBQXNCLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUV4SSxPQUFPLEVBQUUsWUFBWSxFQUFFLFFBQVEsRUFBRSxXQUFXLEVBQUUsb0JBQW9CLEVBQUUsb0JBQW9CLEVBQUUsZUFBZSxFQUFFLGlCQUFpQixFQUFFLFNBQVMsRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBRWpLLE9BQU8sRUFBRSwyQkFBMkIsRUFBRSxNQUFNLGVBQWUsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIEdFTkVSQVRFRCBieSBwcm9qZW4gd2F0Y2ggLSBETyBOT1QgRURJVC5cbi8vIFJlZ2VuZXJhdGVkIGZyb20gdGhlIGV4cG9ydGluZyBtb2R1bGVzIGluIC4vc3JjLlxuLy8gSGFuZCBlZGl0cyBhcmUgb3ZlcndyaXR0ZW4gb24gdGhlIG5leHQgd2F0Y2g7IHRoaXMgZmlsZSBpcyByZWFkLW9ubHkuXG5cbmV4cG9ydCBjb25zdCBQQUNLQUdFX0lERU5USUZJRVIgPSBcIkBkYngtdG9vbHMvY29yZVwiO1xuZXhwb3J0ICogYXMgYmluIGZyb20gXCIuL3NyYy9iaW4udHNcIjtcbmV4cG9ydCAqIGFzIGJyYW5kIGZyb20gXCIuL3NyYy9icmFuZC50c1wiO1xuZXhwb3J0ICogYXMgYnVuZGxlIGZyb20gXCIuL3NyYy9idW5kbGUudHNcIjtcbmV4cG9ydCAqIGFzIGNvbmZpZyBmcm9tIFwiLi9zcmMvY29uZmlnLnRzXCI7XG5leHBvcnQgKiBhcyBleGVjIGZyb20gXCIuL3NyYy9leGVjLnRzXCI7XG5leHBvcnQgKiBhcyBmaWxlIGZyb20gXCIuL3NyYy9maWxlLnRzXCI7XG5leHBvcnQgKiBhcyBmaWxlTG9jayBmcm9tIFwiLi9zcmMvZmlsZS1sb2NrLnRzXCI7XG5leHBvcnQgKiBhcyBwcm9jZXNzTG9jayBmcm9tIFwiLi9zcmMvcHJvY2Vzcy1sb2NrLnRzXCI7XG5leHBvcnQgKiBhcyBwcm9qZWN0IGZyb20gXCIuL3NyYy9wcm9qZWN0LnRzXCI7XG5leHBvcnQgdHlwZSB7IEJpbkNvbnRleHQsIEJpblNlbGVjdGlvbkNvbnRleHQsIEJpblNlbGVjdG9yLCBCaW5WZXJzaW9uT3V0cHV0LCBCaW5WZXJzaW9uUGFyc2VyLCBCaW5PcHRpb25zLCBCaW5VcmwgfSBmcm9tIFwiLi9zcmMvYmluLnRzXCI7XG5leHBvcnQgeyBCcmFuZENvbnRleHRTY2hlbWEsIGRlZmF1bHRCcmFuZENvbnRleHQsIHBhcnNlQnJhbmRDb250ZXh0LCBicmFuZENvbnRleHRKc29uU2NoZW1hLCBicmFuZENvbnRleHRQcm9tcHQgfSBmcm9tIFwiLi9zcmMvYnJhbmQudHNcIjtcbmV4cG9ydCB0eXBlIHsgQnJhbmRDb250ZXh0LCBCcmFuZENvbnRleHRJbnB1dCB9IGZyb20gXCIuL3NyYy9icmFuZC50c1wiO1xuZXhwb3J0IHsgTUFYX1RDUF9QT1JULCBFTlZfT05MWSwgdmFsdWVTY2hlbWEsIGJ1bmRsZVJlc291cmNlU2NoZW1hLCBidW5kbGVFbnZFbnRyeVNjaGVtYSwgYnVuZGxlQXBwU2NoZW1hLCBhcHBFbnZFbnRyeVNjaGVtYSwgYXBwU2NoZW1hIH0gZnJvbSBcIi4vc3JjL2NvbmZpZy50c1wiO1xuZXhwb3J0IHR5cGUgeyBDb25maWdNYXBWYWx1ZSwgQ29uZmlnRGF0YSwgQ29uZmlnU291cmNlLCBDb25maWdPcHRpb25zLCBDb25maWdGaWxlIH0gZnJvbSBcIi4vc3JjL2NvbmZpZy50c1wiO1xuZXhwb3J0IHsgQ09NTUFORF9OT1RfRk9VTkRfRVhJVF9DT0RFIH0gZnJvbSBcIi4vc3JjL2V4ZWMudHNcIjtcbmV4cG9ydCB0eXBlIHsgRXhlY1N0ZGlvLCBMaW5lSGFuZGxlciwgU3RkaW9PcHRpb24sIEV4ZWNSZXN1bHQsIENoaWxkUHJvY2Vzc1Jlc3VsdCwgRXhlY09wdGlvbnMsIFN5bmNFeGVjU3RkaW8sIFN5bmNFeGVjT3B0aW9ucywgU3Bhd25BcmdzIH0gZnJvbSBcIi4vc3JjL2V4ZWMudHNcIjtcbmV4cG9ydCB0eXBlIHsgRmlsZUxvY2tCYWNrZW5kLCBGaWxlTG9ja0FjcXVpc2l0aW9uLCBGaWxlTG9ja09wdGlvbnMgfSBmcm9tIFwiLi9zcmMvZmlsZS1sb2NrLnRzXCI7XG5leHBvcnQgdHlwZSB7IFByb2plY3RDb250ZXh0IH0gZnJvbSBcIi4vc3JjL3Byb2plY3QudHNcIjtcbiJdfQ==
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Streaming discovery of Databricks bundle App resources.
3
+ *
4
+ * Bundle files are found with native filesystem traversal inside a hard project
5
+ * boundary. Each candidate is resolved through `databricks bundle validate
6
+ * --output json` so includes, targets, variables, and App configuration follow
7
+ * the Databricks CLI's semantics.
8
+ *
9
+ * @module
10
+ */
11
+ type AppResource = {
12
+ /** Absolute path to the bundle's root configuration file. */
13
+ bundlePath: string;
14
+ /** Resource key under `resources.apps`. */
15
+ key: string;
16
+ /** Absolute source directory resolved from `source_code_path`. */
17
+ sourceCodePath?: string;
18
+ /** App resource returned by `databricks bundle validate`. */
19
+ config: Record<string, unknown>;
20
+ /** Complete resolved bundle returned by `databricks bundle validate`. */
21
+ data: Record<string, unknown>;
22
+ /** Validation failure associated with partial bundle output. */
23
+ bundleFailure?: Error;
24
+ };
25
+ /**
26
+ * Stream every App resource in bundles under `projectBoundary`.
27
+ *
28
+ * `cwd` defaults to the current directory and may name a file or directory.
29
+ * Bundle files in its ancestor chain are checked before the rest of the project
30
+ * boundary. No App YAML file is required.
31
+ *
32
+ * A missing or invalid boundary/cwd, or a cwd outside the boundary, yields
33
+ * nothing. A non-zero validation with partial JSON attaches
34
+ * `bundleFailure` to each App resource parsed from that bundle. A failed
35
+ * validation with no App resources yields nothing.
36
+ */
37
+ export declare function appResources(projectBoundary: string, cwd?: string): AsyncGenerator<AppResource, void, void>;
38
+ export {};
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Streaming discovery of Databricks bundle App resources.
3
+ *
4
+ * Bundle files are found with native filesystem traversal inside a hard project
5
+ * boundary. Each candidate is resolved through `databricks bundle validate
6
+ * --output json` so includes, targets, variables, and App configuration follow
7
+ * the Databricks CLI's semantics.
8
+ *
9
+ * @module
10
+ */
11
+ import { opendir, realpath, stat } from "node:fs/promises";
12
+ import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
13
+ import { json, object, string } from "@dbx-tools/shared-core";
14
+ import { spawn } from "./exec.js";
15
+ import { resolveWorkingDirectory } from "./project.js";
16
+ import { project } from "../index.js";
17
+ const BUNDLE_FILE_NAMES = new Set(["databricks.yml", "databricks.yaml"]);
18
+ const IGNORED_DIRECTORIES = [
19
+ /^\..*/,
20
+ "node_modules",
21
+ "dist",
22
+ "build",
23
+ "out",
24
+ "target",
25
+ "tmp",
26
+ "vendor",
27
+ ];
28
+ /**
29
+ * Stream every App resource in bundles under `projectBoundary`.
30
+ *
31
+ * `cwd` defaults to the current directory and may name a file or directory.
32
+ * Bundle files in its ancestor chain are checked before the rest of the project
33
+ * boundary. No App YAML file is required.
34
+ *
35
+ * A missing or invalid boundary/cwd, or a cwd outside the boundary, yields
36
+ * nothing. A non-zero validation with partial JSON attaches
37
+ * `bundleFailure` to each App resource parsed from that bundle. A failed
38
+ * validation with no App resources yields nothing.
39
+ */
40
+ export async function* appResources(projectBoundary, cwd) {
41
+ const boundary = await resolveDirectory(projectBoundary);
42
+ const startDirectory = await resolveStartDirectory(cwd);
43
+ if (!boundary || !startDirectory || !isWithinOrEqual(startDirectory, boundary)) {
44
+ return;
45
+ }
46
+ for await (const bundlePath of bundlePaths(boundary, startDirectory)) {
47
+ const validated = await validateBundle(bundlePath);
48
+ for (const [key, config] of Object.entries(bundleApps(validated.data))) {
49
+ if (!object.isRecord(config))
50
+ continue;
51
+ yield {
52
+ bundlePath,
53
+ key,
54
+ config,
55
+ data: validated.data,
56
+ ...object.optional("sourceCodePath", resolveSourceCodePath(bundlePath, config.source_code_path)),
57
+ ...object.optional("bundleFailure", validated.bundleFailure),
58
+ };
59
+ }
60
+ }
61
+ }
62
+ async function resolveDirectory(value) {
63
+ try {
64
+ const resolved = await realpath(resolve(value));
65
+ return (await stat(resolved)).isDirectory() ? resolved : undefined;
66
+ }
67
+ catch {
68
+ return undefined;
69
+ }
70
+ }
71
+ async function resolveStartDirectory(cwd) {
72
+ try {
73
+ const resolved = await realpath(resolveWorkingDirectory(cwd));
74
+ const info = await stat(resolved);
75
+ if (info.isDirectory())
76
+ return resolved;
77
+ if (info.isFile())
78
+ return dirname(resolved);
79
+ return undefined;
80
+ }
81
+ catch {
82
+ return undefined;
83
+ }
84
+ }
85
+ function resolveSourceCodePath(bundlePath, sourceCodePath) {
86
+ if (typeof sourceCodePath !== "string")
87
+ return undefined;
88
+ return isAbsolute(sourceCodePath) ? sourceCodePath : resolve(dirname(bundlePath), sourceCodePath);
89
+ }
90
+ async function* bundlePaths(boundary, startDirectory) {
91
+ const seen = new Set();
92
+ let current = startDirectory;
93
+ while (isWithinOrEqual(current, boundary)) {
94
+ for (const filename of BUNDLE_FILE_NAMES) {
95
+ const candidate = join(current, filename);
96
+ if (!(await isFile(candidate)))
97
+ continue;
98
+ const resolved = await realpath(candidate);
99
+ if (seen.has(resolved))
100
+ continue;
101
+ seen.add(resolved);
102
+ yield resolved;
103
+ }
104
+ if (current === boundary)
105
+ break;
106
+ current = dirname(current);
107
+ }
108
+ yield* descendBundlePaths(boundary, seen);
109
+ }
110
+ async function* descendBundlePaths(directory, seen) {
111
+ const entries = await opendir(directory);
112
+ for await (const entry of entries) {
113
+ const candidate = join(directory, entry.name);
114
+ if (entry.isDirectory()) {
115
+ const ignored = IGNORED_DIRECTORIES.some((ignore) => {
116
+ if (ignore instanceof RegExp)
117
+ return ignore.test(entry.name);
118
+ return ignore === entry.name;
119
+ });
120
+ if (!ignored) {
121
+ yield* descendBundlePaths(candidate, seen);
122
+ }
123
+ continue;
124
+ }
125
+ if (!entry.isFile() || !BUNDLE_FILE_NAMES.has(entry.name))
126
+ continue;
127
+ const resolved = await realpath(candidate);
128
+ if (seen.has(resolved))
129
+ continue;
130
+ seen.add(resolved);
131
+ yield resolved;
132
+ }
133
+ }
134
+ async function validateBundle(bundlePath) {
135
+ const result = await spawn("databricks", ["bundle", "validate", "--output", "json"], {
136
+ cwd: dirname(bundlePath),
137
+ stdin: "ignore",
138
+ stdout: "capture",
139
+ stderr: "capture",
140
+ });
141
+ const detail = string.trimToNull(result.stderr) ?? string.trimToNull(result.stdout);
142
+ const bundleFailure = result.exitCode === 0
143
+ ? undefined
144
+ : new Error(`databricks bundle validate failed for ${bundlePath} (exit ${result.exitCode})${detail ? `: ${detail}` : ""}`);
145
+ const parsed = json.parseRecord(result.stdout);
146
+ if (!parsed) {
147
+ if (bundleFailure)
148
+ return { data: {}, bundleFailure };
149
+ throw new Error(`Databricks CLI returned invalid bundle JSON for ${bundlePath}`);
150
+ }
151
+ return { data: parsed, ...object.optional("bundleFailure", bundleFailure) };
152
+ }
153
+ function bundleApps(bundle) {
154
+ const resources = object.isRecord(bundle.resources) ? bundle.resources : undefined;
155
+ return object.isRecord(resources?.apps) ? resources.apps : {};
156
+ }
157
+ async function isFile(path) {
158
+ try {
159
+ return (await stat(path)).isFile();
160
+ }
161
+ catch {
162
+ return false;
163
+ }
164
+ }
165
+ function isWithinOrEqual(candidate, parent) {
166
+ const path = relative(parent, candidate);
167
+ return path === "" || (path !== ".." && !path.startsWith(`..${sep}`) && !isAbsolute(path));
168
+ }
169
+ if (import.meta.main) {
170
+ const path = process.argv[2];
171
+ if (!path)
172
+ throw new Error("Usage: bun bundle.ts <path>");
173
+ for await (const resource of appResources(project.root(), path)) {
174
+ console.dir(resource, { depth: null });
175
+ }
176
+ }
177
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnVuZGxlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2J1bmRsZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7O0dBU0c7QUFFSCxPQUFPLEVBQUUsT0FBTyxFQUFFLFFBQVEsRUFBRSxJQUFJLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUMzRCxPQUFPLEVBQUUsT0FBTyxFQUFFLFVBQVUsRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFLE9BQU8sRUFBRSxHQUFHLEVBQUUsTUFBTSxXQUFXLENBQUM7QUFDOUUsT0FBTyxFQUFFLElBQUksRUFBRSxNQUFNLEVBQUUsTUFBTSxFQUFFLE1BQU0sd0JBQXdCLENBQUM7QUFDOUQsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLFdBQVcsQ0FBQztBQUNsQyxPQUFPLEVBQUUsdUJBQXVCLEVBQUUsTUFBTSxjQUFjLENBQUM7QUFDdkQsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUV0QyxNQUFNLGlCQUFpQixHQUFHLElBQUksR0FBRyxDQUFDLENBQUMsZ0JBQWdCLEVBQUUsaUJBQWlCLENBQUMsQ0FBQyxDQUFDO0FBQ3pFLE1BQU0sbUJBQW1CLEdBQUc7SUFDMUIsT0FBTztJQUNQLGNBQWM7SUFDZCxNQUFNO0lBQ04sT0FBTztJQUNQLEtBQUs7SUFDTCxRQUFRO0lBQ1IsS0FBSztJQUNMLFFBQVE7Q0FDQSxDQUFDO0FBaUJYOzs7Ozs7Ozs7OztHQVdHO0FBQ0gsTUFBTSxDQUFDLEtBQUssU0FBUyxDQUFDLENBQUMsWUFBWSxDQUNqQyxlQUF1QixFQUN2QixHQUFZO0lBRVosTUFBTSxRQUFRLEdBQUcsTUFBTSxnQkFBZ0IsQ0FBQyxlQUFlLENBQUMsQ0FBQztJQUN6RCxNQUFNLGNBQWMsR0FBRyxNQUFNLHFCQUFxQixDQUFDLEdBQUcsQ0FBQyxDQUFDO0lBQ3hELElBQUksQ0FBQyxRQUFRLElBQUksQ0FBQyxjQUFjLElBQUksQ0FBQyxlQUFlLENBQUMs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SUFBSSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsS0FBSyxHQUFHLEVBQUUsQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUM7QUFDN0YsQ0FBQztBQUVELElBQUksTUFBTSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztJQUNyQixNQUFNLElBQUksR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQzdCLElBQUksQ0FBQyxJQUFJO1FBQUUsTUFBTSxJQUFJLEtBQUssQ0FBQyw2QkFBNkIsQ0FBQyxDQUFDO0lBQzFELElBQUksS0FBSyxFQUFFLE1BQU0sUUFBUSxJQUFJLFlBQVksQ0FBQyxPQUFPLENBQUMsSUFBSSxFQUFHLEVBQUUsSUFBSSxDQUFDLEVBQUUsQ0FBQztRQUNqRSxPQUFPLENBQUMsR0FBRyxDQUFDLFFBQVEsRUFBRSxFQUFFLEtBQUssRUFBRSxJQUFJLEVBQUUsQ0FBQyxDQUFDO0lBQ3pDLENBQUM7QUFDSCxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBTdHJlYW1pbmcgZGlzY292ZXJ5IG9mIERhdGFicmlja3MgYnVuZGxlIEFwcCByZXNvdXJjZXMuXG4gKlxuICogQnVuZGxlIGZpbGVzIGFyZSBmb3VuZCB3aXRoIG5hdGl2ZSBmaWxlc3lzdGVtIHRyYXZlcnNhbCBpbnNpZGUgYSBoYXJkIHByb2plY3RcbiAqIGJvdW5kYXJ5LiBFYWNoIGNhbmRpZGF0ZSBpcyByZXNvbHZlZCB0aHJvdWdoIGBkYXRhYnJpY2tzIGJ1bmRsZSB2YWxpZGF0ZVxuICogLS1vdXRwdXQganNvbmAgc28gaW5jbHVkZXMsIHRhcmdldHMsIHZhcmlhYmxlcywgYW5kIEFwcCBjb25maWd1cmF0aW9uIGZvbGxvd1xuICogdGhlIERhdGFicmlja3MgQ0xJJ3Mgc2VtYW50aWNzLlxuICpcbiAqIEBtb2R1bGVcbiAqL1xuXG5pbXBvcnQgeyBvcGVuZGlyLCByZWFscGF0aCwgc3RhdCB9IGZyb20gXCJub2RlOmZzL3Byb21pc2VzXCI7XG5pbXBvcnQgeyBkaXJuYW1lLCBpc0Fic29sdXRlLCBqb2luLCByZWxhdGl2ZSwgcmVzb2x2ZSwgc2VwIH0gZnJvbSBcIm5vZGU6cGF0aFwiO1xuaW1wb3J0IHsganNvbiwgb2JqZWN0LCBzdHJpbmcgfSBmcm9tIFwiQGRieC10b29scy9zaGFyZWQtY29yZVwiO1xuaW1wb3J0IHsgc3Bhd24gfSBmcm9tIFwiLi9leGVjLnRzXCI7XG5pbXBvcnQgeyByZXNvbHZlV29ya2luZ0RpcmVjdG9yeSB9IGZyb20gXCIuL3Byb2plY3QudHNcIjtcbmltcG9ydCB7IHByb2plY3QgfSBmcm9tIFwiLi4vaW5kZXgudHNcIjtcblxuY29uc3QgQlVORExFX0ZJTEVfTkFNRVMgPSBuZXcgU2V0KFtcImRhdGFicmlja3MueW1sXCIsIFwiZGF0YWJyaWNrcy55YW1sXCJdKTtcbmNvbnN0IElHTk9SRURfRElSRUNUT1JJRVMgPSBbXG4gIC9eXFwuLiovLFxuICBcIm5vZGVfbW9kdWxlc1wiLFxuICBcImRpc3RcIixcbiAgXCJidWlsZFwiLFxuICBcIm91dFwiLFxuICBcInRhcmdldFwiLFxuICBcInRtcFwiLFxuICBcInZlbmRvclwiLFxuXSBhcyBjb25zdDtcblxudHlwZSBBcHBSZXNvdXJjZSA9IHtcbiAgLyoqIEFic29sdXRlIHBhdGggdG8gdGhlIGJ1bmRsZSdzIHJvb3QgY29uZmlndXJhdGlvbiBmaWxlLiAqL1xuICBidW5kbGVQYXRoOiBzdHJpbmc7XG4gIC8qKiBSZXNvdXJjZSBrZXkgdW5kZXIgYHJlc291cmNlcy5hcHBzYC4gKi9cbiAga2V5OiBzdHJpbmc7XG4gIC8qKiBBYnNvbHV0ZSBzb3VyY2UgZGlyZWN0b3J5IHJlc29sdmVkIGZyb20gYHNvdXJjZV9jb2RlX3BhdGhgLiAqL1xuICBzb3VyY2VDb2RlUGF0aD86IHN0cmluZztcbiAgLyoqIEFwcCByZXNvdXJjZSByZXR1cm5lZCBieSBgZGF0YWJyaWNrcyBidW5kbGUgdmFsaWRhdGVgLiAqL1xuICBjb25maWc6IFJlY29yZDxzdHJpbmcsIHVua25vd24+O1xuICAvKiogQ29tcGxldGUgcmVzb2x2ZWQgYnVuZGxlIHJldHVybmVkIGJ5IGBkYXRhYnJpY2tzIGJ1bmRsZSB2YWxpZGF0ZWAuICovXG4gIGRhdGE6IFJlY29yZDxzdHJpbmcsIHVua25vd24+O1xuICAvKiogVmFsaWRhdGlvbiBmYWlsdXJlIGFzc29jaWF0ZWQgd2l0aCBwYXJ0aWFsIGJ1bmRsZSBvdXRwdXQuICovXG4gIGJ1bmRsZUZhaWx1cmU/OiBFcnJvcjtcbn07XG5cbi8qKlxuICogU3RyZWFtIGV2ZXJ5IEFwcCByZXNvdXJjZSBpbiBidW5kbGVzIHVuZGVyIGBwcm9qZWN0Qm91bmRhcnlgLlxuICpcbiAqIGBjd2RgIGRlZmF1bHRzIHRvIHRoZSBjdXJyZW50IGRpcmVjdG9yeSBhbmQgbWF5IG5hbWUgYSBmaWxlIG9yIGRpcmVjdG9yeS5cbiAqIEJ1bmRsZSBmaWxlcyBpbiBpdHMgYW5jZXN0b3IgY2hhaW4gYXJlIGNoZWNrZWQgYmVmb3JlIHRoZSByZXN0IG9mIHRoZSBwcm9qZWN0XG4gKiBib3VuZGFyeS4gTm8gQXBwIFlBTUwgZmlsZSBpcyByZXF1aXJlZC5cbiAqXG4gKiBBIG1pc3Npbmcgb3IgaW52YWxpZCBib3VuZGFyeS9jd2QsIG9yIGEgY3dkIG91dHNpZGUgdGhlIGJvdW5kYXJ5LCB5aWVsZHNcbiAqIG5vdGhpbmcuIEEgbm9uLXplcm8gdmFsaWRhdGlvbiB3aXRoIHBhcnRpYWwgSlNPTiBhdHRhY2hlc1xuICogYGJ1bmRsZUZhaWx1cmVgIHRvIGVhY2ggQXBwIHJlc291cmNlIHBhcnNlZCBmcm9tIHRoYXQgYnVuZGxlLiBBIGZhaWxlZFxuICogdmFsaWRhdGlvbiB3aXRoIG5vIEFwcCByZXNvdXJjZXMgeWllbGRzIG5vdGhpbmcuXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiogYXBwUmVzb3VyY2VzKFxuICBwcm9qZWN0Qm91bmRhcnk6IHN0cmluZyxcbiAgY3dkPzogc3RyaW5nLFxuKTogQXN5bmNHZW5lcmF0b3I8QXBwUmVzb3VyY2UsIHZvaWQsIHZvaWQ+IHtcbiAgY29uc3QgYm91bmRhcnkgPSBhd2FpdCByZXNvbHZlRGlyZWN0b3J5KHByb2plY3RCb3VuZGFyeSk7XG4gIGNvbnN0IHN0YXJ0RGlyZWN0b3J5ID0gYXdhaXQgcmVzb2x2ZVN0YXJ0RGlyZWN0b3J5KGN3ZCk7XG4gIGlmICghYm91bmRhcnkgfHwgIXN0YXJ0RGlyZWN0b3J5IHx8ICFpc1dpdGhpbk9yRXF1YWwoc3RhcnREaXJlY3RvcnksIGJvdW5kYXJ5KSkge1xuICAgIHJldHVybjtcbiAgfVxuXG4gIGZvciBhd2FpdCAoY29uc3QgYnVuZGxlUGF0aCBvZiBidW5kbGVQYXRocyhib3VuZGFyeSwgc3RhcnREaXJlY3RvcnkpKSB7XG4gICAgY29uc3QgdmFsaWRhdGVkID0gYXdhaXQgdmFsaWRhdGVCdW5kbGUoYnVuZGxlUGF0aCk7XG4gICAgZm9yIChjb25zdCBba2V5LCBjb25maWddIG9mIE9iamVjdC5lbnRyaWVzKGJ1bmRsZUFwcHModmFsaWRhdGVkLmRhdGEpKSkge1xuICAgICAgaWYgKCFvYmplY3QuaXNSZWNvcmQoY29uZmlnKSkgY29udGludWU7XG4gICAgICB5aWVsZCB7XG4gICAgICAgIGJ1bmRsZVBhdGgsXG4gICAgICAgIGtleSxcbiAgICAgICAgY29uZmlnLFxuICAgICAgICBkYXRhOiB2YWxpZGF0ZWQuZGF0YSxcbiAgICAgICAgLi4ub2JqZWN0Lm9wdGlvbmFsKFxuICAgICAgICAgIFwic291cmNlQ29kZVBhdGhcIixcbiAgICAgICAgICByZXNvbHZlU291cmNlQ29kZVBhdGgoYnVuZGxlUGF0aCwgY29uZmlnLnNvdXJjZV9jb2RlX3BhdGgpLFxuICAgICAgICApLFxuICAgICAgICAuLi5vYmplY3Qub3B0aW9uYWwoXCJidW5kbGVGYWlsdXJlXCIsIHZhbGlkYXRlZC5idW5kbGVGYWlsdXJlKSxcbiAgICAgIH07XG4gICAgfVxuICB9XG59XG5cbmFzeW5jIGZ1bmN0aW9uIHJlc29sdmVEaXJlY3RvcnkodmFsdWU6IHN0cmluZyk6IFByb21pc2U8c3RyaW5nIHwgdW5kZWZpbmVkPiB7XG4gIHRyeSB7XG4gICAgY29uc3QgcmVzb2x2ZWQgPSBhd2FpdCByZWFscGF0aChyZXNvbHZlKHZhbHVlKSk7XG4gICAgcmV0dXJuIChhd2FpdCBzdGF0KHJlc29sdmVkKSkuaXNEaXJlY3RvcnkoKSA/IHJlc29sdmVkIDogdW5kZWZpbmVkO1xuICB9IGNhdGNoIHtcbiAgICByZXR1cm4gdW5kZWZpbmVkO1xuICB9XG59XG5cbmFzeW5jIGZ1bmN0aW9uIHJlc29sdmVTdGFydERpcmVjdG9yeShjd2Q/OiBzdHJpbmcpOiBQcm9taXNlPHN0cmluZyB8IHVuZGVmaW5lZD4ge1xuICB0cnkge1xuICAgIGNvbnN0IHJlc29sdmVkID0gYXdhaXQgcmVhbHBhdGgocmVzb2x2ZVdvcmtpbmdEaXJlY3RvcnkoY3dkKSk7XG4gICAgY29uc3QgaW5mbyA9IGF3YWl0IHN0YXQocmVzb2x2ZWQpO1xuICAgIGlmIChpbmZvLmlzRGlyZWN0b3J5KCkpIHJldHVybiByZXNvbHZlZDtcbiAgICBpZiAoaW5mby5pc0ZpbGUoKSkgcmV0dXJuIGRpcm5hbWUocmVzb2x2ZWQpO1xuICAgIHJldHVybiB1bmRlZmluZWQ7XG4gIH0gY2F0Y2gge1xuICAgIHJldHVybiB1bmRlZmluZWQ7XG4gIH1cbn1cblxuZnVuY3Rpb24gcmVzb2x2ZVNvdXJjZUNvZGVQYXRoKGJ1bmRsZVBhdGg6IHN0cmluZywgc291cmNlQ29kZVBhdGg6IHVua25vd24pOiBzdHJpbmcgfCB1bmRlZmluZWQge1xuICBpZiAodHlwZW9mIHNvdXJjZUNvZGVQYXRoICE9PSBcInN0cmluZ1wiKSByZXR1cm4gdW5kZWZpbmVkO1xuICByZXR1cm4gaXNBYnNvbHV0ZShzb3VyY2VDb2RlUGF0aCkgPyBzb3VyY2VDb2RlUGF0aCA6IHJlc29sdmUoZGlybmFtZShidW5kbGVQYXRoKSwgc291cmNlQ29kZVBhdGgpO1xufVxuXG5hc3luYyBmdW5jdGlvbiogYnVuZGxlUGF0aHMoXG4gIGJvdW5kYXJ5OiBzdHJpbmcsXG4gIHN0YXJ0RGlyZWN0b3J5OiBzdHJpbmcsXG4pOiBBc3luY0dlbmVyYXRvcjxzdHJpbmcsIHZvaWQsIHZvaWQ+IHtcbiAgY29uc3Qgc2VlbiA9IG5ldyBTZXQ8c3RyaW5nPigpO1xuICBsZXQgY3VycmVudCA9IHN0YXJ0RGlyZWN0b3J5O1xuICB3aGlsZSAoaXNXaXRoaW5PckVxdWFsKGN1cnJlbnQsIGJvdW5kYXJ5KSkge1xuICAgIGZvciAoY29uc3QgZmlsZW5hbWUgb2YgQlVORExFX0ZJTEVfTkFNRVMpIHtcbiAgICAgIGNvbnN0IGNhbmRpZGF0ZSA9IGpvaW4oY3VycmVudCwgZmlsZW5hbWUpO1xuICAgICAgaWYgKCEoYXdhaXQgaXNGaWxlKGNhbmRpZGF0ZSkpKSBjb250aW51ZTtcbiAgICAgIGNvbnN0IHJlc29sdmVkID0gYXdhaXQgcmVhbHBhdGgoY2FuZGlkYXRlKTtcbiAgICAgIGlmIChzZWVuLmhhcyhyZXNvbHZlZCkpIGNvbnRpbnVlO1xuICAgICAgc2Vlbi5hZGQocmVzb2x2ZWQpO1xuICAgICAgeWllbGQgcmVzb2x2ZWQ7XG4gICAgfVxuICAgIGlmIChjdXJyZW50ID09PSBib3VuZGFyeSkgYnJlYWs7XG4gICAgY3VycmVudCA9IGRpcm5hbWUoY3VycmVudCk7XG4gIH1cblxuICB5aWVsZCogZGVzY2VuZEJ1bmRsZVBhdGhzKGJvdW5kYXJ5LCBzZWVuKTtcbn1cblxuYXN5bmMgZnVuY3Rpb24qIGRlc2NlbmRCdW5kbGVQYXRocyhcbiAgZGlyZWN0b3J5OiBzdHJpbmcsXG4gIHNlZW46IFNldDxzdHJpbmc+LFxuKTogQXN5bmNHZW5lcmF0b3I8c3RyaW5nLCB2b2lkLCB2b2lkPiB7XG4gIGNvbnN0IGVudHJpZXMgPSBhd2FpdCBvcGVuZGlyKGRpcmVjdG9yeSk7XG4gIGZvciBhd2FpdCAoY29uc3QgZW50cnkgb2YgZW50cmllcykge1xuICAgIGNvbnN0IGNhbmRpZGF0ZSA9IGpvaW4oZGlyZWN0b3J5LCBlbnRyeS5uYW1lKTtcbiAgICBpZiAoZW50cnkuaXNEaXJlY3RvcnkoKSkge1xuICAgICAgY29uc3QgaWdub3JlZCA9IElHTk9SRURfRElSRUNUT1JJRVMuc29tZSgoaWdub3JlKSA9PiB7XG4gICAgICAgIGlmIChpZ25vcmUgaW5zdGFuY2VvZiBSZWdFeHApIHJldHVybiBpZ25vcmUudGVzdChlbnRyeS5uYW1lKTtcbiAgICAgICAgcmV0dXJuIGlnbm9yZSA9PT0gZW50cnkubmFtZTtcbiAgICAgIH0pO1xuICAgICAgaWYgKCFpZ25vcmVkKSB7XG4gICAgICAgIHlpZWxkKiBkZXNjZW5kQnVuZGxlUGF0aHMoY2FuZGlkYXRlLCBzZWVuKTtcbiAgICAgIH1cbiAgICAgIGNvbnRpbnVlO1xuICAgIH1cbiAgICBpZiAoIWVudHJ5LmlzRmlsZSgpIHx8ICFCVU5ETEVfRklMRV9OQU1FUy5oYXMoZW50cnkubmFtZSkpIGNvbnRpbnVlO1xuICAgIGNvbnN0IHJlc29sdmVkID0gYXdhaXQgcmVhbHBhdGgoY2FuZGlkYXRlKTtcbiAgICBpZiAoc2Vlbi5oYXMocmVzb2x2ZWQpKSBjb250aW51ZTtcbiAgICBzZWVuLmFkZChyZXNvbHZlZCk7XG4gICAgeWllbGQgcmVzb2x2ZWQ7XG4gIH1cbn1cblxuYXN5bmMgZnVuY3Rpb24gdmFsaWRhdGVCdW5kbGUoXG4gIGJ1bmRsZVBhdGg6IHN0cmluZyxcbik6IFByb21pc2U8UGljazxBcHBSZXNvdXJjZSwgXCJkYXRhXCIgfCBcImJ1bmRsZUZhaWx1cmVcIj4+IHtcbiAgY29uc3QgcmVzdWx0ID0gYXdhaXQgc3Bhd24oXCJkYXRhYnJpY2tzXCIsIFtcImJ1bmRsZVwiLCBcInZhbGlkYXRlXCIsIFwiLS1vdXRwdXRcIiwgXCJqc29uXCJdLCB7XG4gICAgY3dkOiBkaXJuYW1lKGJ1bmRsZVBhdGgpLFxuICAgIHN0ZGluOiBcImlnbm9yZVwiLFxuICAgIHN0ZG91dDogXCJjYXB0dXJlXCIsXG4gICAgc3RkZXJyOiBcImNhcHR1cmVcIixcbiAgfSk7XG4gIGNvbnN0IGRldGFpbCA9IHN0cmluZy50cmltVG9OdWxsKHJlc3VsdC5zdGRlcnIpID8/IHN0cmluZy50cmltVG9OdWxsKHJlc3VsdC5zdGRvdXQpO1xuICBjb25zdCBidW5kbGVGYWlsdXJlID1cbiAgICByZXN1bHQuZXhpdENvZGUgPT09IDBcbiAgICAgID8gdW5kZWZpbmVkXG4gICAgICA6IG5ldyBFcnJvcihcbiAgICAgICAgICBgZGF0YWJyaWNrcyBidW5kbGUgdmFsaWRhdGUgZmFpbGVkIGZvciAke2J1bmRsZVBhdGh9IChleGl0ICR7cmVzdWx0LmV4aXRDb2RlfSkke1xuICAgICAgICAgICAgZGV0YWlsID8gYDogJHtkZXRhaWx9YCA6IFwiXCJcbiAgICAgICAgICB9YCxcbiAgICAgICAgKTtcblxuICBjb25zdCBwYXJzZWQgPSBqc29uLnBhcnNlUmVjb3JkKHJlc3VsdC5zdGRvdXQpO1xuICBpZiAoIXBhcnNlZCkge1xuICAgIGlmIChidW5kbGVGYWlsdXJlKSByZXR1cm4geyBkYXRhOiB7fSwgYnVuZGxlRmFpbHVyZSB9O1xuICAgIHRocm93IG5ldyBFcnJvcihgRGF0YWJyaWNrcyBDTEkgcmV0dXJuZWQgaW52YWxpZCBidW5kbGUgSlNPTiBmb3IgJHtidW5kbGVQYXRofWApO1xuICB9XG4gIHJldHVybiB7IGRhdGE6IHBhcnNlZCwgLi4ub2JqZWN0Lm9wdGlvbmFsKFwiYnVuZGxlRmFpbHVyZVwiLCBidW5kbGVGYWlsdXJlKSB9O1xufVxuXG5mdW5jdGlvbiBidW5kbGVBcHBzKGJ1bmRsZTogUmVjb3JkPHN0cmluZywgdW5rbm93bj4pOiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPiB7XG4gIGNvbnN0IHJlc291cmNlcyA9IG9iamVjdC5pc1JlY29yZChidW5kbGUucmVzb3VyY2VzKSA/IGJ1bmRsZS5yZXNvdXJjZXMgOiB1bmRlZmluZWQ7XG4gIHJldHVybiBvYmplY3QuaXNSZWNvcmQocmVzb3VyY2VzPy5hcHBzKSA/IHJlc291cmNlcy5hcHBzIDoge307XG59XG5cbmFzeW5jIGZ1bmN0aW9uIGlzRmlsZShwYXRoOiBzdHJpbmcpOiBQcm9taXNlPGJvb2xlYW4+IHtcbiAgdHJ5IHtcbiAgICByZXR1cm4gKGF3YWl0IHN0YXQocGF0aCkpLmlzRmlsZSgpO1xuICB9IGNhdGNoIHtcbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cbn1cblxuZnVuY3Rpb24gaXNXaXRoaW5PckVxdWFsKGNhbmRpZGF0ZTogc3RyaW5nLCBwYXJlbnQ6IHN0cmluZyk6IGJvb2xlYW4ge1xuICBjb25zdCBwYXRoID0gcmVsYXRpdmUocGFyZW50LCBjYW5kaWRhdGUpO1xuICByZXR1cm4gcGF0aCA9PT0gXCJcIiB8fCAocGF0aCAhPT0gXCIuLlwiICYmICFwYXRoLnN0YXJ0c1dpdGgoYC4uJHtzZXB9YCkgJiYgIWlzQWJzb2x1dGUocGF0aCkpO1xufVxuXG5pZiAoaW1wb3J0Lm1ldGEubWFpbikge1xuICBjb25zdCBwYXRoID0gcHJvY2Vzcy5hcmd2WzJdO1xuICBpZiAoIXBhdGgpIHRocm93IG5ldyBFcnJvcihcIlVzYWdlOiBidW4gYnVuZGxlLnRzIDxwYXRoPlwiKTtcbiAgZm9yIGF3YWl0IChjb25zdCByZXNvdXJjZSBvZiBhcHBSZXNvdXJjZXMocHJvamVjdC5yb290KCkhLCBwYXRoKSkge1xuICAgIGNvbnNvbGUuZGlyKHJlc291cmNlLCB7IGRlcHRoOiBudWxsIH0pO1xuICB9XG59XG4iXX0=
@@ -1 +1 @@
1
- {"root":["../src/bin.ts","../src/brand.ts","../src/config.ts","../src/exec.ts","../src/file-lock.ts","../src/file.ts","../src/process-lock.ts","../src/project.ts","../index.ts"],"version":"5.9.3"}
1
+ {"root":["../src/bin.ts","../src/brand.ts","../src/bundle.ts","../src/config.ts","../src/exec.ts","../src/file-lock.ts","../src/file.ts","../src/process-lock.ts","../src/project.ts","../index.ts"],"version":"5.9.3"}
package/package.json CHANGED
@@ -23,7 +23,7 @@
23
23
  "typescript": "^5.9.3"
24
24
  },
25
25
  "dependencies": {
26
- "@dbx-tools/shared-core": "0.6.90",
26
+ "@dbx-tools/shared-core": "0.6.91",
27
27
  "extract-zip": "^2.0.1",
28
28
  "tar": "^7.5.22",
29
29
  "yaml": "^2.9.0",
@@ -43,7 +43,7 @@
43
43
  "./package.json": "./package.json"
44
44
  }
45
45
  },
46
- "version": "0.6.90",
46
+ "version": "0.6.91",
47
47
  "types": "./lib/index.d.ts",
48
48
  "type": "module",
49
49
  "exports": {
package/src/bundle.ts ADDED
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Streaming discovery of Databricks bundle App resources.
3
+ *
4
+ * Bundle files are found with native filesystem traversal inside a hard project
5
+ * boundary. Each candidate is resolved through `databricks bundle validate
6
+ * --output json` so includes, targets, variables, and App configuration follow
7
+ * the Databricks CLI's semantics.
8
+ *
9
+ * @module
10
+ */
11
+
12
+ import { opendir, realpath, stat } from "node:fs/promises";
13
+ import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
14
+ import { json, object, string } from "@dbx-tools/shared-core";
15
+ import { spawn } from "./exec.ts";
16
+ import { resolveWorkingDirectory } from "./project.ts";
17
+ import { project } from "../index.ts";
18
+
19
+ const BUNDLE_FILE_NAMES = new Set(["databricks.yml", "databricks.yaml"]);
20
+ const IGNORED_DIRECTORIES = [
21
+ /^\..*/,
22
+ "node_modules",
23
+ "dist",
24
+ "build",
25
+ "out",
26
+ "target",
27
+ "tmp",
28
+ "vendor",
29
+ ] as const;
30
+
31
+ type AppResource = {
32
+ /** Absolute path to the bundle's root configuration file. */
33
+ bundlePath: string;
34
+ /** Resource key under `resources.apps`. */
35
+ key: string;
36
+ /** Absolute source directory resolved from `source_code_path`. */
37
+ sourceCodePath?: string;
38
+ /** App resource returned by `databricks bundle validate`. */
39
+ config: Record<string, unknown>;
40
+ /** Complete resolved bundle returned by `databricks bundle validate`. */
41
+ data: Record<string, unknown>;
42
+ /** Validation failure associated with partial bundle output. */
43
+ bundleFailure?: Error;
44
+ };
45
+
46
+ /**
47
+ * Stream every App resource in bundles under `projectBoundary`.
48
+ *
49
+ * `cwd` defaults to the current directory and may name a file or directory.
50
+ * Bundle files in its ancestor chain are checked before the rest of the project
51
+ * boundary. No App YAML file is required.
52
+ *
53
+ * A missing or invalid boundary/cwd, or a cwd outside the boundary, yields
54
+ * nothing. A non-zero validation with partial JSON attaches
55
+ * `bundleFailure` to each App resource parsed from that bundle. A failed
56
+ * validation with no App resources yields nothing.
57
+ */
58
+ export async function* appResources(
59
+ projectBoundary: string,
60
+ cwd?: string,
61
+ ): AsyncGenerator<AppResource, void, void> {
62
+ const boundary = await resolveDirectory(projectBoundary);
63
+ const startDirectory = await resolveStartDirectory(cwd);
64
+ if (!boundary || !startDirectory || !isWithinOrEqual(startDirectory, boundary)) {
65
+ return;
66
+ }
67
+
68
+ for await (const bundlePath of bundlePaths(boundary, startDirectory)) {
69
+ const validated = await validateBundle(bundlePath);
70
+ for (const [key, config] of Object.entries(bundleApps(validated.data))) {
71
+ if (!object.isRecord(config)) continue;
72
+ yield {
73
+ bundlePath,
74
+ key,
75
+ config,
76
+ data: validated.data,
77
+ ...object.optional(
78
+ "sourceCodePath",
79
+ resolveSourceCodePath(bundlePath, config.source_code_path),
80
+ ),
81
+ ...object.optional("bundleFailure", validated.bundleFailure),
82
+ };
83
+ }
84
+ }
85
+ }
86
+
87
+ async function resolveDirectory(value: string): Promise<string | undefined> {
88
+ try {
89
+ const resolved = await realpath(resolve(value));
90
+ return (await stat(resolved)).isDirectory() ? resolved : undefined;
91
+ } catch {
92
+ return undefined;
93
+ }
94
+ }
95
+
96
+ async function resolveStartDirectory(cwd?: string): Promise<string | undefined> {
97
+ try {
98
+ const resolved = await realpath(resolveWorkingDirectory(cwd));
99
+ const info = await stat(resolved);
100
+ if (info.isDirectory()) return resolved;
101
+ if (info.isFile()) return dirname(resolved);
102
+ return undefined;
103
+ } catch {
104
+ return undefined;
105
+ }
106
+ }
107
+
108
+ function resolveSourceCodePath(bundlePath: string, sourceCodePath: unknown): string | undefined {
109
+ if (typeof sourceCodePath !== "string") return undefined;
110
+ return isAbsolute(sourceCodePath) ? sourceCodePath : resolve(dirname(bundlePath), sourceCodePath);
111
+ }
112
+
113
+ async function* bundlePaths(
114
+ boundary: string,
115
+ startDirectory: string,
116
+ ): AsyncGenerator<string, void, void> {
117
+ const seen = new Set<string>();
118
+ let current = startDirectory;
119
+ while (isWithinOrEqual(current, boundary)) {
120
+ for (const filename of BUNDLE_FILE_NAMES) {
121
+ const candidate = join(current, filename);
122
+ if (!(await isFile(candidate))) continue;
123
+ const resolved = await realpath(candidate);
124
+ if (seen.has(resolved)) continue;
125
+ seen.add(resolved);
126
+ yield resolved;
127
+ }
128
+ if (current === boundary) break;
129
+ current = dirname(current);
130
+ }
131
+
132
+ yield* descendBundlePaths(boundary, seen);
133
+ }
134
+
135
+ async function* descendBundlePaths(
136
+ directory: string,
137
+ seen: Set<string>,
138
+ ): AsyncGenerator<string, void, void> {
139
+ const entries = await opendir(directory);
140
+ for await (const entry of entries) {
141
+ const candidate = join(directory, entry.name);
142
+ if (entry.isDirectory()) {
143
+ const ignored = IGNORED_DIRECTORIES.some((ignore) => {
144
+ if (ignore instanceof RegExp) return ignore.test(entry.name);
145
+ return ignore === entry.name;
146
+ });
147
+ if (!ignored) {
148
+ yield* descendBundlePaths(candidate, seen);
149
+ }
150
+ continue;
151
+ }
152
+ if (!entry.isFile() || !BUNDLE_FILE_NAMES.has(entry.name)) continue;
153
+ const resolved = await realpath(candidate);
154
+ if (seen.has(resolved)) continue;
155
+ seen.add(resolved);
156
+ yield resolved;
157
+ }
158
+ }
159
+
160
+ async function validateBundle(
161
+ bundlePath: string,
162
+ ): Promise<Pick<AppResource, "data" | "bundleFailure">> {
163
+ const result = await spawn("databricks", ["bundle", "validate", "--output", "json"], {
164
+ cwd: dirname(bundlePath),
165
+ stdin: "ignore",
166
+ stdout: "capture",
167
+ stderr: "capture",
168
+ });
169
+ const detail = string.trimToNull(result.stderr) ?? string.trimToNull(result.stdout);
170
+ const bundleFailure =
171
+ result.exitCode === 0
172
+ ? undefined
173
+ : new Error(
174
+ `databricks bundle validate failed for ${bundlePath} (exit ${result.exitCode})${
175
+ detail ? `: ${detail}` : ""
176
+ }`,
177
+ );
178
+
179
+ const parsed = json.parseRecord(result.stdout);
180
+ if (!parsed) {
181
+ if (bundleFailure) return { data: {}, bundleFailure };
182
+ throw new Error(`Databricks CLI returned invalid bundle JSON for ${bundlePath}`);
183
+ }
184
+ return { data: parsed, ...object.optional("bundleFailure", bundleFailure) };
185
+ }
186
+
187
+ function bundleApps(bundle: Record<string, unknown>): Record<string, unknown> {
188
+ const resources = object.isRecord(bundle.resources) ? bundle.resources : undefined;
189
+ return object.isRecord(resources?.apps) ? resources.apps : {};
190
+ }
191
+
192
+ async function isFile(path: string): Promise<boolean> {
193
+ try {
194
+ return (await stat(path)).isFile();
195
+ } catch {
196
+ return false;
197
+ }
198
+ }
199
+
200
+ function isWithinOrEqual(candidate: string, parent: string): boolean {
201
+ const path = relative(parent, candidate);
202
+ return path === "" || (path !== ".." && !path.startsWith(`..${sep}`) && !isAbsolute(path));
203
+ }
204
+
205
+ if (import.meta.main) {
206
+ const path = process.argv[2];
207
+ if (!path) throw new Error("Usage: bun bundle.ts <path>");
208
+ for await (const resource of appResources(project.root()!, path)) {
209
+ console.dir(resource, { depth: null });
210
+ }
211
+ }