@databricks/appkit 0.22.0 → 0.24.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 (125) hide show
  1. package/CLAUDE.md +11 -0
  2. package/NOTICE.md +1 -0
  3. package/dist/appkit/package.js +1 -1
  4. package/dist/cache/index.js.map +1 -1
  5. package/dist/cli/commands/docs.js +7 -1
  6. package/dist/cli/commands/docs.js.map +1 -1
  7. package/dist/cli/commands/generate-types.js +27 -15
  8. package/dist/cli/commands/generate-types.js.map +1 -1
  9. package/dist/cli/commands/lint.js +3 -1
  10. package/dist/cli/commands/lint.js.map +1 -1
  11. package/dist/cli/commands/plugin/add-resource/add-resource.js +73 -8
  12. package/dist/cli/commands/plugin/add-resource/add-resource.js.map +1 -1
  13. package/dist/cli/commands/plugin/create/create.js +164 -20
  14. package/dist/cli/commands/plugin/create/create.js.map +1 -1
  15. package/dist/cli/commands/plugin/create/resource-defaults.js +5 -1
  16. package/dist/cli/commands/plugin/create/resource-defaults.js.map +1 -1
  17. package/dist/cli/commands/plugin/index.js +7 -1
  18. package/dist/cli/commands/plugin/index.js.map +1 -1
  19. package/dist/cli/commands/plugin/list/list.js +7 -1
  20. package/dist/cli/commands/plugin/list/list.js.map +1 -1
  21. package/dist/cli/commands/plugin/sync/sync.js +27 -14
  22. package/dist/cli/commands/plugin/sync/sync.js.map +1 -1
  23. package/dist/cli/commands/plugin/validate/validate.js +39 -9
  24. package/dist/cli/commands/plugin/validate/validate.js.map +1 -1
  25. package/dist/cli/commands/setup.js +6 -5
  26. package/dist/cli/commands/setup.js.map +1 -1
  27. package/dist/connectors/index.js +1 -0
  28. package/dist/connectors/lakebase/index.js.map +1 -1
  29. package/dist/connectors/lakebase-v1/client.js.map +1 -1
  30. package/dist/connectors/serving/client.js +47 -0
  31. package/dist/connectors/serving/client.js.map +1 -0
  32. package/dist/connectors/vector-search/client.js +9 -0
  33. package/dist/connectors/vector-search/client.js.map +1 -0
  34. package/dist/connectors/vector-search/index.js +3 -0
  35. package/dist/index.d.ts +6 -1
  36. package/dist/index.js +4 -1
  37. package/dist/index.js.map +1 -1
  38. package/dist/plugin/dev-reader.js.map +1 -1
  39. package/dist/plugin/execution-result.d.ts +26 -0
  40. package/dist/plugin/execution-result.d.ts.map +1 -0
  41. package/dist/plugin/index.d.ts +1 -0
  42. package/dist/plugin/interceptors/retry.js +1 -1
  43. package/dist/plugin/interceptors/retry.js.map +1 -1
  44. package/dist/plugin/plugin.d.ts +7 -4
  45. package/dist/plugin/plugin.d.ts.map +1 -1
  46. package/dist/plugin/plugin.js +36 -5
  47. package/dist/plugin/plugin.js.map +1 -1
  48. package/dist/plugins/analytics/analytics.d.ts.map +1 -1
  49. package/dist/plugins/analytics/analytics.js +2 -3
  50. package/dist/plugins/analytics/analytics.js.map +1 -1
  51. package/dist/plugins/files/plugin.d.ts +1 -0
  52. package/dist/plugins/files/plugin.d.ts.map +1 -1
  53. package/dist/plugins/files/plugin.js +36 -59
  54. package/dist/plugins/files/plugin.js.map +1 -1
  55. package/dist/plugins/index.d.ts +4 -1
  56. package/dist/plugins/index.js +2 -0
  57. package/dist/plugins/server/index.d.ts +1 -1
  58. package/dist/plugins/server/vite-dev-server.js +6 -1
  59. package/dist/plugins/server/vite-dev-server.js.map +1 -1
  60. package/dist/plugins/serving/defaults.js +10 -0
  61. package/dist/plugins/serving/defaults.js.map +1 -0
  62. package/dist/plugins/serving/index.d.ts +2 -0
  63. package/dist/plugins/serving/index.js +3 -0
  64. package/dist/plugins/serving/manifest.js +53 -0
  65. package/dist/plugins/serving/manifest.js.map +1 -0
  66. package/dist/plugins/serving/schema-filter.js +52 -0
  67. package/dist/plugins/serving/schema-filter.js.map +1 -0
  68. package/dist/plugins/serving/serving.d.ts +38 -0
  69. package/dist/plugins/serving/serving.d.ts.map +1 -0
  70. package/dist/plugins/serving/serving.js +227 -0
  71. package/dist/plugins/serving/serving.js.map +1 -0
  72. package/dist/plugins/serving/types.d.ts +59 -0
  73. package/dist/plugins/serving/types.d.ts.map +1 -0
  74. package/dist/shared/src/execute.d.ts +1 -1
  75. package/dist/stream/stream-manager.js +1 -0
  76. package/dist/stream/stream-manager.js.map +1 -1
  77. package/dist/stream/types.js +2 -1
  78. package/dist/stream/types.js.map +1 -1
  79. package/dist/type-generator/cache.js +1 -1
  80. package/dist/type-generator/cache.js.map +1 -1
  81. package/dist/type-generator/index.js +15 -1
  82. package/dist/type-generator/index.js.map +1 -1
  83. package/dist/type-generator/migration.js +155 -0
  84. package/dist/type-generator/migration.js.map +1 -0
  85. package/dist/type-generator/query-registry.js +77 -4
  86. package/dist/type-generator/query-registry.js.map +1 -1
  87. package/dist/type-generator/serving/cache.js +38 -0
  88. package/dist/type-generator/serving/cache.js.map +1 -0
  89. package/dist/type-generator/serving/converter.js +108 -0
  90. package/dist/type-generator/serving/converter.js.map +1 -0
  91. package/dist/type-generator/serving/fetcher.js +54 -0
  92. package/dist/type-generator/serving/fetcher.js.map +1 -0
  93. package/dist/type-generator/serving/generator.js +206 -0
  94. package/dist/type-generator/serving/generator.js.map +1 -0
  95. package/dist/type-generator/serving/server-file-extractor.d.ts +22 -0
  96. package/dist/type-generator/serving/server-file-extractor.d.ts.map +1 -0
  97. package/dist/type-generator/serving/server-file-extractor.js +131 -0
  98. package/dist/type-generator/serving/server-file-extractor.js.map +1 -0
  99. package/dist/type-generator/serving/vite-plugin.d.ts +24 -0
  100. package/dist/type-generator/serving/vite-plugin.d.ts.map +1 -0
  101. package/dist/type-generator/serving/vite-plugin.js +60 -0
  102. package/dist/type-generator/serving/vite-plugin.js.map +1 -0
  103. package/dist/type-generator/vite-plugin.d.ts.map +1 -1
  104. package/dist/type-generator/vite-plugin.js +3 -4
  105. package/dist/type-generator/vite-plugin.js.map +1 -1
  106. package/docs/api/appkit/Class.Plugin.md +8 -3
  107. package/docs/api/appkit/Function.appKitServingTypesPlugin.md +24 -0
  108. package/docs/api/appkit/Function.extractServingEndpoints.md +22 -0
  109. package/docs/api/appkit/Function.findServerFile.md +20 -0
  110. package/docs/api/appkit/Interface.EndpointConfig.md +23 -0
  111. package/docs/api/appkit/Interface.ServingEndpointEntry.md +30 -0
  112. package/docs/api/appkit/Interface.ServingEndpointRegistry.md +3 -0
  113. package/docs/api/appkit/TypeAlias.ExecutionResult.md +36 -0
  114. package/docs/api/appkit/TypeAlias.ServingFactory.md +19 -0
  115. package/docs/api/appkit.md +39 -31
  116. package/docs/development/type-generation.md +6 -5
  117. package/docs/faq.md +66 -0
  118. package/docs/plugins/analytics.md +1 -1
  119. package/docs/plugins/custom-plugins.md +4 -0
  120. package/docs/plugins/plugin-management.md +22 -6
  121. package/docs/plugins/serving.md +223 -0
  122. package/docs/plugins/vector-search.md +247 -0
  123. package/llms.txt +11 -0
  124. package/package.json +2 -2
  125. package/sbom.cdx.json +1 -1
@@ -1,16 +1,150 @@
1
- import { RESOURCE_TYPE_OPTIONS } from "./resource-defaults.js";
1
+ import { DEFAULT_PERMISSION_BY_TYPE, RESOURCE_TYPE_OPTIONS, getDefaultFieldsForType, getValidResourceTypes, humanizeResourceType, resourceKeyFromType } from "./resource-defaults.js";
2
2
  import { promptOneResource } from "./prompt-resource.js";
3
3
  import { resolveTargetDir, scaffoldPlugin } from "./scaffold.js";
4
4
  import fs from "node:fs";
5
5
  import path from "node:path";
6
- import { Command } from "commander";
6
+ import { Command, Option } from "commander";
7
7
  import process from "node:process";
8
8
  import { cancel, confirm, intro, isCancel, multiselect, outro, select, spinner, text } from "@clack/prompts";
9
9
 
10
10
  //#region src/cli/commands/plugin/create/create.ts
11
11
  const NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
12
12
  const DEFAULT_VERSION = "0.1.0";
