@databricks/appkit 0.59.0 → 0.61.0

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 (118) hide show
  1. package/NOTICE.md +11 -10
  2. package/dist/agents/databricks.js +1 -2
  3. package/dist/agents/databricks.js.map +1 -1
  4. package/dist/agents/supervisor-api.js +1 -2
  5. package/dist/agents/supervisor-api.js.map +1 -1
  6. package/dist/appkit/package.js +1 -1
  7. package/dist/cache/index.js +2 -3
  8. package/dist/cache/index.js.map +1 -1
  9. package/dist/cli/commands/doctor/bundle.js +3 -11
  10. package/dist/cli/commands/doctor/bundle.js.map +1 -1
  11. package/dist/cli/commands/doctor/databricks-client.js +18 -22
  12. package/dist/cli/commands/doctor/databricks-client.js.map +1 -1
  13. package/dist/cli/commands/registry/add.js +410 -0
  14. package/dist/cli/commands/registry/add.js.map +1 -0
  15. package/dist/cli/commands/registry/client.js +94 -0
  16. package/dist/cli/commands/registry/client.js.map +1 -0
  17. package/dist/cli/commands/registry/config-plan.js +110 -0
  18. package/dist/cli/commands/registry/config-plan.js.map +1 -0
  19. package/dist/cli/commands/registry/config-writer.js +176 -0
  20. package/dist/cli/commands/registry/config-writer.js.map +1 -0
  21. package/dist/cli/commands/registry/constants.js +62 -0
  22. package/dist/cli/commands/registry/constants.js.map +1 -0
  23. package/dist/cli/commands/registry/env-reconcile.js +116 -0
  24. package/dist/cli/commands/registry/env-reconcile.js.map +1 -0
  25. package/dist/cli/commands/registry/env-writer.js +198 -0
  26. package/dist/cli/commands/registry/env-writer.js.map +1 -0
  27. package/dist/cli/commands/registry/index.js +25 -0
  28. package/dist/cli/commands/registry/index.js.map +1 -0
  29. package/dist/cli/commands/registry/info.js +45 -0
  30. package/dist/cli/commands/registry/info.js.map +1 -0
  31. package/dist/cli/commands/registry/list.js +130 -0
  32. package/dist/cli/commands/registry/list.js.map +1 -0
  33. package/dist/cli/commands/registry/requirements.js +92 -0
  34. package/dist/cli/commands/registry/requirements.js.map +1 -0
  35. package/dist/cli/commands/registry/server-register.js +124 -0
  36. package/dist/cli/commands/registry/server-register.js.map +1 -0
  37. package/dist/cli/commands/registry/workspace-picker.js +353 -0
  38. package/dist/cli/commands/registry/workspace-picker.js.map +1 -0
  39. package/dist/cli/deploy-config.js +36 -0
  40. package/dist/cli/deploy-config.js.map +1 -0
  41. package/dist/cli/index.js +4 -0
  42. package/dist/cli/index.js.map +1 -1
  43. package/dist/connectors/context.js +1 -2
  44. package/dist/connectors/context.js.map +1 -1
  45. package/dist/connectors/files/client.js +1 -2
  46. package/dist/connectors/files/client.js.map +1 -1
  47. package/dist/connectors/genie/client.js +1 -2
  48. package/dist/connectors/genie/client.js.map +1 -1
  49. package/dist/connectors/jobs/client.js +1 -2
  50. package/dist/connectors/jobs/client.js.map +1 -1
  51. package/dist/connectors/sql-warehouse/client.js +1 -2
  52. package/dist/connectors/sql-warehouse/client.js.map +1 -1
  53. package/dist/context/execution-context.d.ts +0 -1
  54. package/dist/context/execution-context.d.ts.map +1 -1
  55. package/dist/context/service-context.d.ts +1 -2
  56. package/dist/context/service-context.d.ts.map +1 -1
  57. package/dist/context/service-context.js +2 -3
  58. package/dist/context/service-context.js.map +1 -1
  59. package/dist/core/appkit.d.ts +1 -2
  60. package/dist/core/appkit.d.ts.map +1 -1
  61. package/dist/index.d.ts +3 -5
  62. package/dist/index.js +2 -3
  63. package/dist/plugins/agents/agents.d.ts.map +1 -1
  64. package/dist/plugins/agents/agents.js +117 -80
  65. package/dist/plugins/agents/agents.js.map +1 -1
  66. package/dist/plugins/agents/manifest.js +10 -0
  67. package/dist/plugins/agents/mlflow.js +123 -0
  68. package/dist/plugins/agents/mlflow.js.map +1 -0
  69. package/dist/plugins/agents/schemas.js +4 -2
  70. package/dist/plugins/agents/schemas.js.map +1 -1
  71. package/dist/plugins/files/plugin.js +1 -2
  72. package/dist/plugins/files/plugin.js.map +1 -1
  73. package/dist/plugins/files/types.d.ts +1 -2
  74. package/dist/plugins/files/types.d.ts.map +1 -1
  75. package/dist/plugins/jobs/types.d.ts +1 -2
  76. package/dist/plugins/jobs/types.d.ts.map +1 -1
  77. package/dist/plugins/lakebase/lakebase.js +1 -2
  78. package/dist/plugins/lakebase/lakebase.js.map +1 -1
  79. package/dist/schemas/manifest.js +4 -4
  80. package/dist/schemas/manifest.js.map +1 -1
  81. package/dist/shared/src/index.d.ts +4 -1
  82. package/dist/shared/src/schemas/manifest.d.ts +6 -6
  83. package/dist/shared/src/workspace-client/client.js +57 -0
  84. package/dist/shared/src/workspace-client/client.js.map +1 -0
  85. package/dist/shared/src/workspace-client/errors.d.ts +2 -0
  86. package/dist/shared/src/workspace-client/errors.js +3 -0
  87. package/dist/shared/src/workspace-client/factory.d.ts +19 -0
  88. package/dist/shared/src/workspace-client/factory.d.ts.map +1 -0
  89. package/dist/shared/src/workspace-client/factory.js +24 -0
  90. package/dist/shared/src/workspace-client/factory.js.map +1 -0
  91. package/dist/shared/src/workspace-client/index.d.ts +4 -0
  92. package/dist/shared/src/workspace-client/legacy.d.ts +31 -0
  93. package/dist/shared/src/workspace-client/legacy.d.ts.map +1 -0
  94. package/dist/shared/src/workspace-client/legacy.js +23 -0
  95. package/dist/shared/src/workspace-client/legacy.js.map +1 -0
  96. package/dist/shared/src/workspace-client/types.d.ts +49 -0
  97. package/dist/shared/src/workspace-client/types.d.ts.map +1 -0
  98. package/dist/type-generator/index.js +1 -2
  99. package/dist/type-generator/index.js.map +1 -1
  100. package/dist/type-generator/query-registry.js +1 -2
  101. package/dist/type-generator/query-registry.js.map +1 -1
  102. package/dist/type-generator/serving/fetcher.js +1 -2
  103. package/dist/type-generator/serving/fetcher.js.map +1 -1
  104. package/dist/type-generator/serving/generator.js +1 -2
  105. package/dist/type-generator/serving/generator.js.map +1 -1
  106. package/dist/type-generator/vite-plugin.js +1 -2
  107. package/dist/type-generator/vite-plugin.js.map +1 -1
  108. package/dist/workspace-client/index.d.ts +4 -3
  109. package/dist/workspace-client/index.js +2 -2
  110. package/dist/workspace-client/legacy.d.ts +9 -4
  111. package/dist/workspace-client/legacy.d.ts.map +1 -1
  112. package/dist/workspace-client/legacy.js +3 -3
  113. package/dist/workspace-client/legacy.js.map +1 -1
  114. package/dist/workspace-client/types.d.ts +2 -2
  115. package/docs/api/appkit/Function.createWorkspaceClient.md +4 -4
  116. package/docs/api/appkit/Interface.WorkspaceClientOptions.md +11 -0
  117. package/package.json +6 -4
  118. package/sbom.cdx.json +1 -1
