@databricks/appkit-ui 0.61.0 → 0.62.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 (117) hide show
  1. package/CLAUDE.md +1 -0
  2. package/NOTICE.md +1 -0
  3. package/dist/cli/commands/codemod/index.js.map +1 -1
  4. package/dist/cli/commands/codemod/on-plugins-ready.js.map +1 -1
  5. package/dist/cli/commands/docs.js.map +1 -1
  6. package/dist/cli/commands/doctor/bundle.js.map +1 -1
  7. package/dist/cli/commands/doctor/index.js.map +1 -1
  8. package/dist/cli/commands/doctor/report.js.map +1 -1
  9. package/dist/cli/commands/doctor/resolve-targets.js +4 -6
  10. package/dist/cli/commands/doctor/resolve-targets.js.map +1 -1
  11. package/dist/cli/commands/doctor/run.js.map +1 -1
  12. package/dist/cli/commands/generate-types.js.map +1 -1
  13. package/dist/cli/commands/lint.js.map +1 -1
  14. package/dist/cli/commands/plugin/add-resource/add-resource.js.map +1 -1
  15. package/dist/cli/commands/plugin/create/create.js.map +1 -1
  16. package/dist/cli/commands/plugin/create/prompt-resource.js.map +1 -1
  17. package/dist/cli/commands/plugin/create/scaffold.js.map +1 -1
  18. package/dist/cli/commands/plugin/index.js.map +1 -1
  19. package/dist/cli/commands/plugin/list/list.js.map +1 -1
  20. package/dist/cli/commands/plugin/promote/promote.js.map +1 -1
  21. package/dist/cli/commands/plugin/sync/sync.js.map +1 -1
  22. package/dist/cli/commands/plugin/validate/validate-manifest.js.map +1 -1
  23. package/dist/cli/commands/plugin/validate/validate.js.map +1 -1
  24. package/dist/cli/commands/registry/add.js.map +1 -1
  25. package/dist/cli/commands/registry/client.js.map +1 -1
  26. package/dist/cli/commands/registry/config-writer.js.map +1 -1
  27. package/dist/cli/commands/registry/env-reconcile.js.map +1 -1
  28. package/dist/cli/commands/registry/env-writer.js.map +1 -1
  29. package/dist/cli/commands/registry/index.js.map +1 -1
  30. package/dist/cli/commands/registry/info.js.map +1 -1
  31. package/dist/cli/commands/registry/list.js.map +1 -1
  32. package/dist/cli/commands/registry/requirements.js.map +1 -1
  33. package/dist/cli/commands/registry/server-register.js.map +1 -1
  34. package/dist/cli/commands/registry/workspace-picker.js.map +1 -1
  35. package/dist/cli/commands/setup.js.map +1 -1
  36. package/dist/cli/index.js.map +1 -1
  37. package/dist/js/arrow/arrow-client.d.ts.map +1 -1
  38. package/dist/js/arrow/arrow-client.js.map +1 -1
  39. package/dist/react/charts/area/index.d.ts.map +1 -1
  40. package/dist/react/charts/area/index.js.map +1 -1
  41. package/dist/react/charts/bar/index.d.ts.map +1 -1
  42. package/dist/react/charts/bar/index.js.map +1 -1
  43. package/dist/react/charts/base.d.ts.map +1 -1
  44. package/dist/react/charts/base.js +1 -1
  45. package/dist/react/charts/base.js.map +1 -1
  46. package/dist/react/charts/heatmap/index.d.ts.map +1 -1
  47. package/dist/react/charts/heatmap/index.js.map +1 -1
  48. package/dist/react/charts/line/index.d.ts.map +1 -1
  49. package/dist/react/charts/line/index.js.map +1 -1
  50. package/dist/react/charts/normalize.d.ts.map +1 -1
  51. package/dist/react/charts/normalize.js.map +1 -1
  52. package/dist/react/charts/pie/index.d.ts.map +1 -1
  53. package/dist/react/charts/pie/index.js.map +1 -1
  54. package/dist/react/charts/radar/index.d.ts.map +1 -1
  55. package/dist/react/charts/radar/index.js.map +1 -1
  56. package/dist/react/charts/scatter/index.d.ts.map +1 -1
  57. package/dist/react/charts/scatter/index.js.map +1 -1
  58. package/dist/react/charts/theme.d.ts.map +1 -1
  59. package/dist/react/charts/theme.js.map +1 -1
  60. package/dist/react/charts/wrapper.d.ts.map +1 -1
  61. package/dist/react/charts/wrapper.js.map +1 -1
  62. package/dist/react/file-browser/directory-list.d.ts.map +1 -1
  63. package/dist/react/file-browser/directory-list.js.map +1 -1
  64. package/dist/react/file-browser/file-breadcrumb.d.ts.map +1 -1
  65. package/dist/react/file-browser/file-breadcrumb.js.map +1 -1
  66. package/dist/react/file-browser/file-entry.d.ts.map +1 -1
  67. package/dist/react/file-browser/file-entry.js.map +1 -1
  68. package/dist/react/file-browser/file-preview-panel.d.ts.map +1 -1
  69. package/dist/react/file-browser/file-preview-panel.js.map +1 -1
  70. package/dist/react/file-browser/new-folder-input.d.ts.map +1 -1
  71. package/dist/react/file-browser/new-folder-input.js.map +1 -1
  72. package/dist/react/genie/genie-chat-input.d.ts.map +1 -1
  73. package/dist/react/genie/genie-chat-input.js.map +1 -1
  74. package/dist/react/genie/genie-chat-message-list.d.ts.map +1 -1
  75. package/dist/react/genie/genie-chat-message-list.js.map +1 -1
  76. package/dist/react/genie/genie-chat-message.d.ts.map +1 -1
  77. package/dist/react/genie/genie-chat-message.js.map +1 -1
  78. package/dist/react/genie/genie-query-visualization.d.ts.map +1 -1
  79. package/dist/react/genie/genie-query-visualization.js.map +1 -1
  80. package/dist/react/genie/use-genie-chat.d.ts.map +1 -1
  81. package/dist/react/genie/use-genie-chat.js.map +1 -1
  82. package/dist/react/hooks/types.d.ts.map +1 -1
  83. package/dist/react/hooks/use-agent-chat.d.ts.map +1 -1
  84. package/dist/react/hooks/use-agent-chat.js.map +1 -1
  85. package/dist/react/hooks/use-ai-search-query.d.ts.map +1 -1
  86. package/dist/react/hooks/use-ai-search-query.js.map +1 -1
  87. package/dist/react/hooks/use-analytics-query.d.ts.map +1 -1
  88. package/dist/react/hooks/use-analytics-query.js.map +1 -1
  89. package/dist/react/hooks/use-analytics-warehouse-status.js.map +1 -1
  90. package/dist/react/hooks/use-chart-data.d.ts.map +1 -1
  91. package/dist/react/hooks/use-chart-data.js.map +1 -1
  92. package/dist/react/hooks/use-metric-view.d.ts.map +1 -1
  93. package/dist/react/hooks/use-metric-view.js.map +1 -1
  94. package/dist/react/hooks/use-plugin-config.d.ts.map +1 -1
  95. package/dist/react/hooks/use-plugin-config.js.map +1 -1
  96. package/dist/react/hooks/use-serving-invoke.d.ts.map +1 -1
  97. package/dist/react/hooks/use-serving-invoke.js.map +1 -1
  98. package/dist/react/hooks/use-serving-stream.d.ts.map +1 -1
  99. package/dist/react/hooks/use-serving-stream.js.map +1 -1
  100. package/dist/react/resource-status-indicator.d.ts.map +1 -1
  101. package/dist/react/resource-status-indicator.js.map +1 -1
  102. package/dist/react/table/data-table.d.ts.map +1 -1
  103. package/dist/react/table/data-table.js.map +1 -1
  104. package/dist/react/table/table-wrapper.js.map +1 -1
  105. package/dist/react/ui-variants/variants.d.ts.map +1 -1
  106. package/dist/react/ui-variants/variants.js.map +1 -1
  107. package/dist/schemas/manifest.d.ts.map +1 -1
  108. package/dist/schemas/manifest.js.map +1 -1
  109. package/dist/schemas/metric-fqn.js.map +1 -1
  110. package/dist/shared/src/plugin.d.ts.map +1 -1
  111. package/dist/styles.css +0 -3
  112. package/dist/workspace-client/legacy.js.map +1 -1
  113. package/dist/workspace-client/types.d.ts.map +1 -1
  114. package/docs/plugins/testing.md +245 -0
  115. package/llms.txt +1 -0
  116. package/package.json +1 -1
  117. package/sbom.cdx.json +1 -1
