@databricks/appkit-ui 0.27.1 → 0.28.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.
- package/CLAUDE.md +1 -0
- package/dist/cli/commands/plugin/create/create.js +18 -0
- package/dist/cli/commands/plugin/create/create.js.map +1 -1
- package/dist/cli/commands/plugin/create/scaffold.js +1 -0
- package/dist/cli/commands/plugin/create/scaffold.js.map +1 -1
- package/dist/cli/commands/plugin/index.js +5 -2
- package/dist/cli/commands/plugin/index.js.map +1 -1
- package/dist/cli/commands/plugin/list/list.js +6 -0
- package/dist/cli/commands/plugin/list/list.js.map +1 -1
- package/dist/cli/commands/plugin/promote/promote.js +314 -0
- package/dist/cli/commands/plugin/promote/promote.js.map +1 -0
- package/dist/cli/commands/plugin/sync/sync.js +24 -4
- package/dist/cli/commands/plugin/sync/sync.js.map +1 -1
- package/dist/js/beta.d.ts +1 -0
- package/dist/js/beta.js +0 -0
- package/dist/react/beta.d.ts +1 -0
- package/dist/react/beta.js +0 -0
- package/dist/schemas/plugin-manifest.generated.d.ts +4 -0
- package/dist/schemas/plugin-manifest.generated.d.ts.map +1 -1
- package/dist/schemas/plugin-manifest.schema.json +6 -0
- package/dist/schemas/template-plugins.schema.json +7 -1
- package/docs/api/appkit/Interface.PluginManifest.md +19 -1
- package/docs/plugins/custom-plugins.md +1 -1
- package/docs/plugins/execution-context.md +13 -1
- package/docs/plugins/stability.md +156 -0
- package/llms.txt +1 -0
- package/package.json +3 -1
- package/sbom.cdx.json +1 -1
package/CLAUDE.md
CHANGED
|
@@ -53,6 +53,7 @@ npx @databricks/appkit docs <query>
|
|
|
53
53
|
- [Model Serving plugin](./docs/plugins/model-serving.md): Provides an authenticated proxy to Databricks Model Serving endpoints, with invoke and streaming support.
|
|
54
54
|
- [Plugin management](./docs/plugins/plugin-management.md): AppKit includes a CLI for managing plugins. All commands are available under npx @databricks/appkit plugin.
|
|
55
55
|
- [Server plugin](./docs/plugins/server.md): Provides HTTP server capabilities with development and production modes.
|
|
56
|
+
- [Plugin Stability Tiers](./docs/plugins/stability.md): AppKit plugins have a two-tier stability system that communicates API maturity and breaking-change expectations.
|
|
56
57
|
- [Vector Search plugin](./docs/plugins/vector-search.md): Query Databricks Vector Search indexes with hybrid search, reranking, and cursor pagination from your AppKit application.
|
|
57
58
|
|
|
58
59
|
## appkit API reference [collapsed]
|
|
@@ -210,6 +210,23 @@ async function runInteractive() {
|
|
|
210
210
|
cancel("Cancelled.");
|
|
211
211
|
process.exit(0);
|
|
212
212
|
}
|
|
213
|
+
const stability = await select({
|
|
214
|
+
message: "Plugin stability level",
|
|
215
|
+
options: [{
|
|
216
|
+
value: "ga",
|
|
217
|
+
label: "GA",
|
|
218
|
+
hint: "API follows semver"
|
|
219
|
+
}, {
|
|
220
|
+
value: "beta",
|
|
221
|
+
label: "Beta",
|
|
222
|
+
hint: "Heading to GA, API may change"
|
|
223
|
+
}],
|
|
224
|
+
initialValue: "ga"
|
|
225
|
+
});
|
|
226
|
+
if (isCancel(stability)) {
|
|
227
|
+
cancel("Cancelled.");
|
|
228
|
+
process.exit(0);
|
|
229
|
+
}
|
|
213
230
|
const resourceTypes = await multiselect({
|
|
214
231
|
message: "Which Databricks resources does this plugin need?",
|
|
215
232
|
options: RESOURCE_TYPE_OPTIONS.map((o) => ({
|
|
@@ -244,6 +261,7 @@ async function runInteractive() {
|
|
|
244
261
|
name: name.trim(),
|
|
245
262
|
displayName: displayName.trim(),
|
|
246
263
|
description: description.trim(),
|
|
264
|
+
stability: stability === "ga" ? void 0 : stability,
|
|
247
265
|
resources,
|
|
248
266
|
version: DEFAULT_VERSION
|
|
249
267
|
};
|
|
@@ -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 { promptOneResource } from \"./prompt-resource\";\nimport {\n DEFAULT_PERMISSION_BY_TYPE,\n getDefaultFieldsForType,\n getValidResourceTypes,\n humanizeResourceType,\n RESOURCE_TYPE_OPTIONS,\n resourceKeyFromType,\n} from \"./resource-defaults\";\nimport { resolveTargetDir, scaffoldPlugin } from \"./scaffold\";\nimport type { CreateAnswers, Placement, SelectedResource } from \"./types\";\n\nconst NAME_PATTERN = /^[a-z][a-z0-9-]*$/;\nconst DEFAULT_VERSION = \"0.1.0\";\nconst VALID_PLACEMENTS: Placement[] = [\"in-repo\", \"isolated\"];\nconst REQUIRED_FLAGS = [\"placement\", \"path\", \"name\", \"description\"] as const;\n\ninterface CreateOptions {\n placement?: string;\n path?: string;\n name?: string;\n displayName?: string;\n description?: string;\n resources?: string;\n resourcesJson?: string;\n force?: boolean;\n}\n\nfunction deriveDisplayName(name: string): string {\n return name\n .split(\"-\")\n .map((s) => s.charAt(0).toUpperCase() + s.slice(1))\n .join(\" \");\n}\n\nfunction deriveExportName(name: string): string {\n return name\n .split(\"-\")\n .map((s, i) => (i === 0 ? s : s.charAt(0).toUpperCase() + s.slice(1)))\n .join(\"\");\n}\n\nfunction buildResourceFromType(type: string): SelectedResource {\n return {\n type,\n required: true,\n description: `Required for ${humanizeResourceType(type)} functionality.`,\n resourceKey: resourceKeyFromType(type),\n permission: DEFAULT_PERMISSION_BY_TYPE[type] ?? \"CAN_VIEW\",\n fields: getDefaultFieldsForType(type),\n };\n}\n\ninterface JsonResourceEntry {\n type: string;\n required?: boolean;\n description?: string;\n resourceKey?: string;\n permission?: string;\n fields?: Record<string, { env: string; description?: string }>;\n}\n\nfunction parseResourcesJson(json: string): SelectedResource[] {\n let parsed: unknown;\n try {\n parsed = JSON.parse(json);\n } catch {\n console.error(\"Error: --resources-json must be valid JSON.\");\n console.error(' Example: --resources-json \\'[{\"type\":\"sql_warehouse\"}]\\'');\n process.exit(1);\n }\n\n if (!Array.isArray(parsed)) {\n console.error(\"Error: --resources-json must be a JSON array.\");\n console.error(' Example: --resources-json \\'[{\"type\":\"sql_warehouse\"}]\\'');\n process.exit(1);\n }\n\n return (parsed as JsonResourceEntry[]).map((entry, i) => {\n if (entry == null || typeof entry !== \"object\") {\n console.error(`Error: --resources-json entry ${i} is not an object.`);\n process.exit(1);\n }\n if (!entry.type || typeof entry.type !== \"string\") {\n console.error(\n `Error: --resources-json entry ${i} missing required \"type\" field.`,\n );\n process.exit(1);\n }\n validateResourceType(entry.type);\n const defaults = buildResourceFromType(entry.type);\n return {\n type: entry.type,\n required: entry.required ?? defaults.required,\n description: entry.description ?? defaults.description,\n resourceKey: entry.resourceKey ?? defaults.resourceKey,\n permission: entry.permission ?? defaults.permission,\n fields: entry.fields ?? defaults.fields,\n };\n });\n}\n\nfunction validateResourceType(type: string): void {\n const validTypes = getValidResourceTypes();\n if (!validTypes.includes(type)) {\n console.error(`Error: Unknown resource type \"${type}\".`);\n console.error(` Valid types: ${validTypes.join(\", \")}`);\n process.exit(1);\n }\n}\n\nfunction parseResourcesShorthand(csv: string): SelectedResource[] {\n const types = csv\n .split(\",\")\n .map((s) => s.trim())\n .filter(Boolean);\n for (const t of types) validateResourceType(t);\n return types.map(buildResourceFromType);\n}\n\nfunction printNextSteps(answers: CreateAnswers, targetDir: string): void {\n const relativePath = path.relative(process.cwd(), targetDir);\n const importPath = relativePath.startsWith(\".\")\n ? relativePath\n : `./${relativePath}`;\n const exportName = deriveExportName(answers.name);\n\n console.log(\"\\nNext steps:\\n\");\n if (answers.placement === \"in-repo\") {\n console.log(` 1. Import and register in your server:`);\n console.log(` import { ${exportName} } from \"${importPath}\";`);\n console.log(` createApp({ plugins: [ ..., ${exportName}() ] });`);\n console.log(\n ` 2. Run \\`npx appkit plugin sync --write\\` to update appkit.plugins.json.\\n`,\n );\n } else {\n console.log(` 1. cd into the new package and install dependencies:`);\n console.log(` cd ${answers.targetPath} && pnpm install`);\n console.log(` 2. Build: pnpm build`);\n console.log(\n ` 3. In your app: pnpm add ./${answers.targetPath} @databricks/appkit`,\n );\n console.log(\n ` 4. Import and register: import { ${exportName} } from \"<package-name>\";\\n`,\n );\n }\n}\n\nfunction runNonInteractive(opts: CreateOptions): void {\n const missing = REQUIRED_FLAGS.filter((f) => !opts[f]);\n if (missing.length > 0) {\n console.error(\n `Error: Non-interactive mode requires: ${REQUIRED_FLAGS.map((f) => `--${f}`).join(\", \")}`,\n );\n console.error(`Missing: ${missing.map((f) => `--${f}`).join(\", \")}`);\n console.error(\n ' appkit plugin create --placement in-repo --path plugins/my-plugin --name my-plugin --description \"Does X\"',\n );\n process.exit(1);\n }\n\n const placement = opts.placement as Placement;\n if (!VALID_PLACEMENTS.includes(placement)) {\n console.error(\n `Error: --placement must be one of: ${VALID_PLACEMENTS.join(\", \")}`,\n );\n process.exit(1);\n }\n\n const targetPath = (opts.path as string).trim();\n if (\n placement === \"in-repo\" &&\n (path.isAbsolute(targetPath) || targetPath.startsWith(\"..\"))\n ) {\n console.error(\n \"Error: --path must be a relative path under the current directory for in-repo plugins.\",\n );\n process.exit(1);\n }\n\n const name = opts.name as string;\n if (!NAME_PATTERN.test(name)) {\n console.error(\n \"Error: --name must be lowercase, start with a letter, and use only letters, numbers, and hyphens.\",\n );\n process.exit(1);\n }\n\n let resources: SelectedResource[] = [];\n if (opts.resourcesJson) {\n resources = parseResourcesJson(opts.resourcesJson);\n } else if (opts.resources) {\n resources = parseResourcesShorthand(opts.resources);\n }\n\n const answers: CreateAnswers = {\n placement,\n targetPath,\n name: name.trim(),\n displayName: opts.displayName?.trim() || deriveDisplayName(name),\n description: (opts.description as string).trim(),\n resources,\n version: DEFAULT_VERSION,\n };\n\n const targetDir = resolveTargetDir(process.cwd(), answers);\n const dirExists = fs.existsSync(targetDir);\n const hasContent = dirExists && fs.readdirSync(targetDir).length > 0;\n if (hasContent && !opts.force) {\n console.error(\n `Error: Directory ${answers.targetPath} already exists and is not empty.`,\n );\n console.error(\" Use --force to overwrite.\");\n process.exit(1);\n }\n\n scaffoldPlugin(targetDir, answers, { isolated: placement === \"isolated\" });\n\n console.log(\n `Plugin \"${answers.name}\" created at ${path.relative(process.cwd(), targetDir)}`,\n );\n printNextSteps(answers, targetDir);\n}\n\nasync function runInteractive(): Promise<void> {\n intro(\"Create a new AppKit plugin\");\n\n try {\n const placement = await select<Placement>({\n message: \"Where should the plugin live?\",\n options: [\n {\n value: \"in-repo\",\n label: \"In this repository (e.g. plugins/my-plugin)\",\n hint: \"folder path\",\n },\n {\n value: \"isolated\",\n label: \"New isolated package\",\n hint: \"full package with package.json\",\n },\n ],\n });\n if (isCancel(placement)) {\n cancel(\"Cancelled.\");\n process.exit(0);\n }\n\n const placementPrompt =\n placement === \"in-repo\"\n ? \"Folder path for the plugin (e.g. plugins/my-feature)\"\n : \"Directory name for the new package (e.g. appkit-plugin-my-feature)\";\n const targetPath = await text({\n message: placementPrompt,\n placeholder:\n placement === \"in-repo\"\n ? \"plugins/my-plugin\"\n : \"appkit-plugin-my-feature\",\n validate(value) {\n if (!value?.trim()) return \"Path is required.\";\n if (\n placement === \"in-repo\" &&\n (path.isAbsolute(value) || value.trim().startsWith(\"..\"))\n ) {\n return \"Use a relative path under the current directory (e.g. plugins/my-plugin).\";\n }\n return undefined;\n },\n });\n if (isCancel(targetPath)) {\n cancel(\"Cancelled.\");\n process.exit(0);\n }\n\n const name = await text({\n message: \"Plugin name (id)\",\n placeholder: \"my-plugin\",\n validate(value) {\n if (!value?.trim()) return \"Name is required.\";\n if (!NAME_PATTERN.test(value as string)) {\n return \"Must be lowercase, start with a letter, and use only letters, numbers, and hyphens.\";\n }\n return undefined;\n },\n });\n if (isCancel(name)) {\n cancel(\"Cancelled.\");\n process.exit(0);\n }\n\n const displayName = await text({\n message: \"Display name\",\n placeholder: \"My Plugin\",\n initialValue: name\n .split(\"-\")\n .map((s) => s.charAt(0).toUpperCase() + s.slice(1))\n .join(\" \"),\n validate(value) {\n if (!value?.trim()) return \"Display name is required.\";\n return undefined;\n },\n });\n if (isCancel(displayName)) {\n cancel(\"Cancelled.\");\n process.exit(0);\n }\n\n const description = await text({\n message: \"Short description\",\n placeholder: \"What does this plugin do?\",\n validate(value) {\n if (!value?.trim()) return \"Description is required.\";\n return undefined;\n },\n });\n if (isCancel(description)) {\n cancel(\"Cancelled.\");\n process.exit(0);\n }\n\n const resourceTypes = await multiselect({\n message: \"Which Databricks resources does this plugin need?\",\n options: RESOURCE_TYPE_OPTIONS.map((o) => ({\n value: o.value,\n label: o.label,\n })),\n required: false,\n });\n if (isCancel(resourceTypes)) {\n cancel(\"Cancelled.\");\n process.exit(0);\n }\n\n const resources: CreateAnswers[\"resources\"] = [];\n for (const type of resourceTypes as string[]) {\n const spec = await promptOneResource({ type });\n if (!spec) {\n cancel(\"Cancelled.\");\n process.exit(0);\n }\n resources.push({\n type: spec.type,\n required: spec.required,\n description: spec.description,\n resourceKey: spec.resourceKey,\n permission: spec.permission,\n fields: spec.fields,\n });\n }\n\n const answers: CreateAnswers = {\n placement,\n targetPath: (targetPath as string).trim(),\n name: (name as string).trim(),\n displayName: (displayName as string).trim(),\n description: (description as string).trim(),\n resources,\n version: DEFAULT_VERSION,\n };\n\n const targetDir = resolveTargetDir(process.cwd(), answers);\n const dirExists = fs.existsSync(targetDir);\n const hasContent = dirExists && fs.readdirSync(targetDir).length > 0;\n if (hasContent) {\n const overwrite = await confirm({\n message: `Directory ${answers.targetPath} already exists and is not empty. Overwrite?`,\n initialValue: false,\n });\n if (isCancel(overwrite) || !overwrite) {\n cancel(\"Cancelled.\");\n process.exit(0);\n }\n }\n\n const s = spinner();\n s.start(\"Writing files…\");\n try {\n scaffoldPlugin(targetDir, answers, {\n isolated: placement === \"isolated\",\n });\n s.stop(\"Files written.\");\n } catch (err) {\n s.stop(\"Failed.\");\n throw err;\n }\n\n outro(\"Plugin created successfully.\");\n printNextSteps(answers, targetDir);\n } catch (err) {\n console.error(err);\n process.exit(1);\n }\n}\n\nconst OPTIONAL_FLAGS = [\n \"displayName\",\n \"resources\",\n \"resourcesJson\",\n \"force\",\n] as const;\n\nasync function runPluginCreate(opts: CreateOptions): Promise<void> {\n const hasRequiredFlag = REQUIRED_FLAGS.some((f) => opts[f] !== undefined);\n if (hasRequiredFlag) {\n runNonInteractive(opts);\n } else {\n const hasOptionalOnly = OPTIONAL_FLAGS.some(\n (f) => opts[f] !== undefined && opts[f] !== false,\n );\n if (hasOptionalOnly) {\n console.error(\n `Error: Non-interactive mode requires: ${REQUIRED_FLAGS.map((f) => `--${f}`).join(\", \")}`,\n );\n console.error(\n ' appkit plugin create --placement in-repo --path plugins/my-plugin --name my-plugin --description \"Does X\"',\n );\n process.exit(1);\n }\n await runInteractive();\n }\n}\n\nexport const pluginCreateCommand = new Command(\"create\")\n .description(\"Scaffold a new AppKit plugin\")\n .option(\"--placement <type>\", \"Where the plugin lives (in-repo, isolated)\")\n .option(\"--path <dir>\", \"Target directory for the plugin\")\n .option(\"--name <id>\", \"Plugin name (lowercase, hyphens allowed)\")\n .option(\"--display-name <name>\", \"Human-readable display name\")\n .option(\"--description <text>\", \"Short description of the plugin\")\n .addOption(\n new Option(\n \"--resources <types>\",\n \"Comma-separated resource types (e.g. sql_warehouse,volume)\",\n ).conflicts(\"resourcesJson\"),\n )\n .addOption(\n new Option(\n \"--resources-json <json>\",\n 'JSON array of resource specs (e.g. \\'[{\"type\":\"sql_warehouse\"}]\\')',\n ).conflicts(\"resources\"),\n )\n .option(\"-f, --force\", \"Overwrite existing directory without confirmation\")\n .addHelpText(\n \"after\",\n `\nExamples:\n $ appkit plugin create\n $ appkit plugin create --placement in-repo --path plugins/my-plugin --name my-plugin --description \"Does X\"\n $ appkit plugin create --placement in-repo --path plugins/my-plugin --name my-plugin --description \"Does X\" --resources sql_warehouse,volume --force\n $ appkit plugin create --placement isolated --path appkit-plugin-ml --name ml --description \"ML\" --resources-json '[{\"type\":\"serving_endpoint\"}]'`,\n )\n .action((opts) =>\n runPluginCreate(opts).catch((err) => {\n console.error(err);\n process.exit(1);\n }),\n );\n\n/** Exported for testing. */\nexport { buildResourceFromType, parseResourcesJson, parseResourcesShorthand };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"create.js","names":[],"sources":["../../../../../src/cli/commands/plugin/create/create.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport process from \"node:process\";\nimport {\n cancel,\n confirm,\n intro,\n isCancel,\n multiselect,\n outro,\n select,\n spinner,\n text,\n} from \"@clack/prompts\";\nimport { Command, Option } from \"commander\";\nimport { promptOneResource } from \"./prompt-resource\";\nimport {\n DEFAULT_PERMISSION_BY_TYPE,\n getDefaultFieldsForType,\n getValidResourceTypes,\n humanizeResourceType,\n RESOURCE_TYPE_OPTIONS,\n resourceKeyFromType,\n} from \"./resource-defaults\";\nimport { resolveTargetDir, scaffoldPlugin } from \"./scaffold\";\nimport type { CreateAnswers, Placement, SelectedResource } from \"./types\";\n\nconst NAME_PATTERN = /^[a-z][a-z0-9-]*$/;\nconst DEFAULT_VERSION = \"0.1.0\";\nconst VALID_PLACEMENTS: Placement[] = [\"in-repo\", \"isolated\"];\nconst REQUIRED_FLAGS = [\"placement\", \"path\", \"name\", \"description\"] as const;\n\ninterface CreateOptions {\n placement?: string;\n path?: string;\n name?: string;\n displayName?: string;\n description?: string;\n resources?: string;\n resourcesJson?: string;\n force?: boolean;\n}\n\nfunction deriveDisplayName(name: string): string {\n return name\n .split(\"-\")\n .map((s) => s.charAt(0).toUpperCase() + s.slice(1))\n .join(\" \");\n}\n\nfunction deriveExportName(name: string): string {\n return name\n .split(\"-\")\n .map((s, i) => (i === 0 ? s : s.charAt(0).toUpperCase() + s.slice(1)))\n .join(\"\");\n}\n\nfunction buildResourceFromType(type: string): SelectedResource {\n return {\n type,\n required: true,\n description: `Required for ${humanizeResourceType(type)} functionality.`,\n resourceKey: resourceKeyFromType(type),\n permission: DEFAULT_PERMISSION_BY_TYPE[type] ?? \"CAN_VIEW\",\n fields: getDefaultFieldsForType(type),\n };\n}\n\ninterface JsonResourceEntry {\n type: string;\n required?: boolean;\n description?: string;\n resourceKey?: string;\n permission?: string;\n fields?: Record<string, { env: string; description?: string }>;\n}\n\nfunction parseResourcesJson(json: string): SelectedResource[] {\n let parsed: unknown;\n try {\n parsed = JSON.parse(json);\n } catch {\n console.error(\"Error: --resources-json must be valid JSON.\");\n console.error(' Example: --resources-json \\'[{\"type\":\"sql_warehouse\"}]\\'');\n process.exit(1);\n }\n\n if (!Array.isArray(parsed)) {\n console.error(\"Error: --resources-json must be a JSON array.\");\n console.error(' Example: --resources-json \\'[{\"type\":\"sql_warehouse\"}]\\'');\n process.exit(1);\n }\n\n return (parsed as JsonResourceEntry[]).map((entry, i) => {\n if (entry == null || typeof entry !== \"object\") {\n console.error(`Error: --resources-json entry ${i} is not an object.`);\n process.exit(1);\n }\n if (!entry.type || typeof entry.type !== \"string\") {\n console.error(\n `Error: --resources-json entry ${i} missing required \"type\" field.`,\n );\n process.exit(1);\n }\n validateResourceType(entry.type);\n const defaults = buildResourceFromType(entry.type);\n return {\n type: entry.type,\n required: entry.required ?? defaults.required,\n description: entry.description ?? defaults.description,\n resourceKey: entry.resourceKey ?? defaults.resourceKey,\n permission: entry.permission ?? defaults.permission,\n fields: entry.fields ?? defaults.fields,\n };\n });\n}\n\nfunction validateResourceType(type: string): void {\n const validTypes = getValidResourceTypes();\n if (!validTypes.includes(type)) {\n console.error(`Error: Unknown resource type \"${type}\".`);\n console.error(` Valid types: ${validTypes.join(\", \")}`);\n process.exit(1);\n }\n}\n\nfunction parseResourcesShorthand(csv: string): SelectedResource[] {\n const types = csv\n .split(\",\")\n .map((s) => s.trim())\n .filter(Boolean);\n for (const t of types) validateResourceType(t);\n return types.map(buildResourceFromType);\n}\n\nfunction printNextSteps(answers: CreateAnswers, targetDir: string): void {\n const relativePath = path.relative(process.cwd(), targetDir);\n const importPath = relativePath.startsWith(\".\")\n ? relativePath\n : `./${relativePath}`;\n const exportName = deriveExportName(answers.name);\n\n console.log(\"\\nNext steps:\\n\");\n if (answers.placement === \"in-repo\") {\n console.log(` 1. Import and register in your server:`);\n console.log(` import { ${exportName} } from \"${importPath}\";`);\n console.log(` createApp({ plugins: [ ..., ${exportName}() ] });`);\n console.log(\n ` 2. Run \\`npx appkit plugin sync --write\\` to update appkit.plugins.json.\\n`,\n );\n } else {\n console.log(` 1. cd into the new package and install dependencies:`);\n console.log(` cd ${answers.targetPath} && pnpm install`);\n console.log(` 2. Build: pnpm build`);\n console.log(\n ` 3. In your app: pnpm add ./${answers.targetPath} @databricks/appkit`,\n );\n console.log(\n ` 4. Import and register: import { ${exportName} } from \"<package-name>\";\\n`,\n );\n }\n}\n\nfunction runNonInteractive(opts: CreateOptions): void {\n const missing = REQUIRED_FLAGS.filter((f) => !opts[f]);\n if (missing.length > 0) {\n console.error(\n `Error: Non-interactive mode requires: ${REQUIRED_FLAGS.map((f) => `--${f}`).join(\", \")}`,\n );\n console.error(`Missing: ${missing.map((f) => `--${f}`).join(\", \")}`);\n console.error(\n ' appkit plugin create --placement in-repo --path plugins/my-plugin --name my-plugin --description \"Does X\"',\n );\n process.exit(1);\n }\n\n const placement = opts.placement as Placement;\n if (!VALID_PLACEMENTS.includes(placement)) {\n console.error(\n `Error: --placement must be one of: ${VALID_PLACEMENTS.join(\", \")}`,\n );\n process.exit(1);\n }\n\n const targetPath = (opts.path as string).trim();\n if (\n placement === \"in-repo\" &&\n (path.isAbsolute(targetPath) || targetPath.startsWith(\"..\"))\n ) {\n console.error(\n \"Error: --path must be a relative path under the current directory for in-repo plugins.\",\n );\n process.exit(1);\n }\n\n const name = opts.name as string;\n if (!NAME_PATTERN.test(name)) {\n console.error(\n \"Error: --name must be lowercase, start with a letter, and use only letters, numbers, and hyphens.\",\n );\n process.exit(1);\n }\n\n let resources: SelectedResource[] = [];\n if (opts.resourcesJson) {\n resources = parseResourcesJson(opts.resourcesJson);\n } else if (opts.resources) {\n resources = parseResourcesShorthand(opts.resources);\n }\n\n const answers: CreateAnswers = {\n placement,\n targetPath,\n name: name.trim(),\n displayName: opts.displayName?.trim() || deriveDisplayName(name),\n description: (opts.description as string).trim(),\n resources,\n version: DEFAULT_VERSION,\n };\n\n const targetDir = resolveTargetDir(process.cwd(), answers);\n const dirExists = fs.existsSync(targetDir);\n const hasContent = dirExists && fs.readdirSync(targetDir).length > 0;\n if (hasContent && !opts.force) {\n console.error(\n `Error: Directory ${answers.targetPath} already exists and is not empty.`,\n );\n console.error(\" Use --force to overwrite.\");\n process.exit(1);\n }\n\n scaffoldPlugin(targetDir, answers, { isolated: placement === \"isolated\" });\n\n console.log(\n `Plugin \"${answers.name}\" created at ${path.relative(process.cwd(), targetDir)}`,\n );\n printNextSteps(answers, targetDir);\n}\n\nasync function runInteractive(): Promise<void> {\n intro(\"Create a new AppKit plugin\");\n\n try {\n const placement = await select<Placement>({\n message: \"Where should the plugin live?\",\n options: [\n {\n value: \"in-repo\",\n label: \"In this repository (e.g. plugins/my-plugin)\",\n hint: \"folder path\",\n },\n {\n value: \"isolated\",\n label: \"New isolated package\",\n hint: \"full package with package.json\",\n },\n ],\n });\n if (isCancel(placement)) {\n cancel(\"Cancelled.\");\n process.exit(0);\n }\n\n const placementPrompt =\n placement === \"in-repo\"\n ? \"Folder path for the plugin (e.g. plugins/my-feature)\"\n : \"Directory name for the new package (e.g. appkit-plugin-my-feature)\";\n const targetPath = await text({\n message: placementPrompt,\n placeholder:\n placement === \"in-repo\"\n ? \"plugins/my-plugin\"\n : \"appkit-plugin-my-feature\",\n validate(value) {\n if (!value?.trim()) return \"Path is required.\";\n if (\n placement === \"in-repo\" &&\n (path.isAbsolute(value) || value.trim().startsWith(\"..\"))\n ) {\n return \"Use a relative path under the current directory (e.g. plugins/my-plugin).\";\n }\n return undefined;\n },\n });\n if (isCancel(targetPath)) {\n cancel(\"Cancelled.\");\n process.exit(0);\n }\n\n const name = await text({\n message: \"Plugin name (id)\",\n placeholder: \"my-plugin\",\n validate(value) {\n if (!value?.trim()) return \"Name is required.\";\n if (!NAME_PATTERN.test(value as string)) {\n return \"Must be lowercase, start with a letter, and use only letters, numbers, and hyphens.\";\n }\n return undefined;\n },\n });\n if (isCancel(name)) {\n cancel(\"Cancelled.\");\n process.exit(0);\n }\n\n const displayName = await text({\n message: \"Display name\",\n placeholder: \"My Plugin\",\n initialValue: name\n .split(\"-\")\n .map((s) => s.charAt(0).toUpperCase() + s.slice(1))\n .join(\" \"),\n validate(value) {\n if (!value?.trim()) return \"Display name is required.\";\n return undefined;\n },\n });\n if (isCancel(displayName)) {\n cancel(\"Cancelled.\");\n process.exit(0);\n }\n\n const description = await text({\n message: \"Short description\",\n placeholder: \"What does this plugin do?\",\n validate(value) {\n if (!value?.trim()) return \"Description is required.\";\n return undefined;\n },\n });\n if (isCancel(description)) {\n cancel(\"Cancelled.\");\n process.exit(0);\n }\n\n const 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 my-plugin --description \"Does X\"',\n );\n process.exit(1);\n }\n await runInteractive();\n }\n}\n\nexport const pluginCreateCommand = new Command(\"create\")\n .description(\"Scaffold a new AppKit plugin\")\n .option(\"--placement <type>\", \"Where the plugin lives (in-repo, isolated)\")\n .option(\"--path <dir>\", \"Target directory for the plugin\")\n .option(\"--name <id>\", \"Plugin name (lowercase, hyphens allowed)\")\n .option(\"--display-name <name>\", \"Human-readable display name\")\n .option(\"--description <text>\", \"Short description of the plugin\")\n .addOption(\n new Option(\n \"--resources <types>\",\n \"Comma-separated resource types (e.g. sql_warehouse,volume)\",\n ).conflicts(\"resourcesJson\"),\n )\n .addOption(\n new Option(\n \"--resources-json <json>\",\n 'JSON array of resource specs (e.g. \\'[{\"type\":\"sql_warehouse\"}]\\')',\n ).conflicts(\"resources\"),\n )\n .option(\"-f, --force\", \"Overwrite existing directory without confirmation\")\n .addHelpText(\n \"after\",\n `\nExamples:\n $ appkit plugin create\n $ appkit plugin create --placement in-repo --path plugins/my-plugin --name my-plugin --description \"Does X\"\n $ appkit plugin create --placement in-repo --path plugins/my-plugin --name my-plugin --description \"Does X\" --resources sql_warehouse,volume --force\n $ appkit plugin create --placement isolated --path appkit-plugin-ml --name ml --description \"ML\" --resources-json '[{\"type\":\"serving_endpoint\"}]'`,\n )\n .action((opts) =>\n runPluginCreate(opts).catch((err) => {\n console.error(err);\n process.exit(1);\n }),\n );\n\n/** Exported for testing. */\nexport { buildResourceFromType, parseResourcesJson, parseResourcesShorthand };\n"],"mappings":"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"}
|
|
@@ -47,6 +47,7 @@ function buildManifest(answers) {
|
|
|
47
47
|
optional
|
|
48
48
|
}
|
|
49
49
|
};
|
|
50
|
+
if (answers.stability) manifest.stability = answers.stability;
|
|
50
51
|
if (answers.author) manifest.author = answers.author;
|
|
51
52
|
manifest.version = answers.version || "0.1.0";
|
|
52
53
|
if (answers.license) manifest.license = answers.license;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scaffold.js","names":[],"sources":["../../../../../src/cli/commands/plugin/create/scaffold.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { humanizeResourceType, MANIFEST_SCHEMA_ID } from \"./resource-defaults\";\nimport type { CreateAnswers } from \"./types\";\n\n/** Convert kebab-name to PascalCase (e.g. my-plugin -> MyPlugin). */\nfunction toPascalCase(name: string): string {\n return name\n .split(\"-\")\n .map((s) => s.charAt(0).toUpperCase() + s.slice(1).toLowerCase())\n .join(\"\");\n}\n\n/** Convert kebab-name to camelCase (e.g. my-plugin -> myPlugin). */\nfunction toCamelCase(name: string): string {\n const pascal = toPascalCase(name);\n return pascal.charAt(0).toLowerCase() + pascal.slice(1);\n}\n\n/** Build manifest.json resources from selected resources. */\nfunction buildManifestResources(answers: CreateAnswers) {\n const required: unknown[] = [];\n const optional: unknown[] = [];\n\n for (const r of answers.resources) {\n const alias = humanizeResourceType(r.type);\n const entry = {\n type: r.type,\n alias,\n resourceKey: r.resourceKey,\n description: r.description || `Required for ${alias} functionality.`,\n permission: r.permission,\n fields: r.fields,\n };\n if (r.required) {\n required.push(entry);\n } else {\n optional.push(entry);\n }\n }\n\n return { required, optional };\n}\n\n/** Build full manifest object for manifest.json. */\nfunction buildManifest(answers: CreateAnswers): Record<string, unknown> {\n const { required, optional } = buildManifestResources(answers);\n const manifest: Record<string, unknown> = {\n $schema: MANIFEST_SCHEMA_ID,\n name: answers.name,\n displayName: answers.displayName,\n description: answers.description,\n resources: { required, optional },\n };\n if (answers.author) manifest.author = answers.author;\n manifest.version = answers.version || \"0.1.0\";\n if (answers.license) manifest.license = answers.license;\n return manifest;\n}\n\n/** Resolve absolute target directory from cwd and answers. */\nexport function resolveTargetDir(cwd: string, answers: CreateAnswers): string {\n return path.resolve(cwd, answers.targetPath);\n}\n\n/** Track files written during scaffolding for rollback on failure. */\nfunction writeTracked(\n filePath: string,\n content: string,\n written: string[],\n): void {\n fs.writeFileSync(filePath, content);\n written.push(filePath);\n}\n\n/** Remove files written during a failed scaffold attempt. */\nfunction rollback(written: string[], targetDir: string): void {\n for (const filePath of written.reverse()) {\n try {\n fs.unlinkSync(filePath);\n } catch {\n // best-effort cleanup\n }\n }\n try {\n const remaining = fs.readdirSync(targetDir);\n if (remaining.length === 0) fs.rmdirSync(targetDir);\n } catch {\n // directory may not be empty or may have been removed already\n }\n}\n\n/**\n * Scaffold plugin files into targetDir. Pure: no interactive I/O.\n * Writes manifest.json, {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\";\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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"}
|
|
@@ -2,6 +2,7 @@ import { pluginAddResourceCommand } from "./add-resource/add-resource.js";
|
|
|
2
2
|
import { pluginCreateCommand } from "./create/create.js";
|
|
3
3
|
import { pluginListCommand } from "./list/list.js";
|
|
4
4
|
import { pluginsSyncCommand } from "./sync/sync.js";
|
|
5
|
+
import { pluginPromoteCommand } from "./promote/promote.js";
|
|
5
6
|
import { pluginValidateCommand } from "./validate/validate.js";
|
|
6
7
|
import { Command } from "commander";
|
|
7
8
|
|
|
@@ -14,14 +15,16 @@ import { Command } from "commander";
|
|
|
14
15
|
* - validate: Validate manifest(s) against the JSON schema
|
|
15
16
|
* - list: List plugins from appkit.plugins.json or a directory
|
|
16
17
|
* - add-resource: Add a resource requirement to a plugin manifest (interactive)
|
|
18
|
+
* - promote: Promote a plugin to a higher stability tier
|
|
17
19
|
*/
|
|
18
|
-
const pluginCommand = new Command("plugin").description("Plugin management commands").addCommand(pluginsSyncCommand).addCommand(pluginCreateCommand).addCommand(pluginValidateCommand).addCommand(pluginListCommand).addCommand(pluginAddResourceCommand).addHelpText("after", `
|
|
20
|
+
const pluginCommand = new Command("plugin").description("Plugin management commands").addCommand(pluginsSyncCommand).addCommand(pluginCreateCommand).addCommand(pluginValidateCommand).addCommand(pluginListCommand).addCommand(pluginAddResourceCommand).addCommand(pluginPromoteCommand).addHelpText("after", `
|
|
19
21
|
Examples:
|
|
20
22
|
$ appkit plugin sync --write
|
|
21
23
|
$ appkit plugin create --placement in-repo --path plugins/my-plugin --name my-plugin --description "Does X"
|
|
22
24
|
$ appkit plugin validate .
|
|
23
25
|
$ appkit plugin list --json
|
|
24
|
-
$ appkit plugin add-resource --path plugins/my-plugin --type sql_warehouse
|
|
26
|
+
$ appkit plugin add-resource --path plugins/my-plugin --type sql_warehouse
|
|
27
|
+
$ appkit plugin promote my-plugin --to ga`);
|
|
25
28
|
|
|
26
29
|
//#endregion
|
|
27
30
|
export { pluginCommand };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../../src/cli/commands/plugin/index.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { pluginAddResourceCommand } from \"./add-resource/add-resource\";\nimport { pluginCreateCommand } from \"./create/create\";\nimport { pluginListCommand } from \"./list/list\";\nimport { pluginsSyncCommand } from \"./sync/sync\";\nimport { pluginValidateCommand } from \"./validate/validate\";\n\n/**\n * Parent command for plugin management operations.\n * Subcommands:\n * - sync: Aggregate plugin manifests into appkit.plugins.json\n * - create: Scaffold a new plugin (interactive)\n * - validate: Validate manifest(s) against the JSON schema\n * - list: List plugins from appkit.plugins.json or a directory\n * - add-resource: Add a resource requirement to a plugin manifest (interactive)\n */\nexport const pluginCommand = new Command(\"plugin\")\n .description(\"Plugin management commands\")\n .addCommand(pluginsSyncCommand)\n .addCommand(pluginCreateCommand)\n .addCommand(pluginValidateCommand)\n .addCommand(pluginListCommand)\n .addCommand(pluginAddResourceCommand)\n .addHelpText(\n \"after\",\n `\nExamples:\n $ appkit plugin sync --write\n $ appkit plugin create --placement in-repo --path plugins/my-plugin --name my-plugin --description \"Does X\"\n $ appkit plugin validate .\n $ appkit plugin list --json\n $ appkit plugin add-resource --path plugins/my-plugin --type sql_warehouse`,\n );\n"],"mappings":"
|
|
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"}
|
|
@@ -29,6 +29,7 @@ function listFromManifestFile(manifestPath) {
|
|
|
29
29
|
name: p.name,
|
|
30
30
|
displayName: p.displayName ?? p.name,
|
|
31
31
|
package: p.package ?? "",
|
|
32
|
+
stability: p.stability ?? "ga",
|
|
32
33
|
required: Array.isArray(p.resources?.required) ? p.resources.required.length : 0,
|
|
33
34
|
optional: Array.isArray(p.resources?.optional) ? p.resources.optional.length : 0
|
|
34
35
|
}));
|
|
@@ -48,10 +49,12 @@ async function collectPluginsRecursive(dir, cwd, rows, allowJsManifest, depth =
|
|
|
48
49
|
if (manifest) {
|
|
49
50
|
const relPath = path.relative(cwd, path.dirname(resolvedManifest.path));
|
|
50
51
|
const packagePath = relPath.startsWith(".") ? relPath : `./${relPath}`;
|
|
52
|
+
const rawManifest = manifest;
|
|
51
53
|
rows.push({
|
|
52
54
|
name: manifest.name,
|
|
53
55
|
displayName: manifest.displayName ?? manifest.name,
|
|
54
56
|
package: packagePath,
|
|
57
|
+
stability: rawManifest.stability ?? "ga",
|
|
55
58
|
required: Array.isArray(manifest.resources?.required) ? manifest.resources.required.length : 0,
|
|
56
59
|
optional: Array.isArray(manifest.resources?.optional) ? manifest.resources.optional.length : 0
|
|
57
60
|
});
|
|
@@ -77,9 +80,11 @@ function printTable(rows) {
|
|
|
77
80
|
const maxName = Math.max(4, ...rows.map((r) => r.name.length));
|
|
78
81
|
const maxDisplay = Math.max(10, ...rows.map((r) => r.displayName.length));
|
|
79
82
|
const maxPkg = Math.max(7, ...rows.map((r) => r.package.length));
|
|
83
|
+
const maxStab = Math.max(9, ...rows.map((r) => r.stability.length));
|
|
80
84
|
const header = [
|
|
81
85
|
"NAME".padEnd(maxName),
|
|
82
86
|
"DISPLAY NAME".padEnd(maxDisplay),
|
|
87
|
+
"STABILITY".padEnd(maxStab),
|
|
83
88
|
"PACKAGE / PATH".padEnd(maxPkg),
|
|
84
89
|
"REQ",
|
|
85
90
|
"OPT"
|
|
@@ -89,6 +94,7 @@ function printTable(rows) {
|
|
|
89
94
|
for (const r of rows) console.log([
|
|
90
95
|
r.name.padEnd(maxName),
|
|
91
96
|
r.displayName.padEnd(maxDisplay),
|
|
97
|
+
r.stability.padEnd(maxStab),
|
|
92
98
|
r.package.padEnd(maxPkg),
|
|
93
99
|
String(r.required).padStart(3),
|
|
94
100
|
String(r.optional).padStart(3)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list.js","names":[],"sources":["../../../../../src/cli/commands/plugin/list/list.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport process from \"node:process\";\nimport { Command } from \"commander\";\nimport {\n loadManifestFromFile,\n resolveManifestInDir,\n} from \"../manifest-resolve\";\nimport { shouldAllowJsManifestForDir } from \"../trusted-js-manifest\";\nimport { validateManifest } from \"../validate/validate-manifest\";\n\n/** Safety limit for recursive directory scanning to prevent runaway traversal. */\nconst MAX_SCAN_DEPTH = 5;\n\nexport interface PluginRow {\n name: string;\n displayName: string;\n package: string;\n required: number;\n optional: number;\n}\n\nexport function listFromManifestFile(manifestPath: string): PluginRow[] {\n let raw: string;\n try {\n raw = fs.readFileSync(manifestPath, \"utf-8\");\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`Failed to read manifest file ${manifestPath}: ${msg}`);\n }\n\n let data: {\n plugins?: Record<\n string,\n {\n name: string;\n displayName: string;\n package: string;\n resources: { required: unknown[]; optional: unknown[] };\n }\n >;\n };\n try {\n data = JSON.parse(raw) as typeof data;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`Failed to parse manifest file ${manifestPath}: ${msg}`);\n }\n\n const plugins = data.plugins ?? {};\n return Object.values(plugins).map((p) => ({\n name: p.name,\n displayName: p.displayName ?? p.name,\n package: p.package ?? \"\",\n required: Array.isArray(p.resources?.required)\n ? p.resources.required.length\n : 0,\n optional: Array.isArray(p.resources?.optional)\n ? p.resources.optional.length\n : 0,\n }));\n}\n\nasync function collectPluginsRecursive(\n dir: string,\n cwd: string,\n rows: PluginRow[],\n allowJsManifest: boolean,\n depth = 0,\n): Promise<void> {\n if (\n !fs.existsSync(dir) ||\n !fs.statSync(dir).isDirectory() ||\n depth >= MAX_SCAN_DEPTH\n )\n return;\n\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n\n const childPath = path.join(dir, entry.name);\n const allowJsForChild =\n allowJsManifest || shouldAllowJsManifestForDir(childPath);\n const resolvedManifest = resolveManifestInDir(childPath, {\n allowJsManifest: allowJsForChild,\n });\n\n if (resolvedManifest) {\n try {\n const obj = await loadManifestFromFile(\n resolvedManifest.path,\n resolvedManifest.type,\n { allowJsManifest: allowJsForChild },\n );\n const result = validateManifest(obj);\n const manifest = result.valid ? result.manifest : null;\n if (manifest) {\n const relPath = path.relative(\n cwd,\n path.dirname(resolvedManifest.path),\n );\n const packagePath = relPath.startsWith(\".\")\n ? relPath\n : `./${relPath}`;\n rows.push({\n name: manifest.name,\n displayName: manifest.displayName ?? manifest.name,\n package: packagePath,\n required: Array.isArray(manifest.resources?.required)\n ? manifest.resources.required.length\n : 0,\n optional: Array.isArray(manifest.resources?.optional)\n ? manifest.resources.optional.length\n : 0,\n });\n }\n } catch {\n // skip invalid manifests\n }\n continue;\n }\n\n await collectPluginsRecursive(\n childPath,\n cwd,\n rows,\n allowJsManifest,\n depth + 1,\n );\n }\n}\n\nexport async function listFromDirectory(\n dirPath: string,\n cwd: string,\n allowJsManifest = false,\n): Promise<PluginRow[]> {\n const resolved = path.resolve(cwd, dirPath);\n if (!fs.existsSync(resolved) || !fs.statSync(resolved).isDirectory()) {\n return [];\n }\n const rows: PluginRow[] = [];\n await collectPluginsRecursive(resolved, cwd, rows, allowJsManifest);\n return rows;\n}\n\nfunction printTable(rows: PluginRow[]): void {\n if (rows.length === 0) {\n console.log(\"No plugins found.\");\n return;\n }\n const maxName = Math.max(4, ...rows.map((r) => r.name.length));\n const maxDisplay = Math.max(10, ...rows.map((r) => r.displayName.length));\n const maxPkg = Math.max(7, ...rows.map((r) => r.package.length));\n const header = [\n \"NAME\".padEnd(maxName),\n \"DISPLAY NAME\".padEnd(maxDisplay),\n \"PACKAGE / PATH\".padEnd(maxPkg),\n \"REQ\",\n \"OPT\",\n ].join(\" \");\n console.log(header);\n console.log(\"-\".repeat(header.length));\n for (const r of rows) {\n console.log(\n [\n r.name.padEnd(maxName),\n r.displayName.padEnd(maxDisplay),\n r.package.padEnd(maxPkg),\n String(r.required).padStart(3),\n String(r.optional).padStart(3),\n ].join(\" \"),\n );\n }\n}\n\nasync function runPluginList(options: {\n manifest?: string;\n dir?: string;\n json?: boolean;\n allowJsManifest?: boolean;\n}): Promise<void> {\n const cwd = process.cwd();\n const allowJsManifest = Boolean(options.allowJsManifest);\n if (allowJsManifest) {\n console.warn(\n \"Warning: --allow-js-manifest executes manifest.js/manifest.cjs files. Only use with trusted code.\",\n );\n }\n let rows: PluginRow[];\n\n if (options.dir !== undefined) {\n rows = await listFromDirectory(options.dir, cwd, allowJsManifest);\n if (rows.length === 0 && options.dir) {\n console.error(\n `No plugin directories with ${allowJsManifest ? \"manifest.json or manifest.js\" : \"manifest.json\"} found in ${options.dir}`,\n );\n process.exit(1);\n }\n } else {\n const manifestPath = path.resolve(\n cwd,\n options.manifest ?? \"appkit.plugins.json\",\n );\n if (!fs.existsSync(manifestPath)) {\n console.error(`Manifest not found: ${manifestPath}`);\n 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\";\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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"}
|