@@ -0,0 +1,124 @@
1
+ import { JS_IDENTIFIER } from "./constants.js";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { Lang, parse } from "@ast-grep/napi";
5
+
6
+ //#region src/cli/commands/registry/server-register.ts
7
+ /** Server entry candidates within the server root, in priority order. */
8
+ const SERVER_FILE_CANDIDATES = [
9
+ "server.ts",
10
+ "index.ts",
11
+ "src/server.ts",
12
+ "src/index.ts"
13
+ ];
14
+ function findServerFile(serverRoot) {
15
+ for (const candidate of SERVER_FILE_CANDIDATES) {
16
+ const p = path.join(serverRoot, candidate);
17
+ if (fs.existsSync(p)) return p;
18
+ }
19
+ return null;
20
+ }
21
+ /** The `plugins: [...]` array node inside a createApp call, if present. */
22
+ function findPluginsArray(root) {
23
+ for (const pair of root.findAll({ rule: { kind: "pair" } })) {
24
+ if (pair.find({ rule: { kind: "property_identifier" } })?.text() !== "plugins") continue;
25
+ const arr = pair.find({ rule: { kind: "array" } });
26
+ if (arr) return arr;
27
+ }
28
+ return null;
29
+ }
30
+ function arrayElementNames(arr) {
31
+ const names = /* @__PURE__ */ new Set();
32
+ for (const child of arr.children()) if (child.kind() === "identifier") names.add(child.text());
33
+ else if (child.kind() === "call_expression") {
34
+ const callee = child.children()[0];
35
+ if (callee?.kind() === "identifier") names.add(callee.text());
36
+ }
37
+ return names;
38
+ }
39
+ /** Local binding names introduced by an import statement — default, namespace,
40
+ * and named specifiers, with aliases resolved to the local name. */
41
+ function importBindingNames(stmt) {
42
+ const names = /* @__PURE__ */ new Set();
43
+ const clause = stmt.find({ rule: { kind: "import_clause" } });
44
+ if (!clause) return names;
45
+ for (const child of clause.children()) {
46
+ const kind = child.kind();
47
+ if (kind === "identifier") names.add(child.text());
48
+ else if (kind === "namespace_import") {
49
+ const id = child.find({ rule: { kind: "identifier" } });
50
+ if (id) names.add(id.text());
51
+ } else if (kind === "named_imports") for (const spec of child.findAll({ rule: { kind: "import_specifier" } })) {
52
+ const local = spec.field("alias")?.text() ?? spec.field("name")?.text();
53
+ if (local) names.add(local);
54
+ }
55
+ }
56
+ return names;
57
+ }
58
+ /**
59
+ * Best-effort: register a plugin in the server entry's `createApp({ plugins })`
60
+ * call by inserting the import and adding it to the array. Only edits the
61
+ * standard shape (a `plugins: [...]` array literal); returns `skipped` otherwise
62
+ * so the caller can fall back to printing manual instructions. Idempotent.
63
+ */
64
+ function registerPluginInServer(serverRoot, importPath, exportName) {
65
+ if (!JS_IDENTIFIER.test(exportName)) return {
66
+ status: "skipped",
67
+ reason: "invalid plugin export name"
68
+ };
69
+ if (!/^[.][./A-Za-z0-9_-]*$/.test(importPath)) return {
70
+ status: "skipped",
71
+ reason: "invalid plugin import path"
72
+ };
73
+ const serverFile = findServerFile(serverRoot);
74
+ if (!serverFile) return {
75
+ status: "skipped",
76
+ reason: "no server entry file found"
77
+ };
78
+ const content = fs.readFileSync(serverFile, "utf-8");
79
+ const root = parse(serverFile.endsWith(".tsx") ? Lang.Tsx : Lang.TypeScript, content).root();
80
+ const arr = findPluginsArray(root);
81
+ if (!arr) return {
82
+ status: "skipped",
83
+ reason: "no createApp({ plugins: [...] }) array found"
84
+ };
85
+ const file = path.relative(serverRoot, serverFile);
86
+ if (arrayElementNames(arr).has(exportName)) return {
87
+ status: "already",
88
+ file
89
+ };
90
+ const edits = [];
91
+ const newElem = `${exportName}()`;
92
+ const elementKinds = [
93
+ "identifier",
94
+ "call_expression",
95
+ "spread_element"
96
+ ];
97
+ const firstEl = arr.children().find((c) => elementKinds.includes(c.kind()));
98
+ if (!firstEl) edits.push(arr.replace(`[${newElem}]`));
99
+ else {
100
+ const startIdx = firstEl.range().start.index;
101
+ const lineStart = content.lastIndexOf("\n", startIdx - 1);
102
+ const indent = content.slice(lineStart + 1, startIdx);
103
+ const sep = lineStart !== -1 && /^[ \t]*$/.test(indent) ? `,\n${indent}` : ", ";
104
+ edits.push(firstEl.replace(`${newElem}${sep}${firstEl.text()}`));
105
+ }
106
+ const importStmts = root.findAll({ rule: { kind: "import_statement" } });
107
+ const hasBinding = importStmts.some((s) => importBindingNames(s).has(exportName));
108
+ const importLine = `import { ${exportName} } from "${importPath}";`;
109
+ if (!hasBinding && importStmts.length > 0) {
110
+ const last = importStmts[importStmts.length - 1];
111
+ edits.push(last.replace(`${last.text()}\n${importLine}`));
112
+ }
113
+ let output = root.commitEdits(edits);
114
+ if (!hasBinding && importStmts.length === 0) output = `${importLine}\n${output}`;
115
+ fs.writeFileSync(serverFile, output);
116
+ return {
117
+ status: "wired",
118
+ file
119
+ };
120
+ }
121
+
122
+ //#endregion
123
+ export { registerPluginInServer };
124
+ //# sourceMappingURL=server-register.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-register.js","names":[],"sources":["../../../../src/cli/commands/registry/server-register.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { Lang, parse, type SgNode } from \"@ast-grep/napi\";\nimport { JS_IDENTIFIER } from \"./constants\";\n\n/** Server entry candidates within the server root, in priority order. */\nconst SERVER_FILE_CANDIDATES = [\n \"server.ts\",\n \"index.ts\",\n \"src/server.ts\",\n \"src/index.ts\",\n];\n\nexport interface RegisterResult {\n /** wired = edited; already = plugin was present; skipped = couldn't safely edit. */\n status: \"wired\" | \"already\" | \"skipped\";\n file?: string;\n reason?: string;\n}\n\nfunction findServerFile(serverRoot: string): string | null {\n for (const candidate of SERVER_FILE_CANDIDATES) {\n const p = path.join(serverRoot, candidate);\n if (fs.existsSync(p)) return p;\n }\n return null;\n}\n\n/** The `plugins: [...]` array node inside a createApp call, if present. */\nfunction findPluginsArray(root: SgNode): SgNode | null {\n for (const pair of root.findAll({ rule: { kind: \"pair\" } })) {\n const key = pair.find({ rule: { kind: \"property_identifier\" } });\n if (key?.text() !== \"plugins\") continue;\n const arr = pair.find({ rule: { kind: \"array\" } });\n if (arr) return arr;\n }\n return null;\n}\n\nfunction arrayElementNames(arr: SgNode): Set<string> {\n const names = new Set<string>();\n for (const child of arr.children()) {\n if (child.kind() === \"identifier\") {\n names.add(child.text());\n } else if (child.kind() === \"call_expression\") {\n const callee = child.children()[0];\n if (callee?.kind() === \"identifier\") names.add(callee.text());\n }\n }\n return names;\n}\n\n/** Local binding names introduced by an import statement — default, namespace,\n * and named specifiers, with aliases resolved to the local name. */\nfunction importBindingNames(stmt: SgNode): Set<string> {\n const names = new Set<string>();\n const clause = stmt.find({ rule: { kind: \"import_clause\" } });\n if (!clause) return names;\n for (const child of clause.children()) {\n const kind = child.kind();\n if (kind === \"identifier\") {\n names.add(child.text());\n } else if (kind === \"namespace_import\") {\n const id = child.find({ rule: { kind: \"identifier\" } });\n if (id) names.add(id.text());\n } else if (kind === \"named_imports\") {\n for (const spec of child.findAll({\n rule: { kind: \"import_specifier\" },\n })) {\n const local = spec.field(\"alias\")?.text() ?? spec.field(\"name\")?.text();\n if (local) names.add(local);\n }\n }\n }\n return names;\n}\n\n/**\n * Best-effort: register a plugin in the server entry's `createApp({ plugins })`\n * call by inserting the import and adding it to the array. Only edits the\n * standard shape (a `plugins: [...]` array literal); returns `skipped` otherwise\n * so the caller can fall back to printing manual instructions. Idempotent.\n */\nexport function registerPluginInServer(\n serverRoot: string,\n importPath: string,\n exportName: string,\n): RegisterResult {\n // exportName and importPath are interpolated into the user's server source.\n // Registry items are untrusted, so refuse anything that isn't a plain JS\n // identifier / clean relative module path — prevents code injection via a\n // crafted export name or import path.\n if (!JS_IDENTIFIER.test(exportName)) {\n return { status: \"skipped\", reason: \"invalid plugin export name\" };\n }\n if (!/^[.][./A-Za-z0-9_-]*$/.test(importPath)) {\n return { status: \"skipped\", reason: \"invalid plugin import path\" };\n }\n\n const serverFile = findServerFile(serverRoot);\n if (!serverFile) {\n return { status: \"skipped\", reason: \"no server entry file found\" };\n }\n\n const content = fs.readFileSync(serverFile, \"utf-8\");\n const lang = serverFile.endsWith(\".tsx\") ? Lang.Tsx : Lang.TypeScript;\n const root = parse(lang, content).root();\n\n const arr = findPluginsArray(root);\n if (!arr) {\n return {\n status: \"skipped\",\n reason: \"no createApp({ plugins: [...] }) array found\",\n };\n }\n\n const file = path.relative(serverRoot, serverFile);\n if (arrayElementNames(arr).has(exportName)) {\n return { status: \"already\", file };\n }\n\n const edits = [];\n\n // toPlugin exports are factories, registered as a call: `hello()`.\n const newElem = `${exportName}()`;\n\n // Insert before the first element, matching its indentation so the array\n // formatting is preserved (or inline for a single-line array).\n const elementKinds = [\"identifier\", \"call_expression\", \"spread_element\"];\n const firstEl = arr\n .children()\n .find((c) => elementKinds.includes(c.kind() as string));\n if (!firstEl) {\n edits.push(arr.replace(`[${newElem}]`));\n } else {\n const startIdx = firstEl.range().start.index;\n const lineStart = content.lastIndexOf(\"\\n\", startIdx - 1);\n const indent = content.slice(lineStart + 1, startIdx);\n const multiline = lineStart !== -1 && /^[ \\t]*$/.test(indent);\n const sep = multiline ? `,\\n${indent}` : \", \";\n edits.push(firstEl.replace(`${newElem}${sep}${firstEl.text()}`));\n }\n\n // Add the import unless `exportName` is already bound by some import. De-dup\n // on the binding, not the module path: an existing import from the same path\n // under a different name (e.g. `import { HelloPlugin } from \"./plugins/hello\"`)\n // must not suppress this one, or the `${exportName}()` element just added to\n // the array would reference an unimported symbol and the server won't compile.\n const importStmts = root.findAll({ rule: { kind: \"import_statement\" } });\n const hasBinding = importStmts.some((s) =>\n importBindingNames(s).has(exportName),\n );\n const importLine = `import { ${exportName} } from \"${importPath}\";`;\n if (!hasBinding && importStmts.length > 0) {\n const last = importStmts[importStmts.length - 1];\n edits.push(last.replace(`${last.text()}\\n${importLine}`));\n }\n\n let output = root.commitEdits(edits);\n if (!hasBinding && importStmts.length === 0) {\n output = `${importLine}\\n${output}`;\n }\n fs.writeFileSync(serverFile, output);\n\n return { status: \"wired\", file };\n}\n"],"mappings":";;;;;;;AAMA,MAAM,yBAAyB;CAC7B;CACA;CACA;CACA;CACD;AASD,SAAS,eAAe,YAAmC;AACzD,MAAK,MAAM,aAAa,wBAAwB;EAC9C,MAAM,IAAI,KAAK,KAAK,YAAY,UAAU;AAC1C,MAAI,GAAG,WAAW,EAAE,CAAE,QAAO;;AAE/B,QAAO;;;AAIT,SAAS,iBAAiB,MAA6B;AACrD,MAAK,MAAM,QAAQ,KAAK,QAAQ,EAAE,MAAM,EAAE,MAAM,QAAQ,EAAE,CAAC,EAAE;AAE3D,MADY,KAAK,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,EAAE,CAAC,EACvD,MAAM,KAAK,UAAW;EAC/B,MAAM,MAAM,KAAK,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,EAAE,CAAC;AAClD,MAAI,IAAK,QAAO;;AAElB,QAAO;;AAGT,SAAS,kBAAkB,KAA0B;CACnD,MAAM,wBAAQ,IAAI,KAAa;AAC/B,MAAK,MAAM,SAAS,IAAI,UAAU,CAChC,KAAI,MAAM,MAAM,KAAK,aACnB,OAAM,IAAI,MAAM,MAAM,CAAC;UACd,MAAM,MAAM,KAAK,mBAAmB;EAC7C,MAAM,SAAS,MAAM,UAAU,CAAC;AAChC,MAAI,QAAQ,MAAM,KAAK,aAAc,OAAM,IAAI,OAAO,MAAM,CAAC;;AAGjE,QAAO;;;;AAKT,SAAS,mBAAmB,MAA2B;CACrD,MAAM,wBAAQ,IAAI,KAAa;CAC/B,MAAM,SAAS,KAAK,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,EAAE,CAAC;AAC7D,KAAI,CAAC,OAAQ,QAAO;AACpB,MAAK,MAAM,SAAS,OAAO,UAAU,EAAE;EACrC,MAAM,OAAO,MAAM,MAAM;AACzB,MAAI,SAAS,aACX,OAAM,IAAI,MAAM,MAAM,CAAC;WACd,SAAS,oBAAoB;GACtC,MAAM,KAAK,MAAM,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,EAAE,CAAC;AACvD,OAAI,GAAI,OAAM,IAAI,GAAG,MAAM,CAAC;aACnB,SAAS,gBAClB,MAAK,MAAM,QAAQ,MAAM,QAAQ,EAC/B,MAAM,EAAE,MAAM,oBAAoB,EACnC,CAAC,EAAE;GACF,MAAM,QAAQ,KAAK,MAAM,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,OAAO,EAAE,MAAM;AACvE,OAAI,MAAO,OAAM,IAAI,MAAM;;;AAIjC,QAAO;;;;;;;;AAST,SAAgB,uBACd,YACA,YACA,YACgB;AAKhB,KAAI,CAAC,cAAc,KAAK,WAAW,CACjC,QAAO;EAAE,QAAQ;EAAW,QAAQ;EAA8B;AAEpE,KAAI,CAAC,wBAAwB,KAAK,WAAW,CAC3C,QAAO;EAAE,QAAQ;EAAW,QAAQ;EAA8B;CAGpE,MAAM,aAAa,eAAe,WAAW;AAC7C,KAAI,CAAC,WACH,QAAO;EAAE,QAAQ;EAAW,QAAQ;EAA8B;CAGpE,MAAM,UAAU,GAAG,aAAa,YAAY,QAAQ;CAEpD,MAAM,OAAO,MADA,WAAW,SAAS,OAAO,GAAG,KAAK,MAAM,KAAK,YAClC,QAAQ,CAAC,MAAM;CAExC,MAAM,MAAM,iBAAiB,KAAK;AAClC,KAAI,CAAC,IACH,QAAO;EACL,QAAQ;EACR,QAAQ;EACT;CAGH,MAAM,OAAO,KAAK,SAAS,YAAY,WAAW;AAClD,KAAI,kBAAkB,IAAI,CAAC,IAAI,WAAW,CACxC,QAAO;EAAE,QAAQ;EAAW;EAAM;CAGpC,MAAM,QAAQ,EAAE;CAGhB,MAAM,UAAU,GAAG,WAAW;CAI9B,MAAM,eAAe;EAAC;EAAc;EAAmB;EAAiB;CACxE,MAAM,UAAU,IACb,UAAU,CACV,MAAM,MAAM,aAAa,SAAS,EAAE,MAAM,CAAW,CAAC;AACzD,KAAI,CAAC,QACH,OAAM,KAAK,IAAI,QAAQ,IAAI,QAAQ,GAAG,CAAC;MAClC;EACL,MAAM,WAAW,QAAQ,OAAO,CAAC,MAAM;EACvC,MAAM,YAAY,QAAQ,YAAY,MAAM,WAAW,EAAE;EACzD,MAAM,SAAS,QAAQ,MAAM,YAAY,GAAG,SAAS;EAErD,MAAM,MADY,cAAc,MAAM,WAAW,KAAK,OAAO,GACrC,MAAM,WAAW;AACzC,QAAM,KAAK,QAAQ,QAAQ,GAAG,UAAU,MAAM,QAAQ,MAAM,GAAG,CAAC;;CAQlE,MAAM,cAAc,KAAK,QAAQ,EAAE,MAAM,EAAE,MAAM,oBAAoB,EAAE,CAAC;CACxE,MAAM,aAAa,YAAY,MAAM,MACnC,mBAAmB,EAAE,CAAC,IAAI,WAAW,CACtC;CACD,MAAM,aAAa,YAAY,WAAW,WAAW,WAAW;AAChE,KAAI,CAAC,cAAc,YAAY,SAAS,GAAG;EACzC,MAAM,OAAO,YAAY,YAAY,SAAS;AAC9C,QAAM,KAAK,KAAK,QAAQ,GAAG,KAAK,MAAM,CAAC,IAAI,aAAa,CAAC;;CAG3D,IAAI,SAAS,KAAK,YAAY,MAAM;AACpC,KAAI,CAAC,cAAc,YAAY,WAAW,EACxC,UAAS,GAAG,WAAW,IAAI;AAE7B,IAAG,cAAc,YAAY,OAAO;AAEpC,QAAO;EAAE,QAAQ;EAAS;EAAM"}
@@ -0,0 +1,353 @@
1
+ import { createWorkspaceClient } from "../../../workspace-client/factory.js";
2
+ import { spawnSync } from "node:child_process";
3
+
4
+ //#region src/cli/commands/registry/workspace-picker.ts
5
+ /** Builds a `{value,label}` from an id field and optional label field. */
6
+ function choiceFrom(item, idField, labelField) {
7
+ const id = item[idField];
8
+ if (id === void 0 || id === null) return null;
9
+ const value = String(id);
10
+ const rawLabel = labelField ? item[labelField] : void 0;
11
+ return {
12
+ value,
13
+ label: typeof rawLabel === "string" && rawLabel.length > 0 ? `${rawLabel} (${value})` : value
14
+ };
15
+ }
16
+ /**
17
+ * Genie listSpaces returns a single page, not an auto-paginating iterable like
18
+ * the other services. Adapt it to one that follows `next_page_token`; the
19
+ * repeated-token guard avoids an infinite loop if the API echoes a token back.
20
+ */
21
+ async function* iterateGenieSpaces(client) {
22
+ let pageToken;
23
+ do {
24
+ const res = await client.genie.listSpaces(pageToken ? { page_token: pageToken } : {});
25
+ for (const space of res.spaces ?? []) yield space;
26
+ const next = res.next_page_token;
27
+ if (next && next === pageToken) break;
28
+ pageToken = next;
29
+ } while (pageToken);
30
+ }
31
+ /** Flat, top-level listable resource types, backed by SDK services. */
32
+ const SDK_LISTERS = {
33
+ sql_warehouse: {
34
+ list: (c) => c.warehouses.list({}),
35
+ toChoice: (i) => choiceFrom(i, "id", "name")
36
+ },
37
+ job: {
38
+ list: (c) => c.jobs.list({}),
39
+ toChoice: (i) => {
40
+ const settings = i.settings;
41
+ return choiceFrom({
42
+ ...i,
43
+ name: settings?.name
44
+ }, "job_id", "name");
45
+ }
46
+ },
47
+ serving_endpoint: {
48
+ list: (c) => c.servingEndpoints.list(),
49
+ toChoice: (i) => choiceFrom(i, "name", "name")
50
+ },
51
+ uc_connection: {
52
+ list: (c) => c.connections.list({}),
53
+ toChoice: (i) => choiceFrom(i, "name", "full_name")
54
+ },
55
+ database: {
56
+ list: (c) => c.database.listDatabaseInstances({}),
57
+ toChoice: (i) => choiceFrom(i, "name", "name")
58
+ },
59
+ genie_space: {
60
+ list: iterateGenieSpaces,
61
+ toChoice: (i) => choiceFrom(i, "space_id", "title")
62
+ },
63
+ experiment: {
64
+ list: (c) => c.experiments.listExperiments({}),
65
+ toChoice: (i) => choiceFrom(i, "experiment_id", "name")
66
+ },
67
+ app: {
68
+ list: (c) => c.apps.list({}),
69
+ toChoice: (i) => choiceFrom(i, "name", "name")
70
+ }
71
+ };
72
+ /** True when a resource type can be listed flat (no parent context). */
73
+ function isFlatListable(resourceType) {
74
+ return resourceType in SDK_LISTERS;
75
+ }
76
+ /**
77
+ * Constructs a raw SDK workspace client for the given profile (or default
78
+ * resolution), via the sanctioned `workspace-client` facade. Uses the legacy
79
+ * escape hatch because the picker needs services (connections, database,
80
+ * experiments, apps) the facade doesn't yet proxy directly.
81
+ */
82
+ function makeWorkspaceClient(profile) {
83
+ return createWorkspaceClient(profile ? { profile } : {}).toLegacyWorkspaceClient();
84
+ }
85
+ /**
86
+ * Max resources fetched for the picker. `list()` auto-paginates, so on a large
87
+ * workspace (5000+ warehouses) breaking out at the cap stops pagination early;
88
+ * the picker's "Enter manually" option covers anything beyond it.
89
+ */
90
+ const MAX_PICKER_RESULTS = 200;
91
+ /** First line of an error, capped, for a user-facing one-liner. */
92
+ function shortErrorMessage(err) {
93
+ return (err instanceof Error ? err.message : String(err)).split("\n")[0].trim().slice(0, 200);
94
+ }
95
+ /**
96
+ * Lists workspace resources of a flat-listable type via the SDK, stopping at
97
+ * MAX_PICKER_RESULTS so pagination doesn't drain a huge workspace. Returns an
98
+ * empty listing on any failure (unknown type, auth/config error, network) so
99
+ * the caller can fall back to free-text entry. `clientFactory` is injectable
100
+ * for tests.
101
+ */
102
+ async function listWorkspaceResources(resourceType, profile, clientFactory = makeWorkspaceClient) {
103
+ const lister = SDK_LISTERS[resourceType];
104
+ if (!lister) return {
105
+ choices: [],
106
+ truncated: false
107
+ };
108
+ try {
109
+ const client = clientFactory(profile);
110
+ const choices = [];
111
+ let truncated = false;
112
+ for await (const item of lister.list(client)) {
113
+ if (typeof item !== "object" || item === null) continue;
114
+ const choice = lister.toChoice(item);
115
+ if (!choice) continue;
116
+ choices.push(choice);
117
+ if (choices.length >= MAX_PICKER_RESULTS) {
118
+ truncated = true;
119
+ break;
120
+ }
121
+ }
122
+ return {
123
+ choices,
124
+ truncated
125
+ };
126
+ } catch (err) {
127
+ return {
128
+ choices: [],
129
+ truncated: false,
130
+ error: shortErrorMessage(err)
131
+ };
132
+ }
133
+ }
134
+ const defaultRunner = (args) => {
135
+ const res = spawnSync("databricks", args, {
136
+ encoding: "utf-8",
137
+ maxBuffer: 64 * 1024 * 1024
138
+ });
139
+ return {
140
+ status: res.status,
141
+ stdout: res.stdout ?? ""
142
+ };
143
+ };
144
+ /**
145
+ * Extracts `{value,label}` choices from a parsed CLI list response. The CLI
146
+ * returns either a bare array or an object wrapping one; we scan for the first
147
+ * array of objects. Items missing the id field are skipped.
148
+ */
149
+ function toChoices(parsed, idField, labelField) {
150
+ const arr = firstArray(parsed);
151
+ const choices = [];
152
+ for (const item of arr) {
153
+ if (typeof item !== "object" || item === null) continue;
154
+ const choice = choiceFrom(item, idField, labelField);
155
+ if (choice) choices.push(choice);
156
+ }
157
+ return choices;
158
+ }
159
+ /** Finds the first array in a CLI response (bare array or single wrapper key). */
160
+ function firstArray(parsed) {
161
+ if (Array.isArray(parsed)) return parsed;
162
+ if (parsed && typeof parsed === "object") {
163
+ for (const v of Object.values(parsed)) if (Array.isArray(v)) return v;
164
+ }
165
+ return [];
166
+ }
167
+ /**
168
+ * Runs a `databricks … list -o json` command and returns parsed choices, or
169
+ * [] on any failure (CLI missing/errored, empty, non-JSON). `command` is the
170
+ * argv after `databricks`; `-o json` and `-p <profile>` are appended.
171
+ * Used for the parent-context drill-down (catalogs/schemas/scopes/endpoints).
172
+ */
173
+ function runList(command, idField, labelField, profile, runner = defaultRunner) {
174
+ const args = [
175
+ ...command,
176
+ "-o",
177
+ "json"
178
+ ];
179
+ if (profile) args.push("-p", profile);
180
+ let result;
181
+ try {
182
+ result = runner(args);
183
+ } catch {
184
+ return [];
185
+ }
186
+ if (result.status !== 0 || !result.stdout.trim()) return [];
187
+ let parsed;
188
+ try {
189
+ parsed = JSON.parse(result.stdout);
190
+ } catch {
191
+ return [];
192
+ }
193
+ return toChoices(parsed, idField, labelField);
194
+ }
195
+ /**
196
+ * Drill-down chains for parent-context resource types. Each ends by listing
197
+ * the resource itself; earlier steps list the parents to pick first.
198
+ * Positional-arg CLI gotcha: `databricks schemas list <CATALOG>` etc. take the
199
+ * parent as a positional, not a flag.
200
+ */
201
+ const PARENT_CONTEXT_CHAINS = {
202
+ volume: [
203
+ {
204
+ key: "catalog",
205
+ list: () => ({
206
+ command: ["catalogs", "list"],
207
+ idField: "name",
208
+ labelField: "name"
209
+ })
210
+ },
211
+ {
212
+ key: "schema",
213
+ list: ([catalog]) => ({
214
+ command: [
215
+ "schemas",
216
+ "list",
217
+ catalog
218
+ ],
219
+ idField: "name",
220
+ labelField: "name"
221
+ })
222
+ },
223
+ {
224
+ key: "volume",
225
+ list: ([catalog, schema]) => ({
226
+ command: [
227
+ "volumes",
228
+ "list",
229
+ catalog,
230
+ schema
231
+ ],
232
+ idField: "full_name",
233
+ labelField: "name"
234
+ })
235
+ }
236
+ ],
237
+ uc_function: [
238
+ {
239
+ key: "catalog",
240
+ list: () => ({
241
+ command: ["catalogs", "list"],
242
+ idField: "name",
243
+ labelField: "name"
244
+ })
245
+ },
246
+ {
247
+ key: "schema",
248
+ list: ([catalog]) => ({
249
+ command: [
250
+ "schemas",
251
+ "list",
252
+ catalog
253
+ ],
254
+ idField: "name",
255
+ labelField: "name"
256
+ })
257
+ },
258
+ {
259
+ key: "function",
260
+ list: ([catalog, schema]) => ({
261
+ command: [
262
+ "functions",
263
+ "list",
264
+ catalog,
265
+ schema
266
+ ],
267
+ idField: "full_name",
268
+ labelField: "name"
269
+ })
270
+ }
271
+ ],
272
+ secret: [{
273
+ key: "scope",
274
+ list: () => ({
275
+ command: ["secrets", "list-scopes"],
276
+ idField: "name",
277
+ labelField: "name"
278
+ })
279
+ }, {
280
+ key: "key",
281
+ list: ([scope]) => ({
282
+ command: [
283
+ "secrets",
284
+ "list-secrets",
285
+ scope
286
+ ],
287
+ idField: "key",
288
+ labelField: "key"
289
+ })
290
+ }],
291
+ vector_search_index: [{
292
+ key: "endpoint",
293
+ list: () => ({
294
+ command: ["vector-search-endpoints", "list-endpoints"],
295
+ idField: "name",
296
+ labelField: "name"
297
+ })
298
+ }, {
299
+ key: "index",
300
+ list: ([endpoint]) => ({
301
+ command: [
302
+ "vector-search-indexes",
303
+ "list-indexes",
304
+ endpoint
305
+ ],
306
+ idField: "name",
307
+ labelField: "name"
308
+ })
309
+ }]
310
+ };
311
+ /** True when a resource type needs a parent-context drill-down to list. */
312
+ function isParentContext(resourceType) {
313
+ return resourceType in PARENT_CONTEXT_CHAINS;
314
+ }
315
+ /**
316
+ * Lists the choices for a single drill-down step given the values picked so
317
+ * far. Returns [] on failure. The caller drives the interaction (present
318
+ * `choices`, collect a pick, call again with it appended to `parents`).
319
+ */
320
+ function listParentContextStep(resourceType, stepIndex, parents, profile, runner = defaultRunner) {
321
+ const chain = PARENT_CONTEXT_CHAINS[resourceType];
322
+ if (!chain || stepIndex >= chain.length) return null;
323
+ const step = chain[stepIndex];
324
+ if (parents.some((p) => p.startsWith("-"))) return {
325
+ key: step.key,
326
+ choices: []
327
+ };
328
+ const spec = step.list(parents);
329
+ return {
330
+ key: step.key,
331
+ choices: runList(spec.command, spec.idField, spec.labelField, profile, runner)
332
+ };
333
+ }
334
+ /** Number of drill-down steps for a parent-context type (0 if not one). */
335
+ function parentContextDepth(resourceType) {
336
+ return PARENT_CONTEXT_CHAINS[resourceType]?.length ?? 0;
337
+ }
338
+ /**
339
+ * Builds the final resource identifier from the values picked across a
340
+ * drill-down. Most types end on a self-qualified id (volume/uc_function list
341
+ * `full_name`; a vector-search index name is already catalog.schema-qualified),
342
+ * so the last pick is the whole answer. A `secret` is addressed by both its
343
+ * scope and key (`scope/key`) — returning only the key drops the scope and
344
+ * yields a value that can't locate the secret — so its picks are joined.
345
+ */
346
+ function composeResourceId(resourceType, picks) {
347
+ if (resourceType === "secret") return picks.join("/");
348
+ return picks[picks.length - 1];
349
+ }
350
+
351
+ //#endregion
352
+ export { composeResourceId, isFlatListable, isParentContext, listParentContextStep, listWorkspaceResources, parentContextDepth };
353
+ //# sourceMappingURL=workspace-picker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-picker.js","names":[],"sources":["../../../../src/cli/commands/registry/workspace-picker.ts"],"sourcesContent":["import { spawnSync } from \"node:child_process\";\nimport {\n createWorkspaceClient,\n type LegacyWorkspaceClient,\n} from \"../../../workspace-client\";\n\n/**\n * Lists a user's real Databricks workspace resources so `appkit add` can offer\n * a picker instead of blind free-text entry.\n *\n * Flat resource types are listed via the Databricks SDK client (typed,\n * auto-paginating) obtained through the sanctioned `workspace-client` facade.\n * Parent-context types (volume, uc_function, secret, vector_search_index) still\n * shell out to the `databricks` CLI for their drill-down. Every path fails\n * soft: any error returns an empty list and the caller drops to free-text entry.\n */\n\n/** A workspace resource choice surfaced in the picker. */\nexport interface WorkspaceChoice {\n /** Value written to the env var (the resource id/name). */\n value: string;\n /** Human label shown in the picker (name, falling back to value). */\n label: string;\n}\n\n/**\n * Per-type SDK lister: streams a resource type off the WorkspaceClient into\n * `{value,label}` choices. `list()` returns an async iterable of raw SDK\n * objects; `toChoice` maps each to a picker choice. The SDK auto-paginates, so\n * we simply iterate to completion.\n */\ninterface SdkLister {\n list: (client: LegacyWorkspaceClient) => AsyncIterable<unknown>;\n toChoice: (item: Record<string, unknown>) => WorkspaceChoice | null;\n}\n\n/** Builds a `{value,label}` from an id field and optional label field. */\nfunction choiceFrom(\n item: Record<string, unknown>,\n idField: string,\n labelField?: string,\n): WorkspaceChoice | null {\n const id = item[idField];\n if (id === undefined || id === null) return null;\n const value = String(id);\n const rawLabel = labelField ? item[labelField] : undefined;\n const label =\n typeof rawLabel === \"string\" && rawLabel.length > 0\n ? `${rawLabel} (${value})`\n : value;\n return { value, label };\n}\n\n/**\n * Genie listSpaces returns a single page, not an auto-paginating iterable like\n * the other services. Adapt it to one that follows `next_page_token`; the\n * repeated-token guard avoids an infinite loop if the API echoes a token back.\n */\nasync function* iterateGenieSpaces(\n client: LegacyWorkspaceClient,\n): AsyncIterable<unknown> {\n let pageToken: string | undefined;\n do {\n const res = await client.genie.listSpaces(\n pageToken ? { page_token: pageToken } : {},\n );\n for (const space of res.spaces ?? []) yield space;\n const next = res.next_page_token;\n if (next && next === pageToken) break;\n pageToken = next;\n } while (pageToken);\n}\n\n/** Flat, top-level listable resource types, backed by SDK services. */\nexport const SDK_LISTERS: Record<string, SdkLister> = {\n sql_warehouse: {\n list: (c) => c.warehouses.list({}),\n toChoice: (i) => choiceFrom(i, \"id\", \"name\"),\n },\n job: {\n list: (c) => c.jobs.list({}),\n // job name lives under settings.name; id is top-level job_id\n toChoice: (i) => {\n const settings = i.settings as { name?: string } | undefined;\n return choiceFrom({ ...i, name: settings?.name }, \"job_id\", \"name\");\n },\n },\n serving_endpoint: {\n list: (c) => c.servingEndpoints.list(),\n toChoice: (i) => choiceFrom(i, \"name\", \"name\"),\n },\n uc_connection: {\n list: (c) => c.connections.list({}),\n toChoice: (i) => choiceFrom(i, \"name\", \"full_name\"),\n },\n database: {\n list: (c) => c.database.listDatabaseInstances({}),\n toChoice: (i) => choiceFrom(i, \"name\", \"name\"),\n },\n genie_space: {\n list: iterateGenieSpaces,\n toChoice: (i) => choiceFrom(i, \"space_id\", \"title\"),\n },\n experiment: {\n list: (c) => c.experiments.listExperiments({}),\n toChoice: (i) => choiceFrom(i, \"experiment_id\", \"name\"),\n },\n app: {\n list: (c) => c.apps.list({}),\n toChoice: (i) => choiceFrom(i, \"name\", \"name\"),\n },\n};\n\n/** True when a resource type can be listed flat (no parent context). */\nexport function isFlatListable(resourceType: string): boolean {\n return resourceType in SDK_LISTERS;\n}\n\n/**\n * Constructs a raw SDK workspace client for the given profile (or default\n * resolution), via the sanctioned `workspace-client` facade. Uses the legacy\n * escape hatch because the picker needs services (connections, database,\n * experiments, apps) the facade doesn't yet proxy directly.\n */\nexport function makeWorkspaceClient(profile?: string): LegacyWorkspaceClient {\n return createWorkspaceClient(\n profile ? { profile } : {},\n ).toLegacyWorkspaceClient();\n}\n\n/**\n * Max resources fetched for the picker. `list()` auto-paginates, so on a large\n * workspace (5000+ warehouses) breaking out at the cap stops pagination early;\n * the picker's \"Enter manually\" option covers anything beyond it.\n */\nexport const MAX_PICKER_RESULTS = 200;\n\n/** A listing result plus whether it was truncated at the fetch cap. */\nexport interface WorkspaceListing {\n choices: WorkspaceChoice[];\n truncated: boolean;\n /** Short reason the listing failed (auth/config/network), if it did — lets\n * the caller distinguish a real error from a genuinely empty workspace. */\n error?: string;\n}\n\n/** First line of an error, capped, for a user-facing one-liner. */\nfunction shortErrorMessage(err: unknown): string {\n const msg = err instanceof Error ? err.message : String(err);\n return msg.split(\"\\n\")[0].trim().slice(0, 200);\n}\n\n/**\n * Lists workspace resources of a flat-listable type via the SDK, stopping at\n * MAX_PICKER_RESULTS so pagination doesn't drain a huge workspace. Returns an\n * empty listing on any failure (unknown type, auth/config error, network) so\n * the caller can fall back to free-text entry. `clientFactory` is injectable\n * for tests.\n */\nexport async function listWorkspaceResources(\n resourceType: string,\n profile?: string,\n clientFactory: (\n profile?: string,\n ) => LegacyWorkspaceClient = makeWorkspaceClient,\n): Promise<WorkspaceListing> {\n const lister = SDK_LISTERS[resourceType];\n if (!lister) return { choices: [], truncated: false };\n try {\n const client = clientFactory(profile);\n const choices: WorkspaceChoice[] = [];\n let truncated = false;\n for await (const item of lister.list(client)) {\n if (typeof item !== \"object\" || item === null) continue;\n const choice = lister.toChoice(item as Record<string, unknown>);\n if (!choice) continue;\n choices.push(choice);\n if (choices.length >= MAX_PICKER_RESULTS) {\n // Stop iterating — halts the async iterator, so no further pages fetch.\n truncated = true;\n break;\n }\n }\n return { choices, truncated };\n } catch (err) {\n // Don't swallow silently: return the reason so the caller can tell the user\n // the picker fell back because listing failed (typically auth/profile), not\n // because the workspace has none of this resource.\n return { choices: [], truncated: false, error: shortErrorMessage(err) };\n }\n}\n\n/** Runs a databricks CLI subcommand returning JSON; injectable for tests. */\nexport type CliRunner = (args: string[]) => {\n status: number | null;\n stdout: string;\n};\n\nconst defaultRunner: CliRunner = (args) => {\n // Parent-context lists can be large; raise maxBuffer well above the 1MB\n // default so a big JSON response isn't truncated into \"no resources found\".\n const res = spawnSync(\"databricks\", args, {\n encoding: \"utf-8\",\n maxBuffer: 64 * 1024 * 1024,\n });\n return { status: res.status, stdout: res.stdout ?? \"\" };\n};\n\n/**\n * Extracts `{value,label}` choices from a parsed CLI list response. The CLI\n * returns either a bare array or an object wrapping one; we scan for the first\n * array of objects. Items missing the id field are skipped.\n */\nexport function toChoices(\n parsed: unknown,\n idField: string,\n labelField?: string,\n): WorkspaceChoice[] {\n const arr = firstArray(parsed);\n const choices: WorkspaceChoice[] = [];\n for (const item of arr) {\n if (typeof item !== \"object\" || item === null) continue;\n const choice = choiceFrom(\n item as Record<string, unknown>,\n idField,\n labelField,\n );\n if (choice) choices.push(choice);\n }\n return choices;\n}\n\n/** Finds the first array in a CLI response (bare array or single wrapper key). */\nfunction firstArray(parsed: unknown): unknown[] {\n if (Array.isArray(parsed)) return parsed;\n if (parsed && typeof parsed === \"object\") {\n for (const v of Object.values(parsed)) {\n if (Array.isArray(v)) return v;\n }\n }\n return [];\n}\n\n/**\n * Runs a `databricks … list -o json` command and returns parsed choices, or\n * [] on any failure (CLI missing/errored, empty, non-JSON). `command` is the\n * argv after `databricks`; `-o json` and `-p <profile>` are appended.\n * Used for the parent-context drill-down (catalogs/schemas/scopes/endpoints).\n */\nexport function runList(\n command: string[],\n idField: string,\n labelField: string | undefined,\n profile: string | undefined,\n runner: CliRunner = defaultRunner,\n): WorkspaceChoice[] {\n const args = [...command, \"-o\", \"json\"];\n if (profile) args.push(\"-p\", profile);\n\n let result: { status: number | null; stdout: string };\n try {\n result = runner(args);\n } catch {\n return [];\n }\n if (result.status !== 0 || !result.stdout.trim()) return [];\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(result.stdout);\n } catch {\n return [];\n }\n return toChoices(parsed, idField, labelField);\n}\n\n/**\n * A drill-down step for a parent-context resource type. `list(parents)` builds\n * the CLI argv given the values picked in prior steps (e.g. [catalog] → schema\n * list command). `key` labels the step for prompts.\n */\nexport interface ParentContextStep {\n key: string;\n list: (parents: string[]) => {\n command: string[];\n idField: string;\n labelField?: string;\n };\n}\n\n/**\n * Drill-down chains for parent-context resource types. Each ends by listing\n * the resource itself; earlier steps list the parents to pick first.\n * Positional-arg CLI gotcha: `databricks schemas list <CATALOG>` etc. take the\n * parent as a positional, not a flag.\n */\nexport const PARENT_CONTEXT_CHAINS: Record<string, ParentContextStep[]> = {\n volume: [\n {\n key: \"catalog\",\n list: () => ({\n command: [\"catalogs\", \"list\"],\n idField: \"name\",\n labelField: \"name\",\n }),\n },\n {\n key: \"schema\",\n list: ([catalog]) => ({\n command: [\"schemas\", \"list\", catalog],\n idField: \"name\",\n labelField: \"name\",\n }),\n },\n {\n key: \"volume\",\n list: ([catalog, schema]) => ({\n command: [\"volumes\", \"list\", catalog, schema],\n idField: \"full_name\",\n labelField: \"name\",\n }),\n },\n ],\n uc_function: [\n {\n key: \"catalog\",\n list: () => ({\n command: [\"catalogs\", \"list\"],\n idField: \"name\",\n labelField: \"name\",\n }),\n },\n {\n key: \"schema\",\n list: ([catalog]) => ({\n command: [\"schemas\", \"list\", catalog],\n idField: \"name\",\n labelField: \"name\",\n }),\n },\n {\n key: \"function\",\n list: ([catalog, schema]) => ({\n command: [\"functions\", \"list\", catalog, schema],\n idField: \"full_name\",\n labelField: \"name\",\n }),\n },\n ],\n secret: [\n {\n key: \"scope\",\n list: () => ({\n command: [\"secrets\", \"list-scopes\"],\n idField: \"name\",\n labelField: \"name\",\n }),\n },\n {\n key: \"key\",\n list: ([scope]) => ({\n command: [\"secrets\", \"list-secrets\", scope],\n idField: \"key\",\n labelField: \"key\",\n }),\n },\n ],\n vector_search_index: [\n {\n key: \"endpoint\",\n list: () => ({\n command: [\"vector-search-endpoints\", \"list-endpoints\"],\n idField: \"name\",\n labelField: \"name\",\n }),\n },\n {\n key: \"index\",\n list: ([endpoint]) => ({\n command: [\"vector-search-indexes\", \"list-indexes\", endpoint],\n idField: \"name\",\n labelField: \"name\",\n }),\n },\n ],\n};\n\n/** True when a resource type needs a parent-context drill-down to list. */\nexport function isParentContext(resourceType: string): boolean {\n return resourceType in PARENT_CONTEXT_CHAINS;\n}\n\n/** One resolved step of a drill-down: the choices to present at this level. */\nexport interface DrillStep {\n key: string;\n choices: WorkspaceChoice[];\n}\n\n/**\n * Lists the choices for a single drill-down step given the values picked so\n * far. Returns [] on failure. The caller drives the interaction (present\n * `choices`, collect a pick, call again with it appended to `parents`).\n */\nexport function listParentContextStep(\n resourceType: string,\n stepIndex: number,\n parents: string[],\n profile?: string,\n runner: CliRunner = defaultRunner,\n): DrillStep | null {\n const chain = PARENT_CONTEXT_CHAINS[resourceType];\n if (!chain || stepIndex >= chain.length) return null;\n const step = chain[stepIndex];\n // Prior picks become positional CLI args; a value starting with `-` would be\n // parsed as a flag, so refuse it (empty step → free-text fallback). Real\n // catalog/schema/scope/endpoint names never start with `-`.\n if (parents.some((p) => p.startsWith(\"-\"))) {\n return { key: step.key, choices: [] };\n }\n const spec = step.list(parents);\n return {\n key: step.key,\n choices: runList(\n spec.command,\n spec.idField,\n spec.labelField,\n profile,\n runner,\n ),\n };\n}\n\n/** Number of drill-down steps for a parent-context type (0 if not one). */\nexport function parentContextDepth(resourceType: string): number {\n return PARENT_CONTEXT_CHAINS[resourceType]?.length ?? 0;\n}\n\n/**\n * Builds the final resource identifier from the values picked across a\n * drill-down. Most types end on a self-qualified id (volume/uc_function list\n * `full_name`; a vector-search index name is already catalog.schema-qualified),\n * so the last pick is the whole answer. A `secret` is addressed by both its\n * scope and key (`scope/key`) — returning only the key drops the scope and\n * yields a value that can't locate the secret — so its picks are joined.\n */\nexport function composeResourceId(\n resourceType: string,\n picks: string[],\n): string {\n if (resourceType === \"secret\") return picks.join(\"/\");\n return picks[picks.length - 1];\n}\n"],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ //#region src/cli/deploy-config.ts
2
+ /**
3
+ * Single source for the `app.yaml` + `databricks.yml` app-resource binding
4
+ * contract, so the registry writer (`config-writer.ts`) and the doctor reader
5
+ * (`bundle.ts`/`checks-wiring.ts`) on opposite ends of it can't drift apart.
6
+ */
7
+ /** Canonical Databricks bundle config file name. */
8
+ const DATABRICKS_YML_FILE = "databricks.yml";
9
+ /** Canonical Databricks Apps runtime config file name. */
10
+ const APP_YAML_FILE = "app.yaml";
11
+ /**
12
+ * Encodes a binding into its `databricks.yml` node: the type is the single
13
+ * non-`name` object key. Inverse of {@link bindingTypeOf}.
14
+ */
15
+ function bindingToNode(binding) {
16
+ const inner = { ...binding.fields };
17
+ if (binding.permission) inner.permission = binding.permission;
18
+ return {
19
+ name: binding.name,
20
+ [binding.type]: inner
21
+ };
22
+ }
23
+ /**
24
+ * Reads the type back out of a `databricks.yml` binding node — the single
25
+ * non-`name` object-valued property; undefined if absent. See {@link bindingToNode}.
26
+ */
27
+ function bindingTypeOf(block) {
28
+ for (const [k, v] of Object.entries(block)) {
29
+ if (k === "name") continue;
30
+ if (v && typeof v === "object") return k;
31
+ }
32
+ }
33
+
34
+ //#endregion
35
+ export { APP_YAML_FILE, DATABRICKS_YML_FILE, bindingToNode, bindingTypeOf };
36
+ //# sourceMappingURL=deploy-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deploy-config.js","names":[],"sources":["../../src/cli/deploy-config.ts"],"sourcesContent":["/**\n * Single source for the `app.yaml` + `databricks.yml` app-resource binding\n * contract, so the registry writer (`config-writer.ts`) and the doctor reader\n * (`bundle.ts`/`checks-wiring.ts`) on opposite ends of it can't drift apart.\n */\n\n/** Canonical Databricks bundle config file name. */\nexport const DATABRICKS_YML_FILE = \"databricks.yml\";\n\n/** Canonical Databricks Apps runtime config file name. */\nexport const APP_YAML_FILE = \"app.yaml\";\n\n/** An `app.yaml` env entry: `- name: <env>`, `valueFrom: <binding name>`. */\nexport interface AppYamlEnvEntry {\n name: string;\n valueFrom: string;\n}\n\n/**\n * A `databricks.yml` app-resource binding under `resources.apps.<app>.resources[]`,\n * serialized as `{ name, <type>: { ...fields, permission? } }` (see {@link bindingToNode}).\n */\nexport interface ResourceBinding {\n /** Binding name — the `valueFrom` join key (equals the resourceKey). */\n name: string;\n /** Resource type key, e.g. `sql_warehouse` / `postgres`. */\n type: string;\n permission?: string;\n /** Binding fields, typically `${var.<variable>}` references. */\n fields: Record<string, string>;\n}\n\n/**\n * Encodes a binding into its `databricks.yml` node: the type is the single\n * non-`name` object key. Inverse of {@link bindingTypeOf}.\n */\nexport function bindingToNode(\n binding: ResourceBinding,\n): Record<string, unknown> {\n const inner: Record<string, unknown> = { ...binding.fields };\n if (binding.permission) inner.permission = binding.permission;\n return { name: binding.name, [binding.type]: inner };\n}\n\n/**\n * Reads the type back out of a `databricks.yml` binding node — the single\n * non-`name` object-valued property; undefined if absent. See {@link bindingToNode}.\n */\nexport function bindingTypeOf(\n block: Record<string, unknown>,\n): string | undefined {\n for (const [k, v] of Object.entries(block)) {\n if (k === \"name\") continue;\n if (v && typeof v === \"object\") return k;\n }\n return undefined;\n}\n"],"mappings":";;;;;;;AAOA,MAAa,sBAAsB;;AAGnC,MAAa,gBAAgB;;;;;AA0B7B,SAAgB,cACd,SACyB;CACzB,MAAM,QAAiC,EAAE,GAAG,QAAQ,QAAQ;AAC5D,KAAI,QAAQ,WAAY,OAAM,aAAa,QAAQ;AACnD,QAAO;EAAE,MAAM,QAAQ;GAAO,QAAQ,OAAO;EAAO;;;;;;AAOtD,SAAgB,cACd,OACoB;AACpB,MAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,MAAM,EAAE;AAC1C,MAAI,MAAM,OAAQ;AAClB,MAAI,KAAK,OAAO,MAAM,SAAU,QAAO"}
package/dist/cli/index.js CHANGED
@@ -5,6 +5,8 @@ import { doctorCommand } from "./commands/doctor/index.js";
5
5
  import { generateTypesCommand } from "./commands/generate-types.js";
