@databricks/appkit-ui 0.15.0 → 0.17.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 (274) hide show
  1. package/dist/cli/commands/plugin/add-resource/add-resource.js +10 -4
  2. package/dist/cli/commands/plugin/add-resource/add-resource.js.map +1 -1
  3. package/dist/cli/commands/plugin/create/scaffold.js +10 -16
  4. package/dist/cli/commands/plugin/create/scaffold.js.map +1 -1
  5. package/dist/cli/commands/plugin/list/list.js +44 -26
  6. package/dist/cli/commands/plugin/list/list.js.map +1 -1
  7. package/dist/cli/commands/plugin/manifest-resolve.js +57 -0
  8. package/dist/cli/commands/plugin/manifest-resolve.js.map +1 -0
  9. package/dist/cli/commands/plugin/sync/sync.js +121 -71
  10. package/dist/cli/commands/plugin/sync/sync.js.map +1 -1
  11. package/dist/cli/commands/plugin/trusted-js-manifest.js +28 -0
  12. package/dist/cli/commands/plugin/trusted-js-manifest.js.map +1 -0
  13. package/dist/cli/commands/plugin/validate/validate.js +32 -14
  14. package/dist/cli/commands/plugin/validate/validate.js.map +1 -1
  15. package/dist/js/arrow/arrow-client.d.ts.map +1 -1
  16. package/dist/js/arrow/index.d.ts +3 -0
  17. package/dist/js/arrow/lazy-arrow.d.ts +0 -1
  18. package/dist/js/arrow/lazy-arrow.d.ts.map +1 -1
  19. package/dist/js/constants.d.ts.map +1 -1
  20. package/dist/js/index.d.ts +2 -0
  21. package/dist/js/sse/connect-sse.d.ts +0 -1
  22. package/dist/js/sse/connect-sse.d.ts.map +1 -1
  23. package/dist/js/sse/types.d.ts +1 -3
  24. package/dist/js/sse/types.d.ts.map +1 -1
  25. package/dist/react/charts/area/index.d.ts +2 -3
  26. package/dist/react/charts/area/index.d.ts.map +1 -1
  27. package/dist/react/charts/bar/index.d.ts +2 -3
  28. package/dist/react/charts/bar/index.d.ts.map +1 -1
  29. package/dist/react/charts/base.d.ts +2 -2
  30. package/dist/react/charts/base.d.ts.map +1 -1
  31. package/dist/react/charts/base.js.map +1 -1
  32. package/dist/react/charts/chart-error-boundary.js.map +1 -1
  33. package/dist/react/charts/constants.d.ts.map +1 -1
  34. package/dist/react/charts/create-chart.d.ts +2 -3
  35. package/dist/react/charts/create-chart.d.ts.map +1 -1
  36. package/dist/react/charts/create-chart.js.map +1 -1
  37. package/dist/react/charts/empty.js.map +1 -1
  38. package/dist/react/charts/error.js.map +1 -1
  39. package/dist/react/charts/heatmap/index.d.ts +2 -3
  40. package/dist/react/charts/heatmap/index.d.ts.map +1 -1
  41. package/dist/react/charts/index.d.ts +18 -0
  42. package/dist/react/charts/line/index.d.ts +2 -3
  43. package/dist/react/charts/line/index.d.ts.map +1 -1
  44. package/dist/react/charts/loading.js.map +1 -1
  45. package/dist/react/charts/normalize.d.ts +0 -1
  46. package/dist/react/charts/normalize.d.ts.map +1 -1
  47. package/dist/react/charts/options.d.ts.map +1 -1
  48. package/dist/react/charts/pie/index.d.ts +3 -4
  49. package/dist/react/charts/pie/index.d.ts.map +1 -1
  50. package/dist/react/charts/radar/index.d.ts +2 -3
  51. package/dist/react/charts/radar/index.d.ts.map +1 -1
  52. package/dist/react/charts/scatter/index.d.ts +2 -3
  53. package/dist/react/charts/scatter/index.d.ts.map +1 -1
  54. package/dist/react/charts/theme.d.ts +0 -1
  55. package/dist/react/charts/theme.d.ts.map +1 -1
  56. package/dist/react/charts/types.d.ts.map +1 -1
  57. package/dist/react/charts/utils.d.ts.map +1 -1
  58. package/dist/react/charts/wrapper.d.ts +2 -2
  59. package/dist/react/charts/wrapper.d.ts.map +1 -1
  60. package/dist/react/charts/wrapper.js.map +1 -1
  61. package/dist/react/genie/genie-chat-input.d.ts +2 -2
  62. package/dist/react/genie/genie-chat-input.d.ts.map +1 -1
  63. package/dist/react/genie/genie-chat-input.js.map +1 -1
  64. package/dist/react/genie/genie-chat-message-list.d.ts +9 -3
  65. package/dist/react/genie/genie-chat-message-list.d.ts.map +1 -1
  66. package/dist/react/genie/genie-chat-message-list.js +92 -17
  67. package/dist/react/genie/genie-chat-message-list.js.map +1 -1
  68. package/dist/react/genie/genie-chat-message.d.ts +2 -2
  69. package/dist/react/genie/genie-chat-message.d.ts.map +1 -1
  70. package/dist/react/genie/genie-chat-message.js.map +1 -1
  71. package/dist/react/genie/genie-chat.d.ts +2 -2
  72. package/dist/react/genie/genie-chat.d.ts.map +1 -1
  73. package/dist/react/genie/genie-chat.js +4 -2
  74. package/dist/react/genie/genie-chat.js.map +1 -1
  75. package/dist/react/genie/index.d.ts +7 -0
  76. package/dist/react/genie/types.d.ts +8 -1
  77. package/dist/react/genie/types.d.ts.map +1 -1
  78. package/dist/react/genie/use-genie-chat.d.ts +0 -1
  79. package/dist/react/genie/use-genie-chat.d.ts.map +1 -1
  80. package/dist/react/genie/use-genie-chat.js +119 -41
  81. package/dist/react/genie/use-genie-chat.js.map +1 -1
  82. package/dist/react/hooks/index.d.ts +3 -0
  83. package/dist/react/hooks/types.d.ts.map +1 -1
  84. package/dist/react/hooks/use-analytics-query.d.ts +0 -1
  85. package/dist/react/hooks/use-analytics-query.d.ts.map +1 -1
  86. package/dist/react/hooks/use-chart-data.d.ts.map +1 -1
  87. package/dist/react/index.d.ts +5 -0
  88. package/dist/react/lib/utils.d.ts.map +1 -1
  89. package/dist/react/portal-container-context.d.ts +0 -1
  90. package/dist/react/portal-container-context.d.ts.map +1 -1
  91. package/dist/react/portal-container-context.js.map +1 -1
  92. package/dist/react/table/data-table.d.ts +2 -3
  93. package/dist/react/table/data-table.d.ts.map +1 -1
  94. package/dist/react/table/data-table.js.map +1 -1
  95. package/dist/react/table/empty.js.map +1 -1
  96. package/dist/react/table/error.js.map +1 -1
  97. package/dist/react/table/index.d.ts +1 -0
  98. package/dist/react/table/loading.js.map +1 -1
  99. package/dist/react/table/table-wrapper.js.map +1 -1
  100. package/dist/react/table/types.d.ts +0 -1
  101. package/dist/react/table/types.d.ts.map +1 -1
  102. package/dist/react/ui/accordion.d.ts.map +1 -1
  103. package/dist/react/ui/accordion.js.map +1 -1
  104. package/dist/react/ui/alert-dialog.d.ts +12 -12
  105. package/dist/react/ui/alert-dialog.d.ts.map +1 -1
  106. package/dist/react/ui/alert-dialog.js.map +1 -1
  107. package/dist/react/ui/alert.d.ts +4 -4
  108. package/dist/react/ui/alert.d.ts.map +1 -1
  109. package/dist/react/ui/alert.js.map +1 -1
  110. package/dist/react/ui/aspect-ratio.d.ts +2 -2
  111. package/dist/react/ui/aspect-ratio.d.ts.map +1 -1
  112. package/dist/react/ui/aspect-ratio.js.map +1 -1
  113. package/dist/react/ui/avatar.d.ts +4 -4
  114. package/dist/react/ui/avatar.d.ts.map +1 -1
  115. package/dist/react/ui/avatar.js.map +1 -1
  116. package/dist/react/ui/badge.d.ts +2 -2
  117. package/dist/react/ui/badge.d.ts.map +1 -1
  118. package/dist/react/ui/badge.js.map +1 -1
  119. package/dist/react/ui/breadcrumb.d.ts +8 -8
  120. package/dist/react/ui/breadcrumb.d.ts.map +1 -1
  121. package/dist/react/ui/breadcrumb.js.map +1 -1
  122. package/dist/react/ui/button-group.d.ts +6 -6
  123. package/dist/react/ui/button-group.d.ts.map +1 -1
  124. package/dist/react/ui/button-group.js.map +1 -1
  125. package/dist/react/ui/button.d.ts +4 -4
  126. package/dist/react/ui/button.d.ts.map +1 -1
  127. package/dist/react/ui/button.js.map +1 -1
  128. package/dist/react/ui/calendar.d.ts +3 -3
  129. package/dist/react/ui/calendar.d.ts.map +1 -1
  130. package/dist/react/ui/calendar.js.map +1 -1
  131. package/dist/react/ui/card.d.ts +8 -8
  132. package/dist/react/ui/card.d.ts.map +1 -1
  133. package/dist/react/ui/card.js.map +1 -1
  134. package/dist/react/ui/carousel.d.ts +6 -6
  135. package/dist/react/ui/carousel.d.ts.map +1 -1
  136. package/dist/react/ui/carousel.js.map +1 -1
  137. package/dist/react/ui/chart.d.ts +19 -19
  138. package/dist/react/ui/chart.d.ts.map +1 -1
  139. package/dist/react/ui/chart.js.map +1 -1
  140. package/dist/react/ui/checkbox.d.ts +2 -2
  141. package/dist/react/ui/checkbox.d.ts.map +1 -1
  142. package/dist/react/ui/checkbox.js.map +1 -1
  143. package/dist/react/ui/collapsible.d.ts +4 -4
  144. package/dist/react/ui/collapsible.d.ts.map +1 -1
  145. package/dist/react/ui/collapsible.js.map +1 -1
  146. package/dist/react/ui/command.d.ts +10 -10
  147. package/dist/react/ui/command.d.ts.map +1 -1
  148. package/dist/react/ui/command.js.map +1 -1
  149. package/dist/react/ui/context-menu.d.ts +16 -16
  150. package/dist/react/ui/context-menu.d.ts.map +1 -1
  151. package/dist/react/ui/context-menu.js.map +1 -1
  152. package/dist/react/ui/dialog.d.ts +11 -11
  153. package/dist/react/ui/dialog.d.ts.map +1 -1
  154. package/dist/react/ui/dialog.js.map +1 -1
  155. package/dist/react/ui/drawer.d.ts +11 -11
  156. package/dist/react/ui/drawer.d.ts.map +1 -1
  157. package/dist/react/ui/drawer.js.map +1 -1
  158. package/dist/react/ui/dropdown-menu.d.ts +16 -16
  159. package/dist/react/ui/dropdown-menu.d.ts.map +1 -1
  160. package/dist/react/ui/dropdown-menu.js.map +1 -1
  161. package/dist/react/ui/empty.d.ts +9 -9
  162. package/dist/react/ui/empty.d.ts.map +1 -1
  163. package/dist/react/ui/empty.js.map +1 -1
  164. package/dist/react/ui/field.d.ts +13 -13
  165. package/dist/react/ui/field.d.ts.map +1 -1
  166. package/dist/react/ui/field.js.map +1 -1
  167. package/dist/react/ui/form.d.ts +9 -9
  168. package/dist/react/ui/form.d.ts.map +1 -1
  169. package/dist/react/ui/form.js.map +1 -1
  170. package/dist/react/ui/hover-card.d.ts +4 -4
  171. package/dist/react/ui/hover-card.d.ts.map +1 -1
  172. package/dist/react/ui/hover-card.js.map +1 -1
  173. package/dist/react/ui/index.d.ts +53 -0
  174. package/dist/react/ui/input-group.d.ts +10 -10
  175. package/dist/react/ui/input-group.d.ts.map +1 -1
  176. package/dist/react/ui/input-group.js.map +1 -1
  177. package/dist/react/ui/input-otp.d.ts +5 -5
  178. package/dist/react/ui/input-otp.d.ts.map +1 -1
  179. package/dist/react/ui/input-otp.js.map +1 -1
  180. package/dist/react/ui/input.d.ts +2 -2
  181. package/dist/react/ui/input.d.ts.map +1 -1
  182. package/dist/react/ui/input.js.map +1 -1
  183. package/dist/react/ui/item.d.ts +14 -14
  184. package/dist/react/ui/item.d.ts.map +1 -1
  185. package/dist/react/ui/item.js.map +1 -1
  186. package/dist/react/ui/kbd.d.ts +3 -3
  187. package/dist/react/ui/kbd.d.ts.map +1 -1
  188. package/dist/react/ui/kbd.js.map +1 -1
  189. package/dist/react/ui/label.d.ts +2 -2
  190. package/dist/react/ui/label.d.ts.map +1 -1
  191. package/dist/react/ui/label.js.map +1 -1
  192. package/dist/react/ui/menubar.d.ts +17 -17
  193. package/dist/react/ui/menubar.d.ts.map +1 -1
  194. package/dist/react/ui/menubar.js.map +1 -1
  195. package/dist/react/ui/navigation-menu.d.ts +11 -11
  196. package/dist/react/ui/navigation-menu.d.ts.map +1 -1
  197. package/dist/react/ui/navigation-menu.js.map +1 -1
  198. package/dist/react/ui/pagination.d.ts +8 -8
  199. package/dist/react/ui/pagination.d.ts.map +1 -1
  200. package/dist/react/ui/pagination.js.map +1 -1
  201. package/dist/react/ui/popover.d.ts +5 -5
  202. package/dist/react/ui/popover.d.ts.map +1 -1
  203. package/dist/react/ui/popover.js.map +1 -1
  204. package/dist/react/ui/progress.d.ts +2 -2
  205. package/dist/react/ui/progress.d.ts.map +1 -1
  206. package/dist/react/ui/progress.js.map +1 -1
  207. package/dist/react/ui/radio-group.d.ts +3 -3
  208. package/dist/react/ui/radio-group.d.ts.map +1 -1
  209. package/dist/react/ui/radio-group.js.map +1 -1
  210. package/dist/react/ui/resizable.d.ts +4 -4
  211. package/dist/react/ui/resizable.d.ts.map +1 -1
  212. package/dist/react/ui/resizable.js.map +1 -1
  213. package/dist/react/ui/scroll-area.d.ts +3 -3
  214. package/dist/react/ui/scroll-area.d.ts.map +1 -1
  215. package/dist/react/ui/scroll-area.js.map +1 -1
  216. package/dist/react/ui/select.d.ts +11 -11
  217. package/dist/react/ui/select.d.ts.map +1 -1
  218. package/dist/react/ui/select.js.map +1 -1
  219. package/dist/react/ui/separator.d.ts +2 -2
  220. package/dist/react/ui/separator.d.ts.map +1 -1
  221. package/dist/react/ui/separator.js.map +1 -1
  222. package/dist/react/ui/sheet.d.ts +9 -9
  223. package/dist/react/ui/sheet.d.ts.map +1 -1
  224. package/dist/react/ui/sheet.js.map +1 -1
  225. package/dist/react/ui/sidebar.d.ts +38 -55
  226. package/dist/react/ui/sidebar.d.ts.map +1 -1
  227. package/dist/react/ui/sidebar.js.map +1 -1
  228. package/dist/react/ui/skeleton.d.ts +2 -2
  229. package/dist/react/ui/skeleton.d.ts.map +1 -1
  230. package/dist/react/ui/skeleton.js.map +1 -1
  231. package/dist/react/ui/slider.d.ts +2 -2
  232. package/dist/react/ui/slider.d.ts.map +1 -1
  233. package/dist/react/ui/slider.js.map +1 -1
  234. package/dist/react/ui/sonner.d.ts +2 -2
  235. package/dist/react/ui/sonner.d.ts.map +1 -1
  236. package/dist/react/ui/sonner.js.map +1 -1
  237. package/dist/react/ui/spinner.d.ts +2 -2
  238. package/dist/react/ui/spinner.d.ts.map +1 -1
  239. package/dist/react/ui/spinner.js.map +1 -1
  240. package/dist/react/ui/switch.d.ts +2 -2
  241. package/dist/react/ui/switch.d.ts.map +1 -1
  242. package/dist/react/ui/switch.js.map +1 -1
  243. package/dist/react/ui/table.d.ts +9 -9
  244. package/dist/react/ui/table.d.ts.map +1 -1
  245. package/dist/react/ui/table.js.map +1 -1
  246. package/dist/react/ui/tabs.d.ts +5 -5
  247. package/dist/react/ui/tabs.d.ts.map +1 -1
  248. package/dist/react/ui/tabs.js.map +1 -1
  249. package/dist/react/ui/textarea.d.ts +2 -2
  250. package/dist/react/ui/textarea.d.ts.map +1 -1
  251. package/dist/react/ui/textarea.js.map +1 -1
  252. package/dist/react/ui/toggle-group.d.ts +3 -3
  253. package/dist/react/ui/toggle-group.d.ts.map +1 -1
  254. package/dist/react/ui/toggle-group.js.map +1 -1
  255. package/dist/react/ui/toggle.d.ts +4 -4
  256. package/dist/react/ui/toggle.d.ts.map +1 -1
  257. package/dist/react/ui/toggle.js.map +1 -1
  258. package/dist/react/ui/tooltip.d.ts +5 -5
  259. package/dist/react/ui/tooltip.d.ts.map +1 -1
  260. package/dist/react/ui/tooltip.js.map +1 -1
  261. package/dist/shared/src/cache.d.ts +1 -0
  262. package/dist/shared/src/execute.d.ts +1 -0
  263. package/dist/shared/src/genie.d.ts +6 -0
  264. package/dist/shared/src/genie.d.ts.map +1 -1
  265. package/dist/shared/src/index.d.ts +7 -0
  266. package/dist/shared/src/plugin.d.ts +2 -0
  267. package/dist/shared/src/sql/helpers.d.ts +0 -1
  268. package/dist/shared/src/sql/helpers.d.ts.map +1 -1
  269. package/dist/shared/src/sql/types.d.ts.map +1 -1
  270. package/dist/shared/src/tunnel.d.ts +1 -0
  271. package/docs/api/appkit-ui/genie/GenieChatMessageList.md +7 -5
  272. package/docs/development/project-setup.md +1 -1
  273. package/docs/plugins/plugin-management.md +16 -2
  274. package/package.json +3 -1
