@databricks/appkit-ui 0.8.0 → 0.10.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 +3 -1
- package/NOTICE.md +1 -0
- package/dist/cli/commands/plugin/add-resource/add-resource.js +61 -0
- package/dist/cli/commands/plugin/add-resource/add-resource.js.map +1 -0
- package/dist/cli/commands/plugin/create/create.js +162 -0
- package/dist/cli/commands/plugin/create/create.js.map +1 -0
- package/dist/cli/commands/plugin/create/prompt-resource.js +95 -0
- package/dist/cli/commands/plugin/create/prompt-resource.js.map +1 -0
- package/dist/cli/commands/plugin/create/resource-defaults.js +105 -0
- package/dist/cli/commands/plugin/create/resource-defaults.js.map +1 -0
- package/dist/cli/commands/plugin/create/scaffold.js +195 -0
- package/dist/cli/commands/plugin/create/scaffold.js.map +1 -0
- package/dist/cli/commands/plugin/index.js +22 -0
- package/dist/cli/commands/plugin/index.js.map +1 -0
- package/dist/cli/commands/plugin/list/list.js +113 -0
- package/dist/cli/commands/plugin/list/list.js.map +1 -0
- package/dist/cli/commands/plugin/schema-resources.js +82 -0
- package/dist/cli/commands/plugin/schema-resources.js.map +1 -0
- package/dist/cli/commands/{plugins-sync.js → plugin/sync/sync.js} +98 -79
- package/dist/cli/commands/plugin/sync/sync.js.map +1 -0
- package/dist/cli/commands/plugin/validate/validate-manifest.js +216 -0
- package/dist/cli/commands/plugin/validate/validate-manifest.js.map +1 -0
- package/dist/cli/commands/plugin/validate/validate.js +67 -0
- package/dist/cli/commands/plugin/validate/validate.js.map +1 -0
- package/dist/cli/index.js +2 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/react/charts/area/index.d.ts +2 -2
- package/dist/react/charts/bar/index.d.ts +2 -2
- package/dist/react/charts/base.d.ts +2 -2
- package/dist/react/charts/base.d.ts.map +1 -1
- package/dist/react/charts/create-chart.d.ts +2 -2
- package/dist/react/charts/create-chart.d.ts.map +1 -1
- package/dist/react/charts/heatmap/index.d.ts +2 -2
- package/dist/react/charts/line/index.d.ts +2 -2
- package/dist/react/charts/options.d.ts.map +1 -1
- package/dist/react/charts/pie/index.d.ts +3 -3
- package/dist/react/charts/radar/index.d.ts +2 -2
- package/dist/react/charts/scatter/index.d.ts +2 -2
- package/dist/react/charts/wrapper.d.ts +2 -2
- package/dist/react/charts/wrapper.d.ts.map +1 -1
- package/dist/react/table/data-table.d.ts +2 -2
- package/dist/react/table/data-table.d.ts.map +1 -1
- package/dist/react/ui/accordion.d.ts +5 -5
- package/dist/react/ui/accordion.d.ts.map +1 -1
- package/dist/react/ui/alert-dialog.d.ts +12 -12
- package/dist/react/ui/alert.d.ts +4 -4
- package/dist/react/ui/alert.d.ts.map +1 -1
- package/dist/react/ui/aspect-ratio.d.ts +2 -2
- package/dist/react/ui/avatar.d.ts +4 -4
- package/dist/react/ui/badge.d.ts +2 -2
- package/dist/react/ui/breadcrumb.d.ts +8 -8
- package/dist/react/ui/button-group.d.ts +4 -4
- package/dist/react/ui/button.d.ts +2 -2
- package/dist/react/ui/calendar.d.ts +3 -3
- package/dist/react/ui/card.d.ts +8 -8
- package/dist/react/ui/carousel.d.ts +6 -6
- package/dist/react/ui/chart.d.ts +5 -5
- package/dist/react/ui/checkbox.d.ts +2 -2
- package/dist/react/ui/collapsible.d.ts +4 -4
- package/dist/react/ui/command.d.ts +10 -10
- package/dist/react/ui/context-menu.d.ts +16 -16
- package/dist/react/ui/dialog.d.ts +11 -11
- package/dist/react/ui/drawer.d.ts +11 -11
- package/dist/react/ui/drawer.d.ts.map +1 -1
- package/dist/react/ui/dropdown-menu.d.ts +16 -16
- package/dist/react/ui/empty.d.ts +7 -7
- package/dist/react/ui/field.d.ts +11 -11
- package/dist/react/ui/form.d.ts +7 -7
- package/dist/react/ui/hover-card.d.ts +4 -4
- package/dist/react/ui/input-group.d.ts +7 -7
- package/dist/react/ui/input-otp.d.ts +5 -5
- package/dist/react/ui/input.d.ts +2 -2
- package/dist/react/ui/item.d.ts +11 -11
- package/dist/react/ui/kbd.d.ts +3 -3
- package/dist/react/ui/label.d.ts +2 -2
- package/dist/react/ui/menubar.d.ts +17 -17
- package/dist/react/ui/navigation-menu.d.ts +9 -9
- package/dist/react/ui/pagination.d.ts +8 -8
- package/dist/react/ui/popover.d.ts +5 -5
- package/dist/react/ui/progress.d.ts +2 -2
- package/dist/react/ui/radio-group.d.ts +3 -3
- package/dist/react/ui/resizable.d.ts +4 -4
- package/dist/react/ui/scroll-area.d.ts +3 -3
- package/dist/react/ui/select.d.ts +11 -11
- package/dist/react/ui/separator.d.ts +2 -2
- package/dist/react/ui/sheet.d.ts +9 -9
- package/dist/react/ui/sidebar.d.ts +24 -24
- package/dist/react/ui/skeleton.d.ts +2 -2
- package/dist/react/ui/slider.d.ts +2 -2
- package/dist/react/ui/sonner.d.ts +2 -2
- package/dist/react/ui/spinner.d.ts +2 -2
- package/dist/react/ui/switch.d.ts +2 -2
- package/dist/react/ui/table.d.ts +9 -9
- package/dist/react/ui/tabs.d.ts +5 -5
- package/dist/react/ui/textarea.d.ts +2 -2
- package/dist/react/ui/toggle-group.d.ts +3 -3
- package/dist/react/ui/toggle.d.ts +2 -2
- package/dist/react/ui/tooltip.d.ts +5 -5
- package/dist/react/ui/tooltip.d.ts.map +1 -1
- package/dist/schemas/plugin-manifest.schema.json +439 -0
- package/dist/schemas/template-plugins.schema.json +103 -0
- package/docs/docs/api/appkit/Class.AppKitError/index.html +3 -3
- package/docs/docs/api/appkit/Class.AuthenticationError/index.html +3 -3
- package/docs/docs/api/appkit/Class.ConfigurationError/index.html +3 -3
- package/docs/docs/api/appkit/Class.ConnectionError/index.html +3 -3
- package/docs/docs/api/appkit/Class.ExecutionError/index.html +3 -3
- package/docs/docs/api/appkit/Class.InitializationError/index.html +3 -3
- package/docs/docs/api/appkit/Class.Plugin/index.html +3 -3
- package/docs/docs/api/appkit/Class.ResourceRegistry/index.html +3 -3
- package/docs/docs/api/appkit/Class.ServerError/index.html +3 -3
- package/docs/docs/api/appkit/Class.TunnelError/index.html +3 -3
- package/docs/docs/api/appkit/Class.ValidationError/index.html +3 -3
- package/docs/docs/api/appkit/Enumeration.RequestedClaimsPermissionSet/index.html +3 -3
- package/docs/docs/api/appkit/Enumeration.ResourceType/index.html +6 -19
- package/docs/docs/api/appkit/Enumeration.ResourceType.md +1 -25
- package/docs/docs/api/appkit/Function.appKitTypesPlugin/index.html +3 -3
- package/docs/docs/api/appkit/Function.createApp/index.html +3 -3
- package/docs/docs/api/appkit/Function.createLakebasePool/index.html +3 -3
- package/docs/docs/api/appkit/Function.generateDatabaseCredential/index.html +3 -3
- package/docs/docs/api/appkit/Function.getExecutionContext/index.html +3 -3
- package/docs/docs/api/appkit/Function.getLakebaseOrmConfig/index.html +3 -3
- package/docs/docs/api/appkit/Function.getLakebasePgConfig/index.html +3 -3
- package/docs/docs/api/appkit/Function.getPluginManifest/index.html +3 -3
- package/docs/docs/api/appkit/Function.getResourceRequirements/index.html +4 -4
- package/docs/docs/api/appkit/Function.getUsernameWithApiLookup/index.html +35 -0
- package/docs/docs/api/appkit/Function.getUsernameWithApiLookup.md +29 -0
- package/docs/docs/api/appkit/Function.getWorkspaceClient/index.html +5 -5
- package/docs/docs/api/appkit/Function.getWorkspaceClient.md +2 -2
- package/docs/docs/api/appkit/Function.isSQLTypeMarker/index.html +3 -3
- package/docs/docs/api/appkit/Interface.BasePluginConfig/index.html +3 -3
- package/docs/docs/api/appkit/Interface.CacheConfig/index.html +3 -3
- package/docs/docs/api/appkit/Interface.DatabaseCredential/index.html +3 -3
- package/docs/docs/api/appkit/Interface.GenerateDatabaseCredentialRequest/index.html +3 -3
- package/docs/docs/api/appkit/Interface.ITelemetry/index.html +3 -3
- package/docs/docs/api/appkit/Interface.LakebasePoolConfig/index.html +3 -3
- package/docs/docs/api/appkit/Interface.PluginManifest/index.html +3 -3
- package/docs/docs/api/appkit/Interface.RequestedClaims/index.html +3 -3
- package/docs/docs/api/appkit/Interface.RequestedResource/index.html +3 -3
- package/docs/docs/api/appkit/Interface.ResourceEntry/index.html +3 -3
- package/docs/docs/api/appkit/Interface.ResourceFieldEntry/index.html +3 -3
- package/docs/docs/api/appkit/Interface.ResourceRequirement/index.html +3 -3
- package/docs/docs/api/appkit/Interface.StreamExecutionSettings/index.html +3 -3
- package/docs/docs/api/appkit/Interface.TelemetryConfig/index.html +3 -3
- package/docs/docs/api/appkit/Interface.ValidationResult/index.html +3 -3
- package/docs/docs/api/appkit/TypeAlias.ConfigSchema/index.html +3 -3
- package/docs/docs/api/appkit/TypeAlias.IAppRouter/index.html +3 -3
- package/docs/docs/api/appkit/TypeAlias.ResourcePermission/index.html +4 -4
- package/docs/docs/api/appkit/TypeAlias.ToPlugin/index.html +23 -0
- package/docs/docs/api/appkit/TypeAlias.ToPlugin.md +24 -0
- package/docs/docs/api/appkit/Variable.sql/index.html +4 -4
- package/docs/docs/api/appkit/index.html +6 -6
- package/docs/docs/api/appkit-ui/data/AreaChart/index.html +2 -2
- package/docs/docs/api/appkit-ui/data/BarChart/index.html +2 -2
- package/docs/docs/api/appkit-ui/data/DataTable/index.html +2 -2
- package/docs/docs/api/appkit-ui/data/DonutChart/index.html +2 -2
- package/docs/docs/api/appkit-ui/data/HeatmapChart/index.html +2 -2
- package/docs/docs/api/appkit-ui/data/LineChart/index.html +2 -2
- package/docs/docs/api/appkit-ui/data/PieChart/index.html +2 -2
- package/docs/docs/api/appkit-ui/data/RadarChart/index.html +2 -2
- package/docs/docs/api/appkit-ui/data/ScatterChart/index.html +2 -2
- package/docs/docs/api/appkit-ui/index.html +2 -2
- package/docs/docs/api/appkit-ui/styling/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/Accordion/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/Alert/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/AlertDialog/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/AspectRatio/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/Avatar/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/Badge/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/Breadcrumb/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/Button/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/ButtonGroup/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/Calendar/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/Card/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/Carousel/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/ChartContainer/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/Checkbox/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/Collapsible/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/Command/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/ContextMenu/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/Dialog/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/Drawer/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/DropdownMenu/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/Empty/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/Field/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/FormControl/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/HoverCard/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/Input/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/InputGroup/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/InputOTP/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/Item/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/Kbd/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/Label/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/Menubar/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/NavigationMenu/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/Pagination/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/Popover/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/Progress/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/RadioGroup/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/ResizableHandle/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/ScrollArea/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/Select/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/Separator/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/Sheet/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/Sidebar/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/Skeleton/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/Slider/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/Spinner/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/Switch/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/Table/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/Tabs/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/Textarea/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/Toaster/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/Toggle/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/ToggleGroup/index.html +2 -2
- package/docs/docs/api/appkit-ui/ui/Tooltip/index.html +2 -2
- package/docs/docs/api/appkit.md +6 -4
- package/docs/docs/api/index.html +2 -2
- package/docs/docs/app-management/index.html +2 -2
- package/docs/docs/architecture/index.html +2 -2
- package/docs/docs/category/development/index.html +2 -2
- package/docs/docs/configuration/index.html +2 -2
- package/docs/docs/core-principles/index.html +2 -2
- package/docs/docs/development/ai-assisted-development/index.html +2 -2
- package/docs/docs/development/index.html +2 -2
- package/docs/docs/development/llm-guide/index.html +2 -2
- package/docs/docs/development/local-development/index.html +2 -2
- package/docs/docs/development/project-setup/index.html +2 -2
- package/docs/docs/development/remote-bridge/index.html +2 -2
- package/docs/docs/development/type-generation/index.html +2 -2
- package/docs/docs/index.html +2 -2
- package/docs/docs/plugins/index.html +35 -4
- package/docs/docs/plugins.md +97 -1
- package/llms.txt +3 -1
- package/package.json +2 -1
- package/dist/cli/commands/plugins-sync.js.map +0 -1
- package/dist/cli/commands/plugins.js +0 -19
- package/dist/cli/commands/plugins.js.map +0 -1
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { MANIFEST_SCHEMA_ID, humanizeResourceType } from "./resource-defaults.js";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
//#region src/cli/commands/plugin/create/scaffold.ts
|
|
6
|
+
/** Convert kebab-name to PascalCase (e.g. my-plugin -> MyPlugin). */
|
|
7
|
+
function toPascalCase(name) {
|
|
8
|
+
return name.split("-").map((s) => s.charAt(0).toUpperCase() + s.slice(1).toLowerCase()).join("");
|
|
9
|
+
}
|
|
10
|
+
/** Convert kebab-name to camelCase (e.g. my-plugin -> myPlugin). */
|
|
11
|
+
function toCamelCase(name) {
|
|
12
|
+
const pascal = toPascalCase(name);
|
|
13
|
+
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
14
|
+
}
|
|
15
|
+
/** Build manifest.json resources from selected resources. */
|
|
16
|
+
function buildManifestResources(answers) {
|
|
17
|
+
const required = [];
|
|
18
|
+
const optional = [];
|
|
19
|
+
for (const r of answers.resources) {
|
|
20
|
+
const alias = humanizeResourceType(r.type);
|
|
21
|
+
const entry = {
|
|
22
|
+
type: r.type,
|
|
23
|
+
alias,
|
|
24
|
+
resourceKey: r.resourceKey,
|
|
25
|
+
description: r.description || `Required for ${alias} functionality.`,
|
|
26
|
+
permission: r.permission,
|
|
27
|
+
fields: r.fields
|
|
28
|
+
};
|
|
29
|
+
if (r.required) required.push(entry);
|
|
30
|
+
else optional.push(entry);
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
required,
|
|
34
|
+
optional
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/** Build full manifest object for manifest.json. */
|
|
38
|
+
function buildManifest(answers) {
|
|
39
|
+
const { required, optional } = buildManifestResources(answers);
|
|
40
|
+
const manifest = {
|
|
41
|
+
$schema: MANIFEST_SCHEMA_ID,
|
|
42
|
+
name: answers.name,
|
|
43
|
+
displayName: answers.displayName,
|
|
44
|
+
description: answers.description,
|
|
45
|
+
resources: {
|
|
46
|
+
required,
|
|
47
|
+
optional
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
if (answers.author) manifest.author = answers.author;
|
|
51
|
+
manifest.version = answers.version || "0.1.0";
|
|
52
|
+
if (answers.license) manifest.license = answers.license;
|
|
53
|
+
return manifest;
|
|
54
|
+
}
|
|
55
|
+
/** Resolve absolute target directory from cwd and answers. */
|
|
56
|
+
function resolveTargetDir(cwd, answers) {
|
|
57
|
+
return path.resolve(cwd, answers.targetPath);
|
|
58
|
+
}
|
|
59
|
+
/** Track files written during scaffolding for rollback on failure. */
|
|
60
|
+
function writeTracked(filePath, content, written) {
|
|
61
|
+
fs.writeFileSync(filePath, content);
|
|
62
|
+
written.push(filePath);
|
|
63
|
+
}
|
|
64
|
+
/** Remove files written during a failed scaffold attempt. */
|
|
65
|
+
function rollback(written, targetDir) {
|
|
66
|
+
for (const filePath of written.reverse()) try {
|
|
67
|
+
fs.unlinkSync(filePath);
|
|
68
|
+
} catch {}
|
|
69
|
+
try {
|
|
70
|
+
if (fs.readdirSync(targetDir).length === 0) fs.rmdirSync(targetDir);
|
|
71
|
+
} catch {}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Scaffold plugin files into targetDir. Pure: no interactive I/O.
|
|
75
|
+
* Writes manifest.json, manifest.ts, {name}.ts, index.ts; for isolated also package.json, tsconfig.json, README.md.
|
|
76
|
+
* On failure, rolls back any files already written.
|
|
77
|
+
*/
|
|
78
|
+
function scaffoldPlugin(targetDir, answers, options) {
|
|
79
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
80
|
+
const written = [];
|
|
81
|
+
try {
|
|
82
|
+
const manifest = buildManifest(answers);
|
|
83
|
+
const className = toPascalCase(answers.name);
|
|
84
|
+
const exportName = toCamelCase(answers.name);
|
|
85
|
+
writeTracked(path.join(targetDir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`, written);
|
|
86
|
+
writeTracked(path.join(targetDir, "manifest.ts"), `import { readFileSync } from "node:fs";
|
|
87
|
+
import { dirname, join } from "node:path";
|
|
88
|
+
import { fileURLToPath } from "node:url";
|
|
89
|
+
import type { PluginManifest } from "@databricks/appkit";
|
|
90
|
+
|
|
91
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
92
|
+
|
|
93
|
+
export const manifest = JSON.parse(
|
|
94
|
+
readFileSync(join(__dirname, "manifest.json"), "utf-8"),
|
|
95
|
+
) as PluginManifest;
|
|
96
|
+
`, written);
|
|
97
|
+
const pluginTs = `import { Plugin, toPlugin, type IAppRouter } from "@databricks/appkit";
|
|
98
|
+
import { manifest } from "./manifest.js";
|
|
99
|
+
|
|
100
|
+
export class ${className} extends Plugin {
|
|
101
|
+
name = "${answers.name}";
|
|
102
|
+
|
|
103
|
+
static manifest = manifest;
|
|
104
|
+
|
|
105
|
+
injectRoutes(router: IAppRouter): void {
|
|
106
|
+
// Add your routes here, e.g.:
|
|
107
|
+
// this.route(router, {
|
|
108
|
+
// name: "example",
|
|
109
|
+
// method: "get",
|
|
110
|
+
// path: "/",
|
|
111
|
+
// handler: async (_req, res) => {
|
|
112
|
+
// res.json({ message: "Hello from ${answers.name}" });
|
|
113
|
+
// },
|
|
114
|
+
// });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export const ${exportName} = toPlugin<
|
|
119
|
+
typeof ${className},
|
|
120
|
+
Record<string, never>,
|
|
121
|
+
"${answers.name}"
|
|
122
|
+
>(${className}, "${answers.name}");
|
|
123
|
+
`;
|
|
124
|
+
writeTracked(path.join(targetDir, `${answers.name}.ts`), pluginTs, written);
|
|
125
|
+
const indexTs = `export { ${className}, ${exportName} } from "./${answers.name}.js";
|
|
126
|
+
`;
|
|
127
|
+
writeTracked(path.join(targetDir, "index.ts"), indexTs, written);
|
|
128
|
+
if (options.isolated) {
|
|
129
|
+
const packageName = answers.name.includes("/") || answers.name.startsWith("@") ? answers.name : `appkit-plugin-${answers.name}`;
|
|
130
|
+
const packageJson = {
|
|
131
|
+
name: packageName,
|
|
132
|
+
version: answers.version || "0.1.0",
|
|
133
|
+
type: "module",
|
|
134
|
+
main: "./dist/index.js",
|
|
135
|
+
types: "./dist/index.d.ts",
|
|
136
|
+
files: ["dist"],
|
|
137
|
+
scripts: {
|
|
138
|
+
build: "tsc",
|
|
139
|
+
typecheck: "tsc --noEmit"
|
|
140
|
+
},
|
|
141
|
+
peerDependencies: { "@databricks/appkit": ">=0.5.0" },
|
|
142
|
+
devDependencies: { typescript: "^5.0.0" }
|
|
143
|
+
};
|
|
144
|
+
writeTracked(path.join(targetDir, "package.json"), `${JSON.stringify(packageJson, null, 2)}\n`, written);
|
|
145
|
+
writeTracked(path.join(targetDir, "tsconfig.json"), `${JSON.stringify({
|
|
146
|
+
compilerOptions: {
|
|
147
|
+
target: "ES2022",
|
|
148
|
+
module: "NodeNext",
|
|
149
|
+
moduleResolution: "NodeNext",
|
|
150
|
+
outDir: "dist",
|
|
151
|
+
rootDir: ".",
|
|
152
|
+
declaration: true,
|
|
153
|
+
strict: true,
|
|
154
|
+
skipLibCheck: true
|
|
155
|
+
},
|
|
156
|
+
include: ["*.ts"],
|
|
157
|
+
exclude: ["node_modules", "dist"]
|
|
158
|
+
}, null, 2)}\n`, written);
|
|
159
|
+
const readme = `# ${answers.displayName}
|
|
160
|
+
|
|
161
|
+
${answers.description}
|
|
162
|
+
|
|
163
|
+
## Installation
|
|
164
|
+
|
|
165
|
+
\`\`\`bash
|
|
166
|
+
pnpm add ${packageName} @databricks/appkit
|
|
167
|
+
\`\`\`
|
|
168
|
+
|
|
169
|
+
## Usage
|
|
170
|
+
|
|
171
|
+
Register the plugin in your AppKit app:
|
|
172
|
+
|
|
173
|
+
\`\`\`ts
|
|
174
|
+
import { createApp } from "@databricks/appkit";
|
|
175
|
+
import { ${exportName} } from "${packageName}";
|
|
176
|
+
|
|
177
|
+
createApp({
|
|
178
|
+
plugins: [
|
|
179
|
+
${exportName}(),
|
|
180
|
+
// ... other plugins
|
|
181
|
+
],
|
|
182
|
+
}).then((app) => { /* ... */ });
|
|
183
|
+
\`\`\`
|
|
184
|
+
`;
|
|
185
|
+
writeTracked(path.join(targetDir, "README.md"), readme, written);
|
|
186
|
+
}
|
|
187
|
+
} catch (err) {
|
|
188
|
+
rollback(written, targetDir);
|
|
189
|
+
throw err;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
//#endregion
|
|
194
|
+
export { resolveTargetDir, scaffoldPlugin };
|
|
195
|
+
//# sourceMappingURL=scaffold.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scaffold.js","names":[],"sources":["../../../../../src/cli/commands/plugin/create/scaffold.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { humanizeResourceType, MANIFEST_SCHEMA_ID } from \"./resource-defaults\";\nimport type { CreateAnswers } from \"./types\";\n\n/** Convert kebab-name to PascalCase (e.g. my-plugin -> MyPlugin). */\nfunction toPascalCase(name: string): string {\n return name\n .split(\"-\")\n .map((s) => s.charAt(0).toUpperCase() + s.slice(1).toLowerCase())\n .join(\"\");\n}\n\n/** Convert kebab-name to camelCase (e.g. my-plugin -> myPlugin). */\nfunction toCamelCase(name: string): string {\n const pascal = toPascalCase(name);\n return pascal.charAt(0).toLowerCase() + pascal.slice(1);\n}\n\n/** Build manifest.json resources from selected resources. */\nfunction buildManifestResources(answers: CreateAnswers) {\n const required: unknown[] = [];\n const optional: unknown[] = [];\n\n for (const r of answers.resources) {\n const alias = humanizeResourceType(r.type);\n const entry = {\n type: r.type,\n alias,\n resourceKey: r.resourceKey,\n description: r.description || `Required for ${alias} functionality.`,\n permission: r.permission,\n fields: r.fields,\n };\n if (r.required) {\n required.push(entry);\n } else {\n optional.push(entry);\n }\n }\n\n return { required, optional };\n}\n\n/** Build full manifest object for manifest.json. */\nfunction buildManifest(answers: CreateAnswers): Record<string, unknown> {\n const { required, optional } = buildManifestResources(answers);\n const manifest: Record<string, unknown> = {\n $schema: MANIFEST_SCHEMA_ID,\n name: answers.name,\n displayName: answers.displayName,\n description: answers.description,\n resources: { required, optional },\n };\n if (answers.author) manifest.author = answers.author;\n manifest.version = answers.version || \"0.1.0\";\n if (answers.license) manifest.license = answers.license;\n return manifest;\n}\n\n/** Resolve absolute target directory from cwd and answers. */\nexport function resolveTargetDir(cwd: string, answers: CreateAnswers): string {\n return path.resolve(cwd, answers.targetPath);\n}\n\n/** Track files written during scaffolding for rollback on failure. */\nfunction writeTracked(\n filePath: string,\n content: string,\n written: string[],\n): void {\n fs.writeFileSync(filePath, content);\n written.push(filePath);\n}\n\n/** Remove files written during a failed scaffold attempt. */\nfunction rollback(written: string[], targetDir: string): void {\n for (const filePath of written.reverse()) {\n try {\n fs.unlinkSync(filePath);\n } catch {\n // best-effort cleanup\n }\n }\n try {\n const remaining = fs.readdirSync(targetDir);\n if (remaining.length === 0) fs.rmdirSync(targetDir);\n } catch {\n // directory may not be empty or may have been removed already\n }\n}\n\n/**\n * Scaffold plugin files into targetDir. Pure: no interactive I/O.\n * Writes manifest.json, manifest.ts, {name}.ts, index.ts; for isolated also package.json, tsconfig.json, README.md.\n * On failure, rolls back any files already written.\n */\nexport function scaffoldPlugin(\n targetDir: string,\n answers: CreateAnswers,\n options: { isolated: boolean },\n): void {\n fs.mkdirSync(targetDir, { recursive: true });\n\n const written: string[] = [];\n\n try {\n const manifest = buildManifest(answers);\n const className = toPascalCase(answers.name);\n const exportName = toCamelCase(answers.name);\n\n writeTracked(\n path.join(targetDir, \"manifest.json\"),\n `${JSON.stringify(manifest, null, 2)}\\n`,\n written,\n );\n\n const manifestTs = `import { readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type { PluginManifest } from \"@databricks/appkit\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nexport const manifest = JSON.parse(\n readFileSync(join(__dirname, \"manifest.json\"), \"utf-8\"),\n) as PluginManifest;\n`;\n\n writeTracked(path.join(targetDir, \"manifest.ts\"), manifestTs, written);\n\n const pluginTs = `import { Plugin, toPlugin, type IAppRouter } from \"@databricks/appkit\";\nimport { manifest } from \"./manifest.js\";\n\nexport class ${className} extends Plugin {\n name = \"${answers.name}\";\n\n static manifest = manifest;\n\n injectRoutes(router: IAppRouter): void {\n // Add your routes here, e.g.:\n // this.route(router, {\n // name: \"example\",\n // method: \"get\",\n // path: \"/\",\n // handler: async (_req, res) => {\n // res.json({ message: \"Hello from ${answers.name}\" });\n // },\n // });\n }\n}\n\nexport const ${exportName} = toPlugin<\n typeof ${className},\n Record<string, never>,\n \"${answers.name}\"\n>(${className}, \"${answers.name}\");\n`;\n\n writeTracked(path.join(targetDir, `${answers.name}.ts`), pluginTs, written);\n\n const indexTs = `export { ${className}, ${exportName} } from \"./${answers.name}.js\";\n`;\n\n writeTracked(path.join(targetDir, \"index.ts\"), indexTs, written);\n\n if (options.isolated) {\n const packageName =\n answers.name.includes(\"/\") || answers.name.startsWith(\"@\")\n ? answers.name\n : `appkit-plugin-${answers.name}`;\n\n const packageJson = {\n name: packageName,\n version: answers.version || \"0.1.0\",\n type: \"module\",\n main: \"./dist/index.js\",\n types: \"./dist/index.d.ts\",\n files: [\"dist\"],\n scripts: {\n build: \"tsc\",\n typecheck: \"tsc --noEmit\",\n },\n peerDependencies: {\n \"@databricks/appkit\": \">=0.5.0\",\n },\n devDependencies: {\n typescript: \"^5.0.0\",\n },\n };\n\n writeTracked(\n path.join(targetDir, \"package.json\"),\n `${JSON.stringify(packageJson, null, 2)}\\n`,\n written,\n );\n\n const tsconfigJson = {\n compilerOptions: {\n target: \"ES2022\",\n module: \"NodeNext\",\n moduleResolution: \"NodeNext\",\n outDir: \"dist\",\n rootDir: \".\",\n declaration: true,\n strict: true,\n skipLibCheck: true,\n },\n include: [\"*.ts\"],\n exclude: [\"node_modules\", \"dist\"],\n };\n\n writeTracked(\n path.join(targetDir, \"tsconfig.json\"),\n `${JSON.stringify(tsconfigJson, null, 2)}\\n`,\n written,\n );\n\n const readme = `# ${answers.displayName}\n\n${answers.description}\n\n## Installation\n\n\\`\\`\\`bash\npnpm add ${packageName} @databricks/appkit\n\\`\\`\\`\n\n## Usage\n\nRegister the plugin in your AppKit app:\n\n\\`\\`\\`ts\nimport { createApp } from \"@databricks/appkit\";\nimport { ${exportName} } from \"${packageName}\";\n\ncreateApp({\n plugins: [\n ${exportName}(),\n // ... other plugins\n ],\n}).then((app) => { /* ... */ });\n\\`\\`\\`\n`;\n\n writeTracked(path.join(targetDir, \"README.md\"), readme, written);\n }\n } catch (err) {\n rollback(written, targetDir);\n throw err;\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { pluginAddResourceCommand } from "./add-resource/add-resource.js";
|
|
2
|
+
import { pluginCreateCommand } from "./create/create.js";
|
|
3
|
+
import { pluginListCommand } from "./list/list.js";
|
|
4
|
+
import { pluginsSyncCommand } from "./sync/sync.js";
|
|
5
|
+
import { pluginValidateCommand } from "./validate/validate.js";
|
|
6
|
+
import { Command } from "commander";
|
|
7
|
+
|
|
8
|
+
//#region src/cli/commands/plugin/index.ts
|
|
9
|
+
/**
|
|
10
|
+
* Parent command for plugin management operations.
|
|
11
|
+
* Subcommands:
|
|
12
|
+
* - sync: Aggregate plugin manifests into appkit.plugins.json
|
|
13
|
+
* - create: Scaffold a new plugin (interactive)
|
|
14
|
+
* - validate: Validate manifest(s) against the JSON schema
|
|
15
|
+
* - list: List plugins from appkit.plugins.json or a directory
|
|
16
|
+
* - add-resource: Add a resource requirement to a plugin manifest (interactive)
|
|
17
|
+
*/
|
|
18
|
+
const pluginCommand = new Command("plugin").description("Plugin management commands").addCommand(pluginsSyncCommand).addCommand(pluginCreateCommand).addCommand(pluginValidateCommand).addCommand(pluginListCommand).addCommand(pluginAddResourceCommand);
|
|
19
|
+
|
|
20
|
+
//#endregion
|
|
21
|
+
export { pluginCommand };
|
|
22
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../../src/cli/commands/plugin/index.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { pluginAddResourceCommand } from \"./add-resource/add-resource\";\nimport { pluginCreateCommand } from \"./create/create\";\nimport { pluginListCommand } from \"./list/list\";\nimport { pluginsSyncCommand } from \"./sync/sync\";\nimport { pluginValidateCommand } from \"./validate/validate\";\n\n/**\n * Parent command for plugin management operations.\n * Subcommands:\n * - sync: Aggregate plugin manifests into appkit.plugins.json\n * - create: Scaffold a new plugin (interactive)\n * - validate: Validate manifest(s) against the JSON schema\n * - list: List plugins from appkit.plugins.json or a directory\n * - add-resource: Add a resource requirement to a plugin manifest (interactive)\n */\nexport const pluginCommand = new Command(\"plugin\")\n .description(\"Plugin management commands\")\n .addCommand(pluginsSyncCommand)\n .addCommand(pluginCreateCommand)\n .addCommand(pluginValidateCommand)\n .addCommand(pluginListCommand)\n .addCommand(pluginAddResourceCommand);\n"],"mappings":";;;;;;;;;;;;;;;;;AAgBA,MAAa,gBAAgB,IAAI,QAAQ,SAAS,CAC/C,YAAY,6BAA6B,CACzC,WAAW,mBAAmB,CAC9B,WAAW,oBAAoB,CAC/B,WAAW,sBAAsB,CACjC,WAAW,kBAAkB,CAC7B,WAAW,yBAAyB"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { validateManifest } from "../validate/validate-manifest.js";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import process from "node:process";
|
|
6
|
+
|
|
7
|
+
//#region src/cli/commands/plugin/list/list.ts
|
|
8
|
+
function listFromManifestFile(manifestPath) {
|
|
9
|
+
let raw;
|
|
10
|
+
try {
|
|
11
|
+
raw = fs.readFileSync(manifestPath, "utf-8");
|
|
12
|
+
} catch (err) {
|
|
13
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
14
|
+
throw new Error(`Failed to read manifest file ${manifestPath}: ${msg}`);
|
|
15
|
+
}
|
|
16
|
+
let data;
|
|
17
|
+
try {
|
|
18
|
+
data = JSON.parse(raw);
|
|
19
|
+
} catch (err) {
|
|
20
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
21
|
+
throw new Error(`Failed to parse manifest file ${manifestPath}: ${msg}`);
|
|
22
|
+
}
|
|
23
|
+
const plugins = data.plugins ?? {};
|
|
24
|
+
return Object.values(plugins).map((p) => ({
|
|
25
|
+
name: p.name,
|
|
26
|
+
displayName: p.displayName ?? p.name,
|
|
27
|
+
package: p.package ?? "",
|
|
28
|
+
required: Array.isArray(p.resources?.required) ? p.resources.required.length : 0,
|
|
29
|
+
optional: Array.isArray(p.resources?.optional) ? p.resources.optional.length : 0
|
|
30
|
+
}));
|
|
31
|
+
}
|
|
32
|
+
function listFromDirectory(dirPath, cwd) {
|
|
33
|
+
const resolved = path.resolve(cwd, dirPath);
|
|
34
|
+
if (!fs.existsSync(resolved) || !fs.statSync(resolved).isDirectory()) return [];
|
|
35
|
+
const entries = fs.readdirSync(resolved, { withFileTypes: true });
|
|
36
|
+
const rows = [];
|
|
37
|
+
for (const entry of entries) {
|
|
38
|
+
if (!entry.isDirectory()) continue;
|
|
39
|
+
const manifestPath = path.join(resolved, entry.name, "manifest.json");
|
|
40
|
+
if (!fs.existsSync(manifestPath)) continue;
|
|
41
|
+
try {
|
|
42
|
+
const raw = fs.readFileSync(manifestPath, "utf-8");
|
|
43
|
+
const result = validateManifest(JSON.parse(raw));
|
|
44
|
+
const manifest = result.valid ? result.manifest : null;
|
|
45
|
+
if (!manifest) continue;
|
|
46
|
+
const relPath = path.relative(cwd, path.dirname(manifestPath));
|
|
47
|
+
const packagePath = relPath.startsWith(".") ? relPath : `./${relPath}`;
|
|
48
|
+
rows.push({
|
|
49
|
+
name: manifest.name,
|
|
50
|
+
displayName: manifest.displayName ?? manifest.name,
|
|
51
|
+
package: packagePath,
|
|
52
|
+
required: Array.isArray(manifest.resources?.required) ? manifest.resources.required.length : 0,
|
|
53
|
+
optional: Array.isArray(manifest.resources?.optional) ? manifest.resources.optional.length : 0
|
|
54
|
+
});
|
|
55
|
+
} catch {}
|
|
56
|
+
}
|
|
57
|
+
return rows;
|
|
58
|
+
}
|
|
59
|
+
function printTable(rows) {
|
|
60
|
+
if (rows.length === 0) {
|
|
61
|
+
console.log("No plugins found.");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const maxName = Math.max(4, ...rows.map((r) => r.name.length));
|
|
65
|
+
const maxDisplay = Math.max(10, ...rows.map((r) => r.displayName.length));
|
|
66
|
+
const maxPkg = Math.max(7, ...rows.map((r) => r.package.length));
|
|
67
|
+
const header = [
|
|
68
|
+
"NAME".padEnd(maxName),
|
|
69
|
+
"DISPLAY NAME".padEnd(maxDisplay),
|
|
70
|
+
"PACKAGE / PATH".padEnd(maxPkg),
|
|
71
|
+
"REQ",
|
|
72
|
+
"OPT"
|
|
73
|
+
].join(" ");
|
|
74
|
+
console.log(header);
|
|
75
|
+
console.log("-".repeat(header.length));
|
|
76
|
+
for (const r of rows) console.log([
|
|
77
|
+
r.name.padEnd(maxName),
|
|
78
|
+
r.displayName.padEnd(maxDisplay),
|
|
79
|
+
r.package.padEnd(maxPkg),
|
|
80
|
+
String(r.required).padStart(3),
|
|
81
|
+
String(r.optional).padStart(3)
|
|
82
|
+
].join(" "));
|
|
83
|
+
}
|
|
84
|
+
function runPluginList(options) {
|
|
85
|
+
const cwd = process.cwd();
|
|
86
|
+
let rows;
|
|
87
|
+
if (options.dir !== void 0) {
|
|
88
|
+
rows = listFromDirectory(options.dir, cwd);
|
|
89
|
+
if (rows.length === 0 && options.dir) {
|
|
90
|
+
console.error(`No plugin directories with manifest.json found in ${options.dir}`);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
} else {
|
|
94
|
+
const manifestPath = path.resolve(cwd, options.manifest ?? "appkit.plugins.json");
|
|
95
|
+
if (!fs.existsSync(manifestPath)) {
|
|
96
|
+
console.error(`Manifest not found: ${manifestPath}`);
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
rows = listFromManifestFile(manifestPath);
|
|
101
|
+
} catch (err) {
|
|
102
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (options.json) console.log(JSON.stringify(rows, null, 2));
|
|
107
|
+
else printTable(rows);
|
|
108
|
+
}
|
|
109
|
+
const pluginListCommand = new Command("list").description("List plugins from appkit.plugins.json or a directory").option("-m, --manifest <path>", "Path to appkit.plugins.json", "appkit.plugins.json").option("-d, --dir <path>", "Scan directory for plugin folders (each with manifest.json)").option("--json", "Output as JSON").action(runPluginList);
|
|
110
|
+
|
|
111
|
+
//#endregion
|
|
112
|
+
export { pluginListCommand };
|
|
113
|
+
//# sourceMappingURL=list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list.js","names":[],"sources":["../../../../../src/cli/commands/plugin/list/list.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport process from \"node:process\";\nimport { Command } from \"commander\";\nimport { validateManifest } from \"../validate/validate-manifest\";\n\nexport interface PluginRow {\n name: string;\n displayName: string;\n package: string;\n required: number;\n optional: number;\n}\n\nexport function listFromManifestFile(manifestPath: string): PluginRow[] {\n let raw: string;\n try {\n raw = fs.readFileSync(manifestPath, \"utf-8\");\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`Failed to read manifest file ${manifestPath}: ${msg}`);\n }\n\n let data: {\n plugins?: Record<\n string,\n {\n name: string;\n displayName: string;\n package: string;\n resources: { required: unknown[]; optional: unknown[] };\n }\n >;\n };\n try {\n data = JSON.parse(raw) as typeof data;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`Failed to parse manifest file ${manifestPath}: ${msg}`);\n }\n\n const plugins = data.plugins ?? {};\n return Object.values(plugins).map((p) => ({\n name: p.name,\n displayName: p.displayName ?? p.name,\n package: p.package ?? \"\",\n required: Array.isArray(p.resources?.required)\n ? p.resources.required.length\n : 0,\n optional: Array.isArray(p.resources?.optional)\n ? p.resources.optional.length\n : 0,\n }));\n}\n\nexport function listFromDirectory(dirPath: string, cwd: string): PluginRow[] {\n const resolved = path.resolve(cwd, dirPath);\n if (!fs.existsSync(resolved) || !fs.statSync(resolved).isDirectory()) {\n return [];\n }\n const entries = fs.readdirSync(resolved, { withFileTypes: true });\n const rows: PluginRow[] = [];\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n const manifestPath = path.join(resolved, entry.name, \"manifest.json\");\n if (!fs.existsSync(manifestPath)) continue;\n try {\n const raw = fs.readFileSync(manifestPath, \"utf-8\");\n const obj = JSON.parse(raw);\n const result = validateManifest(obj);\n const manifest = result.valid ? result.manifest : null;\n if (!manifest) continue;\n const relPath = path.relative(cwd, path.dirname(manifestPath));\n const packagePath = relPath.startsWith(\".\") ? relPath : `./${relPath}`;\n rows.push({\n name: manifest.name,\n displayName: manifest.displayName ?? manifest.name,\n package: packagePath,\n required: Array.isArray(manifest.resources?.required)\n ? manifest.resources.required.length\n : 0,\n optional: Array.isArray(manifest.resources?.optional)\n ? manifest.resources.optional.length\n : 0,\n });\n } catch {\n // skip invalid manifests\n }\n }\n return rows;\n}\n\nfunction printTable(rows: PluginRow[]): void {\n if (rows.length === 0) {\n console.log(\"No plugins found.\");\n return;\n }\n const maxName = Math.max(4, ...rows.map((r) => r.name.length));\n const maxDisplay = Math.max(10, ...rows.map((r) => r.displayName.length));\n const maxPkg = Math.max(7, ...rows.map((r) => r.package.length));\n const header = [\n \"NAME\".padEnd(maxName),\n \"DISPLAY NAME\".padEnd(maxDisplay),\n \"PACKAGE / PATH\".padEnd(maxPkg),\n \"REQ\",\n \"OPT\",\n ].join(\" \");\n console.log(header);\n console.log(\"-\".repeat(header.length));\n for (const r of rows) {\n console.log(\n [\n r.name.padEnd(maxName),\n r.displayName.padEnd(maxDisplay),\n r.package.padEnd(maxPkg),\n String(r.required).padStart(3),\n String(r.optional).padStart(3),\n ].join(\" \"),\n );\n }\n}\n\nfunction runPluginList(options: {\n manifest?: string;\n dir?: string;\n json?: boolean;\n}): void {\n const cwd = process.cwd();\n let rows: PluginRow[];\n\n if (options.dir !== undefined) {\n rows = listFromDirectory(options.dir, cwd);\n if (rows.length === 0 && options.dir) {\n console.error(\n `No plugin directories with manifest.json found in ${options.dir}`,\n );\n process.exit(1);\n }\n } else {\n const manifestPath = path.resolve(\n cwd,\n options.manifest ?? \"appkit.plugins.json\",\n );\n if (!fs.existsSync(manifestPath)) {\n console.error(`Manifest not found: ${manifestPath}`);\n process.exit(1);\n }\n try {\n rows = listFromManifestFile(manifestPath);\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n }\n\n if (options.json) {\n console.log(JSON.stringify(rows, null, 2));\n } else {\n printTable(rows);\n }\n}\n\nexport const pluginListCommand = new Command(\"list\")\n .description(\"List plugins from appkit.plugins.json or a directory\")\n .option(\n \"-m, --manifest <path>\",\n \"Path to appkit.plugins.json\",\n \"appkit.plugins.json\",\n )\n .option(\n \"-d, --dir <path>\",\n \"Scan directory for plugin folders (each with manifest.json)\",\n )\n .option(\"--json\", \"Output as JSON\")\n .action(runPluginList);\n"],"mappings":"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"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
//#region src/cli/commands/plugin/schema-resources.ts
|
|
6
|
+
/**
|
|
7
|
+
* Resource types and permissions derived from plugin-manifest.schema.json.
|
|
8
|
+
* Single source of truth so create, add-resource, and validate stay in sync with the schema.
|
|
9
|
+
*/
|
|
10
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const SCHEMA_NAME = "plugin-manifest.schema.json";
|
|
12
|
+
const SCHEMA_PATHS = [path.join(__dirname, "..", "..", "..", "schemas", SCHEMA_NAME), path.join(__dirname, "..", "..", "schemas", SCHEMA_NAME)];
|
|
13
|
+
function loadSchema() {
|
|
14
|
+
for (const schemaPath of SCHEMA_PATHS) try {
|
|
15
|
+
if (fs.existsSync(schemaPath)) return JSON.parse(fs.readFileSync(schemaPath, "utf-8"));
|
|
16
|
+
} catch {}
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
/** Optional display overrides for acronyms (e.g. SQL, UC). Omitted entries use title-case of value. */
|
|
20
|
+
const LABEL_OVERRIDES = {
|
|
21
|
+
sql_warehouse: "SQL Warehouse",
|
|
22
|
+
uc_function: "UC Function",
|
|
23
|
+
uc_connection: "UC Connection"
|
|
24
|
+
};
|
|
25
|
+
function humanize(value) {
|
|
26
|
+
if (LABEL_OVERRIDES[value]) return LABEL_OVERRIDES[value];
|
|
27
|
+
return value.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
28
|
+
}
|
|
29
|
+
let cachedOptions = null;
|
|
30
|
+
let cachedPermissions = null;
|
|
31
|
+
/**
|
|
32
|
+
* Resource type options (value + label) from schema $defs.resourceType.enum.
|
|
33
|
+
*/
|
|
34
|
+
function getResourceTypeOptions() {
|
|
35
|
+
if (cachedOptions) return cachedOptions;
|
|
36
|
+
const enumArr = ((loadSchema()?.$defs)?.resourceType)?.enum;
|
|
37
|
+
if (!Array.isArray(enumArr)) {
|
|
38
|
+
cachedOptions = [];
|
|
39
|
+
return cachedOptions;
|
|
40
|
+
}
|
|
41
|
+
cachedOptions = enumArr.map((value) => ({
|
|
42
|
+
value,
|
|
43
|
+
label: humanize(value)
|
|
44
|
+
}));
|
|
45
|
+
return cachedOptions;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Permissions per resource type from schema resourceRequirement.allOf (if/then).
|
|
49
|
+
*/
|
|
50
|
+
function getResourceTypePermissions() {
|
|
51
|
+
if (cachedPermissions) return cachedPermissions;
|
|
52
|
+
const schema = loadSchema();
|
|
53
|
+
const out = {};
|
|
54
|
+
if (!schema?.$defs || typeof schema.$defs !== "object") {
|
|
55
|
+
cachedPermissions = out;
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
const allOf = schema.$defs.resourceRequirement?.allOf;
|
|
59
|
+
if (!Array.isArray(allOf)) {
|
|
60
|
+
cachedPermissions = out;
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
for (const branch of allOf) {
|
|
64
|
+
const typeConst = branch?.if?.properties?.type?.const;
|
|
65
|
+
const ref = branch?.then?.properties?.permission?.$ref;
|
|
66
|
+
if (typeof typeConst !== "string" || typeof ref !== "string") continue;
|
|
67
|
+
const refSegments = ref.replace(/^#\//, "").split("/");
|
|
68
|
+
let def = schema;
|
|
69
|
+
for (const seg of refSegments) {
|
|
70
|
+
if (def == null || typeof def !== "object") break;
|
|
71
|
+
def = def[seg];
|
|
72
|
+
}
|
|
73
|
+
const enumArr = Array.isArray(def?.enum) ? def.enum : void 0;
|
|
74
|
+
if (enumArr?.length) out[typeConst] = enumArr;
|
|
75
|
+
}
|
|
76
|
+
cachedPermissions = out;
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
//#endregion
|
|
81
|
+
export { getResourceTypeOptions, getResourceTypePermissions };
|
|
82
|
+
//# sourceMappingURL=schema-resources.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-resources.js","names":[],"sources":["../../../../src/cli/commands/plugin/schema-resources.ts"],"sourcesContent":["/**\n * Resource types and permissions derived from plugin-manifest.schema.json.\n * Single source of truth so create, add-resource, and validate stay in sync with the schema.\n */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\nconst SCHEMA_NAME = \"plugin-manifest.schema.json\";\n// Try dist/schemas first (shared build + appkit pack), then dist/cli/schemas\nconst SCHEMA_PATHS = [\n path.join(__dirname, \"..\", \"..\", \"..\", \"schemas\", SCHEMA_NAME),\n path.join(__dirname, \"..\", \"..\", \"schemas\", SCHEMA_NAME),\n];\n\nexport interface ResourceTypeOption {\n value: string;\n label: string;\n}\n\nfunction loadSchema(): Record<string, unknown> | null {\n for (const schemaPath of SCHEMA_PATHS) {\n try {\n if (fs.existsSync(schemaPath)) {\n return JSON.parse(fs.readFileSync(schemaPath, \"utf-8\")) as Record<\n string,\n unknown\n >;\n }\n } catch {\n // try next path\n }\n }\n return null;\n}\n\n/** Optional display overrides for acronyms (e.g. SQL, UC). Omitted entries use title-case of value. */\nconst LABEL_OVERRIDES: Record<string, string> = {\n sql_warehouse: \"SQL Warehouse\",\n uc_function: \"UC Function\",\n uc_connection: \"UC Connection\",\n};\n\nfunction humanize(value: string): string {\n if (LABEL_OVERRIDES[value]) return LABEL_OVERRIDES[value];\n return value.replace(/_/g, \" \").replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n\nlet cachedOptions: ResourceTypeOption[] | null = null;\nlet cachedPermissions: Record<string, string[]> | null = null;\n\n/**\n * Resource type options (value + label) from schema $defs.resourceType.enum.\n */\nexport function getResourceTypeOptions(): ResourceTypeOption[] {\n if (cachedOptions) return cachedOptions;\n const schema = loadSchema();\n const defs = schema?.$defs as Record<string, unknown> | undefined;\n const resourceType = defs?.resourceType as { enum?: string[] } | undefined;\n const enumArr = resourceType?.enum;\n if (!Array.isArray(enumArr)) {\n cachedOptions = [];\n return cachedOptions;\n }\n cachedOptions = enumArr.map((value) => ({\n value,\n label: humanize(value),\n }));\n return cachedOptions;\n}\n\n/**\n * Permissions per resource type from schema resourceRequirement.allOf (if/then).\n */\nexport function getResourceTypePermissions(): Record<string, string[]> {\n if (cachedPermissions) return cachedPermissions;\n const schema = loadSchema();\n const out: Record<string, string[]> = {};\n if (!schema?.$defs || typeof schema.$defs !== \"object\") {\n cachedPermissions = out;\n return out;\n }\n const defs = schema.$defs as Record<string, unknown>;\n const resourceReq = defs.resourceRequirement as\n | Record<string, unknown>\n | undefined;\n const allOf = resourceReq?.allOf as\n | Array<{\n if?: { properties?: { type?: { const?: string } } };\n then?: { properties?: { permission?: { $ref?: string } } };\n }>\n | undefined;\n if (!Array.isArray(allOf)) {\n cachedPermissions = out;\n return out;\n }\n for (const branch of allOf) {\n const typeConst = branch?.if?.properties?.type?.const;\n const ref = branch?.then?.properties?.permission?.$ref;\n if (typeof typeConst !== \"string\" || typeof ref !== \"string\") continue;\n const refSegments = ref.replace(/^#\\//, \"\").split(\"/\");\n let def: unknown = schema;\n for (const seg of refSegments) {\n if (def == null || typeof def !== \"object\") break;\n def = (def as Record<string, unknown>)[seg];\n }\n const enumArr = Array.isArray((def as { enum?: string[] })?.enum)\n ? (def as { enum: string[] }).enum\n : undefined;\n if (enumArr?.length) out[typeConst] = enumArr;\n }\n cachedPermissions = out;\n return out;\n}\n"],"mappings":";;;;;;;;;AAQA,MAAM,YAAY,KAAK,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;AAC9D,MAAM,cAAc;AAEpB,MAAM,eAAe,CACnB,KAAK,KAAK,WAAW,MAAM,MAAM,MAAM,WAAW,YAAY,EAC9D,KAAK,KAAK,WAAW,MAAM,MAAM,WAAW,YAAY,CACzD;AAOD,SAAS,aAA6C;AACpD,MAAK,MAAM,cAAc,aACvB,KAAI;AACF,MAAI,GAAG,WAAW,WAAW,CAC3B,QAAO,KAAK,MAAM,GAAG,aAAa,YAAY,QAAQ,CAAC;SAKnD;AAIV,QAAO;;;AAIT,MAAM,kBAA0C;CAC9C,eAAe;CACf,aAAa;CACb,eAAe;CAChB;AAED,SAAS,SAAS,OAAuB;AACvC,KAAI,gBAAgB,OAAQ,QAAO,gBAAgB;AACnD,QAAO,MAAM,QAAQ,MAAM,IAAI,CAAC,QAAQ,UAAU,MAAM,EAAE,aAAa,CAAC;;AAG1E,IAAI,gBAA6C;AACjD,IAAI,oBAAqD;;;;AAKzD,SAAgB,yBAA+C;AAC7D,KAAI,cAAe,QAAO;CAI1B,MAAM,YAHS,YAAY,EACN,QACM,eACG;AAC9B,KAAI,CAAC,MAAM,QAAQ,QAAQ,EAAE;AAC3B,kBAAgB,EAAE;AAClB,SAAO;;AAET,iBAAgB,QAAQ,KAAK,WAAW;EACtC;EACA,OAAO,SAAS,MAAM;EACvB,EAAE;AACH,QAAO;;;;;AAMT,SAAgB,6BAAuD;AACrE,KAAI,kBAAmB,QAAO;CAC9B,MAAM,SAAS,YAAY;CAC3B,MAAM,MAAgC,EAAE;AACxC,KAAI,CAAC,QAAQ,SAAS,OAAO,OAAO,UAAU,UAAU;AACtD,sBAAoB;AACpB,SAAO;;CAMT,MAAM,QAJO,OAAO,MACK,qBAGE;AAM3B,KAAI,CAAC,MAAM,QAAQ,MAAM,EAAE;AACzB,sBAAoB;AACpB,SAAO;;AAET,MAAK,MAAM,UAAU,OAAO;EAC1B,MAAM,YAAY,QAAQ,IAAI,YAAY,MAAM;EAChD,MAAM,MAAM,QAAQ,MAAM,YAAY,YAAY;AAClD,MAAI,OAAO,cAAc,YAAY,OAAO,QAAQ,SAAU;EAC9D,MAAM,cAAc,IAAI,QAAQ,QAAQ,GAAG,CAAC,MAAM,IAAI;EACtD,IAAI,MAAe;AACnB,OAAK,MAAM,OAAO,aAAa;AAC7B,OAAI,OAAO,QAAQ,OAAO,QAAQ,SAAU;AAC5C,SAAO,IAAgC;;EAEzC,MAAM,UAAU,MAAM,QAAS,KAA6B,KAAK,GAC5D,IAA2B,OAC5B;AACJ,MAAI,SAAS,OAAQ,KAAI,aAAa;;AAExC,qBAAoB;AACpB,QAAO"}
|