13
- async function runPluginCreate() {
13
+ const VALID_PLACEMENTS = ["in-repo", "isolated"];
14
+ const REQUIRED_FLAGS = [
15
+ "placement",
16
+ "path",
17
+ "name",
18
+ "description"
19
+ ];
20
+ function deriveDisplayName(name) {
21
+ return name.split("-").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(" ");
22
+ }
23
+ function deriveExportName(name) {
24
+ return name.split("-").map((s, i) => i === 0 ? s : s.charAt(0).toUpperCase() + s.slice(1)).join("");
25
+ }
26
+ function buildResourceFromType(type) {
27
+ return {
28
+ type,
29
+ required: true,
30
+ description: `Required for ${humanizeResourceType(type)} functionality.`,
31
+ resourceKey: resourceKeyFromType(type),
32
+ permission: DEFAULT_PERMISSION_BY_TYPE[type] ?? "CAN_VIEW",
33
+ fields: getDefaultFieldsForType(type)
34
+ };
35
+ }
36
+ function parseResourcesJson(json) {
37
+ let parsed;
38
+ try {
39
+ parsed = JSON.parse(json);
40
+ } catch {
41
+ console.error("Error: --resources-json must be valid JSON.");
42
+ console.error(" Example: --resources-json '[{\"type\":\"sql_warehouse\"}]'");
43
+ process.exit(1);
44
+ }
45
+ if (!Array.isArray(parsed)) {
46
+ console.error("Error: --resources-json must be a JSON array.");
47
+ console.error(" Example: --resources-json '[{\"type\":\"sql_warehouse\"}]'");
48
+ process.exit(1);
49
+ }
50
+ return parsed.map((entry, i) => {
51
+ if (entry == null || typeof entry !== "object") {
52
+ console.error(`Error: --resources-json entry ${i} is not an object.`);
53
+ process.exit(1);
54
+ }
55
+ if (!entry.type || typeof entry.type !== "string") {
56
+ console.error(`Error: --resources-json entry ${i} missing required "type" field.`);
57
+ process.exit(1);
58
+ }
59
+ validateResourceType(entry.type);
60
+ const defaults = buildResourceFromType(entry.type);
61
+ return {
62
+ type: entry.type,
63
+ required: entry.required ?? defaults.required,
64
+ description: entry.description ?? defaults.description,
65
+ resourceKey: entry.resourceKey ?? defaults.resourceKey,
66
+ permission: entry.permission ?? defaults.permission,
67
+ fields: entry.fields ?? defaults.fields
68
+ };
69
+ });
70
+ }
71
+ function validateResourceType(type) {
72
+ const validTypes = getValidResourceTypes();
73
+ if (!validTypes.includes(type)) {
74
+ console.error(`Error: Unknown resource type "${type}".`);
75
+ console.error(` Valid types: ${validTypes.join(", ")}`);
76
+ process.exit(1);
77
+ }
78
+ }
79
+ function parseResourcesShorthand(csv) {
80
+ const types = csv.split(",").map((s) => s.trim()).filter(Boolean);
81
+ for (const t of types) validateResourceType(t);
82
+ return types.map(buildResourceFromType);
83
+ }
84
+ function printNextSteps(answers, targetDir) {
85
+ const relativePath = path.relative(process.cwd(), targetDir);
86
+ const importPath = relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
87
+ const exportName = deriveExportName(answers.name);
88
+ console.log("\nNext steps:\n");
89
+ if (answers.placement === "in-repo") {
90
+ console.log(` 1. Import and register in your server:`);
91
+ console.log(` import { ${exportName} } from "${importPath}";`);
92
+ console.log(` createApp({ plugins: [ ..., ${exportName}() ] });`);
93
+ console.log(` 2. Run \`npx appkit plugin sync --write\` to update appkit.plugins.json.\n`);
94
+ } else {
95
+ console.log(` 1. cd into the new package and install dependencies:`);
96
+ console.log(` cd ${answers.targetPath} && pnpm install`);
97
+ console.log(` 2. Build: pnpm build`);
98
+ console.log(` 3. In your app: pnpm add ./${answers.targetPath} @databricks/appkit`);
99
+ console.log(` 4. Import and register: import { ${exportName} } from "<package-name>";\n`);
100
+ }
101
+ }
102
+ function runNonInteractive(opts) {
103
+ const missing = REQUIRED_FLAGS.filter((f) => !opts[f]);
104
+ if (missing.length > 0) {
105
+ console.error(`Error: Non-interactive mode requires: ${REQUIRED_FLAGS.map((f) => `--${f}`).join(", ")}`);
106
+ console.error(`Missing: ${missing.map((f) => `--${f}`).join(", ")}`);
107
+ console.error(" appkit plugin create --placement in-repo --path plugins/my-plugin --name my-plugin --description \"Does X\"");
108
+ process.exit(1);
109
+ }
110
+ const placement = opts.placement;
111
+ if (!VALID_PLACEMENTS.includes(placement)) {
112
+ console.error(`Error: --placement must be one of: ${VALID_PLACEMENTS.join(", ")}`);
113
+ process.exit(1);
114
+ }
115
+ const targetPath = opts.path.trim();
116
+ if (placement === "in-repo" && (path.isAbsolute(targetPath) || targetPath.startsWith(".."))) {
117
+ console.error("Error: --path must be a relative path under the current directory for in-repo plugins.");
118
+ process.exit(1);
119
+ }
120
+ const name = opts.name;
121
+ if (!NAME_PATTERN.test(name)) {
122
+ console.error("Error: --name must be lowercase, start with a letter, and use only letters, numbers, and hyphens.");
123
+ process.exit(1);
124
+ }
125
+ let resources = [];
126
+ if (opts.resourcesJson) resources = parseResourcesJson(opts.resourcesJson);
127
+ else if (opts.resources) resources = parseResourcesShorthand(opts.resources);
128
+ const answers = {
129
+ placement,
130
+ targetPath,
131
+ name: name.trim(),
132
+ displayName: opts.displayName?.trim() || deriveDisplayName(name),
133
+ description: opts.description.trim(),
134
+ resources,
135
+ version: DEFAULT_VERSION
136
+ };
137
+ const targetDir = resolveTargetDir(process.cwd(), answers);
138
+ if (fs.existsSync(targetDir) && fs.readdirSync(targetDir).length > 0 && !opts.force) {
139
+ console.error(`Error: Directory ${answers.targetPath} already exists and is not empty.`);
140
+ console.error(" Use --force to overwrite.");
141
+ process.exit(1);
142
+ }
143
+ scaffoldPlugin(targetDir, answers, { isolated: placement === "isolated" });
144
+ console.log(`Plugin "${answers.name}" created at ${path.relative(process.cwd(), targetDir)}`);
145
+ printNextSteps(answers, targetDir);
146
+ }
147
+ async function runInteractive() {
14
148
  intro("Create a new AppKit plugin");
15
149
  try {
16
150
  const placement = await select({
@@ -133,29 +267,39 @@ async function runPluginCreate() {
133
267
  s.stop("Failed.");
134
268
  throw err;
135
269
  }
136
- const relativePath = path.relative(process.cwd(), targetDir);
137
- const importPath = relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
138
- const exportName = answers.name.split("-").map((s, i) => i === 0 ? s : s.charAt(0).toUpperCase() + s.slice(1)).join("");
139
270
  outro("Plugin created successfully.");
140
- console.log("\nNext steps:\n");
141
- if (placement === "in-repo") {
142
- console.log(` 1. Import and register in your server:`);
143
- console.log(` import { ${exportName} } from "${importPath}";`);
144
- console.log(` createApp({ plugins: [ ..., ${exportName}() ] });`);
145
- console.log(` 2. Run \`npx appkit plugin sync --write\` to update appkit.plugins.json.\n`);
146
- } else {
147
- console.log(` 1. cd into the new package and install dependencies:`);
148
- console.log(` cd ${answers.targetPath} && pnpm install`);
149
- console.log(` 2. Build: pnpm build`);
150
- console.log(` 3. In your app: pnpm add ./${answers.targetPath} @databricks/appkit`);
151
- console.log(` 4. Import and register: import { ${exportName} } from "<package-name>";\n`);
152
- }
271
+ printNextSteps(answers, targetDir);
153
272
  } catch (err) {
154
273
  console.error(err);
155
274
  process.exit(1);
156
275
  }
157
276
  }
158
- const pluginCreateCommand = new Command("create").description("Scaffold a new AppKit plugin (interactive)").action(runPluginCreate);
277
+ const OPTIONAL_FLAGS = [
278
+ "displayName",
279
+ "resources",
280
+ "resourcesJson",
281
+ "force"
282
+ ];
283
+ async function runPluginCreate(opts) {
284
+ if (REQUIRED_FLAGS.some((f) => opts[f] !== void 0)) runNonInteractive(opts);
285
+ else {
286
+ if (OPTIONAL_FLAGS.some((f) => opts[f] !== void 0 && opts[f] !== false)) {
287
+ console.error(`Error: Non-interactive mode requires: ${REQUIRED_FLAGS.map((f) => `--${f}`).join(", ")}`);
288
+ console.error(" appkit plugin create --placement in-repo --path plugins/my-plugin --name my-plugin --description \"Does X\"");
289
+ process.exit(1);
290
+ }
291
+ await runInteractive();
292
+ }
293
+ }
294
+ const pluginCreateCommand = new Command("create").description("Scaffold a new AppKit plugin").option("--placement <type>", "Where the plugin lives (in-repo, isolated)").option("--path <dir>", "Target directory for the plugin").option("--name <id>", "Plugin name (lowercase, hyphens allowed)").option("--display-name <name>", "Human-readable display name").option("--description <text>", "Short description of the plugin").addOption(new Option("--resources <types>", "Comma-separated resource types (e.g. sql_warehouse,volume)").conflicts("resourcesJson")).addOption(new Option("--resources-json <json>", "JSON array of resource specs (e.g. '[{\"type\":\"sql_warehouse\"}]')").conflicts("resources")).option("-f, --force", "Overwrite existing directory without confirmation").addHelpText("after", `
295
+ Examples:
296
+ $ appkit plugin create
297
+ $ appkit plugin create --placement in-repo --path plugins/my-plugin --name my-plugin --description "Does X"
298
+ $ appkit plugin create --placement in-repo --path plugins/my-plugin --name my-plugin --description "Does X" --resources sql_warehouse,volume --force
299
+ $ appkit plugin create --placement isolated --path appkit-plugin-ml --name ml --description "ML" --resources-json '[{"type":"serving_endpoint"}]'`).action((opts) => runPluginCreate(opts).catch((err) => {
300
+ console.error(err);
301
+ process.exit(1);
302
+ }));
159
303
 
160
304
  //#endregion
161
305
  export { pluginCreateCommand };
@@ -1 +1 @@
1
- {"version":3,"file":"create.js","names":[],"sources":["../../../../../src/cli/commands/plugin/create/create.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport process from \"node:process\";\nimport {\n cancel,\n confirm,\n intro,\n isCancel,\n multiselect,\n outro,\n select,\n spinner,\n text,\n} from \"@clack/prompts\";\nimport { Command } from \"commander\";\nimport { promptOneResource } from \"./prompt-resource\";\nimport { RESOURCE_TYPE_OPTIONS } from \"./resource-defaults\";\nimport { resolveTargetDir, scaffoldPlugin } from \"./scaffold\";\nimport type { CreateAnswers, Placement } from \"./types\";\n\nconst NAME_PATTERN = /^[a-z][a-z0-9-]*$/;\nconst DEFAULT_VERSION = \"0.1.0\";\n\nasync function runPluginCreate(): Promise<void> {\n intro(\"Create a new AppKit plugin\");\n\n try {\n const placement = await select<Placement>({\n message: \"Where should the plugin live?\",\n options: [\n {\n value: \"in-repo\",\n label: \"In this repository (e.g. plugins/my-plugin)\",\n hint: \"folder path\",\n },\n {\n value: \"isolated\",\n label: \"New isolated package\",\n hint: \"full package with package.json\",\n },\n ],\n });\n if (isCancel(placement)) {\n cancel(\"Cancelled.\");\n process.exit(0);\n }\n\n const placementPrompt =\n placement === \"in-repo\"\n ? \"Folder path for the plugin (e.g. plugins/my-feature)\"\n : \"Directory name for the new package (e.g. appkit-plugin-my-feature)\";\n const targetPath = await text({\n message: placementPrompt,\n placeholder:\n placement === \"in-repo\"\n ? \"plugins/my-plugin\"\n : \"appkit-plugin-my-feature\",\n validate(value) {\n if (!value?.trim()) return \"Path is required.\";\n if (\n placement === \"in-repo\" &&\n (path.isAbsolute(value) || value.trim().startsWith(\"..\"))\n ) {\n return \"Use a relative path under the current directory (e.g. plugins/my-plugin).\";\n }\n return undefined;\n },\n });\n if (isCancel(targetPath)) {\n cancel(\"Cancelled.\");\n process.exit(0);\n }\n\n const name = await text({\n message: \"Plugin name (id)\",\n placeholder: \"my-plugin\",\n validate(value) {\n if (!value?.trim()) return \"Name is required.\";\n if (!NAME_PATTERN.test(value as string)) {\n return \"Must be lowercase, start with a letter, and use only letters, numbers, and hyphens.\";\n }\n return undefined;\n },\n });\n if (isCancel(name)) {\n cancel(\"Cancelled.\");\n process.exit(0);\n }\n\n const displayName = await text({\n message: \"Display name\",\n placeholder: \"My Plugin\",\n initialValue: name\n .split(\"-\")\n .map((s) => s.charAt(0).toUpperCase() + s.slice(1))\n .join(\" \"),\n validate(value) {\n if (!value?.trim()) return \"Display name is required.\";\n return undefined;\n },\n });\n if (isCancel(displayName)) {\n cancel(\"Cancelled.\");\n process.exit(0);\n }\n\n const description = await text({\n message: \"Short description\",\n placeholder: \"What does this plugin do?\",\n validate(value) {\n if (!value?.trim()) return \"Description is required.\";\n return undefined;\n },\n });\n if (isCancel(description)) {\n cancel(\"Cancelled.\");\n process.exit(0);\n }\n\n const resourceTypes = await multiselect({\n message: \"Which Databricks resources does this plugin need?\",\n options: RESOURCE_TYPE_OPTIONS.map((o) => ({\n value: o.value,\n label: o.label,\n })),\n required: false,\n });\n if (isCancel(resourceTypes)) {\n cancel(\"Cancelled.\");\n process.exit(0);\n }\n\n const resources: CreateAnswers[\"resources\"] = [];\n for (const type of resourceTypes as string[]) {\n const spec = await promptOneResource({ type });\n if (!spec) {\n cancel(\"Cancelled.\");\n process.exit(0);\n }\n resources.push({\n type: spec.type,\n required: spec.required,\n description: spec.description,\n resourceKey: spec.resourceKey,\n permission: spec.permission,\n fields: spec.fields,\n });\n }\n\n const answers: CreateAnswers = {\n placement,\n targetPath: (targetPath as string).trim(),\n name: (name as string).trim(),\n displayName: (displayName as string).trim(),\n description: (description as string).trim(),\n resources,\n version: DEFAULT_VERSION,\n };\n\n const targetDir = resolveTargetDir(process.cwd(), answers);\n const dirExists = fs.existsSync(targetDir);\n const hasContent = dirExists && fs.readdirSync(targetDir).length > 0;\n if (hasContent) {\n const overwrite = await confirm({\n message: `Directory ${answers.targetPath} already exists and is not empty. Overwrite?`,\n initialValue: false,\n });\n if (isCancel(overwrite) || !overwrite) {\n cancel(\"Cancelled.\");\n process.exit(0);\n }\n }\n\n const s = spinner();\n s.start(\"Writing files…\");\n try {\n scaffoldPlugin(targetDir, answers, {\n isolated: placement === \"isolated\",\n });\n s.stop(\"Files written.\");\n } catch (err) {\n s.stop(\"Failed.\");\n throw err;\n }\n\n const relativePath = path.relative(process.cwd(), targetDir);\n const importPath = relativePath.startsWith(\".\")\n ? relativePath\n : `./${relativePath}`;\n const exportName = answers.name\n .split(\"-\")\n .map((s, i) => (i === 0 ? s : s.charAt(0).toUpperCase() + s.slice(1)))\n .join(\"\");\n\n outro(\"Plugin created successfully.\");\n\n console.log(\"\\nNext steps:\\n\");\n if (placement === \"in-repo\") {\n console.log(` 1. Import and register in your server:`);\n console.log(` import { ${exportName} } from \"${importPath}\";`);\n console.log(` createApp({ plugins: [ ..., ${exportName}() ] });`);\n console.log(\n ` 2. Run \\`npx appkit plugin sync --write\\` to update appkit.plugins.json.\\n`,\n );\n } else {\n console.log(` 1. cd into the new package and install dependencies:`);\n console.log(` cd ${answers.targetPath} && pnpm install`);\n console.log(` 2. Build: pnpm build`);\n console.log(\n ` 3. In your app: pnpm add ./${answers.targetPath} @databricks/appkit`,\n );\n console.log(\n ` 4. Import and register: import { ${exportName} } from \"<package-name>\";\\n`,\n );\n }\n } catch (err) {\n console.error(err);\n process.exit(1);\n }\n}\n\nexport const pluginCreateCommand = new Command(\"create\")\n .description(\"Scaffold a new AppKit plugin (interactive)\")\n .action(runPluginCreate);\n"],"mappings":"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"}
1
+ {"version":3,"file":"create.js","names":[],"sources":["../../../../../src/cli/commands/plugin/create/create.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport process from \"node:process\";\nimport {\n cancel,\n confirm,\n intro,\n isCancel,\n multiselect,\n outro,\n select,\n spinner,\n text,\n} from \"@clack/prompts\";\nimport { Command, Option } from \"commander\";\nimport { promptOneResource } from \"./prompt-resource\";\nimport {\n DEFAULT_PERMISSION_BY_TYPE,\n getDefaultFieldsForType,\n getValidResourceTypes,\n humanizeResourceType,\n RESOURCE_TYPE_OPTIONS,\n resourceKeyFromType,\n} from \"./resource-defaults\";\nimport { resolveTargetDir, scaffoldPlugin } from \"./scaffold\";\nimport type { CreateAnswers, Placement, SelectedResource } from \"./types\";\n\nconst NAME_PATTERN = /^[a-z][a-z0-9-]*$/;\nconst DEFAULT_VERSION = \"0.1.0\";\nconst VALID_PLACEMENTS: Placement[] = [\"in-repo\", \"isolated\"];\nconst REQUIRED_FLAGS = [\"placement\", \"path\", \"name\", \"description\"] as const;\n\ninterface CreateOptions {\n placement?: string;\n path?: string;\n name?: string;\n displayName?: string;\n description?: string;\n resources?: string;\n resourcesJson?: string;\n force?: boolean;\n}\n\nfunction deriveDisplayName(name: string): string {\n return name\n .split(\"-\")\n .map((s) => s.charAt(0).toUpperCase() + s.slice(1))\n .join(\" \");\n}\n\nfunction deriveExportName(name: string): string {\n return name\n .split(\"-\")\n .map((s, i) => (i === 0 ? s : s.charAt(0).toUpperCase() + s.slice(1)))\n .join(\"\");\n}\n\nfunction buildResourceFromType(type: string): SelectedResource {\n return {\n type,\n required: true,\n description: `Required for ${humanizeResourceType(type)} functionality.`,\n resourceKey: resourceKeyFromType(type),\n permission: DEFAULT_PERMISSION_BY_TYPE[type] ?? \"CAN_VIEW\",\n fields: getDefaultFieldsForType(type),\n };\n}\n\ninterface JsonResourceEntry {\n type: string;\n required?: boolean;\n description?: string;\n resourceKey?: string;\n permission?: string;\n fields?: Record<string, { env: string; description?: string }>;\n}\n\nfunction parseResourcesJson(json: string): SelectedResource[] {\n let parsed: unknown;\n try {\n parsed = JSON.parse(json);\n } catch {\n console.error(\"Error: --resources-json must be valid JSON.\");\n console.error(' Example: --resources-json \\'[{\"type\":\"sql_warehouse\"}]\\'');\n process.exit(1);\n }\n\n if (!Array.isArray(parsed)) {\n console.error(\"Error: --resources-json must be a JSON array.\");\n console.error(' Example: --resources-json \\'[{\"type\":\"sql_warehouse\"}]\\'');\n process.exit(1);\n }\n\n return (parsed as JsonResourceEntry[]).map((entry, i) => {\n if (entry == null || typeof entry !== \"object\") {\n console.error(`Error: --resources-json entry ${i} is not an object.`);\n process.exit(1);\n }\n if (!entry.type || typeof entry.type !== \"string\") {\n console.error(\n `Error: --resources-json entry ${i} missing required \"type\" field.`,\n );\n process.exit(1);\n }\n validateResourceType(entry.type);\n const defaults = buildResourceFromType(entry.type);\n return {\n type: entry.type,\n required: entry.required ?? defaults.required,\n description: entry.description ?? defaults.description,\n resourceKey: entry.resourceKey ?? defaults.resourceKey,\n permission: entry.permission ?? defaults.permission,\n fields: entry.fields ?? defaults.fields,\n };\n });\n}\n\nfunction validateResourceType(type: string): void {\n const validTypes = getValidResourceTypes();\n if (!validTypes.includes(type)) {\n console.error(`Error: Unknown resource type \"${type}\".`);\n console.error(` Valid types: ${validTypes.join(\", \")}`);\n process.exit(1);\n }\n}\n\nfunction parseResourcesShorthand(csv: string): SelectedResource[] {\n const types = csv\n .split(\",\")\n .map((s) => s.trim())\n .filter(Boolean);\n for (const t of types) validateResourceType(t);\n return types.map(buildResourceFromType);\n}\n\nfunction printNextSteps(answers: CreateAnswers, targetDir: string): void {\n const relativePath = path.relative(process.cwd(), targetDir);\n const importPath = relativePath.startsWith(\".\")\n ? relativePath\n : `./${relativePath}`;\n const exportName = deriveExportName(answers.name);\n\n console.log(\"\\nNext steps:\\n\");\n if (answers.placement === \"in-repo\") {\n console.log(` 1. Import and register in your server:`);\n console.log(` import { ${exportName} } from \"${importPath}\";`);\n console.log(` createApp({ plugins: [ ..., ${exportName}() ] });`);\n console.log(\n ` 2. Run \\`npx appkit plugin sync --write\\` to update appkit.plugins.json.\\n`,\n );\n } else {\n console.log(` 1. cd into the new package and install dependencies:`);\n console.log(` cd ${answers.targetPath} && pnpm install`);\n console.log(` 2. Build: pnpm build`);\n console.log(\n ` 3. In your app: pnpm add ./${answers.targetPath} @databricks/appkit`,\n );\n console.log(\n ` 4. Import and register: import { ${exportName} } from \"<package-name>\";\\n`,\n );\n }\n}\n\nfunction runNonInteractive(opts: CreateOptions): void {\n const missing = REQUIRED_FLAGS.filter((f) => !opts[f]);\n if (missing.length > 0) {\n console.error(\n `Error: Non-interactive mode requires: ${REQUIRED_FLAGS.map((f) => `--${f}`).join(\", \")}`,\n );\n console.error(`Missing: ${missing.map((f) => `--${f}`).join(\", \")}`);\n console.error(\n ' appkit plugin create --placement in-repo --path plugins/my-plugin --name my-plugin --description \"Does X\"',\n );\n process.exit(1);\n }\n\n const placement = opts.placement as Placement;\n if (!VALID_PLACEMENTS.includes(placement)) {\n console.error(\n `Error: --placement must be one of: ${VALID_PLACEMENTS.join(\", \")}`,\n );\n process.exit(1);\n }\n\n const targetPath = (opts.path as string).trim();\n if (\n placement === \"in-repo\" &&\n (path.isAbsolute(targetPath) || targetPath.startsWith(\"..\"))\n ) {\n console.error(\n \"Error: --path must be a relative path under the current directory for in-repo plugins.\",\n );\n process.exit(1);\n }\n\n const name = opts.name as string;\n if (!NAME_PATTERN.test(name)) {\n console.error(\n \"Error: --name must be lowercase, start with a letter, and use only letters, numbers, and hyphens.\",\n );\n process.exit(1);\n }\n\n let resources: SelectedResource[] = [];\n if (opts.resourcesJson) {\n resources = parseResourcesJson(opts.resourcesJson);\n } else if (opts.resources) {\n resources = parseResourcesShorthand(opts.resources);\n }\n\n const answers: CreateAnswers = {\n placement,\n targetPath,\n name: name.trim(),\n displayName: opts.displayName?.trim() || deriveDisplayName(name),\n description: (opts.description as string).trim(),\n resources,\n version: DEFAULT_VERSION,\n };\n\n const targetDir = resolveTargetDir(process.cwd(), answers);\n const dirExists = fs.existsSync(targetDir);\n const hasContent = dirExists && fs.readdirSync(targetDir).length > 0;\n if (hasContent && !opts.force) {\n console.error(\n `Error: Directory ${answers.targetPath} already exists and is not empty.`,\n );\n console.error(\" Use --force to overwrite.\");\n process.exit(1);\n }\n\n scaffoldPlugin(targetDir, answers, { isolated: placement === \"isolated\" });\n\n console.log(\n `Plugin \"${answers.name}\" created at ${path.relative(process.cwd(), targetDir)}`,\n );\n printNextSteps(answers, targetDir);\n}\n\nasync function runInteractive(): Promise<void> {\n intro(\"Create a new AppKit plugin\");\n\n try {\n const placement = await select<Placement>({\n message: \"Where should the plugin live?\",\n options: [\n {\n value: \"in-repo\",\n label: \"In this repository (e.g. plugins/my-plugin)\",\n hint: \"folder path\",\n },\n {\n value: \"isolated\",\n label: \"New isolated package\",\n hint: \"full package with package.json\",\n },\n ],\n });\n if (isCancel(placement)) {\n cancel(\"Cancelled.\");\n process.exit(0);\n }\n\n const placementPrompt =\n placement === \"in-repo\"\n ? \"Folder path for the plugin (e.g. plugins/my-feature)\"\n : \"Directory name for the new package (e.g. appkit-plugin-my-feature)\";\n const targetPath = await text({\n message: placementPrompt,\n placeholder:\n placement === \"in-repo\"\n ? \"plugins/my-plugin\"\n : \"appkit-plugin-my-feature\",\n validate(value) {\n if (!value?.trim()) return \"Path is required.\";\n if (\n placement === \"in-repo\" &&\n (path.isAbsolute(value) || value.trim().startsWith(\"..\"))\n ) {\n return \"Use a relative path under the current directory (e.g. plugins/my-plugin).\";\n }\n return undefined;\n },\n });\n if (isCancel(targetPath)) {\n cancel(\"Cancelled.\");\n process.exit(0);\n }\n\n const name = await text({\n message: \"Plugin name (id)\",\n placeholder: \"my-plugin\",\n validate(value) {\n if (!value?.trim()) return \"Name is required.\";\n if (!NAME_PATTERN.test(value as string)) {\n return \"Must be lowercase, start with a letter, and use only letters, numbers, and hyphens.\";\n }\n return undefined;\n },\n });\n if (isCancel(name)) {\n cancel(\"Cancelled.\");\n process.exit(0);\n }\n\n const displayName = await text({\n message: \"Display name\",\n placeholder: \"My Plugin\",\n initialValue: name\n .split(\"-\")\n .map((s) => s.charAt(0).toUpperCase() + s.slice(1))\n .join(\" \"),\n validate(value) {\n if (!value?.trim()) return \"Display name is required.\";\n return undefined;\n },\n });\n if (isCancel(displayName)) {\n cancel(\"Cancelled.\");\n process.exit(0);\n }\n\n const description = await text({\n message: \"Short description\",\n placeholder: \"What does this plugin do?\",\n validate(value) {\n if (!value?.trim()) return \"Description is required.\";\n return undefined;\n },\n });\n if (isCancel(description)) {\n cancel(\"Cancelled.\");\n process.exit(0);\n }\n\n const resourceTypes = await multiselect({\n message: \"Which Databricks resources does this plugin need?\",\n options: RESOURCE_TYPE_OPTIONS.map((o) => ({\n value: o.value,\n label: o.label,\n })),\n required: false,\n });\n if (isCancel(resourceTypes)) {\n cancel(\"Cancelled.\");\n process.exit(0);\n }\n\n const resources: CreateAnswers[\"resources\"] = [];\n for (const type of resourceTypes as string[]) {\n const spec = await promptOneResource({ type });\n if (!spec) {\n cancel(\"Cancelled.\");\n process.exit(0);\n }\n resources.push({\n type: spec.type,\n required: spec.required,\n description: spec.description,\n resourceKey: spec.resourceKey,\n permission: spec.permission,\n fields: spec.fields,\n });\n }\n\n const answers: CreateAnswers = {\n placement,\n targetPath: (targetPath as string).trim(),\n name: (name as string).trim(),\n displayName: (displayName as string).trim(),\n description: (description as string).trim(),\n resources,\n version: DEFAULT_VERSION,\n };\n\n const targetDir = resolveTargetDir(process.cwd(), answers);\n const dirExists = fs.existsSync(targetDir);\n const hasContent = dirExists && fs.readdirSync(targetDir).length > 0;\n if (hasContent) {\n const overwrite = await confirm({\n message: `Directory ${answers.targetPath} already exists and is not empty. Overwrite?`,\n initialValue: false,\n });\n if (isCancel(overwrite) || !overwrite) {\n cancel(\"Cancelled.\");\n process.exit(0);\n }\n }\n\n const s = spinner();\n s.start(\"Writing files…\");\n try {\n scaffoldPlugin(targetDir, answers, {\n isolated: placement === \"isolated\",\n });\n s.stop(\"Files written.\");\n } catch (err) {\n s.stop(\"Failed.\");\n throw err;\n }\n\n outro(\"Plugin created successfully.\");\n printNextSteps(answers, targetDir);\n } catch (err) {\n console.error(err);\n process.exit(1);\n }\n}\n\nconst OPTIONAL_FLAGS = [\n \"displayName\",\n \"resources\",\n \"resourcesJson\",\n \"force\",\n] as const;\n\nasync function runPluginCreate(opts: CreateOptions): Promise<void> {\n const hasRequiredFlag = REQUIRED_FLAGS.some((f) => opts[f] !== undefined);\n if (hasRequiredFlag) {\n runNonInteractive(opts);\n } else {\n const hasOptionalOnly = OPTIONAL_FLAGS.some(\n (f) => opts[f] !== undefined && opts[f] !== false,\n );\n if (hasOptionalOnly) {\n console.error(\n `Error: Non-interactive mode requires: ${REQUIRED_FLAGS.map((f) => `--${f}`).join(\", \")}`,\n );\n console.error(\n ' appkit plugin create --placement in-repo --path plugins/my-plugin --name my-plugin --description \"Does X\"',\n );\n process.exit(1);\n }\n await runInteractive();\n }\n}\n\nexport const pluginCreateCommand = new Command(\"create\")\n .description(\"Scaffold a new AppKit plugin\")\n .option(\"--placement <type>\", \"Where the plugin lives (in-repo, isolated)\")\n .option(\"--path <dir>\", \"Target directory for the plugin\")\n .option(\"--name <id>\", \"Plugin name (lowercase, hyphens allowed)\")\n .option(\"--display-name <name>\", \"Human-readable display name\")\n .option(\"--description <text>\", \"Short description of the plugin\")\n .addOption(\n new Option(\n \"--resources <types>\",\n \"Comma-separated resource types (e.g. sql_warehouse,volume)\",\n ).conflicts(\"resourcesJson\"),\n )\n .addOption(\n new Option(\n \"--resources-json <json>\",\n 'JSON array of resource specs (e.g. \\'[{\"type\":\"sql_warehouse\"}]\\')',\n ).conflicts(\"resources\"),\n )\n .option(\"-f, --force\", \"Overwrite existing directory without confirmation\")\n .addHelpText(\n \"after\",\n `\nExamples:\n $ appkit plugin create\n $ appkit plugin create --placement in-repo --path plugins/my-plugin --name my-plugin --description \"Does X\"\n $ appkit plugin create --placement in-repo --path plugins/my-plugin --name my-plugin --description \"Does X\" --resources sql_warehouse,volume --force\n $ appkit plugin create --placement isolated --path appkit-plugin-ml --name ml --description \"ML\" --resources-json '[{\"type\":\"serving_endpoint\"}]'`,\n )\n .action((opts) =>\n runPluginCreate(opts).catch((err) => {\n console.error(err);\n process.exit(1);\n }),\n );\n\n/** Exported for testing. */\nexport { buildResourceFromType, parseResourcesJson, parseResourcesShorthand };\n"],"mappings":"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"}
@@ -81,6 +81,10 @@ const DEFAULT_FIELDS_BY_TYPE = {
81
81
  description: "Databricks App ID"
82
82
  } }
83
83
  };
84
+ /** Valid resource type values from the schema. */
85
+ function getValidResourceTypes() {
86
+ return RESOURCE_TYPE_OPTIONS.map((o) => o.value);
87
+ }
84
88
  /** Humanized alias from resource type (e.g. sql_warehouse -> "SQL Warehouse"). */
85
89
  function humanizeResourceType(type) {
86
90
  const option = RESOURCE_TYPE_OPTIONS.find((o) => o.value === type);
@@ -101,5 +105,5 @@ function getDefaultFieldsForType(type) {
101
105
  }
102
106
 
103
107
  //#endregion
104
- export { MANIFEST_SCHEMA_ID, PERMISSIONS_BY_TYPE, RESOURCE_TYPE_OPTIONS, getDefaultFieldsForType, humanizeResourceType, resourceKeyFromType };
108
+ export { DEFAULT_PERMISSION_BY_TYPE, MANIFEST_SCHEMA_ID, PERMISSIONS_BY_TYPE, RESOURCE_TYPE_OPTIONS, getDefaultFieldsForType, getValidResourceTypes, humanizeResourceType, resourceKeyFromType };
105
109
  //# sourceMappingURL=resource-defaults.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"resource-defaults.js","names":[],"sources":["../../../../../src/cli/commands/plugin/create/resource-defaults.ts"],"sourcesContent":["/**\n * Resource type and permission defaults for plugin scaffolding.\n * Values are derived from plugin-manifest.schema.json via schema-resources.\n */\n\nimport {\n getResourceTypeOptions,\n getResourceTypePermissions,\n type ResourceTypeOption,\n} from \"../schema-resources\";\n\nexport const MANIFEST_SCHEMA_ID =\n \"https://databricks.github.io/appkit/schemas/plugin-manifest.schema.json\";\n\nexport type { ResourceTypeOption };\n\n/** Resource types from schema resourceType enum (value, human label). */\nexport const RESOURCE_TYPE_OPTIONS: ResourceTypeOption[] =\n getResourceTypeOptions();\n\n/** All valid permissions per resource type, from schema allOf/if-then rules. */\nexport const PERMISSIONS_BY_TYPE: Record<string, string[]> =\n getResourceTypePermissions();\n\n/** Default (first) permission per resource type for scaffolding. */\nexport const DEFAULT_PERMISSION_BY_TYPE: Record<string, string> =\n Object.fromEntries(\n Object.entries(PERMISSIONS_BY_TYPE).map(([type, perms]) => [\n type,\n perms[0],\n ]),\n );\n\n/** Default fields per resource type: field key -> { env, description }. */\nexport const DEFAULT_FIELDS_BY_TYPE: Record<\n string,\n Record<string, { env: string; description?: string }>\n> = {\n sql_warehouse: {\n id: { env: \"DATABRICKS_WAREHOUSE_ID\", description: \"SQL Warehouse ID\" },\n },\n secret: {\n scope: { env: \"SECRET_SCOPE\", description: \"Secret scope name\" },\n key: { env: \"SECRET_KEY\", description: \"Secret key\" },\n },\n job: {\n id: { env: \"DATABRICKS_JOB_ID\", description: \"Job ID\" },\n },\n serving_endpoint: {\n id: {\n env: \"DATABRICKS_SERVING_ENDPOINT_ID\",\n description: \"Serving endpoint ID\",\n },\n },\n volume: {\n name: { env: \"VOLUME_NAME\", description: \"Volume name\" },\n },\n vector_search_index: {\n endpoint_name: {\n env: \"VECTOR_SEARCH_ENDPOINT_NAME\",\n description: \"Vector search endpoint name\",\n },\n index_name: {\n env: \"VECTOR_SEARCH_INDEX_NAME\",\n description: \"Vector search index name\",\n },\n },\n uc_function: {\n name: {\n env: \"UC_FUNCTION_NAME\",\n description: \"Unity Catalog function name\",\n },\n },\n uc_connection: {\n name: {\n env: \"UC_CONNECTION_NAME\",\n description: \"Unity Catalog connection name\",\n },\n },\n database: {\n instance_name: {\n env: \"DATABRICKS_INSTANCE_NAME\",\n description: \"Databricks instance name\",\n },\n database_name: {\n env: \"DATABASE_NAME\",\n description: \"Database name\",\n },\n },\n genie_space: {\n id: { env: \"GENIE_SPACE_ID\", description: \"Genie Space ID\" },\n },\n experiment: {\n id: { env: \"MLFLOW_EXPERIMENT_ID\", description: \"MLflow experiment ID\" },\n },\n app: {\n id: { env: \"DATABRICKS_APP_ID\", description: \"Databricks App ID\" },\n },\n};\n\n/** Humanized alias from resource type (e.g. sql_warehouse -> \"SQL Warehouse\"). */\nexport function humanizeResourceType(type: string): string {\n const option = RESOURCE_TYPE_OPTIONS.find((o) => o.value === type);\n return option ? option.label : type.replace(/_/g, \" \");\n}\n\n/** Kebab-case resource key from type (e.g. sql_warehouse -> \"sql-warehouse\"). */\nexport function resourceKeyFromType(type: string): string {\n return type.replace(/_/g, \"-\");\n}\n\n/** Get default fields for a resource type; fallback to single id field. */\nexport function getDefaultFieldsForType(\n type: string,\n): Record<string, { env: string; description?: string }> {\n const known = DEFAULT_FIELDS_BY_TYPE[type];\n if (known) return known;\n const key = resourceKeyFromType(type);\n const envName = `DATABRICKS_${key.toUpperCase().replace(/-/g, \"_\")}_ID`;\n return {\n id: { env: envName, description: `${humanizeResourceType(type)} ID` },\n };\n}\n"],"mappings":";;;;;;;AAWA,MAAa,qBACX;;AAKF,MAAa,wBACX,wBAAwB;;AAG1B,MAAa,sBACX,4BAA4B;;AAG9B,MAAa,6BACX,OAAO,YACL,OAAO,QAAQ,oBAAoB,CAAC,KAAK,CAAC,MAAM,WAAW,CACzD,MACA,MAAM,GACP,CAAC,CACH;;AAGH,MAAa,yBAGT;CACF,eAAe,EACb,IAAI;EAAE,KAAK;EAA2B,aAAa;EAAoB,EACxE;CACD,QAAQ;EACN,OAAO;GAAE,KAAK;GAAgB,aAAa;GAAqB;EAChE,KAAK;GAAE,KAAK;GAAc,aAAa;GAAc;EACtD;CACD,KAAK,EACH,IAAI;EAAE,KAAK;EAAqB,aAAa;EAAU,EACxD;CACD,kBAAkB,EAChB,IAAI;EACF,KAAK;EACL,aAAa;EACd,EACF;CACD,QAAQ,EACN,MAAM;EAAE,KAAK;EAAe,aAAa;EAAe,EACzD;CACD,qBAAqB;EACnB,eAAe;GACb,KAAK;GACL,aAAa;GACd;EACD,YAAY;GACV,KAAK;GACL,aAAa;GACd;EACF;CACD,aAAa,EACX,MAAM;EACJ,KAAK;EACL,aAAa;EACd,EACF;CACD,eAAe,EACb,MAAM;EACJ,KAAK;EACL,aAAa;EACd,EACF;CACD,UAAU;EACR,eAAe;GACb,KAAK;GACL,aAAa;GACd;EACD,eAAe;GACb,KAAK;GACL,aAAa;GACd;EACF;CACD,aAAa,EACX,IAAI;EAAE,KAAK;EAAkB,aAAa;EAAkB,EAC7D;CACD,YAAY,EACV,IAAI;EAAE,KAAK;EAAwB,aAAa;EAAwB,EACzE;CACD,KAAK,EACH,IAAI;EAAE,KAAK;EAAqB,aAAa;EAAqB,EACnE;CACF;;AAGD,SAAgB,qBAAqB,MAAsB;CACzD,MAAM,SAAS,sBAAsB,MAAM,MAAM,EAAE,UAAU,KAAK;AAClE,QAAO,SAAS,OAAO,QAAQ,KAAK,QAAQ,MAAM,IAAI;;;AAIxD,SAAgB,oBAAoB,MAAsB;AACxD,QAAO,KAAK,QAAQ,MAAM,IAAI;;;AAIhC,SAAgB,wBACd,MACuD;CACvD,MAAM,QAAQ,uBAAuB;AACrC,KAAI,MAAO,QAAO;AAGlB,QAAO,EACL,IAAI;EAAE,KAFQ,cADJ,oBAAoB,KAAK,CACH,aAAa,CAAC,QAAQ,MAAM,IAAI,CAAC;EAE7C,aAAa,GAAG,qBAAqB,KAAK,CAAC;EAAM,EACtE"}
1
+ {"version":3,"file":"resource-defaults.js","names":[],"sources":["../../../../../src/cli/commands/plugin/create/resource-defaults.ts"],"sourcesContent":["/**\n * Resource type and permission defaults for plugin scaffolding.\n * Values are derived from plugin-manifest.schema.json via schema-resources.\n */\n\nimport {\n getResourceTypeOptions,\n getResourceTypePermissions,\n type ResourceTypeOption,\n} from \"../schema-resources\";\n\nexport const MANIFEST_SCHEMA_ID =\n \"https://databricks.github.io/appkit/schemas/plugin-manifest.schema.json\";\n\nexport type { ResourceTypeOption };\n\n/** Resource types from schema resourceType enum (value, human label). */\nexport const RESOURCE_TYPE_OPTIONS: ResourceTypeOption[] =\n getResourceTypeOptions();\n\n/** All valid permissions per resource type, from schema allOf/if-then rules. */\nexport const PERMISSIONS_BY_TYPE: Record<string, string[]> =\n getResourceTypePermissions();\n\n/** Default (first) permission per resource type for scaffolding. */\nexport const DEFAULT_PERMISSION_BY_TYPE: Record<string, string> =\n Object.fromEntries(\n Object.entries(PERMISSIONS_BY_TYPE).map(([type, perms]) => [\n type,\n perms[0],\n ]),\n );\n\n/** Default fields per resource type: field key -> { env, description }. */\nexport const DEFAULT_FIELDS_BY_TYPE: Record<\n string,\n Record<string, { env: string; description?: string }>\n> = {\n sql_warehouse: {\n id: { env: \"DATABRICKS_WAREHOUSE_ID\", description: \"SQL Warehouse ID\" },\n },\n secret: {\n scope: { env: \"SECRET_SCOPE\", description: \"Secret scope name\" },\n key: { env: \"SECRET_KEY\", description: \"Secret key\" },\n },\n job: {\n id: { env: \"DATABRICKS_JOB_ID\", description: \"Job ID\" },\n },\n serving_endpoint: {\n id: {\n env: \"DATABRICKS_SERVING_ENDPOINT_ID\",\n description: \"Serving endpoint ID\",\n },\n },\n volume: {\n name: { env: \"VOLUME_NAME\", description: \"Volume name\" },\n },\n vector_search_index: {\n endpoint_name: {\n env: \"VECTOR_SEARCH_ENDPOINT_NAME\",\n description: \"Vector search endpoint name\",\n },\n index_name: {\n env: \"VECTOR_SEARCH_INDEX_NAME\",\n description: \"Vector search index name\",\n },\n },\n uc_function: {\n name: {\n env: \"UC_FUNCTION_NAME\",\n description: \"Unity Catalog function name\",\n },\n },\n uc_connection: {\n name: {\n env: \"UC_CONNECTION_NAME\",\n description: \"Unity Catalog connection name\",\n },\n },\n database: {\n instance_name: {\n env: \"DATABRICKS_INSTANCE_NAME\",\n description: \"Databricks instance name\",\n },\n database_name: {\n env: \"DATABASE_NAME\",\n description: \"Database name\",\n },\n },\n genie_space: {\n id: { env: \"GENIE_SPACE_ID\", description: \"Genie Space ID\" },\n },\n experiment: {\n id: { env: \"MLFLOW_EXPERIMENT_ID\", description: \"MLflow experiment ID\" },\n },\n app: {\n id: { env: \"DATABRICKS_APP_ID\", description: \"Databricks App ID\" },\n },\n};\n\n/** Valid resource type values from the schema. */\nexport function getValidResourceTypes(): string[] {\n return RESOURCE_TYPE_OPTIONS.map((o) => o.value);\n}\n\n/** Humanized alias from resource type (e.g. sql_warehouse -> \"SQL Warehouse\"). */\nexport function humanizeResourceType(type: string): string {\n const option = RESOURCE_TYPE_OPTIONS.find((o) => o.value === type);\n return option ? option.label : type.replace(/_/g, \" \");\n}\n\n/** Kebab-case resource key from type (e.g. sql_warehouse -> \"sql-warehouse\"). */\nexport function resourceKeyFromType(type: string): string {\n return type.replace(/_/g, \"-\");\n}\n\n/** Get default fields for a resource type; fallback to single id field. */\nexport function getDefaultFieldsForType(\n type: string,\n): Record<string, { env: string; description?: string }> {\n const known = DEFAULT_FIELDS_BY_TYPE[type];\n if (known) return known;\n const key = resourceKeyFromType(type);\n const envName = `DATABRICKS_${key.toUpperCase().replace(/-/g, \"_\")}_ID`;\n return {\n id: { env: envName, description: `${humanizeResourceType(type)} ID` },\n };\n}\n"],"mappings":";;;;;;;AAWA,MAAa,qBACX;;AAKF,MAAa,wBACX,wBAAwB;;AAG1B,MAAa,sBACX,4BAA4B;;AAG9B,MAAa,6BACX,OAAO,YACL,OAAO,QAAQ,oBAAoB,CAAC,KAAK,CAAC,MAAM,WAAW,CACzD,MACA,MAAM,GACP,CAAC,CACH;;AAGH,MAAa,yBAGT;CACF,eAAe,EACb,IAAI;EAAE,KAAK;EAA2B,aAAa;EAAoB,EACxE;CACD,QAAQ;EACN,OAAO;GAAE,KAAK;GAAgB,aAAa;GAAqB;EAChE,KAAK;GAAE,KAAK;GAAc,aAAa;GAAc;EACtD;CACD,KAAK,EACH,IAAI;EAAE,KAAK;EAAqB,aAAa;EAAU,EACxD;CACD,kBAAkB,EAChB,IAAI;EACF,KAAK;EACL,aAAa;EACd,EACF;CACD,QAAQ,EACN,MAAM;EAAE,KAAK;EAAe,aAAa;EAAe,EACzD;CACD,qBAAqB;EACnB,eAAe;GACb,KAAK;GACL,aAAa;GACd;EACD,YAAY;GACV,KAAK;GACL,aAAa;GACd;EACF;CACD,aAAa,EACX,MAAM;EACJ,KAAK;EACL,aAAa;EACd,EACF;CACD,eAAe,EACb,MAAM;EACJ,KAAK;EACL,aAAa;EACd,EACF;CACD,UAAU;EACR,eAAe;GACb,KAAK;GACL,aAAa;GACd;EACD,eAAe;GACb,KAAK;GACL,aAAa;GACd;EACF;CACD,aAAa,EACX,IAAI;EAAE,KAAK;EAAkB,aAAa;EAAkB,EAC7D;CACD,YAAY,EACV,IAAI;EAAE,KAAK;EAAwB,aAAa;EAAwB,EACzE;CACD,KAAK,EACH,IAAI;EAAE,KAAK;EAAqB,aAAa;EAAqB,EACnE;CACF;;AAGD,SAAgB,wBAAkC;AAChD,QAAO,sBAAsB,KAAK,MAAM,EAAE,MAAM;;;AAIlD,SAAgB,qBAAqB,MAAsB;CACzD,MAAM,SAAS,sBAAsB,MAAM,MAAM,EAAE,UAAU,KAAK;AAClE,QAAO,SAAS,OAAO,QAAQ,KAAK,QAAQ,MAAM,IAAI;;;AAIxD,SAAgB,oBAAoB,MAAsB;AACxD,QAAO,KAAK,QAAQ,MAAM,IAAI;;;AAIhC,SAAgB,wBACd,MACuD;CACvD,MAAM,QAAQ,uBAAuB;AACrC,KAAI,MAAO,QAAO;AAGlB,QAAO,EACL,IAAI;EAAE,KAFQ,cADJ,oBAAoB,KAAK,CACH,aAAa,CAAC,QAAQ,MAAM,IAAI,CAAC;EAE7C,aAAa,GAAG,qBAAqB,KAAK,CAAC;EAAM,EACtE"}
@@ -15,7 +15,13 @@ import { Command } from "commander";
15
15
  * - list: List plugins from appkit.plugins.json or a directory
16
16
  * - add-resource: Add a resource requirement to a plugin manifest (interactive)
17
17
  */
18
- const pluginCommand = new Command("plugin").description("Plugin management commands").addCommand(pluginsSyncCommand).addCommand(pluginCreateCommand).addCommand(pluginValidateCommand).addCommand(pluginListCommand).addCommand(pluginAddResourceCommand);
18
+ const pluginCommand = new Command("plugin").description("Plugin management commands").addCommand(pluginsSyncCommand).addCommand(pluginCreateCommand).addCommand(pluginValidateCommand).addCommand(pluginListCommand).addCommand(pluginAddResourceCommand).addHelpText("after", `
19
+ Examples:
20
+ $ appkit plugin sync --write
21
+ $ appkit plugin create --placement in-repo --path plugins/my-plugin --name my-plugin --description "Does X"
22
+ $ appkit plugin validate .
23
+ $ appkit plugin list --json
24
+ $ appkit plugin add-resource --path plugins/my-plugin --type sql_warehouse`);
19
25
 
20
26
  //#endregion
21
27
  export { pluginCommand };
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../../src/cli/commands/plugin/index.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { pluginAddResourceCommand } from \"./add-resource/add-resource\";\nimport { pluginCreateCommand } from \"./create/create\";\nimport { pluginListCommand } from \"./list/list\";\nimport { pluginsSyncCommand } from \"./sync/sync\";\nimport { pluginValidateCommand } from \"./validate/validate\";\n\n/**\n * Parent command for plugin management operations.\n * Subcommands:\n * - sync: Aggregate plugin manifests into appkit.plugins.json\n * - create: Scaffold a new plugin (interactive)\n * - validate: Validate manifest(s) against the JSON schema\n * - list: List plugins from appkit.plugins.json or a directory\n * - add-resource: Add a resource requirement to a plugin manifest (interactive)\n */\nexport const pluginCommand = new Command(\"plugin\")\n .description(\"Plugin management commands\")\n .addCommand(pluginsSyncCommand)\n .addCommand(pluginCreateCommand)\n .addCommand(pluginValidateCommand)\n .addCommand(pluginListCommand)\n .addCommand(pluginAddResourceCommand);\n"],"mappings":";;;;;;;;;;;;;;;;;AAgBA,MAAa,gBAAgB,IAAI,QAAQ,SAAS,CAC/C,YAAY,6BAA6B,CACzC,WAAW,mBAAmB,CAC9B,WAAW,oBAAoB,CAC/B,WAAW,sBAAsB,CACjC,WAAW,kBAAkB,CAC7B,WAAW,yBAAyB"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../../src/cli/commands/plugin/index.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { pluginAddResourceCommand } from \"./add-resource/add-resource\";\nimport { pluginCreateCommand } from \"./create/create\";\nimport { pluginListCommand } from \"./list/list\";\nimport { pluginsSyncCommand } from \"./sync/sync\";\nimport { pluginValidateCommand } from \"./validate/validate\";\n\n/**\n * Parent command for plugin management operations.\n * Subcommands:\n * - sync: Aggregate plugin manifests into appkit.plugins.json\n * - create: Scaffold a new plugin (interactive)\n * - validate: Validate manifest(s) against the JSON schema\n * - list: List plugins from appkit.plugins.json or a directory\n * - add-resource: Add a resource requirement to a plugin manifest (interactive)\n */\nexport const pluginCommand = new Command(\"plugin\")\n .description(\"Plugin management commands\")\n .addCommand(pluginsSyncCommand)\n .addCommand(pluginCreateCommand)\n .addCommand(pluginValidateCommand)\n .addCommand(pluginListCommand)\n .addCommand(pluginAddResourceCommand)\n .addHelpText(\n \"after\",\n `\nExamples:\n $ appkit plugin sync --write\n $ appkit plugin create --placement in-repo --path plugins/my-plugin --name my-plugin --description \"Does X\"\n $ appkit plugin validate .\n $ appkit plugin list --json\n $ appkit plugin add-resource --path plugins/my-plugin --type sql_warehouse`,\n );\n"],"mappings":";;;;;;;;;;;;;;;;;AAgBA,MAAa,gBAAgB,IAAI,QAAQ,SAAS,CAC/C,YAAY,6BAA6B,CACzC,WAAW,mBAAmB,CAC9B,WAAW,oBAAoB,CAC/B,WAAW,sBAAsB,CACjC,WAAW,kBAAkB,CAC7B,WAAW,yBAAyB,CACpC,YACC,SACA;;;;;;8EAOD"}
@@ -109,6 +109,7 @@ async function runPluginList(options) {
109
109
  const manifestPath = path.resolve(cwd, options.manifest ?? "appkit.plugins.json");
110
110
  if (!fs.existsSync(manifestPath)) {
111
111
  console.error(`Manifest not found: ${manifestPath}`);
112
+ console.error(" appkit plugin list --manifest <path-to-manifest> or appkit plugin list --dir <plugins-directory>");
112
113
  process.exit(1);
113
114
  }
114
115
  try {
@@ -121,7 +122,12 @@ async function runPluginList(options) {
121
122
  if (options.json) console.log(JSON.stringify(rows, null, 2));
122
123
  else printTable(rows);
123
124
  }
124
- const pluginListCommand = new Command("list").description("List plugins from appkit.plugins.json or a directory").option("-m, --manifest <path>", "Path to appkit.plugins.json", "appkit.plugins.json").option("-d, --dir <path>", "Scan directory recursively for plugin folders (manifest.json by default)").option("--allow-js-manifest", "Allow reading manifest.js/manifest.cjs (executes code; use only with trusted plugins)").option("--json", "Output as JSON").action((opts) => runPluginList(opts).catch((err) => {
125
+ const pluginListCommand = new Command("list").description("List plugins from appkit.plugins.json or a directory").option("-m, --manifest <path>", "Path to appkit.plugins.json", "appkit.plugins.json").option("-d, --dir <path>", "Scan directory recursively for plugin folders (manifest.json by default)").option("--allow-js-manifest", "Allow reading manifest.js/manifest.cjs (executes code; use only with trusted plugins)").option("--json", "Output as JSON").addHelpText("after", `
126
+ Examples:
127
+ $ appkit plugin list
128
+ $ appkit plugin list --json
129
+ $ appkit plugin list --manifest custom-manifest.json
130
+ $ appkit plugin list --dir plugins/`).action((opts) => runPluginList(opts).catch((err) => {
125
131
  console.error(err);
126
132
  process.exit(1);
127
133
  }));
@@ -1 +1 @@
1
- {"version":3,"file":"list.js","names":[],"sources":["../../../../../src/cli/commands/plugin/list/list.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 resolveManifestInDir,\n} from \"../manifest-resolve\";\nimport { shouldAllowJsManifestForDir } from \"../trusted-js-manifest\";\nimport { validateManifest } from \"../validate/validate-manifest\";\n\n/** Safety limit for recursive directory scanning to prevent runaway traversal. */\nconst MAX_SCAN_DEPTH = 5;\n\nexport interface PluginRow {\n name: string;\n displayName: string;\n package: string;\n required: number;\n optional: number;\n}\n\nexport function listFromManifestFile(manifestPath: string): PluginRow[] {\n let raw: string;\n try {\n raw = fs.readFileSync(manifestPath, \"utf-8\");\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`Failed to read manifest file ${manifestPath}: ${msg}`);\n }\n\n let data: {\n plugins?: Record<\n string,\n {\n name: string;\n displayName: string;\n package: string;\n resources: { required: unknown[]; optional: unknown[] };\n }\n >;\n };\n try {\n data = JSON.parse(raw) as typeof data;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`Failed to parse manifest file ${manifestPath}: ${msg}`);\n }\n\n const plugins = data.plugins ?? {};\n return Object.values(plugins).map((p) => ({\n name: p.name,\n displayName: p.displayName ?? p.name,\n package: p.package ?? \"\",\n required: Array.isArray(p.resources?.required)\n ? p.resources.required.length\n : 0,\n optional: Array.isArray(p.resources?.optional)\n ? p.resources.optional.length\n : 0,\n }));\n}\n\nasync function collectPluginsRecursive(\n dir: string,\n cwd: string,\n rows: PluginRow[],\n allowJsManifest: boolean,\n depth = 0,\n): Promise<void> {\n if (\n !fs.existsSync(dir) ||\n !fs.statSync(dir).isDirectory() ||\n depth >= MAX_SCAN_DEPTH\n )\n return;\n\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n\n const childPath = path.join(dir, entry.name);\n const allowJsForChild =\n allowJsManifest || shouldAllowJsManifestForDir(childPath);\n const resolvedManifest = resolveManifestInDir(childPath, {\n allowJsManifest: allowJsForChild,\n });\n\n if (resolvedManifest) {\n try {\n const obj = await loadManifestFromFile(\n resolvedManifest.path,\n resolvedManifest.type,\n { allowJsManifest: allowJsForChild },\n );\n const result = validateManifest(obj);\n const manifest = result.valid ? result.manifest : null;\n if (manifest) {\n const relPath = path.relative(\n cwd,\n path.dirname(resolvedManifest.path),\n );\n const packagePath = relPath.startsWith(\".\")\n ? relPath\n : `./${relPath}`;\n rows.push({\n name: manifest.name,\n displayName: manifest.displayName ?? manifest.name,\n package: packagePath,\n required: Array.isArray(manifest.resources?.required)\n ? manifest.resources.required.length\n : 0,\n optional: Array.isArray(manifest.resources?.optional)\n ? manifest.resources.optional.length\n : 0,\n });\n }\n } catch {\n // skip invalid manifests\n }\n continue;\n }\n\n await collectPluginsRecursive(\n childPath,\n cwd,\n rows,\n allowJsManifest,\n depth + 1,\n );\n }\n}\n\nexport async function listFromDirectory(\n dirPath: string,\n cwd: string,\n allowJsManifest = false,\n): Promise<PluginRow[]> {\n const resolved = path.resolve(cwd, dirPath);\n if (!fs.existsSync(resolved) || !fs.statSync(resolved).isDirectory()) {\n return [];\n }\n const rows: PluginRow[] = [];\n await collectPluginsRecursive(resolved, cwd, rows, allowJsManifest);\n return rows;\n}\n\nfunction printTable(rows: PluginRow[]): void {\n if (rows.length === 0) {\n console.log(\"No plugins found.\");\n return;\n }\n const maxName = Math.max(4, ...rows.map((r) => r.name.length));\n const maxDisplay = Math.max(10, ...rows.map((r) => r.displayName.length));\n const maxPkg = Math.max(7, ...rows.map((r) => r.package.length));\n const header = [\n \"NAME\".padEnd(maxName),\n \"DISPLAY NAME\".padEnd(maxDisplay),\n \"PACKAGE / PATH\".padEnd(maxPkg),\n \"REQ\",\n \"OPT\",\n ].join(\" \");\n console.log(header);\n console.log(\"-\".repeat(header.length));\n for (const r of rows) {\n console.log(\n [\n r.name.padEnd(maxName),\n r.displayName.padEnd(maxDisplay),\n r.package.padEnd(maxPkg),\n String(r.required).padStart(3),\n String(r.optional).padStart(3),\n ].join(\" \"),\n );\n }\n}\n\nasync function runPluginList(options: {\n manifest?: string;\n dir?: string;\n json?: boolean;\n allowJsManifest?: boolean;\n}): Promise<void> {\n const cwd = process.cwd();\n const allowJsManifest = Boolean(options.allowJsManifest);\n if (allowJsManifest) {\n console.warn(\n \"Warning: --allow-js-manifest executes manifest.js/manifest.cjs files. Only use with trusted code.\",\n );\n }\n let rows: PluginRow[];\n\n if (options.dir !== undefined) {\n rows = await listFromDirectory(options.dir, cwd, allowJsManifest);\n if (rows.length === 0 && options.dir) {\n console.error(\n `No plugin directories with ${allowJsManifest ? \"manifest.json or manifest.js\" : \"manifest.json\"} found in ${options.dir}`,\n );\n process.exit(1);\n }\n } else {\n const manifestPath = path.resolve(\n cwd,\n options.manifest ?? \"appkit.plugins.json\",\n );\n if (!fs.existsSync(manifestPath)) {\n console.error(`Manifest not found: ${manifestPath}`);\n process.exit(1);\n }\n try {\n rows = listFromManifestFile(manifestPath);\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n }\n\n if (options.json) {\n console.log(JSON.stringify(rows, null, 2));\n } else {\n printTable(rows);\n }\n}\n\nexport const pluginListCommand = new Command(\"list\")\n .description(\"List plugins from appkit.plugins.json or a directory\")\n .option(\n \"-m, --manifest <path>\",\n \"Path to appkit.plugins.json\",\n \"appkit.plugins.json\",\n )\n .option(\n \"-d, --dir <path>\",\n \"Scan directory recursively for plugin folders (manifest.json by default)\",\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 as JSON\")\n .action((opts) =>\n runPluginList(opts).catch((err) => {\n console.error(err);\n process.exit(1);\n }),\n );\n"],"mappings":"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"}
1
+ {"version":3,"file":"list.js","names":[],"sources":["../../../../../src/cli/commands/plugin/list/list.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 resolveManifestInDir,\n} from \"../manifest-resolve\";\nimport { shouldAllowJsManifestForDir } from \"../trusted-js-manifest\";\nimport { validateManifest } from \"../validate/validate-manifest\";\n\n/** Safety limit for recursive directory scanning to prevent runaway traversal. */\nconst MAX_SCAN_DEPTH = 5;\n\nexport interface PluginRow {\n name: string;\n displayName: string;\n package: string;\n required: number;\n optional: number;\n}\n\nexport function listFromManifestFile(manifestPath: string): PluginRow[] {\n let raw: string;\n try {\n raw = fs.readFileSync(manifestPath, \"utf-8\");\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`Failed to read manifest file ${manifestPath}: ${msg}`);\n }\n\n let data: {\n plugins?: Record<\n string,\n {\n name: string;\n displayName: string;\n package: string;\n resources: { required: unknown[]; optional: unknown[] };\n }\n >;\n };\n try {\n data = JSON.parse(raw) as typeof data;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`Failed to parse manifest file ${manifestPath}: ${msg}`);\n }\n\n const plugins = data.plugins ?? {};\n return Object.values(plugins).map((p) => ({\n name: p.name,\n displayName: p.displayName ?? p.name,\n package: p.package ?? \"\",\n required: Array.isArray(p.resources?.required)\n ? p.resources.required.length\n : 0,\n optional: Array.isArray(p.resources?.optional)\n ? p.resources.optional.length\n : 0,\n }));\n}\n\nasync function collectPluginsRecursive(\n dir: string,\n cwd: string,\n rows: PluginRow[],\n allowJsManifest: boolean,\n depth = 0,\n): Promise<void> {\n if (\n !fs.existsSync(dir) ||\n !fs.statSync(dir).isDirectory() ||\n depth >= MAX_SCAN_DEPTH\n )\n return;\n\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n\n const childPath = path.join(dir, entry.name);\n const allowJsForChild =\n allowJsManifest || shouldAllowJsManifestForDir(childPath);\n const resolvedManifest = resolveManifestInDir(childPath, {\n allowJsManifest: allowJsForChild,\n });\n\n if (resolvedManifest) {\n try {\n const obj = await loadManifestFromFile(\n resolvedManifest.path,\n resolvedManifest.type,\n { allowJsManifest: allowJsForChild },\n );\n const result = validateManifest(obj);\n const manifest = result.valid ? result.manifest : null;\n if (manifest) {\n const relPath = path.relative(\n cwd,\n path.dirname(resolvedManifest.path),\n );\n const packagePath = relPath.startsWith(\".\")\n ? relPath\n : `./${relPath}`;\n rows.push({\n name: manifest.name,\n displayName: manifest.displayName ?? manifest.name,\n package: packagePath,\n required: Array.isArray(manifest.resources?.required)\n ? manifest.resources.required.length\n : 0,\n optional: Array.isArray(manifest.resources?.optional)\n ? manifest.resources.optional.length\n : 0,\n });\n }\n } catch {\n // skip invalid manifests\n }\n continue;\n }\n\n await collectPluginsRecursive(\n childPath,\n cwd,\n rows,\n allowJsManifest,\n depth + 1,\n );\n }\n}\n\nexport async function listFromDirectory(\n dirPath: string,\n cwd: string,\n allowJsManifest = false,\n): Promise<PluginRow[]> {\n const resolved = path.resolve(cwd, dirPath);\n if (!fs.existsSync(resolved) || !fs.statSync(resolved).isDirectory()) {\n return [];\n }\n const rows: PluginRow[] = [];\n await collectPluginsRecursive(resolved, cwd, rows, allowJsManifest);\n return rows;\n}\n\nfunction printTable(rows: PluginRow[]): void {\n if (rows.length === 0) {\n console.log(\"No plugins found.\");\n return;\n }\n const maxName = Math.max(4, ...rows.map((r) => r.name.length));\n const maxDisplay = Math.max(10, ...rows.map((r) => r.displayName.length));\n const maxPkg = Math.max(7, ...rows.map((r) => r.package.length));\n const header = [\n \"NAME\".padEnd(maxName),\n \"DISPLAY NAME\".padEnd(maxDisplay),\n \"PACKAGE / PATH\".padEnd(maxPkg),\n \"REQ\",\n \"OPT\",\n ].join(\" \");\n console.log(header);\n console.log(\"-\".repeat(header.length));\n for (const r of rows) {\n console.log(\n [\n r.name.padEnd(maxName),\n r.displayName.padEnd(maxDisplay),\n r.package.padEnd(maxPkg),\n String(r.required).padStart(3),\n String(r.optional).padStart(3),\n ].join(\" \"),\n );\n }\n}\n\nasync function runPluginList(options: {\n manifest?: string;\n dir?: string;\n json?: boolean;\n allowJsManifest?: boolean;\n}): Promise<void> {\n const cwd = process.cwd();\n const allowJsManifest = Boolean(options.allowJsManifest);\n if (allowJsManifest) {\n console.warn(\n \"Warning: --allow-js-manifest executes manifest.js/manifest.cjs files. Only use with trusted code.\",\n );\n }\n let rows: PluginRow[];\n\n if (options.dir !== undefined) {\n rows = await listFromDirectory(options.dir, cwd, allowJsManifest);\n if (rows.length === 0 && options.dir) {\n console.error(\n `No plugin directories with ${allowJsManifest ? \"manifest.json or manifest.js\" : \"manifest.json\"} found in ${options.dir}`,\n );\n process.exit(1);\n }\n } else {\n const manifestPath = path.resolve(\n cwd,\n options.manifest ?? \"appkit.plugins.json\",\n );\n if (!fs.existsSync(manifestPath)) {\n console.error(`Manifest not found: ${manifestPath}`);\n console.error(\n \" appkit plugin list --manifest <path-to-manifest> or appkit plugin list --dir <plugins-directory>\",\n );\n process.exit(1);\n }\n try {\n rows = listFromManifestFile(manifestPath);\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n }\n\n if (options.json) {\n console.log(JSON.stringify(rows, null, 2));\n } else {\n printTable(rows);\n }\n}\n\nexport const pluginListCommand = new Command(\"list\")\n .description(\"List plugins from appkit.plugins.json or a directory\")\n .option(\n \"-m, --manifest <path>\",\n \"Path to appkit.plugins.json\",\n \"appkit.plugins.json\",\n )\n .option(\n \"-d, --dir <path>\",\n \"Scan directory recursively for plugin folders (manifest.json by default)\",\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 as JSON\")\n .addHelpText(\n \"after\",\n `\nExamples:\n $ appkit plugin list\n $ appkit plugin list --json\n $ appkit plugin list --manifest custom-manifest.json\n $ appkit plugin list --dir plugins/`,\n )\n .action((opts) =>\n runPluginList(opts).catch((err) => {\n console.error(err);\n process.exit(1);\n }),\n );\n"],"mappings":"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"}
@@ -326,15 +326,17 @@ function writeManifest(outputPath, { plugins }, options) {
326
326
  version: "1.0",
327
327
  plugins
328
328
  };
329
+ const serialized = JSON.stringify(templateManifest, null, 2);
330
+ if (options.json) console.log(serialized);
329
331
  if (options.write) {
330
- fs.writeFileSync(outputPath, `${JSON.stringify(templateManifest, null, 2)}\n`);
331
- if (!options.silent) console.log(`\n✓ Wrote ${outputPath}`);
332
- } else if (!options.silent) {
332
+ fs.writeFileSync(outputPath, `${serialized}\n`);
333
+ if (!options.silent && !options.json) console.log(`\n✓ Wrote ${outputPath}`);
334
+ } else if (!options.silent && !options.json) {
333
335
  console.log("\nTo write the manifest, run:");
334
336
  console.log(" npx appkit plugin sync --write\n");
335
337
  console.log("Preview:");
336
338
  console.log("─".repeat(60));
337
- console.log(JSON.stringify(templateManifest, null, 2));
339
+ console.log(serialized);
338
340
  console.log("─".repeat(60));
339
341
  }
340
342
  }
@@ -352,7 +354,7 @@ async function runPluginsSync(options) {
352
354
  console.error(`Error: Output path "${options.output}" resolves outside the project directory.`);
353
355
  process.exit(1);
354
356
  }
355
- if (!options.silent) {
357
+ if (!options.silent && !options.json) {
356
358
  console.log("Scanning for AppKit plugins...\n");
357
359
  if (allowJsManifest) console.warn("Warning: --allow-js-manifest executes manifest.js/manifest.cjs files. Only use with trusted code.");
358
360
  }
@@ -360,7 +362,7 @@ async function runPluginsSync(options) {
360
362
  let serverImports = [];
361
363
  let pluginUsages = /* @__PURE__ */ new Set();
362
364
  if (serverFile) {
363
- if (!options.silent) {
365
+ if (!options.silent && !options.json) {
364
366
  const relativePath = path.relative(cwd, serverFile);
365
367
  console.log(`Server entry file: ${relativePath}`);
366
368
  }
@@ -368,14 +370,14 @@ async function runPluginsSync(options) {
368
370
  const root = parse(serverFile.endsWith(".tsx") ? Lang.Tsx : Lang.TypeScript, content).root();
369
371
  serverImports = parseImports(root);
370
372
  pluginUsages = parsePluginUsages(root);
371
- } else if (!options.silent) console.log("No server entry file found. Checked:", SERVER_FILE_CANDIDATES.join(", "));
373
+ } else if (!options.silent && !options.json) console.log("No server entry file found. Checked:", SERVER_FILE_CANDIDATES.join(", "));
372
374
  const npmImports = serverImports.filter((i) => !i.source.startsWith(".") && !i.source.startsWith("/"));
373
375
  const localImports = serverImports.filter((i) => i.source.startsWith(".") || i.source.startsWith("/"));
374
376
  const plugins = {};
375
377
  if (options.pluginsDir) {
376
378
  const resolvedDir = path.resolve(cwd, options.pluginsDir);
377
379
  const pkgName = options.packageName ?? "@databricks/appkit";
378
- if (!options.silent) console.log(`Scanning plugins directory: ${options.pluginsDir}`);
380
+ if (!options.silent && !options.json) console.log(`Scanning plugins directory: ${options.pluginsDir}`);
379
381
  Object.assign(plugins, await scanPluginsDir(resolvedDir, pkgName, allowJsManifest));
380
382
  } else {
381
383
  const npmPackages = new Set([...KNOWN_PLUGIN_PACKAGES, ...npmImports.map((i) => i.source)]);
@@ -389,19 +391,23 @@ async function runPluginsSync(options) {
389
391
  for (const dir of localDirsToScan) {
390
392
  const resolvedDir = path.resolve(cwd, dir);
391
393
  if (!fs.existsSync(resolvedDir)) continue;
392
- if (!options.silent) console.log(`Scanning local plugins directory: ${dir}`);
394
+ if (!options.silent && !options.json) console.log(`Scanning local plugins directory: ${dir}`);
393
395
  const discovered = await scanPluginsDirRecursive(resolvedDir, cwd, allowJsManifest);
394
396
  for (const [name, entry] of Object.entries(discovered)) if (!plugins[name]) plugins[name] = entry;
395
397
  }
396
398
  const pluginCount = Object.keys(plugins).length;
397
399
  if (pluginCount === 0) {
398
- if (options.silent) {
400
+ if (options.silent || options.json) {
399
401
  writeManifest(outputPath, { plugins: {} }, options);
400
- return;
402
+ if (options.silent) return;
403
+ process.exit(1);
401
404
  }
402
405
  console.log("No plugins found.");
403
406
  if (options.pluginsDir) console.log(`\nNo manifest (${allowJsManifest ? "manifest.json or manifest.js" : "manifest.json"}) found in: ${options.pluginsDir}`);
404
- else console.log("\nMake sure you have plugin packages installed.");
407
+ else {
408
+ console.log("\nMake sure you have plugin packages installed, or specify a directory:");
409
+ console.log(" appkit plugin sync --plugins-dir <path>");
410
+ }
405
411
  process.exit(1);
406
412
  }
407
413
  const serverFileDir = serverFile ? path.dirname(serverFile) : cwd;
@@ -423,7 +429,7 @@ async function runPluginsSync(options) {
423
429
  for (const name of explicitNames) if (plugins[name]) plugins[name].requiredByTemplate = true;
424
430
  else if (!options.silent) console.warn(`Warning: --require-plugins referenced "${name}" but no such plugin was discovered`);
425
431
  }
426
- if (!options.silent) {
432
+ if (!options.silent && !options.json) {
427
433
  console.log(`\nFound ${pluginCount} plugin(s):`);
428
434
  for (const [name, manifest] of Object.entries(plugins)) {
429
435
  const resourceCount = manifest.resources.required.length + manifest.resources.optional.length;
@@ -434,7 +440,14 @@ async function runPluginsSync(options) {
434
440
  }
435
441
  writeManifest(outputPath, { plugins }, options);
436
442
  }
437
- const pluginsSyncCommand = new Command("sync").description("Sync plugin manifests from installed packages into appkit.plugins.json").option("-w, --write", "Write the manifest file").option("-o, --output <path>", "Output file path (default: ./appkit.plugins.json)").option("-s, --silent", "Suppress output and never exit with error (for use in predev/prebuild hooks)").option("--require-plugins <names>", "Comma-separated plugin names to mark as requiredByTemplate (e.g. server,analytics)").option("--plugins-dir <path>", "Scan this directory for plugin subdirectories with manifest.json (instead of node_modules)").option("--package-name <name>", "Package name to assign to plugins found via --plugins-dir (default: @databricks/appkit)").option("--local-plugins-dir <path>", "Also scan this directory for local plugin manifests (default: plugins, server)").option("--allow-js-manifest", "Allow reading manifest.js/manifest.cjs (executes code; use only with trusted plugins)").action((opts) => runPluginsSync(opts).catch((err) => {
443
+ const pluginsSyncCommand = new Command("sync").description("Sync plugin manifests from installed packages into appkit.plugins.json").option("-w, --write", "Write the manifest file").option("-o, --output <path>", "Output file path (default: ./appkit.plugins.json)").option("-s, --silent", "Suppress output and never exit with error (for use in predev/prebuild hooks)").option("--require-plugins <names>", "Comma-separated plugin names to mark as requiredByTemplate (e.g. server,analytics)").option("--plugins-dir <path>", "Scan this directory for plugin subdirectories with manifest.json (instead of node_modules)").option("--package-name <name>", "Package name to assign to plugins found via --plugins-dir (default: @databricks/appkit)").option("--local-plugins-dir <path>", "Also scan this directory for local plugin manifests (default: plugins, server)").option("--allow-js-manifest", "Allow reading manifest.js/manifest.cjs (executes code; use only with trusted plugins)").option("--json", "Output manifest as JSON to stdout").addHelpText("after", `
444
+ Examples:
445
+ $ appkit plugin sync
446
+ $ appkit plugin sync --write
447
+ $ appkit plugin sync --write --require-plugins server,analytics
448
+ $ appkit plugin sync --write --plugins-dir src/plugins --package-name @my/pkg
449
+ $ appkit plugin sync --json
450
+ $ appkit plugin sync --silent`).action((opts) => runPluginsSync(opts).catch((err) => {
438
451
  console.error(err);
439
452
  process.exit(1);
440
453
  }));