@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
@@ -1,14 +1,6 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import { fileURLToPath } from "node:url";
4
- import Ajv from "ajv";
5
- import addFormats from "ajv-formats";
1
+ import { pluginManifestSchema, templatePluginsManifestSchema } from "../../../../schemas/manifest.js";
6
2
 
7
3
  //#region src/cli/commands/plugin/validate/validate-manifest.ts
8
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
- const SCHEMAS_DIR = path.join(__dirname, "..", "..", "..", "..", "schemas");
10
- const PLUGIN_MANIFEST_SCHEMA_PATH = path.join(SCHEMAS_DIR, "plugin-manifest.schema.json");
11
- const TEMPLATE_PLUGINS_SCHEMA_PATH = path.join(SCHEMAS_DIR, "template-plugins.schema.json");
12
4
  const SCHEMA_ID_MAP = {
13
5
  "https://databricks.github.io/appkit/schemas/plugin-manifest.schema.json": "plugin-manifest",
14
6
  "https://databricks.github.io/appkit/schemas/template-plugins.schema.json": "template-plugins"
@@ -23,192 +15,114 @@ function detectSchemaType(obj) {
23
15
  if (typeof schemaUrl !== "string") return "unknown";
24
16
  return SCHEMA_ID_MAP[schemaUrl] ?? "unknown";
25
17
  }
26
- let schemaLoadWarned = false;
27
- function loadSchema(schemaPath) {
28
- try {
29
- return JSON.parse(fs.readFileSync(schemaPath, "utf-8"));
30
- } catch (err) {
31
- if (!schemaLoadWarned) {
32
- schemaLoadWarned = true;
33
- console.warn(`Warning: Could not load JSON schema at ${schemaPath}: ${err instanceof Error ? err.message : err}. Falling back to basic validation.`);
18
+ /**
19
+ * Convert a Standard Schema issue path (array of property keys / path segments)
20
+ * into a humanized string like `resources.required[0].permission`.
21
+ */
22
+ function humanizePath(path) {
23
+ if (!path || path.length === 0) return "(root)";
24
+ let out = "";
25
+ for (const segment of path) {
26
+ const key = typeof segment === "object" && segment !== null && "key" in segment ? segment.key : segment;
27
+ if (typeof key === "number") out += `[${key}]`;
28
+ else {
29
+ const str = String(key);
30
+ out += out.length === 0 ? str : `.${str}`;
34
31
  }
35
- return null;
36
32
  }
33
+ return out.length === 0 ? "(root)" : out;
37
34
  }
38
- let compiledPluginValidator = null;
39
- function getPluginValidator() {
40
- if (compiledPluginValidator) return compiledPluginValidator;
41
- const schema = loadSchema(PLUGIN_MANIFEST_SCHEMA_PATH);
42
- if (!schema) return null;
43
- try {
44
- const ajv = new Ajv({
45
- allErrors: true,
46
- strict: false
47
- });
48
- addFormats(ajv);
49
- compiledPluginValidator = ajv.compile(schema);
50
- return compiledPluginValidator;
51
- } catch {
52
- return null;
53
- }
35
+ /**
36
+ * Map a Standard Schema issue list into the legacy `SemanticIssue` shape so
37
+ * consumer code can stay uniform.
38
+ */
39
+ function mapIssues(issues) {
40
+ const result = [];
41
+ for (const issue of issues) result.push({
42
+ level: "error",
43
+ path: humanizePath(issue.path),
44
+ message: issue.message
45
+ });
46
+ return result;
54
47
  }
55
- let compiledTemplateValidator = null;
56
- function getTemplateValidator() {
57
- if (compiledTemplateValidator) return compiledTemplateValidator;
58
- const pluginSchema = loadSchema(PLUGIN_MANIFEST_SCHEMA_PATH);
59
- const templateSchema = loadSchema(TEMPLATE_PLUGINS_SCHEMA_PATH);
60
- if (!pluginSchema || !templateSchema) return null;
61
- try {
62
- const ajv = new Ajv({
63
- allErrors: true,
64
- strict: false
65
- });
66
- addFormats(ajv);
67
- ajv.addSchema(pluginSchema);
68
- compiledTemplateValidator = ajv.compile(templateSchema);
69
- return compiledTemplateValidator;
70
- } catch {
71
- return null;
72
- }
48
+ /**
49
+ * Validate an object against a Standard Schema and produce a uniform
50
+ * `ValidateResult`. The manifest output is captured for downstream consumers.
51
+ */
52
+ function validateWithStandardSchema(schema, obj) {
53
+ const result = schema["~standard"].validate(obj);
54
+ if (result instanceof Promise) return {
55
+ valid: false,
56
+ errors: [{
57
+ level: "error",
58
+ path: "(root)",
59
+ message: "Schema returned an async validation result; expected synchronous validation."
60
+ }]
61
+ };
62
+ if (result.issues) return {
63
+ valid: false,
64
+ errors: mapIssues(result.issues)
65
+ };
66
+ return {
67
+ valid: true,
68
+ output: result.value
69
+ };
73
70
  }
74
71
  /**
75
- * Validate a manifest object against the plugin-manifest JSON schema.
72
+ * Validate a manifest object against the plugin-manifest schema.
76
73
  * Returns validation result with optional errors for CLI output.
74
+ *
75
+ * On success, the returned `manifest` is the original input object (preserving
76
+ * its property order) typed as `PluginManifest`, not a re-emitted Zod copy.
77
+ * Round-trip writers like `add-resource` rely on this to keep the on-disk
78
+ * field ordering stable.
77
79
  */
78
80
  function validateManifest(obj) {
79
81
  if (!obj || typeof obj !== "object") return {
80
82
  valid: false,
81
83
  errors: [{
82
- instancePath: "",
84
+ level: "error",
85
+ path: "(root)",
83
86
  message: "Manifest is not a valid object"
84
87
  }]
85
88
  };
86
- const validate = getPluginValidator();
87
- if (!validate) {
88
- const m = obj;
89
- if (typeof m.name === "string" && m.name.length > 0 && typeof m.displayName === "string" && m.displayName.length > 0 && typeof m.description === "string" && m.description.length > 0 && m.resources && typeof m.resources === "object" && Array.isArray(m.resources.required)) return {
90
- valid: true,
91
- manifest: obj
92
- };
93
- return {
94
- valid: false,
95
- errors: [{
96
- instancePath: "",
97
- message: "Invalid manifest structure"
98
- }]
99
- };
100
- }
101
- if (validate(obj)) return {
89
+ const result = validateWithStandardSchema(pluginManifestSchema, obj);
90
+ if (result.valid) return {
102
91
  valid: true,
103
92
  manifest: obj
104
93
  };
105
94
  return {
106
95
  valid: false,
107
- errors: validate.errors ?? []
96
+ errors: result.errors
108
97
  };
109
98
  }
110
99
  /**
111
100
  * Validate a template-plugins manifest (appkit.plugins.json) against its schema.
112
- * Registers the plugin-manifest schema first so external $refs resolve.
113
101
  */
114
102
  function validateTemplateManifest(obj) {
115
103
  if (!obj || typeof obj !== "object") return {
116
104
  valid: false,
117
105
  errors: [{
118
- instancePath: "",
106
+ level: "error",
107
+ path: "(root)",
119
108
  message: "Template manifest is not a valid object"
120
109
  }]
121
110
  };
122
- const validate = getTemplateValidator();
123
- if (!validate) {
124
- const m = obj;
125
- if (typeof m.version === "string" && m.plugins && typeof m.plugins === "object") return { valid: true };
126
- return {
127
- valid: false,
128
- errors: [{
129
- instancePath: "",
130
- message: "Invalid template manifest structure"
131
- }]
132
- };
133
- }
134
- if (validate(obj)) return { valid: true };
111
+ const result = validateWithStandardSchema(templatePluginsManifestSchema, obj);
112
+ if (result.valid) return { valid: true };
135
113
  return {
136
114
  valid: false,
137
- errors: validate.errors ?? []
115
+ errors: result.errors
138
116
  };
139
117
  }
140
118
  /**
141
- * Convert a JSON pointer like /resources/required/0/permission
142
- * to a readable path like resources.required[0].permission
143
- */
144
- function humanizePath(instancePath) {
145
- if (!instancePath) return "(root)";
146
- return instancePath.replace(/^\//, "").replace(/\/(\d+)\//g, "[$1].").replace(/\/(\d+)$/g, "[$1]").replace(/\//g, ".");
147
- }
148
- /**
149
- * Resolve a JSON pointer to the actual value in the parsed object.
150
- */
151
- function resolvePointer(obj, instancePath) {
152
- if (!instancePath) return obj;
153
- const segments = instancePath.replace(/^\//, "").split("/");
154
- let current = obj;
155
- for (const seg of segments) {
156
- if (current == null || typeof current !== "object") return void 0;
157
- current = current[seg];
158
- }
159
- return current;
160
- }
161
- /**
162
- * Format schema errors for CLI output.
163
- * Collapses anyOf/oneOf sub-errors into a single message and shows
164
- * the actual invalid value when available.
165
- *
166
- * @param errors - AJV error objects
167
- * @param obj - The original parsed object (optional, used to show actual values)
119
+ * Format validation issues for CLI output. Each issue is rendered on its own
120
+ * line indented by two spaces, with the format ` <path>: <message>`. The
121
+ * `path` is already humanized when issues are produced by `validateManifest`
122
+ * / `validateTemplateManifest`.
168
123
  */
169
- function formatValidationErrors(errors, obj) {
170
- const grouped = /* @__PURE__ */ new Map();
171
- for (const e of errors) {
172
- const key = e.instancePath || "/";
173
- if (!grouped.has(key)) grouped.set(key, []);
174
- const list = grouped.get(key);
175
- if (list) list.push(e);
176
- }
177
- const lines = [];
178
- for (const [path, errs] of grouped) {
179
- const readable = humanizePath(path);
180
- const anyOfErr = errs.find((e) => e.keyword === "anyOf" || e.keyword === "oneOf");
181
- if (anyOfErr) {
182
- const enumErrors = errs.filter((e) => e.keyword === "enum");
183
- if (enumErrors.length > 0) {
184
- const allValues = [...new Set(enumErrors.flatMap((e) => e.params?.allowedValues ?? []))];
185
- const actual = obj !== void 0 ? resolvePointer(obj, path) : void 0;
186
- const valueHint = actual !== void 0 ? ` (got ${JSON.stringify(actual)})` : "";
187
- lines.push(` ${readable}: invalid value${valueHint}`, ` allowed: ${allValues.join(", ")}`);
188
- continue;
189
- }
190
- }
191
- for (const e of errs) {
192
- if (e.keyword === "anyOf" || e.keyword === "oneOf") continue;
193
- if (e.keyword === "if") continue;
194
- if (anyOfErr && e.keyword === "enum") continue;
195
- if (e.keyword === "enum") {
196
- const allowed = e.params?.allowedValues ?? [];
197
- const actual = obj !== void 0 ? resolvePointer(obj, path) : void 0;
198
- const valueHint = actual !== void 0 ? ` (got ${JSON.stringify(actual)})` : "";
199
- lines.push(` ${readable}: invalid value${valueHint}, allowed: ${allowed.join(", ")}`);
200
- } else if (e.keyword === "required") lines.push(` ${readable}: missing required property "${e.params?.missingProperty}"`);
201
- else if (e.keyword === "additionalProperties") lines.push(` ${readable}: unknown property "${e.params?.additionalProperty}"`);
202
- else if (e.keyword === "pattern") {
203
- const actual = obj !== void 0 ? resolvePointer(obj, path) : void 0;
204
- const valueHint = actual !== void 0 ? ` (got ${JSON.stringify(actual)})` : "";
205
- lines.push(` ${readable}: does not match expected pattern${valueHint}`);
206
- } else if (e.keyword === "type") lines.push(` ${readable}: expected type "${e.params?.type}"`);
207
- else if (e.keyword === "minLength") lines.push(` ${readable}: must not be empty`);
208
- else lines.push(` ${readable}: ${e.message}${e.params ? ` (${JSON.stringify(e.params)})` : ""}`);
209
- }
210
- }
211
- return lines.join("\n");
124
+ function formatValidationErrors(issues) {
125
+ return issues.map((issue) => ` ${issue.path}: ${issue.message}`).join("\n");
212
126
  }
213
127
 
214
128
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"validate-manifest.js","names":[],"sources":["../../../../../src/cli/commands/plugin/validate/validate-manifest.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport Ajv, { type ErrorObject } from \"ajv\";\nimport addFormats from \"ajv-formats\";\nimport type { PluginManifest } from \"../manifest-types\";\n\nexport type { PluginManifest };\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\nconst SCHEMAS_DIR = path.join(__dirname, \"..\", \"..\", \"..\", \"..\", \"schemas\");\nconst PLUGIN_MANIFEST_SCHEMA_PATH = path.join(\n SCHEMAS_DIR,\n \"plugin-manifest.schema.json\",\n);\nconst TEMPLATE_PLUGINS_SCHEMA_PATH = path.join(\n SCHEMAS_DIR,\n \"template-plugins.schema.json\",\n);\n\nexport type SchemaType = \"plugin-manifest\" | \"template-plugins\" | \"unknown\";\n\nconst SCHEMA_ID_MAP: Record<string, SchemaType> = {\n \"https://databricks.github.io/appkit/schemas/plugin-manifest.schema.json\":\n \"plugin-manifest\",\n \"https://databricks.github.io/appkit/schemas/template-plugins.schema.json\":\n \"template-plugins\",\n};\n\n/**\n * Detect which schema type a parsed JSON object targets based on its $schema field.\n * Returns \"unknown\" when the field is missing or unrecognized.\n */\nexport function detectSchemaType(obj: unknown): SchemaType {\n if (!obj || typeof obj !== \"object\") return \"unknown\";\n const schemaUrl = (obj as Record<string, unknown>).$schema;\n if (typeof schemaUrl !== \"string\") return \"unknown\";\n return SCHEMA_ID_MAP[schemaUrl] ?? \"unknown\";\n}\n\nexport interface ValidateResult {\n valid: boolean;\n manifest?: PluginManifest;\n errors?: ErrorObject[];\n}\n\nlet schemaLoadWarned = false;\n\nfunction loadSchema(schemaPath: string): object | null {\n try {\n return JSON.parse(fs.readFileSync(schemaPath, \"utf-8\")) as object;\n } catch (err) {\n if (!schemaLoadWarned) {\n schemaLoadWarned = true;\n console.warn(\n `Warning: Could not load JSON schema at ${schemaPath}: ${err instanceof Error ? err.message : err}. Falling back to basic validation.`,\n );\n }\n return null;\n }\n}\n\nlet compiledPluginValidator: ReturnType<Ajv[\"compile\"]> | null = null;\n\nfunction getPluginValidator(): ReturnType<Ajv[\"compile\"]> | null {\n if (compiledPluginValidator) return compiledPluginValidator;\n const schema = loadSchema(PLUGIN_MANIFEST_SCHEMA_PATH);\n if (!schema) return null;\n try {\n const ajv = new Ajv({ allErrors: true, strict: false });\n addFormats(ajv);\n compiledPluginValidator = ajv.compile(schema);\n return compiledPluginValidator;\n } catch {\n return null;\n }\n}\n\nlet compiledTemplateValidator: ReturnType<Ajv[\"compile\"]> | null = null;\n\nfunction getTemplateValidator(): ReturnType<Ajv[\"compile\"]> | null {\n if (compiledTemplateValidator) return compiledTemplateValidator;\n const pluginSchema = loadSchema(PLUGIN_MANIFEST_SCHEMA_PATH);\n const templateSchema = loadSchema(TEMPLATE_PLUGINS_SCHEMA_PATH);\n if (!pluginSchema || !templateSchema) return null;\n try {\n const ajv = new Ajv({ allErrors: true, strict: false });\n addFormats(ajv);\n ajv.addSchema(pluginSchema);\n compiledTemplateValidator = ajv.compile(templateSchema);\n return compiledTemplateValidator;\n } catch {\n return null;\n }\n}\n\n/**\n * Validate a manifest object against the plugin-manifest JSON schema.\n * Returns validation result with optional errors for CLI output.\n */\nexport function validateManifest(obj: unknown): ValidateResult {\n if (!obj || typeof obj !== \"object\") {\n return {\n valid: false,\n errors: [\n {\n instancePath: \"\",\n message: \"Manifest is not a valid object\",\n } as ErrorObject,\n ],\n };\n }\n\n const validate = getPluginValidator();\n if (!validate) {\n const m = obj as Record<string, unknown>;\n const basicValid =\n typeof m.name === \"string\" &&\n m.name.length > 0 &&\n typeof m.displayName === \"string\" &&\n m.displayName.length > 0 &&\n typeof m.description === \"string\" &&\n m.description.length > 0 &&\n m.resources &&\n typeof m.resources === \"object\" &&\n Array.isArray((m.resources as { required?: unknown }).required);\n if (basicValid) return { valid: true, manifest: obj as PluginManifest };\n return {\n valid: false,\n errors: [\n {\n instancePath: \"\",\n message: \"Invalid manifest structure\",\n } as ErrorObject,\n ],\n };\n }\n\n const valid = validate(obj);\n if (valid) return { valid: true, manifest: obj as PluginManifest };\n return { valid: false, errors: validate.errors ?? [] };\n}\n\n/**\n * Validate a template-plugins manifest (appkit.plugins.json) against its schema.\n * Registers the plugin-manifest schema first so external $refs resolve.\n */\nexport function validateTemplateManifest(obj: unknown): ValidateResult {\n if (!obj || typeof obj !== \"object\") {\n return {\n valid: false,\n errors: [\n {\n instancePath: \"\",\n message: \"Template manifest is not a valid object\",\n } as ErrorObject,\n ],\n };\n }\n\n const validate = getTemplateValidator();\n if (!validate) {\n const m = obj as Record<string, unknown>;\n const basicValid =\n typeof m.version === \"string\" &&\n m.plugins &&\n typeof m.plugins === \"object\";\n if (basicValid) return { valid: true };\n return {\n valid: false,\n errors: [\n {\n instancePath: \"\",\n message: \"Invalid template manifest structure\",\n } as ErrorObject,\n ],\n };\n }\n\n const valid = validate(obj);\n if (valid) return { valid: true };\n return { valid: false, errors: validate.errors ?? [] };\n}\n\n/**\n * Convert a JSON pointer like /resources/required/0/permission\n * to a readable path like resources.required[0].permission\n */\nfunction humanizePath(instancePath: string): string {\n if (!instancePath) return \"(root)\";\n return instancePath\n .replace(/^\\//, \"\")\n .replace(/\\/(\\d+)\\//g, \"[$1].\")\n .replace(/\\/(\\d+)$/g, \"[$1]\")\n .replace(/\\//g, \".\");\n}\n\n/**\n * Resolve a JSON pointer to the actual value in the parsed object.\n */\nfunction resolvePointer(obj: unknown, instancePath: string): unknown {\n if (!instancePath) return obj;\n const segments = instancePath.replace(/^\\//, \"\").split(\"/\");\n let current: unknown = obj;\n for (const seg of segments) {\n if (current == null || typeof current !== \"object\") return undefined;\n current = (current as Record<string, unknown>)[seg];\n }\n return current;\n}\n\n/**\n * Format schema errors for CLI output.\n * Collapses anyOf/oneOf sub-errors into a single message and shows\n * the actual invalid value when available.\n *\n * @param errors - AJV error objects\n * @param obj - The original parsed object (optional, used to show actual values)\n */\nexport function formatValidationErrors(\n errors: ErrorObject[],\n obj?: unknown,\n): string {\n const grouped = new Map<string, ErrorObject[]>();\n for (const e of errors) {\n const key = e.instancePath || \"/\";\n if (!grouped.has(key)) grouped.set(key, []);\n const list = grouped.get(key);\n if (list) list.push(e);\n }\n\n const lines: string[] = [];\n\n for (const [path, errs] of grouped) {\n const readable = humanizePath(path);\n const anyOfErr = errs.find(\n (e) => e.keyword === \"anyOf\" || e.keyword === \"oneOf\",\n );\n\n if (anyOfErr) {\n const enumErrors = errs.filter((e) => e.keyword === \"enum\");\n if (enumErrors.length > 0) {\n const allValues = [\n ...new Set(\n enumErrors.flatMap(\n (e) => (e.params?.allowedValues as string[]) ?? [],\n ),\n ),\n ];\n const actual =\n obj !== undefined ? resolvePointer(obj, path) : undefined;\n const valueHint =\n actual !== undefined ? ` (got ${JSON.stringify(actual)})` : \"\";\n lines.push(\n ` ${readable}: invalid value${valueHint}`,\n ` allowed: ${allValues.join(\", \")}`,\n );\n continue;\n }\n }\n\n for (const e of errs) {\n if (e.keyword === \"anyOf\" || e.keyword === \"oneOf\") continue;\n if (e.keyword === \"if\") continue;\n if (anyOfErr && e.keyword === \"enum\") continue;\n\n if (e.keyword === \"enum\") {\n const allowed = (e.params?.allowedValues as string[]) ?? [];\n const actual =\n obj !== undefined ? resolvePointer(obj, path) : undefined;\n const valueHint =\n actual !== undefined ? ` (got ${JSON.stringify(actual)})` : \"\";\n lines.push(\n ` ${readable}: invalid value${valueHint}, allowed: ${allowed.join(\", \")}`,\n );\n } else if (e.keyword === \"required\") {\n lines.push(\n ` ${readable}: missing required property \"${e.params?.missingProperty}\"`,\n );\n } else if (e.keyword === \"additionalProperties\") {\n lines.push(\n ` ${readable}: unknown property \"${e.params?.additionalProperty}\"`,\n );\n } else if (e.keyword === \"pattern\") {\n const actual =\n obj !== undefined ? resolvePointer(obj, path) : undefined;\n const valueHint =\n actual !== undefined ? ` (got ${JSON.stringify(actual)})` : \"\";\n lines.push(\n ` ${readable}: does not match expected pattern${valueHint}`,\n );\n } else if (e.keyword === \"type\") {\n lines.push(` ${readable}: expected type \"${e.params?.type}\"`);\n } else if (e.keyword === \"minLength\") {\n lines.push(` ${readable}: must not be empty`);\n } else {\n lines.push(\n ` ${readable}: ${e.message}${e.params ? ` (${JSON.stringify(e.params)})` : \"\"}`,\n );\n }\n }\n }\n\n return lines.join(\"\\n\");\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"validate-manifest.js","names":[],"sources":["../../../../../src/cli/commands/plugin/validate/validate-manifest.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport {\n pluginManifestSchema,\n templatePluginsManifestSchema,\n} from \"../../../../schemas/manifest\";\nimport type { PluginManifest } from \"../manifest-types\";\n\nexport type { PluginManifest };\n\nexport type SchemaType = \"plugin-manifest\" | \"template-plugins\" | \"unknown\";\n\nconst SCHEMA_ID_MAP: Record<string, SchemaType> = {\n \"https://databricks.github.io/appkit/schemas/plugin-manifest.schema.json\":\n \"plugin-manifest\",\n \"https://databricks.github.io/appkit/schemas/template-plugins.schema.json\":\n \"template-plugins\",\n};\n\n/**\n * Detect which schema type a parsed JSON object targets based on its $schema field.\n * Returns \"unknown\" when the field is missing or unrecognized.\n */\nexport function detectSchemaType(obj: unknown): SchemaType {\n if (!obj || typeof obj !== \"object\") return \"unknown\";\n const schemaUrl = (obj as Record<string, unknown>).$schema;\n if (typeof schemaUrl !== \"string\") return \"unknown\";\n return SCHEMA_ID_MAP[schemaUrl] ?? \"unknown\";\n}\n\n/**\n * A single validation issue produced by the schema validator. The `path`\n * is a humanized property path (e.g., `resources.required[0].permission`)\n * suitable for direct CLI output.\n */\nexport interface SemanticIssue {\n level: \"error\" | \"warning\";\n path: string;\n message: string;\n}\n\nexport interface ValidateResult {\n valid: boolean;\n manifest?: PluginManifest;\n errors?: SemanticIssue[];\n}\n\n/**\n * Convert a Standard Schema issue path (array of property keys / path segments)\n * into a humanized string like `resources.required[0].permission`.\n */\nfunction humanizePath(\n path: ReadonlyArray<PropertyKey | StandardSchemaV1.PathSegment> | undefined,\n): string {\n if (!path || path.length === 0) return \"(root)\";\n\n let out = \"\";\n for (const segment of path) {\n const key =\n typeof segment === \"object\" && segment !== null && \"key\" in segment\n ? segment.key\n : segment;\n if (typeof key === \"number\") {\n out += `[${key}]`;\n } else {\n const str = String(key);\n out += out.length === 0 ? str : `.${str}`;\n }\n }\n return out.length === 0 ? \"(root)\" : out;\n}\n\n/**\n * Map a Standard Schema issue list into the legacy `SemanticIssue` shape so\n * consumer code can stay uniform.\n */\nfunction mapIssues(\n issues: ReadonlyArray<StandardSchemaV1.Issue>,\n): SemanticIssue[] {\n const result: SemanticIssue[] = [];\n for (const issue of issues) {\n result.push({\n level: \"error\",\n path: humanizePath(issue.path),\n message: issue.message,\n });\n }\n return result;\n}\n\n/**\n * Validate an object against a Standard Schema and produce a uniform\n * `ValidateResult`. The manifest output is captured for downstream consumers.\n */\nfunction validateWithStandardSchema<T>(\n schema: StandardSchemaV1<unknown, T>,\n obj: unknown,\n): { valid: true; output: T } | { valid: false; errors: SemanticIssue[] } {\n const result = schema[\"~standard\"].validate(obj);\n if (result instanceof Promise) {\n // Our schemas are synchronous; treat any async resolution as an error\n // rather than blocking the CLI on a background promise.\n return {\n valid: false,\n errors: [\n {\n level: \"error\",\n path: \"(root)\",\n message:\n \"Schema returned an async validation result; expected synchronous validation.\",\n },\n ],\n };\n }\n if (result.issues) {\n return { valid: false, errors: mapIssues(result.issues) };\n }\n return { valid: true, output: result.value };\n}\n\n/**\n * Validate a manifest object against the plugin-manifest schema.\n * Returns validation result with optional errors for CLI output.\n *\n * On success, the returned `manifest` is the original input object (preserving\n * its property order) typed as `PluginManifest`, not a re-emitted Zod copy.\n * Round-trip writers like `add-resource` rely on this to keep the on-disk\n * field ordering stable.\n */\nexport function validateManifest(obj: unknown): ValidateResult {\n if (!obj || typeof obj !== \"object\") {\n return {\n valid: false,\n errors: [\n {\n level: \"error\",\n path: \"(root)\",\n message: \"Manifest is not a valid object\",\n },\n ],\n };\n }\n\n const result = validateWithStandardSchema(pluginManifestSchema, obj);\n if (result.valid) {\n return { valid: true, manifest: obj as PluginManifest };\n }\n return { valid: false, errors: result.errors };\n}\n\n/**\n * Validate a template-plugins manifest (appkit.plugins.json) against its schema.\n */\nexport function validateTemplateManifest(obj: unknown): ValidateResult {\n if (!obj || typeof obj !== \"object\") {\n return {\n valid: false,\n errors: [\n {\n level: \"error\",\n path: \"(root)\",\n message: \"Template manifest is not a valid object\",\n },\n ],\n };\n }\n\n const result = validateWithStandardSchema(templatePluginsManifestSchema, obj);\n if (result.valid) {\n return { valid: true };\n }\n return { valid: false, errors: result.errors };\n}\n\n/**\n * Format validation issues for CLI output. Each issue is rendered on its own\n * line indented by two spaces, with the format ` <path>: <message>`. The\n * `path` is already humanized when issues are produced by `validateManifest`\n * / `validateTemplateManifest`.\n */\nexport function formatValidationErrors(issues: SemanticIssue[]): string {\n return issues.map((issue) => ` ${issue.path}: ${issue.message}`).join(\"\\n\");\n}\n"],"mappings":";;;AAWA,MAAM,gBAA4C;CAChD,2EACE;CACF,4EACE;CACH;;;;;AAMD,SAAgB,iBAAiB,KAA0B;AACzD,KAAI,CAAC,OAAO,OAAO,QAAQ,SAAU,QAAO;CAC5C,MAAM,YAAa,IAAgC;AACnD,KAAI,OAAO,cAAc,SAAU,QAAO;AAC1C,QAAO,cAAc,cAAc;;;;;;AAwBrC,SAAS,aACP,MACQ;AACR,KAAI,CAAC,QAAQ,KAAK,WAAW,EAAG,QAAO;CAEvC,IAAI,MAAM;AACV,MAAK,MAAM,WAAW,MAAM;EAC1B,MAAM,MACJ,OAAO,YAAY,YAAY,YAAY,QAAQ,SAAS,UACxD,QAAQ,MACR;AACN,MAAI,OAAO,QAAQ,SACjB,QAAO,IAAI,IAAI;OACV;GACL,MAAM,MAAM,OAAO,IAAI;AACvB,UAAO,IAAI,WAAW,IAAI,MAAM,IAAI;;;AAGxC,QAAO,IAAI,WAAW,IAAI,WAAW;;;;;;AAOvC,SAAS,UACP,QACiB;CACjB,MAAM,SAA0B,EAAE;AAClC,MAAK,MAAM,SAAS,OAClB,QAAO,KAAK;EACV,OAAO;EACP,MAAM,aAAa,MAAM,KAAK;EAC9B,SAAS,MAAM;EAChB,CAAC;AAEJ,QAAO;;;;;;AAOT,SAAS,2BACP,QACA,KACwE;CACxE,MAAM,SAAS,OAAO,aAAa,SAAS,IAAI;AAChD,KAAI,kBAAkB,QAGpB,QAAO;EACL,OAAO;EACP,QAAQ,CACN;GACE,OAAO;GACP,MAAM;GACN,SACE;GACH,CACF;EACF;AAEH,KAAI,OAAO,OACT,QAAO;EAAE,OAAO;EAAO,QAAQ,UAAU,OAAO,OAAO;EAAE;AAE3D,QAAO;EAAE,OAAO;EAAM,QAAQ,OAAO;EAAO;;;;;;;;;;;AAY9C,SAAgB,iBAAiB,KAA8B;AAC7D,KAAI,CAAC,OAAO,OAAO,QAAQ,SACzB,QAAO;EACL,OAAO;EACP,QAAQ,CACN;GACE,OAAO;GACP,MAAM;GACN,SAAS;GACV,CACF;EACF;CAGH,MAAM,SAAS,2BAA2B,sBAAsB,IAAI;AACpE,KAAI,OAAO,MACT,QAAO;EAAE,OAAO;EAAM,UAAU;EAAuB;AAEzD,QAAO;EAAE,OAAO;EAAO,QAAQ,OAAO;EAAQ;;;;;AAMhD,SAAgB,yBAAyB,KAA8B;AACrE,KAAI,CAAC,OAAO,OAAO,QAAQ,SACzB,QAAO;EACL,OAAO;EACP,QAAQ,CACN;GACE,OAAO;GACP,MAAM;GACN,SAAS;GACV,CACF;EACF;CAGH,MAAM,SAAS,2BAA2B,+BAA+B,IAAI;AAC7E,KAAI,OAAO,MACT,QAAO,EAAE,OAAO,MAAM;AAExB,QAAO;EAAE,OAAO;EAAO,QAAQ,OAAO;EAAQ;;;;;;;;AAShD,SAAgB,uBAAuB,QAAiC;AACtE,QAAO,OAAO,KAAK,UAAU,KAAK,MAAM,KAAK,IAAI,MAAM,UAAU,CAAC,KAAK,KAAK"}
@@ -83,7 +83,7 @@ async function runPluginValidate(paths, options) {
83
83
  else console.log(`✓ ${relativePath}`);
84
84
  else {
85
85
  if (options.json) {
86
- const errors = result.errors?.length ? formatValidationErrors(result.errors, obj).split("\n").filter(Boolean) : [];
86
+ const errors = result.errors?.length ? formatValidationErrors(result.errors).split("\n").filter(Boolean) : [];
87
87
  jsonResults.push({
88
88
  path: relativePath,
89
89
  valid: false,
@@ -91,7 +91,7 @@ async function runPluginValidate(paths, options) {
91
91
  });
92
92
  } else {
93
93
  console.error(`✗ ${relativePath}`);
94
- if (result.errors?.length) console.error(formatValidationErrors(result.errors, obj));
94
+ if (result.errors?.length) console.error(formatValidationErrors(result.errors));
95
95
  }
96
96
  hasFailure = true;
97
97
  }
@@ -1 +1 @@
1
- {"version":3,"file":"validate.js","names":[],"sources":["../../../../../src/cli/commands/plugin/validate/validate.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport process from \"node:process\";\nimport { Command } from \"commander\";\nimport {\n loadManifestFromFile,\n type ResolvedManifest,\n resolveManifestInDir,\n} from \"../manifest-resolve\";\nimport {\n detectSchemaType,\n formatValidationErrors,\n validateManifest,\n validateTemplateManifest,\n} from \"./validate-manifest\";\n\nfunction resolveManifestPaths(\n paths: string[],\n cwd: string,\n allowJsManifest: boolean,\n): ResolvedManifest[] {\n const out: ResolvedManifest[] = [];\n for (const p of paths) {\n const resolved = path.resolve(cwd, p);\n if (!fs.existsSync(resolved)) {\n console.error(`Path not found: ${p}`);\n continue;\n }\n const stat = fs.statSync(resolved);\n if (stat.isDirectory()) {\n let found = false;\n const pluginResolved = resolveManifestInDir(resolved, {\n allowJsManifest,\n });\n if (pluginResolved) {\n out.push(pluginResolved);\n found = true;\n }\n const templateManifest = path.join(resolved, \"appkit.plugins.json\");\n if (fs.existsSync(templateManifest)) {\n out.push({ path: templateManifest, type: \"json\" });\n found = true;\n }\n if (!found) {\n console.error(\n `No ${allowJsManifest ? \"manifest.json, manifest.js, or\" : \"manifest.json or\"} appkit.plugins.json in directory: ${p}`,\n );\n }\n } else {\n const ext = path.extname(resolved).toLowerCase();\n if (!allowJsManifest && (ext === \".js\" || ext === \".cjs\")) {\n console.error(\n `JS manifest provided but disabled by default: ${p}. Re-run with --allow-js-manifest to opt in.`,\n );\n continue;\n }\n out.push({\n path: resolved,\n type: ext === \".js\" || ext === \".cjs\" ? \"js\" : \"json\",\n });\n }\n }\n return out;\n}\n\ninterface ValidateOptions {\n allowJsManifest?: boolean;\n json?: boolean;\n}\n\nasync function runPluginValidate(\n paths: string[],\n options: ValidateOptions,\n): Promise<void> {\n const cwd = process.cwd();\n const allowJsManifest = Boolean(options.allowJsManifest);\n if (allowJsManifest && !options.json) {\n console.warn(\n \"Warning: --allow-js-manifest executes manifest.js/manifest.cjs files. Only use with trusted code.\",\n );\n }\n const toValidate = paths.length > 0 ? paths : [\".\"];\n const manifestPaths = resolveManifestPaths(toValidate, cwd, allowJsManifest);\n\n if (manifestPaths.length === 0) {\n if (options.json) {\n console.log(\"[]\");\n } else {\n console.error(\"No manifest files to validate.\");\n }\n process.exit(1);\n }\n\n let hasFailure = false;\n const jsonResults: { path: string; valid: boolean; errors?: string[] }[] = [];\n\n for (const { path: manifestPath, type } of manifestPaths) {\n const relativePath = path.relative(cwd, manifestPath);\n let obj: unknown;\n try {\n obj = await loadManifestFromFile(manifestPath, type, { allowJsManifest });\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n if (options.json) {\n jsonResults.push({\n path: relativePath,\n valid: false,\n errors: [errMsg],\n });\n } else {\n console.error(`✗ ${manifestPath}`);\n console.error(` ${errMsg}`);\n }\n hasFailure = true;\n continue;\n }\n\n const schemaType = detectSchemaType(obj);\n const result =\n schemaType === \"template-plugins\"\n ? validateTemplateManifest(obj)\n : validateManifest(obj);\n\n if (result.valid) {\n if (options.json) {\n jsonResults.push({ path: relativePath, valid: true });\n } else {\n console.log(`✓ ${relativePath}`);\n }\n } else {\n if (options.json) {\n const errors = result.errors?.length\n ? formatValidationErrors(result.errors, obj)\n .split(\"\\n\")\n .filter(Boolean)\n : [];\n jsonResults.push({\n path: relativePath,\n valid: false,\n ...(errors.length > 0 && { errors }),\n });\n } else {\n console.error(`✗ ${relativePath}`);\n if (result.errors?.length) {\n console.error(formatValidationErrors(result.errors, obj));\n }\n }\n hasFailure = true;\n }\n }\n\n if (options.json) {\n console.log(JSON.stringify(jsonResults, null, 2));\n }\n\n process.exit(hasFailure ? 1 : 0);\n}\n\nexport const pluginValidateCommand = new Command(\"validate\")\n .description(\n \"Validate plugin manifest(s) or template manifests against their JSON schema\",\n )\n .argument(\n \"[paths...]\",\n \"Paths to manifest.json or appkit.plugins.json (or plugin directories); use --allow-js-manifest to include manifest.js\",\n )\n .option(\n \"--allow-js-manifest\",\n \"Allow reading manifest.js/manifest.cjs (executes code; use only with trusted plugins)\",\n )\n .option(\"--json\", \"Output validation results as JSON\")\n .addHelpText(\n \"after\",\n `\nExamples:\n $ appkit plugin validate\n $ appkit plugin validate plugins/my-plugin\n $ appkit plugin validate plugins/my-plugin plugins/other\n $ appkit plugin validate appkit.plugins.json\n $ appkit plugin validate --json`,\n )\n .action((paths: string[], opts: ValidateOptions) =>\n runPluginValidate(paths, opts).catch((err) => {\n console.error(err);\n process.exit(1);\n }),\n );\n"],"mappings":"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"}
1
+ {"version":3,"file":"validate.js","names":[],"sources":["../../../../../src/cli/commands/plugin/validate/validate.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport process from \"node:process\";\nimport { Command } from \"commander\";\nimport {\n loadManifestFromFile,\n type ResolvedManifest,\n resolveManifestInDir,\n} from \"../manifest-resolve\";\nimport type { ValidateResult } from \"./validate-manifest\";\nimport {\n detectSchemaType,\n formatValidationErrors,\n validateManifest,\n validateTemplateManifest,\n} from \"./validate-manifest\";\n\nfunction resolveManifestPaths(\n paths: string[],\n cwd: string,\n allowJsManifest: boolean,\n): ResolvedManifest[] {\n const out: ResolvedManifest[] = [];\n for (const p of paths) {\n const resolved = path.resolve(cwd, p);\n if (!fs.existsSync(resolved)) {\n console.error(`Path not found: ${p}`);\n continue;\n }\n const stat = fs.statSync(resolved);\n if (stat.isDirectory()) {\n let found = false;\n const pluginResolved = resolveManifestInDir(resolved, {\n allowJsManifest,\n });\n if (pluginResolved) {\n out.push(pluginResolved);\n found = true;\n }\n const templateManifest = path.join(resolved, \"appkit.plugins.json\");\n if (fs.existsSync(templateManifest)) {\n out.push({ path: templateManifest, type: \"json\" });\n found = true;\n }\n if (!found) {\n console.error(\n `No ${allowJsManifest ? \"manifest.json, manifest.js, or\" : \"manifest.json or\"} appkit.plugins.json in directory: ${p}`,\n );\n }\n } else {\n const ext = path.extname(resolved).toLowerCase();\n if (!allowJsManifest && (ext === \".js\" || ext === \".cjs\")) {\n console.error(\n `JS manifest provided but disabled by default: ${p}. Re-run with --allow-js-manifest to opt in.`,\n );\n continue;\n }\n out.push({\n path: resolved,\n type: ext === \".js\" || ext === \".cjs\" ? \"js\" : \"json\",\n });\n }\n }\n return out;\n}\n\ninterface ValidateOptions {\n allowJsManifest?: boolean;\n json?: boolean;\n}\n\nasync function runPluginValidate(\n paths: string[],\n options: ValidateOptions,\n): Promise<void> {\n const cwd = process.cwd();\n const allowJsManifest = Boolean(options.allowJsManifest);\n if (allowJsManifest && !options.json) {\n console.warn(\n \"Warning: --allow-js-manifest executes manifest.js/manifest.cjs files. Only use with trusted code.\",\n );\n }\n const toValidate = paths.length > 0 ? paths : [\".\"];\n const manifestPaths = resolveManifestPaths(toValidate, cwd, allowJsManifest);\n\n if (manifestPaths.length === 0) {\n if (options.json) {\n console.log(\"[]\");\n } else {\n console.error(\"No manifest files to validate.\");\n }\n process.exit(1);\n }\n\n let hasFailure = false;\n const jsonResults: {\n path: string;\n valid: boolean;\n errors?: string[];\n warnings?: string[];\n }[] = [];\n\n for (const { path: manifestPath, type } of manifestPaths) {\n const relativePath = path.relative(cwd, manifestPath);\n let obj: unknown;\n try {\n obj = await loadManifestFromFile(manifestPath, type, { allowJsManifest });\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n if (options.json) {\n jsonResults.push({\n path: relativePath,\n valid: false,\n errors: [errMsg],\n });\n } else {\n console.error(`✗ ${manifestPath}`);\n console.error(` ${errMsg}`);\n }\n hasFailure = true;\n continue;\n }\n\n const schemaType = detectSchemaType(obj);\n const result: ValidateResult =\n schemaType === \"template-plugins\"\n ? validateTemplateManifest(obj)\n : validateManifest(obj);\n\n if (result.valid) {\n if (options.json) {\n jsonResults.push({ path: relativePath, valid: true });\n } else {\n console.log(`✓ ${relativePath}`);\n }\n } else {\n if (options.json) {\n const errors = result.errors?.length\n ? formatValidationErrors(result.errors).split(\"\\n\").filter(Boolean)\n : [];\n jsonResults.push({\n path: relativePath,\n valid: false,\n ...(errors.length > 0 && { errors }),\n });\n } else {\n console.error(`✗ ${relativePath}`);\n if (result.errors?.length) {\n console.error(formatValidationErrors(result.errors));\n }\n }\n hasFailure = true;\n }\n }\n\n if (options.json) {\n console.log(JSON.stringify(jsonResults, null, 2));\n }\n\n process.exit(hasFailure ? 1 : 0);\n}\n\nexport const pluginValidateCommand = new Command(\"validate\")\n .description(\n \"Validate plugin manifest(s) or template manifests against their JSON schema\",\n )\n .argument(\n \"[paths...]\",\n \"Paths to manifest.json or appkit.plugins.json (or plugin directories); use --allow-js-manifest to include manifest.js\",\n )\n .option(\n \"--allow-js-manifest\",\n \"Allow reading manifest.js/manifest.cjs (executes code; use only with trusted plugins)\",\n )\n .option(\"--json\", \"Output validation results as JSON\")\n .addHelpText(\n \"after\",\n `\nExamples:\n $ appkit plugin validate\n $ appkit plugin validate plugins/my-plugin\n $ appkit plugin validate plugins/my-plugin plugins/other\n $ appkit plugin validate appkit.plugins.json\n $ appkit plugin validate --json`,\n )\n .action((paths: string[], opts: ValidateOptions) =>\n runPluginValidate(paths, opts).catch((err) => {\n console.error(err);\n process.exit(1);\n }),\n );\n"],"mappings":"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"}
@@ -43,7 +43,7 @@ ${packages.map((pkg) => {
43
43
  For enhanced AI assistance with Databricks CLI operations, authentication, data exploration, and app development, install the Databricks skills:
44
44
 
45
45
  \`\`\`bash
46
- databricks experimental aitools install
46
+ databricks aitools install
47
47
  \`\`\`
48
48
  ${SECTION_END}`;
49
49
  }
@@ -68,7 +68,7 @@ ${packages.map((pkg) => {
68
68
  For enhanced AI assistance with Databricks CLI operations, authentication, data exploration, and app development, install the Databricks skills:
69
69
 
70
70
  \`\`\`bash
71
- databricks experimental aitools install
71
+ databricks aitools install
72
72
  \`\`\`
73
73
  ${SECTION_END}
74
74
  `;
@@ -1 +1 @@
1
- {"version":3,"file":"setup.js","names":[],"sources":["../../../src/cli/commands/setup.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { Command } from \"commander\";\n\nconst PACKAGES = [\n { name: \"@databricks/appkit\", description: \"Backend SDK\" },\n {\n name: \"@databricks/appkit-ui\",\n description: \"UI Integration, Charts, Tables, SSE, and more.\",\n },\n];\n\nconst SECTION_START = \"<!-- appkit-instructions-start -->\";\nconst SECTION_END = \"<!-- appkit-instructions-end -->\";\n\n/**\n * Find which AppKit packages are installed by checking for package.json\n */\nfunction findInstalledPackages() {\n const cwd = process.cwd();\n const installed = [];\n\n for (const pkg of PACKAGES) {\n const packagePath = path.join(\n cwd,\n \"node_modules\",\n pkg.name,\n \"package.json\",\n );\n if (fs.existsSync(packagePath)) {\n installed.push(pkg);\n }\n }\n\n return installed;\n}\n\n/**\n * Generate the AppKit section content\n */\nfunction generateSection(packages: typeof PACKAGES) {\n const links = packages\n .map((pkg) => {\n const docPath = `./node_modules/${pkg.name}/CLAUDE.md`;\n return `- **${pkg.name}** (${pkg.description}): [${docPath}](${docPath})`;\n })\n .join(\"\\n\");\n\n return `${SECTION_START}\n## Databricks AppKit\n\nThis project uses Databricks AppKit packages. For AI assistant guidance on using these packages, refer to:\n\n${links}\n\n### Databricks Skills\n\nFor enhanced AI assistance with Databricks CLI operations, authentication, data exploration, and app development, install the Databricks skills:\n\n\\`\\`\\`bash\ndatabricks experimental aitools install\n\\`\\`\\`\n${SECTION_END}`;\n}\n\n/**\n * Generate standalone CLAUDE.md content (when no existing file)\n */\nfunction generateStandalone(packages: typeof PACKAGES) {\n const links = packages\n .map((pkg) => {\n const docPath = `./node_modules/${pkg.name}/CLAUDE.md`;\n return `- **${pkg.name}** (${pkg.description}): [${docPath}](${docPath})`;\n })\n .join(\"\\n\");\n\n return `# AI Assistant Instructions\n\n${SECTION_START}\n## Databricks AppKit\n\nThis project uses Databricks AppKit packages. For AI assistant guidance on using these packages, refer to:\n\n${links}\n\n### Databricks Skills\n\nFor enhanced AI assistance with Databricks CLI operations, authentication, data exploration, and app development, install the Databricks skills:\n\n\\`\\`\\`bash\ndatabricks experimental aitools install\n\\`\\`\\`\n${SECTION_END}\n`;\n}\n\n/**\n * Update existing content with AppKit section\n */\nfunction updateContent(existingContent: string, packages: typeof PACKAGES) {\n const newSection = generateSection(packages);\n\n // Check if AppKit section already exists\n const startIndex = existingContent.indexOf(SECTION_START);\n const endIndex = existingContent.indexOf(SECTION_END);\n\n if (startIndex !== -1 && endIndex !== -1) {\n // Replace existing section\n const before = existingContent.substring(0, startIndex);\n const after = existingContent.substring(endIndex + SECTION_END.length);\n return before + newSection + after;\n }\n\n // Append section to end\n return `${existingContent.trimEnd()}\\n\\n${newSection}\\n`;\n}\n\n/**\n * Setup command implementation\n */\nfunction runSetup(options: { write?: boolean }) {\n const shouldWrite = options.write;\n\n // Find installed packages\n const installed = findInstalledPackages();\n\n if (installed.length === 0) {\n console.log(\"No @databricks/appkit packages found in node_modules.\");\n console.log(\"\\nInstall at least one of:\");\n for (const pkg of PACKAGES) {\n console.log(` npm install ${pkg.name}`);\n }\n process.exit(1);\n }\n\n console.log(\"Detected packages:\");\n installed.forEach((pkg) => {\n console.log(` ✓ ${pkg.name}`);\n });\n\n const claudePath = path.join(process.cwd(), \"CLAUDE.md\");\n const existingContent = fs.existsSync(claudePath)\n ? fs.readFileSync(claudePath, \"utf-8\")\n : null;\n\n let finalContent: string;\n let action: string;\n\n if (existingContent) {\n finalContent = updateContent(existingContent, installed);\n action = existingContent.includes(SECTION_START) ? \"Updated\" : \"Added to\";\n } else {\n finalContent = generateStandalone(installed);\n action = \"Created\";\n }\n\n if (shouldWrite) {\n fs.writeFileSync(claudePath, finalContent);\n console.log(`\\n✓ ${action} CLAUDE.md`);\n console.log(` Path: ${claudePath}`);\n } else {\n console.log(\"\\nTo create/update CLAUDE.md, run:\");\n console.log(\" npx appkit setup --write\\n\");\n\n if (existingContent) {\n console.log(\n `This will ${\n existingContent.includes(SECTION_START)\n ? \"update the existing\"\n : \"add a new\"\n } AppKit section.\\n`,\n );\n }\n\n console.log(\"Preview of AppKit section:\\n\");\n console.log(\"─\".repeat(50));\n console.log(generateSection(installed));\n console.log(\"─\".repeat(50));\n }\n}\n\nexport const setupCommand = new Command(\"setup\")\n .description(\"Setup CLAUDE.md with AppKit package references\")\n .option(\"-w, --write\", \"Create or update CLAUDE.md file in current directory\")\n .addHelpText(\n \"after\",\n `\nExamples:\n $ appkit setup\n $ appkit setup --write`,\n )\n .action(runSetup);\n"],"mappings":"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"}
1
+ {"version":3,"file":"setup.js","names":[],"sources":["../../../src/cli/commands/setup.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { Command } from \"commander\";\n\nconst PACKAGES = [\n { name: \"@databricks/appkit\", description: \"Backend SDK\" },\n {\n name: \"@databricks/appkit-ui\",\n description: \"UI Integration, Charts, Tables, SSE, and more.\",\n },\n];\n\nconst SECTION_START = \"<!-- appkit-instructions-start -->\";\nconst SECTION_END = \"<!-- appkit-instructions-end -->\";\n\n/**\n * Find which AppKit packages are installed by checking for package.json\n */\nfunction findInstalledPackages() {\n const cwd = process.cwd();\n const installed = [];\n\n for (const pkg of PACKAGES) {\n const packagePath = path.join(\n cwd,\n \"node_modules\",\n pkg.name,\n \"package.json\",\n );\n if (fs.existsSync(packagePath)) {\n installed.push(pkg);\n }\n }\n\n return installed;\n}\n\n/**\n * Generate the AppKit section content\n */\nfunction generateSection(packages: typeof PACKAGES) {\n const links = packages\n .map((pkg) => {\n const docPath = `./node_modules/${pkg.name}/CLAUDE.md`;\n return `- **${pkg.name}** (${pkg.description}): [${docPath}](${docPath})`;\n })\n .join(\"\\n\");\n\n return `${SECTION_START}\n## Databricks AppKit\n\nThis project uses Databricks AppKit packages. For AI assistant guidance on using these packages, refer to:\n\n${links}\n\n### Databricks Skills\n\nFor enhanced AI assistance with Databricks CLI operations, authentication, data exploration, and app development, install the Databricks skills:\n\n\\`\\`\\`bash\ndatabricks aitools install\n\\`\\`\\`\n${SECTION_END}`;\n}\n\n/**\n * Generate standalone CLAUDE.md content (when no existing file)\n */\nfunction generateStandalone(packages: typeof PACKAGES) {\n const links = packages\n .map((pkg) => {\n const docPath = `./node_modules/${pkg.name}/CLAUDE.md`;\n return `- **${pkg.name}** (${pkg.description}): [${docPath}](${docPath})`;\n })\n .join(\"\\n\");\n\n return `# AI Assistant Instructions\n\n${SECTION_START}\n## Databricks AppKit\n\nThis project uses Databricks AppKit packages. For AI assistant guidance on using these packages, refer to:\n\n${links}\n\n### Databricks Skills\n\nFor enhanced AI assistance with Databricks CLI operations, authentication, data exploration, and app development, install the Databricks skills:\n\n\\`\\`\\`bash\ndatabricks aitools install\n\\`\\`\\`\n${SECTION_END}\n`;\n}\n\n/**\n * Update existing content with AppKit section\n */\nfunction updateContent(existingContent: string, packages: typeof PACKAGES) {\n const newSection = generateSection(packages);\n\n // Check if AppKit section already exists\n const startIndex = existingContent.indexOf(SECTION_START);\n const endIndex = existingContent.indexOf(SECTION_END);\n\n if (startIndex !== -1 && endIndex !== -1) {\n // Replace existing section\n const before = existingContent.substring(0, startIndex);\n const after = existingContent.substring(endIndex + SECTION_END.length);\n return before + newSection + after;\n }\n\n // Append section to end\n return `${existingContent.trimEnd()}\\n\\n${newSection}\\n`;\n}\n\n/**\n * Setup command implementation\n */\nfunction runSetup(options: { write?: boolean }) {\n const shouldWrite = options.write;\n\n // Find installed packages\n const installed = findInstalledPackages();\n\n if (installed.length === 0) {\n console.log(\"No @databricks/appkit packages found in node_modules.\");\n console.log(\"\\nInstall at least one of:\");\n for (const pkg of PACKAGES) {\n console.log(` npm install ${pkg.name}`);\n }\n process.exit(1);\n }\n\n console.log(\"Detected packages:\");\n installed.forEach((pkg) => {\n console.log(` ✓ ${pkg.name}`);\n });\n\n const claudePath = path.join(process.cwd(), \"CLAUDE.md\");\n const existingContent = fs.existsSync(claudePath)\n ? fs.readFileSync(claudePath, \"utf-8\")\n : null;\n\n let finalContent: string;\n let action: string;\n\n if (existingContent) {\n finalContent = updateContent(existingContent, installed);\n action = existingContent.includes(SECTION_START) ? \"Updated\" : \"Added to\";\n } else {\n finalContent = generateStandalone(installed);\n action = \"Created\";\n }\n\n if (shouldWrite) {\n fs.writeFileSync(claudePath, finalContent);\n console.log(`\\n✓ ${action} CLAUDE.md`);\n console.log(` Path: ${claudePath}`);\n } else {\n console.log(\"\\nTo create/update CLAUDE.md, run:\");\n console.log(\" npx appkit setup --write\\n\");\n\n if (existingContent) {\n console.log(\n `This will ${\n existingContent.includes(SECTION_START)\n ? \"update the existing\"\n : \"add a new\"\n } AppKit section.\\n`,\n );\n }\n\n console.log(\"Preview of AppKit section:\\n\");\n console.log(\"─\".repeat(50));\n console.log(generateSection(installed));\n console.log(\"─\".repeat(50));\n }\n}\n\nexport const setupCommand = new Command(\"setup\")\n .description(\"Setup CLAUDE.md with AppKit package references\")\n .option(\"-w, --write\", \"Create or update CLAUDE.md file in current directory\")\n .addHelpText(\n \"after\",\n `\nExamples:\n $ appkit setup\n $ appkit setup --write`,\n )\n .action(runSetup);\n"],"mappings":";;;;;AAIA,MAAM,WAAW,CACf;CAAE,MAAM;CAAsB,aAAa;CAAe,EAC1D;CACE,MAAM;CACN,aAAa;CACd,CACF;AAED,MAAM,gBAAgB;AACtB,MAAM,cAAc;;;;AAKpB,SAAS,wBAAwB;CAC/B,MAAM,MAAM,QAAQ,KAAK;CACzB,MAAM,YAAY,EAAE;AAEpB,MAAK,MAAM,OAAO,UAAU;EAC1B,MAAM,cAAc,KAAK,KACvB,KACA,gBACA,IAAI,MACJ,eACD;AACD,MAAI,GAAG,WAAW,YAAY,CAC5B,WAAU,KAAK,IAAI;;AAIvB,QAAO;;;;;AAMT,SAAS,gBAAgB,UAA2B;AAQlD,QAAO,GAAG,cAAc;;;;;EAPV,SACX,KAAK,QAAQ;EACZ,MAAM,UAAU,kBAAkB,IAAI,KAAK;AAC3C,SAAO,OAAO,IAAI,KAAK,MAAM,IAAI,YAAY,MAAM,QAAQ,IAAI,QAAQ;GACvE,CACD,KAAK,KAAK,CAOP;;;;;;;;;EASN;;;;;AAMF,SAAS,mBAAmB,UAA2B;AAQrD,QAAO;;EAEP,cAAc;;;;;EATA,SACX,KAAK,QAAQ;EACZ,MAAM,UAAU,kBAAkB,IAAI,KAAK;AAC3C,SAAO,OAAO,IAAI,KAAK,MAAM,IAAI,YAAY,MAAM,QAAQ,IAAI,QAAQ;GACvE,CACD,KAAK,KAAK,CASP;;;;;;;;;EASN,YAAY;;;;;;AAOd,SAAS,cAAc,iBAAyB,UAA2B;CACzE,MAAM,aAAa,gBAAgB,SAAS;CAG5C,MAAM,aAAa,gBAAgB,QAAQ,cAAc;CACzD,MAAM,WAAW,gBAAgB,QAAQ,YAAY;AAErD,KAAI,eAAe,MAAM,aAAa,IAAI;EAExC,MAAM,SAAS,gBAAgB,UAAU,GAAG,WAAW;EACvD,MAAM,QAAQ,gBAAgB,UAAU,WAAW,GAAmB;AACtE,SAAO,SAAS,aAAa;;AAI/B,QAAO,GAAG,gBAAgB,SAAS,CAAC,MAAM,WAAW;;;;;AAMvD,SAAS,SAAS,SAA8B;CAC9C,MAAM,cAAc,QAAQ;CAG5B,MAAM,YAAY,uBAAuB;AAEzC,KAAI,UAAU,WAAW,GAAG;AAC1B,UAAQ,IAAI,wDAAwD;AACpE,UAAQ,IAAI,6BAA6B;AACzC,OAAK,MAAM,OAAO,SAChB,SAAQ,IAAI,iBAAiB,IAAI,OAAO;AAE1C,UAAQ,KAAK,EAAE;;AAGjB,SAAQ,IAAI,qBAAqB;AACjC,WAAU,SAAS,QAAQ;AACzB,UAAQ,IAAI,OAAO,IAAI,OAAO;GAC9B;CAEF,MAAM,aAAa,KAAK,KAAK,QAAQ,KAAK,EAAE,YAAY;CACxD,MAAM,kBAAkB,GAAG,WAAW,WAAW,GAC7C,GAAG,aAAa,YAAY,QAAQ,GACpC;CAEJ,IAAI;CACJ,IAAI;AAEJ,KAAI,iBAAiB;AACnB,iBAAe,cAAc,iBAAiB,UAAU;AACxD,WAAS,gBAAgB,SAAS,cAAc,GAAG,YAAY;QAC1D;AACL,iBAAe,mBAAmB,UAAU;AAC5C,WAAS;;AAGX,KAAI,aAAa;AACf,KAAG,cAAc,YAAY,aAAa;AAC1C,UAAQ,IAAI,OAAO,OAAO,YAAY;AACtC,UAAQ,IAAI,WAAW,aAAa;QAC/B;AACL,UAAQ,IAAI,qCAAqC;AACjD,UAAQ,IAAI,+BAA+B;AAE3C,MAAI,gBACF,SAAQ,IACN,aACE,gBAAgB,SAAS,cAAc,GACnC,wBACA,YACL,oBACF;AAGH,UAAQ,IAAI,+BAA+B;AAC3C,UAAQ,IAAI,IAAI,OAAO,GAAG,CAAC;AAC3B,UAAQ,IAAI,gBAAgB,UAAU,CAAC;AACvC,UAAQ,IAAI,IAAI,OAAO,GAAG,CAAC;;;AAI/B,MAAa,eAAe,IAAI,QAAQ,QAAQ,CAC7C,YAAY,iDAAiD,CAC7D,OAAO,eAAe,uDAAuD,CAC7E,YACC,SACA;;;0BAID,CACA,OAAO,SAAS"}