@databricks/appkit-ui 0.37.0 → 0.38.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 (45) hide show
  1. package/CLAUDE.md +3 -2
  2. package/dist/cli/commands/plugin/add-resource/add-resource.js.map +1 -1
  3. package/dist/cli/commands/plugin/create/resource-defaults.js +2 -1
  4. package/dist/cli/commands/plugin/create/resource-defaults.js.map +1 -1
  5. package/dist/cli/commands/plugin/schema-resources.js +34 -51
  6. package/dist/cli/commands/plugin/schema-resources.js.map +1 -1
  7. package/dist/cli/commands/plugin/sync/sync.js +20 -6
  8. package/dist/cli/commands/plugin/sync/sync.js.map +1 -1
  9. package/dist/cli/commands/plugin/validate/validate-manifest.js +71 -157
  10. package/dist/cli/commands/plugin/validate/validate-manifest.js.map +1 -1
  11. package/dist/cli/commands/plugin/validate/validate.js +2 -2
  12. package/dist/cli/commands/plugin/validate/validate.js.map +1 -1
  13. package/dist/cli/commands/setup.js +2 -2
  14. package/dist/cli/commands/setup.js.map +1 -1
  15. package/dist/schemas/manifest.d.ts +1139 -0
  16. package/dist/schemas/manifest.d.ts.map +1 -0
  17. package/dist/schemas/manifest.js +524 -0
  18. package/dist/schemas/manifest.js.map +1 -0
  19. package/dist/shared/src/plugin.d.ts +1 -0
  20. package/dist/shared/src/plugin.d.ts.map +1 -1
  21. package/dist/shared/src/schemas/manifest.d.ts +1 -0
  22. package/docs/api/appkit/Enumeration.ResourceType.md +1 -1
  23. package/docs/api/appkit/Interface.PluginManifest.md +57 -3
  24. package/docs/api/appkit/Interface.ResourceEntry.md +6 -4
  25. package/docs/api/appkit/Interface.ResourceRequirement.md +7 -61
  26. package/docs/api/appkit/TypeAlias.ResourceFieldEntry.md +6 -0
  27. package/docs/api/appkit.md +6 -6
  28. package/docs/app-management.md +1 -1
  29. package/docs/development/ai-assisted-development.md +2 -2
  30. package/docs/development/local-development.md +1 -1
  31. package/docs/development/remote-bridge.md +1 -1
  32. package/docs/development/templates.md +118 -12
  33. package/docs/development.md +1 -1
  34. package/docs/plugins/custom-plugins.md +33 -23
  35. package/docs/plugins/lakebase.md +1 -1
  36. package/docs/plugins/manifest.md +293 -0
  37. package/docs.md +2 -2
  38. package/llms.txt +3 -2
  39. package/package.json +5 -4
  40. package/sbom.cdx.json +1 -1
  41. package/dist/schemas/plugin-manifest.generated.d.ts +0 -182
  42. package/dist/schemas/plugin-manifest.generated.d.ts.map +0 -1
  43. package/dist/schemas/plugin-manifest.schema.json +0 -489
  44. package/dist/schemas/template-plugins.schema.json +0 -113
  45. package/docs/api/appkit/Interface.ResourceFieldEntry.md +0 -82
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","names":[],"sources":["../../src/schemas/manifest.ts"],"mappings":";;;cAsPa,yBAAA,EAAyB,CAAA,CAAA,qBAAA,EAAA,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;cA8FzB,wBAAA,EAAwB,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAkQxB,4BAAA,EAA4B,CAAA,CAAA,SAAA;;;;;cAoE5B,oBAAA,EAAoB,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA8frB,mBAAA,GAAsB,CAAA,CAAE,KAAA,QAAa,yBAAA;AAAA,KACrC,kBAAA,GAAqB,CAAA,CAAE,KAAA,QAAa,wBAAA;AAAA,KAIpC,sBAAA,GAAyB,CAAA,CAAE,KAAA,QAC9B,4BAAA;AAAA,KAEG,cAAA,GAAiB,CAAA,CAAE,KAAA,QAAa,oBAAA"}
@@ -0,0 +1,524 @@
1
+ import { z } from "zod";
2
+
3
+ //#region src/schemas/manifest.ts
4
+ /**
5
+ * Zod-authoring module for AppKit plugin manifest schemas.
6
+ *
7
+ * Single source of truth for the plugin manifest contract. JSON Schema
8
+ * artifacts published at the docs URL are emitted from these schemas via
9
+ * `tools/generate-json-schema.ts` and live only in `docs/static/schemas/`
10
+ * (no package-internal copies).
11
+ *
12
+ * - Cross-field constraints (cycle/dangling-reference checks, `<PROFILE>`
13
+ * placeholder, post-scaffold instruction non-empty) are refinements
14
+ * co-located with the shape they constrain. Validation is driven through
15
+ * the Standard Schema interface from `validate-manifest.ts`.
16
+ * - `templateFieldEntrySchema` is a transform that emits `origin` from
17
+ * `localOnly`/`value`/`resolve`. The input slot is still allowed so
18
+ * re-parsing previously-synced template manifests does not fail, but the
19
+ * transform always overwrites it — drift-by-construction for hand-edits.
20
+ * - `discoveryDescriptorSchema` is a discriminated union over a `type`
21
+ * literal. The `kind` variant references one of the well-known
22
+ * `resourceKind` values for which AppKit owns the CLI command map (see
23
+ * `RESOURCE_KIND_COMMANDS` below). The `cli` variant is the escape hatch
24
+ * carrying the existing free-form fields (with the `<PROFILE>`
25
+ * refinement). Hierarchical context for volumes (catalog/schema parent
26
+ * walk) is encoded via the kind's `parents` array, not via dependsOn.
27
+ * - Scaffolding rule items carry a `maxLength` (120 chars) so
28
+ * `rules.never[]` / `rules.must[]` / `rules.should[]` stay short
29
+ * directives by contract, and the canonical `TEMPLATE_SCAFFOLDING`
30
+ * constant lives co-located with the scaffolding schemas (sync.ts
31
+ * imports it).
32
+ */
33
+ const resourceTypeSchema = z.enum([
34
+ "secret",
35
+ "job",
36
+ "sql_warehouse",
37
+ "serving_endpoint",
38
+ "volume",
39
+ "vector_search_index",
40
+ "uc_function",
41
+ "uc_connection",
42
+ "database",
43
+ "postgres",
44
+ "genie_space",
45
+ "experiment",
46
+ "app"
47
+ ]).describe("Type of Databricks resource");
48
+ const secretPermissionSchema = z.enum([
49
+ "READ",
50
+ "WRITE",
51
+ "MANAGE"
52
+ ]).describe("Permission for secret resources (order: weakest to strongest)");
53
+ const jobPermissionSchema = z.enum([
54
+ "CAN_VIEW",
55
+ "CAN_MANAGE_RUN",
56
+ "CAN_MANAGE"
57
+ ]).describe("Permission for job resources (order: weakest to strongest)");
58
+ const sqlWarehousePermissionSchema = z.enum(["CAN_USE", "CAN_MANAGE"]).describe("Permission for SQL warehouse resources (order: weakest to strongest)");
59
+ const servingEndpointPermissionSchema = z.enum([
60
+ "CAN_VIEW",
61
+ "CAN_QUERY",
62
+ "CAN_MANAGE"
63
+ ]).describe("Permission for serving endpoint resources (order: weakest to strongest)");
64
+ const volumePermissionSchema = z.enum(["READ_VOLUME", "WRITE_VOLUME"]).describe("Permission for Unity Catalog volume resources");
65
+ const vectorSearchIndexPermissionSchema = z.enum(["SELECT"]).describe("Permission for vector search index resources");
66
+ const ucFunctionPermissionSchema = z.enum(["EXECUTE"]).describe("Permission for Unity Catalog function resources");
67
+ const ucConnectionPermissionSchema = z.enum(["USE_CONNECTION"]).describe("Permission for Unity Catalog connection resources");
68
+ const databasePermissionSchema = z.enum(["CAN_CONNECT_AND_CREATE"]).describe("Permission for database resources");
69
+ const postgresPermissionSchema = z.enum(["CAN_CONNECT_AND_CREATE"]).describe("Permission for Postgres resources");
70
+ const genieSpacePermissionSchema = z.enum([
71
+ "CAN_VIEW",
72
+ "CAN_RUN",
73
+ "CAN_EDIT",
74
+ "CAN_MANAGE"
75
+ ]).describe("Permission for Genie Space resources (order: weakest to strongest)");
76
+ const experimentPermissionSchema = z.enum([
77
+ "CAN_READ",
78
+ "CAN_EDIT",
79
+ "CAN_MANAGE"
80
+ ]).describe("Permission for MLflow experiment resources (order: weakest to strongest)");
81
+ const appPermissionSchema = z.enum(["CAN_USE"]).describe("Permission for Databricks App resources");
82
+ /**
83
+ * Well-known Databricks resource kinds for which AppKit owns the CLI
84
+ * command map. Plugins reference one of these via the `kind` variant of the
85
+ * discovery descriptor; everything else falls back to the free-form `cli`
86
+ * variant.
87
+ *
88
+ * Kept narrow on purpose: each entry costs an addition to
89
+ * `RESOURCE_KIND_COMMANDS` below, which is the single source of truth for
90
+ * how that kind is enumerated.
91
+ */
92
+ const resourceKindSchema = z.enum([
93
+ "warehouse",
94
+ "genie_space",
95
+ "postgres_project",
96
+ "postgres_branch",
97
+ "postgres_database",
98
+ "volume"
99
+ ]).describe("Well-known Databricks resource kind whose listing command is owned by AppKit (see RESOURCE_KIND_COMMANDS).");
100
+ const kindDiscoveryDescriptorSchema = z.object({
101
+ type: z.literal("kind").describe("Discriminator: 'kind' uses the AppKit-owned command map for the named resourceKind."),
102
+ resourceKind: resourceKindSchema.describe("Reference to a well-known Databricks resource kind. AppKit owns the CLI command, response shape, and unwrap rules."),
103
+ select: z.string().optional().describe("Field name in the parsed CLI response used as the selected value (e.g., 'id'). Defaults to the kind's natural identifier when omitted."),
104
+ display: z.string().optional().describe("Field name in the parsed CLI response shown to the user in selection UI. Defaults to `select` if omitted."),
105
+ dependsOn: z.string().optional().describe("Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields."),
106
+ shortcut: z.string().optional().describe("Single-value fast-path command that returns exactly one value, skipping interactive selection.")
107
+ }).strict().describe("Discovery via a well-known resource kind. AppKit owns the CLI command and unwrap rules for the named kind.");
108
+ /**
109
+ * Shell metacharacters rejected on free-form CLI command strings. Catches the
110
+ * common foot-guns (statement separators, pipes, redirects via shell, command
111
+ * substitution, newlines). Not a security boundary on its own — executors must
112
+ * always pass these via argv, never shell-exec the string. Angle brackets are
113
+ * permitted because `<PROFILE>` (and future `<…>` placeholders) are part of
114
+ * the command-template convention.
115
+ */
116
+ const SHELL_METACHAR_RE = /[;|&`$\n\r]/;
117
+ const cliDiscoveryDescriptorSchema = z.object({
118
+ type: z.literal("cli").describe("Discriminator: 'cli' uses a free-form Databricks CLI command supplied by the plugin."),
119
+ cliCommand: z.string().describe("Databricks CLI command that lists resources. Must include <PROFILE>. Shell metacharacters (;|&`$ and newlines) are rejected; for first-party Databricks resources prefer the `kind` variant which uses AppKit's typed command map."),
120
+ selectField: z.string().describe("jq-style path to the field used as the selected value (e.g., '.id', '.name')."),
121
+ displayField: z.string().optional().describe("jq-style path to the field shown to the user in selection UI. Defaults to selectField if omitted."),
122
+ dependsOn: z.string().optional().describe("Name of a sibling field within the same resource that must be resolved first. Used to express ordering dependencies between resource fields."),
123
+ shortcut: z.string().optional().describe("Single-value fast-path command that returns exactly one value, skipping interactive selection. Shell metacharacters are rejected.")
124
+ }).strict().refine((descriptor) => descriptor.cliCommand.includes("<PROFILE>"), {
125
+ message: "must include <PROFILE> placeholder",
126
+ path: ["cliCommand"]
127
+ }).refine((descriptor) => !SHELL_METACHAR_RE.test(descriptor.cliCommand), {
128
+ message: "must not contain shell metacharacters (;|&`$ or newlines); use the `kind` variant for typed Databricks resources",
129
+ path: ["cliCommand"]
130
+ }).refine((descriptor) => descriptor.shortcut === void 0 || !SHELL_METACHAR_RE.test(descriptor.shortcut), {
131
+ message: "must not contain shell metacharacters (;|&`$ or newlines)",
132
+ path: ["shortcut"]
133
+ }).describe("Discovery via a free-form Databricks CLI command. Escape hatch — prefer the `kind` variant when a typed resourceKind covers the resource. This shape is intentionally minimal and may tighten further in future versions.");
134
+ const discoveryDescriptorSchema = z.discriminatedUnion("type", [kindDiscoveryDescriptorSchema, cliDiscoveryDescriptorSchema]).describe("Describes how the CLI discovers values for a resource field. 'kind' references a well-known Databricks resource kind whose command is owned by AppKit; 'cli' is the escape hatch carrying a free-form Databricks CLI command.");
135
+ const resourceFieldEntrySchema = z.object({
136
+ env: z.string().regex(/^[A-Z][A-Z0-9_]*$/).optional().describe("Environment variable name for this field"),
137
+ description: z.string().optional().describe("Human-readable description for this field"),
138
+ bundleIgnore: z.boolean().optional().describe("When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation."),
139
+ examples: z.array(z.string()).optional().describe("Example values showing the expected format for this field"),
140
+ localOnly: z.boolean().optional().describe("When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time."),
141
+ value: z.string().optional().describe("Static value for this field. Used when no prompted or resolved value exists."),
142
+ resolve: z.string().regex(/^[a-z_]+:[a-zA-Z]+$/).optional().describe("Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow."),
143
+ discovery: discoveryDescriptorSchema.optional()
144
+ }).strict().describe("Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).");
145
+ /**
146
+ * Build a per-type variant. Each variant fixes `type` to a literal and constrains
147
+ * `permission` to the matching enum, mirroring the existing JSON Schema's
148
+ * `allOf + if/then` block. `fields` and the rest of the shape come from a
149
+ * shared base.
150
+ */
151
+ const resourceRequirementBaseShape = {
152
+ alias: z.string().min(1).describe("Human-readable label for UI/display only. Deduplication uses resourceKey, not alias."),
153
+ resourceKey: z.string().regex(/^[a-z][a-z0-9-]*$/).describe("Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup."),
154
+ description: z.string().min(1).describe("Human-readable description of why this resource is needed"),
155
+ fields: z.record(z.string(), resourceFieldEntrySchema).refine((obj) => Object.keys(obj).length >= 1, { message: "fields must contain at least one entry" }).optional().describe("Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key).")
156
+ };
157
+ /**
158
+ * Adds the cycle/dangling-reference cross-field check to a resource variant.
159
+ * Iterates the resource's `fields`, validates each `discovery.dependsOn` target
160
+ * is a sibling field name, then runs DFS over the dependsOn graph to detect
161
+ * cycles. Issue paths target either the offending field's `dependsOn` slot or
162
+ * the resource itself for cycles.
163
+ */
164
+ function refineResourceDependsOn(resource, ctx) {
165
+ if (!resource.fields) return;
166
+ const fieldNames = new Set(Object.keys(resource.fields));
167
+ const deps = /* @__PURE__ */ new Map();
168
+ for (const [name, field] of Object.entries(resource.fields)) {
169
+ const dep = field.discovery?.dependsOn;
170
+ if (!dep) continue;
171
+ if (!fieldNames.has(dep)) ctx.addIssue({
172
+ code: "custom",
173
+ path: [
174
+ "fields",
175
+ name,
176
+ "discovery",
177
+ "dependsOn"
178
+ ],
179
+ message: `references non-existent sibling field '${dep}'`
180
+ });
181
+ deps.set(name, dep);
182
+ }
183
+ const visited = /* @__PURE__ */ new Set();
184
+ const visiting = /* @__PURE__ */ new Set();
185
+ function dfs(node, chain) {
186
+ if (visiting.has(node)) return [...chain, node];
187
+ if (visited.has(node)) return null;
188
+ visiting.add(node);
189
+ const next = deps.get(node);
190
+ if (next) {
191
+ const cycle = dfs(next, [...chain, node]);
192
+ if (cycle) return cycle;
193
+ }
194
+ visiting.delete(node);
195
+ visited.add(node);
196
+ return null;
197
+ }
198
+ for (const node of deps.keys()) {
199
+ if (visited.has(node)) continue;
200
+ const cycle = dfs(node, []);
201
+ if (cycle) {
202
+ ctx.addIssue({
203
+ code: "custom",
204
+ path: [],
205
+ message: `discovery.dependsOn creates a cycle: ${cycle.join(" → ")}`
206
+ });
207
+ break;
208
+ }
209
+ }
210
+ }
211
+ function makeResourceVariant(typeLiteral, permission) {
212
+ return z.object({
213
+ type: typeLiteral,
214
+ ...resourceRequirementBaseShape,
215
+ permission: permission.describe("Required permission level. Validated per resource type.")
216
+ }).strict().superRefine(refineResourceDependsOn);
217
+ }
218
+ const resourceRequirementSchema = z.discriminatedUnion("type", [
219
+ makeResourceVariant(z.literal("secret"), secretPermissionSchema),
220
+ makeResourceVariant(z.literal("job"), jobPermissionSchema),
221
+ makeResourceVariant(z.literal("sql_warehouse"), sqlWarehousePermissionSchema),
222
+ makeResourceVariant(z.literal("serving_endpoint"), servingEndpointPermissionSchema),
223
+ makeResourceVariant(z.literal("volume"), volumePermissionSchema),
224
+ makeResourceVariant(z.literal("vector_search_index"), vectorSearchIndexPermissionSchema),
225
+ makeResourceVariant(z.literal("uc_function"), ucFunctionPermissionSchema),
226
+ makeResourceVariant(z.literal("uc_connection"), ucConnectionPermissionSchema),
227
+ makeResourceVariant(z.literal("database"), databasePermissionSchema),
228
+ makeResourceVariant(z.literal("postgres"), postgresPermissionSchema),
229
+ makeResourceVariant(z.literal("genie_space"), genieSpacePermissionSchema),
230
+ makeResourceVariant(z.literal("experiment"), experimentPermissionSchema),
231
+ makeResourceVariant(z.literal("app"), appPermissionSchema)
232
+ ]).describe("Declares a resource requirement for a plugin. Can be defined statically in a manifest or dynamically via getResourceRequirements().");
233
+ const configSchemaPropertySchema = z.lazy(() => z.object({
234
+ type: z.enum([
235
+ "object",
236
+ "array",
237
+ "string",
238
+ "number",
239
+ "boolean",
240
+ "integer"
241
+ ]),
242
+ description: z.string().optional(),
243
+ default: z.unknown().optional(),
244
+ enum: z.array(z.unknown()).optional(),
245
+ properties: z.record(z.string(), configSchemaPropertySchema).optional(),
246
+ items: configSchemaPropertySchema.optional(),
247
+ minimum: z.number().optional(),
248
+ maximum: z.number().optional(),
249
+ minLength: z.number().int().min(0).optional(),
250
+ maxLength: z.number().int().min(0).optional(),
251
+ required: z.array(z.string()).optional(),
252
+ additionalProperties: z.union([z.boolean(), configSchemaPropertySchema]).optional()
253
+ }).strict());
254
+ const configSchemaSchema = z.lazy(() => z.object({
255
+ type: z.enum([
256
+ "object",
257
+ "array",
258
+ "string",
259
+ "number",
260
+ "boolean"
261
+ ]),
262
+ properties: z.record(z.string(), configSchemaPropertySchema).optional(),
263
+ items: configSchemaSchema.optional(),
264
+ required: z.array(z.string()).optional(),
265
+ additionalProperties: z.boolean().optional()
266
+ }).strict());
267
+ /**
268
+ * Per-item upper bound on plugin-level scaffolding rule strings. Matches the
269
+ * template-level `SCAFFOLDING_RULE_MAX_LENGTH` defined below — rules at both
270
+ * levels are short directives, not prose. The literal value lives here (and
271
+ * not by reference to the template-side constant) because this declaration
272
+ * is read in source order and the template constant is declared later.
273
+ */
274
+ const PLUGIN_SCAFFOLDING_RULE_MAX_LENGTH = 120;
275
+ const pluginScaffoldingRuleItemSchema = z.string().min(1).max(PLUGIN_SCAFFOLDING_RULE_MAX_LENGTH, `rule entries must be ≤ ${PLUGIN_SCAFFOLDING_RULE_MAX_LENGTH} chars`);
276
+ const pluginScaffoldingRulesSchema = z.object({
277
+ must: z.array(pluginScaffoldingRuleItemSchema).optional().describe("Actions the scaffolding agent must always perform."),
278
+ should: z.array(pluginScaffoldingRuleItemSchema).optional().describe("Recommended actions for the scaffolding agent."),
279
+ never: z.array(pluginScaffoldingRuleItemSchema).optional().describe("Actions the scaffolding agent must never perform.")
280
+ }).strict().superRefine((rules, ctx) => {
281
+ const buckets = [
282
+ ["must", rules.must],
283
+ ["should", rules.should],
284
+ ["never", rules.never]
285
+ ];
286
+ for (const [bucketName, items] of buckets) {
287
+ if (!items) continue;
288
+ const seen = /* @__PURE__ */ new Map();
289
+ items.forEach((item, idx) => {
290
+ const prev = seen.get(item);
291
+ if (prev === void 0) {
292
+ seen.set(item, idx);
293
+ return;
294
+ }
295
+ ctx.addIssue({
296
+ code: "custom",
297
+ path: [bucketName, idx],
298
+ message: `duplicate rule entry: "${item}" already declared at index ${prev}`
299
+ });
300
+ });
301
+ }
302
+ const owner = /* @__PURE__ */ new Map();
303
+ for (const [bucketName, items] of buckets) {
304
+ if (!items) continue;
305
+ for (let i = 0; i < items.length; i++) {
306
+ const item = items[i];
307
+ const existing = owner.get(item);
308
+ if (existing === void 0) {
309
+ owner.set(item, bucketName);
310
+ continue;
311
+ }
312
+ if (existing !== bucketName) ctx.addIssue({
313
+ code: "custom",
314
+ path: [bucketName, i],
315
+ message: `rule entry "${item}" appears in both '${existing}' and '${bucketName}'; rules must belong to exactly one bucket`
316
+ });
317
+ }
318
+ }
319
+ }).describe("Structured rules for scaffolding agents declared at the plugin level. Each rule is a short directive (≤120 chars).");
320
+ const pluginManifestSchema = z.object({
321
+ $schema: z.string().optional().describe("Reference to the JSON Schema for validation"),
322
+ name: z.string().regex(/^[a-z][a-z0-9-]*$/).describe("Plugin identifier. Must be lowercase, start with a letter, and contain only letters, numbers, and hyphens."),
323
+ displayName: z.string().min(1).describe("Human-readable display name for UI and CLI"),
324
+ description: z.string().min(1).describe("Brief description of what the plugin does"),
325
+ resources: z.object({
326
+ required: z.array(resourceRequirementSchema).describe("Resources that must be available for the plugin to function"),
327
+ optional: z.array(resourceRequirementSchema).describe("Resources that enhance functionality but are not mandatory")
328
+ }).strict().describe("Databricks resource requirements for this plugin"),
329
+ config: z.object({ schema: configSchemaSchema.optional() }).strict().optional().describe("Configuration schema for the plugin"),
330
+ author: z.string().optional().describe("Author name or organization"),
331
+ version: z.string().regex(/^\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?$/).optional().describe("Plugin version (semver format)"),
332
+ repository: z.url().optional().describe("URL to the plugin's source repository"),
333
+ keywords: z.array(z.string()).optional().describe("Keywords for plugin discovery"),
334
+ license: z.string().optional().describe("SPDX license identifier"),
335
+ onSetupMessage: z.string().optional().describe("Message displayed to the user after project initialization. Use this to inform about manual setup steps (e.g. environment variables, resource provisioning)."),
336
+ hidden: z.boolean().optional().describe("When true, this plugin is excluded from the template plugins manifest (appkit.plugins.json) during sync."),
337
+ stability: z.enum(["beta", "ga"]).optional().describe("Plugin stability level. Beta plugins may have breaking API changes between minor releases but are on a path to GA. GA (general availability) plugins follow semver strictly."),
338
+ scaffolding: z.object({ rules: pluginScaffoldingRulesSchema.optional().describe("Structured rules for scaffolding agents declared at the plugin level.") }).strict().optional().describe("Plugin-level scaffolding metadata consumed by scaffolding agents. Symmetric with template-level `scaffolding`.")
339
+ }).strict().describe("Schema for Databricks AppKit plugin manifest files. Defines plugin metadata, resource requirements, and configuration options.");
340
+ const originSchema = z.enum([
341
+ "user",
342
+ "platform",
343
+ "static",
344
+ "cli"
345
+ ]).describe("How the field value is determined. Computed during sync, not authored by plugin developers.");
346
+ /**
347
+ * Derives the canonical origin of a resource field value from its shape.
348
+ *
349
+ * - `localOnly: true` → `"platform"` (auto-injected by the Databricks Apps
350
+ * platform at deploy time; takes precedence over `value`/`resolve`).
351
+ * - `value !== undefined` → `"static"` (hardcoded value).
352
+ * - `resolve !== undefined` → `"cli"` (resolved by the CLI during init).
353
+ * - else → `"user"` (user must provide the value at init time).
354
+ *
355
+ * Co-located with `templateFieldEntrySchema` because the transform is the
356
+ * only consumer. Kept private so any other "origin computation" goes
357
+ * through the schema rather than re-implementing the rules.
358
+ */
359
+ function computeOriginFromField(field) {
360
+ if (field.localOnly) return "platform";
361
+ if (field.value !== void 0) return "static";
362
+ if (field.resolve !== void 0) return "cli";
363
+ return "user";
364
+ }
365
+ /**
366
+ * Template field entry: extends the plugin manifest field entry with an
367
+ * optional `origin` input slot, then runs a `.transform()` that overwrites
368
+ * `origin` with the computed value. Allowing `origin` on input means
369
+ * re-parsing a previously-synced template manifest does not fail; emitting
370
+ * `origin` always means hand-edits in synced JSON are silently corrected
371
+ * on the next parse — drift-by-construction.
372
+ */
373
+ const templateFieldEntrySchema = resourceFieldEntrySchema.extend({ origin: originSchema.optional() }).transform((field) => ({
374
+ ...field,
375
+ origin: computeOriginFromField(field)
376
+ }));
377
+ const templateResourceRequirementBaseShape = {
378
+ alias: z.string().min(1).describe("Human-readable label for UI/display only."),
379
+ resourceKey: z.string().regex(/^[a-z][a-z0-9-]*$/).describe("Stable key for machine use: deduplication, env naming, composite keys."),
380
+ description: z.string().min(1).describe("Human-readable description of why this resource is needed"),
381
+ fields: z.record(z.string(), templateFieldEntrySchema).refine((obj) => Object.keys(obj).length >= 1, { message: "fields must contain at least one entry" }).optional().describe("Map of field name to field entry with computed origin.")
382
+ };
383
+ function makeTemplateResourceVariant(typeLiteral, permission) {
384
+ return z.object({
385
+ type: typeLiteral,
386
+ ...templateResourceRequirementBaseShape,
387
+ permission: permission.describe("Required permission level. Validated per resource type.")
388
+ }).strict().superRefine(refineResourceDependsOn);
389
+ }
390
+ const templateResourceRequirementSchema = z.discriminatedUnion("type", [
391
+ makeTemplateResourceVariant(z.literal("secret"), secretPermissionSchema),
392
+ makeTemplateResourceVariant(z.literal("job"), jobPermissionSchema),
393
+ makeTemplateResourceVariant(z.literal("sql_warehouse"), sqlWarehousePermissionSchema),
394
+ makeTemplateResourceVariant(z.literal("serving_endpoint"), servingEndpointPermissionSchema),
395
+ makeTemplateResourceVariant(z.literal("volume"), volumePermissionSchema),
396
+ makeTemplateResourceVariant(z.literal("vector_search_index"), vectorSearchIndexPermissionSchema),
397
+ makeTemplateResourceVariant(z.literal("uc_function"), ucFunctionPermissionSchema),
398
+ makeTemplateResourceVariant(z.literal("uc_connection"), ucConnectionPermissionSchema),
399
+ makeTemplateResourceVariant(z.literal("database"), databasePermissionSchema),
400
+ makeTemplateResourceVariant(z.literal("postgres"), postgresPermissionSchema),
401
+ makeTemplateResourceVariant(z.literal("genie_space"), genieSpacePermissionSchema),
402
+ makeTemplateResourceVariant(z.literal("experiment"), experimentPermissionSchema),
403
+ makeTemplateResourceVariant(z.literal("app"), appPermissionSchema)
404
+ ]).describe("Resource requirement with template-specific field entries (includes computed origin).");
405
+ const templatePluginSchema = z.object({
406
+ name: z.string().regex(/^[a-z][a-z0-9-]*$/).describe("Plugin identifier. Must be lowercase, start with a letter, and contain only letters, numbers, and hyphens."),
407
+ displayName: z.string().min(1).describe("Human-readable display name for UI and CLI"),
408
+ description: z.string().min(1).describe("Brief description of what the plugin does"),
409
+ package: z.string().min(1).describe("NPM package name that provides this plugin"),
410
+ requiredByTemplate: z.boolean().optional().describe("When true, this plugin is required by the template and cannot be deselected during CLI init. The user will only be prompted to configure its resources. When absent or false, the plugin is optional and the user can choose whether to include it."),
411
+ onSetupMessage: z.string().optional().describe("Message displayed to the user after project initialization. Use this to inform about manual setup steps (e.g. environment variables, resource provisioning)."),
412
+ stability: z.enum(["beta", "ga"]).optional().describe("Plugin stability level. Beta is heading to GA; APIs may change between minor releases. GA (general availability) follows semver."),
413
+ scaffolding: z.object({ rules: pluginScaffoldingRulesSchema.optional().describe("Structured rules for scaffolding agents propagated from the plugin manifest.") }).strict().optional().describe("Plugin-level scaffolding metadata propagated from the plugin manifest."),
414
+ resources: z.object({
415
+ required: z.array(templateResourceRequirementSchema).describe("Resources that must be available for the plugin to function"),
416
+ optional: z.array(templateResourceRequirementSchema).describe("Resources that enhance functionality but are not mandatory")
417
+ }).strict().describe("Databricks resource requirements for this plugin")
418
+ }).strict().describe("Plugin manifest with package source information");
419
+ const scaffoldingFlagSchema = z.object({
420
+ description: z.string().describe("Human-readable description of the flag."),
421
+ required: z.boolean().optional().describe("Whether this flag is required."),
422
+ pattern: z.string().optional().describe("Regex pattern for validating the flag value."),
423
+ default: z.string().optional().describe("Default value for this flag.")
424
+ }).strict().describe("A flag for the scaffolding command.");
425
+ /**
426
+ * Per-item upper bound on scaffolding rule strings. The intent is to enforce
427
+ * "short directive" by contract — long paragraphs fail validation and force
428
+ * authors to split prose into discrete actionable items.
429
+ */
430
+ const SCAFFOLDING_RULE_MAX_LENGTH = 120;
431
+ const scaffoldingRuleItemSchema = z.string().max(SCAFFOLDING_RULE_MAX_LENGTH, `rule item must be ≤ ${SCAFFOLDING_RULE_MAX_LENGTH} chars`);
432
+ const scaffoldingRulesSchema = z.object({
433
+ never: z.array(scaffoldingRuleItemSchema).optional().describe("Actions the scaffolding agent must never perform."),
434
+ must: z.array(scaffoldingRuleItemSchema).optional().describe("Actions the scaffolding agent must always perform."),
435
+ should: z.array(scaffoldingRuleItemSchema).optional().describe("Recommended actions for the scaffolding agent (parity with plugin-level rules).")
436
+ }).strict().describe("Structured rules for scaffolding agents.");
437
+ const scaffoldingDescriptorSchema = z.object({
438
+ command: z.string().describe("The scaffolding command (e.g., 'databricks apps init')."),
439
+ flags: z.record(z.string(), scaffoldingFlagSchema).optional().describe("Map of flag name to flag descriptor."),
440
+ rules: scaffoldingRulesSchema.optional().describe("Structured rules for scaffolding agents.")
441
+ }).strict().describe("Describes the scaffolding command, flags, and rules for project initialization.");
442
+ /**
443
+ * Canonical scaffolding descriptor for the `databricks apps init` command,
444
+ * embedded in v2.0 template manifests to guide scaffolding agents.
445
+ *
446
+ * Co-located with `scaffoldingDescriptorSchema` so any change to the rule set
447
+ * (or the schema's `maxLength` ceiling) shows up next to its consumer. The
448
+ * `satisfies` annotation gives compile-time validation that the literal
449
+ * matches the schema's input shape; if a `must`/`never` entry exceeds the
450
+ * `maxLength` ceiling at runtime, `scaffoldingDescriptorSchema.parse` would
451
+ * surface the breach in tests.
452
+ */
453
+ const TEMPLATE_SCAFFOLDING = {
454
+ command: "databricks apps init",
455
+ flags: {
456
+ "--name": {
457
+ description: "Project name — sets {{.projectName}} in package.json, databricks.yml, and .env. Required for non-interactive scaffolding.",
458
+ required: true,
459
+ pattern: "^[a-z][a-z0-9-]*$"
460
+ },
461
+ "--template": {
462
+ description: "Template path (local directory or GitHub URL)",
463
+ required: false
464
+ },
465
+ "--version": {
466
+ description: "AppKit version to use; defaults to auto-detected",
467
+ required: false
468
+ },
469
+ "--features": {
470
+ description: "Plugins to enable (comma-separated, no spaces; must match keys in this manifest's plugins map)",
471
+ required: false,
472
+ pattern: "^[a-zA-Z0-9_-]+(,[a-zA-Z0-9_-]+)*$"
473
+ },
474
+ "--set": {
475
+ description: "Set resource values (format: plugin.resourceKey.field=value, repeatable)",
476
+ required: false
477
+ },
478
+ "--output-dir": {
479
+ description: "Directory to write the project to",
480
+ required: false
481
+ },
482
+ "--description": {
483
+ description: "App description",
484
+ required: false
485
+ },
486
+ "--run": {
487
+ description: "Run the app after creation (none, dev, dev-remote)",
488
+ required: false
489
+ },
490
+ "--auto-approve": {
491
+ description: "Pass as a bare flag (no value) to skip prompts for optional resources. Not recommended for agent-driven init — conflicts with the 'ask user when in doubt' rule.",
492
+ required: false
493
+ },
494
+ "--profile": {
495
+ description: "Databricks CLI profile to use for authentication (global flag)",
496
+ required: false
497
+ }
498
+ },
499
+ rules: {
500
+ must: ["Keep all secrets and credentials only in app.yaml, databricks.yml, and/or .env"],
501
+ should: ["ask user when in doubt of resource to use for plugin"],
502
+ never: ["guess resources when multiple or no options are available", "embed secrets in files that will go to the client-bundle"]
503
+ }
504
+ };
505
+ const templatePluginsManifestSchema = z.object({
506
+ $schema: z.string().optional().describe("Reference to the JSON Schema for validation"),
507
+ version: z.enum([
508
+ "1.0",
509
+ "1.1",
510
+ "2.0"
511
+ ]).describe("Schema version for the template plugins manifest"),
512
+ plugins: z.record(z.string(), templatePluginSchema).describe("Map of plugin name to plugin manifest with package source"),
513
+ scaffolding: scaffoldingDescriptorSchema.optional().describe("Describes the scaffolding command and its configuration for project initialization.")
514
+ }).strict().superRefine((value, ctx) => {
515
+ if (value.version === "2.0" && !value.scaffolding) ctx.addIssue({
516
+ code: "custom",
517
+ path: ["scaffolding"],
518
+ message: "scaffolding is required when version is '2.0'"
519
+ });
520
+ }).describe("Aggregated plugin manifest for AppKit templates. Read by Databricks CLI during init to discover available plugins and their resource requirements.");
521
+
522
+ //#endregion
523
+ export { TEMPLATE_SCAFFOLDING, appPermissionSchema, databasePermissionSchema, experimentPermissionSchema, genieSpacePermissionSchema, jobPermissionSchema, pluginManifestSchema, postgresPermissionSchema, resourceTypeSchema, secretPermissionSchema, servingEndpointPermissionSchema, sqlWarehousePermissionSchema, templateFieldEntrySchema, templatePluginsManifestSchema, ucConnectionPermissionSchema, ucFunctionPermissionSchema, vectorSearchIndexPermissionSchema, volumePermissionSchema };
524
+ //# sourceMappingURL=manifest.js.map