6
6
  import { lintCommand } from "./commands/lint.js";
7
7
  import { pluginCommand } from "./commands/plugin/index.js";
8
+ import { addCommand } from "./commands/registry/add.js";
9
+ import { registryCommand } from "./commands/registry/index.js";
8
10
  import { setupCommand } from "./commands/setup.js";
9
11
  import "dotenv/config";
10
12
  import { readFileSync } from "node:fs";
@@ -24,6 +26,8 @@ cmd.addCommand(docsCommand);
24
26
  cmd.addCommand(pluginCommand);
25
27
  cmd.addCommand(codemodCommand);
26
28
  cmd.addCommand(doctorCommand);
29
+ cmd.addCommand(registryCommand, { hidden: true });
30
+ cmd.addCommand(addCommand, { hidden: true });
27
31
  await cmd.parseAsync();
28
32
 
29
33
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node\nimport \"dotenv/config\";\nimport { readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { Command } from \"commander\";\nimport { codemodCommand } from \"./commands/codemod/index.js\";\nimport { docsCommand } from \"./commands/docs.js\";\nimport { doctorCommand } from \"./commands/doctor/index.js\";\nimport { generateTypesCommand } from \"./commands/generate-types.js\";\nimport { lintCommand } from \"./commands/lint.js\";\nimport { pluginCommand } from \"./commands/plugin/index.js\";\nimport { setupCommand } from \"./commands/setup.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst pkgPath = join(__dirname, \"../../package.json\");\nconst pkg = JSON.parse(readFileSync(pkgPath, \"utf-8\"));\n\nconst cmd = new Command();\n\ncmd\n .name(\"appkit\")\n .description(\"CLI tools for Databricks AppKit\")\n .version(pkg.version);\n\ncmd.addCommand(setupCommand);\ncmd.addCommand(generateTypesCommand);\ncmd.addCommand(lintCommand);\ncmd.addCommand(docsCommand);\ncmd.addCommand(pluginCommand);\ncmd.addCommand(codemodCommand);\ncmd.addCommand(doctorCommand);\n\nawait cmd.parseAsync();\n"],"mappings":";;;;;;;;;;;;;;;AAeA,MAAM,UAAU,KADE,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC,EACzB,qBAAqB;AACrD,MAAM,MAAM,KAAK,MAAM,aAAa,SAAS,QAAQ,CAAC;AAEtD,MAAM,MAAM,IAAI,SAAS;AAEzB,IACG,KAAK,SAAS,CACd,YAAY,kCAAkC,CAC9C,QAAQ,IAAI,QAAQ;AAEvB,IAAI,WAAW,aAAa;AAC5B,IAAI,WAAW,qBAAqB;AACpC,IAAI,WAAW,YAAY;AAC3B,IAAI,WAAW,YAAY;AAC3B,IAAI,WAAW,cAAc;AAC7B,IAAI,WAAW,eAAe;AAC9B,IAAI,WAAW,cAAc;AAE7B,MAAM,IAAI,YAAY"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node\nimport \"dotenv/config\";\nimport { readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { Command } from \"commander\";\nimport { codemodCommand } from \"./commands/codemod/index.js\";\nimport { docsCommand } from \"./commands/docs.js\";\nimport { doctorCommand } from \"./commands/doctor/index.js\";\nimport { generateTypesCommand } from \"./commands/generate-types.js\";\nimport { lintCommand } from \"./commands/lint.js\";\nimport { pluginCommand } from \"./commands/plugin/index.js\";\nimport { addCommand } from \"./commands/registry/add.js\";\nimport { registryCommand } from \"./commands/registry/index.js\";\nimport { setupCommand } from \"./commands/setup.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst pkgPath = join(__dirname, \"../../package.json\");\nconst pkg = JSON.parse(readFileSync(pkgPath, \"utf-8\"));\n\nconst cmd = new Command();\n\ncmd\n .name(\"appkit\")\n .description(\"CLI tools for Databricks AppKit\")\n .version(pkg.version);\n\ncmd.addCommand(setupCommand);\ncmd.addCommand(generateTypesCommand);\ncmd.addCommand(lintCommand);\ncmd.addCommand(docsCommand);\ncmd.addCommand(pluginCommand);\ncmd.addCommand(codemodCommand);\ncmd.addCommand(doctorCommand);\n// Registry commands are executable but hidden from --help while the feature\n// is still in development (registry + add work end-to-end but aren't announced).\ncmd.addCommand(registryCommand, { hidden: true });\ncmd.addCommand(addCommand, { hidden: true });\n\nawait cmd.parseAsync();\n"],"mappings":";;;;;;;;;;;;;;;;;AAiBA,MAAM,UAAU,KADE,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC,EACzB,qBAAqB;AACrD,MAAM,MAAM,KAAK,MAAM,aAAa,SAAS,QAAQ,CAAC;AAEtD,MAAM,MAAM,IAAI,SAAS;AAEzB,IACG,KAAK,SAAS,CACd,YAAY,kCAAkC,CAC9C,QAAQ,IAAI,QAAQ;AAEvB,IAAI,WAAW,aAAa;AAC5B,IAAI,WAAW,qBAAqB;AACpC,IAAI,WAAW,YAAY;AAC3B,IAAI,WAAW,YAAY;AAC3B,IAAI,WAAW,cAAc;AAC7B,IAAI,WAAW,eAAe;AAC9B,IAAI,WAAW,cAAc;AAG7B,IAAI,WAAW,iBAAiB,EAAE,QAAQ,MAAM,CAAC;AACjD,IAAI,WAAW,YAAY,EAAE,QAAQ,MAAM,CAAC;AAE5C,MAAM,IAAI,YAAY"}
@@ -1,5 +1,4 @@
1
- import { Context } from "../workspace-client/legacy.js";
2
- import "../workspace-client/index.js";
1
+ import { Context } from "../shared/src/workspace-client/legacy.js";
3
2
 