@@ -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 {\n DEFAULT_PERMISSION_BY_TYPE,\n getDefaultFieldsForType,\n getValidResourceTypes,\n humanizeResourceType,\n resourceKeyFromType,\n} from \"../create/resource-defaults\";\nimport { resolveManifestInDir } from \"../manifest-resolve\";\nimport type { PluginManifest, ResourceRequirement } 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\ninterface AddResourceOptions {\n path?: string;\n type?: string;\n required?: boolean;\n resourceKey?: string;\n description?: string;\n permission?: string;\n fieldsJson?: string;\n dryRun?: boolean;\n}\n\nfunction loadManifest(\n pluginDir: string,\n): { manifest: ManifestWithExtras; manifestPath: string } | null {\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 console.error(\n \" appkit plugin add-resource --path <dir-with-manifest.json>\",\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 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 return { manifest: parsed as ManifestWithExtras, manifestPath };\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\nfunction buildEntry(\n type: string,\n opts: AddResourceOptions,\n): { entry: ResourceRequirement; isRequired: boolean } {\n const alias = humanizeResourceType(type);\n const isRequired = opts.required !== false;\n\n let fields = getDefaultFieldsForType(type);\n if (opts.fieldsJson) {\n try {\n const parsed = JSON.parse(opts.fieldsJson) as Record<\n string,\n { env: string; description?: string }\n >;\n fields = { ...fields, ...parsed };\n } catch {\n console.error(\"Error: --fields-json must be valid JSON.\");\n console.error(\n ' Example: --fields-json \\'{\"id\":{\"env\":\"MY_WAREHOUSE_ID\"}}\\'',\n );\n process.exit(1);\n }\n }\n\n // Constructed dynamically from a string `type` and string `permission`;\n // the per-type permission tightness from the discriminated union is\n // recovered at runtime by `validateManifest` before the entry is written.\n const entry = {\n type,\n alias,\n resourceKey: opts.resourceKey ?? resourceKeyFromType(type),\n description:\n opts.description ||\n `${isRequired ? \"Required\" : \"Optional\"} for ${alias} functionality.`,\n permission:\n opts.permission ?? DEFAULT_PERMISSION_BY_TYPE[type] ?? \"CAN_VIEW\",\n fields,\n } as ResourceRequirement;\n\n return { entry, isRequired };\n}\n\nfunction runNonInteractive(opts: AddResourceOptions): void {\n const cwd = process.cwd();\n const pluginDir = path.resolve(cwd, opts.path ?? \".\");\n const loaded = loadManifest(pluginDir);\n if (!loaded) return;\n const { manifest, manifestPath } = loaded;\n\n const type = opts.type as string;\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 const { entry, isRequired } = buildEntry(type, opts);\n\n if (isRequired) {\n manifest.resources.required.push(entry);\n } else {\n manifest.resources.optional.push(entry);\n }\n\n if (opts.dryRun) {\n console.log(JSON.stringify(manifest, null, 2));\n return;\n }\n\n fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\\n`);\n console.log(\n `Added ${entry.alias} as ${isRequired ? \"required\" : \"optional\"} to ${path.relative(cwd, manifestPath)}`,\n );\n}\n\nasync function runInteractive(opts: AddResourceOptions): Promise<void> {\n intro(\"Add resource to plugin manifest\");\n\n const cwd = process.cwd();\n const pluginDir = path.resolve(cwd, opts.path ?? \".\");\n const loaded = loadManifest(pluginDir);\n if (!loaded) return;\n const { manifest, manifestPath } = loaded;\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 // See note on the non-interactive variant in `buildEntry` — the entry is\n // assembled from prompt strings and validated by Zod at write time.\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 } as ResourceRequirement;\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\nasync function runPluginAddResource(opts: AddResourceOptions): Promise<void> {\n if (opts.type) {\n runNonInteractive(opts);\n } else {\n await runInteractive(opts);\n }\n}\n\nexport const pluginAddResourceCommand = new Command(\"add-resource\")\n .description(\n \"Add a resource requirement to an existing plugin manifest. Overwrites manifest.json in place.\",\n )\n .option(\n \"-p, --path <dir>\",\n \"Plugin directory containing manifest.json (default: .)\",\n )\n .option(\n \"-t, --type <resource_type>\",\n \"Resource type (e.g. sql_warehouse, volume). Enables non-interactive mode.\",\n )\n .option(\"--required\", \"Mark resource as required (default: true)\", true)\n .option(\"--no-required\", \"Mark resource as optional\")\n .option(\"--resource-key <key>\", \"Resource key (default: derived from type)\")\n .option(\"--description <text>\", \"Description of the resource requirement\")\n .option(\"--permission <perm>\", \"Permission level (default: from schema)\")\n .option(\n \"--fields-json <json>\",\n 'JSON object overriding field env vars (e.g. \\'{\"id\":{\"env\":\"MY_WAREHOUSE_ID\"}}\\')',\n )\n .option(\"--dry-run\", \"Preview the updated manifest without writing\")\n .addHelpText(\n \"after\",\n `\nExamples:\n $ appkit plugin add-resource\n $ appkit plugin add-resource --path plugins/my-plugin --type sql_warehouse\n $ appkit plugin add-resource --path plugins/my-plugin --type volume --no-required --dry-run\n $ appkit plugin add-resource --type sql_warehouse --fields-json '{\"id\":{\"env\":\"MY_WAREHOUSE_ID\"}}'`,\n )\n .action((opts) =>\n runPluginAddResource(opts).catch((err) => {\n console.error(err);\n process.exit(1);\n }),\n );\n"],"mappings":"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"}
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\";\n\nimport { cancel, intro, outro } from \"@clack/prompts\";\nimport { Command } from \"commander\";\n\nimport { promptOneResource } from \"../create/prompt-resource\";\nimport {\n DEFAULT_PERMISSION_BY_TYPE,\n getDefaultFieldsForType,\n getValidResourceTypes,\n humanizeResourceType,\n resourceKeyFromType,\n} from \"../create/resource-defaults\";\nimport { resolveManifestInDir } from \"../manifest-resolve\";\nimport type { PluginManifest, ResourceRequirement } 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\ninterface AddResourceOptions {\n path?: string;\n type?: string;\n required?: boolean;\n resourceKey?: string;\n description?: string;\n permission?: string;\n fieldsJson?: string;\n dryRun?: boolean;\n}\n\nfunction loadManifest(\n pluginDir: string,\n): { manifest: ManifestWithExtras; manifestPath: string } | null {\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 console.error(\n \" appkit plugin add-resource --path <dir-with-manifest.json>\",\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 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 return { manifest: parsed as ManifestWithExtras, manifestPath };\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\nfunction buildEntry(\n type: string,\n opts: AddResourceOptions,\n): { entry: ResourceRequirement; isRequired: boolean } {\n const alias = humanizeResourceType(type);\n const isRequired = opts.required !== false;\n\n let fields = getDefaultFieldsForType(type);\n if (opts.fieldsJson) {\n try {\n const parsed = JSON.parse(opts.fieldsJson) as Record<\n string,\n { env: string; description?: string }\n >;\n fields = { ...fields, ...parsed };\n } catch {\n console.error(\"Error: --fields-json must be valid JSON.\");\n console.error(\n ' Example: --fields-json \\'{\"id\":{\"env\":\"MY_WAREHOUSE_ID\"}}\\'',\n );\n process.exit(1);\n }\n }\n\n // Constructed dynamically from a string `type` and string `permission`;\n // the per-type permission tightness from the discriminated union is\n // recovered at runtime by `validateManifest` before the entry is written.\n const entry = {\n type,\n alias,\n resourceKey: opts.resourceKey ?? resourceKeyFromType(type),\n description:\n opts.description ||\n `${isRequired ? \"Required\" : \"Optional\"} for ${alias} functionality.`,\n permission:\n opts.permission ?? DEFAULT_PERMISSION_BY_TYPE[type] ?? \"CAN_VIEW\",\n fields,\n } as ResourceRequirement;\n\n return { entry, isRequired };\n}\n\nfunction runNonInteractive(opts: AddResourceOptions): void {\n const cwd = process.cwd();\n const pluginDir = path.resolve(cwd, opts.path ?? \".\");\n const loaded = loadManifest(pluginDir);\n if (!loaded) return;\n const { manifest, manifestPath } = loaded;\n\n const type = opts.type as string;\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 const { entry, isRequired } = buildEntry(type, opts);\n\n if (isRequired) {\n manifest.resources.required.push(entry);\n } else {\n manifest.resources.optional.push(entry);\n }\n\n if (opts.dryRun) {\n console.log(JSON.stringify(manifest, null, 2));\n return;\n }\n\n fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\\n`);\n console.log(\n `Added ${entry.alias} as ${isRequired ? \"required\" : \"optional\"} to ${path.relative(cwd, manifestPath)}`,\n );\n}\n\nasync function runInteractive(opts: AddResourceOptions): Promise<void> {\n intro(\"Add resource to plugin manifest\");\n\n const cwd = process.cwd();\n const pluginDir = path.resolve(cwd, opts.path ?? \".\");\n const loaded = loadManifest(pluginDir);\n if (!loaded) return;\n const { manifest, manifestPath } = loaded;\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 // See note on the non-interactive variant in `buildEntry` — the entry is\n // assembled from prompt strings and validated by Zod at write time.\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 } as ResourceRequirement;\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\nasync function runPluginAddResource(opts: AddResourceOptions): Promise<void> {\n if (opts.type) {\n runNonInteractive(opts);\n } else {\n await runInteractive(opts);\n }\n}\n\nexport const pluginAddResourceCommand = new Command(\"add-resource\")\n .description(\n \"Add a resource requirement to an existing plugin manifest. Overwrites manifest.json in place.\",\n )\n .option(\n \"-p, --path <dir>\",\n \"Plugin directory containing manifest.json (default: .)\",\n )\n .option(\n \"-t, --type <resource_type>\",\n \"Resource type (e.g. sql_warehouse, volume). Enables non-interactive mode.\",\n )\n .option(\"--required\", \"Mark resource as required (default: true)\", true)\n .option(\"--no-required\", \"Mark resource as optional\")\n .option(\"--resource-key <key>\", \"Resource key (default: derived from type)\")\n .option(\"--description <text>\", \"Description of the resource requirement\")\n .option(\"--permission <perm>\", \"Permission level (default: from schema)\")\n .option(\n \"--fields-json <json>\",\n 'JSON object overriding field env vars (e.g. \\'{\"id\":{\"env\":\"MY_WAREHOUSE_ID\"}}\\')',\n )\n .option(\"--dry-run\", \"Preview the updated manifest without writing\")\n .addHelpText(\n \"after\",\n `\nExamples:\n $ appkit plugin add-resource\n $ appkit plugin add-resource --path plugins/my-plugin --type sql_warehouse\n $ appkit plugin add-resource --path plugins/my-plugin --type volume --no-required --dry-run\n $ appkit plugin add-resource --type sql_warehouse --fields-json '{\"id\":{\"env\":\"MY_WAREHOUSE_ID\"}}'`,\n )\n .action((opts) =>\n runPluginAddResource(opts).catch((err) => {\n console.error(err);\n process.exit(1);\n }),\n );\n"],"mappings":"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"}
@@ -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, Option } from \"commander\";\nimport { PLUGIN_NAME_PATTERN } from \"../../../../naming\";\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 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 // camelCase -> Title Case words (e.g. \"myPlugin\" -> \"My Plugin\").\n const spaced = name.replace(/([a-z0-9])([A-Z])/g, \"$1 $2\");\n return spaced.charAt(0).toUpperCase() + spaced.slice(1);\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 // The camelCase plugin name is already the JS binding.\n const exportName = 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 myPlugin --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 (!PLUGIN_NAME_PATTERN.test(name)) {\n console.error(\n \"Error: --name must start with a lowercase letter and be camelCase (letters and numbers only, e.g. myPlugin).\",\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: \"myPlugin\",\n validate(value) {\n if (!value?.trim()) return \"Name is required.\";\n if (!PLUGIN_NAME_PATTERN.test(value as string)) {\n return \"Must start with a lowercase letter and be camelCase (letters and numbers only, e.g. myPlugin).\";\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 stability = await select<\"ga\" | \"beta\">({\n message: \"Plugin stability level\",\n options: [\n { value: \"ga\", label: \"GA\", hint: \"API follows semver\" },\n {\n value: \"beta\",\n label: \"Beta\",\n hint: \"Heading to GA, API may change\",\n },\n ],\n initialValue: \"ga\" as \"ga\" | \"beta\",\n });\n if (isCancel(stability)) {\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 stability: stability === \"ga\" ? undefined : stability,\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 myPlugin --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 myPlugin --description \"Does X\"\n $ appkit plugin create --placement in-repo --path plugins/my-plugin --name myPlugin --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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"}
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\";\n\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\";\n\nimport { PLUGIN_NAME_PATTERN } from \"../../../../naming\";\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 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 // camelCase -> Title Case words (e.g. \"myPlugin\" -> \"My Plugin\").\n const spaced = name.replace(/([a-z0-9])([A-Z])/g, \"$1 $2\");\n return spaced.charAt(0).toUpperCase() + spaced.slice(1);\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 // The camelCase plugin name is already the JS binding.\n const exportName = 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 myPlugin --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 (!PLUGIN_NAME_PATTERN.test(name)) {\n console.error(\n \"Error: --name must start with a lowercase letter and be camelCase (letters and numbers only, e.g. myPlugin).\",\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: \"myPlugin\",\n validate(value) {\n if (!value?.trim()) return \"Name is required.\";\n if (!PLUGIN_NAME_PATTERN.test(value as string)) {\n return \"Must start with a lowercase letter and be camelCase (letters and numbers only, e.g. myPlugin).\";\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 stability = await select<\"ga\" | \"beta\">({\n message: \"Plugin stability level\",\n options: [\n { value: \"ga\", label: \"GA\", hint: \"API follows semver\" },\n {\n value: \"beta\",\n label: \"Beta\",\n hint: \"Heading to GA, API may change\",\n },\n ],\n initialValue: \"ga\" as \"ga\" | \"beta\",\n });\n if (isCancel(stability)) {\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 stability: stability === \"ga\" ? undefined : stability,\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 myPlugin --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 myPlugin --description \"Does X\"\n $ appkit plugin create --placement in-repo --path plugins/my-plugin --name myPlugin --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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"}
@@ -1 +1 @@
1
- {"version":3,"file":"prompt-resource.js","names":[],"sources":["../../../../../src/cli/commands/plugin/create/prompt-resource.ts"],"sourcesContent":["import { isCancel, select, text } from \"@clack/prompts\";\nimport {\n getDefaultFieldsForType,\n humanizeResourceType,\n PERMISSIONS_BY_TYPE,\n RESOURCE_TYPE_OPTIONS,\n resourceKeyFromType,\n} from \"./resource-defaults\";\n\n/** Full resource spec collected from prompts (shared by create and add-resource). */\nexport interface ResourceSpec {\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\n/**\n * Prompt for a single resource: type (optional), required, description, resourceKey, permission, and field env vars.\n * When type is provided (e.g. from create's multiselect), type prompt is skipped.\n */\nexport async function promptOneResource(opts?: {\n type?: string;\n}): Promise<ResourceSpec | null> {\n let type = opts?.type;\n\n if (!type) {\n const resourceType = await select({\n message: \"Resource type\",\n options: RESOURCE_TYPE_OPTIONS.map((o) => ({\n value: o.value,\n label: o.label,\n })),\n });\n if (isCancel(resourceType)) return null;\n type = resourceType as string;\n }\n\n const required = await select<boolean>({\n message: `${humanizeResourceType(type)} – required or optional?`,\n options: [\n { value: true, label: \"Required\", hint: \"plugin needs it to function\" },\n { value: false, label: \"Optional\", hint: \"enhances functionality\" },\n ],\n });\n if (isCancel(required)) return null;\n\n const description = await text({\n message: `Short description for ${humanizeResourceType(type)}`,\n placeholder: required ? \"Required for …\" : \"Optional for …\",\n });\n if (isCancel(description)) return null;\n\n const defaultKey = resourceKeyFromType(type);\n const resourceKey = await text({\n message: \"Resource key (unique identifier within the manifest)\",\n initialValue: defaultKey,\n placeholder: defaultKey,\n validate: (val = \"\") => {\n if (!val.trim()) return \"Resource key is required\";\n if (!/^[a-z][a-z0-9-]*$/.test(val))\n return \"Must be lowercase, start with a letter, and contain only letters, numbers, and hyphens\";\n },\n });\n if (isCancel(resourceKey)) return null;\n\n const typePermissions = PERMISSIONS_BY_TYPE[type] ?? [\"CAN_VIEW\"];\n let permission: string;\n if (typePermissions.length === 1) {\n permission = typePermissions[0];\n } else {\n const selected = await select({\n message: \"Permission level\",\n options: typePermissions.map((p) => ({ value: p, label: p })),\n });\n if (isCancel(selected)) return null;\n permission = selected as string;\n }\n\n const defaultFields = getDefaultFieldsForType(type);\n const fields: Record<string, { env: string; description?: string }> = {};\n\n for (const [fieldKey, defaults] of Object.entries(defaultFields)) {\n const envName = await text({\n message: `Env var for \"${fieldKey}\"${defaults.description ? ` (${defaults.description})` : \"\"}`,\n initialValue: defaults.env,\n placeholder: defaults.env,\n validate: (val = \"\") => {\n if (!val.trim()) return \"Env var name is required\";\n if (!/^[A-Z][A-Z0-9_]*$/.test(val))\n return \"Must be uppercase, start with a letter (e.g. DATABRICKS_WAREHOUSE_ID)\";\n },\n });\n if (isCancel(envName)) return null;\n fields[fieldKey] = {\n env: (envName as string).trim(),\n ...(defaults.description ? { description: defaults.description } : {}),\n };\n }\n\n return {\n type,\n required: required as boolean,\n description: (description as string)?.trim() || \"\",\n resourceKey: (resourceKey as string).trim(),\n permission,\n fields,\n };\n}\n"],"mappings":";;;;;;;;AAuBA,eAAsB,kBAAkB,MAEP;CAC/B,IAAI,OAAO,MAAM;AAEjB,KAAI,CAAC,MAAM;EACT,MAAM,eAAe,MAAM,OAAO;GAChC,SAAS;GACT,SAAS,sBAAsB,KAAK,OAAO;IACzC,OAAO,EAAE;IACT,OAAO,EAAE;IACV,EAAE;GACJ,CAAC;AACF,MAAI,SAAS,aAAa,CAAE,QAAO;AACnC,SAAO;;CAGT,MAAM,WAAW,MAAM,OAAgB;EACrC,SAAS,GAAG,qBAAqB,KAAK,CAAC;EACvC,SAAS,CACP;GAAE,OAAO;GAAM,OAAO;GAAY,MAAM;GAA+B,EACvE;GAAE,OAAO;GAAO,OAAO;GAAY,MAAM;GAA0B,CACpE;EACF,CAAC;AACF,KAAI,SAAS,SAAS,CAAE,QAAO;CAE/B,MAAM,cAAc,MAAM,KAAK;EAC7B,SAAS,yBAAyB,qBAAqB,KAAK;EAC5D,aAAa,WAAW,mBAAmB;EAC5C,CAAC;AACF,KAAI,SAAS,YAAY,CAAE,QAAO;CAElC,MAAM,aAAa,oBAAoB,KAAK;CAC5C,MAAM,cAAc,MAAM,KAAK;EAC7B,SAAS;EACT,cAAc;EACd,aAAa;EACb,WAAW,MAAM,OAAO;AACtB,OAAI,CAAC,IAAI,MAAM,CAAE,QAAO;AACxB,OAAI,CAAC,oBAAoB,KAAK,IAAI,CAChC,QAAO;;EAEZ,CAAC;AACF,KAAI,SAAS,YAAY,CAAE,QAAO;CAElC,MAAM,kBAAkB,oBAAoB,SAAS,CAAC,WAAW;CACjE,IAAI;AACJ,KAAI,gBAAgB,WAAW,EAC7B,cAAa,gBAAgB;MACxB;EACL,MAAM,WAAW,MAAM,OAAO;GAC5B,SAAS;GACT,SAAS,gBAAgB,KAAK,OAAO;IAAE,OAAO;IAAG,OAAO;IAAG,EAAE;GAC9D,CAAC;AACF,MAAI,SAAS,SAAS,CAAE,QAAO;AAC/B,eAAa;;CAGf,MAAM,gBAAgB,wBAAwB,KAAK;CACnD,MAAM,SAAgE,EAAE;AAExE,MAAK,MAAM,CAAC,UAAU,aAAa,OAAO,QAAQ,cAAc,EAAE;EAChE,MAAM,UAAU,MAAM,KAAK;GACzB,SAAS,gBAAgB,SAAS,GAAG,SAAS,cAAc,KAAK,SAAS,YAAY,KAAK;GAC3F,cAAc,SAAS;GACvB,aAAa,SAAS;GACtB,WAAW,MAAM,OAAO;AACtB,QAAI,CAAC,IAAI,MAAM,CAAE,QAAO;AACxB,QAAI,CAAC,oBAAoB,KAAK,IAAI,CAChC,QAAO;;GAEZ,CAAC;AACF,MAAI,SAAS,QAAQ,CAAE,QAAO;AAC9B,SAAO,YAAY;GACjB,KAAM,QAAmB,MAAM;GAC/B,GAAI,SAAS,cAAc,EAAE,aAAa,SAAS,aAAa,GAAG,EAAE;GACtE;;AAGH,QAAO;EACL;EACU;EACV,aAAc,aAAwB,MAAM,IAAI;EAChD,aAAc,YAAuB,MAAM;EAC3C;EACA;EACD"}
1
+ {"version":3,"file":"prompt-resource.js","names":[],"sources":["../../../../../src/cli/commands/plugin/create/prompt-resource.ts"],"sourcesContent":["import { isCancel, select, text } from \"@clack/prompts\";\n\nimport {\n getDefaultFieldsForType,\n humanizeResourceType,\n PERMISSIONS_BY_TYPE,\n RESOURCE_TYPE_OPTIONS,\n resourceKeyFromType,\n} from \"./resource-defaults\";\n\n/** Full resource spec collected from prompts (shared by create and add-resource). */\nexport interface ResourceSpec {\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\n/**\n * Prompt for a single resource: type (optional), required, description, resourceKey, permission, and field env vars.\n * When type is provided (e.g. from create's multiselect), type prompt is skipped.\n */\nexport async function promptOneResource(opts?: {\n type?: string;\n}): Promise<ResourceSpec | null> {\n let type = opts?.type;\n\n if (!type) {\n const resourceType = await select({\n message: \"Resource type\",\n options: RESOURCE_TYPE_OPTIONS.map((o) => ({\n value: o.value,\n label: o.label,\n })),\n });\n if (isCancel(resourceType)) return null;\n type = resourceType as string;\n }\n\n const required = await select<boolean>({\n message: `${humanizeResourceType(type)} – required or optional?`,\n options: [\n { value: true, label: \"Required\", hint: \"plugin needs it to function\" },\n { value: false, label: \"Optional\", hint: \"enhances functionality\" },\n ],\n });\n if (isCancel(required)) return null;\n\n const description = await text({\n message: `Short description for ${humanizeResourceType(type)}`,\n placeholder: required ? \"Required for …\" : \"Optional for …\",\n });\n if (isCancel(description)) return null;\n\n const defaultKey = resourceKeyFromType(type);\n const resourceKey = await text({\n message: \"Resource key (unique identifier within the manifest)\",\n initialValue: defaultKey,\n placeholder: defaultKey,\n validate: (val = \"\") => {\n if (!val.trim()) return \"Resource key is required\";\n if (!/^[a-z][a-z0-9-]*$/.test(val))\n return \"Must be lowercase, start with a letter, and contain only letters, numbers, and hyphens\";\n },\n });\n if (isCancel(resourceKey)) return null;\n\n const typePermissions = PERMISSIONS_BY_TYPE[type] ?? [\"CAN_VIEW\"];\n let permission: string;\n if (typePermissions.length === 1) {\n permission = typePermissions[0];\n } else {\n const selected = await select({\n message: \"Permission level\",\n options: typePermissions.map((p) => ({ value: p, label: p })),\n });\n if (isCancel(selected)) return null;\n permission = selected as string;\n }\n\n const defaultFields = getDefaultFieldsForType(type);\n const fields: Record<string, { env: string; description?: string }> = {};\n\n for (const [fieldKey, defaults] of Object.entries(defaultFields)) {\n const envName = await text({\n message: `Env var for \"${fieldKey}\"${defaults.description ? ` (${defaults.description})` : \"\"}`,\n initialValue: defaults.env,\n placeholder: defaults.env,\n validate: (val = \"\") => {\n if (!val.trim()) return \"Env var name is required\";\n if (!/^[A-Z][A-Z0-9_]*$/.test(val))\n return \"Must be uppercase, start with a letter (e.g. DATABRICKS_WAREHOUSE_ID)\";\n },\n });\n if (isCancel(envName)) return null;\n fields[fieldKey] = {\n env: (envName as string).trim(),\n ...(defaults.description ? { description: defaults.description } : {}),\n };\n }\n\n return {\n type,\n required: required as boolean,\n description: (description as string)?.trim() || \"\",\n resourceKey: (resourceKey as string).trim(),\n permission,\n fields,\n };\n}\n"],"mappings":";;;;;;;;AAwBA,eAAsB,kBAAkB,MAEP;CAC/B,IAAI,OAAO,MAAM;AAEjB,KAAI,CAAC,MAAM;EACT,MAAM,eAAe,MAAM,OAAO;GAChC,SAAS;GACT,SAAS,sBAAsB,KAAK,OAAO;IACzC,OAAO,EAAE;IACT,OAAO,EAAE;IACV,EAAE;GACJ,CAAC;AACF,MAAI,SAAS,aAAa,CAAE,QAAO;AACnC,SAAO;;CAGT,MAAM,WAAW,MAAM,OAAgB;EACrC,SAAS,GAAG,qBAAqB,KAAK,CAAC;EACvC,SAAS,CACP;GAAE,OAAO;GAAM,OAAO;GAAY,MAAM;GAA+B,EACvE;GAAE,OAAO;GAAO,OAAO;GAAY,MAAM;GAA0B,CACpE;EACF,CAAC;AACF,KAAI,SAAS,SAAS,CAAE,QAAO;CAE/B,MAAM,cAAc,MAAM,KAAK;EAC7B,SAAS,yBAAyB,qBAAqB,KAAK;EAC5D,aAAa,WAAW,mBAAmB;EAC5C,CAAC;AACF,KAAI,SAAS,YAAY,CAAE,QAAO;CAElC,MAAM,aAAa,oBAAoB,KAAK;CAC5C,MAAM,cAAc,MAAM,KAAK;EAC7B,SAAS;EACT,cAAc;EACd,aAAa;EACb,WAAW,MAAM,OAAO;AACtB,OAAI,CAAC,IAAI,MAAM,CAAE,QAAO;AACxB,OAAI,CAAC,oBAAoB,KAAK,IAAI,CAChC,QAAO;;EAEZ,CAAC;AACF,KAAI,SAAS,YAAY,CAAE,QAAO;CAElC,MAAM,kBAAkB,oBAAoB,SAAS,CAAC,WAAW;CACjE,IAAI;AACJ,KAAI,gBAAgB,WAAW,EAC7B,cAAa,gBAAgB;MACxB;EACL,MAAM,WAAW,MAAM,OAAO;GAC5B,SAAS;GACT,SAAS,gBAAgB,KAAK,OAAO;IAAE,OAAO;IAAG,OAAO;IAAG,EAAE;GAC9D,CAAC;AACF,MAAI,SAAS,SAAS,CAAE,QAAO;AAC/B,eAAa;;CAGf,MAAM,gBAAgB,wBAAwB,KAAK;CACnD,MAAM,SAAgE,EAAE;AAExE,MAAK,MAAM,CAAC,UAAU,aAAa,OAAO,QAAQ,cAAc,EAAE;EAChE,MAAM,UAAU,MAAM,KAAK;GACzB,SAAS,gBAAgB,SAAS,GAAG,SAAS,cAAc,KAAK,SAAS,YAAY,KAAK;GAC3F,cAAc,SAAS;GACvB,aAAa,SAAS;GACtB,WAAW,MAAM,OAAO;AACtB,QAAI,CAAC,IAAI,MAAM,CAAE,QAAO;AACxB,QAAI,CAAC,oBAAoB,KAAK,IAAI,CAChC,QAAO;;GAEZ,CAAC;AACF,MAAI,SAAS,QAAQ,CAAE,QAAO;AAC9B,SAAO,YAAY;GACjB,KAAM,QAAmB,MAAM;GAC/B,GAAI,SAAS,cAAc,EAAE,aAAa,SAAS,aAAa,GAAG,EAAE;GACtE;;AAGH,QAAO;EACL;EACU;EACV,aAAc,aAAwB,MAAM,IAAI;EAChD,aAAc,YAAuB,MAAM;EAC3C;EACA;EACD"}
@@ -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.stability) manifest.stability = answers.stability;\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 static manifest = manifest as PluginManifest<\"${answers.name}\">;\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(${className});\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
+ {"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\";\n\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.stability) manifest.stability = answers.stability;\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 static manifest = manifest as PluginManifest<\"${answers.name}\">;\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(${className});\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 +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 { pluginPromoteCommand } from \"./promote/promote\";\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 * - promote: Promote a plugin to a higher stability tier\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 .addCommand(pluginPromoteCommand)\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 $ appkit plugin promote my-plugin --to ga`,\n );\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAkBA,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,WAAW,qBAAqB,CAChC,YACC,SACA;;;;;;;6CAQD"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../../src/cli/commands/plugin/index.ts"],"sourcesContent":["import { Command } from \"commander\";\n\nimport { pluginAddResourceCommand } from \"./add-resource/add-resource\";\nimport { pluginCreateCommand } from \"./create/create\";\nimport { pluginListCommand } from \"./list/list\";\nimport { pluginPromoteCommand } from \"./promote/promote\";\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 * - promote: Promote a plugin to a higher stability tier\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 .addCommand(pluginPromoteCommand)\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 $ appkit plugin promote my-plugin --to ga`,\n );\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAmBA,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,WAAW,qBAAqB,CAChC,YACC,SACA;;;;;;;6CAQD"}
@@ -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 stability: \"beta\" | \"ga\";\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 stability?: \"beta\" | \"ga\";\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 stability: p.stability ?? \"ga\",\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 const rawManifest = manifest as typeof manifest & {\n stability?: \"beta\" | \"ga\";\n };\n rows.push({\n name: manifest.name,\n displayName: manifest.displayName ?? manifest.name,\n package: packagePath,\n stability: rawManifest.stability ?? \"ga\",\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 maxStab = Math.max(9, ...rows.map((r) => r.stability.length));\n const header = [\n \"NAME\".padEnd(maxName),\n \"DISPLAY NAME\".padEnd(maxDisplay),\n \"STABILITY\".padEnd(maxStab),\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.stability.padEnd(maxStab),\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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"}
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\";\n\nimport { Command } from \"commander\";\n\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 stability: \"beta\" | \"ga\";\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 stability?: \"beta\" | \"ga\";\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 stability: p.stability ?? \"ga\",\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 const rawManifest = manifest as typeof manifest & {\n stability?: \"beta\" | \"ga\";\n };\n rows.push({\n name: manifest.name,\n displayName: manifest.displayName ?? manifest.name,\n package: packagePath,\n stability: rawManifest.stability ?? \"ga\",\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 maxStab = Math.max(9, ...rows.map((r) => r.stability.length));\n const header = [\n \"NAME\".padEnd(maxName),\n \"DISPLAY NAME\".padEnd(maxDisplay),\n \"STABILITY\".padEnd(maxStab),\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.stability.padEnd(maxStab),\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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"}
@@ -1 +1 @@
1
- {"version":3,"file":"promote.js","names":[],"sources":["../../../../../src/cli/commands/plugin/promote/promote.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport process from \"node:process\";\nimport { Command } from \"commander\";\nimport { kebabToCamel } from \"../../../../naming\";\nimport { resolveManifestInDir } from \"../manifest-resolve\";\nimport { isWithinDirectory } from \"../sync/sync\";\nimport { shouldAllowJsManifestForDir } from \"../trusted-js-manifest\";\n\ntype Stability = \"beta\" | \"ga\";\n\nconst TIER_ORDER: Record<Stability, number> = {\n beta: 0,\n ga: 1,\n};\n\nconst IMPORT_PATH_MAP: Record<Stability, string> = {\n beta: \"/beta\",\n ga: \"\",\n};\n\n/** Aligned with sync.ts and list.ts; keep all plugin-tree walks at the same cap. */\nconst MAX_SCAN_DEPTH = 5;\n\n/**\n * Directories that should never be walked when discovering plugin manifests\n * or rewriting imports. Mirrors common build/output trees.\n */\nconst SKIP_DIRECTORIES = new Set([\n \"node_modules\",\n \"dist\",\n \"build\",\n \"out\",\n \".git\",\n \".turbo\",\n \".next\",\n \".nuxt\",\n \".cache\",\n \".svelte-kit\",\n \".vite\",\n \".parcel-cache\",\n \"coverage\",\n]);\n\n/**\n * Plugin name charset accepted by the promote command. Mirrors npm package\n * naming (lowercase, dashes, underscores, dots, optional @scope/) and explicitly\n * forbids path separators, traversal, and NUL — so the name cannot escape its\n * intended directory when used in path.join().\n */\nconst PLUGIN_NAME_PATTERN =\n /^(?:@[a-z0-9][a-z0-9._-]*\\/)?[a-z0-9][a-z0-9._-]*$/i;\n\nfunction validatePluginName(pluginName: string): void {\n if (!pluginName || pluginName.includes(\"\\0\") || pluginName.includes(\"..\")) {\n throw new Error(\n `Invalid plugin name \"${pluginName}\". Plugin names must not contain \"..\", or null bytes.`,\n );\n }\n // Backslash is never allowed (treated as a path separator on Windows).\n if (pluginName.includes(\"\\\\\")) {\n throw new Error(\n `Invalid plugin name \"${pluginName}\". Plugin names must not contain backslashes.`,\n );\n }\n // Forward slash is only allowed inside the @scope/name form.\n if (pluginName.includes(\"/\") && !pluginName.startsWith(\"@\")) {\n throw new Error(\n `Invalid plugin name \"${pluginName}\". Plugin names must not contain \"/\" unless they are a scoped package (e.g. @scope/name).`,\n );\n }\n if (!PLUGIN_NAME_PATTERN.test(pluginName)) {\n throw new Error(\n `Invalid plugin name \"${pluginName}\". Expected lowercase alphanumeric with optional dashes, underscores, dots, or @scope/ prefix.`,\n );\n }\n}\n\nfunction isStability(value: unknown): value is Stability {\n return value === \"beta\" || value === \"ga\";\n}\n\nfunction findPluginManifest(\n pluginName: string,\n cwd: string,\n): { manifestPath: string; isLocal: boolean } | null {\n const dirsToScan = [\"plugins\", \"server\", \".\"];\n\n for (const dir of dirsToScan) {\n const absDir = path.resolve(cwd, dir);\n const result = scanDirForPlugin(absDir, pluginName, cwd, 0);\n if (result) return { manifestPath: result, isLocal: true };\n }\n\n // node_modules fallback. Validate and bound to the dist/plugins subtree to\n // make sure a malicious or typo'd name cannot escape via path.join.\n const nodeModulesDir = path.join(cwd, \"node_modules\", \"@databricks/appkit\");\n if (fs.existsSync(nodeModulesDir)) {\n const pluginsDir = path.join(nodeModulesDir, \"dist\", \"plugins\");\n if (fs.existsSync(pluginsDir)) {\n const manifestPath = path.resolve(\n pluginsDir,\n pluginName,\n \"manifest.json\",\n );\n if (\n isWithinDirectory(manifestPath, pluginsDir) &&\n fs.existsSync(manifestPath)\n ) {\n return { manifestPath, isLocal: false };\n }\n }\n }\n\n return null;\n}\n\nfunction scanDirForPlugin(\n dir: string,\n pluginName: string,\n cwd: string,\n depth: number,\n): string | null {\n if (depth >= MAX_SCAN_DEPTH) return null;\n let stat: fs.Stats;\n try {\n stat = fs.statSync(dir);\n } catch {\n return null;\n }\n if (!stat.isDirectory()) return null;\n\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n if (SKIP_DIRECTORIES.has(entry.name)) continue;\n if (entry.isSymbolicLink()) continue;\n const childPath = path.join(dir, entry.name);\n const allowJs = shouldAllowJsManifestForDir(childPath);\n const resolved = resolveManifestInDir(childPath, {\n allowJsManifest: allowJs,\n });\n\n if (resolved) {\n try {\n const obj = loadManifestFromFileSync(resolved.path);\n if (\n obj &&\n typeof obj === \"object\" &&\n \"name\" in obj &&\n (obj as { name: string }).name === pluginName\n ) {\n return resolved.path;\n }\n } catch {\n // skip unreadable / invalid manifest\n }\n continue;\n }\n\n const deeper = scanDirForPlugin(childPath, pluginName, cwd, depth + 1);\n if (deeper) return deeper;\n }\n return null;\n}\n\nfunction loadManifestFromFileSync(filePath: string): unknown {\n const raw = fs.readFileSync(filePath, \"utf-8\");\n return JSON.parse(raw);\n}\n\nfunction escapeRegex(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n/**\n * Returns true when the named import specifier `spec` resolves to either\n * `pluginName` itself or its kebab-to-camelCase JS-identifier form.\n * Handles the `name`, `name as alias`, and inline-type (`type name`,\n * `type name as alias`) forms. Matches on the imported binding before any\n * `as` rename so a promotion finds the right specifier regardless of how\n * the user aliased it.\n */\nfunction specifierMatchesPlugin(spec: string, pluginName: string): boolean {\n const stripped = spec.replace(/^type\\s+/, \"\").trim();\n const head = stripped.split(/\\s+as\\s+/)[0]?.trim();\n if (!head) return false;\n return head === pluginName || head === kebabToCamel(pluginName);\n}\n\n/**\n * Rewrite imports of `pluginName` from `<pkg><oldSuffix>` to `<pkg><newSuffix>`\n * across one file, leaving every OTHER specifier on the same import line at\n * its original source. The naïve `split/join` approach this replaced was\n * promoting *every* beta specifier in the file along with the targeted\n * plugin — a bug because beta specifiers don't exist at the GA subpath.\n *\n * Behaviour:\n * - If the targeted plugin is the only specifier on an import line, the\n * line's source is rewritten to the new path.\n * - If the import has multiple specifiers, the targeted one is moved to a\n * newly-emitted import line at the new source, and the original import\n * keeps the remaining specifiers at the old source.\n * - Imports that don't reference `pluginName` are left untouched.\n *\n * Multi-line specifier lists, type-only imports (`import type { ... }`),\n * inline-type specifiers (`import { type Foo }`), and `as`-aliased\n * specifiers are all preserved through the rewrite.\n */\nfunction rewriteImportsInFile(\n filePath: string,\n pluginName: string,\n oldSuffix: string,\n newSuffix: string,\n dryRun: boolean,\n): { file: string; from: string; to: string } | null {\n const content = fs.readFileSync(filePath, \"utf-8\");\n\n const packages = [\n \"@databricks/appkit\",\n \"@databricks/appkit-ui/js\",\n \"@databricks/appkit-ui/react\",\n ];\n let updated = content;\n let changed = false;\n\n for (const pkg of packages) {\n const oldPath = `${pkg}${oldSuffix}`;\n const newPath = `${pkg}${newSuffix}`;\n\n // Match: `import [type] { ...specifiers... } from \"<oldPath>\";`\n // - `[^}]*` lets the specifier list span newlines (safe — TS imports\n // don't have nested braces inside the specifier list).\n // - Captures the `type ` keyword (if any), the specifier body, and\n // the surrounding quote style so we can preserve them on output.\n const importRe = new RegExp(\n `import\\\\s+(type\\\\s+)?\\\\{([^}]*)\\\\}\\\\s*from\\\\s*([\"'])${escapeRegex(oldPath)}\\\\3\\\\s*;?`,\n \"g\",\n );\n\n updated = updated.replace(\n importRe,\n (full, typeKeyword, specifiers, quote) => {\n const specList: string[] = specifiers\n .split(\",\")\n .map((s: string) => s.trim())\n .filter((s: string) => s.length > 0);\n\n const promotedSpec = specList.find((s) =>\n specifierMatchesPlugin(s, pluginName),\n );\n if (!promotedSpec) {\n // Plugin not in this import — leave it alone.\n return full;\n }\n\n const remaining = specList.filter(\n (s) => !specifierMatchesPlugin(s, pluginName),\n );\n changed = true;\n\n const tk = typeKeyword ?? \"\";\n const promotedImport = `import ${tk}{ ${promotedSpec} } from ${quote}${newPath}${quote};`;\n\n if (remaining.length === 0) {\n // Only specifier — just rewrite the source.\n return promotedImport;\n }\n\n const remainingImport = `import ${tk}{ ${remaining.join(\", \")} } from ${quote}${oldPath}${quote};`;\n return `${remainingImport}\\n${promotedImport}`;\n },\n );\n }\n\n if (!changed) return null;\n\n if (!dryRun) {\n fs.writeFileSync(filePath, updated);\n }\n\n return {\n file: filePath,\n from: oldSuffix || \"(root)\",\n to: newSuffix || \"(root)\",\n };\n}\n\nfunction findTsFiles(dir: string, projectRoot: string, depth = 0): string[] {\n if (depth >= MAX_SCAN_DEPTH) return [];\n\n const results: string[] = [];\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true });\n } catch {\n return results;\n }\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isSymbolicLink()) continue;\n if (entry.isDirectory()) {\n if (SKIP_DIRECTORIES.has(entry.name)) continue;\n // Boundary check to ensure recursion stays inside the project root,\n // even if a future change introduces a symlink-following path.\n if (!isWithinDirectory(fullPath, projectRoot)) continue;\n results.push(...findTsFiles(fullPath, projectRoot, depth + 1));\n } else if (entry.name.endsWith(\".ts\") || entry.name.endsWith(\".tsx\")) {\n results.push(fullPath);\n }\n }\n\n return results;\n}\n\nasync function runPromote(\n pluginName: string,\n options: {\n to: string;\n dryRun?: boolean;\n skipImports?: boolean;\n skipSync?: boolean;\n allowInstalled?: boolean;\n },\n): Promise<void> {\n validatePluginName(pluginName);\n\n const cwd = process.cwd();\n\n if (!isStability(options.to)) {\n console.error(\n `Invalid target tier \"${options.to}\". Must be one of: beta, ga.`,\n );\n process.exit(1);\n }\n const target: Stability = options.to;\n\n const found = findPluginManifest(pluginName, cwd);\n if (!found) {\n console.error(\n `Plugin \"${pluginName}\" not found. Searched local dirs (plugins, server, .) and node_modules.`,\n );\n process.exit(1);\n }\n\n const { manifestPath, isLocal } = found;\n\n if (!isLocal && !options.allowInstalled) {\n console.error(\n `Plugin \"${pluginName}\" was only found under node_modules (${path.relative(cwd, manifestPath)}).\\n` +\n `Refusing to mutate an installed package — re-install would overwrite the change.\\n` +\n `Pass --allow-installed to override (advanced; not recommended).`,\n );\n process.exit(1);\n }\n\n let raw: Record<string, unknown>;\n try {\n const parsed = loadManifestFromFileSync(manifestPath);\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n console.error(\n `Manifest at ${path.relative(cwd, manifestPath)} is not a JSON object.`,\n );\n process.exit(1);\n }\n raw = parsed as Record<string, unknown>;\n } catch (err) {\n console.error(\n `Failed to read manifest at ${path.relative(cwd, manifestPath)}: ${err instanceof Error ? err.message : String(err)}`,\n );\n process.exit(1);\n }\n\n const rawStability = raw.stability ?? \"ga\";\n if (!isStability(rawStability)) {\n console.error(\n `Manifest at ${path.relative(cwd, manifestPath)} has an invalid stability value \"${String(rawStability)}\". ` +\n `Must be one of: beta, ga (or omitted for ga).`,\n );\n process.exit(1);\n }\n const currentStability: Stability = rawStability;\n\n if (currentStability === target) {\n console.error(\n `Plugin \"${pluginName}\" is already at \"${target}\". Nothing to do.`,\n );\n process.exit(1);\n }\n\n if (TIER_ORDER[target] <= TIER_ORDER[currentStability]) {\n console.error(\n `Cannot demote \"${pluginName}\" from \"${currentStability}\" to \"${target}\". Promotion is one-way only.`,\n );\n process.exit(1);\n }\n\n const prefix = options.dryRun ? \"[dry-run] \" : \"\";\n\n if (target === \"ga\") {\n delete raw.stability;\n } else {\n raw.stability = target;\n }\n\n if (!options.dryRun) {\n fs.writeFileSync(manifestPath, `${JSON.stringify(raw, null, 2)}\\n`);\n }\n console.log(\n `${prefix}Updated manifest: ${path.relative(cwd, manifestPath)} (${currentStability} → ${target})`,\n );\n\n const importRewrites: { file: string; from: string; to: string }[] = [];\n if (!options.skipImports) {\n const oldSuffix = IMPORT_PATH_MAP[currentStability];\n const newSuffix = IMPORT_PATH_MAP[target];\n\n const tsFiles = findTsFiles(cwd, cwd);\n for (const file of tsFiles) {\n const result = rewriteImportsInFile(\n file,\n pluginName,\n oldSuffix,\n newSuffix,\n Boolean(options.dryRun),\n );\n if (result) {\n importRewrites.push(result);\n console.log(\n `${prefix}Rewritten imports in: ${path.relative(cwd, file)}`,\n );\n }\n }\n\n if (importRewrites.length === 0) {\n console.log(`${prefix}No import paths to rewrite.`);\n }\n }\n\n if (!options.skipSync && !options.dryRun) {\n // Monorepo-only: regenerate every artifact derived from plugin\n // manifests (ga/beta export barrels AND the per-plugin docs-page\n // stability banner) so all manifest-derived layers move together.\n // No-op outside the AppKit monorepo (third-party plugin projects don't\n // ship the generators).\n const generatorPath = path.join(cwd, \"tools\", \"generate-plugin-entries.ts\");\n if (fs.existsSync(generatorPath)) {\n console.log(`\\n${prefix}Regenerating manifest-derived artifacts...`);\n const { execSync } = await import(\"node:child_process\");\n try {\n execSync(\"pnpm run generate:types\", { cwd, stdio: \"inherit\" });\n } catch {\n console.error(\n `Error: post-promote 'generate:types' failed. ` +\n `Manifest was updated, but generated barrels and docs banners may be stale. ` +\n `Run 'pnpm run generate:types' manually.`,\n );\n process.exit(1);\n }\n }\n\n console.log(`\\n${prefix}Running plugin sync...`);\n const { execSync } = await import(\"node:child_process\");\n // Monorepo flavor: the AppKit monorepo's `pnpm sync:template` script\n // points sync at `template/appkit.plugins.json` (the file shipped to\n // consumers and read by the Go init template), not the project-root\n // default. Detect the monorepo via the same generator-path probe used\n // above and prefer the script when available so the manifest, the\n // synced template, and the runtime barrels stay aligned.\n const syncCommand = fs.existsSync(generatorPath)\n ? \"pnpm run sync:template\"\n : \"npx appkit plugin sync --write\";\n try {\n execSync(syncCommand, {\n cwd,\n stdio: \"inherit\",\n });\n } catch {\n console.error(\n `Error: post-promote sync ('${syncCommand}') failed. ` +\n `Manifest and imports were updated, but the synced plugin manifest ` +\n `may be stale. Run '${syncCommand}' manually.`,\n );\n process.exit(1);\n }\n }\n\n console.log(\n `\\n${prefix}Promotion complete: ${pluginName} ${currentStability} → ${target}`,\n );\n if (importRewrites.length > 0) {\n console.log(` ${importRewrites.length} file(s) with import rewrites`);\n }\n}\n\n/** Exported for testing. */\nexport {\n PLUGIN_NAME_PATTERN,\n TIER_ORDER,\n IMPORT_PATH_MAP,\n SKIP_DIRECTORIES,\n isStability,\n validatePluginName,\n rewriteImportsInFile,\n runPromote,\n};\n\nexport const pluginPromoteCommand = new Command(\"promote\")\n .description(\"Promote a plugin to a higher stability tier\")\n .argument(\"<plugin-name>\", \"Plugin name to promote\")\n .requiredOption(\"--to <tier>\", \"Target stability tier (beta, ga)\")\n .option(\"--dry-run\", \"Show what would change without modifying files\")\n .option(\"--skip-imports\", \"Only update manifest, skip import path rewriting\")\n .option(\"--skip-sync\", \"Don't auto-run plugin sync after promotion\")\n .option(\n \"--allow-installed\",\n \"Allow promoting a plugin that lives only under node_modules (advanced)\",\n )\n .action((pluginName, opts) =>\n runPromote(pluginName, opts).catch((err) => {\n const message = err instanceof Error ? err.message : String(err);\n console.error(`Error: ${message}`);\n if (process.env.DEBUG) console.error(err);\n process.exit(1);\n }),\n );\n"],"mappings":"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"}
1
+ {"version":3,"file":"promote.js","names":[],"sources":["../../../../../src/cli/commands/plugin/promote/promote.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport process from \"node:process\";\n\nimport { Command } from \"commander\";\n\nimport { kebabToCamel } from \"../../../../naming\";\nimport { resolveManifestInDir } from \"../manifest-resolve\";\nimport { isWithinDirectory } from \"../sync/sync\";\nimport { shouldAllowJsManifestForDir } from \"../trusted-js-manifest\";\n\ntype Stability = \"beta\" | \"ga\";\n\nconst TIER_ORDER: Record<Stability, number> = {\n beta: 0,\n ga: 1,\n};\n\nconst IMPORT_PATH_MAP: Record<Stability, string> = {\n beta: \"/beta\",\n ga: \"\",\n};\n\n/** Aligned with sync.ts and list.ts; keep all plugin-tree walks at the same cap. */\nconst MAX_SCAN_DEPTH = 5;\n\n/**\n * Directories that should never be walked when discovering plugin manifests\n * or rewriting imports. Mirrors common build/output trees.\n */\nconst SKIP_DIRECTORIES = new Set([\n \"node_modules\",\n \"dist\",\n \"build\",\n \"out\",\n \".git\",\n \".turbo\",\n \".next\",\n \".nuxt\",\n \".cache\",\n \".svelte-kit\",\n \".vite\",\n \".parcel-cache\",\n \"coverage\",\n]);\n\n/**\n * Plugin name charset accepted by the promote command. Mirrors npm package\n * naming (lowercase, dashes, underscores, dots, optional @scope/) and explicitly\n * forbids path separators, traversal, and NUL — so the name cannot escape its\n * intended directory when used in path.join().\n */\nconst PLUGIN_NAME_PATTERN =\n /^(?:@[a-z0-9][a-z0-9._-]*\\/)?[a-z0-9][a-z0-9._-]*$/i;\n\nfunction validatePluginName(pluginName: string): void {\n if (!pluginName || pluginName.includes(\"\\0\") || pluginName.includes(\"..\")) {\n throw new Error(\n `Invalid plugin name \"${pluginName}\". Plugin names must not contain \"..\", or null bytes.`,\n );\n }\n // Backslash is never allowed (treated as a path separator on Windows).\n if (pluginName.includes(\"\\\\\")) {\n throw new Error(\n `Invalid plugin name \"${pluginName}\". Plugin names must not contain backslashes.`,\n );\n }\n // Forward slash is only allowed inside the @scope/name form.\n if (pluginName.includes(\"/\") && !pluginName.startsWith(\"@\")) {\n throw new Error(\n `Invalid plugin name \"${pluginName}\". Plugin names must not contain \"/\" unless they are a scoped package (e.g. @scope/name).`,\n );\n }\n if (!PLUGIN_NAME_PATTERN.test(pluginName)) {\n throw new Error(\n `Invalid plugin name \"${pluginName}\". Expected lowercase alphanumeric with optional dashes, underscores, dots, or @scope/ prefix.`,\n );\n }\n}\n\nfunction isStability(value: unknown): value is Stability {\n return value === \"beta\" || value === \"ga\";\n}\n\nfunction findPluginManifest(\n pluginName: string,\n cwd: string,\n): { manifestPath: string; isLocal: boolean } | null {\n const dirsToScan = [\"plugins\", \"server\", \".\"];\n\n for (const dir of dirsToScan) {\n const absDir = path.resolve(cwd, dir);\n const result = scanDirForPlugin(absDir, pluginName, cwd, 0);\n if (result) return { manifestPath: result, isLocal: true };\n }\n\n // node_modules fallback. Validate and bound to the dist/plugins subtree to\n // make sure a malicious or typo'd name cannot escape via path.join.\n const nodeModulesDir = path.join(cwd, \"node_modules\", \"@databricks/appkit\");\n if (fs.existsSync(nodeModulesDir)) {\n const pluginsDir = path.join(nodeModulesDir, \"dist\", \"plugins\");\n if (fs.existsSync(pluginsDir)) {\n const manifestPath = path.resolve(\n pluginsDir,\n pluginName,\n \"manifest.json\",\n );\n if (\n isWithinDirectory(manifestPath, pluginsDir) &&\n fs.existsSync(manifestPath)\n ) {\n return { manifestPath, isLocal: false };\n }\n }\n }\n\n return null;\n}\n\nfunction scanDirForPlugin(\n dir: string,\n pluginName: string,\n cwd: string,\n depth: number,\n): string | null {\n if (depth >= MAX_SCAN_DEPTH) return null;\n let stat: fs.Stats;\n try {\n stat = fs.statSync(dir);\n } catch {\n return null;\n }\n if (!stat.isDirectory()) return null;\n\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n if (SKIP_DIRECTORIES.has(entry.name)) continue;\n if (entry.isSymbolicLink()) continue;\n const childPath = path.join(dir, entry.name);\n const allowJs = shouldAllowJsManifestForDir(childPath);\n const resolved = resolveManifestInDir(childPath, {\n allowJsManifest: allowJs,\n });\n\n if (resolved) {\n try {\n const obj = loadManifestFromFileSync(resolved.path);\n if (\n obj &&\n typeof obj === \"object\" &&\n \"name\" in obj &&\n (obj as { name: string }).name === pluginName\n ) {\n return resolved.path;\n }\n } catch {\n // skip unreadable / invalid manifest\n }\n continue;\n }\n\n const deeper = scanDirForPlugin(childPath, pluginName, cwd, depth + 1);\n if (deeper) return deeper;\n }\n return null;\n}\n\nfunction loadManifestFromFileSync(filePath: string): unknown {\n const raw = fs.readFileSync(filePath, \"utf-8\");\n return JSON.parse(raw);\n}\n\nfunction escapeRegex(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n/**\n * Returns true when the named import specifier `spec` resolves to either\n * `pluginName` itself or its kebab-to-camelCase JS-identifier form.\n * Handles the `name`, `name as alias`, and inline-type (`type name`,\n * `type name as alias`) forms. Matches on the imported binding before any\n * `as` rename so a promotion finds the right specifier regardless of how\n * the user aliased it.\n */\nfunction specifierMatchesPlugin(spec: string, pluginName: string): boolean {\n const stripped = spec.replace(/^type\\s+/, \"\").trim();\n const head = stripped.split(/\\s+as\\s+/)[0]?.trim();\n if (!head) return false;\n return head === pluginName || head === kebabToCamel(pluginName);\n}\n\n/**\n * Rewrite imports of `pluginName` from `<pkg><oldSuffix>` to `<pkg><newSuffix>`\n * across one file, leaving every OTHER specifier on the same import line at\n * its original source. The naïve `split/join` approach this replaced was\n * promoting *every* beta specifier in the file along with the targeted\n * plugin — a bug because beta specifiers don't exist at the GA subpath.\n *\n * Behaviour:\n * - If the targeted plugin is the only specifier on an import line, the\n * line's source is rewritten to the new path.\n * - If the import has multiple specifiers, the targeted one is moved to a\n * newly-emitted import line at the new source, and the original import\n * keeps the remaining specifiers at the old source.\n * - Imports that don't reference `pluginName` are left untouched.\n *\n * Multi-line specifier lists, type-only imports (`import type { ... }`),\n * inline-type specifiers (`import { type Foo }`), and `as`-aliased\n * specifiers are all preserved through the rewrite.\n */\nfunction rewriteImportsInFile(\n filePath: string,\n pluginName: string,\n oldSuffix: string,\n newSuffix: string,\n dryRun: boolean,\n): { file: string; from: string; to: string } | null {\n const content = fs.readFileSync(filePath, \"utf-8\");\n\n const packages = [\n \"@databricks/appkit\",\n \"@databricks/appkit-ui/js\",\n \"@databricks/appkit-ui/react\",\n ];\n let updated = content;\n let changed = false;\n\n for (const pkg of packages) {\n const oldPath = `${pkg}${oldSuffix}`;\n const newPath = `${pkg}${newSuffix}`;\n\n // Match: `import [type] { ...specifiers... } from \"<oldPath>\";`\n // - `[^}]*` lets the specifier list span newlines (safe — TS imports\n // don't have nested braces inside the specifier list).\n // - Captures the `type ` keyword (if any), the specifier body, and\n // the surrounding quote style so we can preserve them on output.\n const importRe = new RegExp(\n `import\\\\s+(type\\\\s+)?\\\\{([^}]*)\\\\}\\\\s*from\\\\s*([\"'])${escapeRegex(oldPath)}\\\\3\\\\s*;?`,\n \"g\",\n );\n\n updated = updated.replace(\n importRe,\n (full, typeKeyword, specifiers, quote) => {\n const specList: string[] = specifiers\n .split(\",\")\n .map((s: string) => s.trim())\n .filter((s: string) => s.length > 0);\n\n const promotedSpec = specList.find((s) =>\n specifierMatchesPlugin(s, pluginName),\n );\n if (!promotedSpec) {\n // Plugin not in this import — leave it alone.\n return full;\n }\n\n const remaining = specList.filter(\n (s) => !specifierMatchesPlugin(s, pluginName),\n );\n changed = true;\n\n const tk = typeKeyword ?? \"\";\n const promotedImport = `import ${tk}{ ${promotedSpec} } from ${quote}${newPath}${quote};`;\n\n if (remaining.length === 0) {\n // Only specifier — just rewrite the source.\n return promotedImport;\n }\n\n const remainingImport = `import ${tk}{ ${remaining.join(\", \")} } from ${quote}${oldPath}${quote};`;\n return `${remainingImport}\\n${promotedImport}`;\n },\n );\n }\n\n if (!changed) return null;\n\n if (!dryRun) {\n fs.writeFileSync(filePath, updated);\n }\n\n return {\n file: filePath,\n from: oldSuffix || \"(root)\",\n to: newSuffix || \"(root)\",\n };\n}\n\nfunction findTsFiles(dir: string, projectRoot: string, depth = 0): string[] {\n if (depth >= MAX_SCAN_DEPTH) return [];\n\n const results: string[] = [];\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true });\n } catch {\n return results;\n }\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isSymbolicLink()) continue;\n if (entry.isDirectory()) {\n if (SKIP_DIRECTORIES.has(entry.name)) continue;\n // Boundary check to ensure recursion stays inside the project root,\n // even if a future change introduces a symlink-following path.\n if (!isWithinDirectory(fullPath, projectRoot)) continue;\n results.push(...findTsFiles(fullPath, projectRoot, depth + 1));\n } else if (entry.name.endsWith(\".ts\") || entry.name.endsWith(\".tsx\")) {\n results.push(fullPath);\n }\n }\n\n return results;\n}\n\nasync function runPromote(\n pluginName: string,\n options: {\n to: string;\n dryRun?: boolean;\n skipImports?: boolean;\n skipSync?: boolean;\n allowInstalled?: boolean;\n },\n): Promise<void> {\n validatePluginName(pluginName);\n\n const cwd = process.cwd();\n\n if (!isStability(options.to)) {\n console.error(\n `Invalid target tier \"${options.to}\". Must be one of: beta, ga.`,\n );\n process.exit(1);\n }\n const target: Stability = options.to;\n\n const found = findPluginManifest(pluginName, cwd);\n if (!found) {\n console.error(\n `Plugin \"${pluginName}\" not found. Searched local dirs (plugins, server, .) and node_modules.`,\n );\n process.exit(1);\n }\n\n const { manifestPath, isLocal } = found;\n\n if (!isLocal && !options.allowInstalled) {\n console.error(\n `Plugin \"${pluginName}\" was only found under node_modules (${path.relative(cwd, manifestPath)}).\\n` +\n `Refusing to mutate an installed package — re-install would overwrite the change.\\n` +\n `Pass --allow-installed to override (advanced; not recommended).`,\n );\n process.exit(1);\n }\n\n let raw: Record<string, unknown>;\n try {\n const parsed = loadManifestFromFileSync(manifestPath);\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n console.error(\n `Manifest at ${path.relative(cwd, manifestPath)} is not a JSON object.`,\n );\n process.exit(1);\n }\n raw = parsed as Record<string, unknown>;\n } catch (err) {\n console.error(\n `Failed to read manifest at ${path.relative(cwd, manifestPath)}: ${err instanceof Error ? err.message : String(err)}`,\n );\n process.exit(1);\n }\n\n const rawStability = raw.stability ?? \"ga\";\n if (!isStability(rawStability)) {\n console.error(\n `Manifest at ${path.relative(cwd, manifestPath)} has an invalid stability value \"${String(rawStability)}\". ` +\n `Must be one of: beta, ga (or omitted for ga).`,\n );\n process.exit(1);\n }\n const currentStability: Stability = rawStability;\n\n if (currentStability === target) {\n console.error(\n `Plugin \"${pluginName}\" is already at \"${target}\". Nothing to do.`,\n );\n process.exit(1);\n }\n\n if (TIER_ORDER[target] <= TIER_ORDER[currentStability]) {\n console.error(\n `Cannot demote \"${pluginName}\" from \"${currentStability}\" to \"${target}\". Promotion is one-way only.`,\n );\n process.exit(1);\n }\n\n const prefix = options.dryRun ? \"[dry-run] \" : \"\";\n\n if (target === \"ga\") {\n delete raw.stability;\n } else {\n raw.stability = target;\n }\n\n if (!options.dryRun) {\n fs.writeFileSync(manifestPath, `${JSON.stringify(raw, null, 2)}\\n`);\n }\n console.log(\n `${prefix}Updated manifest: ${path.relative(cwd, manifestPath)} (${currentStability} → ${target})`,\n );\n\n const importRewrites: { file: string; from: string; to: string }[] = [];\n if (!options.skipImports) {\n const oldSuffix = IMPORT_PATH_MAP[currentStability];\n const newSuffix = IMPORT_PATH_MAP[target];\n\n const tsFiles = findTsFiles(cwd, cwd);\n for (const file of tsFiles) {\n const result = rewriteImportsInFile(\n file,\n pluginName,\n oldSuffix,\n newSuffix,\n Boolean(options.dryRun),\n );\n if (result) {\n importRewrites.push(result);\n console.log(\n `${prefix}Rewritten imports in: ${path.relative(cwd, file)}`,\n );\n }\n }\n\n if (importRewrites.length === 0) {\n console.log(`${prefix}No import paths to rewrite.`);\n }\n }\n\n if (!options.skipSync && !options.dryRun) {\n // Monorepo-only: regenerate every artifact derived from plugin\n // manifests (ga/beta export barrels AND the per-plugin docs-page\n // stability banner) so all manifest-derived layers move together.\n // No-op outside the AppKit monorepo (third-party plugin projects don't\n // ship the generators).\n const generatorPath = path.join(cwd, \"tools\", \"generate-plugin-entries.ts\");\n if (fs.existsSync(generatorPath)) {\n console.log(`\\n${prefix}Regenerating manifest-derived artifacts...`);\n const { execSync } = await import(\"node:child_process\");\n try {\n execSync(\"pnpm run generate:types\", { cwd, stdio: \"inherit\" });\n } catch {\n console.error(\n `Error: post-promote 'generate:types' failed. ` +\n `Manifest was updated, but generated barrels and docs banners may be stale. ` +\n `Run 'pnpm run generate:types' manually.`,\n );\n process.exit(1);\n }\n }\n\n console.log(`\\n${prefix}Running plugin sync...`);\n const { execSync } = await import(\"node:child_process\");\n // Monorepo flavor: the AppKit monorepo's `pnpm sync:template` script\n // points sync at `template/appkit.plugins.json` (the file shipped to\n // consumers and read by the Go init template), not the project-root\n // default. Detect the monorepo via the same generator-path probe used\n // above and prefer the script when available so the manifest, the\n // synced template, and the runtime barrels stay aligned.\n const syncCommand = fs.existsSync(generatorPath)\n ? \"pnpm run sync:template\"\n : \"npx appkit plugin sync --write\";\n try {\n execSync(syncCommand, {\n cwd,\n stdio: \"inherit\",\n });\n } catch {\n console.error(\n `Error: post-promote sync ('${syncCommand}') failed. ` +\n `Manifest and imports were updated, but the synced plugin manifest ` +\n `may be stale. Run '${syncCommand}' manually.`,\n );\n process.exit(1);\n }\n }\n\n console.log(\n `\\n${prefix}Promotion complete: ${pluginName} ${currentStability} → ${target}`,\n );\n if (importRewrites.length > 0) {\n console.log(` ${importRewrites.length} file(s) with import rewrites`);\n }\n}\n\n/** Exported for testing. */\nexport {\n PLUGIN_NAME_PATTERN,\n TIER_ORDER,\n IMPORT_PATH_MAP,\n SKIP_DIRECTORIES,\n isStability,\n validatePluginName,\n rewriteImportsInFile,\n runPromote,\n};\n\nexport const pluginPromoteCommand = new Command(\"promote\")\n .description(\"Promote a plugin to a higher stability tier\")\n .argument(\"<plugin-name>\", \"Plugin name to promote\")\n .requiredOption(\"--to <tier>\", \"Target stability tier (beta, ga)\")\n .option(\"--dry-run\", \"Show what would change without modifying files\")\n .option(\"--skip-imports\", \"Only update manifest, skip import path rewriting\")\n .option(\"--skip-sync\", \"Don't auto-run plugin sync after promotion\")\n .option(\n \"--allow-installed\",\n \"Allow promoting a plugin that lives only under node_modules (advanced)\",\n )\n .action((pluginName, opts) =>\n runPromote(pluginName, opts).catch((err) => {\n const message = err instanceof Error ? err.message : String(err);\n console.error(`Error: ${message}`);\n if (process.env.DEBUG) console.error(err);\n process.exit(1);\n }),\n );\n"],"mappings":"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"}