@@ -1,5 +1,6 @@
1
1
  import { humanizeResourceType } from "../create/resource-defaults.js";
2
2
  import { promptOneResource } from "../create/prompt-resource.js";
3
+ import { resolveManifestInDir } from "../manifest-resolve.js";
3
4
  import { validateManifest } from "../validate/validate-manifest.js";
4
5
  import fs from "node:fs";
5
6
  import path from "node:path";
@@ -12,11 +13,16 @@ async function runPluginAddResource(options) {
12
13
  intro("Add resource to plugin manifest");
13
14
  const cwd = process.cwd();
14
15
  const pluginDir = path.resolve(cwd, options.path ?? ".");
15
- const manifestPath = path.join(pluginDir, "manifest.json");
16
- if (!fs.existsSync(manifestPath)) {
17
- console.error(`manifest.json not found at ${manifestPath}`);
16
+ const resolved = resolveManifestInDir(pluginDir, { allowJsManifest: true });
17
+ if (!resolved) {
18
+ console.error(`No manifest found in ${pluginDir}. This command requires manifest.json (manifest.js cannot be edited in place).`);
18
19
  process.exit(1);
19
20
  }
21
+ if (resolved.type !== "json") {
22
+ console.error(`Editable manifest not found. add-resource only supports plugin directories that contain manifest.json (found ${path.basename(resolved.path)}).`);
23
+ process.exit(1);
24
+ }
25
+ const manifestPath = resolved.path;
20
26
  let manifest;
21
27
  try {
22
28
  const raw = fs.readFileSync(manifestPath, "utf-8");
@@ -51,7 +57,7 @@ async function runPluginAddResource(options) {
51
57
  outro("Resource added.");
52
58
  console.log(`\nAdded ${alias} as ${spec.required ? "required" : "optional"} to ${path.relative(cwd, manifestPath)}`);
53
59
  }
54
- const pluginAddResourceCommand = new Command("add-resource").description("Add a resource requirement to an existing plugin manifest (interactive). Overwrites manifest.json in place.").option("-p, --path <dir>", "Plugin directory containing manifest.json (default: .)").action((opts) => runPluginAddResource(opts).catch((err) => {
60
+ const pluginAddResourceCommand = new Command("add-resource").description("Add a resource requirement to an existing plugin manifest (interactive). Overwrites manifest.json in place.").option("-p, --path <dir>", "Plugin directory containing manifest.json, which will be edited in place (default: .)").action((opts) => runPluginAddResource(opts).catch((err) => {
55
61
  console.error(err);
56
62
  process.exit(1);
57
63
  }));
@@ -1 +1 @@
1
- {"version":3,"file":"add-resource.js","names":[],"sources":["../../../../../src/cli/commands/plugin/add-resource/add-resource.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport process from \"node:process\";\nimport { cancel, intro, outro } from \"@clack/prompts\";\nimport { Command } from \"commander\";\nimport { promptOneResource } from \"../create/prompt-resource\";\nimport { humanizeResourceType } from \"../create/resource-defaults\";\nimport type { PluginManifest } from \"../manifest-types\";\nimport { validateManifest } from \"../validate/validate-manifest\";\n\n/** Extended manifest type that preserves extra JSON fields (e.g. $schema, author, version) for round-trip writes. */\ninterface ManifestWithExtras extends PluginManifest {\n [key: string]: unknown;\n}\n\nasync function runPluginAddResource(options: { path?: string }): Promise<void> {\n intro(\"Add resource to plugin manifest\");\n\n const cwd = process.cwd();\n const pluginDir = path.resolve(cwd, options.path ?? \".\");\n const manifestPath = path.join(pluginDir, \"manifest.json\");\n\n if (!fs.existsSync(manifestPath)) {\n console.error(`manifest.json not found at ${manifestPath}`);\n process.exit(1);\n }\n\n let manifest: ManifestWithExtras;\n try {\n const raw = fs.readFileSync(manifestPath, \"utf-8\");\n const parsed = JSON.parse(raw) as unknown;\n const result = validateManifest(parsed);\n if (!result.valid || !result.manifest) {\n console.error(\n \"Invalid manifest. Run `appkit plugin validate` for details.\",\n );\n process.exit(1);\n }\n manifest = parsed as ManifestWithExtras;\n } catch (err) {\n console.error(\n \"Failed to read or parse manifest.json:\",\n err instanceof Error ? err.message : err,\n );\n process.exit(1);\n }\n\n const spec = await promptOneResource();\n if (!spec) {\n cancel(\"Cancelled.\");\n process.exit(0);\n }\n\n const alias = humanizeResourceType(spec.type);\n const entry = {\n type: spec.type,\n alias,\n resourceKey: spec.resourceKey,\n description: spec.description || `Required for ${alias} functionality.`,\n permission: spec.permission,\n fields: spec.fields,\n };\n\n if (spec.required) {\n manifest.resources.required.push(entry);\n } else {\n manifest.resources.optional.push(entry);\n }\n\n fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\\n`);\n\n outro(\"Resource added.\");\n console.log(\n `\\nAdded ${alias} as ${spec.required ? \"required\" : \"optional\"} to ${path.relative(cwd, manifestPath)}`,\n );\n}\n\nexport const pluginAddResourceCommand = new Command(\"add-resource\")\n .description(\n \"Add a resource requirement to an existing plugin manifest (interactive). Overwrites manifest.json in place.\",\n )\n .option(\n \"-p, --path <dir>\",\n \"Plugin directory containing manifest.json (default: .)\",\n )\n .action((opts) =>\n runPluginAddResource(opts).catch((err) => {\n console.error(err);\n process.exit(1);\n }),\n );\n"],"mappings":";;;;;;;;;;AAeA,eAAe,qBAAqB,SAA2C;AAC7E,OAAM,kCAAkC;CAExC,MAAM,MAAM,QAAQ,KAAK;CACzB,MAAM,YAAY,KAAK,QAAQ,KAAK,QAAQ,QAAQ,IAAI;CACxD,MAAM,eAAe,KAAK,KAAK,WAAW,gBAAgB;AAE1D,KAAI,CAAC,GAAG,WAAW,aAAa,EAAE;AAChC,UAAQ,MAAM,8BAA8B,eAAe;AAC3D,UAAQ,KAAK,EAAE;;CAGjB,IAAI;AACJ,KAAI;EACF,MAAM,MAAM,GAAG,aAAa,cAAc,QAAQ;EAClD,MAAM,SAAS,KAAK,MAAM,IAAI;EAC9B,MAAM,SAAS,iBAAiB,OAAO;AACvC,MAAI,CAAC,OAAO,SAAS,CAAC,OAAO,UAAU;AACrC,WAAQ,MACN,8DACD;AACD,WAAQ,KAAK,EAAE;;AAEjB,aAAW;UACJ,KAAK;AACZ,UAAQ,MACN,0CACA,eAAe,QAAQ,IAAI,UAAU,IACtC;AACD,UAAQ,KAAK,EAAE;;CAGjB,MAAM,OAAO,MAAM,mBAAmB;AACtC,KAAI,CAAC,MAAM;AACT,SAAO,aAAa;AACpB,UAAQ,KAAK,EAAE;;CAGjB,MAAM,QAAQ,qBAAqB,KAAK,KAAK;CAC7C,MAAM,QAAQ;EACZ,MAAM,KAAK;EACX;EACA,aAAa,KAAK;EAClB,aAAa,KAAK,eAAe,gBAAgB,MAAM;EACvD,YAAY,KAAK;EACjB,QAAQ,KAAK;EACd;AAED,KAAI,KAAK,SACP,UAAS,UAAU,SAAS,KAAK,MAAM;KAEvC,UAAS,UAAU,SAAS,KAAK,MAAM;AAGzC,IAAG,cAAc,cAAc,GAAG,KAAK,UAAU,UAAU,MAAM,EAAE,CAAC,IAAI;AAExE,OAAM,kBAAkB;AACxB,SAAQ,IACN,WAAW,MAAM,MAAM,KAAK,WAAW,aAAa,WAAW,MAAM,KAAK,SAAS,KAAK,aAAa,GACtG;;AAGH,MAAa,2BAA2B,IAAI,QAAQ,eAAe,CAChE,YACC,8GACD,CACA,OACC,oBACA,yDACD,CACA,QAAQ,SACP,qBAAqB,KAAK,CAAC,OAAO,QAAQ;AACxC,SAAQ,MAAM,IAAI;AAClB,SAAQ,KAAK,EAAE;EACf,CACH"}
1
+ {"version":3,"file":"add-resource.js","names":[],"sources":["../../../../../src/cli/commands/plugin/add-resource/add-resource.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport process from \"node:process\";\nimport { cancel, intro, outro } from \"@clack/prompts\";\nimport { Command } from \"commander\";\nimport { promptOneResource } from \"../create/prompt-resource\";\nimport { humanizeResourceType } from \"../create/resource-defaults\";\nimport { resolveManifestInDir } from \"../manifest-resolve\";\nimport type { PluginManifest } from \"../manifest-types\";\nimport { validateManifest } from \"../validate/validate-manifest\";\n\n/** Extended manifest type that preserves extra JSON fields (e.g. $schema, author, version) for round-trip writes. */\ninterface ManifestWithExtras extends PluginManifest {\n [key: string]: unknown;\n}\n\nasync function runPluginAddResource(options: { path?: string }): Promise<void> {\n intro(\"Add resource to plugin manifest\");\n\n const cwd = process.cwd();\n const pluginDir = path.resolve(cwd, options.path ?? \".\");\n const resolved = resolveManifestInDir(pluginDir, { allowJsManifest: true });\n\n if (!resolved) {\n console.error(\n `No manifest found in ${pluginDir}. This command requires manifest.json (manifest.js cannot be edited in place).`,\n );\n process.exit(1);\n }\n\n if (resolved.type !== \"json\") {\n console.error(\n `Editable manifest not found. add-resource only supports plugin directories that contain manifest.json (found ${path.basename(resolved.path)}).`,\n );\n process.exit(1);\n }\n\n const manifestPath = resolved.path;\n\n let manifest: ManifestWithExtras;\n try {\n const raw = fs.readFileSync(manifestPath, \"utf-8\");\n const parsed = JSON.parse(raw) as unknown;\n const result = validateManifest(parsed);\n if (!result.valid || !result.manifest) {\n console.error(\n \"Invalid manifest. Run `appkit plugin validate` for details.\",\n );\n process.exit(1);\n }\n manifest = parsed as ManifestWithExtras;\n } catch (err) {\n console.error(\n \"Failed to read or parse manifest.json:\",\n err instanceof Error ? err.message : err,\n );\n process.exit(1);\n }\n\n const spec = await promptOneResource();\n if (!spec) {\n cancel(\"Cancelled.\");\n process.exit(0);\n }\n\n const alias = humanizeResourceType(spec.type);\n const entry = {\n type: spec.type,\n alias,\n resourceKey: spec.resourceKey,\n description: spec.description || `Required for ${alias} functionality.`,\n permission: spec.permission,\n fields: spec.fields,\n };\n\n if (spec.required) {\n manifest.resources.required.push(entry);\n } else {\n manifest.resources.optional.push(entry);\n }\n\n fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\\n`);\n\n outro(\"Resource added.\");\n console.log(\n `\\nAdded ${alias} as ${spec.required ? \"required\" : \"optional\"} to ${path.relative(cwd, manifestPath)}`,\n );\n}\n\nexport const pluginAddResourceCommand = new Command(\"add-resource\")\n .description(\n \"Add a resource requirement to an existing plugin manifest (interactive). Overwrites manifest.json in place.\",\n )\n .option(\n \"-p, --path <dir>\",\n \"Plugin directory containing manifest.json, which will be edited in place (default: .)\",\n )\n .action((opts) =>\n runPluginAddResource(opts).catch((err) => {\n console.error(err);\n process.exit(1);\n }),\n );\n"],"mappings":";;;;;;;;;;;AAgBA,eAAe,qBAAqB,SAA2C;AAC7E,OAAM,kCAAkC;CAExC,MAAM,MAAM,QAAQ,KAAK;CACzB,MAAM,YAAY,KAAK,QAAQ,KAAK,QAAQ,QAAQ,IAAI;CACxD,MAAM,WAAW,qBAAqB,WAAW,EAAE,iBAAiB,MAAM,CAAC;AAE3E,KAAI,CAAC,UAAU;AACb,UAAQ,MACN,wBAAwB,UAAU,gFACnC;AACD,UAAQ,KAAK,EAAE;;AAGjB,KAAI,SAAS,SAAS,QAAQ;AAC5B,UAAQ,MACN,gHAAgH,KAAK,SAAS,SAAS,KAAK,CAAC,IAC9I;AACD,UAAQ,KAAK,EAAE;;CAGjB,MAAM,eAAe,SAAS;CAE9B,IAAI;AACJ,KAAI;EACF,MAAM,MAAM,GAAG,aAAa,cAAc,QAAQ;EAClD,MAAM,SAAS,KAAK,MAAM,IAAI;EAC9B,MAAM,SAAS,iBAAiB,OAAO;AACvC,MAAI,CAAC,OAAO,SAAS,CAAC,OAAO,UAAU;AACrC,WAAQ,MACN,8DACD;AACD,WAAQ,KAAK,EAAE;;AAEjB,aAAW;UACJ,KAAK;AACZ,UAAQ,MACN,0CACA,eAAe,QAAQ,IAAI,UAAU,IACtC;AACD,UAAQ,KAAK,EAAE;;CAGjB,MAAM,OAAO,MAAM,mBAAmB;AACtC,KAAI,CAAC,MAAM;AACT,SAAO,aAAa;AACpB,UAAQ,KAAK,EAAE;;CAGjB,MAAM,QAAQ,qBAAqB,KAAK,KAAK;CAC7C,MAAM,QAAQ;EACZ,MAAM,KAAK;EACX;EACA,aAAa,KAAK;EAClB,aAAa,KAAK,eAAe,gBAAgB,MAAM;EACvD,YAAY,KAAK;EACjB,QAAQ,KAAK;EACd;AAED,KAAI,KAAK,SACP,UAAS,UAAU,SAAS,KAAK,MAAM;KAEvC,UAAS,UAAU,SAAS,KAAK,MAAM;AAGzC,IAAG,cAAc,cAAc,GAAG,KAAK,UAAU,UAAU,MAAM,EAAE,CAAC,IAAI;AAExE,OAAM,kBAAkB;AACxB,SAAQ,IACN,WAAW,MAAM,MAAM,KAAK,WAAW,aAAa,WAAW,MAAM,KAAK,SAAS,KAAK,aAAa,GACtG;;AAGH,MAAa,2BAA2B,IAAI,QAAQ,eAAe,CAChE,YACC,8GACD,CACA,OACC,oBACA,wFACD,CACA,QAAQ,SACP,qBAAqB,KAAK,CAAC,OAAO,QAAQ;AACxC,SAAQ,MAAM,IAAI;AAClB,SAAQ,KAAK,EAAE;EACf,CACH"}
@@ -72,7 +72,7 @@ function rollback(written, targetDir) {
72
72
  }
73
73
  /**
74
74
  * Scaffold plugin files into targetDir. Pure: no interactive I/O.
75
- * Writes manifest.json, manifest.ts, {name}.ts, index.ts; for isolated also package.json, tsconfig.json, README.md.
75
+ * Writes manifest.json, {name}.ts, index.ts; for isolated also package.json, tsconfig.json, README.md.
76
76
  * On failure, rolls back any files already written.
77
77
  */
78
78
  function scaffoldPlugin(targetDir, answers, options) {
@@ -83,24 +83,18 @@ function scaffoldPlugin(targetDir, answers, options) {
83
83
  const className = toPascalCase(answers.name);
84
84
  const exportName = toCamelCase(answers.name);
85
85
  writeTracked(path.join(targetDir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`, written);
86
- writeTracked(path.join(targetDir, "manifest.ts"), `import { readFileSync } from "node:fs";
87
- import { dirname, join } from "node:path";
88
- import { fileURLToPath } from "node:url";
89
- import type { PluginManifest } from "@databricks/appkit";
90
-
91
- const __dirname = dirname(fileURLToPath(import.meta.url));
92
-
93
- export const manifest = JSON.parse(
94
- readFileSync(join(__dirname, "manifest.json"), "utf-8"),
95
- ) as PluginManifest;
96
- `, written);
97
- const pluginTs = `import { Plugin, toPlugin, type IAppRouter } from "@databricks/appkit";
98
- import { manifest } from "./manifest.js";
86
+ const pluginTs = `import {
87
+ Plugin,
88
+ toPlugin,
89
+ type IAppRouter,
90
+ type PluginManifest,
91
+ } from "@databricks/appkit";
92
+ import manifest from "./manifest.json";
99
93
 
100
94
  export class ${className} extends Plugin {
101
95
  name = "${answers.name}";
102
96
 
103
- static manifest = manifest;
97
+ static manifest = manifest as PluginManifest;
104
98
 
105
99
  injectRoutes(router: IAppRouter): void {
106
100
  // Add your routes here, e.g.:
@@ -122,7 +116,7 @@ export const ${exportName} = toPlugin<
122
116
  >(${className}, "${answers.name}");
123
117
  `;
124
118
  writeTracked(path.join(targetDir, `${answers.name}.ts`), pluginTs, written);
125
- const indexTs = `export { ${className}, ${exportName} } from "./${answers.name}.js";
119
+ const indexTs = `export { ${className}, ${exportName} } from "./${answers.name}";
126
120
  `;
127
121
  writeTracked(path.join(targetDir, "index.ts"), indexTs, written);
128
122
  if (options.isolated) {
@@ -1 +1 @@
1
- {"version":3,"file":"scaffold.js","names":[],"sources":["../../../../../src/cli/commands/plugin/create/scaffold.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { humanizeResourceType, MANIFEST_SCHEMA_ID } from \"./resource-defaults\";\nimport type { CreateAnswers } from \"./types\";\n\n/** Convert kebab-name to PascalCase (e.g. my-plugin -> MyPlugin). */\nfunction toPascalCase(name: string): string {\n return name\n .split(\"-\")\n .map((s) => s.charAt(0).toUpperCase() + s.slice(1).toLowerCase())\n .join(\"\");\n}\n\n/** Convert kebab-name to camelCase (e.g. my-plugin -> myPlugin). */\nfunction toCamelCase(name: string): string {\n const pascal = toPascalCase(name);\n return pascal.charAt(0).toLowerCase() + pascal.slice(1);\n}\n\n/** Build manifest.json resources from selected resources. */\nfunction buildManifestResources(answers: CreateAnswers) {\n const required: unknown[] = [];\n const optional: unknown[] = [];\n\n for (const r of answers.resources) {\n const alias = humanizeResourceType(r.type);\n const entry = {\n type: r.type,\n alias,\n resourceKey: r.resourceKey,\n description: r.description || `Required for ${alias} functionality.`,\n permission: r.permission,\n fields: r.fields,\n };\n if (r.required) {\n required.push(entry);\n } else {\n optional.push(entry);\n }\n }\n\n return { required, optional };\n}\n\n/** Build full manifest object for manifest.json. */\nfunction buildManifest(answers: CreateAnswers): Record<string, unknown> {\n const { required, optional } = buildManifestResources(answers);\n const manifest: Record<string, unknown> = {\n $schema: MANIFEST_SCHEMA_ID,\n name: answers.name,\n displayName: answers.displayName,\n description: answers.description,\n resources: { required, optional },\n };\n if (answers.author) manifest.author = answers.author;\n manifest.version = answers.version || \"0.1.0\";\n if (answers.license) manifest.license = answers.license;\n return manifest;\n}\n\n/** Resolve absolute target directory from cwd and answers. */\nexport function resolveTargetDir(cwd: string, answers: CreateAnswers): string {\n return path.resolve(cwd, answers.targetPath);\n}\n\n/** Track files written during scaffolding for rollback on failure. */\nfunction writeTracked(\n filePath: string,\n content: string,\n written: string[],\n): void {\n fs.writeFileSync(filePath, content);\n written.push(filePath);\n}\n\n/** Remove files written during a failed scaffold attempt. */\nfunction rollback(written: string[], targetDir: string): void {\n for (const filePath of written.reverse()) {\n try {\n fs.unlinkSync(filePath);\n } catch {\n // best-effort cleanup\n }\n }\n try {\n const remaining = fs.readdirSync(targetDir);\n if (remaining.length === 0) fs.rmdirSync(targetDir);\n } catch {\n // directory may not be empty or may have been removed already\n }\n}\n\n/**\n * Scaffold plugin files into targetDir. Pure: no interactive I/O.\n * Writes manifest.json, manifest.ts, {name}.ts, index.ts; for isolated also package.json, tsconfig.json, README.md.\n * On failure, rolls back any files already written.\n */\nexport function scaffoldPlugin(\n targetDir: string,\n answers: CreateAnswers,\n options: { isolated: boolean },\n): void {\n fs.mkdirSync(targetDir, { recursive: true });\n\n const written: string[] = [];\n\n try {\n const manifest = buildManifest(answers);\n const className = toPascalCase(answers.name);\n const exportName = toCamelCase(answers.name);\n\n writeTracked(\n path.join(targetDir, \"manifest.json\"),\n `${JSON.stringify(manifest, null, 2)}\\n`,\n written,\n );\n\n const manifestTs = `import { readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type { PluginManifest } from \"@databricks/appkit\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nexport const manifest = JSON.parse(\n readFileSync(join(__dirname, \"manifest.json\"), \"utf-8\"),\n) as PluginManifest;\n`;\n\n writeTracked(path.join(targetDir, \"manifest.ts\"), manifestTs, written);\n\n const pluginTs = `import { Plugin, toPlugin, type IAppRouter } from \"@databricks/appkit\";\nimport { manifest } from \"./manifest.js\";\n\nexport class ${className} extends Plugin {\n name = \"${answers.name}\";\n\n static manifest = manifest;\n\n injectRoutes(router: IAppRouter): void {\n // Add your routes here, e.g.:\n // this.route(router, {\n // name: \"example\",\n // method: \"get\",\n // path: \"/\",\n // handler: async (_req, res) => {\n // res.json({ message: \"Hello from ${answers.name}\" });\n // },\n // });\n }\n}\n\nexport const ${exportName} = toPlugin<\n typeof ${className},\n Record<string, never>,\n \"${answers.name}\"\n>(${className}, \"${answers.name}\");\n`;\n\n writeTracked(path.join(targetDir, `${answers.name}.ts`), pluginTs, written);\n\n const indexTs = `export { ${className}, ${exportName} } from \"./${answers.name}.js\";\n`;\n\n writeTracked(path.join(targetDir, \"index.ts\"), indexTs, written);\n\n if (options.isolated) {\n const packageName =\n answers.name.includes(\"/\") || answers.name.startsWith(\"@\")\n ? answers.name\n : `appkit-plugin-${answers.name}`;\n\n const packageJson = {\n name: packageName,\n version: answers.version || \"0.1.0\",\n type: \"module\",\n main: \"./dist/index.js\",\n types: \"./dist/index.d.ts\",\n files: [\"dist\"],\n scripts: {\n build: \"tsc\",\n typecheck: \"tsc --noEmit\",\n },\n peerDependencies: {\n \"@databricks/appkit\": \">=0.5.0\",\n },\n devDependencies: {\n typescript: \"^5.0.0\",\n },\n };\n\n writeTracked(\n path.join(targetDir, \"package.json\"),\n `${JSON.stringify(packageJson, null, 2)}\\n`,\n written,\n );\n\n const tsconfigJson = {\n compilerOptions: {\n target: \"ES2022\",\n module: \"NodeNext\",\n moduleResolution: \"NodeNext\",\n outDir: \"dist\",\n rootDir: \".\",\n declaration: true,\n strict: true,\n skipLibCheck: true,\n },\n include: [\"*.ts\"],\n exclude: [\"node_modules\", \"dist\"],\n };\n\n writeTracked(\n path.join(targetDir, \"tsconfig.json\"),\n `${JSON.stringify(tsconfigJson, null, 2)}\\n`,\n written,\n );\n\n const readme = `# ${answers.displayName}\n\n${answers.description}\n\n## Installation\n\n\\`\\`\\`bash\npnpm add ${packageName} @databricks/appkit\n\\`\\`\\`\n\n## Usage\n\nRegister the plugin in your AppKit app:\n\n\\`\\`\\`ts\nimport { createApp } from \"@databricks/appkit\";\nimport { ${exportName} } from \"${packageName}\";\n\ncreateApp({\n plugins: [\n ${exportName}(),\n // ... other plugins\n ],\n}).then((app) => { /* ... */ });\n\\`\\`\\`\n`;\n\n writeTracked(path.join(targetDir, \"README.md\"), readme, written);\n }\n } catch (err) {\n rollback(written, targetDir);\n throw err;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"scaffold.js","names":[],"sources":["../../../../../src/cli/commands/plugin/create/scaffold.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { humanizeResourceType, MANIFEST_SCHEMA_ID } from \"./resource-defaults\";\nimport type { CreateAnswers } from \"./types\";\n\n/** Convert kebab-name to PascalCase (e.g. my-plugin -> MyPlugin). */\nfunction toPascalCase(name: string): string {\n return name\n .split(\"-\")\n .map((s) => s.charAt(0).toUpperCase() + s.slice(1).toLowerCase())\n .join(\"\");\n}\n\n/** Convert kebab-name to camelCase (e.g. my-plugin -> myPlugin). */\nfunction toCamelCase(name: string): string {\n const pascal = toPascalCase(name);\n return pascal.charAt(0).toLowerCase() + pascal.slice(1);\n}\n\n/** Build manifest.json resources from selected resources. */\nfunction buildManifestResources(answers: CreateAnswers) {\n const required: unknown[] = [];\n const optional: unknown[] = [];\n\n for (const r of answers.resources) {\n const alias = humanizeResourceType(r.type);\n const entry = {\n type: r.type,\n alias,\n resourceKey: r.resourceKey,\n description: r.description || `Required for ${alias} functionality.`,\n permission: r.permission,\n fields: r.fields,\n };\n if (r.required) {\n required.push(entry);\n } else {\n optional.push(entry);\n }\n }\n\n return { required, optional };\n}\n\n/** Build full manifest object for manifest.json. */\nfunction buildManifest(answers: CreateAnswers): Record<string, unknown> {\n const { required, optional } = buildManifestResources(answers);\n const manifest: Record<string, unknown> = {\n $schema: MANIFEST_SCHEMA_ID,\n name: answers.name,\n displayName: answers.displayName,\n description: answers.description,\n resources: { required, optional },\n };\n if (answers.author) manifest.author = answers.author;\n manifest.version = answers.version || \"0.1.0\";\n if (answers.license) manifest.license = answers.license;\n return manifest;\n}\n\n/** Resolve absolute target directory from cwd and answers. */\nexport function resolveTargetDir(cwd: string, answers: CreateAnswers): string {\n return path.resolve(cwd, answers.targetPath);\n}\n\n/** Track files written during scaffolding for rollback on failure. */\nfunction writeTracked(\n filePath: string,\n content: string,\n written: string[],\n): void {\n fs.writeFileSync(filePath, content);\n written.push(filePath);\n}\n\n/** Remove files written during a failed scaffold attempt. */\nfunction rollback(written: string[], targetDir: string): void {\n for (const filePath of written.reverse()) {\n try {\n fs.unlinkSync(filePath);\n } catch {\n // best-effort cleanup\n }\n }\n try {\n const remaining = fs.readdirSync(targetDir);\n if (remaining.length === 0) fs.rmdirSync(targetDir);\n } catch {\n // directory may not be empty or may have been removed already\n }\n}\n\n/**\n * Scaffold plugin files into targetDir. Pure: no interactive I/O.\n * Writes manifest.json, {name}.ts, index.ts; for isolated also package.json, tsconfig.json, README.md.\n * On failure, rolls back any files already written.\n */\nexport function scaffoldPlugin(\n targetDir: string,\n answers: CreateAnswers,\n options: { isolated: boolean },\n): void {\n fs.mkdirSync(targetDir, { recursive: true });\n\n const written: string[] = [];\n\n try {\n const manifest = buildManifest(answers);\n const className = toPascalCase(answers.name);\n const exportName = toCamelCase(answers.name);\n\n writeTracked(\n path.join(targetDir, \"manifest.json\"),\n `${JSON.stringify(manifest, null, 2)}\\n`,\n written,\n );\n\n const pluginTs = `import {\n Plugin,\n toPlugin,\n type IAppRouter,\n type PluginManifest,\n} from \"@databricks/appkit\";\nimport manifest from \"./manifest.json\";\n\nexport class ${className} extends Plugin {\n name = \"${answers.name}\";\n\n static manifest = manifest as PluginManifest;\n\n injectRoutes(router: IAppRouter): void {\n // Add your routes here, e.g.:\n // this.route(router, {\n // name: \"example\",\n // method: \"get\",\n // path: \"/\",\n // handler: async (_req, res) => {\n // res.json({ message: \"Hello from ${answers.name}\" });\n // },\n // });\n }\n}\n\nexport const ${exportName} = toPlugin<\n typeof ${className},\n Record<string, never>,\n \"${answers.name}\"\n>(${className}, \"${answers.name}\");\n`;\n\n writeTracked(path.join(targetDir, `${answers.name}.ts`), pluginTs, written);\n\n const indexTs = `export { ${className}, ${exportName} } from \"./${answers.name}\";\n`;\n\n writeTracked(path.join(targetDir, \"index.ts\"), indexTs, written);\n\n if (options.isolated) {\n const packageName =\n answers.name.includes(\"/\") || answers.name.startsWith(\"@\")\n ? answers.name\n : `appkit-plugin-${answers.name}`;\n\n const packageJson = {\n name: packageName,\n version: answers.version || \"0.1.0\",\n type: \"module\",\n main: \"./dist/index.js\",\n types: \"./dist/index.d.ts\",\n files: [\"dist\"],\n scripts: {\n build: \"tsc\",\n typecheck: \"tsc --noEmit\",\n },\n peerDependencies: {\n \"@databricks/appkit\": \">=0.5.0\",\n },\n devDependencies: {\n typescript: \"^5.0.0\",\n },\n };\n\n writeTracked(\n path.join(targetDir, \"package.json\"),\n `${JSON.stringify(packageJson, null, 2)}\\n`,\n written,\n );\n\n const tsconfigJson = {\n compilerOptions: {\n target: \"ES2022\",\n module: \"NodeNext\",\n moduleResolution: \"NodeNext\",\n outDir: \"dist\",\n rootDir: \".\",\n declaration: true,\n strict: true,\n skipLibCheck: true,\n },\n include: [\"*.ts\"],\n exclude: [\"node_modules\", \"dist\"],\n };\n\n writeTracked(\n path.join(targetDir, \"tsconfig.json\"),\n `${JSON.stringify(tsconfigJson, null, 2)}\\n`,\n written,\n );\n\n const readme = `# ${answers.displayName}\n\n${answers.description}\n\n## Installation\n\n\\`\\`\\`bash\npnpm add ${packageName} @databricks/appkit\n\\`\\`\\`\n\n## Usage\n\nRegister the plugin in your AppKit app:\n\n\\`\\`\\`ts\nimport { createApp } from \"@databricks/appkit\";\nimport { ${exportName} } from \"${packageName}\";\n\ncreateApp({\n plugins: [\n ${exportName}(),\n // ... other plugins\n ],\n}).then((app) => { /* ... */ });\n\\`\\`\\`\n`;\n\n writeTracked(path.join(targetDir, \"README.md\"), readme, written);\n }\n } catch (err) {\n rollback(written, targetDir);\n throw err;\n }\n}\n"],"mappings":"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"}
@@ -1,10 +1,14 @@
1
+ import { loadManifestFromFile, resolveManifestInDir } from "../manifest-resolve.js";
1
2
  import { validateManifest } from "../validate/validate-manifest.js";
3
+ import { shouldAllowJsManifestForDir } from "../trusted-js-manifest.js";
2
4
  import fs from "node:fs";
3
5
  import path from "node:path";
4
6
  import { Command } from "commander";
5
7
  import process from "node:process";
6
8
 
7
9
  //#region src/cli/commands/plugin/list/list.ts
10
+ /** Safety limit for recursive directory scanning to prevent runaway traversal. */
11
+ const MAX_SCAN_DEPTH = 5;
8
12
  function listFromManifestFile(manifestPath) {
9
13
  let raw;
10
14
  try {
@@ -29,31 +33,40 @@ function listFromManifestFile(manifestPath) {
29
33
  optional: Array.isArray(p.resources?.optional) ? p.resources.optional.length : 0
30
34
  }));
31
35
  }
32
- function listFromDirectory(dirPath, cwd) {
33
- const resolved = path.resolve(cwd, dirPath);
34
- if (!fs.existsSync(resolved) || !fs.statSync(resolved).isDirectory()) return [];
35
- const entries = fs.readdirSync(resolved, { withFileTypes: true });
36
- const rows = [];
36
+ async function collectPluginsRecursive(dir, cwd, rows, allowJsManifest, depth = 0) {
37
+ if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory() || depth >= MAX_SCAN_DEPTH) return;
38
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
37
39
  for (const entry of entries) {
38
40
  if (!entry.isDirectory()) continue;
39
- const manifestPath = path.join(resolved, entry.name, "manifest.json");
40
- if (!fs.existsSync(manifestPath)) continue;
41
- try {
42
- const raw = fs.readFileSync(manifestPath, "utf-8");
43
- const result = validateManifest(JSON.parse(raw));
44
- const manifest = result.valid ? result.manifest : null;
45
- if (!manifest) continue;
46
- const relPath = path.relative(cwd, path.dirname(manifestPath));
47
- const packagePath = relPath.startsWith(".") ? relPath : `./${relPath}`;
48
- rows.push({
49
- name: manifest.name,
50
- displayName: manifest.displayName ?? manifest.name,
51
- package: packagePath,
52
- required: Array.isArray(manifest.resources?.required) ? manifest.resources.required.length : 0,
53
- optional: Array.isArray(manifest.resources?.optional) ? manifest.resources.optional.length : 0
54
- });
55
- } catch {}
41
+ const childPath = path.join(dir, entry.name);
42
+ const allowJsForChild = allowJsManifest || shouldAllowJsManifestForDir(childPath);
43
+ const resolvedManifest = resolveManifestInDir(childPath, { allowJsManifest: allowJsForChild });
44
+ if (resolvedManifest) {
45
+ try {
46
+ const result = validateManifest(await loadManifestFromFile(resolvedManifest.path, resolvedManifest.type, { allowJsManifest: allowJsForChild }));
47
+ const manifest = result.valid ? result.manifest : null;
48
+ if (manifest) {
49
+ const relPath = path.relative(cwd, path.dirname(resolvedManifest.path));
50
+ const packagePath = relPath.startsWith(".") ? relPath : `./${relPath}`;
51
+ rows.push({
52
+ name: manifest.name,
53
+ displayName: manifest.displayName ?? manifest.name,
54
+ package: packagePath,
55
+ required: Array.isArray(manifest.resources?.required) ? manifest.resources.required.length : 0,
56
+ optional: Array.isArray(manifest.resources?.optional) ? manifest.resources.optional.length : 0
57
+ });
58
+ }
59
+ } catch {}
60
+ continue;
61
+ }
62
+ await collectPluginsRecursive(childPath, cwd, rows, allowJsManifest, depth + 1);
56
63
  }
64
+ }
65
+ async function listFromDirectory(dirPath, cwd, allowJsManifest = false) {
66
+ const resolved = path.resolve(cwd, dirPath);
67
+ if (!fs.existsSync(resolved) || !fs.statSync(resolved).isDirectory()) return [];
68
+ const rows = [];
69
+ await collectPluginsRecursive(resolved, cwd, rows, allowJsManifest);
57
70
  return rows;
58
71
  }
59
72
  function printTable(rows) {
@@ -81,13 +94,15 @@ function printTable(rows) {
81
94
  String(r.optional).padStart(3)
82
95
  ].join(" "));
83
96
  }
84
- function runPluginList(options) {
97
+ async function runPluginList(options) {
85
98
  const cwd = process.cwd();
99
+ const allowJsManifest = Boolean(options.allowJsManifest);
100
+ if (allowJsManifest) console.warn("Warning: --allow-js-manifest executes manifest.js/manifest.cjs files. Only use with trusted code.");
86
101
  let rows;
87
102
  if (options.dir !== void 0) {
88
- rows = listFromDirectory(options.dir, cwd);
103
+ rows = await listFromDirectory(options.dir, cwd, allowJsManifest);
89
104
  if (rows.length === 0 && options.dir) {
90
- console.error(`No plugin directories with manifest.json found in ${options.dir}`);
105
+ console.error(`No plugin directories with ${allowJsManifest ? "manifest.json or manifest.js" : "manifest.json"} found in ${options.dir}`);
91
106
  process.exit(1);
92
107
  }
93
108
  } else {
@@ -106,7 +121,10 @@ function runPluginList(options) {
106
121
  if (options.json) console.log(JSON.stringify(rows, null, 2));
107
122
  else printTable(rows);
108
123
  }
109
- 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 for plugin folders (each with manifest.json)").option("--json", "Output as JSON").action(runPluginList);
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
+ console.error(err);
126
+ process.exit(1);
127
+ }));
110
128
 
111
129
  //#endregion
112
130
  export { pluginListCommand };
@@ -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 { validateManifest } from \"../validate/validate-manifest\";\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\nexport function listFromDirectory(dirPath: string, cwd: string): PluginRow[] {\n const resolved = path.resolve(cwd, dirPath);\n if (!fs.existsSync(resolved) || !fs.statSync(resolved).isDirectory()) {\n return [];\n }\n const entries = fs.readdirSync(resolved, { withFileTypes: true });\n const rows: PluginRow[] = [];\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n const manifestPath = path.join(resolved, entry.name, \"manifest.json\");\n if (!fs.existsSync(manifestPath)) continue;\n try {\n const raw = fs.readFileSync(manifestPath, \"utf-8\");\n const obj = JSON.parse(raw);\n const result = validateManifest(obj);\n const manifest = result.valid ? result.manifest : null;\n if (!manifest) continue;\n const relPath = path.relative(cwd, path.dirname(manifestPath));\n const packagePath = relPath.startsWith(\".\") ? relPath : `./${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 } catch {\n // skip invalid manifests\n }\n }\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\nfunction runPluginList(options: {\n manifest?: string;\n dir?: string;\n json?: boolean;\n}): void {\n const cwd = process.cwd();\n let rows: PluginRow[];\n\n if (options.dir !== undefined) {\n rows = listFromDirectory(options.dir, cwd);\n if (rows.length === 0 && options.dir) {\n console.error(\n `No plugin directories with 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 for plugin folders (each with manifest.json)\",\n )\n .option(\"--json\", \"Output as JSON\")\n .action(runPluginList);\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 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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"}
@@ -0,0 +1,57 @@
1
+ import { createRequire } from "node:module";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { pathToFileURL } from "node:url";
5
+
6
+ //#region src/cli/commands/plugin/manifest-resolve.ts
7
+ const MANIFEST_JSON = "manifest.json";
8
+ /** Resolution order for manifest files in a plugin directory. */
9
+ const MANIFEST_CANDIDATES = [
10
+ MANIFEST_JSON,
11
+ "manifest.js",
12
+ "manifest.cjs"
13
+ ];
14
+ /**
15
+ * Resolve the manifest file in a plugin directory.
16
+ * By default tries only manifest.json. If allowJsManifest=true, then also
17
+ * tries manifest.js and manifest.cjs.
18
+ *
19
+ * @param pluginDir - Absolute path to the plugin directory
20
+ * @returns The resolved file path and type, or null if none found
21
+ */
22
+ function resolveManifestInDir(pluginDir, options = {}) {
23
+ const candidates = options.allowJsManifest ? MANIFEST_CANDIDATES : [MANIFEST_JSON];
24
+ for (const name of candidates) {
25
+ const manifestPath = path.join(pluginDir, name);
26
+ if (fs.existsSync(manifestPath) && fs.statSync(manifestPath).isFile()) return {
27
+ path: path.resolve(manifestPath),
28
+ type: name === MANIFEST_JSON ? "json" : "js"
29
+ };
30
+ }
31
+ return null;
32
+ }
33
+ /**
34
+ * Load a manifest from a file (JSON or JS).
35
+ * JSON: read and parse. JS: dynamic import (ESM) or require (CJS); the module must default-export the manifest object.
36
+ *
37
+ * @param manifestPath - Absolute path to manifest.json or manifest.js/.cjs
38
+ * @param type - "json" or "js"
39
+ * @returns The parsed manifest object (caller should validate with schema)
40
+ */
41
+ async function loadManifestFromFile(manifestPath, type, options = {}) {
42
+ if (type === "json") {
43
+ const raw = fs.readFileSync(manifestPath, "utf-8");
44
+ return JSON.parse(raw);
45
+ }
46
+ if (!options.allowJsManifest) throw new Error(`Refusing to execute JS manifest at ${manifestPath}. Pass --allow-js-manifest to opt in.`);
47
+ if (path.extname(manifestPath).toLowerCase() === ".cjs") {
48
+ const mod = createRequire(import.meta.url)(manifestPath);
49
+ return mod?.default ?? mod;
50
+ }
51
+ const mod = await import(pathToFileURL(manifestPath).href);
52
+ return mod?.default ?? mod;
53
+ }
54
+
55
+ //#endregion
56
+ export { loadManifestFromFile, resolveManifestInDir };
57
+ //# sourceMappingURL=manifest-resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest-resolve.js","names":[],"sources":["../../../../src/cli/commands/plugin/manifest-resolve.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\n\nconst MANIFEST_JSON = \"manifest.json\";\nconst MANIFEST_JS = \"manifest.js\";\nconst MANIFEST_CJS = \"manifest.cjs\";\n\n/** Resolution order for manifest files in a plugin directory. */\nconst MANIFEST_CANDIDATES = [MANIFEST_JSON, MANIFEST_JS, MANIFEST_CJS] as const;\n\nexport type ManifestFileType = \"json\" | \"js\";\n\nexport interface ResolvedManifest {\n /** Absolute path to the manifest file */\n path: string;\n /** How to load it: JSON (read + parse) or JS (dynamic import / require) */\n type: ManifestFileType;\n}\n\nexport interface ManifestLoadOptions {\n /**\n * Allow loading JS manifests via import/require.\n * Disabled by default to avoid executing untrusted code.\n */\n allowJsManifest?: boolean;\n}\n\n/**\n * Resolve the manifest file in a plugin directory.\n * By default tries only manifest.json. If allowJsManifest=true, then also\n * tries manifest.js and manifest.cjs.\n *\n * @param pluginDir - Absolute path to the plugin directory\n * @returns The resolved file path and type, or null if none found\n */\nexport function resolveManifestInDir(\n pluginDir: string,\n options: ManifestLoadOptions = {},\n): ResolvedManifest | null {\n const candidates = options.allowJsManifest\n ? MANIFEST_CANDIDATES\n : [MANIFEST_JSON];\n for (const name of candidates) {\n const manifestPath = path.join(pluginDir, name);\n if (fs.existsSync(manifestPath) && fs.statSync(manifestPath).isFile()) {\n return {\n path: path.resolve(manifestPath),\n type: name === MANIFEST_JSON ? \"json\" : \"js\",\n };\n }\n }\n return null;\n}\n\n/**\n * Load a manifest from a file (JSON or JS).\n * JSON: read and parse. JS: dynamic import (ESM) or require (CJS); the module must default-export the manifest object.\n *\n * @param manifestPath - Absolute path to manifest.json or manifest.js/.cjs\n * @param type - \"json\" or \"js\"\n * @returns The parsed manifest object (caller should validate with schema)\n */\nexport async function loadManifestFromFile(\n manifestPath: string,\n type: ManifestFileType,\n options: ManifestLoadOptions = {},\n): Promise<unknown> {\n if (type === \"json\") {\n const raw = fs.readFileSync(manifestPath, \"utf-8\");\n return JSON.parse(raw) as unknown;\n }\n\n if (!options.allowJsManifest) {\n throw new Error(\n `Refusing to execute JS manifest at ${manifestPath}. Pass --allow-js-manifest to opt in.`,\n );\n }\n\n const ext = path.extname(manifestPath).toLowerCase();\n if (ext === \".cjs\") {\n const require = createRequire(import.meta.url);\n const mod = require(manifestPath);\n return mod?.default ?? mod;\n }\n\n const url = pathToFileURL(manifestPath).href;\n const mod = await import(url);\n return mod?.default ?? mod;\n}\n"],"mappings":";;;;;;AAKA,MAAM,gBAAgB;;AAKtB,MAAM,sBAAsB;CAAC;CAJT;CACC;CAGiD;;;;;;;;;AA2BtE,SAAgB,qBACd,WACA,UAA+B,EAAE,EACR;CACzB,MAAM,aAAa,QAAQ,kBACvB,sBACA,CAAC,cAAc;AACnB,MAAK,MAAM,QAAQ,YAAY;EAC7B,MAAM,eAAe,KAAK,KAAK,WAAW,KAAK;AAC/C,MAAI,GAAG,WAAW,aAAa,IAAI,GAAG,SAAS,aAAa,CAAC,QAAQ,CACnE,QAAO;GACL,MAAM,KAAK,QAAQ,aAAa;GAChC,MAAM,SAAS,gBAAgB,SAAS;GACzC;;AAGL,QAAO;;;;;;;;;;AAWT,eAAsB,qBACpB,cACA,MACA,UAA+B,EAAE,EACf;AAClB,KAAI,SAAS,QAAQ;EACnB,MAAM,MAAM,GAAG,aAAa,cAAc,QAAQ;AAClD,SAAO,KAAK,MAAM,IAAI;;AAGxB,KAAI,CAAC,QAAQ,gBACX,OAAM,IAAI,MACR,sCAAsC,aAAa,uCACpD;AAIH,KADY,KAAK,QAAQ,aAAa,CAAC,aAAa,KACxC,QAAQ;EAElB,MAAM,MADU,cAAc,OAAO,KAAK,IAAI,CAC1B,aAAa;AACjC,SAAO,KAAK,WAAW;;CAIzB,MAAM,MAAM,MAAM,OADN,cAAc,aAAa,CAAC;AAExC,QAAO,KAAK,WAAW"}