4
3
  //#region src/connectors/context.ts
5
4
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"context.js","names":[],"sources":["../../src/connectors/context.ts"],"sourcesContent":["import { type CancellationToken, Context } from \"../workspace-client\";\n\n/**\n * Bridges {@link AbortSignal} to the SDK's {@link CancellationToken} so\n * `apiClient.request` can abort the outbound HTTP request (and stop pulling a\n * response body) when the caller aborts.\n */\nfunction cancellationTokenFromAbortSignal(\n signal: AbortSignal,\n): CancellationToken {\n const listeners = new Set<() => void>();\n signal.addEventListener(\n \"abort\",\n () => {\n for (const cb of listeners) {\n try {\n cb();\n } catch {\n // ignore listener failures — abort must stay best-effort\n }\n }\n },\n { passive: true },\n );\n\n return {\n get isCancellationRequested() {\n return signal.aborted;\n },\n onCancellationRequested(callback: (e?: unknown) => unknown) {\n listeners.add(callback as () => void);\n if (signal.aborted) {\n void callback();\n }\n },\n };\n}\n\n/**\n * Wraps an optional {@link AbortSignal} in an SDK {@link Context} for the\n * second argument of `apiClient.request`. Returns `undefined` when no signal\n * is given, so callers can pass the result straight through.\n */\nexport function contextFromAbortSignal(\n signal?: AbortSignal,\n): InstanceType<typeof Context> | undefined {\n return signal\n ? new Context({\n cancellationToken: cancellationTokenFromAbortSignal(signal),\n })\n : undefined;\n}\n"],"mappings":";;;;;;;;;AAOA,SAAS,iCACP,QACmB;CACnB,MAAM,4BAAY,IAAI,KAAiB;AACvC,QAAO,iBACL,eACM;AACJ,OAAK,MAAM,MAAM,UACf,KAAI;AACF,OAAI;UACE;IAKZ,EAAE,SAAS,MAAM,CAClB;AAED,QAAO;EACL,IAAI,0BAA0B;AAC5B,UAAO,OAAO;;EAEhB,wBAAwB,UAAoC;AAC1D,aAAU,IAAI,SAAuB;AACrC,OAAI,OAAO,QACT,CAAK,UAAU;;EAGpB;;;;;;;AAQH,SAAgB,uBACd,QAC0C;AAC1C,QAAO,SACH,IAAI,QAAQ,EACV,mBAAmB,iCAAiC,OAAO,EAC5D,CAAC,GACF"}
1
+ {"version":3,"file":"context.js","names":[],"sources":["../../src/connectors/context.ts"],"sourcesContent":["import { type CancellationToken, Context } from \"../workspace-client\";\n\n/**\n * Bridges {@link AbortSignal} to the SDK's {@link CancellationToken} so\n * `apiClient.request` can abort the outbound HTTP request (and stop pulling a\n * response body) when the caller aborts.\n */\nfunction cancellationTokenFromAbortSignal(\n signal: AbortSignal,\n): CancellationToken {\n const listeners = new Set<() => void>();\n signal.addEventListener(\n \"abort\",\n () => {\n for (const cb of listeners) {\n try {\n cb();\n } catch {\n // ignore listener failures — abort must stay best-effort\n }\n }\n },\n { passive: true },\n );\n\n return {\n get isCancellationRequested() {\n return signal.aborted;\n },\n onCancellationRequested(callback: (e?: unknown) => unknown) {\n listeners.add(callback as () => void);\n if (signal.aborted) {\n void callback();\n }\n },\n };\n}\n\n/**\n * Wraps an optional {@link AbortSignal} in an SDK {@link Context} for the\n * second argument of `apiClient.request`. Returns `undefined` when no signal\n * is given, so callers can pass the result straight through.\n */\nexport function contextFromAbortSignal(\n signal?: AbortSignal,\n): InstanceType<typeof Context> | undefined {\n return signal\n ? new Context({\n cancellationToken: cancellationTokenFromAbortSignal(signal),\n })\n : undefined;\n}\n"],"mappings":";;;;;;;;AAOA,SAAS,iCACP,QACmB;CACnB,MAAM,4BAAY,IAAI,KAAiB;AACvC,QAAO,iBACL,eACM;AACJ,OAAK,MAAM,MAAM,UACf,KAAI;AACF,OAAI;UACE;IAKZ,EAAE,SAAS,MAAM,CAClB;AAED,QAAO;EACL,IAAI,0BAA0B;AAC5B,UAAO,OAAO;;EAEhB,wBAAwB,UAAoC;AAC1D,aAAU,IAAI,SAAuB;AACrC,OAAI,OAAO,QACT,CAAK,UAAU;;EAGpB;;;;;;;AAQH,SAAgB,uBACd,QAC0C;AAC1C,QAAO,SACH,IAAI,QAAQ,EACV,mBAAmB,iCAAiC,OAAO,EAC5D,CAAC,GACF"}
@@ -1,6 +1,5 @@
1
+ import { ApiError } from "../../shared/src/workspace-client/errors.js";
1
2
  import { createLogger } from "../../logging/logger.js";
2
- import { ApiError } from "../../workspace-client/errors.js";
3
- import "../../workspace-client/index.js";
4
3
  import { TelemetryManager } from "../../telemetry/telemetry-manager.js";
5
4
  import { SpanKind, SpanStatusCode } from "../../telemetry/index.js";
6
5
  import { FILES_MAX_READ_SIZE, contentTypeFromPath, isTextContentType } from "./defaults.js";