@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
|
@@ -1,14 +1,10 @@
|
|
|
1
|
+
import { formatValidationErrors, validateManifest } from "../validate/validate-manifest.js";
|
|
1
2
|
import fs from "node:fs";
|
|
2
3
|
import path from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import { Lang, parse } from "@ast-grep/napi";
|
|
6
|
-
import Ajv from "ajv";
|
|
7
|
-
import addFormats from "ajv-formats";
|
|
8
6
|
|
|
9
|
-
//#region src/cli/commands/
|
|
10
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
-
const PLUGIN_MANIFEST_SCHEMA_PATH = path.join(__dirname, "..", "..", "..", "schemas", "plugin-manifest.schema.json");
|
|
7
|
+
//#region src/cli/commands/plugin/sync/sync.ts
|
|
12
8
|
/**
|
|
13
9
|
* Checks whether a resolved file path is within a given directory boundary.
|
|
14
10
|
* Uses path.resolve + startsWith to prevent directory traversal.
|
|
@@ -22,51 +18,14 @@ function isWithinDirectory(filePath, boundary) {
|
|
|
22
18
|
const resolvedBoundary = path.resolve(boundary);
|
|
23
19
|
return resolvedPath === resolvedBoundary || resolvedPath.startsWith(`${resolvedBoundary}${path.sep}`);
|
|
24
20
|
}
|
|
25
|
-
let pluginManifestValidator = null;
|
|
26
|
-
/**
|
|
27
|
-
* Loads and compiles the plugin-manifest JSON schema (cached).
|
|
28
|
-
* Returns the compiled validate function or null if the schema cannot be loaded.
|
|
29
|
-
*/
|
|
30
|
-
function getPluginManifestValidator() {
|
|
31
|
-
if (pluginManifestValidator) return pluginManifestValidator;
|
|
32
|
-
try {
|
|
33
|
-
const schemaRaw = fs.readFileSync(PLUGIN_MANIFEST_SCHEMA_PATH, "utf-8");
|
|
34
|
-
const schema = JSON.parse(schemaRaw);
|
|
35
|
-
const ajv = new Ajv({
|
|
36
|
-
allErrors: true,
|
|
37
|
-
strict: false
|
|
38
|
-
});
|
|
39
|
-
addFormats(ajv);
|
|
40
|
-
pluginManifestValidator = ajv.compile(schema);
|
|
41
|
-
return pluginManifestValidator;
|
|
42
|
-
} catch (err) {
|
|
43
|
-
console.warn("Warning: Could not load plugin-manifest schema for validation:", err instanceof Error ? err.message : err);
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
21
|
/**
|
|
48
22
|
* Validates a parsed JSON object against the plugin-manifest JSON schema.
|
|
49
23
|
* Returns the manifest if valid, or null and logs schema errors.
|
|
50
|
-
*
|
|
51
|
-
* @param obj - The parsed JSON object to validate
|
|
52
|
-
* @param sourcePath - Path to the manifest file (for warning messages)
|
|
53
|
-
* @returns A valid PluginManifest or null
|
|
54
24
|
*/
|
|
55
25
|
function validateManifestWithSchema(obj, sourcePath) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
const validate = getPluginManifestValidator();
|
|
61
|
-
if (!validate) {
|
|
62
|
-
const m = obj;
|
|
63
|
-
if (typeof m.name === "string" && m.name.length > 0 && typeof m.displayName === "string" && m.displayName.length > 0 && typeof m.description === "string" && m.description.length > 0 && m.resources && typeof m.resources === "object" && Array.isArray(m.resources.required)) return obj;
|
|
64
|
-
console.warn(`Warning: Manifest at ${sourcePath} has invalid structure`);
|
|
65
|
-
return null;
|
|
66
|
-
}
|
|
67
|
-
if (validate(obj)) return obj;
|
|
68
|
-
const message = (validate.errors ?? []).map((e) => ` ${e.instancePath || "/"} ${e.message}${e.params ? ` (${JSON.stringify(e.params)})` : ""}`).join("\n");
|
|
69
|
-
console.warn(`Warning: Manifest at ${sourcePath} failed schema validation:\n${message}`);
|
|
26
|
+
const result = validateManifest(obj);
|
|
27
|
+
if (result.valid && result.manifest) return result.manifest;
|
|
28
|
+
if (result.errors?.length) console.warn(`Warning: Manifest at ${sourcePath} failed schema validation:\n${formatValidationErrors(result.errors, obj)}`);
|
|
70
29
|
return null;
|
|
71
30
|
}
|
|
72
31
|
/**
|
|
@@ -78,7 +37,7 @@ const KNOWN_PLUGIN_PACKAGES = ["@databricks/appkit"];
|
|
|
78
37
|
* Candidate paths for the server entry file, relative to cwd.
|
|
79
38
|
* Checked in order; the first that exists is used.
|
|
80
39
|
*/
|
|
81
|
-
const SERVER_FILE_CANDIDATES = ["server/server.ts"];
|
|
40
|
+
const SERVER_FILE_CANDIDATES = ["server/server.ts", "server/index.ts"];
|
|
82
41
|
/**
|
|
83
42
|
* Find the server entry file by checking candidate paths in order.
|
|
84
43
|
*
|
|
@@ -285,7 +244,61 @@ function scanForPlugins(cwd, packages) {
|
|
|
285
244
|
return plugins;
|
|
286
245
|
}
|
|
287
246
|
/**
|
|
288
|
-
*
|
|
247
|
+
* Scan a directory for plugin manifests in direct subdirectories.
|
|
248
|
+
* Each subdirectory is expected to contain a manifest.json file.
|
|
249
|
+
* Used with --plugins-dir to discover plugins from source instead of node_modules.
|
|
250
|
+
*
|
|
251
|
+
* @param dir - Absolute path to the directory containing plugin subdirectories
|
|
252
|
+
* @param packageName - Package name to assign to discovered plugins
|
|
253
|
+
* @returns Map of plugin name to template plugin entry
|
|
254
|
+
*/
|
|
255
|
+
function scanPluginsDir(dir, packageName) {
|
|
256
|
+
const plugins = {};
|
|
257
|
+
if (!fs.existsSync(dir)) return plugins;
|
|
258
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
259
|
+
for (const entry of entries) {
|
|
260
|
+
if (!entry.isDirectory()) continue;
|
|
261
|
+
const manifestPath = path.join(dir, entry.name, "manifest.json");
|
|
262
|
+
if (!fs.existsSync(manifestPath)) continue;
|
|
263
|
+
try {
|
|
264
|
+
const content = fs.readFileSync(manifestPath, "utf-8");
|
|
265
|
+
const manifest = validateManifestWithSchema(JSON.parse(content), manifestPath);
|
|
266
|
+
if (manifest) plugins[manifest.name] = {
|
|
267
|
+
name: manifest.name,
|
|
268
|
+
displayName: manifest.displayName,
|
|
269
|
+
description: manifest.description,
|
|
270
|
+
package: packageName,
|
|
271
|
+
resources: manifest.resources
|
|
272
|
+
};
|
|
273
|
+
} catch (error) {
|
|
274
|
+
console.warn(`Warning: Failed to parse manifest at ${manifestPath}:`, error instanceof Error ? error.message : error);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return plugins;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Write (or preview) the template plugins manifest to disk.
|
|
281
|
+
*/
|
|
282
|
+
function writeManifest(outputPath, { plugins }, options) {
|
|
283
|
+
const templateManifest = {
|
|
284
|
+
$schema: "https://databricks.github.io/appkit/schemas/template-plugins.schema.json",
|
|
285
|
+
version: "1.0",
|
|
286
|
+
plugins
|
|
287
|
+
};
|
|
288
|
+
if (options.write) {
|
|
289
|
+
fs.writeFileSync(outputPath, `${JSON.stringify(templateManifest, null, 2)}\n`);
|
|
290
|
+
if (!options.silent) console.log(`\n✓ Wrote ${outputPath}`);
|
|
291
|
+
} else if (!options.silent) {
|
|
292
|
+
console.log("\nTo write the manifest, run:");
|
|
293
|
+
console.log(" npx appkit plugin sync --write\n");
|
|
294
|
+
console.log("Preview:");
|
|
295
|
+
console.log("─".repeat(60));
|
|
296
|
+
console.log(JSON.stringify(templateManifest, null, 2));
|
|
297
|
+
console.log("─".repeat(60));
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Run the plugin sync command.
|
|
289
302
|
* Parses the server entry file to discover which packages to scan for plugin
|
|
290
303
|
* manifests, then marks plugins that are actually used in the `plugins: [...]`
|
|
291
304
|
* array as requiredByTemplate.
|
|
@@ -297,31 +310,45 @@ function runPluginsSync(options) {
|
|
|
297
310
|
console.error(`Error: Output path "${options.output}" resolves outside the project directory.`);
|
|
298
311
|
process.exit(1);
|
|
299
312
|
}
|
|
300
|
-
console.log("Scanning for AppKit plugins...\n");
|
|
313
|
+
if (!options.silent) console.log("Scanning for AppKit plugins...\n");
|
|
301
314
|
const serverFile = findServerFile(cwd);
|
|
302
315
|
let serverImports = [];
|
|
303
316
|
let pluginUsages = /* @__PURE__ */ new Set();
|
|
304
317
|
if (serverFile) {
|
|
305
|
-
|
|
306
|
-
|
|
318
|
+
if (!options.silent) {
|
|
319
|
+
const relativePath = path.relative(cwd, serverFile);
|
|
320
|
+
console.log(`Server entry file: ${relativePath}`);
|
|
321
|
+
}
|
|
307
322
|
const content = fs.readFileSync(serverFile, "utf-8");
|
|
308
323
|
const root = parse(serverFile.endsWith(".tsx") ? Lang.Tsx : Lang.TypeScript, content).root();
|
|
309
324
|
serverImports = parseImports(root);
|
|
310
325
|
pluginUsages = parsePluginUsages(root);
|
|
311
|
-
} else console.log("No server entry file found. Checked:", SERVER_FILE_CANDIDATES.join(", "));
|
|
326
|
+
} else if (!options.silent) console.log("No server entry file found. Checked:", SERVER_FILE_CANDIDATES.join(", "));
|
|
312
327
|
const npmImports = serverImports.filter((i) => !i.source.startsWith(".") && !i.source.startsWith("/"));
|
|
313
328
|
const localImports = serverImports.filter((i) => i.source.startsWith(".") || i.source.startsWith("/"));
|
|
314
|
-
const
|
|
315
|
-
|
|
329
|
+
const plugins = {};
|
|
330
|
+
if (options.pluginsDir) {
|
|
331
|
+
const resolvedDir = path.resolve(cwd, options.pluginsDir);
|
|
332
|
+
const pkgName = options.packageName ?? "@databricks/appkit";
|
|
333
|
+
if (!options.silent) console.log(`Scanning plugins directory: ${options.pluginsDir}`);
|
|
334
|
+
Object.assign(plugins, scanPluginsDir(resolvedDir, pkgName));
|
|
335
|
+
} else {
|
|
336
|
+
const npmPackages = new Set([...KNOWN_PLUGIN_PACKAGES, ...npmImports.map((i) => i.source)]);
|
|
337
|
+
Object.assign(plugins, scanForPlugins(cwd, npmPackages));
|
|
338
|
+
}
|
|
316
339
|
if (serverFile && localImports.length > 0) {
|
|
317
340
|
const localPlugins = discoverLocalPlugins(localImports, path.dirname(serverFile), cwd);
|
|
318
341
|
Object.assign(plugins, localPlugins);
|
|
319
342
|
}
|
|
320
343
|
const pluginCount = Object.keys(plugins).length;
|
|
321
344
|
if (pluginCount === 0) {
|
|
345
|
+
if (options.silent) {
|
|
346
|
+
writeManifest(outputPath, { plugins: {} }, options);
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
322
349
|
console.log("No plugins found.");
|
|
323
|
-
console.log(
|
|
324
|
-
|
|
350
|
+
if (options.pluginsDir) console.log(`\nNo manifest.json files found in: ${options.pluginsDir}`);
|
|
351
|
+
else console.log("\nMake sure you have plugin packages installed.");
|
|
325
352
|
process.exit(1);
|
|
326
353
|
}
|
|
327
354
|
const serverFileDir = serverFile ? path.dirname(serverFile) : cwd;
|
|
@@ -338,32 +365,24 @@ function runPluginsSync(options) {
|
|
|
338
365
|
} else plugin = Object.values(plugins).find((p) => p.package === imp.source && p.name === imp.originalName);
|
|
339
366
|
if (plugin) plugin.requiredByTemplate = true;
|
|
340
367
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
const mandatoryTag = manifest.requiredByTemplate ? " (mandatory)" : "";
|
|
346
|
-
console.log(` ${manifest.requiredByTemplate ? "●" : "○"} ${manifest.displayName} (${name}) from ${manifest.package}${resourceInfo}${mandatoryTag}`);
|
|
368
|
+
if (options.requirePlugins) {
|
|
369
|
+
const explicitNames = options.requirePlugins.split(",").map((s) => s.trim()).filter(Boolean);
|
|
370
|
+
for (const name of explicitNames) if (plugins[name]) plugins[name].requiredByTemplate = true;
|
|
371
|
+
else if (!options.silent) console.warn(`Warning: --require-plugins referenced "${name}" but no such plugin was discovered`);
|
|
347
372
|
}
|
|
348
|
-
|
|
349
|
-
$
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
} else {
|
|
357
|
-
console.log("\nTo write the manifest, run:");
|
|
358
|
-
console.log(" npx appkit plugins sync --write\n");
|
|
359
|
-
console.log("Preview:");
|
|
360
|
-
console.log("─".repeat(60));
|
|
361
|
-
console.log(JSON.stringify(templateManifest, null, 2));
|
|
362
|
-
console.log("─".repeat(60));
|
|
373
|
+
if (!options.silent) {
|
|
374
|
+
console.log(`\nFound ${pluginCount} plugin(s):`);
|
|
375
|
+
for (const [name, manifest] of Object.entries(plugins)) {
|
|
376
|
+
const resourceCount = manifest.resources.required.length + manifest.resources.optional.length;
|
|
377
|
+
const resourceInfo = resourceCount > 0 ? ` [${resourceCount} resource(s)]` : "";
|
|
378
|
+
const mandatoryTag = manifest.requiredByTemplate ? " (mandatory)" : "";
|
|
379
|
+
console.log(` ${manifest.requiredByTemplate ? "●" : "○"} ${manifest.displayName} (${name}) from ${manifest.package}${resourceInfo}${mandatoryTag}`);
|
|
380
|
+
}
|
|
363
381
|
}
|
|
382
|
+
writeManifest(outputPath, { plugins }, options);
|
|
364
383
|
}
|
|
365
|
-
const pluginsSyncCommand = new Command("sync").description("Sync plugin manifests from installed packages into appkit.plugins.json").option("-w, --write", "Write the manifest file").option("-o, --output <path>", "Output file path (default: ./appkit.plugins.json)").action(runPluginsSync);
|
|
384
|
+
const pluginsSyncCommand = new Command("sync").description("Sync plugin manifests from installed packages into appkit.plugins.json").option("-w, --write", "Write the manifest file").option("-o, --output <path>", "Output file path (default: ./appkit.plugins.json)").option("-s, --silent", "Suppress output and never exit with error (for use in predev/prebuild hooks)").option("--require-plugins <names>", "Comma-separated plugin names to mark as requiredByTemplate (e.g. server,analytics)").option("--plugins-dir <path>", "Scan this directory for plugin subdirectories with manifest.json (instead of node_modules)").option("--package-name <name>", "Package name to assign to plugins found via --plugins-dir (default: @databricks/appkit)").action(runPluginsSync);
|
|
366
385
|
|
|
367
386
|
//#endregion
|
|
368
387
|
export { pluginsSyncCommand };
|
|
369
|
-
//# sourceMappingURL=
|
|
388
|
+
//# sourceMappingURL=sync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.js","names":[],"sources":["../../../../../src/cli/commands/plugin/sync/sync.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { Lang, parse, type SgNode } from \"@ast-grep/napi\";\nimport { Command } from \"commander\";\nimport type {\n PluginManifest,\n TemplatePlugin,\n TemplatePluginsManifest,\n} from \"../manifest-types\";\nimport {\n formatValidationErrors,\n validateManifest,\n} from \"../validate/validate-manifest\";\n\n/**\n * Checks whether a resolved file path is within a given directory boundary.\n * Uses path.resolve + startsWith to prevent directory traversal.\n *\n * @param filePath - The path to check (will be resolved to absolute)\n * @param boundary - The directory that must contain filePath\n * @returns true if filePath is inside boundary (or equal to it)\n */\nfunction isWithinDirectory(filePath: string, boundary: string): boolean {\n const resolvedPath = path.resolve(filePath);\n const resolvedBoundary = path.resolve(boundary);\n // Append separator to avoid prefix false-positives (e.g. /foo-bar matching /foo)\n return (\n resolvedPath === resolvedBoundary ||\n resolvedPath.startsWith(`${resolvedBoundary}${path.sep}`)\n );\n}\n\n/**\n * Validates a parsed JSON object against the plugin-manifest JSON schema.\n * Returns the manifest if valid, or null and logs schema errors.\n */\nfunction validateManifestWithSchema(\n obj: unknown,\n sourcePath: string,\n): PluginManifest | null {\n const result = validateManifest(obj);\n if (result.valid && result.manifest) return result.manifest;\n if (result.errors?.length) {\n console.warn(\n `Warning: Manifest at ${sourcePath} failed schema validation:\\n${formatValidationErrors(result.errors, obj)}`,\n );\n }\n return null;\n}\n\n/**\n * Known packages that may contain AppKit plugins.\n * Always scanned for manifests, even if not imported in the server file.\n */\nconst KNOWN_PLUGIN_PACKAGES = [\"@databricks/appkit\"];\n\n/**\n * Candidate paths for the server entry file, relative to cwd.\n * Checked in order; the first that exists is used.\n */\nconst SERVER_FILE_CANDIDATES = [\"server/server.ts\", \"server/index.ts\"];\n\n/**\n * Find the server entry file by checking candidate paths in order.\n *\n * @param cwd - Current working directory\n * @returns Absolute path to the server file, or null if none found\n */\nfunction findServerFile(cwd: string): string | null {\n for (const candidate of SERVER_FILE_CANDIDATES) {\n const fullPath = path.join(cwd, candidate);\n if (fs.existsSync(fullPath)) {\n return fullPath;\n }\n }\n return null;\n}\n\n/**\n * Represents a single named import extracted from the server file.\n */\ninterface ParsedImport {\n /** The imported name (or local alias if renamed) */\n name: string;\n /** The original exported name (differs from name when using `import { foo as bar }`) */\n originalName: string;\n /** The module specifier (package name or relative path) */\n source: string;\n}\n\n/**\n * Extract all named imports from the AST root using structural node traversal.\n * Handles single/double quotes, multiline imports, and aliased imports.\n *\n * @param root - AST root node\n * @returns Array of parsed imports with name, original name, and source\n */\nfunction parseImports(root: SgNode): ParsedImport[] {\n const imports: ParsedImport[] = [];\n\n // Find all import_statement nodes in the AST\n const importStatements = root.findAll({\n rule: { kind: \"import_statement\" },\n });\n\n for (const stmt of importStatements) {\n // Extract the module specifier (the string node, e.g. '@databricks/appkit')\n const sourceNode = stmt.find({ rule: { kind: \"string\" } });\n if (!sourceNode) continue;\n\n // Strip surrounding quotes from the string node text\n const source = sourceNode.text().replace(/^['\"]|['\"]$/g, \"\");\n\n // Find named_imports block: { createApp, analytics, server }\n const namedImports = stmt.find({ rule: { kind: \"named_imports\" } });\n if (!namedImports) continue;\n\n // Extract each import_specifier\n const specifiers = namedImports.findAll({\n rule: { kind: \"import_specifier\" },\n });\n\n for (const specifier of specifiers) {\n const children = specifier.children();\n if (children.length >= 3) {\n // Aliased import: `foo as bar` — children are [name, \"as\", alias]\n const originalName = children[0].text();\n const localName = children[children.length - 1].text();\n imports.push({ name: localName, originalName, source });\n } else {\n // Simple import: `foo`\n const name = specifier.text();\n imports.push({ name, originalName: name, source });\n }\n }\n }\n\n return imports;\n}\n\n/**\n * Extract local names of plugins actually used in the `plugins: [...]` array\n * passed to `createApp()`. Uses structural AST traversal to find `pair` nodes\n * with key \"plugins\" and array values containing call expressions.\n *\n * @param root - AST root node\n * @returns Set of local variable names used as plugin calls in the plugins array\n */\nfunction parsePluginUsages(root: SgNode): Set<string> {\n const usedNames = new Set<string>();\n\n // Find all property pairs in the AST\n const pairs = root.findAll({ rule: { kind: \"pair\" } });\n\n for (const pair of pairs) {\n // Check if the property key is \"plugins\"\n const key = pair.find({ rule: { kind: \"property_identifier\" } });\n if (!key || key.text() !== \"plugins\") continue;\n\n // Find the array value\n const arrayNode = pair.find({ rule: { kind: \"array\" } });\n if (!arrayNode) continue;\n\n // Iterate direct children of the array to find call expressions\n for (const child of arrayNode.children()) {\n if (child.kind() === \"call_expression\") {\n // The callee is the first child (the identifier being called)\n const callee = child.children()[0];\n if (callee?.kind() === \"identifier\") {\n usedNames.add(callee.text());\n }\n }\n }\n }\n\n return usedNames;\n}\n\n/**\n * File extensions to try when resolving a relative import to a file path.\n */\nconst RESOLVE_EXTENSIONS = [\".ts\", \".tsx\", \".js\", \".jsx\"];\n\n/**\n * Resolve a relative import source to the plugin directory containing a manifest.json.\n * Follows the convention that plugins live in their own directory with a manifest.json.\n *\n * Resolution strategy:\n * 1. If the import path is a directory, look for manifest.json directly in it\n * 2. If the import path + extension is a file, look for manifest.json in its parent directory\n * 3. If the import path is a directory with an index file, look for manifest.json in that directory\n *\n * @param importSource - The relative import specifier (e.g. \"./plugins/my-plugin\")\n * @param serverFileDir - Absolute path to the directory containing the server file\n * @returns Absolute path to manifest.json, or null if not found\n */\nfunction resolveLocalManifest(\n importSource: string,\n serverFileDir: string,\n projectRoot?: string,\n): string | null {\n const resolved = path.resolve(serverFileDir, importSource);\n\n // Security: Reject paths that escape the project root\n const boundary = projectRoot || serverFileDir;\n if (!isWithinDirectory(resolved, boundary)) {\n console.warn(\n `Warning: Skipping import \"${importSource}\" — resolves outside the project directory`,\n );\n return null;\n }\n\n // Case 1: Import path is a directory with manifest.json\n // e.g. ./plugins/my-plugin → ./plugins/my-plugin/manifest.json\n if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) {\n const manifestPath = path.join(resolved, \"manifest.json\");\n if (fs.existsSync(manifestPath)) return manifestPath;\n }\n\n // Case 2: Import path + extension resolves to a file\n // e.g. ./plugins/my-plugin → ./plugins/my-plugin.ts\n // Look for manifest.json in the same directory\n for (const ext of RESOLVE_EXTENSIONS) {\n const filePath = `${resolved}${ext}`;\n if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {\n const dir = path.dirname(filePath);\n const manifestPath = path.join(dir, \"manifest.json\");\n if (fs.existsSync(manifestPath)) return manifestPath;\n break;\n }\n }\n\n // Case 3: Import path is a directory with an index file\n // e.g. ./plugins/my-plugin → ./plugins/my-plugin/index.ts\n for (const ext of RESOLVE_EXTENSIONS) {\n const indexPath = path.join(resolved, `index${ext}`);\n if (fs.existsSync(indexPath)) {\n const manifestPath = path.join(resolved, \"manifest.json\");\n if (fs.existsSync(manifestPath)) return manifestPath;\n break;\n }\n }\n\n return null;\n}\n\n/**\n * Discover plugin manifests from local (relative) imports in the server file.\n * Resolves each relative import to a directory and looks for manifest.json.\n *\n * @param relativeImports - Parsed imports with relative sources (starting with . or /)\n * @param serverFileDir - Absolute path to the directory containing the server file\n * @param cwd - Current working directory (for computing relative paths in output)\n * @returns Map of plugin name to template plugin entry for local plugins\n */\nfunction discoverLocalPlugins(\n relativeImports: ParsedImport[],\n serverFileDir: string,\n cwd: string,\n): TemplatePluginsManifest[\"plugins\"] {\n const plugins: TemplatePluginsManifest[\"plugins\"] = {};\n\n for (const imp of relativeImports) {\n const manifestPath = resolveLocalManifest(imp.source, serverFileDir, cwd);\n if (!manifestPath) continue;\n\n try {\n const content = fs.readFileSync(manifestPath, \"utf-8\");\n const parsed = JSON.parse(content);\n const manifest = validateManifestWithSchema(parsed, manifestPath);\n if (!manifest) continue;\n\n const relativePath = path.relative(cwd, path.dirname(manifestPath));\n\n plugins[manifest.name] = {\n name: manifest.name,\n displayName: manifest.displayName,\n description: manifest.description,\n package: `./${relativePath}`,\n resources: manifest.resources,\n };\n } catch (error) {\n console.warn(\n `Warning: Failed to parse manifest at ${manifestPath}:`,\n error instanceof Error ? error.message : error,\n );\n }\n }\n\n return plugins;\n}\n\n/**\n * Discover plugin manifests from a package's dist folder.\n * Looks for manifest.json files in dist/plugins/{plugin-name}/ directories.\n *\n * @param packagePath - Path to the package in node_modules\n * @returns Array of plugin manifests found in the package\n */\nfunction discoverPluginManifests(packagePath: string): PluginManifest[] {\n const pluginsDir = path.join(packagePath, \"dist\", \"plugins\");\n const manifests: PluginManifest[] = [];\n\n if (!fs.existsSync(pluginsDir)) {\n return manifests;\n }\n\n const entries = fs.readdirSync(pluginsDir, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.isDirectory()) {\n const manifestPath = path.join(pluginsDir, entry.name, \"manifest.json\");\n if (fs.existsSync(manifestPath)) {\n try {\n const content = fs.readFileSync(manifestPath, \"utf-8\");\n const parsed = JSON.parse(content);\n const manifest = validateManifestWithSchema(parsed, manifestPath);\n if (manifest) {\n manifests.push(manifest);\n }\n } catch (error) {\n console.warn(\n `Warning: Failed to parse manifest at ${manifestPath}:`,\n error instanceof Error ? error.message : error,\n );\n }\n }\n }\n }\n\n return manifests;\n}\n\n/**\n * Scan node_modules for packages with plugin manifests.\n *\n * @param cwd - Current working directory to search from\n * @param packages - Set of npm package names to scan for plugin manifests\n * @returns Map of plugin name to template plugin entry\n */\nfunction scanForPlugins(\n cwd: string,\n packages: Iterable<string>,\n): TemplatePluginsManifest[\"plugins\"] {\n const plugins: TemplatePluginsManifest[\"plugins\"] = {};\n\n for (const packageName of packages) {\n const packagePath = path.join(cwd, \"node_modules\", packageName);\n if (!fs.existsSync(packagePath)) {\n continue;\n }\n\n const manifests = discoverPluginManifests(packagePath);\n for (const manifest of manifests) {\n // Convert to template plugin format (exclude config schema)\n plugins[manifest.name] = {\n name: manifest.name,\n displayName: manifest.displayName,\n description: manifest.description,\n package: packageName,\n resources: manifest.resources,\n };\n }\n }\n\n return plugins;\n}\n\n/**\n * Scan a directory for plugin manifests in direct subdirectories.\n * Each subdirectory is expected to contain a manifest.json file.\n * Used with --plugins-dir to discover plugins from source instead of node_modules.\n *\n * @param dir - Absolute path to the directory containing plugin subdirectories\n * @param packageName - Package name to assign to discovered plugins\n * @returns Map of plugin name to template plugin entry\n */\nfunction scanPluginsDir(\n dir: string,\n packageName: string,\n): TemplatePluginsManifest[\"plugins\"] {\n const plugins: TemplatePluginsManifest[\"plugins\"] = {};\n\n if (!fs.existsSync(dir)) return plugins;\n\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n\n const manifestPath = path.join(dir, entry.name, \"manifest.json\");\n if (!fs.existsSync(manifestPath)) continue;\n\n try {\n const content = fs.readFileSync(manifestPath, \"utf-8\");\n const parsed = JSON.parse(content);\n const manifest = validateManifestWithSchema(parsed, manifestPath);\n if (manifest) {\n plugins[manifest.name] = {\n name: manifest.name,\n displayName: manifest.displayName,\n description: manifest.description,\n package: packageName,\n resources: manifest.resources,\n };\n }\n } catch (error) {\n console.warn(\n `Warning: Failed to parse manifest at ${manifestPath}:`,\n error instanceof Error ? error.message : error,\n );\n }\n }\n\n return plugins;\n}\n\n/**\n * Write (or preview) the template plugins manifest to disk.\n */\nfunction writeManifest(\n outputPath: string,\n { plugins }: { plugins: TemplatePluginsManifest[\"plugins\"] },\n options: { write?: boolean; silent?: boolean },\n) {\n const templateManifest: TemplatePluginsManifest = {\n $schema:\n \"https://databricks.github.io/appkit/schemas/template-plugins.schema.json\",\n version: \"1.0\",\n plugins,\n };\n\n if (options.write) {\n fs.writeFileSync(\n outputPath,\n `${JSON.stringify(templateManifest, null, 2)}\\n`,\n );\n if (!options.silent) {\n console.log(`\\n✓ Wrote ${outputPath}`);\n }\n } else if (!options.silent) {\n console.log(\"\\nTo write the manifest, run:\");\n console.log(\" npx appkit plugin sync --write\\n\");\n console.log(\"Preview:\");\n console.log(\"─\".repeat(60));\n console.log(JSON.stringify(templateManifest, null, 2));\n console.log(\"─\".repeat(60));\n }\n}\n\n/**\n * Run the plugin sync command.\n * Parses the server entry file to discover which packages to scan for plugin\n * manifests, then marks plugins that are actually used in the `plugins: [...]`\n * array as requiredByTemplate.\n */\nfunction runPluginsSync(options: {\n write?: boolean;\n output?: string;\n silent?: boolean;\n requirePlugins?: string;\n pluginsDir?: string;\n packageName?: string;\n}) {\n const cwd = process.cwd();\n const outputPath = path.resolve(cwd, options.output || \"appkit.plugins.json\");\n\n // Security: Reject output paths that escape the project root\n if (!isWithinDirectory(outputPath, cwd)) {\n console.error(\n `Error: Output path \"${options.output}\" resolves outside the project directory.`,\n );\n process.exit(1);\n }\n\n if (!options.silent) {\n console.log(\"Scanning for AppKit plugins...\\n\");\n }\n\n // Step 1: Parse server file to discover imports and plugin usages\n const serverFile = findServerFile(cwd);\n let serverImports: ParsedImport[] = [];\n let pluginUsages = new Set<string>();\n\n if (serverFile) {\n if (!options.silent) {\n const relativePath = path.relative(cwd, serverFile);\n console.log(`Server entry file: ${relativePath}`);\n }\n\n const content = fs.readFileSync(serverFile, \"utf-8\");\n const lang = serverFile.endsWith(\".tsx\") ? Lang.Tsx : Lang.TypeScript;\n const ast = parse(lang, content);\n const root = ast.root();\n\n serverImports = parseImports(root);\n pluginUsages = parsePluginUsages(root);\n } else if (!options.silent) {\n console.log(\n \"No server entry file found. Checked:\",\n SERVER_FILE_CANDIDATES.join(\", \"),\n );\n }\n\n // Step 2: Split imports into npm packages and local (relative) imports\n const npmImports = serverImports.filter(\n (i) => !i.source.startsWith(\".\") && !i.source.startsWith(\"/\"),\n );\n const localImports = serverImports.filter(\n (i) => i.source.startsWith(\".\") || i.source.startsWith(\"/\"),\n );\n\n // Step 3: Scan for plugin manifests (--plugins-dir or node_modules)\n const plugins: TemplatePluginsManifest[\"plugins\"] = {};\n\n if (options.pluginsDir) {\n const resolvedDir = path.resolve(cwd, options.pluginsDir);\n const pkgName = options.packageName ?? \"@databricks/appkit\";\n if (!options.silent) {\n console.log(`Scanning plugins directory: ${options.pluginsDir}`);\n }\n Object.assign(plugins, scanPluginsDir(resolvedDir, pkgName));\n } else {\n const npmPackages = new Set([\n ...KNOWN_PLUGIN_PACKAGES,\n ...npmImports.map((i) => i.source),\n ]);\n Object.assign(plugins, scanForPlugins(cwd, npmPackages));\n }\n\n // Step 4: Discover local plugin manifests from relative imports\n if (serverFile && localImports.length > 0) {\n const serverFileDir = path.dirname(serverFile);\n const localPlugins = discoverLocalPlugins(localImports, serverFileDir, cwd);\n Object.assign(plugins, localPlugins);\n }\n\n const pluginCount = Object.keys(plugins).length;\n\n if (pluginCount === 0) {\n if (options.silent) {\n writeManifest(outputPath, { plugins: {} }, options);\n return;\n }\n console.log(\"No plugins found.\");\n if (options.pluginsDir) {\n console.log(`\\nNo manifest.json files found in: ${options.pluginsDir}`);\n } else {\n console.log(\"\\nMake sure you have plugin packages installed.\");\n }\n process.exit(1);\n }\n\n // Step 5: Mark plugins that are imported AND used in the plugins array as mandatory.\n // For npm imports, match by package name + plugin name.\n // For local imports, resolve both paths to absolute and compare.\n const serverFileDir = serverFile ? path.dirname(serverFile) : cwd;\n\n for (const imp of serverImports) {\n if (!pluginUsages.has(imp.name)) continue;\n\n const isLocal = imp.source.startsWith(\".\") || imp.source.startsWith(\"/\");\n let plugin: TemplatePlugin | undefined;\n\n if (isLocal) {\n // Resolve the import source to an absolute path from the server file directory\n const resolvedImportDir = path.resolve(serverFileDir, imp.source);\n plugin = Object.values(plugins).find((p) => {\n if (!p.package.startsWith(\".\")) return false;\n const resolvedPluginDir = path.resolve(cwd, p.package);\n return (\n resolvedPluginDir === resolvedImportDir && p.name === imp.originalName\n );\n });\n } else {\n // npm import: direct string comparison\n plugin = Object.values(plugins).find(\n (p) => p.package === imp.source && p.name === imp.originalName,\n );\n }\n\n if (plugin) {\n plugin.requiredByTemplate = true;\n }\n }\n\n // Step 6: Apply explicit --require-plugins overrides\n if (options.requirePlugins) {\n const explicitNames = options.requirePlugins\n .split(\",\")\n .map((s) => s.trim())\n .filter(Boolean);\n for (const name of explicitNames) {\n if (plugins[name]) {\n plugins[name].requiredByTemplate = true;\n } else if (!options.silent) {\n console.warn(\n `Warning: --require-plugins referenced \"${name}\" but no such plugin was discovered`,\n );\n }\n }\n }\n\n if (!options.silent) {\n console.log(`\\nFound ${pluginCount} plugin(s):`);\n for (const [name, manifest] of Object.entries(plugins)) {\n const resourceCount =\n manifest.resources.required.length + manifest.resources.optional.length;\n const resourceInfo =\n resourceCount > 0 ? ` [${resourceCount} resource(s)]` : \"\";\n const mandatoryTag = manifest.requiredByTemplate ? \" (mandatory)\" : \"\";\n console.log(\n ` ${manifest.requiredByTemplate ? \"●\" : \"○\"} ${manifest.displayName} (${name}) from ${manifest.package}${resourceInfo}${mandatoryTag}`,\n );\n }\n }\n\n writeManifest(outputPath, { plugins }, options);\n}\n\n/** Exported for testing: path boundary check, AST parsing. */\nexport { isWithinDirectory, parseImports, parsePluginUsages };\n\nexport const pluginsSyncCommand = new Command(\"sync\")\n .description(\n \"Sync plugin manifests from installed packages into appkit.plugins.json\",\n )\n .option(\"-w, --write\", \"Write the manifest file\")\n .option(\n \"-o, --output <path>\",\n \"Output file path (default: ./appkit.plugins.json)\",\n )\n .option(\n \"-s, --silent\",\n \"Suppress output and never exit with error (for use in predev/prebuild hooks)\",\n )\n .option(\n \"--require-plugins <names>\",\n \"Comma-separated plugin names to mark as requiredByTemplate (e.g. server,analytics)\",\n )\n .option(\n \"--plugins-dir <path>\",\n \"Scan this directory for plugin subdirectories with manifest.json (instead of node_modules)\",\n )\n .option(\n \"--package-name <name>\",\n \"Package name to assign to plugins found via --plugins-dir (default: @databricks/appkit)\",\n )\n .action(runPluginsSync);\n"],"mappings":";;;;;;;;;;;;;;;AAsBA,SAAS,kBAAkB,UAAkB,UAA2B;CACtE,MAAM,eAAe,KAAK,QAAQ,SAAS;CAC3C,MAAM,mBAAmB,KAAK,QAAQ,SAAS;AAE/C,QACE,iBAAiB,oBACjB,aAAa,WAAW,GAAG,mBAAmB,KAAK,MAAM;;;;;;AAQ7D,SAAS,2BACP,KACA,YACuB;CACvB,MAAM,SAAS,iBAAiB,IAAI;AACpC,KAAI,OAAO,SAAS,OAAO,SAAU,QAAO,OAAO;AACnD,KAAI,OAAO,QAAQ,OACjB,SAAQ,KACN,wBAAwB,WAAW,8BAA8B,uBAAuB,OAAO,QAAQ,IAAI,GAC5G;AAEH,QAAO;;;;;;AAOT,MAAM,wBAAwB,CAAC,qBAAqB;;;;;AAMpD,MAAM,yBAAyB,CAAC,oBAAoB,kBAAkB;;;;;;;AAQtE,SAAS,eAAe,KAA4B;AAClD,MAAK,MAAM,aAAa,wBAAwB;EAC9C,MAAM,WAAW,KAAK,KAAK,KAAK,UAAU;AAC1C,MAAI,GAAG,WAAW,SAAS,CACzB,QAAO;;AAGX,QAAO;;;;;;;;;AAsBT,SAAS,aAAa,MAA8B;CAClD,MAAM,UAA0B,EAAE;CAGlC,MAAM,mBAAmB,KAAK,QAAQ,EACpC,MAAM,EAAE,MAAM,oBAAoB,EACnC,CAAC;AAEF,MAAK,MAAM,QAAQ,kBAAkB;EAEnC,MAAM,aAAa,KAAK,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,EAAE,CAAC;AAC1D,MAAI,CAAC,WAAY;EAGjB,MAAM,SAAS,WAAW,MAAM,CAAC,QAAQ,gBAAgB,GAAG;EAG5D,MAAM,eAAe,KAAK,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,EAAE,CAAC;AACnE,MAAI,CAAC,aAAc;EAGnB,MAAM,aAAa,aAAa,QAAQ,EACtC,MAAM,EAAE,MAAM,oBAAoB,EACnC,CAAC;AAEF,OAAK,MAAM,aAAa,YAAY;GAClC,MAAM,WAAW,UAAU,UAAU;AACrC,OAAI,SAAS,UAAU,GAAG;IAExB,MAAM,eAAe,SAAS,GAAG,MAAM;IACvC,MAAM,YAAY,SAAS,SAAS,SAAS,GAAG,MAAM;AACtD,YAAQ,KAAK;KAAE,MAAM;KAAW;KAAc;KAAQ,CAAC;UAClD;IAEL,MAAM,OAAO,UAAU,MAAM;AAC7B,YAAQ,KAAK;KAAE;KAAM,cAAc;KAAM;KAAQ,CAAC;;;;AAKxD,QAAO;;;;;;;;;;AAWT,SAAS,kBAAkB,MAA2B;CACpD,MAAM,4BAAY,IAAI,KAAa;CAGnC,MAAM,QAAQ,KAAK,QAAQ,EAAE,MAAM,EAAE,MAAM,QAAQ,EAAE,CAAC;AAEtD,MAAK,MAAM,QAAQ,OAAO;EAExB,MAAM,MAAM,KAAK,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,EAAE,CAAC;AAChE,MAAI,CAAC,OAAO,IAAI,MAAM,KAAK,UAAW;EAGtC,MAAM,YAAY,KAAK,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,EAAE,CAAC;AACxD,MAAI,CAAC,UAAW;AAGhB,OAAK,MAAM,SAAS,UAAU,UAAU,CACtC,KAAI,MAAM,MAAM,KAAK,mBAAmB;GAEtC,MAAM,SAAS,MAAM,UAAU,CAAC;AAChC,OAAI,QAAQ,MAAM,KAAK,aACrB,WAAU,IAAI,OAAO,MAAM,CAAC;;;AAMpC,QAAO;;;;;AAMT,MAAM,qBAAqB;CAAC;CAAO;CAAQ;CAAO;CAAO;;;;;;;;;;;;;;AAezD,SAAS,qBACP,cACA,eACA,aACe;CACf,MAAM,WAAW,KAAK,QAAQ,eAAe,aAAa;AAI1D,KAAI,CAAC,kBAAkB,UADN,eAAe,cACU,EAAE;AAC1C,UAAQ,KACN,6BAA6B,aAAa,4CAC3C;AACD,SAAO;;AAKT,KAAI,GAAG,WAAW,SAAS,IAAI,GAAG,SAAS,SAAS,CAAC,aAAa,EAAE;EAClE,MAAM,eAAe,KAAK,KAAK,UAAU,gBAAgB;AACzD,MAAI,GAAG,WAAW,aAAa,CAAE,QAAO;;AAM1C,MAAK,MAAM,OAAO,oBAAoB;EACpC,MAAM,WAAW,GAAG,WAAW;AAC/B,MAAI,GAAG,WAAW,SAAS,IAAI,GAAG,SAAS,SAAS,CAAC,QAAQ,EAAE;GAC7D,MAAM,MAAM,KAAK,QAAQ,SAAS;GAClC,MAAM,eAAe,KAAK,KAAK,KAAK,gBAAgB;AACpD,OAAI,GAAG,WAAW,aAAa,CAAE,QAAO;AACxC;;;AAMJ,MAAK,MAAM,OAAO,oBAAoB;EACpC,MAAM,YAAY,KAAK,KAAK,UAAU,QAAQ,MAAM;AACpD,MAAI,GAAG,WAAW,UAAU,EAAE;GAC5B,MAAM,eAAe,KAAK,KAAK,UAAU,gBAAgB;AACzD,OAAI,GAAG,WAAW,aAAa,CAAE,QAAO;AACxC;;;AAIJ,QAAO;;;;;;;;;;;AAYT,SAAS,qBACP,iBACA,eACA,KACoC;CACpC,MAAM,UAA8C,EAAE;AAEtD,MAAK,MAAM,OAAO,iBAAiB;EACjC,MAAM,eAAe,qBAAqB,IAAI,QAAQ,eAAe,IAAI;AACzE,MAAI,CAAC,aAAc;AAEnB,MAAI;GACF,MAAM,UAAU,GAAG,aAAa,cAAc,QAAQ;GAEtD,MAAM,WAAW,2BADF,KAAK,MAAM,QAAQ,EACkB,aAAa;AACjE,OAAI,CAAC,SAAU;GAEf,MAAM,eAAe,KAAK,SAAS,KAAK,KAAK,QAAQ,aAAa,CAAC;AAEnE,WAAQ,SAAS,QAAQ;IACvB,MAAM,SAAS;IACf,aAAa,SAAS;IACtB,aAAa,SAAS;IACtB,SAAS,KAAK;IACd,WAAW,SAAS;IACrB;WACM,OAAO;AACd,WAAQ,KACN,wCAAwC,aAAa,IACrD,iBAAiB,QAAQ,MAAM,UAAU,MAC1C;;;AAIL,QAAO;;;;;;;;;AAUT,SAAS,wBAAwB,aAAuC;CACtE,MAAM,aAAa,KAAK,KAAK,aAAa,QAAQ,UAAU;CAC5D,MAAM,YAA8B,EAAE;AAEtC,KAAI,CAAC,GAAG,WAAW,WAAW,CAC5B,QAAO;CAGT,MAAM,UAAU,GAAG,YAAY,YAAY,EAAE,eAAe,MAAM,CAAC;AACnE,MAAK,MAAM,SAAS,QAClB,KAAI,MAAM,aAAa,EAAE;EACvB,MAAM,eAAe,KAAK,KAAK,YAAY,MAAM,MAAM,gBAAgB;AACvE,MAAI,GAAG,WAAW,aAAa,CAC7B,KAAI;GACF,MAAM,UAAU,GAAG,aAAa,cAAc,QAAQ;GAEtD,MAAM,WAAW,2BADF,KAAK,MAAM,QAAQ,EACkB,aAAa;AACjE,OAAI,SACF,WAAU,KAAK,SAAS;WAEnB,OAAO;AACd,WAAQ,KACN,wCAAwC,aAAa,IACrD,iBAAiB,QAAQ,MAAM,UAAU,MAC1C;;;AAMT,QAAO;;;;;;;;;AAUT,SAAS,eACP,KACA,UACoC;CACpC,MAAM,UAA8C,EAAE;AAEtD,MAAK,MAAM,eAAe,UAAU;EAClC,MAAM,cAAc,KAAK,KAAK,KAAK,gBAAgB,YAAY;AAC/D,MAAI,CAAC,GAAG,WAAW,YAAY,CAC7B;EAGF,MAAM,YAAY,wBAAwB,YAAY;AACtD,OAAK,MAAM,YAAY,UAErB,SAAQ,SAAS,QAAQ;GACvB,MAAM,SAAS;GACf,aAAa,SAAS;GACtB,aAAa,SAAS;GACtB,SAAS;GACT,WAAW,SAAS;GACrB;;AAIL,QAAO;;;;;;;;;;;AAYT,SAAS,eACP,KACA,aACoC;CACpC,MAAM,UAA8C,EAAE;AAEtD,KAAI,CAAC,GAAG,WAAW,IAAI,CAAE,QAAO;CAEhC,MAAM,UAAU,GAAG,YAAY,KAAK,EAAE,eAAe,MAAM,CAAC;AAC5D,MAAK,MAAM,SAAS,SAAS;AAC3B,MAAI,CAAC,MAAM,aAAa,CAAE;EAE1B,MAAM,eAAe,KAAK,KAAK,KAAK,MAAM,MAAM,gBAAgB;AAChE,MAAI,CAAC,GAAG,WAAW,aAAa,CAAE;AAElC,MAAI;GACF,MAAM,UAAU,GAAG,aAAa,cAAc,QAAQ;GAEtD,MAAM,WAAW,2BADF,KAAK,MAAM,QAAQ,EACkB,aAAa;AACjE,OAAI,SACF,SAAQ,SAAS,QAAQ;IACvB,MAAM,SAAS;IACf,aAAa,SAAS;IACtB,aAAa,SAAS;IACtB,SAAS;IACT,WAAW,SAAS;IACrB;WAEI,OAAO;AACd,WAAQ,KACN,wCAAwC,aAAa,IACrD,iBAAiB,QAAQ,MAAM,UAAU,MAC1C;;;AAIL,QAAO;;;;;AAMT,SAAS,cACP,YACA,EAAE,WACF,SACA;CACA,MAAM,mBAA4C;EAChD,SACE;EACF,SAAS;EACT;EACD;AAED,KAAI,QAAQ,OAAO;AACjB,KAAG,cACD,YACA,GAAG,KAAK,UAAU,kBAAkB,MAAM,EAAE,CAAC,IAC9C;AACD,MAAI,CAAC,QAAQ,OACX,SAAQ,IAAI,aAAa,aAAa;YAE/B,CAAC,QAAQ,QAAQ;AAC1B,UAAQ,IAAI,gCAAgC;AAC5C,UAAQ,IAAI,qCAAqC;AACjD,UAAQ,IAAI,WAAW;AACvB,UAAQ,IAAI,IAAI,OAAO,GAAG,CAAC;AAC3B,UAAQ,IAAI,KAAK,UAAU,kBAAkB,MAAM,EAAE,CAAC;AACtD,UAAQ,IAAI,IAAI,OAAO,GAAG,CAAC;;;;;;;;;AAU/B,SAAS,eAAe,SAOrB;CACD,MAAM,MAAM,QAAQ,KAAK;CACzB,MAAM,aAAa,KAAK,QAAQ,KAAK,QAAQ,UAAU,sBAAsB;AAG7E,KAAI,CAAC,kBAAkB,YAAY,IAAI,EAAE;AACvC,UAAQ,MACN,uBAAuB,QAAQ,OAAO,2CACvC;AACD,UAAQ,KAAK,EAAE;;AAGjB,KAAI,CAAC,QAAQ,OACX,SAAQ,IAAI,mCAAmC;CAIjD,MAAM,aAAa,eAAe,IAAI;CACtC,IAAI,gBAAgC,EAAE;CACtC,IAAI,+BAAe,IAAI,KAAa;AAEpC,KAAI,YAAY;AACd,MAAI,CAAC,QAAQ,QAAQ;GACnB,MAAM,eAAe,KAAK,SAAS,KAAK,WAAW;AACnD,WAAQ,IAAI,sBAAsB,eAAe;;EAGnD,MAAM,UAAU,GAAG,aAAa,YAAY,QAAQ;EAGpD,MAAM,OADM,MADC,WAAW,SAAS,OAAO,GAAG,KAAK,MAAM,KAAK,YACnC,QAAQ,CACf,MAAM;AAEvB,kBAAgB,aAAa,KAAK;AAClC,iBAAe,kBAAkB,KAAK;YAC7B,CAAC,QAAQ,OAClB,SAAQ,IACN,wCACA,uBAAuB,KAAK,KAAK,CAClC;CAIH,MAAM,aAAa,cAAc,QAC9B,MAAM,CAAC,EAAE,OAAO,WAAW,IAAI,IAAI,CAAC,EAAE,OAAO,WAAW,IAAI,CAC9D;CACD,MAAM,eAAe,cAAc,QAChC,MAAM,EAAE,OAAO,WAAW,IAAI,IAAI,EAAE,OAAO,WAAW,IAAI,CAC5D;CAGD,MAAM,UAA8C,EAAE;AAEtD,KAAI,QAAQ,YAAY;EACtB,MAAM,cAAc,KAAK,QAAQ,KAAK,QAAQ,WAAW;EACzD,MAAM,UAAU,QAAQ,eAAe;AACvC,MAAI,CAAC,QAAQ,OACX,SAAQ,IAAI,+BAA+B,QAAQ,aAAa;AAElE,SAAO,OAAO,SAAS,eAAe,aAAa,QAAQ,CAAC;QACvD;EACL,MAAM,cAAc,IAAI,IAAI,CAC1B,GAAG,uBACH,GAAG,WAAW,KAAK,MAAM,EAAE,OAAO,CACnC,CAAC;AACF,SAAO,OAAO,SAAS,eAAe,KAAK,YAAY,CAAC;;AAI1D,KAAI,cAAc,aAAa,SAAS,GAAG;EAEzC,MAAM,eAAe,qBAAqB,cADpB,KAAK,QAAQ,WAAW,EACyB,IAAI;AAC3E,SAAO,OAAO,SAAS,aAAa;;CAGtC,MAAM,cAAc,OAAO,KAAK,QAAQ,CAAC;AAEzC,KAAI,gBAAgB,GAAG;AACrB,MAAI,QAAQ,QAAQ;AAClB,iBAAc,YAAY,EAAE,SAAS,EAAE,EAAE,EAAE,QAAQ;AACnD;;AAEF,UAAQ,IAAI,oBAAoB;AAChC,MAAI,QAAQ,WACV,SAAQ,IAAI,sCAAsC,QAAQ,aAAa;MAEvE,SAAQ,IAAI,kDAAkD;AAEhE,UAAQ,KAAK,EAAE;;CAMjB,MAAM,gBAAgB,aAAa,KAAK,QAAQ,WAAW,GAAG;AAE9D,MAAK,MAAM,OAAO,eAAe;AAC/B,MAAI,CAAC,aAAa,IAAI,IAAI,KAAK,CAAE;EAEjC,MAAM,UAAU,IAAI,OAAO,WAAW,IAAI,IAAI,IAAI,OAAO,WAAW,IAAI;EACxE,IAAI;AAEJ,MAAI,SAAS;GAEX,MAAM,oBAAoB,KAAK,QAAQ,eAAe,IAAI,OAAO;AACjE,YAAS,OAAO,OAAO,QAAQ,CAAC,MAAM,MAAM;AAC1C,QAAI,CAAC,EAAE,QAAQ,WAAW,IAAI,CAAE,QAAO;AAEvC,WAD0B,KAAK,QAAQ,KAAK,EAAE,QAAQ,KAE9B,qBAAqB,EAAE,SAAS,IAAI;KAE5D;QAGF,UAAS,OAAO,OAAO,QAAQ,CAAC,MAC7B,MAAM,EAAE,YAAY,IAAI,UAAU,EAAE,SAAS,IAAI,aACnD;AAGH,MAAI,OACF,QAAO,qBAAqB;;AAKhC,KAAI,QAAQ,gBAAgB;EAC1B,MAAM,gBAAgB,QAAQ,eAC3B,MAAM,IAAI,CACV,KAAK,MAAM,EAAE,MAAM,CAAC,CACpB,OAAO,QAAQ;AAClB,OAAK,MAAM,QAAQ,cACjB,KAAI,QAAQ,MACV,SAAQ,MAAM,qBAAqB;WAC1B,CAAC,QAAQ,OAClB,SAAQ,KACN,0CAA0C,KAAK,qCAChD;;AAKP,KAAI,CAAC,QAAQ,QAAQ;AACnB,UAAQ,IAAI,WAAW,YAAY,aAAa;AAChD,OAAK,MAAM,CAAC,MAAM,aAAa,OAAO,QAAQ,QAAQ,EAAE;GACtD,MAAM,gBACJ,SAAS,UAAU,SAAS,SAAS,SAAS,UAAU,SAAS;GACnE,MAAM,eACJ,gBAAgB,IAAI,KAAK,cAAc,iBAAiB;GAC1D,MAAM,eAAe,SAAS,qBAAqB,iBAAiB;AACpE,WAAQ,IACN,KAAK,SAAS,qBAAqB,MAAM,IAAI,GAAG,SAAS,YAAY,IAAI,KAAK,SAAS,SAAS,UAAU,eAAe,eAC1H;;;AAIL,eAAc,YAAY,EAAE,SAAS,EAAE,QAAQ;;AAMjD,MAAa,qBAAqB,IAAI,QAAQ,OAAO,CAClD,YACC,yEACD,CACA,OAAO,eAAe,0BAA0B,CAChD,OACC,uBACA,oDACD,CACA,OACC,gBACA,+EACD,CACA,OACC,6BACA,qFACD,CACA,OACC,wBACA,6FACD,CACA,OACC,yBACA,0FACD,CACA,OAAO,eAAe"}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import Ajv from "ajv";
|
|
5
|
+
import addFormats from "ajv-formats";
|
|
6
|
+
|
|
7
|
+
//#region src/cli/commands/plugin/validate/validate-manifest.ts
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const SCHEMAS_DIR = path.join(__dirname, "..", "..", "..", "..", "schemas");
|
|
10
|
+
const PLUGIN_MANIFEST_SCHEMA_PATH = path.join(SCHEMAS_DIR, "plugin-manifest.schema.json");
|
|
11
|
+
const TEMPLATE_PLUGINS_SCHEMA_PATH = path.join(SCHEMAS_DIR, "template-plugins.schema.json");
|
|
12
|
+
const SCHEMA_ID_MAP = {
|
|
13
|
+
"https://databricks.github.io/appkit/schemas/plugin-manifest.schema.json": "plugin-manifest",
|
|
14
|
+
"https://databricks.github.io/appkit/schemas/template-plugins.schema.json": "template-plugins"
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Detect which schema type a parsed JSON object targets based on its $schema field.
|
|
18
|
+
* Returns "unknown" when the field is missing or unrecognized.
|
|
19
|
+
*/
|
|
20
|
+
function detectSchemaType(obj) {
|
|
21
|
+
if (!obj || typeof obj !== "object") return "unknown";
|
|
22
|
+
const schemaUrl = obj.$schema;
|
|
23
|
+
if (typeof schemaUrl !== "string") return "unknown";
|
|
24
|
+
return SCHEMA_ID_MAP[schemaUrl] ?? "unknown";
|
|
25
|
+
}
|
|
26
|
+
let schemaLoadWarned = false;
|
|
27
|
+
function loadSchema(schemaPath) {
|
|
28
|
+
try {
|
|
29
|
+
return JSON.parse(fs.readFileSync(schemaPath, "utf-8"));
|
|
30
|
+
} catch (err) {
|
|
31
|
+
if (!schemaLoadWarned) {
|
|
32
|
+
schemaLoadWarned = true;
|
|
33
|
+
console.warn(`Warning: Could not load JSON schema at ${schemaPath}: ${err instanceof Error ? err.message : err}. Falling back to basic validation.`);
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
let compiledPluginValidator = null;
|
|
39
|
+
function getPluginValidator() {
|
|
40
|
+
if (compiledPluginValidator) return compiledPluginValidator;
|
|
41
|
+
const schema = loadSchema(PLUGIN_MANIFEST_SCHEMA_PATH);
|
|
42
|
+
if (!schema) return null;
|
|
43
|
+
try {
|
|
44
|
+
const ajv = new Ajv({
|
|
45
|
+
allErrors: true,
|
|
46
|
+
strict: false
|
|
47
|
+
});
|
|
48
|
+
addFormats(ajv);
|
|
49
|
+
compiledPluginValidator = ajv.compile(schema);
|
|
50
|
+
return compiledPluginValidator;
|
|
51
|
+
} catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
let compiledTemplateValidator = null;
|
|
56
|
+
function getTemplateValidator() {
|
|
57
|
+
if (compiledTemplateValidator) return compiledTemplateValidator;
|
|
58
|
+
const pluginSchema = loadSchema(PLUGIN_MANIFEST_SCHEMA_PATH);
|
|
59
|
+
const templateSchema = loadSchema(TEMPLATE_PLUGINS_SCHEMA_PATH);
|
|
60
|
+
if (!pluginSchema || !templateSchema) return null;
|
|
61
|
+
try {
|
|
62
|
+
const ajv = new Ajv({
|
|
63
|
+
allErrors: true,
|
|
64
|
+
strict: false
|
|
65
|
+
});
|
|
66
|
+
addFormats(ajv);
|
|
67
|
+
ajv.addSchema(pluginSchema);
|
|
68
|
+
compiledTemplateValidator = ajv.compile(templateSchema);
|
|
69
|
+
return compiledTemplateValidator;
|
|
70
|
+
} catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Validate a manifest object against the plugin-manifest JSON schema.
|
|
76
|
+
* Returns validation result with optional errors for CLI output.
|
|
77
|
+
*/
|
|
78
|
+
function validateManifest(obj) {
|
|
79
|
+
if (!obj || typeof obj !== "object") return {
|
|
80
|
+
valid: false,
|
|
81
|
+
errors: [{
|
|
82
|
+
instancePath: "",
|
|
83
|
+
message: "Manifest is not a valid object"
|
|
84
|
+
}]
|
|
85
|
+
};
|
|
86
|
+
const validate = getPluginValidator();
|
|
87
|
+
if (!validate) {
|
|
88
|
+
const m = obj;
|
|
89
|
+
if (typeof m.name === "string" && m.name.length > 0 && typeof m.displayName === "string" && m.displayName.length > 0 && typeof m.description === "string" && m.description.length > 0 && m.resources && typeof m.resources === "object" && Array.isArray(m.resources.required)) return {
|
|
90
|
+
valid: true,
|
|
91
|
+
manifest: obj
|
|
92
|
+
};
|
|
93
|
+
return {
|
|
94
|
+
valid: false,
|
|
95
|
+
errors: [{
|
|
96
|
+
instancePath: "",
|
|
97
|
+
message: "Invalid manifest structure"
|
|
98
|
+
}]
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (validate(obj)) return {
|
|
102
|
+
valid: true,
|
|
103
|
+
manifest: obj
|
|
104
|
+
};
|
|
105
|
+
return {
|
|
106
|
+
valid: false,
|
|
107
|
+
errors: validate.errors ?? []
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Validate a template-plugins manifest (appkit.plugins.json) against its schema.
|
|
112
|
+
* Registers the plugin-manifest schema first so external $refs resolve.
|
|
113
|
+
*/
|
|
114
|
+
function validateTemplateManifest(obj) {
|
|
115
|
+
if (!obj || typeof obj !== "object") return {
|
|
116
|
+
valid: false,
|
|
117
|
+
errors: [{
|
|
118
|
+
instancePath: "",
|
|
119
|
+
message: "Template manifest is not a valid object"
|
|
120
|
+
}]
|
|
121
|
+
};
|
|
122
|
+
const validate = getTemplateValidator();
|
|
123
|
+
if (!validate) {
|
|
124
|
+
const m = obj;
|
|
125
|
+
if (typeof m.version === "string" && m.plugins && typeof m.plugins === "object") return { valid: true };
|
|
126
|
+
return {
|
|
127
|
+
valid: false,
|
|
128
|
+
errors: [{
|
|
129
|
+
instancePath: "",
|
|
130
|
+
message: "Invalid template manifest structure"
|
|
131
|
+
}]
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
if (validate(obj)) return { valid: true };
|
|
135
|
+
return {
|
|
136
|
+
valid: false,
|
|
137
|
+
errors: validate.errors ?? []
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Convert a JSON pointer like /resources/required/0/permission
|
|
142
|
+
* to a readable path like resources.required[0].permission
|
|
143
|
+
*/
|
|
144
|
+
function humanizePath(instancePath) {
|
|
145
|
+
if (!instancePath) return "(root)";
|
|
146
|
+
return instancePath.replace(/^\//, "").replace(/\/(\d+)\//g, "[$1].").replace(/\/(\d+)$/g, "[$1]").replace(/\//g, ".");
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Resolve a JSON pointer to the actual value in the parsed object.
|
|
150
|
+
*/
|
|
151
|
+
function resolvePointer(obj, instancePath) {
|
|
152
|
+
if (!instancePath) return obj;
|
|
153
|
+
const segments = instancePath.replace(/^\//, "").split("/");
|
|
154
|
+
let current = obj;
|
|
155
|
+
for (const seg of segments) {
|
|
156
|
+
if (current == null || typeof current !== "object") return void 0;
|
|
157
|
+
current = current[seg];
|
|
158
|
+
}
|
|
159
|
+
return current;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Format schema errors for CLI output.
|
|
163
|
+
* Collapses anyOf/oneOf sub-errors into a single message and shows
|
|
164
|
+
* the actual invalid value when available.
|
|
165
|
+
*
|
|
166
|
+
* @param errors - AJV error objects
|
|
167
|
+
* @param obj - The original parsed object (optional, used to show actual values)
|
|
168
|
+
*/
|
|
169
|
+
function formatValidationErrors(errors, obj) {
|
|
170
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
171
|
+
for (const e of errors) {
|
|
172
|
+
const key = e.instancePath || "/";
|
|
173
|
+
if (!grouped.has(key)) grouped.set(key, []);
|
|
174
|
+
const list = grouped.get(key);
|
|
175
|
+
if (list) list.push(e);
|
|
176
|
+
}
|
|
177
|
+
const lines = [];
|
|
178
|
+
for (const [path, errs] of grouped) {
|
|
179
|
+
const readable = humanizePath(path);
|
|
180
|
+
const anyOfErr = errs.find((e) => e.keyword === "anyOf" || e.keyword === "oneOf");
|
|
181
|
+
if (anyOfErr) {
|
|
182
|
+
const enumErrors = errs.filter((e) => e.keyword === "enum");
|
|
183
|
+
if (enumErrors.length > 0) {
|
|
184
|
+
const allValues = [...new Set(enumErrors.flatMap((e) => e.params?.allowedValues ?? []))];
|
|
185
|
+
const actual = obj !== void 0 ? resolvePointer(obj, path) : void 0;
|
|
186
|
+
const valueHint = actual !== void 0 ? ` (got ${JSON.stringify(actual)})` : "";
|
|
187
|
+
lines.push(` ${readable}: invalid value${valueHint}`, ` allowed: ${allValues.join(", ")}`);
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
for (const e of errs) {
|
|
192
|
+
if (e.keyword === "anyOf" || e.keyword === "oneOf") continue;
|
|
193
|
+
if (e.keyword === "if") continue;
|
|
194
|
+
if (anyOfErr && e.keyword === "enum") continue;
|
|
195
|
+
if (e.keyword === "enum") {
|
|
196
|
+
const allowed = e.params?.allowedValues ?? [];
|
|
197
|
+
const actual = obj !== void 0 ? resolvePointer(obj, path) : void 0;
|
|
198
|
+
const valueHint = actual !== void 0 ? ` (got ${JSON.stringify(actual)})` : "";
|
|
199
|
+
lines.push(` ${readable}: invalid value${valueHint}, allowed: ${allowed.join(", ")}`);
|
|
200
|
+
} else if (e.keyword === "required") lines.push(` ${readable}: missing required property "${e.params?.missingProperty}"`);
|
|
201
|
+
else if (e.keyword === "additionalProperties") lines.push(` ${readable}: unknown property "${e.params?.additionalProperty}"`);
|
|
202
|
+
else if (e.keyword === "pattern") {
|
|
203
|
+
const actual = obj !== void 0 ? resolvePointer(obj, path) : void 0;
|
|
204
|
+
const valueHint = actual !== void 0 ? ` (got ${JSON.stringify(actual)})` : "";
|
|
205
|
+
lines.push(` ${readable}: does not match expected pattern${valueHint}`);
|
|
206
|
+
} else if (e.keyword === "type") lines.push(` ${readable}: expected type "${e.params?.type}"`);
|
|
207
|
+
else if (e.keyword === "minLength") lines.push(` ${readable}: must not be empty`);
|
|
208
|
+
else lines.push(` ${readable}: ${e.message}${e.params ? ` (${JSON.stringify(e.params)})` : ""}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return lines.join("\n");
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
//#endregion
|
|
215
|
+
export { detectSchemaType, formatValidationErrors, validateManifest, validateTemplateManifest };
|
|
216
|
+
//# sourceMappingURL=validate-manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-manifest.js","names":[],"sources":["../../../../../src/cli/commands/plugin/validate/validate-manifest.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport Ajv, { type ErrorObject } from \"ajv\";\nimport addFormats from \"ajv-formats\";\nimport type { PluginManifest } from \"../manifest-types\";\n\nexport type { PluginManifest };\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\nconst SCHEMAS_DIR = path.join(__dirname, \"..\", \"..\", \"..\", \"..\", \"schemas\");\nconst PLUGIN_MANIFEST_SCHEMA_PATH = path.join(\n SCHEMAS_DIR,\n \"plugin-manifest.schema.json\",\n);\nconst TEMPLATE_PLUGINS_SCHEMA_PATH = path.join(\n SCHEMAS_DIR,\n \"template-plugins.schema.json\",\n);\n\nexport type SchemaType = \"plugin-manifest\" | \"template-plugins\" | \"unknown\";\n\nconst SCHEMA_ID_MAP: Record<string, SchemaType> = {\n \"https://databricks.github.io/appkit/schemas/plugin-manifest.schema.json\":\n \"plugin-manifest\",\n \"https://databricks.github.io/appkit/schemas/template-plugins.schema.json\":\n \"template-plugins\",\n};\n\n/**\n * Detect which schema type a parsed JSON object targets based on its $schema field.\n * Returns \"unknown\" when the field is missing or unrecognized.\n */\nexport function detectSchemaType(obj: unknown): SchemaType {\n if (!obj || typeof obj !== \"object\") return \"unknown\";\n const schemaUrl = (obj as Record<string, unknown>).$schema;\n if (typeof schemaUrl !== \"string\") return \"unknown\";\n return SCHEMA_ID_MAP[schemaUrl] ?? \"unknown\";\n}\n\nexport interface ValidateResult {\n valid: boolean;\n manifest?: PluginManifest;\n errors?: ErrorObject[];\n}\n\nlet schemaLoadWarned = false;\n\nfunction loadSchema(schemaPath: string): object | null {\n try {\n return JSON.parse(fs.readFileSync(schemaPath, \"utf-8\")) as object;\n } catch (err) {\n if (!schemaLoadWarned) {\n schemaLoadWarned = true;\n console.warn(\n `Warning: Could not load JSON schema at ${schemaPath}: ${err instanceof Error ? err.message : err}. Falling back to basic validation.`,\n );\n }\n return null;\n }\n}\n\nlet compiledPluginValidator: ReturnType<Ajv[\"compile\"]> | null = null;\n\nfunction getPluginValidator(): ReturnType<Ajv[\"compile\"]> | null {\n if (compiledPluginValidator) return compiledPluginValidator;\n const schema = loadSchema(PLUGIN_MANIFEST_SCHEMA_PATH);\n if (!schema) return null;\n try {\n const ajv = new Ajv({ allErrors: true, strict: false });\n addFormats(ajv);\n compiledPluginValidator = ajv.compile(schema);\n return compiledPluginValidator;\n } catch {\n return null;\n }\n}\n\nlet compiledTemplateValidator: ReturnType<Ajv[\"compile\"]> | null = null;\n\nfunction getTemplateValidator(): ReturnType<Ajv[\"compile\"]> | null {\n if (compiledTemplateValidator) return compiledTemplateValidator;\n const pluginSchema = loadSchema(PLUGIN_MANIFEST_SCHEMA_PATH);\n const templateSchema = loadSchema(TEMPLATE_PLUGINS_SCHEMA_PATH);\n if (!pluginSchema || !templateSchema) return null;\n try {\n const ajv = new Ajv({ allErrors: true, strict: false });\n addFormats(ajv);\n ajv.addSchema(pluginSchema);\n compiledTemplateValidator = ajv.compile(templateSchema);\n return compiledTemplateValidator;\n } catch {\n return null;\n }\n}\n\n/**\n * Validate a manifest object against the plugin-manifest JSON schema.\n * Returns validation result with optional errors for CLI output.\n */\nexport function validateManifest(obj: unknown): ValidateResult {\n if (!obj || typeof obj !== \"object\") {\n return {\n valid: false,\n errors: [\n {\n instancePath: \"\",\n message: \"Manifest is not a valid object\",\n } as ErrorObject,\n ],\n };\n }\n\n const validate = getPluginValidator();\n if (!validate) {\n const m = obj as Record<string, unknown>;\n const basicValid =\n typeof m.name === \"string\" &&\n m.name.length > 0 &&\n typeof m.displayName === \"string\" &&\n m.displayName.length > 0 &&\n typeof m.description === \"string\" &&\n m.description.length > 0 &&\n m.resources &&\n typeof m.resources === \"object\" &&\n Array.isArray((m.resources as { required?: unknown }).required);\n if (basicValid) return { valid: true, manifest: obj as PluginManifest };\n return {\n valid: false,\n errors: [\n {\n instancePath: \"\",\n message: \"Invalid manifest structure\",\n } as ErrorObject,\n ],\n };\n }\n\n const valid = validate(obj);\n if (valid) return { valid: true, manifest: obj as PluginManifest };\n return { valid: false, errors: validate.errors ?? [] };\n}\n\n/**\n * Validate a template-plugins manifest (appkit.plugins.json) against its schema.\n * Registers the plugin-manifest schema first so external $refs resolve.\n */\nexport function validateTemplateManifest(obj: unknown): ValidateResult {\n if (!obj || typeof obj !== \"object\") {\n return {\n valid: false,\n errors: [\n {\n instancePath: \"\",\n message: \"Template manifest is not a valid object\",\n } as ErrorObject,\n ],\n };\n }\n\n const validate = getTemplateValidator();\n if (!validate) {\n const m = obj as Record<string, unknown>;\n const basicValid =\n typeof m.version === \"string\" &&\n m.plugins &&\n typeof m.plugins === \"object\";\n if (basicValid) return { valid: true };\n return {\n valid: false,\n errors: [\n {\n instancePath: \"\",\n message: \"Invalid template manifest structure\",\n } as ErrorObject,\n ],\n };\n }\n\n const valid = validate(obj);\n if (valid) return { valid: true };\n return { valid: false, errors: validate.errors ?? [] };\n}\n\n/**\n * Convert a JSON pointer like /resources/required/0/permission\n * to a readable path like resources.required[0].permission\n */\nfunction humanizePath(instancePath: string): string {\n if (!instancePath) return \"(root)\";\n return instancePath\n .replace(/^\\//, \"\")\n .replace(/\\/(\\d+)\\//g, \"[$1].\")\n .replace(/\\/(\\d+)$/g, \"[$1]\")\n .replace(/\\//g, \".\");\n}\n\n/**\n * Resolve a JSON pointer to the actual value in the parsed object.\n */\nfunction resolvePointer(obj: unknown, instancePath: string): unknown {\n if (!instancePath) return obj;\n const segments = instancePath.replace(/^\\//, \"\").split(\"/\");\n let current: unknown = obj;\n for (const seg of segments) {\n if (current == null || typeof current !== \"object\") return undefined;\n current = (current as Record<string, unknown>)[seg];\n }\n return current;\n}\n\n/**\n * Format schema errors for CLI output.\n * Collapses anyOf/oneOf sub-errors into a single message and shows\n * the actual invalid value when available.\n *\n * @param errors - AJV error objects\n * @param obj - The original parsed object (optional, used to show actual values)\n */\nexport function formatValidationErrors(\n errors: ErrorObject[],\n obj?: unknown,\n): string {\n const grouped = new Map<string, ErrorObject[]>();\n for (const e of errors) {\n const key = e.instancePath || \"/\";\n if (!grouped.has(key)) grouped.set(key, []);\n const list = grouped.get(key);\n if (list) list.push(e);\n }\n\n const lines: string[] = [];\n\n for (const [path, errs] of grouped) {\n const readable = humanizePath(path);\n const anyOfErr = errs.find(\n (e) => e.keyword === \"anyOf\" || e.keyword === \"oneOf\",\n );\n\n if (anyOfErr) {\n const enumErrors = errs.filter((e) => e.keyword === \"enum\");\n if (enumErrors.length > 0) {\n const allValues = [\n ...new Set(\n enumErrors.flatMap(\n (e) => (e.params?.allowedValues as string[]) ?? [],\n ),\n ),\n ];\n const actual =\n obj !== undefined ? resolvePointer(obj, path) : undefined;\n const valueHint =\n actual !== undefined ? ` (got ${JSON.stringify(actual)})` : \"\";\n lines.push(\n ` ${readable}: invalid value${valueHint}`,\n ` allowed: ${allValues.join(\", \")}`,\n );\n continue;\n }\n }\n\n for (const e of errs) {\n if (e.keyword === \"anyOf\" || e.keyword === \"oneOf\") continue;\n if (e.keyword === \"if\") continue;\n if (anyOfErr && e.keyword === \"enum\") continue;\n\n if (e.keyword === \"enum\") {\n const allowed = (e.params?.allowedValues as string[]) ?? [];\n const actual =\n obj !== undefined ? resolvePointer(obj, path) : undefined;\n const valueHint =\n actual !== undefined ? ` (got ${JSON.stringify(actual)})` : \"\";\n lines.push(\n ` ${readable}: invalid value${valueHint}, allowed: ${allowed.join(\", \")}`,\n );\n } else if (e.keyword === \"required\") {\n lines.push(\n ` ${readable}: missing required property \"${e.params?.missingProperty}\"`,\n );\n } else if (e.keyword === \"additionalProperties\") {\n lines.push(\n ` ${readable}: unknown property \"${e.params?.additionalProperty}\"`,\n );\n } else if (e.keyword === \"pattern\") {\n const actual =\n obj !== undefined ? resolvePointer(obj, path) : undefined;\n const valueHint =\n actual !== undefined ? ` (got ${JSON.stringify(actual)})` : \"\";\n lines.push(\n ` ${readable}: does not match expected pattern${valueHint}`,\n );\n } else if (e.keyword === \"type\") {\n lines.push(` ${readable}: expected type \"${e.params?.type}\"`);\n } else if (e.keyword === \"minLength\") {\n lines.push(` ${readable}: must not be empty`);\n } else {\n lines.push(\n ` ${readable}: ${e.message}${e.params ? ` (${JSON.stringify(e.params)})` : \"\"}`,\n );\n }\n }\n }\n\n return lines.join(\"\\n\");\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { detectSchemaType, formatValidationErrors, validateManifest, validateTemplateManifest } from "./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/validate/validate.ts
|
|
8
|
+
function resolveManifestPaths(paths, cwd) {
|
|
9
|
+
const out = [];
|
|
10
|
+
for (const p of paths) {
|
|
11
|
+
const resolved = path.resolve(cwd, p);
|
|
12
|
+
if (!fs.existsSync(resolved)) {
|
|
13
|
+
console.error(`Path not found: ${p}`);
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
if (fs.statSync(resolved).isDirectory()) {
|
|
17
|
+
const pluginManifest = path.join(resolved, "manifest.json");
|
|
18
|
+
const templateManifest = path.join(resolved, "appkit.plugins.json");
|
|
19
|
+
let found = false;
|
|
20
|
+
if (fs.existsSync(pluginManifest)) {
|
|
21
|
+
out.push(pluginManifest);
|
|
22
|
+
found = true;
|
|
23
|
+
}
|
|
24
|
+
if (fs.existsSync(templateManifest)) {
|
|
25
|
+
out.push(templateManifest);
|
|
26
|
+
found = true;
|
|
27
|
+
}
|
|
28
|
+
if (!found) console.error(`No manifest.json or appkit.plugins.json in directory: ${p}`);
|
|
29
|
+
} else out.push(resolved);
|
|
30
|
+
}
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
function runPluginValidate(paths) {
|
|
34
|
+
const cwd = process.cwd();
|
|
35
|
+
const manifestPaths = resolveManifestPaths(paths.length > 0 ? paths : ["."], cwd);
|
|
36
|
+
if (manifestPaths.length === 0) {
|
|
37
|
+
console.error("No manifest files to validate.");
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
let hasFailure = false;
|
|
41
|
+
for (const manifestPath of manifestPaths) {
|
|
42
|
+
let obj;
|
|
43
|
+
try {
|
|
44
|
+
const raw = fs.readFileSync(manifestPath, "utf-8");
|
|
45
|
+
obj = JSON.parse(raw);
|
|
46
|
+
} catch (err) {
|
|
47
|
+
console.error(`✗ ${manifestPath}`);
|
|
48
|
+
console.error(` ${err instanceof Error ? err.message : String(err)}`);
|
|
49
|
+
hasFailure = true;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const result = detectSchemaType(obj) === "template-plugins" ? validateTemplateManifest(obj) : validateManifest(obj);
|
|
53
|
+
const relativePath = path.relative(cwd, manifestPath);
|
|
54
|
+
if (result.valid) console.log(`✓ ${relativePath}`);
|
|
55
|
+
else {
|
|
56
|
+
console.error(`✗ ${relativePath}`);
|
|
57
|
+
if (result.errors?.length) console.error(formatValidationErrors(result.errors, obj));
|
|
58
|
+
hasFailure = true;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
process.exit(hasFailure ? 1 : 0);
|
|
62
|
+
}
|
|
63
|
+
const pluginValidateCommand = new Command("validate").description("Validate plugin manifest(s) or template manifests against their JSON schema").argument("[paths...]", "Paths to manifest.json, appkit.plugins.json, or plugin directories (default: .)").action(runPluginValidate);
|
|
64
|
+
|
|
65
|
+
//#endregion
|
|
66
|
+
export { pluginValidateCommand };
|
|
67
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","names":[],"sources":["../../../../../src/cli/commands/plugin/validate/validate.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport process from \"node:process\";\nimport { Command } from \"commander\";\nimport {\n detectSchemaType,\n formatValidationErrors,\n validateManifest,\n validateTemplateManifest,\n} from \"./validate-manifest\";\n\nfunction resolveManifestPaths(paths: string[], cwd: string): string[] {\n const out: string[] = [];\n for (const p of paths) {\n const resolved = path.resolve(cwd, p);\n if (!fs.existsSync(resolved)) {\n console.error(`Path not found: ${p}`);\n continue;\n }\n const stat = fs.statSync(resolved);\n if (stat.isDirectory()) {\n const pluginManifest = path.join(resolved, \"manifest.json\");\n const templateManifest = path.join(resolved, \"appkit.plugins.json\");\n let found = false;\n if (fs.existsSync(pluginManifest)) {\n out.push(pluginManifest);\n found = true;\n }\n if (fs.existsSync(templateManifest)) {\n out.push(templateManifest);\n found = true;\n }\n if (!found) {\n console.error(\n `No manifest.json or appkit.plugins.json in directory: ${p}`,\n );\n }\n } else {\n out.push(resolved);\n }\n }\n return out;\n}\n\nfunction runPluginValidate(paths: string[]): void {\n const cwd = process.cwd();\n const toValidate = paths.length > 0 ? paths : [\".\"];\n const manifestPaths = resolveManifestPaths(toValidate, cwd);\n\n if (manifestPaths.length === 0) {\n console.error(\"No manifest files to validate.\");\n process.exit(1);\n }\n\n let hasFailure = false;\n for (const manifestPath of manifestPaths) {\n let obj: unknown;\n try {\n const raw = fs.readFileSync(manifestPath, \"utf-8\");\n obj = JSON.parse(raw);\n } catch (err) {\n console.error(`✗ ${manifestPath}`);\n console.error(` ${err instanceof Error ? err.message : String(err)}`);\n hasFailure = true;\n continue;\n }\n\n const schemaType = detectSchemaType(obj);\n const result =\n schemaType === \"template-plugins\"\n ? validateTemplateManifest(obj)\n : validateManifest(obj);\n\n const relativePath = path.relative(cwd, manifestPath);\n if (result.valid) {\n console.log(`✓ ${relativePath}`);\n } else {\n console.error(`✗ ${relativePath}`);\n if (result.errors?.length) {\n console.error(formatValidationErrors(result.errors, obj));\n }\n hasFailure = true;\n }\n }\n\n process.exit(hasFailure ? 1 : 0);\n}\n\nexport const pluginValidateCommand = new Command(\"validate\")\n .description(\n \"Validate plugin manifest(s) or template manifests against their JSON schema\",\n )\n .argument(\n \"[paths...]\",\n \"Paths to manifest.json, appkit.plugins.json, or plugin directories (default: .)\",\n )\n .action(runPluginValidate);\n"],"mappings":";;;;;;;AAWA,SAAS,qBAAqB,OAAiB,KAAuB;CACpE,MAAM,MAAgB,EAAE;AACxB,MAAK,MAAM,KAAK,OAAO;EACrB,MAAM,WAAW,KAAK,QAAQ,KAAK,EAAE;AACrC,MAAI,CAAC,GAAG,WAAW,SAAS,EAAE;AAC5B,WAAQ,MAAM,mBAAmB,IAAI;AACrC;;AAGF,MADa,GAAG,SAAS,SAAS,CACzB,aAAa,EAAE;GACtB,MAAM,iBAAiB,KAAK,KAAK,UAAU,gBAAgB;GAC3D,MAAM,mBAAmB,KAAK,KAAK,UAAU,sBAAsB;GACnE,IAAI,QAAQ;AACZ,OAAI,GAAG,WAAW,eAAe,EAAE;AACjC,QAAI,KAAK,eAAe;AACxB,YAAQ;;AAEV,OAAI,GAAG,WAAW,iBAAiB,EAAE;AACnC,QAAI,KAAK,iBAAiB;AAC1B,YAAQ;;AAEV,OAAI,CAAC,MACH,SAAQ,MACN,yDAAyD,IAC1D;QAGH,KAAI,KAAK,SAAS;;AAGtB,QAAO;;AAGT,SAAS,kBAAkB,OAAuB;CAChD,MAAM,MAAM,QAAQ,KAAK;CAEzB,MAAM,gBAAgB,qBADH,MAAM,SAAS,IAAI,QAAQ,CAAC,IAAI,EACI,IAAI;AAE3D,KAAI,cAAc,WAAW,GAAG;AAC9B,UAAQ,MAAM,iCAAiC;AAC/C,UAAQ,KAAK,EAAE;;CAGjB,IAAI,aAAa;AACjB,MAAK,MAAM,gBAAgB,eAAe;EACxC,IAAI;AACJ,MAAI;GACF,MAAM,MAAM,GAAG,aAAa,cAAc,QAAQ;AAClD,SAAM,KAAK,MAAM,IAAI;WACd,KAAK;AACZ,WAAQ,MAAM,KAAK,eAAe;AAClC,WAAQ,MAAM,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;AACtE,gBAAa;AACb;;EAIF,MAAM,SADa,iBAAiB,IAAI,KAEvB,qBACX,yBAAyB,IAAI,GAC7B,iBAAiB,IAAI;EAE3B,MAAM,eAAe,KAAK,SAAS,KAAK,aAAa;AACrD,MAAI,OAAO,MACT,SAAQ,IAAI,KAAK,eAAe;OAC3B;AACL,WAAQ,MAAM,KAAK,eAAe;AAClC,OAAI,OAAO,QAAQ,OACjB,SAAQ,MAAM,uBAAuB,OAAO,QAAQ,IAAI,CAAC;AAE3D,gBAAa;;;AAIjB,SAAQ,KAAK,aAAa,IAAI,EAAE;;AAGlC,MAAa,wBAAwB,IAAI,QAAQ,WAAW,CACzD,YACC,8EACD,CACA,SACC,cACA,kFACD,CACA,OAAO,kBAAkB"}
|