@databricks/appkit 0.61.1 → 0.63.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +1 -0
- package/dist/agents/databricks.d.ts.map +1 -1
- package/dist/agents/databricks.js.map +1 -1
- package/dist/agents/supervisor-api.d.ts.map +1 -1
- package/dist/agents/supervisor-api.js.map +1 -1
- package/dist/app/index.d.ts.map +1 -1
- package/dist/app/index.js.map +1 -1
- package/dist/appkit/package.js +1 -1
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/cache/index.js.map +1 -1
- package/dist/cache/storage/memory.js.map +1 -1
- package/dist/cache/storage/persistent.js.map +1 -1
- package/dist/cli/commands/codemod/index.js.map +1 -1
- package/dist/cli/commands/codemod/on-plugins-ready.js.map +1 -1
- package/dist/cli/commands/docs.js.map +1 -1
- package/dist/cli/commands/doctor/bundle.js.map +1 -1
- package/dist/cli/commands/doctor/index.js.map +1 -1
- package/dist/cli/commands/doctor/report.js.map +1 -1
- package/dist/cli/commands/doctor/resolve-targets.js.map +1 -1
- package/dist/cli/commands/doctor/run.js.map +1 -1
- package/dist/cli/commands/generate-types.js.map +1 -1
- package/dist/cli/commands/lint.js.map +1 -1
- package/dist/cli/commands/plugin/add-resource/add-resource.js.map +1 -1
- package/dist/cli/commands/plugin/create/create.js.map +1 -1
- package/dist/cli/commands/plugin/create/prompt-resource.js.map +1 -1
- package/dist/cli/commands/plugin/create/scaffold.js.map +1 -1
- package/dist/cli/commands/plugin/index.js.map +1 -1
- package/dist/cli/commands/plugin/list/list.js.map +1 -1
- package/dist/cli/commands/plugin/promote/promote.js.map +1 -1
- package/dist/cli/commands/plugin/sync/sync.js.map +1 -1
- package/dist/cli/commands/plugin/validate/validate-manifest.js.map +1 -1
- package/dist/cli/commands/plugin/validate/validate.js.map +1 -1
- package/dist/cli/commands/registry/add.js.map +1 -1
- package/dist/cli/commands/registry/client.js.map +1 -1
- package/dist/cli/commands/registry/config-writer.js.map +1 -1
- package/dist/cli/commands/registry/env-reconcile.js.map +1 -1
- package/dist/cli/commands/registry/env-writer.js.map +1 -1
- package/dist/cli/commands/registry/index.js.map +1 -1
- package/dist/cli/commands/registry/info.js.map +1 -1
- package/dist/cli/commands/registry/list.js.map +1 -1
- package/dist/cli/commands/registry/requirements.js.map +1 -1
- package/dist/cli/commands/registry/server-register.js.map +1 -1
- package/dist/cli/commands/registry/workspace-picker.js.map +1 -1
- package/dist/cli/commands/setup.js.map +1 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/connectors/files/client.js.map +1 -1
- package/dist/connectors/lakebase/index.d.ts.map +1 -1
- package/dist/connectors/lakebase/index.js.map +1 -1
- package/dist/connectors/lakebase/pool-manager.d.ts.map +1 -1
- package/dist/connectors/lakebase/pool-manager.js.map +1 -1
- package/dist/connectors/lakebase/routing-pool.d.ts.map +1 -1
- package/dist/connectors/lakebase/routing-pool.js.map +1 -1
- package/dist/connectors/mcp/client.d.ts.map +1 -1
- package/dist/connectors/mcp/client.js.map +1 -1
- package/dist/connectors/sql-warehouse/client.js.map +1 -1
- package/dist/context/client-options.js.map +1 -1
- package/dist/context/execution-context.d.ts.map +1 -1
- package/dist/context/execution-context.js.map +1 -1
- package/dist/context/index.d.ts +1 -1
- package/dist/context/service-context.d.ts +55 -3
- package/dist/context/service-context.d.ts.map +1 -1
- package/dist/context/service-context.js.map +1 -1
- package/dist/core/agent/build-toolkit.js.map +1 -1
- package/dist/core/agent/load-agents.d.ts.map +1 -1
- package/dist/core/agent/load-agents.js.map +1 -1
- package/dist/core/agent/run-agent.d.ts.map +1 -1
- package/dist/core/agent/run-agent.js.map +1 -1
- package/dist/core/agent/toolkit-resolver.js.map +1 -1
- package/dist/core/agent/tools/define-tool.d.ts.map +1 -1
- package/dist/core/agent/tools/define-tool.js.map +1 -1
- package/dist/core/agent/tools/tool.d.ts.map +1 -1
- package/dist/core/agent/tools/tool.js.map +1 -1
- package/dist/core/agent/types.d.ts.map +1 -1
- package/dist/core/agent/types.js.map +1 -1
- package/dist/core/appkit.d.ts.map +1 -1
- package/dist/core/appkit.js.map +1 -1
- package/dist/core/lifecycle-manager.js.map +1 -1
- package/dist/core/plugin-context.d.ts +13 -0
- package/dist/core/plugin-context.d.ts.map +1 -1
- package/dist/core/plugin-context.js +12 -1
- package/dist/core/plugin-context.js.map +1 -1
- package/dist/errors/configuration.d.ts.map +1 -1
- package/dist/errors/configuration.js.map +1 -1
- package/dist/logging/logger.js.map +1 -1
- package/dist/logging/wide-event-emitter.js.map +1 -1
- package/dist/plugin/dev-reader.d.ts.map +1 -1
- package/dist/plugin/dev-reader.js.map +1 -1
- package/dist/plugin/interceptors/cache.js.map +1 -1
- package/dist/plugin/interceptors/retry.js.map +1 -1
- package/dist/plugin/interceptors/telemetry.js.map +1 -1
- package/dist/plugin/plugin.d.ts +2 -2
- package/dist/plugin/plugin.d.ts.map +1 -1
- package/dist/plugin/plugin.js.map +1 -1
- package/dist/plugins/agents/agents.d.ts.map +1 -1
- package/dist/plugins/agents/agents.js.map +1 -1
- package/dist/plugins/agents/event-translator.js.map +1 -1
- package/dist/plugins/agents/thread-store.js.map +1 -1
- package/dist/plugins/ai-search/ai-search.d.ts.map +1 -1
- package/dist/plugins/ai-search/ai-search.js.map +1 -1
- package/dist/plugins/analytics/analytics.d.ts.map +1 -1
- package/dist/plugins/analytics/analytics.js.map +1 -1
- package/dist/plugins/analytics/mv/cache.js.map +1 -1
- package/dist/plugins/analytics/mv/constants.js.map +1 -1
- package/dist/plugins/analytics/mv/formatters.js.map +1 -1
- package/dist/plugins/analytics/mv/metadata.js.map +1 -1
- package/dist/plugins/analytics/mv/registry.js.map +1 -1
- package/dist/plugins/analytics/mv/schemas.js.map +1 -1
- package/dist/plugins/analytics/query.js.map +1 -1
- package/dist/plugins/analytics/result-delivery.js.map +1 -1
- package/dist/plugins/files/helpers.js.map +1 -1
- package/dist/plugins/files/plugin.d.ts.map +1 -1
- package/dist/plugins/files/plugin.js.map +1 -1
- package/dist/plugins/files/types.d.ts.map +1 -1
- package/dist/plugins/genie/genie.d.ts.map +1 -1
- package/dist/plugins/genie/genie.js.map +1 -1
- package/dist/plugins/jobs/plugin.d.ts.map +1 -1
- package/dist/plugins/jobs/plugin.js.map +1 -1
- package/dist/plugins/jobs/types.d.ts.map +1 -1
- package/dist/plugins/lakebase/lakebase.d.ts.map +1 -1
- package/dist/plugins/lakebase/lakebase.js.map +1 -1
- package/dist/plugins/lakebase/types.d.ts.map +1 -1
- package/dist/plugins/server/base-server.js.map +1 -1
- package/dist/plugins/server/client-config-sanitizer.js.map +1 -1
- package/dist/plugins/server/index.d.ts.map +1 -1
- package/dist/plugins/server/index.js.map +1 -1
- package/dist/plugins/server/react-source-loc-vite-plugin.js.map +1 -1
- package/dist/plugins/server/remote-tunnel/remote-tunnel-controller.js.map +1 -1
- package/dist/plugins/server/remote-tunnel/remote-tunnel-manager.js.map +1 -1
- package/dist/plugins/server/static-server.js.map +1 -1
- package/dist/plugins/server/utils.js.map +1 -1
- package/dist/plugins/server/vite-dev-server.js.map +1 -1
- package/dist/plugins/serving/schema-filter.js.map +1 -1
- package/dist/plugins/serving/serving.d.ts.map +1 -1
- package/dist/plugins/serving/serving.js.map +1 -1
- package/dist/plugins/ui-variants/index.js.map +1 -1
- package/dist/registry/manifest-loader.d.ts.map +1 -1
- package/dist/registry/manifest-loader.js.map +1 -1
- package/dist/registry/resource-registry.d.ts.map +1 -1
- package/dist/registry/resource-registry.js.map +1 -1
- package/dist/registry/types.d.ts.map +1 -1
- package/dist/registry/types.js.map +1 -1
- package/dist/schemas/manifest.d.ts.map +1 -1
- package/dist/schemas/manifest.js.map +1 -1
- package/dist/schemas/metric-fqn.js.map +1 -1
- package/dist/shared/src/plugin.d.ts.map +1 -1
- package/dist/shared/src/schemas/manifest.d.ts.map +1 -1
- package/dist/shared/src/schemas/metric-fqn.js.map +1 -1
- package/dist/shared/src/schemas/metric-metadata-bundle.js.map +1 -1
- package/dist/shared/src/schemas/metric-source.js.map +1 -1
- package/dist/shared/src/sse/analytics.js.map +1 -1
- package/dist/shared/src/workspace-client/legacy.js.map +1 -1
- package/dist/shared/src/workspace-client/types.d.ts.map +1 -1
- package/dist/stream/sse-writer.js.map +1 -1
- package/dist/stream/stream-manager.d.ts.map +1 -1
- package/dist/stream/stream-manager.js.map +1 -1
- package/dist/stream/types.js.map +1 -1
- package/dist/telemetry/instrumentations.js.map +1 -1
- package/dist/telemetry/telemetry-manager.js.map +1 -1
- package/dist/telemetry/telemetry-provider.js.map +1 -1
- package/dist/telemetry/trace-sampler.js.map +1 -1
- package/dist/testing/expect-stream.d.ts +111 -0
- package/dist/testing/expect-stream.d.ts.map +1 -0
- package/dist/testing/expect-stream.js +154 -0
- package/dist/testing/expect-stream.js.map +1 -0
- package/dist/testing/fixtures.d.ts +257 -0
- package/dist/testing/fixtures.d.ts.map +1 -0
- package/dist/testing/fixtures.js +385 -0
- package/dist/testing/fixtures.js.map +1 -0
- package/dist/testing/index.d.ts +5 -0
- package/dist/testing/index.js +5 -0
- package/dist/testing/test-plugin-context.d.ts +158 -0
- package/dist/testing/test-plugin-context.d.ts.map +1 -0
- package/dist/testing/test-plugin-context.js +139 -0
- package/dist/testing/test-plugin-context.js.map +1 -0
- package/dist/type-generator/cache.js.map +1 -1
- package/dist/type-generator/index.js.map +1 -1
- package/dist/type-generator/migration.js.map +1 -1
- package/dist/type-generator/mv-registry/config.js.map +1 -1
- package/dist/type-generator/query-registry.js.map +1 -1
- package/dist/type-generator/serving/cache.js.map +1 -1
- package/dist/type-generator/serving/generator.js.map +1 -1
- package/dist/type-generator/serving/server-file-extractor.d.ts.map +1 -1
- package/dist/type-generator/serving/server-file-extractor.js.map +1 -1
- package/dist/type-generator/serving/vite-plugin.d.ts.map +1 -1
- package/dist/type-generator/serving/vite-plugin.js.map +1 -1
- package/dist/type-generator/vite-plugin.d.ts.map +1 -1
- package/dist/type-generator/vite-plugin.js.map +1 -1
- package/dist/workspace-client/legacy.js.map +1 -1
- package/dist/workspace-client/types.d.ts.map +1 -1
- package/docs/plugins/testing.md +245 -0
- package/llms.txt +1 -0
- package/package.json +15 -2
- package/sbom.cdx.json +1 -1
- package/skills/appkit-ui-variants/SKILL.md +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"promote.js","names":[],"sources":["../../../../../src/cli/commands/plugin/promote/promote.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport process from \"node:process\";\nimport { Command } from \"commander\";\nimport { kebabToCamel } from \"../../../../naming\";\nimport { resolveManifestInDir } from \"../manifest-resolve\";\nimport { isWithinDirectory } from \"../sync/sync\";\nimport { shouldAllowJsManifestForDir } from \"../trusted-js-manifest\";\n\ntype Stability = \"beta\" | \"ga\";\n\nconst TIER_ORDER: Record<Stability, number> = {\n beta: 0,\n ga: 1,\n};\n\nconst IMPORT_PATH_MAP: Record<Stability, string> = {\n beta: \"/beta\",\n ga: \"\",\n};\n\n/** Aligned with sync.ts and list.ts; keep all plugin-tree walks at the same cap. */\nconst MAX_SCAN_DEPTH = 5;\n\n/**\n * Directories that should never be walked when discovering plugin manifests\n * or rewriting imports. Mirrors common build/output trees.\n */\nconst SKIP_DIRECTORIES = new Set([\n \"node_modules\",\n \"dist\",\n \"build\",\n \"out\",\n \".git\",\n \".turbo\",\n \".next\",\n \".nuxt\",\n \".cache\",\n \".svelte-kit\",\n \".vite\",\n \".parcel-cache\",\n \"coverage\",\n]);\n\n/**\n * Plugin name charset accepted by the promote command. Mirrors npm package\n * naming (lowercase, dashes, underscores, dots, optional @scope/) and explicitly\n * forbids path separators, traversal, and NUL — so the name cannot escape its\n * intended directory when used in path.join().\n */\nconst PLUGIN_NAME_PATTERN =\n /^(?:@[a-z0-9][a-z0-9._-]*\\/)?[a-z0-9][a-z0-9._-]*$/i;\n\nfunction validatePluginName(pluginName: string): void {\n if (!pluginName || pluginName.includes(\"\\0\") || pluginName.includes(\"..\")) {\n throw new Error(\n `Invalid plugin name \"${pluginName}\". Plugin names must not contain \"..\", or null bytes.`,\n );\n }\n // Backslash is never allowed (treated as a path separator on Windows).\n if (pluginName.includes(\"\\\\\")) {\n throw new Error(\n `Invalid plugin name \"${pluginName}\". Plugin names must not contain backslashes.`,\n );\n }\n // Forward slash is only allowed inside the @scope/name form.\n if (pluginName.includes(\"/\") && !pluginName.startsWith(\"@\")) {\n throw new Error(\n `Invalid plugin name \"${pluginName}\". Plugin names must not contain \"/\" unless they are a scoped package (e.g. @scope/name).`,\n );\n }\n if (!PLUGIN_NAME_PATTERN.test(pluginName)) {\n throw new Error(\n `Invalid plugin name \"${pluginName}\". Expected lowercase alphanumeric with optional dashes, underscores, dots, or @scope/ prefix.`,\n );\n }\n}\n\nfunction isStability(value: unknown): value is Stability {\n return value === \"beta\" || value === \"ga\";\n}\n\nfunction findPluginManifest(\n pluginName: string,\n cwd: string,\n): { manifestPath: string; isLocal: boolean } | null {\n const dirsToScan = [\"plugins\", \"server\", \".\"];\n\n for (const dir of dirsToScan) {\n const absDir = path.resolve(cwd, dir);\n const result = scanDirForPlugin(absDir, pluginName, cwd, 0);\n if (result) return { manifestPath: result, isLocal: true };\n }\n\n // node_modules fallback. Validate and bound to the dist/plugins subtree to\n // make sure a malicious or typo'd name cannot escape via path.join.\n const nodeModulesDir = path.join(cwd, \"node_modules\", \"@databricks/appkit\");\n if (fs.existsSync(nodeModulesDir)) {\n const pluginsDir = path.join(nodeModulesDir, \"dist\", \"plugins\");\n if (fs.existsSync(pluginsDir)) {\n const manifestPath = path.resolve(\n pluginsDir,\n pluginName,\n \"manifest.json\",\n );\n if (\n isWithinDirectory(manifestPath, pluginsDir) &&\n fs.existsSync(manifestPath)\n ) {\n return { manifestPath, isLocal: false };\n }\n }\n }\n\n return null;\n}\n\nfunction scanDirForPlugin(\n dir: string,\n pluginName: string,\n cwd: string,\n depth: number,\n): string | null {\n if (depth >= MAX_SCAN_DEPTH) return null;\n let stat: fs.Stats;\n try {\n stat = fs.statSync(dir);\n } catch {\n return null;\n }\n if (!stat.isDirectory()) return null;\n\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n if (SKIP_DIRECTORIES.has(entry.name)) continue;\n if (entry.isSymbolicLink()) continue;\n const childPath = path.join(dir, entry.name);\n const allowJs = shouldAllowJsManifestForDir(childPath);\n const resolved = resolveManifestInDir(childPath, {\n allowJsManifest: allowJs,\n });\n\n if (resolved) {\n try {\n const obj = loadManifestFromFileSync(resolved.path);\n if (\n obj &&\n typeof obj === \"object\" &&\n \"name\" in obj &&\n (obj as { name: string }).name === pluginName\n ) {\n return resolved.path;\n }\n } catch {\n // skip unreadable / invalid manifest\n }\n continue;\n }\n\n const deeper = scanDirForPlugin(childPath, pluginName, cwd, depth + 1);\n if (deeper) return deeper;\n }\n return null;\n}\n\nfunction loadManifestFromFileSync(filePath: string): unknown {\n const raw = fs.readFileSync(filePath, \"utf-8\");\n return JSON.parse(raw);\n}\n\nfunction escapeRegex(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n/**\n * Returns true when the named import specifier `spec` resolves to either\n * `pluginName` itself or its kebab-to-camelCase JS-identifier form.\n * Handles the `name`, `name as alias`, and inline-type (`type name`,\n * `type name as alias`) forms. Matches on the imported binding before any\n * `as` rename so a promotion finds the right specifier regardless of how\n * the user aliased it.\n */\nfunction specifierMatchesPlugin(spec: string, pluginName: string): boolean {\n const stripped = spec.replace(/^type\\s+/, \"\").trim();\n const head = stripped.split(/\\s+as\\s+/)[0]?.trim();\n if (!head) return false;\n return head === pluginName || head === kebabToCamel(pluginName);\n}\n\n/**\n * Rewrite imports of `pluginName` from `<pkg><oldSuffix>` to `<pkg><newSuffix>`\n * across one file, leaving every OTHER specifier on the same import line at\n * its original source. The naïve `split/join` approach this replaced was\n * promoting *every* beta specifier in the file along with the targeted\n * plugin — a bug because beta specifiers don't exist at the GA subpath.\n *\n * Behaviour:\n * - If the targeted plugin is the only specifier on an import line, the\n * line's source is rewritten to the new path.\n * - If the import has multiple specifiers, the targeted one is moved to a\n * newly-emitted import line at the new source, and the original import\n * keeps the remaining specifiers at the old source.\n * - Imports that don't reference `pluginName` are left untouched.\n *\n * Multi-line specifier lists, type-only imports (`import type { ... }`),\n * inline-type specifiers (`import { type Foo }`), and `as`-aliased\n * specifiers are all preserved through the rewrite.\n */\nfunction rewriteImportsInFile(\n filePath: string,\n pluginName: string,\n oldSuffix: string,\n newSuffix: string,\n dryRun: boolean,\n): { file: string; from: string; to: string } | null {\n const content = fs.readFileSync(filePath, \"utf-8\");\n\n const packages = [\n \"@databricks/appkit\",\n \"@databricks/appkit-ui/js\",\n \"@databricks/appkit-ui/react\",\n ];\n let updated = content;\n let changed = false;\n\n for (const pkg of packages) {\n const oldPath = `${pkg}${oldSuffix}`;\n const newPath = `${pkg}${newSuffix}`;\n\n // Match: `import [type] { ...specifiers... } from \"<oldPath>\";`\n // - `[^}]*` lets the specifier list span newlines (safe — TS imports\n // don't have nested braces inside the specifier list).\n // - Captures the `type ` keyword (if any), the specifier body, and\n // the surrounding quote style so we can preserve them on output.\n const importRe = new RegExp(\n `import\\\\s+(type\\\\s+)?\\\\{([^}]*)\\\\}\\\\s*from\\\\s*([\"'])${escapeRegex(oldPath)}\\\\3\\\\s*;?`,\n \"g\",\n );\n\n updated = updated.replace(\n importRe,\n (full, typeKeyword, specifiers, quote) => {\n const specList: string[] = specifiers\n .split(\",\")\n .map((s: string) => s.trim())\n .filter((s: string) => s.length > 0);\n\n const promotedSpec = specList.find((s) =>\n specifierMatchesPlugin(s, pluginName),\n );\n if (!promotedSpec) {\n // Plugin not in this import — leave it alone.\n return full;\n }\n\n const remaining = specList.filter(\n (s) => !specifierMatchesPlugin(s, pluginName),\n );\n changed = true;\n\n const tk = typeKeyword ?? \"\";\n const promotedImport = `import ${tk}{ ${promotedSpec} } from ${quote}${newPath}${quote};`;\n\n if (remaining.length === 0) {\n // Only specifier — just rewrite the source.\n return promotedImport;\n }\n\n const remainingImport = `import ${tk}{ ${remaining.join(\", \")} } from ${quote}${oldPath}${quote};`;\n return `${remainingImport}\\n${promotedImport}`;\n },\n );\n }\n\n if (!changed) return null;\n\n if (!dryRun) {\n fs.writeFileSync(filePath, updated);\n }\n\n return {\n file: filePath,\n from: oldSuffix || \"(root)\",\n to: newSuffix || \"(root)\",\n };\n}\n\nfunction findTsFiles(dir: string, projectRoot: string, depth = 0): string[] {\n if (depth >= MAX_SCAN_DEPTH) return [];\n\n const results: string[] = [];\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true });\n } catch {\n return results;\n }\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isSymbolicLink()) continue;\n if (entry.isDirectory()) {\n if (SKIP_DIRECTORIES.has(entry.name)) continue;\n // Boundary check to ensure recursion stays inside the project root,\n // even if a future change introduces a symlink-following path.\n if (!isWithinDirectory(fullPath, projectRoot)) continue;\n results.push(...findTsFiles(fullPath, projectRoot, depth + 1));\n } else if (entry.name.endsWith(\".ts\") || entry.name.endsWith(\".tsx\")) {\n results.push(fullPath);\n }\n }\n\n return results;\n}\n\nasync function runPromote(\n pluginName: string,\n options: {\n to: string;\n dryRun?: boolean;\n skipImports?: boolean;\n skipSync?: boolean;\n allowInstalled?: boolean;\n },\n): Promise<void> {\n validatePluginName(pluginName);\n\n const cwd = process.cwd();\n\n if (!isStability(options.to)) {\n console.error(\n `Invalid target tier \"${options.to}\". Must be one of: beta, ga.`,\n );\n process.exit(1);\n }\n const target: Stability = options.to;\n\n const found = findPluginManifest(pluginName, cwd);\n if (!found) {\n console.error(\n `Plugin \"${pluginName}\" not found. Searched local dirs (plugins, server, .) and node_modules.`,\n );\n process.exit(1);\n }\n\n const { manifestPath, isLocal } = found;\n\n if (!isLocal && !options.allowInstalled) {\n console.error(\n `Plugin \"${pluginName}\" was only found under node_modules (${path.relative(cwd, manifestPath)}).\\n` +\n `Refusing to mutate an installed package — re-install would overwrite the change.\\n` +\n `Pass --allow-installed to override (advanced; not recommended).`,\n );\n process.exit(1);\n }\n\n let raw: Record<string, unknown>;\n try {\n const parsed = loadManifestFromFileSync(manifestPath);\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n console.error(\n `Manifest at ${path.relative(cwd, manifestPath)} is not a JSON object.`,\n );\n process.exit(1);\n }\n raw = parsed as Record<string, unknown>;\n } catch (err) {\n console.error(\n `Failed to read manifest at ${path.relative(cwd, manifestPath)}: ${err instanceof Error ? err.message : String(err)}`,\n );\n process.exit(1);\n }\n\n const rawStability = raw.stability ?? \"ga\";\n if (!isStability(rawStability)) {\n console.error(\n `Manifest at ${path.relative(cwd, manifestPath)} has an invalid stability value \"${String(rawStability)}\". ` +\n `Must be one of: beta, ga (or omitted for ga).`,\n );\n process.exit(1);\n }\n const currentStability: Stability = rawStability;\n\n if (currentStability === target) {\n console.error(\n `Plugin \"${pluginName}\" is already at \"${target}\". Nothing to do.`,\n );\n process.exit(1);\n }\n\n if (TIER_ORDER[target] <= TIER_ORDER[currentStability]) {\n console.error(\n `Cannot demote \"${pluginName}\" from \"${currentStability}\" to \"${target}\". Promotion is one-way only.`,\n );\n process.exit(1);\n }\n\n const prefix = options.dryRun ? \"[dry-run] \" : \"\";\n\n if (target === \"ga\") {\n delete raw.stability;\n } else {\n raw.stability = target;\n }\n\n if (!options.dryRun) {\n fs.writeFileSync(manifestPath, `${JSON.stringify(raw, null, 2)}\\n`);\n }\n console.log(\n `${prefix}Updated manifest: ${path.relative(cwd, manifestPath)} (${currentStability} → ${target})`,\n );\n\n const importRewrites: { file: string; from: string; to: string }[] = [];\n if (!options.skipImports) {\n const oldSuffix = IMPORT_PATH_MAP[currentStability];\n const newSuffix = IMPORT_PATH_MAP[target];\n\n const tsFiles = findTsFiles(cwd, cwd);\n for (const file of tsFiles) {\n const result = rewriteImportsInFile(\n file,\n pluginName,\n oldSuffix,\n newSuffix,\n Boolean(options.dryRun),\n );\n if (result) {\n importRewrites.push(result);\n console.log(\n `${prefix}Rewritten imports in: ${path.relative(cwd, file)}`,\n );\n }\n }\n\n if (importRewrites.length === 0) {\n console.log(`${prefix}No import paths to rewrite.`);\n }\n }\n\n if (!options.skipSync && !options.dryRun) {\n // Monorepo-only: regenerate every artifact derived from plugin\n // manifests (ga/beta export barrels AND the per-plugin docs-page\n // stability banner) so all manifest-derived layers move together.\n // No-op outside the AppKit monorepo (third-party plugin projects don't\n // ship the generators).\n const generatorPath = path.join(cwd, \"tools\", \"generate-plugin-entries.ts\");\n if (fs.existsSync(generatorPath)) {\n console.log(`\\n${prefix}Regenerating manifest-derived artifacts...`);\n const { execSync } = await import(\"node:child_process\");\n try {\n execSync(\"pnpm run generate:types\", { cwd, stdio: \"inherit\" });\n } catch {\n console.error(\n `Error: post-promote 'generate:types' failed. ` +\n `Manifest was updated, but generated barrels and docs banners may be stale. ` +\n `Run 'pnpm run generate:types' manually.`,\n );\n process.exit(1);\n }\n }\n\n console.log(`\\n${prefix}Running plugin sync...`);\n const { execSync } = await import(\"node:child_process\");\n // Monorepo flavor: the AppKit monorepo's `pnpm sync:template` script\n // points sync at `template/appkit.plugins.json` (the file shipped to\n // consumers and read by the Go init template), not the project-root\n // default. Detect the monorepo via the same generator-path probe used\n // above and prefer the script when available so the manifest, the\n // synced template, and the runtime barrels stay aligned.\n const syncCommand = fs.existsSync(generatorPath)\n ? \"pnpm run sync:template\"\n : \"npx appkit plugin sync --write\";\n try {\n execSync(syncCommand, {\n cwd,\n stdio: \"inherit\",\n });\n } catch {\n console.error(\n `Error: post-promote sync ('${syncCommand}') failed. ` +\n `Manifest and imports were updated, but the synced plugin manifest ` +\n `may be stale. Run '${syncCommand}' manually.`,\n );\n process.exit(1);\n }\n }\n\n console.log(\n `\\n${prefix}Promotion complete: ${pluginName} ${currentStability} → ${target}`,\n );\n if (importRewrites.length > 0) {\n console.log(` ${importRewrites.length} file(s) with import rewrites`);\n }\n}\n\n/** Exported for testing. */\nexport {\n PLUGIN_NAME_PATTERN,\n TIER_ORDER,\n IMPORT_PATH_MAP,\n SKIP_DIRECTORIES,\n isStability,\n validatePluginName,\n rewriteImportsInFile,\n runPromote,\n};\n\nexport const pluginPromoteCommand = new Command(\"promote\")\n .description(\"Promote a plugin to a higher stability tier\")\n .argument(\"<plugin-name>\", \"Plugin name to promote\")\n .requiredOption(\"--to <tier>\", \"Target stability tier (beta, ga)\")\n .option(\"--dry-run\", \"Show what would change without modifying files\")\n .option(\"--skip-imports\", \"Only update manifest, skip import path rewriting\")\n .option(\"--skip-sync\", \"Don't auto-run plugin sync after promotion\")\n .option(\n \"--allow-installed\",\n \"Allow promoting a plugin that lives only under node_modules (advanced)\",\n )\n .action((pluginName, opts) =>\n runPromote(pluginName, opts).catch((err) => {\n const message = err instanceof Error ? err.message : String(err);\n console.error(`Error: ${message}`);\n if (process.env.DEBUG) console.error(err);\n process.exit(1);\n }),\n );\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"promote.js","names":[],"sources":["../../../../../src/cli/commands/plugin/promote/promote.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport process from \"node:process\";\n\nimport { Command } from \"commander\";\n\nimport { kebabToCamel } from \"../../../../naming\";\nimport { resolveManifestInDir } from \"../manifest-resolve\";\nimport { isWithinDirectory } from \"../sync/sync\";\nimport { shouldAllowJsManifestForDir } from \"../trusted-js-manifest\";\n\ntype Stability = \"beta\" | \"ga\";\n\nconst TIER_ORDER: Record<Stability, number> = {\n beta: 0,\n ga: 1,\n};\n\nconst IMPORT_PATH_MAP: Record<Stability, string> = {\n beta: \"/beta\",\n ga: \"\",\n};\n\n/** Aligned with sync.ts and list.ts; keep all plugin-tree walks at the same cap. */\nconst MAX_SCAN_DEPTH = 5;\n\n/**\n * Directories that should never be walked when discovering plugin manifests\n * or rewriting imports. Mirrors common build/output trees.\n */\nconst SKIP_DIRECTORIES = new Set([\n \"node_modules\",\n \"dist\",\n \"build\",\n \"out\",\n \".git\",\n \".turbo\",\n \".next\",\n \".nuxt\",\n \".cache\",\n \".svelte-kit\",\n \".vite\",\n \".parcel-cache\",\n \"coverage\",\n]);\n\n/**\n * Plugin name charset accepted by the promote command. Mirrors npm package\n * naming (lowercase, dashes, underscores, dots, optional @scope/) and explicitly\n * forbids path separators, traversal, and NUL — so the name cannot escape its\n * intended directory when used in path.join().\n */\nconst PLUGIN_NAME_PATTERN =\n /^(?:@[a-z0-9][a-z0-9._-]*\\/)?[a-z0-9][a-z0-9._-]*$/i;\n\nfunction validatePluginName(pluginName: string): void {\n if (!pluginName || pluginName.includes(\"\\0\") || pluginName.includes(\"..\")) {\n throw new Error(\n `Invalid plugin name \"${pluginName}\". Plugin names must not contain \"..\", or null bytes.`,\n );\n }\n // Backslash is never allowed (treated as a path separator on Windows).\n if (pluginName.includes(\"\\\\\")) {\n throw new Error(\n `Invalid plugin name \"${pluginName}\". Plugin names must not contain backslashes.`,\n );\n }\n // Forward slash is only allowed inside the @scope/name form.\n if (pluginName.includes(\"/\") && !pluginName.startsWith(\"@\")) {\n throw new Error(\n `Invalid plugin name \"${pluginName}\". Plugin names must not contain \"/\" unless they are a scoped package (e.g. @scope/name).`,\n );\n }\n if (!PLUGIN_NAME_PATTERN.test(pluginName)) {\n throw new Error(\n `Invalid plugin name \"${pluginName}\". Expected lowercase alphanumeric with optional dashes, underscores, dots, or @scope/ prefix.`,\n );\n }\n}\n\nfunction isStability(value: unknown): value is Stability {\n return value === \"beta\" || value === \"ga\";\n}\n\nfunction findPluginManifest(\n pluginName: string,\n cwd: string,\n): { manifestPath: string; isLocal: boolean } | null {\n const dirsToScan = [\"plugins\", \"server\", \".\"];\n\n for (const dir of dirsToScan) {\n const absDir = path.resolve(cwd, dir);\n const result = scanDirForPlugin(absDir, pluginName, cwd, 0);\n if (result) return { manifestPath: result, isLocal: true };\n }\n\n // node_modules fallback. Validate and bound to the dist/plugins subtree to\n // make sure a malicious or typo'd name cannot escape via path.join.\n const nodeModulesDir = path.join(cwd, \"node_modules\", \"@databricks/appkit\");\n if (fs.existsSync(nodeModulesDir)) {\n const pluginsDir = path.join(nodeModulesDir, \"dist\", \"plugins\");\n if (fs.existsSync(pluginsDir)) {\n const manifestPath = path.resolve(\n pluginsDir,\n pluginName,\n \"manifest.json\",\n );\n if (\n isWithinDirectory(manifestPath, pluginsDir) &&\n fs.existsSync(manifestPath)\n ) {\n return { manifestPath, isLocal: false };\n }\n }\n }\n\n return null;\n}\n\nfunction scanDirForPlugin(\n dir: string,\n pluginName: string,\n cwd: string,\n depth: number,\n): string | null {\n if (depth >= MAX_SCAN_DEPTH) return null;\n let stat: fs.Stats;\n try {\n stat = fs.statSync(dir);\n } catch {\n return null;\n }\n if (!stat.isDirectory()) return null;\n\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n if (SKIP_DIRECTORIES.has(entry.name)) continue;\n if (entry.isSymbolicLink()) continue;\n const childPath = path.join(dir, entry.name);\n const allowJs = shouldAllowJsManifestForDir(childPath);\n const resolved = resolveManifestInDir(childPath, {\n allowJsManifest: allowJs,\n });\n\n if (resolved) {\n try {\n const obj = loadManifestFromFileSync(resolved.path);\n if (\n obj &&\n typeof obj === \"object\" &&\n \"name\" in obj &&\n (obj as { name: string }).name === pluginName\n ) {\n return resolved.path;\n }\n } catch {\n // skip unreadable / invalid manifest\n }\n continue;\n }\n\n const deeper = scanDirForPlugin(childPath, pluginName, cwd, depth + 1);\n if (deeper) return deeper;\n }\n return null;\n}\n\nfunction loadManifestFromFileSync(filePath: string): unknown {\n const raw = fs.readFileSync(filePath, \"utf-8\");\n return JSON.parse(raw);\n}\n\nfunction escapeRegex(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n/**\n * Returns true when the named import specifier `spec` resolves to either\n * `pluginName` itself or its kebab-to-camelCase JS-identifier form.\n * Handles the `name`, `name as alias`, and inline-type (`type name`,\n * `type name as alias`) forms. Matches on the imported binding before any\n * `as` rename so a promotion finds the right specifier regardless of how\n * the user aliased it.\n */\nfunction specifierMatchesPlugin(spec: string, pluginName: string): boolean {\n const stripped = spec.replace(/^type\\s+/, \"\").trim();\n const head = stripped.split(/\\s+as\\s+/)[0]?.trim();\n if (!head) return false;\n return head === pluginName || head === kebabToCamel(pluginName);\n}\n\n/**\n * Rewrite imports of `pluginName` from `<pkg><oldSuffix>` to `<pkg><newSuffix>`\n * across one file, leaving every OTHER specifier on the same import line at\n * its original source. The naïve `split/join` approach this replaced was\n * promoting *every* beta specifier in the file along with the targeted\n * plugin — a bug because beta specifiers don't exist at the GA subpath.\n *\n * Behaviour:\n * - If the targeted plugin is the only specifier on an import line, the\n * line's source is rewritten to the new path.\n * - If the import has multiple specifiers, the targeted one is moved to a\n * newly-emitted import line at the new source, and the original import\n * keeps the remaining specifiers at the old source.\n * - Imports that don't reference `pluginName` are left untouched.\n *\n * Multi-line specifier lists, type-only imports (`import type { ... }`),\n * inline-type specifiers (`import { type Foo }`), and `as`-aliased\n * specifiers are all preserved through the rewrite.\n */\nfunction rewriteImportsInFile(\n filePath: string,\n pluginName: string,\n oldSuffix: string,\n newSuffix: string,\n dryRun: boolean,\n): { file: string; from: string; to: string } | null {\n const content = fs.readFileSync(filePath, \"utf-8\");\n\n const packages = [\n \"@databricks/appkit\",\n \"@databricks/appkit-ui/js\",\n \"@databricks/appkit-ui/react\",\n ];\n let updated = content;\n let changed = false;\n\n for (const pkg of packages) {\n const oldPath = `${pkg}${oldSuffix}`;\n const newPath = `${pkg}${newSuffix}`;\n\n // Match: `import [type] { ...specifiers... } from \"<oldPath>\";`\n // - `[^}]*` lets the specifier list span newlines (safe — TS imports\n // don't have nested braces inside the specifier list).\n // - Captures the `type ` keyword (if any), the specifier body, and\n // the surrounding quote style so we can preserve them on output.\n const importRe = new RegExp(\n `import\\\\s+(type\\\\s+)?\\\\{([^}]*)\\\\}\\\\s*from\\\\s*([\"'])${escapeRegex(oldPath)}\\\\3\\\\s*;?`,\n \"g\",\n );\n\n updated = updated.replace(\n importRe,\n (full, typeKeyword, specifiers, quote) => {\n const specList: string[] = specifiers\n .split(\",\")\n .map((s: string) => s.trim())\n .filter((s: string) => s.length > 0);\n\n const promotedSpec = specList.find((s) =>\n specifierMatchesPlugin(s, pluginName),\n );\n if (!promotedSpec) {\n // Plugin not in this import — leave it alone.\n return full;\n }\n\n const remaining = specList.filter(\n (s) => !specifierMatchesPlugin(s, pluginName),\n );\n changed = true;\n\n const tk = typeKeyword ?? \"\";\n const promotedImport = `import ${tk}{ ${promotedSpec} } from ${quote}${newPath}${quote};`;\n\n if (remaining.length === 0) {\n // Only specifier — just rewrite the source.\n return promotedImport;\n }\n\n const remainingImport = `import ${tk}{ ${remaining.join(\", \")} } from ${quote}${oldPath}${quote};`;\n return `${remainingImport}\\n${promotedImport}`;\n },\n );\n }\n\n if (!changed) return null;\n\n if (!dryRun) {\n fs.writeFileSync(filePath, updated);\n }\n\n return {\n file: filePath,\n from: oldSuffix || \"(root)\",\n to: newSuffix || \"(root)\",\n };\n}\n\nfunction findTsFiles(dir: string, projectRoot: string, depth = 0): string[] {\n if (depth >= MAX_SCAN_DEPTH) return [];\n\n const results: string[] = [];\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true });\n } catch {\n return results;\n }\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isSymbolicLink()) continue;\n if (entry.isDirectory()) {\n if (SKIP_DIRECTORIES.has(entry.name)) continue;\n // Boundary check to ensure recursion stays inside the project root,\n // even if a future change introduces a symlink-following path.\n if (!isWithinDirectory(fullPath, projectRoot)) continue;\n results.push(...findTsFiles(fullPath, projectRoot, depth + 1));\n } else if (entry.name.endsWith(\".ts\") || entry.name.endsWith(\".tsx\")) {\n results.push(fullPath);\n }\n }\n\n return results;\n}\n\nasync function runPromote(\n pluginName: string,\n options: {\n to: string;\n dryRun?: boolean;\n skipImports?: boolean;\n skipSync?: boolean;\n allowInstalled?: boolean;\n },\n): Promise<void> {\n validatePluginName(pluginName);\n\n const cwd = process.cwd();\n\n if (!isStability(options.to)) {\n console.error(\n `Invalid target tier \"${options.to}\". Must be one of: beta, ga.`,\n );\n process.exit(1);\n }\n const target: Stability = options.to;\n\n const found = findPluginManifest(pluginName, cwd);\n if (!found) {\n console.error(\n `Plugin \"${pluginName}\" not found. Searched local dirs (plugins, server, .) and node_modules.`,\n );\n process.exit(1);\n }\n\n const { manifestPath, isLocal } = found;\n\n if (!isLocal && !options.allowInstalled) {\n console.error(\n `Plugin \"${pluginName}\" was only found under node_modules (${path.relative(cwd, manifestPath)}).\\n` +\n `Refusing to mutate an installed package — re-install would overwrite the change.\\n` +\n `Pass --allow-installed to override (advanced; not recommended).`,\n );\n process.exit(1);\n }\n\n let raw: Record<string, unknown>;\n try {\n const parsed = loadManifestFromFileSync(manifestPath);\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n console.error(\n `Manifest at ${path.relative(cwd, manifestPath)} is not a JSON object.`,\n );\n process.exit(1);\n }\n raw = parsed as Record<string, unknown>;\n } catch (err) {\n console.error(\n `Failed to read manifest at ${path.relative(cwd, manifestPath)}: ${err instanceof Error ? err.message : String(err)}`,\n );\n process.exit(1);\n }\n\n const rawStability = raw.stability ?? \"ga\";\n if (!isStability(rawStability)) {\n console.error(\n `Manifest at ${path.relative(cwd, manifestPath)} has an invalid stability value \"${String(rawStability)}\". ` +\n `Must be one of: beta, ga (or omitted for ga).`,\n );\n process.exit(1);\n }\n const currentStability: Stability = rawStability;\n\n if (currentStability === target) {\n console.error(\n `Plugin \"${pluginName}\" is already at \"${target}\". Nothing to do.`,\n );\n process.exit(1);\n }\n\n if (TIER_ORDER[target] <= TIER_ORDER[currentStability]) {\n console.error(\n `Cannot demote \"${pluginName}\" from \"${currentStability}\" to \"${target}\". Promotion is one-way only.`,\n );\n process.exit(1);\n }\n\n const prefix = options.dryRun ? \"[dry-run] \" : \"\";\n\n if (target === \"ga\") {\n delete raw.stability;\n } else {\n raw.stability = target;\n }\n\n if (!options.dryRun) {\n fs.writeFileSync(manifestPath, `${JSON.stringify(raw, null, 2)}\\n`);\n }\n console.log(\n `${prefix}Updated manifest: ${path.relative(cwd, manifestPath)} (${currentStability} → ${target})`,\n );\n\n const importRewrites: { file: string; from: string; to: string }[] = [];\n if (!options.skipImports) {\n const oldSuffix = IMPORT_PATH_MAP[currentStability];\n const newSuffix = IMPORT_PATH_MAP[target];\n\n const tsFiles = findTsFiles(cwd, cwd);\n for (const file of tsFiles) {\n const result = rewriteImportsInFile(\n file,\n pluginName,\n oldSuffix,\n newSuffix,\n Boolean(options.dryRun),\n );\n if (result) {\n importRewrites.push(result);\n console.log(\n `${prefix}Rewritten imports in: ${path.relative(cwd, file)}`,\n );\n }\n }\n\n if (importRewrites.length === 0) {\n console.log(`${prefix}No import paths to rewrite.`);\n }\n }\n\n if (!options.skipSync && !options.dryRun) {\n // Monorepo-only: regenerate every artifact derived from plugin\n // manifests (ga/beta export barrels AND the per-plugin docs-page\n // stability banner) so all manifest-derived layers move together.\n // No-op outside the AppKit monorepo (third-party plugin projects don't\n // ship the generators).\n const generatorPath = path.join(cwd, \"tools\", \"generate-plugin-entries.ts\");\n if (fs.existsSync(generatorPath)) {\n console.log(`\\n${prefix}Regenerating manifest-derived artifacts...`);\n const { execSync } = await import(\"node:child_process\");\n try {\n execSync(\"pnpm run generate:types\", { cwd, stdio: \"inherit\" });\n } catch {\n console.error(\n `Error: post-promote 'generate:types' failed. ` +\n `Manifest was updated, but generated barrels and docs banners may be stale. ` +\n `Run 'pnpm run generate:types' manually.`,\n );\n process.exit(1);\n }\n }\n\n console.log(`\\n${prefix}Running plugin sync...`);\n const { execSync } = await import(\"node:child_process\");\n // Monorepo flavor: the AppKit monorepo's `pnpm sync:template` script\n // points sync at `template/appkit.plugins.json` (the file shipped to\n // consumers and read by the Go init template), not the project-root\n // default. Detect the monorepo via the same generator-path probe used\n // above and prefer the script when available so the manifest, the\n // synced template, and the runtime barrels stay aligned.\n const syncCommand = fs.existsSync(generatorPath)\n ? \"pnpm run sync:template\"\n : \"npx appkit plugin sync --write\";\n try {\n execSync(syncCommand, {\n cwd,\n stdio: \"inherit\",\n });\n } catch {\n console.error(\n `Error: post-promote sync ('${syncCommand}') failed. ` +\n `Manifest and imports were updated, but the synced plugin manifest ` +\n `may be stale. Run '${syncCommand}' manually.`,\n );\n process.exit(1);\n }\n }\n\n console.log(\n `\\n${prefix}Promotion complete: ${pluginName} ${currentStability} → ${target}`,\n );\n if (importRewrites.length > 0) {\n console.log(` ${importRewrites.length} file(s) with import rewrites`);\n }\n}\n\n/** Exported for testing. */\nexport {\n PLUGIN_NAME_PATTERN,\n TIER_ORDER,\n IMPORT_PATH_MAP,\n SKIP_DIRECTORIES,\n isStability,\n validatePluginName,\n rewriteImportsInFile,\n runPromote,\n};\n\nexport const pluginPromoteCommand = new Command(\"promote\")\n .description(\"Promote a plugin to a higher stability tier\")\n .argument(\"<plugin-name>\", \"Plugin name to promote\")\n .requiredOption(\"--to <tier>\", \"Target stability tier (beta, ga)\")\n .option(\"--dry-run\", \"Show what would change without modifying files\")\n .option(\"--skip-imports\", \"Only update manifest, skip import path rewriting\")\n .option(\"--skip-sync\", \"Don't auto-run plugin sync after promotion\")\n .option(\n \"--allow-installed\",\n \"Allow promoting a plugin that lives only under node_modules (advanced)\",\n )\n .action((pluginName, opts) =>\n runPromote(pluginName, opts).catch((err) => {\n const message = err instanceof Error ? err.message : String(err);\n console.error(`Error: ${message}`);\n if (process.env.DEBUG) console.error(err);\n process.exit(1);\n }),\n );\n"],"mappings":"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"}
|
|
@@ -1 +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 {\n TEMPLATE_SCAFFOLDING,\n templateFieldEntrySchema,\n} from \"../../../../schemas/manifest\";\nimport {\n loadManifestFromFile,\n type ResolvedManifest,\n resolveManifestInDir,\n} from \"../manifest-resolve\";\nimport type {\n PluginManifest,\n TemplatePlugin,\n TemplatePluginsManifest,\n} from \"../manifest-types\";\nimport { shouldAllowJsManifestForPackage } from \"../trusted-js-manifest\";\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 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)}`,\n );\n }\n return null;\n}\n\n/** Safety limit for recursive directory scanning to prevent runaway traversal. */\nconst MAX_SCAN_DEPTH = 5;\n\n/**\n * Load and validate a resolved manifest, returning a TemplatePlugin entry or null.\n * Centralises the resolve → load → validate → build-entry pipeline used by\n * multiple discovery functions.\n */\nasync function loadPluginEntry(\n resolved: ResolvedManifest,\n pkg: string,\n allowJsManifest: boolean,\n): Promise<[string, TemplatePlugin] | null> {\n const parsed = await loadManifestFromFile(resolved.path, resolved.type, {\n allowJsManifest,\n });\n const manifest = validateManifestWithSchema(parsed, resolved.path);\n if (!manifest || manifest.hidden) return null;\n\n return [\n manifest.name,\n {\n name: manifest.name,\n displayName: manifest.displayName,\n description: manifest.description,\n package: pkg,\n resources: manifest.resources,\n ...(manifest.onSetupMessage && {\n onSetupMessage: manifest.onSetupMessage,\n }),\n // Narrowing on `!== \"ga\"` removes \"ga\"; the truthy check\n // removes `undefined`. What's left is the non-GA tier set,\n // which TypeScript already knows is assignable to TemplatePlugin's\n // `stability` field — so no cast is needed and adding a future\n // tier (e.g. \"alpha\") flows through type-correctly.\n ...(manifest.stability &&\n manifest.stability !== \"ga\" && {\n stability: manifest.stability,\n }),\n ...(manifest.scaffolding && {\n scaffolding: manifest.scaffolding,\n }),\n },\n ];\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 * Conventional directories to scan for local plugin manifests when\n * --local-plugins-dir is not set. Checked in order; each that exists is scanned.\n * Plugins found here are added to the manifest even if not imported in the server.\n */\nconst CONVENTIONAL_LOCAL_PLUGIN_DIRS = [\"plugins\", \"server\"];\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\n * (manifest.json or manifest.js). Follows the convention that plugins live in\n * their own directory with a manifest file.\n *\n * Resolution strategy:\n * 1. If the import path is a directory, look for manifest.json/js in it\n * 2. If the import path + extension is a file, look for manifest in its parent directory\n * 3. If the import path is a directory with an index file, look for manifest 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 Resolved manifest file path and type, or null if not found\n */\nfunction resolveLocalManifest(\n importSource: string,\n serverFileDir: string,\n allowJsManifest: boolean,\n projectRoot?: string,\n): ResolvedManifest | 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\n if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) {\n return resolveManifestInDir(resolved, { allowJsManifest });\n }\n\n // Case 2: Import path + extension resolves to a file — manifest in parent dir\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 if (!isWithinDirectory(dir, boundary)) return null;\n return resolveManifestInDir(dir, { allowJsManifest });\n }\n }\n\n // Case 3: Import path is a directory with an index file\n for (const ext of RESOLVE_EXTENSIONS) {\n const indexPath = path.join(resolved, `index${ext}`);\n if (fs.existsSync(indexPath)) {\n return resolveManifestInDir(resolved, { allowJsManifest });\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 loads manifest.json or manifest.js.\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 */\nasync function discoverLocalPlugins(\n relativeImports: ParsedImport[],\n serverFileDir: string,\n cwd: string,\n allowJsManifest: boolean,\n): Promise<TemplatePluginsManifest[\"plugins\"]> {\n const plugins: TemplatePluginsManifest[\"plugins\"] = {};\n\n for (const imp of relativeImports) {\n const resolved = resolveLocalManifest(\n imp.source,\n serverFileDir,\n allowJsManifest,\n cwd,\n );\n if (!resolved) continue;\n\n try {\n const relativePath = path.relative(cwd, path.dirname(resolved.path));\n const entry = await loadPluginEntry(\n resolved,\n `./${relativePath}`,\n allowJsManifest,\n );\n if (entry) plugins[entry[0]] = entry[1];\n } catch (error) {\n console.warn(\n `Warning: Failed to load manifest at ${resolved.path}:`,\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 or manifest.js 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 */\nasync function discoverPluginManifests(\n packagePath: string,\n allowJsManifest: boolean,\n): Promise<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()) continue;\n const resolved = resolveManifestInDir(path.join(pluginsDir, entry.name), {\n allowJsManifest,\n });\n if (!resolved) continue;\n\n try {\n const parsed = await loadManifestFromFile(resolved.path, resolved.type, {\n allowJsManifest,\n });\n const manifest = validateManifestWithSchema(parsed, resolved.path);\n if (manifest) {\n manifests.push(manifest);\n }\n } catch (error) {\n console.warn(\n `Warning: Failed to load manifest at ${resolved.path}:`,\n error instanceof Error ? error.message : error,\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 */\nasync function scanForPlugins(\n cwd: string,\n packages: Iterable<string>,\n allowJsManifest: boolean,\n): Promise<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 allowJsForPackage =\n allowJsManifest || shouldAllowJsManifestForPackage(packageName);\n\n const manifests = await discoverPluginManifests(\n packagePath,\n allowJsForPackage,\n );\n for (const manifest of manifests) {\n if (manifest.hidden) continue;\n plugins[manifest.name] = {\n name: manifest.name,\n displayName: manifest.displayName,\n description: manifest.description,\n package: packageName,\n resources: manifest.resources,\n ...(manifest.onSetupMessage && {\n onSetupMessage: manifest.onSetupMessage,\n }),\n ...(manifest.stability &&\n manifest.stability !== \"ga\" && {\n stability: manifest.stability,\n }),\n ...(manifest.scaffolding && {\n scaffolding: manifest.scaffolding,\n }),\n } satisfies TemplatePlugin;\n }\n }\n\n return plugins;\n}\n\n/**\n * Recursively scan a directory for plugin manifests. Any directory that\n * contains manifest.json or manifest.js is treated as a plugin root; we do\n * not descend into that directory's children. Used for local plugins discovery\n * so nested paths like server/plugins/category/my-plugin are found.\n */\nasync function scanPluginsDirRecursive(\n dir: string,\n cwd: string,\n allowJsManifest: boolean,\n depth = 0,\n): Promise<TemplatePluginsManifest[\"plugins\"]> {\n const plugins: TemplatePluginsManifest[\"plugins\"] = {};\n if (!fs.existsSync(dir) || depth >= MAX_SCAN_DEPTH) 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 pluginDir = path.join(dir, entry.name);\n const resolved = resolveManifestInDir(pluginDir, { allowJsManifest });\n\n if (resolved) {\n const pkg = `./${path.relative(cwd, pluginDir)}`;\n try {\n const pluginEntry = await loadPluginEntry(\n resolved,\n pkg,\n allowJsManifest,\n );\n if (pluginEntry) plugins[pluginEntry[0]] = pluginEntry[1];\n } catch (error) {\n console.warn(\n `Warning: Failed to load manifest at ${resolved.path}:`,\n error instanceof Error ? error.message : error,\n );\n }\n continue;\n }\n\n Object.assign(\n plugins,\n await scanPluginsDirRecursive(pluginDir, cwd, allowJsManifest, depth + 1),\n );\n }\n\n return plugins;\n}\n\n/**\n * Scan a directory for plugin manifests in direct subdirectories only.\n * Each subdirectory may contain manifest.json or manifest.js.\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 (used when cwd is not set)\n * @param cwd - When set, each plugin's package is set to ./<path from cwd to plugin subdir>, e.g. ./server/my-plugin\n * @returns Map of plugin name to template plugin entry\n */\nasync function scanPluginsDir(\n dir: string,\n packageName: string,\n allowJsManifest: boolean,\n cwd?: string,\n): Promise<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 pluginDir = path.join(dir, entry.name);\n const resolved = resolveManifestInDir(pluginDir, { allowJsManifest });\n if (!resolved) continue;\n\n const pkg =\n cwd !== undefined ? `./${path.relative(cwd, pluginDir)}` : packageName;\n\n try {\n const pluginEntry = await loadPluginEntry(resolved, pkg, allowJsManifest);\n if (pluginEntry) plugins[pluginEntry[0]] = pluginEntry[1];\n } catch (error) {\n console.warn(\n `Warning: Failed to load manifest at ${resolved.path}:`,\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 *\n * Each resource field is parsed through `templateFieldEntrySchema` so the\n * `origin` transform fires and produces canonical `origin` values, even\n * when the input carries a stale `origin`. Parsing per-field (rather than\n * the whole manifest) keeps the surrounding plugin/resource key order\n * stable so the synced JSON's diff stays minimal.\n */\nfunction writeManifest(\n outputPath: string,\n { plugins }: { plugins: TemplatePluginsManifest[\"plugins\"] },\n options: { write?: boolean; silent?: boolean; json?: boolean },\n) {\n for (const plugin of Object.values(plugins)) {\n for (const group of [\n plugin.resources.required,\n plugin.resources.optional,\n ]) {\n for (const resource of group) {\n if (!resource.fields) continue;\n for (const fieldName of Object.keys(resource.fields)) {\n resource.fields[fieldName] = templateFieldEntrySchema.parse(\n resource.fields[fieldName],\n );\n }\n }\n }\n }\n\n const templateManifest: TemplatePluginsManifest = {\n $schema:\n \"https://databricks.github.io/appkit/schemas/template-plugins.schema.json\",\n version: \"2.0\",\n plugins,\n scaffolding: TEMPLATE_SCAFFOLDING,\n };\n\n const serialized = JSON.stringify(templateManifest, null, 2);\n\n if (options.json) {\n console.log(serialized);\n }\n\n if (options.write) {\n fs.writeFileSync(outputPath, `${serialized}\\n`);\n if (!options.silent && !options.json) {\n console.log(`\\n✓ Wrote ${outputPath}`);\n }\n } else if (!options.silent && !options.json) {\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(serialized);\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 */\nasync function runPluginsSync(options: {\n write?: boolean;\n output?: string;\n silent?: boolean;\n json?: boolean;\n requirePlugins?: string;\n pluginsDir?: string;\n packageName?: string;\n localPluginsDir?: string;\n allowJsManifest?: boolean;\n}): Promise<void> {\n const cwd = process.cwd();\n const allowJsManifest = Boolean(options.allowJsManifest);\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 && !options.json) {\n console.log(\"Scanning for AppKit plugins...\\n\");\n if (allowJsManifest) {\n console.warn(\n \"Warning: --allow-js-manifest executes manifest.js/manifest.cjs files. Only use with trusted code.\",\n );\n }\n }\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 && !options.json) {\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 && !options.json) {\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 && !options.json) {\n console.log(`Scanning plugins directory: ${options.pluginsDir}`);\n }\n Object.assign(\n plugins,\n await scanPluginsDir(resolvedDir, pkgName, allowJsManifest),\n );\n } else {\n const npmPackages = new Set([\n ...KNOWN_PLUGIN_PACKAGES,\n ...npmImports.map((i) => i.source),\n ]);\n Object.assign(\n plugins,\n await scanForPlugins(cwd, npmPackages, allowJsManifest),\n );\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 = await discoverLocalPlugins(\n localImports,\n serverFileDir,\n cwd,\n allowJsManifest,\n );\n Object.assign(plugins, localPlugins);\n }\n\n // Step 4b: Discover local plugins from conventional directory (or --local-plugins-dir).\n // These are included even when not imported in the server.\n const localDirsToScan: string[] = options.localPluginsDir\n ? [options.localPluginsDir]\n : CONVENTIONAL_LOCAL_PLUGIN_DIRS.filter((d) =>\n fs.existsSync(path.join(cwd, d)),\n );\n for (const dir of localDirsToScan) {\n const resolvedDir = path.resolve(cwd, dir);\n if (!fs.existsSync(resolvedDir)) continue;\n if (!options.silent && !options.json) {\n console.log(`Scanning local plugins directory: ${dir}`);\n }\n const discovered = await scanPluginsDirRecursive(\n resolvedDir,\n cwd,\n allowJsManifest,\n );\n for (const [name, entry] of Object.entries(discovered)) {\n if (!plugins[name]) plugins[name] = entry;\n }\n }\n\n const pluginCount = Object.keys(plugins).length;\n\n if (pluginCount === 0) {\n if (options.silent || options.json) {\n writeManifest(outputPath, { plugins: {} }, options);\n if (options.silent) return;\n process.exit(1);\n }\n console.log(\"No plugins found.\");\n if (options.pluginsDir) {\n console.log(\n `\\nNo manifest (${allowJsManifest ? \"manifest.json or manifest.js\" : \"manifest.json\"}) found in: ${options.pluginsDir}`,\n );\n } else {\n console.log(\n \"\\nMake sure you have plugin packages installed, or specify a directory:\",\n );\n console.log(\" appkit plugin sync --plugins-dir <path>\");\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 // Step 6b: Strip requiredByTemplate for non-GA plugins\n for (const plugin of Object.values(plugins)) {\n if (\n plugin.requiredByTemplate &&\n plugin.stability &&\n plugin.stability !== \"ga\"\n ) {\n plugin.requiredByTemplate = undefined;\n }\n }\n\n if (!options.silent && !options.json) {\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 // Step 7: Detect orphaned resources from removed plugins\n if (!options.silent && fs.existsSync(outputPath)) {\n try {\n const oldRaw = fs.readFileSync(outputPath, \"utf-8\");\n const oldManifest = JSON.parse(oldRaw) as TemplatePluginsManifest;\n const oldNames = new Set(Object.keys(oldManifest.plugins ?? {}));\n const newNames = new Set(Object.keys(plugins));\n for (const name of oldNames) {\n if (newNames.has(name)) continue;\n const oldPlugin = oldManifest.plugins?.[name];\n if (!oldPlugin || typeof oldPlugin !== \"object\") continue;\n const envVars: string[] = [];\n for (const res of [\n ...(oldPlugin.resources?.required ?? []),\n ...(oldPlugin.resources?.optional ?? []),\n ]) {\n if (res?.fields) {\n for (const field of Object.values(res.fields)) {\n if (field?.env) envVars.push(field.env);\n }\n }\n }\n const envInfo =\n envVars.length > 0\n ? ` The following resource env vars may be orphaned: ${envVars.join(\", \")}`\n : \"\";\n console.warn(`Warning: Plugin \"${name}\" was removed.${envInfo}`);\n }\n } catch {\n // Ignore parse errors on existing manifest\n }\n }\n\n writeManifest(outputPath, { plugins }, options);\n}\n\n/** Exported for testing: path boundary check, AST parsing, trust checks. */\nexport {\n isWithinDirectory,\n parseImports,\n parsePluginUsages,\n shouldAllowJsManifestForPackage,\n};\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 .option(\n \"--local-plugins-dir <path>\",\n \"Also scan this directory for local plugin manifests (default: plugins, server)\",\n )\n .option(\n \"--allow-js-manifest\",\n \"Allow reading manifest.js/manifest.cjs (executes code; use only with trusted plugins)\",\n )\n .option(\"--json\", \"Output manifest as JSON to stdout\")\n .addHelpText(\n \"after\",\n `\nExamples:\n $ appkit plugin sync\n $ appkit plugin sync --write\n $ appkit plugin sync --write --require-plugins server,analytics\n $ appkit plugin sync --write --plugins-dir src/plugins --package-name @my/pkg\n $ appkit plugin sync --json\n $ appkit plugin sync --silent`,\n )\n .action((opts) =>\n runPluginsSync(opts).catch((err) => {\n console.error(err);\n process.exit(1);\n }),\n );\n"],"mappings":"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"}
|
|
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\";\n\nimport { Lang, parse, type SgNode } from \"@ast-grep/napi\";\nimport { Command } from \"commander\";\n\nimport {\n TEMPLATE_SCAFFOLDING,\n templateFieldEntrySchema,\n} from \"../../../../schemas/manifest\";\nimport {\n loadManifestFromFile,\n type ResolvedManifest,\n resolveManifestInDir,\n} from \"../manifest-resolve\";\nimport type {\n PluginManifest,\n TemplatePlugin,\n TemplatePluginsManifest,\n} from \"../manifest-types\";\nimport { shouldAllowJsManifestForPackage } from \"../trusted-js-manifest\";\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 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)}`,\n );\n }\n return null;\n}\n\n/** Safety limit for recursive directory scanning to prevent runaway traversal. */\nconst MAX_SCAN_DEPTH = 5;\n\n/**\n * Load and validate a resolved manifest, returning a TemplatePlugin entry or null.\n * Centralises the resolve → load → validate → build-entry pipeline used by\n * multiple discovery functions.\n */\nasync function loadPluginEntry(\n resolved: ResolvedManifest,\n pkg: string,\n allowJsManifest: boolean,\n): Promise<[string, TemplatePlugin] | null> {\n const parsed = await loadManifestFromFile(resolved.path, resolved.type, {\n allowJsManifest,\n });\n const manifest = validateManifestWithSchema(parsed, resolved.path);\n if (!manifest || manifest.hidden) return null;\n\n return [\n manifest.name,\n {\n name: manifest.name,\n displayName: manifest.displayName,\n description: manifest.description,\n package: pkg,\n resources: manifest.resources,\n ...(manifest.onSetupMessage && {\n onSetupMessage: manifest.onSetupMessage,\n }),\n // Narrowing on `!== \"ga\"` removes \"ga\"; the truthy check\n // removes `undefined`. What's left is the non-GA tier set,\n // which TypeScript already knows is assignable to TemplatePlugin's\n // `stability` field — so no cast is needed and adding a future\n // tier (e.g. \"alpha\") flows through type-correctly.\n ...(manifest.stability &&\n manifest.stability !== \"ga\" && {\n stability: manifest.stability,\n }),\n ...(manifest.scaffolding && {\n scaffolding: manifest.scaffolding,\n }),\n },\n ];\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 * Conventional directories to scan for local plugin manifests when\n * --local-plugins-dir is not set. Checked in order; each that exists is scanned.\n * Plugins found here are added to the manifest even if not imported in the server.\n */\nconst CONVENTIONAL_LOCAL_PLUGIN_DIRS = [\"plugins\", \"server\"];\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\n * (manifest.json or manifest.js). Follows the convention that plugins live in\n * their own directory with a manifest file.\n *\n * Resolution strategy:\n * 1. If the import path is a directory, look for manifest.json/js in it\n * 2. If the import path + extension is a file, look for manifest in its parent directory\n * 3. If the import path is a directory with an index file, look for manifest 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 Resolved manifest file path and type, or null if not found\n */\nfunction resolveLocalManifest(\n importSource: string,\n serverFileDir: string,\n allowJsManifest: boolean,\n projectRoot?: string,\n): ResolvedManifest | 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\n if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) {\n return resolveManifestInDir(resolved, { allowJsManifest });\n }\n\n // Case 2: Import path + extension resolves to a file — manifest in parent dir\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 if (!isWithinDirectory(dir, boundary)) return null;\n return resolveManifestInDir(dir, { allowJsManifest });\n }\n }\n\n // Case 3: Import path is a directory with an index file\n for (const ext of RESOLVE_EXTENSIONS) {\n const indexPath = path.join(resolved, `index${ext}`);\n if (fs.existsSync(indexPath)) {\n return resolveManifestInDir(resolved, { allowJsManifest });\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 loads manifest.json or manifest.js.\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 */\nasync function discoverLocalPlugins(\n relativeImports: ParsedImport[],\n serverFileDir: string,\n cwd: string,\n allowJsManifest: boolean,\n): Promise<TemplatePluginsManifest[\"plugins\"]> {\n const plugins: TemplatePluginsManifest[\"plugins\"] = {};\n\n for (const imp of relativeImports) {\n const resolved = resolveLocalManifest(\n imp.source,\n serverFileDir,\n allowJsManifest,\n cwd,\n );\n if (!resolved) continue;\n\n try {\n const relativePath = path.relative(cwd, path.dirname(resolved.path));\n const entry = await loadPluginEntry(\n resolved,\n `./${relativePath}`,\n allowJsManifest,\n );\n if (entry) plugins[entry[0]] = entry[1];\n } catch (error) {\n console.warn(\n `Warning: Failed to load manifest at ${resolved.path}:`,\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 or manifest.js 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 */\nasync function discoverPluginManifests(\n packagePath: string,\n allowJsManifest: boolean,\n): Promise<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()) continue;\n const resolved = resolveManifestInDir(path.join(pluginsDir, entry.name), {\n allowJsManifest,\n });\n if (!resolved) continue;\n\n try {\n const parsed = await loadManifestFromFile(resolved.path, resolved.type, {\n allowJsManifest,\n });\n const manifest = validateManifestWithSchema(parsed, resolved.path);\n if (manifest) {\n manifests.push(manifest);\n }\n } catch (error) {\n console.warn(\n `Warning: Failed to load manifest at ${resolved.path}:`,\n error instanceof Error ? error.message : error,\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 */\nasync function scanForPlugins(\n cwd: string,\n packages: Iterable<string>,\n allowJsManifest: boolean,\n): Promise<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 allowJsForPackage =\n allowJsManifest || shouldAllowJsManifestForPackage(packageName);\n\n const manifests = await discoverPluginManifests(\n packagePath,\n allowJsForPackage,\n );\n for (const manifest of manifests) {\n if (manifest.hidden) continue;\n plugins[manifest.name] = {\n name: manifest.name,\n displayName: manifest.displayName,\n description: manifest.description,\n package: packageName,\n resources: manifest.resources,\n ...(manifest.onSetupMessage && {\n onSetupMessage: manifest.onSetupMessage,\n }),\n ...(manifest.stability &&\n manifest.stability !== \"ga\" && {\n stability: manifest.stability,\n }),\n ...(manifest.scaffolding && {\n scaffolding: manifest.scaffolding,\n }),\n } satisfies TemplatePlugin;\n }\n }\n\n return plugins;\n}\n\n/**\n * Recursively scan a directory for plugin manifests. Any directory that\n * contains manifest.json or manifest.js is treated as a plugin root; we do\n * not descend into that directory's children. Used for local plugins discovery\n * so nested paths like server/plugins/category/my-plugin are found.\n */\nasync function scanPluginsDirRecursive(\n dir: string,\n cwd: string,\n allowJsManifest: boolean,\n depth = 0,\n): Promise<TemplatePluginsManifest[\"plugins\"]> {\n const plugins: TemplatePluginsManifest[\"plugins\"] = {};\n if (!fs.existsSync(dir) || depth >= MAX_SCAN_DEPTH) 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 pluginDir = path.join(dir, entry.name);\n const resolved = resolveManifestInDir(pluginDir, { allowJsManifest });\n\n if (resolved) {\n const pkg = `./${path.relative(cwd, pluginDir)}`;\n try {\n const pluginEntry = await loadPluginEntry(\n resolved,\n pkg,\n allowJsManifest,\n );\n if (pluginEntry) plugins[pluginEntry[0]] = pluginEntry[1];\n } catch (error) {\n console.warn(\n `Warning: Failed to load manifest at ${resolved.path}:`,\n error instanceof Error ? error.message : error,\n );\n }\n continue;\n }\n\n Object.assign(\n plugins,\n await scanPluginsDirRecursive(pluginDir, cwd, allowJsManifest, depth + 1),\n );\n }\n\n return plugins;\n}\n\n/**\n * Scan a directory for plugin manifests in direct subdirectories only.\n * Each subdirectory may contain manifest.json or manifest.js.\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 (used when cwd is not set)\n * @param cwd - When set, each plugin's package is set to ./<path from cwd to plugin subdir>, e.g. ./server/my-plugin\n * @returns Map of plugin name to template plugin entry\n */\nasync function scanPluginsDir(\n dir: string,\n packageName: string,\n allowJsManifest: boolean,\n cwd?: string,\n): Promise<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 pluginDir = path.join(dir, entry.name);\n const resolved = resolveManifestInDir(pluginDir, { allowJsManifest });\n if (!resolved) continue;\n\n const pkg =\n cwd !== undefined ? `./${path.relative(cwd, pluginDir)}` : packageName;\n\n try {\n const pluginEntry = await loadPluginEntry(resolved, pkg, allowJsManifest);\n if (pluginEntry) plugins[pluginEntry[0]] = pluginEntry[1];\n } catch (error) {\n console.warn(\n `Warning: Failed to load manifest at ${resolved.path}:`,\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 *\n * Each resource field is parsed through `templateFieldEntrySchema` so the\n * `origin` transform fires and produces canonical `origin` values, even\n * when the input carries a stale `origin`. Parsing per-field (rather than\n * the whole manifest) keeps the surrounding plugin/resource key order\n * stable so the synced JSON's diff stays minimal.\n */\nfunction writeManifest(\n outputPath: string,\n { plugins }: { plugins: TemplatePluginsManifest[\"plugins\"] },\n options: { write?: boolean; silent?: boolean; json?: boolean },\n) {\n for (const plugin of Object.values(plugins)) {\n for (const group of [\n plugin.resources.required,\n plugin.resources.optional,\n ]) {\n for (const resource of group) {\n if (!resource.fields) continue;\n for (const fieldName of Object.keys(resource.fields)) {\n resource.fields[fieldName] = templateFieldEntrySchema.parse(\n resource.fields[fieldName],\n );\n }\n }\n }\n }\n\n const templateManifest: TemplatePluginsManifest = {\n $schema:\n \"https://databricks.github.io/appkit/schemas/template-plugins.schema.json\",\n version: \"2.0\",\n plugins,\n scaffolding: TEMPLATE_SCAFFOLDING,\n };\n\n const serialized = JSON.stringify(templateManifest, null, 2);\n\n if (options.json) {\n console.log(serialized);\n }\n\n if (options.write) {\n fs.writeFileSync(outputPath, `${serialized}\\n`);\n if (!options.silent && !options.json) {\n console.log(`\\n✓ Wrote ${outputPath}`);\n }\n } else if (!options.silent && !options.json) {\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(serialized);\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 */\nasync function runPluginsSync(options: {\n write?: boolean;\n output?: string;\n silent?: boolean;\n json?: boolean;\n requirePlugins?: string;\n pluginsDir?: string;\n packageName?: string;\n localPluginsDir?: string;\n allowJsManifest?: boolean;\n}): Promise<void> {\n const cwd = process.cwd();\n const allowJsManifest = Boolean(options.allowJsManifest);\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 && !options.json) {\n console.log(\"Scanning for AppKit plugins...\\n\");\n if (allowJsManifest) {\n console.warn(\n \"Warning: --allow-js-manifest executes manifest.js/manifest.cjs files. Only use with trusted code.\",\n );\n }\n }\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 && !options.json) {\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 && !options.json) {\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 && !options.json) {\n console.log(`Scanning plugins directory: ${options.pluginsDir}`);\n }\n Object.assign(\n plugins,\n await scanPluginsDir(resolvedDir, pkgName, allowJsManifest),\n );\n } else {\n const npmPackages = new Set([\n ...KNOWN_PLUGIN_PACKAGES,\n ...npmImports.map((i) => i.source),\n ]);\n Object.assign(\n plugins,\n await scanForPlugins(cwd, npmPackages, allowJsManifest),\n );\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 = await discoverLocalPlugins(\n localImports,\n serverFileDir,\n cwd,\n allowJsManifest,\n );\n Object.assign(plugins, localPlugins);\n }\n\n // Step 4b: Discover local plugins from conventional directory (or --local-plugins-dir).\n // These are included even when not imported in the server.\n const localDirsToScan: string[] = options.localPluginsDir\n ? [options.localPluginsDir]\n : CONVENTIONAL_LOCAL_PLUGIN_DIRS.filter((d) =>\n fs.existsSync(path.join(cwd, d)),\n );\n for (const dir of localDirsToScan) {\n const resolvedDir = path.resolve(cwd, dir);\n if (!fs.existsSync(resolvedDir)) continue;\n if (!options.silent && !options.json) {\n console.log(`Scanning local plugins directory: ${dir}`);\n }\n const discovered = await scanPluginsDirRecursive(\n resolvedDir,\n cwd,\n allowJsManifest,\n );\n for (const [name, entry] of Object.entries(discovered)) {\n if (!plugins[name]) plugins[name] = entry;\n }\n }\n\n const pluginCount = Object.keys(plugins).length;\n\n if (pluginCount === 0) {\n if (options.silent || options.json) {\n writeManifest(outputPath, { plugins: {} }, options);\n if (options.silent) return;\n process.exit(1);\n }\n console.log(\"No plugins found.\");\n if (options.pluginsDir) {\n console.log(\n `\\nNo manifest (${allowJsManifest ? \"manifest.json or manifest.js\" : \"manifest.json\"}) found in: ${options.pluginsDir}`,\n );\n } else {\n console.log(\n \"\\nMake sure you have plugin packages installed, or specify a directory:\",\n );\n console.log(\" appkit plugin sync --plugins-dir <path>\");\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 // Step 6b: Strip requiredByTemplate for non-GA plugins\n for (const plugin of Object.values(plugins)) {\n if (\n plugin.requiredByTemplate &&\n plugin.stability &&\n plugin.stability !== \"ga\"\n ) {\n plugin.requiredByTemplate = undefined;\n }\n }\n\n if (!options.silent && !options.json) {\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 // Step 7: Detect orphaned resources from removed plugins\n if (!options.silent && fs.existsSync(outputPath)) {\n try {\n const oldRaw = fs.readFileSync(outputPath, \"utf-8\");\n const oldManifest = JSON.parse(oldRaw) as TemplatePluginsManifest;\n const oldNames = new Set(Object.keys(oldManifest.plugins ?? {}));\n const newNames = new Set(Object.keys(plugins));\n for (const name of oldNames) {\n if (newNames.has(name)) continue;\n const oldPlugin = oldManifest.plugins?.[name];\n if (!oldPlugin || typeof oldPlugin !== \"object\") continue;\n const envVars: string[] = [];\n for (const res of [\n ...(oldPlugin.resources?.required ?? []),\n ...(oldPlugin.resources?.optional ?? []),\n ]) {\n if (res?.fields) {\n for (const field of Object.values(res.fields)) {\n if (field?.env) envVars.push(field.env);\n }\n }\n }\n const envInfo =\n envVars.length > 0\n ? ` The following resource env vars may be orphaned: ${envVars.join(\", \")}`\n : \"\";\n console.warn(`Warning: Plugin \"${name}\" was removed.${envInfo}`);\n }\n } catch {\n // Ignore parse errors on existing manifest\n }\n }\n\n writeManifest(outputPath, { plugins }, options);\n}\n\n/** Exported for testing: path boundary check, AST parsing, trust checks. */\nexport {\n isWithinDirectory,\n parseImports,\n parsePluginUsages,\n shouldAllowJsManifestForPackage,\n};\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 .option(\n \"--local-plugins-dir <path>\",\n \"Also scan this directory for local plugin manifests (default: plugins, server)\",\n )\n .option(\n \"--allow-js-manifest\",\n \"Allow reading manifest.js/manifest.cjs (executes code; use only with trusted plugins)\",\n )\n .option(\"--json\", \"Output manifest as JSON to stdout\")\n .addHelpText(\n \"after\",\n `\nExamples:\n $ appkit plugin sync\n $ appkit plugin sync --write\n $ appkit plugin sync --write --require-plugins server,analytics\n $ appkit plugin sync --write --plugins-dir src/plugins --package-name @my/pkg\n $ appkit plugin sync --json\n $ appkit plugin sync --silent`,\n )\n .action((opts) =>\n runPluginsSync(opts).catch((err) => {\n console.error(err);\n process.exit(1);\n }),\n );\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-manifest.js","names":[],"sources":["../../../../../src/cli/commands/plugin/validate/validate-manifest.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport {\n pluginManifestSchema,\n templatePluginsManifestSchema,\n} from \"../../../../schemas/manifest\";\nimport type { PluginManifest } from \"../manifest-types\";\n\nexport type { PluginManifest };\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\n/**\n * A single validation issue produced by the schema validator. The `path`\n * is a humanized property path (e.g., `resources.required[0].permission`)\n * suitable for direct CLI output.\n */\nexport interface SemanticIssue {\n level: \"error\" | \"warning\";\n path: string;\n message: string;\n}\n\nexport interface ValidateResult {\n valid: boolean;\n manifest?: PluginManifest;\n errors?: SemanticIssue[];\n}\n\n/**\n * Convert a Standard Schema issue path (array of property keys / path segments)\n * into a humanized string like `resources.required[0].permission`.\n */\nfunction humanizePath(\n path: ReadonlyArray<PropertyKey | StandardSchemaV1.PathSegment> | undefined,\n): string {\n if (!path || path.length === 0) return \"(root)\";\n\n let out = \"\";\n for (const segment of path) {\n const key =\n typeof segment === \"object\" && segment !== null && \"key\" in segment\n ? segment.key\n : segment;\n if (typeof key === \"number\") {\n out += `[${key}]`;\n } else {\n const str = String(key);\n out += out.length === 0 ? str : `.${str}`;\n }\n }\n return out.length === 0 ? \"(root)\" : out;\n}\n\n/**\n * Map a Standard Schema issue list into the legacy `SemanticIssue` shape so\n * consumer code can stay uniform.\n */\nfunction mapIssues(\n issues: ReadonlyArray<StandardSchemaV1.Issue>,\n): SemanticIssue[] {\n const result: SemanticIssue[] = [];\n for (const issue of issues) {\n result.push({\n level: \"error\",\n path: humanizePath(issue.path),\n message: issue.message,\n });\n }\n return result;\n}\n\n/**\n * Validate an object against a Standard Schema and produce a uniform\n * `ValidateResult`. The manifest output is captured for downstream consumers.\n */\nfunction validateWithStandardSchema<T>(\n schema: StandardSchemaV1<unknown, T>,\n obj: unknown,\n): { valid: true; output: T } | { valid: false; errors: SemanticIssue[] } {\n const result = schema[\"~standard\"].validate(obj);\n if (result instanceof Promise) {\n // Our schemas are synchronous; treat any async resolution as an error\n // rather than blocking the CLI on a background promise.\n return {\n valid: false,\n errors: [\n {\n level: \"error\",\n path: \"(root)\",\n message:\n \"Schema returned an async validation result; expected synchronous validation.\",\n },\n ],\n };\n }\n if (result.issues) {\n return { valid: false, errors: mapIssues(result.issues) };\n }\n return { valid: true, output: result.value };\n}\n\n/**\n * Validate a manifest object against the plugin-manifest schema.\n * Returns validation result with optional errors for CLI output.\n *\n * On success, the returned `manifest` is the original input object (preserving\n * its property order) typed as `PluginManifest`, not a re-emitted Zod copy.\n * Round-trip writers like `add-resource` rely on this to keep the on-disk\n * field ordering stable.\n */\nexport function validateManifest(obj: unknown): ValidateResult {\n if (!obj || typeof obj !== \"object\") {\n return {\n valid: false,\n errors: [\n {\n level: \"error\",\n path: \"(root)\",\n message: \"Manifest is not a valid object\",\n },\n ],\n };\n }\n\n const result = validateWithStandardSchema(pluginManifestSchema, obj);\n if (result.valid) {\n return { valid: true, manifest: obj as PluginManifest };\n }\n return { valid: false, errors: result.errors };\n}\n\n/**\n * Validate a template-plugins manifest (appkit.plugins.json) against its schema.\n */\nexport function validateTemplateManifest(obj: unknown): ValidateResult {\n if (!obj || typeof obj !== \"object\") {\n return {\n valid: false,\n errors: [\n {\n level: \"error\",\n path: \"(root)\",\n message: \"Template manifest is not a valid object\",\n },\n ],\n };\n }\n\n const result = validateWithStandardSchema(templatePluginsManifestSchema, obj);\n if (result.valid) {\n return { valid: true };\n }\n return { valid: false, errors: result.errors };\n}\n\n/**\n * Format validation issues for CLI output. Each issue is rendered on its own\n * line indented by two spaces, with the format ` <path>: <message>`. The\n * `path` is already humanized when issues are produced by `validateManifest`\n * / `validateTemplateManifest`.\n */\nexport function formatValidationErrors(issues: SemanticIssue[]): string {\n return issues.map((issue) => ` ${issue.path}: ${issue.message}`).join(\"\\n\");\n}\n"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"validate-manifest.js","names":[],"sources":["../../../../../src/cli/commands/plugin/validate/validate-manifest.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\n\nimport {\n pluginManifestSchema,\n templatePluginsManifestSchema,\n} from \"../../../../schemas/manifest\";\nimport type { PluginManifest } from \"../manifest-types\";\n\nexport type { PluginManifest };\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\n/**\n * A single validation issue produced by the schema validator. The `path`\n * is a humanized property path (e.g., `resources.required[0].permission`)\n * suitable for direct CLI output.\n */\nexport interface SemanticIssue {\n level: \"error\" | \"warning\";\n path: string;\n message: string;\n}\n\nexport interface ValidateResult {\n valid: boolean;\n manifest?: PluginManifest;\n errors?: SemanticIssue[];\n}\n\n/**\n * Convert a Standard Schema issue path (array of property keys / path segments)\n * into a humanized string like `resources.required[0].permission`.\n */\nfunction humanizePath(\n path: ReadonlyArray<PropertyKey | StandardSchemaV1.PathSegment> | undefined,\n): string {\n if (!path || path.length === 0) return \"(root)\";\n\n let out = \"\";\n for (const segment of path) {\n const key =\n typeof segment === \"object\" && segment !== null && \"key\" in segment\n ? segment.key\n : segment;\n if (typeof key === \"number\") {\n out += `[${key}]`;\n } else {\n const str = String(key);\n out += out.length === 0 ? str : `.${str}`;\n }\n }\n return out.length === 0 ? \"(root)\" : out;\n}\n\n/**\n * Map a Standard Schema issue list into the legacy `SemanticIssue` shape so\n * consumer code can stay uniform.\n */\nfunction mapIssues(\n issues: ReadonlyArray<StandardSchemaV1.Issue>,\n): SemanticIssue[] {\n const result: SemanticIssue[] = [];\n for (const issue of issues) {\n result.push({\n level: \"error\",\n path: humanizePath(issue.path),\n message: issue.message,\n });\n }\n return result;\n}\n\n/**\n * Validate an object against a Standard Schema and produce a uniform\n * `ValidateResult`. The manifest output is captured for downstream consumers.\n */\nfunction validateWithStandardSchema<T>(\n schema: StandardSchemaV1<unknown, T>,\n obj: unknown,\n): { valid: true; output: T } | { valid: false; errors: SemanticIssue[] } {\n const result = schema[\"~standard\"].validate(obj);\n if (result instanceof Promise) {\n // Our schemas are synchronous; treat any async resolution as an error\n // rather than blocking the CLI on a background promise.\n return {\n valid: false,\n errors: [\n {\n level: \"error\",\n path: \"(root)\",\n message:\n \"Schema returned an async validation result; expected synchronous validation.\",\n },\n ],\n };\n }\n if (result.issues) {\n return { valid: false, errors: mapIssues(result.issues) };\n }\n return { valid: true, output: result.value };\n}\n\n/**\n * Validate a manifest object against the plugin-manifest schema.\n * Returns validation result with optional errors for CLI output.\n *\n * On success, the returned `manifest` is the original input object (preserving\n * its property order) typed as `PluginManifest`, not a re-emitted Zod copy.\n * Round-trip writers like `add-resource` rely on this to keep the on-disk\n * field ordering stable.\n */\nexport function validateManifest(obj: unknown): ValidateResult {\n if (!obj || typeof obj !== \"object\") {\n return {\n valid: false,\n errors: [\n {\n level: \"error\",\n path: \"(root)\",\n message: \"Manifest is not a valid object\",\n },\n ],\n };\n }\n\n const result = validateWithStandardSchema(pluginManifestSchema, obj);\n if (result.valid) {\n return { valid: true, manifest: obj as PluginManifest };\n }\n return { valid: false, errors: result.errors };\n}\n\n/**\n * Validate a template-plugins manifest (appkit.plugins.json) against its schema.\n */\nexport function validateTemplateManifest(obj: unknown): ValidateResult {\n if (!obj || typeof obj !== \"object\") {\n return {\n valid: false,\n errors: [\n {\n level: \"error\",\n path: \"(root)\",\n message: \"Template manifest is not a valid object\",\n },\n ],\n };\n }\n\n const result = validateWithStandardSchema(templatePluginsManifestSchema, obj);\n if (result.valid) {\n return { valid: true };\n }\n return { valid: false, errors: result.errors };\n}\n\n/**\n * Format validation issues for CLI output. Each issue is rendered on its own\n * line indented by two spaces, with the format ` <path>: <message>`. The\n * `path` is already humanized when issues are produced by `validateManifest`\n * / `validateTemplateManifest`.\n */\nexport function formatValidationErrors(issues: SemanticIssue[]): string {\n return issues.map((issue) => ` ${issue.path}: ${issue.message}`).join(\"\\n\");\n}\n"],"mappings":";;;AAYA,MAAM,gBAA4C;CAChD,2EACE;CACF,4EACE;CACH;;;;;AAMD,SAAgB,iBAAiB,KAA0B;AACzD,KAAI,CAAC,OAAO,OAAO,QAAQ,SAAU,QAAO;CAC5C,MAAM,YAAa,IAAgC;AACnD,KAAI,OAAO,cAAc,SAAU,QAAO;AAC1C,QAAO,cAAc,cAAc;;;;;;AAwBrC,SAAS,aACP,MACQ;AACR,KAAI,CAAC,QAAQ,KAAK,WAAW,EAAG,QAAO;CAEvC,IAAI,MAAM;AACV,MAAK,MAAM,WAAW,MAAM;EAC1B,MAAM,MACJ,OAAO,YAAY,YAAY,YAAY,QAAQ,SAAS,UACxD,QAAQ,MACR;AACN,MAAI,OAAO,QAAQ,SACjB,QAAO,IAAI,IAAI;OACV;GACL,MAAM,MAAM,OAAO,IAAI;AACvB,UAAO,IAAI,WAAW,IAAI,MAAM,IAAI;;;AAGxC,QAAO,IAAI,WAAW,IAAI,WAAW;;;;;;AAOvC,SAAS,UACP,QACiB;CACjB,MAAM,SAA0B,EAAE;AAClC,MAAK,MAAM,SAAS,OAClB,QAAO,KAAK;EACV,OAAO;EACP,MAAM,aAAa,MAAM,KAAK;EAC9B,SAAS,MAAM;EAChB,CAAC;AAEJ,QAAO;;;;;;AAOT,SAAS,2BACP,QACA,KACwE;CACxE,MAAM,SAAS,OAAO,aAAa,SAAS,IAAI;AAChD,KAAI,kBAAkB,QAGpB,QAAO;EACL,OAAO;EACP,QAAQ,CACN;GACE,OAAO;GACP,MAAM;GACN,SACE;GACH,CACF;EACF;AAEH,KAAI,OAAO,OACT,QAAO;EAAE,OAAO;EAAO,QAAQ,UAAU,OAAO,OAAO;EAAE;AAE3D,QAAO;EAAE,OAAO;EAAM,QAAQ,OAAO;EAAO;;;;;;;;;;;AAY9C,SAAgB,iBAAiB,KAA8B;AAC7D,KAAI,CAAC,OAAO,OAAO,QAAQ,SACzB,QAAO;EACL,OAAO;EACP,QAAQ,CACN;GACE,OAAO;GACP,MAAM;GACN,SAAS;GACV,CACF;EACF;CAGH,MAAM,SAAS,2BAA2B,sBAAsB,IAAI;AACpE,KAAI,OAAO,MACT,QAAO;EAAE,OAAO;EAAM,UAAU;EAAuB;AAEzD,QAAO;EAAE,OAAO;EAAO,QAAQ,OAAO;EAAQ;;;;;AAMhD,SAAgB,yBAAyB,KAA8B;AACrE,KAAI,CAAC,OAAO,OAAO,QAAQ,SACzB,QAAO;EACL,OAAO;EACP,QAAQ,CACN;GACE,OAAO;GACP,MAAM;GACN,SAAS;GACV,CACF;EACF;CAGH,MAAM,SAAS,2BAA2B,+BAA+B,IAAI;AAC7E,KAAI,OAAO,MACT,QAAO,EAAE,OAAO,MAAM;AAExB,QAAO;EAAE,OAAO;EAAO,QAAQ,OAAO;EAAQ;;;;;;;;AAShD,SAAgB,uBAAuB,QAAiC;AACtE,QAAO,OAAO,KAAK,UAAU,KAAK,MAAM,KAAK,IAAI,MAAM,UAAU,CAAC,KAAK,KAAK"}
|
|
@@ -1 +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 loadManifestFromFile,\n type ResolvedManifest,\n resolveManifestInDir,\n} from \"../manifest-resolve\";\nimport type { ValidateResult } from \"./validate-manifest\";\nimport {\n detectSchemaType,\n formatValidationErrors,\n validateManifest,\n validateTemplateManifest,\n} from \"./validate-manifest\";\n\nfunction resolveManifestPaths(\n paths: string[],\n cwd: string,\n allowJsManifest: boolean,\n): ResolvedManifest[] {\n const out: ResolvedManifest[] = [];\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 let found = false;\n const pluginResolved = resolveManifestInDir(resolved, {\n allowJsManifest,\n });\n if (pluginResolved) {\n out.push(pluginResolved);\n found = true;\n }\n const templateManifest = path.join(resolved, \"appkit.plugins.json\");\n if (fs.existsSync(templateManifest)) {\n out.push({ path: templateManifest, type: \"json\" });\n found = true;\n }\n if (!found) {\n console.error(\n `No ${allowJsManifest ? \"manifest.json, manifest.js, or\" : \"manifest.json or\"} appkit.plugins.json in directory: ${p}`,\n );\n }\n } else {\n const ext = path.extname(resolved).toLowerCase();\n if (!allowJsManifest && (ext === \".js\" || ext === \".cjs\")) {\n console.error(\n `JS manifest provided but disabled by default: ${p}. Re-run with --allow-js-manifest to opt in.`,\n );\n continue;\n }\n out.push({\n path: resolved,\n type: ext === \".js\" || ext === \".cjs\" ? \"js\" : \"json\",\n });\n }\n }\n return out;\n}\n\ninterface ValidateOptions {\n allowJsManifest?: boolean;\n json?: boolean;\n}\n\nasync function runPluginValidate(\n paths: string[],\n options: ValidateOptions,\n): Promise<void> {\n const cwd = process.cwd();\n const allowJsManifest = Boolean(options.allowJsManifest);\n if (allowJsManifest && !options.json) {\n console.warn(\n \"Warning: --allow-js-manifest executes manifest.js/manifest.cjs files. Only use with trusted code.\",\n );\n }\n const toValidate = paths.length > 0 ? paths : [\".\"];\n const manifestPaths = resolveManifestPaths(toValidate, cwd, allowJsManifest);\n\n if (manifestPaths.length === 0) {\n if (options.json) {\n console.log(\"[]\");\n } else {\n console.error(\"No manifest files to validate.\");\n }\n process.exit(1);\n }\n\n let hasFailure = false;\n const jsonResults: {\n path: string;\n valid: boolean;\n errors?: string[];\n warnings?: string[];\n }[] = [];\n\n for (const { path: manifestPath, type } of manifestPaths) {\n const relativePath = path.relative(cwd, manifestPath);\n let obj: unknown;\n try {\n obj = await loadManifestFromFile(manifestPath, type, { allowJsManifest });\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n if (options.json) {\n jsonResults.push({\n path: relativePath,\n valid: false,\n errors: [errMsg],\n });\n } else {\n console.error(`✗ ${manifestPath}`);\n console.error(` ${errMsg}`);\n }\n hasFailure = true;\n continue;\n }\n\n const schemaType = detectSchemaType(obj);\n const result: ValidateResult =\n schemaType === \"template-plugins\"\n ? validateTemplateManifest(obj)\n : validateManifest(obj);\n\n if (result.valid) {\n if (options.json) {\n jsonResults.push({ path: relativePath, valid: true });\n } else {\n console.log(`✓ ${relativePath}`);\n }\n } else {\n if (options.json) {\n const errors = result.errors?.length\n ? formatValidationErrors(result.errors).split(\"\\n\").filter(Boolean)\n : [];\n jsonResults.push({\n path: relativePath,\n valid: false,\n ...(errors.length > 0 && { errors }),\n });\n } else {\n console.error(`✗ ${relativePath}`);\n if (result.errors?.length) {\n console.error(formatValidationErrors(result.errors));\n }\n }\n hasFailure = true;\n }\n }\n\n if (options.json) {\n console.log(JSON.stringify(jsonResults, null, 2));\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 or appkit.plugins.json (or plugin directories); use --allow-js-manifest to include manifest.js\",\n )\n .option(\n \"--allow-js-manifest\",\n \"Allow reading manifest.js/manifest.cjs (executes code; use only with trusted plugins)\",\n )\n .option(\"--json\", \"Output validation results as JSON\")\n .addHelpText(\n \"after\",\n `\nExamples:\n $ appkit plugin validate\n $ appkit plugin validate plugins/my-plugin\n $ appkit plugin validate plugins/my-plugin plugins/other\n $ appkit plugin validate appkit.plugins.json\n $ appkit plugin validate --json`,\n )\n .action((paths: string[], opts: ValidateOptions) =>\n runPluginValidate(paths, opts).catch((err) => {\n console.error(err);\n process.exit(1);\n }),\n );\n"],"mappings":";;;;;;;;
|
|
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\";\n\nimport { Command } from \"commander\";\n\nimport {\n loadManifestFromFile,\n type ResolvedManifest,\n resolveManifestInDir,\n} from \"../manifest-resolve\";\nimport type { ValidateResult } from \"./validate-manifest\";\nimport {\n detectSchemaType,\n formatValidationErrors,\n validateManifest,\n validateTemplateManifest,\n} from \"./validate-manifest\";\n\nfunction resolveManifestPaths(\n paths: string[],\n cwd: string,\n allowJsManifest: boolean,\n): ResolvedManifest[] {\n const out: ResolvedManifest[] = [];\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 let found = false;\n const pluginResolved = resolveManifestInDir(resolved, {\n allowJsManifest,\n });\n if (pluginResolved) {\n out.push(pluginResolved);\n found = true;\n }\n const templateManifest = path.join(resolved, \"appkit.plugins.json\");\n if (fs.existsSync(templateManifest)) {\n out.push({ path: templateManifest, type: \"json\" });\n found = true;\n }\n if (!found) {\n console.error(\n `No ${allowJsManifest ? \"manifest.json, manifest.js, or\" : \"manifest.json or\"} appkit.plugins.json in directory: ${p}`,\n );\n }\n } else {\n const ext = path.extname(resolved).toLowerCase();\n if (!allowJsManifest && (ext === \".js\" || ext === \".cjs\")) {\n console.error(\n `JS manifest provided but disabled by default: ${p}. Re-run with --allow-js-manifest to opt in.`,\n );\n continue;\n }\n out.push({\n path: resolved,\n type: ext === \".js\" || ext === \".cjs\" ? \"js\" : \"json\",\n });\n }\n }\n return out;\n}\n\ninterface ValidateOptions {\n allowJsManifest?: boolean;\n json?: boolean;\n}\n\nasync function runPluginValidate(\n paths: string[],\n options: ValidateOptions,\n): Promise<void> {\n const cwd = process.cwd();\n const allowJsManifest = Boolean(options.allowJsManifest);\n if (allowJsManifest && !options.json) {\n console.warn(\n \"Warning: --allow-js-manifest executes manifest.js/manifest.cjs files. Only use with trusted code.\",\n );\n }\n const toValidate = paths.length > 0 ? paths : [\".\"];\n const manifestPaths = resolveManifestPaths(toValidate, cwd, allowJsManifest);\n\n if (manifestPaths.length === 0) {\n if (options.json) {\n console.log(\"[]\");\n } else {\n console.error(\"No manifest files to validate.\");\n }\n process.exit(1);\n }\n\n let hasFailure = false;\n const jsonResults: {\n path: string;\n valid: boolean;\n errors?: string[];\n warnings?: string[];\n }[] = [];\n\n for (const { path: manifestPath, type } of manifestPaths) {\n const relativePath = path.relative(cwd, manifestPath);\n let obj: unknown;\n try {\n obj = await loadManifestFromFile(manifestPath, type, { allowJsManifest });\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n if (options.json) {\n jsonResults.push({\n path: relativePath,\n valid: false,\n errors: [errMsg],\n });\n } else {\n console.error(`✗ ${manifestPath}`);\n console.error(` ${errMsg}`);\n }\n hasFailure = true;\n continue;\n }\n\n const schemaType = detectSchemaType(obj);\n const result: ValidateResult =\n schemaType === \"template-plugins\"\n ? validateTemplateManifest(obj)\n : validateManifest(obj);\n\n if (result.valid) {\n if (options.json) {\n jsonResults.push({ path: relativePath, valid: true });\n } else {\n console.log(`✓ ${relativePath}`);\n }\n } else {\n if (options.json) {\n const errors = result.errors?.length\n ? formatValidationErrors(result.errors).split(\"\\n\").filter(Boolean)\n : [];\n jsonResults.push({\n path: relativePath,\n valid: false,\n ...(errors.length > 0 && { errors }),\n });\n } else {\n console.error(`✗ ${relativePath}`);\n if (result.errors?.length) {\n console.error(formatValidationErrors(result.errors));\n }\n }\n hasFailure = true;\n }\n }\n\n if (options.json) {\n console.log(JSON.stringify(jsonResults, null, 2));\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 or appkit.plugins.json (or plugin directories); use --allow-js-manifest to include manifest.js\",\n )\n .option(\n \"--allow-js-manifest\",\n \"Allow reading manifest.js/manifest.cjs (executes code; use only with trusted plugins)\",\n )\n .option(\"--json\", \"Output validation results as JSON\")\n .addHelpText(\n \"after\",\n `\nExamples:\n $ appkit plugin validate\n $ appkit plugin validate plugins/my-plugin\n $ appkit plugin validate plugins/my-plugin plugins/other\n $ appkit plugin validate appkit.plugins.json\n $ appkit plugin validate --json`,\n )\n .action((paths: string[], opts: ValidateOptions) =>\n runPluginValidate(paths, opts).catch((err) => {\n console.error(err);\n process.exit(1);\n }),\n );\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"add.js","names":[],"sources":["../../../../src/cli/commands/registry/add.ts"],"sourcesContent":["import { spawnSync } from \"node:child_process\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport process from \"node:process\";\nimport { Command } from \"commander\";\nimport pc from \"picocolors\";\nimport {\n fetchRegistryItem,\n fetchVerifiedNames,\n isValidItemName,\n type RegistryItem,\n type RegistryItemFile,\n stripNamespace,\n} from \"./client\";\nimport {\n buildConfigPlan,\n collectBindingValueNeeds,\n planHasContent,\n} from \"./config-plan\";\nimport {\n reportConfigWrite,\n validateBundle,\n writeConfig,\n} from \"./config-writer\";\nimport {\n JS_IDENTIFIER,\n REGISTRY_REPO,\n type RegistryToken,\n resolveToken,\n} from \"./constants\";\nimport { parseEnv } from \"./env-reconcile\";\nimport {\n extractRequirements,\n type ResourceRequirementRow,\n renderRequirements,\n} from \"./requirements\";\n\n/** Subdirectories that commonly hold the frontend / server in an AppKit app. */\nconst FRONTEND_SUBDIRS = [\"client\", \"frontend\", \"web\", \"app\"];\nconst SERVER_SUBDIRS = [\"server\", \"api\", \"backend\"];\n\nfunction isDir(p: string): boolean {\n return fs.existsSync(p) && fs.statSync(p).isDirectory();\n}\n\n/** A registry item is a server plugin if it ships a manifest.json. */\nfunction isPluginItem(item: RegistryItem): boolean {\n return (item.files ?? []).some(\n (f) => path.basename(f.target ?? f.path) === \"manifest.json\",\n );\n}\n\n/**\n * Locates the frontend root for UI components. AppKit apps put the client in a\n * client/ subdir (with its own components.json + src/); the CLI is typically\n * run from the repo root. Prefer the dir with components.json, then a src/.\n */\nfunction findFrontendRoot(cwd: string): string {\n if (fs.existsSync(path.join(cwd, \"components.json\"))) return cwd;\n for (const sub of FRONTEND_SUBDIRS) {\n if (fs.existsSync(path.join(cwd, sub, \"components.json\"))) {\n return path.join(cwd, sub);\n }\n }\n if (isDir(path.join(cwd, \"src\"))) return cwd;\n for (const sub of FRONTEND_SUBDIRS) {\n if (isDir(path.join(cwd, sub, \"src\"))) return path.join(cwd, sub);\n }\n return cwd;\n}\n\n/** Locates the server root for plugins (the server/ subdir, else cwd). */\nfunction findServerRoot(cwd: string): string {\n for (const sub of SERVER_SUBDIRS) {\n if (isDir(path.join(cwd, sub))) return path.join(cwd, sub);\n }\n return cwd;\n}\n\n/** Nearest dir with a package.json, walking up from start (for dep install). */\nfunction findNearestPackageJson(start: string): string {\n let dir = start;\n for (;;) {\n if (fs.existsSync(path.join(dir, \"package.json\"))) return dir;\n const parent = path.dirname(dir);\n if (parent === dir) return start;\n dir = parent;\n }\n}\n\n/**\n * The Databricks profile the app is configured with, read from\n * `DATABRICKS_CONFIG_PROFILE` in `cwd/.env` via the same dotenv parser the app\n * loads its env with. The CLI's top-level `dotenv/config` only loads the launch\n * dir's `.env` into `process.env`; when `--cwd` points at a different app dir,\n * that file isn't loaded, so the workspace picker and bundle validate would\n * miss the app's profile — this reads it directly. Undefined if the file or key\n * is absent, so the SDK's own default resolution still applies.\n */\nexport function profileFromEnv(cwd: string): string | undefined {\n try {\n const content = fs.readFileSync(path.join(cwd, \".env\"), \"utf-8\");\n return parseEnv(content).DATABRICKS_CONFIG_PROFILE || undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Resolves a registry item's `target` under `base`, enforcing that the result\n * stays inside `base`. Registry items are untrusted remote data; a `target`\n * like `../../../.zshrc` or an absolute path could otherwise write files\n * anywhere on disk (arbitrary-write → RCE). Throws on any escape.\n */\nexport function resolveWithinBase(base: string, target: string): string {\n if (path.isAbsolute(target)) {\n throw new Error(`Refusing absolute file target from registry: ${target}`);\n }\n const baseResolved = path.resolve(base);\n const resolved = path.resolve(baseResolved, target);\n if (\n resolved !== baseResolved &&\n !resolved.startsWith(baseResolved + path.sep)\n ) {\n throw new Error(\n `Refusing file target that escapes the destination directory: ${target}`,\n );\n }\n return resolved;\n}\n\n/** UI file destination (relative to the frontend root): placed in src/ if present. */\nfunction uiTargetPath(base: string, file: RegistryItemFile): string {\n let target = file.target ?? path.join(\"components\", path.basename(file.path));\n if (!target.startsWith(\"src/\") && isDir(path.join(base, \"src\"))) {\n target = path.join(\"src\", target);\n }\n return target;\n}\n\n/** Best-effort: the `toPlugin` export name from the item's index.ts. Returns\n * null (caller falls back to printed instructions) unless the name is a plain\n * JS identifier — the item is untrusted remote content and the value is\n * interpolated into the user's server.ts. */\nexport function pluginExportName(item: RegistryItem): string | null {\n const index = (item.files ?? []).find(\n (f) => path.basename(f.target ?? f.path) === \"index.ts\",\n );\n const match = index?.content.match(/export\\s*\\{([^}]*)\\}/);\n if (!match) return null;\n const names = match[1].split(\",\").map((s) => s.trim());\n // Prefer the camelCase toPlugin instance over the PascalCase class.\n const chosen = names.find((n) => /^[a-z]/.test(n)) ?? names[0];\n return chosen && JS_IDENTIFIER.test(chosen) ? chosen : null;\n}\n\nfunction detectPackageManager(cwd: string): \"pnpm\" | \"yarn\" | \"bun\" | \"npm\" {\n if (fs.existsSync(path.join(cwd, \"pnpm-lock.yaml\"))) return \"pnpm\";\n if (fs.existsSync(path.join(cwd, \"yarn.lock\"))) return \"yarn\";\n if (fs.existsSync(path.join(cwd, \"bun.lockb\"))) return \"bun\";\n return \"npm\";\n}\n\n/**\n * A safe npm dependency spec: `[@scope/]name` with an optional `@version`\n * range. Registry `dependencies` are untrusted remote data passed to the\n * package manager, so we reject anything that isn't a plain name+range —\n * blocks tarball/git URL specs (install-script RCE) and `-`-prefixed entries\n * that the PM would parse as flags (argument injection).\n */\nconst SAFE_DEP_SPEC =\n /^(@[a-z0-9][\\w.-]*\\/)?[a-z0-9][\\w.-]*(@[\\w.\\-+~^><=|* ]+)?$/i;\n\n/** Splits deps into safe (installable) and rejected (surfaced to the user). */\nexport function partitionDeps(deps: string[]): {\n safe: string[];\n rejected: string[];\n} {\n const safe: string[] = [];\n const rejected: string[] = [];\n for (const dep of deps) {\n if (dep.startsWith(\"-\") || !SAFE_DEP_SPEC.test(dep)) rejected.push(dep);\n else safe.push(dep);\n }\n return { safe, rejected };\n}\n\nfunction installDependencies(deps: string[], cwd: string): void {\n if (deps.length === 0) return;\n\n const { safe, rejected } = partitionDeps(deps);\n if (rejected.length > 0) {\n console.warn(\n pc.yellow(\n `Skipping suspicious dependenc${rejected.length === 1 ? \"y\" : \"ies\"} from the registry (not a plain name@version): ${rejected.join(\", \")}. Install manually if you trust them.`,\n ),\n );\n }\n if (safe.length === 0) return;\n\n if (!fs.existsSync(path.join(cwd, \"package.json\"))) {\n console.warn(\n pc.yellow(\n `No package.json found — install these manually: ${safe.join(\" \")}`,\n ),\n );\n return;\n }\n const pm = detectPackageManager(cwd);\n const subcommand = pm === \"npm\" ? \"install\" : \"add\";\n console.log(`\\nInstalling dependencies with ${pm}: ${safe.join(\" \")}`);\n // `--` stops the PM from parsing any dep as a flag (defense in depth).\n const result = spawnSync(pm, [subcommand, \"--\", ...safe], {\n stdio: \"inherit\",\n cwd,\n });\n if (result.status !== 0) {\n console.warn(\n pc.yellow(\n `Dependency install exited with code ${result.status ?? \"unknown\"} — install manually if needed: ${safe.join(\" \")}`,\n ),\n );\n }\n}\n\n/** Runs `appkit plugin sync --write` via this same CLI binary. */\nfunction runPluginSync(cwd: string): void {\n const result = spawnSync(\n process.execPath,\n [process.argv[1], \"plugin\", \"sync\", \"--write\"],\n { stdio: \"inherit\", cwd },\n );\n if (result.status !== 0) {\n console.warn(\n pc.yellow(\n \" Plugin sync did not complete cleanly — run `appkit plugin sync --write` manually.\",\n ),\n );\n }\n}\n\nfunction writeItemFile(\n base: string,\n target: string,\n content: string,\n force: boolean,\n cwd: string,\n): void {\n const dest = resolveWithinBase(base, target);\n const existed = fs.existsSync(dest);\n if (existed && !force) {\n console.error(\n pc.red(\n `Refusing to overwrite ${path.relative(cwd, dest)} — pass --force to replace it.`,\n ),\n );\n process.exit(1);\n }\n fs.mkdirSync(path.dirname(dest), { recursive: true });\n fs.writeFileSync(dest, content);\n const label = existed ? pc.yellow(\"Updated\") : pc.green(\"Created\");\n console.log(`${label} ${path.relative(cwd, dest)}`);\n}\n\ninterface PluginSummary {\n importPath: string;\n exportName: string | null;\n}\n\n/**\n * Fetches the requested items plus their transitive registryDependencies.\n * Dependencies are resolved breadth-first and de-duplicated by name, so a\n * plugin that depends on another registry item pulls the whole graph in one\n * `add`. Explicitly-requested items keep their request order and come first.\n */\nexport async function resolveItems(\n names: string[],\n token: RegistryToken | null,\n fetchItem: (\n name: string,\n token: RegistryToken | null,\n ) => Promise<RegistryItem> = fetchRegistryItem,\n): Promise<RegistryItem[]> {\n const seen = new Set<string>();\n const ordered: RegistryItem[] = [];\n // A name is both the fetch path and the on-disk `plugins/<name>` dir, so\n // reject non-slug refs (`/`, `..`, control chars) before they reach either.\n const enqueue = (ref: string): string => {\n const name = stripNamespace(ref);\n if (!isValidItemName(name)) {\n throw new Error(`Invalid registry item name: ${JSON.stringify(ref)}`);\n }\n return name;\n };\n // Breadth-first over the dependency graph, one level per iteration. Items in\n // a level are fetched concurrently (fetch latency is additive otherwise), but\n // levels stay ordered and dedup/cycle handling is unchanged: a name is marked\n // seen before its level is fetched, so it's never fetched or queued twice.\n let level = names.map(enqueue).filter((name) => {\n if (seen.has(name)) return false;\n seen.add(name);\n return true;\n });\n\n while (level.length > 0) {\n const items = await Promise.all(\n level.map(async (key) => {\n const item = await fetchItem(key, token);\n // Pin to the fetch key: the body's self-reported `name` is untrusted\n // and could claim a verified name to slip past the gate. The key is the\n // trustworthy identity the index keys `verified` on.\n item.name = key;\n return item;\n }),\n );\n ordered.push(...items);\n const next: string[] = [];\n for (const item of items) {\n for (const dep of item.registryDependencies ?? []) {\n const depName = enqueue(dep);\n if (seen.has(depName)) continue;\n seen.add(depName);\n next.push(depName);\n }\n }\n level = next;\n }\n\n return ordered;\n}\n\ninterface AddOptions {\n force?: boolean;\n cwd?: string;\n register?: boolean;\n /** false = don't reconcile resource env vars into .env. */\n resources?: boolean;\n /** true = never prompt; use --env flags or leave unset (agent/CI). */\n yes?: boolean;\n /** Pre-supplied env values from repeated --env KEY=VALUE flags. */\n env?: Record<string, string>;\n /** Databricks profile passed to `bundle validate` after writing config. */\n profile?: string;\n /** true = install items the registry index doesn't mark verified. */\n allowUnverified?: boolean;\n}\n\n/**\n * Splits requested names into verified and unverified against the index's\n * verified set. When `verified` is null the index couldn't be read — we can't\n * prove anything is verified, so every name is treated as unverified (the gate\n * then decides whether to warn-and-continue or block). Names are compared with\n * the namespace stripped, matching how items are fetched.\n */\nexport function partitionVerified(\n refs: string[],\n verified: Set<string> | null,\n): { verified: string[]; unverified: string[] } {\n const ok: string[] = [];\n const bad: string[] = [];\n for (const ref of refs) {\n const name = stripNamespace(ref);\n if (verified?.has(name)) ok.push(name);\n else bad.push(name);\n }\n return { verified: ok, unverified: bad };\n}\n\nasync function runAdd(refs: string[], opts: AddOptions): Promise<void> {\n const cwd = opts.cwd ? path.resolve(opts.cwd) : process.cwd();\n const token = resolveToken();\n if (token) {\n console.log(\n `Using ${token.envName} to fetch from ${REGISTRY_REPO} (private).`,\n );\n }\n\n // Resolve the full graph and fetch the verified index concurrently (two\n // independent round-trips). Item resolution is read-only — nothing is written\n // or installed until after the gate below.\n const verifiedP = opts.allowUnverified\n ? Promise.resolve(null)\n : fetchVerifiedNames(token);\n const items = await resolveItems(refs, token);\n\n // Integrity gate over the *entire resolved set* (not just requested names, so\n // an unverified transitive dep can't ride in on a verified item). Fails closed:\n // a null verified set (unreadable index) makes every item count as unverified.\n if (!opts.allowUnverified) {\n const verified = await verifiedP;\n const { unverified } = partitionVerified(\n items.map((i) => i.name),\n verified,\n );\n if (unverified.length > 0) {\n const reason =\n verified === null\n ? \"could not read the registry index to verify these items\"\n : `not marked verified in ${REGISTRY_REPO}`;\n console.error(\n pc.red(\n `Refusing to add unverified item(s) (${reason}): ${unverified.join(\", \")}.`,\n ),\n );\n console.error(\n pc.dim(\n \" Re-run with --allow-unverified if you trust the source; unverified items run code in your app.\",\n ),\n );\n process.exit(1);\n }\n }\n\n const hasUi = items.some((i) => !isPluginItem(i));\n const hasPlugin = items.some(isPluginItem);\n const frontendRoot = hasUi ? findFrontendRoot(cwd) : cwd;\n const serverRoot = hasPlugin ? findServerRoot(cwd) : cwd;\n if (hasUi && frontendRoot !== cwd) {\n console.log(pc.dim(`UI components → ${path.relative(cwd, frontendRoot)}/`));\n }\n if (hasPlugin && serverRoot !== cwd) {\n console.log(pc.dim(`Plugins → ${path.relative(cwd, serverRoot)}/`));\n }\n\n const deps = new Set<string>();\n let wroteUi = false;\n const pluginSummaries: PluginSummary[] = [];\n const allRequirements: ResourceRequirementRow[] = [];\n\n for (const item of items) {\n for (const dep of item.dependencies ?? []) deps.add(dep);\n\n if (isPluginItem(item)) {\n let pluginRel = path.join(\"plugins\", item.name);\n for (const file of item.files ?? []) {\n const target =\n file.target ??\n path.join(\"plugins\", item.name, path.basename(file.path));\n writeItemFile(\n serverRoot,\n target,\n file.content,\n Boolean(opts.force),\n cwd,\n );\n if (path.basename(target) === \"manifest.json\") {\n pluginRel = path.dirname(target);\n }\n }\n const requirements = extractRequirements(item);\n if (requirements.length > 0) {\n console.log(`\\n${renderRequirements(item, requirements)}`);\n allRequirements.push(...requirements);\n }\n pluginSummaries.push({\n importPath: `./${pluginRel}`,\n exportName: pluginExportName(item),\n });\n } else {\n for (const file of item.files ?? []) {\n writeItemFile(\n frontendRoot,\n uiTargetPath(frontendRoot, file),\n file.content,\n Boolean(opts.force),\n cwd,\n );\n wroteUi = true;\n }\n }\n }\n\n installDependencies([...deps], findNearestPackageJson(cwd));\n\n if (hasPlugin) {\n console.log(pc.dim(\"\\nRegistering plugins (appkit plugin sync)...\"));\n runPluginSync(cwd);\n }\n\n if (wroteUi) {\n console.log(\n pc.dim(\n '\\nReminder: import \"@databricks/appkit-ui/styles.css\" once at your app root so components are themed.',\n ),\n );\n }\n // Lazy import: server-register pulls in @ast-grep/napi (a native addon), and\n // this CLI is imported eagerly by index.ts, so a static import would make\n // every unrelated command pay that cost.\n const registerPluginInServer =\n opts.register !== false && pluginSummaries.some((s) => s.exportName)\n ? (await import(\"./server-register.js\")).registerPluginInServer\n : null;\n for (const s of pluginSummaries) {\n // Try to wire the plugin into the server's createApp call automatically;\n // fall back to printing the snippet when the shape isn't the standard one.\n let wired = false;\n // Why auto-registration was skipped, so the fallback can say so (an empty\n // reason means the user opted out via --no-register — no nag then).\n let skipReason: string | undefined;\n if (opts.register === false) {\n // opted out — print the snippet without a \"couldn't\" message\n } else if (!s.exportName) {\n skipReason =\n \"couldn't read a plugin export name from the item's index.ts\";\n } else if (registerPluginInServer) {\n const result = registerPluginInServer(\n serverRoot,\n s.importPath,\n s.exportName,\n );\n // result.file is relative to serverRoot; show it from cwd for the user.\n const shown =\n result.file && path.relative(cwd, path.join(serverRoot, result.file));\n if (result.status === \"wired\") {\n console.log(`\\n${pc.green(\"Registered\")} ${s.exportName} in ${shown}`);\n wired = true;\n } else if (result.status === \"already\") {\n console.log(\n pc.dim(`\\n${s.exportName} is already registered in ${shown}`),\n );\n wired = true;\n } else {\n skipReason = result.reason;\n }\n }\n if (!wired) {\n const imp = s.exportName ?? \"<plugin>\";\n if (skipReason) {\n console.log(\n pc.yellow(`\\nCouldn't auto-register ${imp} — ${skipReason}.`),\n );\n }\n console.log(\n `${pc.bold(\"Add this to your server's createApp call:\")}\\n` +\n pc.dim(\n ` import { ${imp} } from \"${s.importPath}\";\\n` +\n ` const app = await createApp({ plugins: [${imp}(), /* ... */] });`,\n ),\n );\n }\n }\n\n if (opts.resources !== false && allRequirements.length > 0) {\n // Lazy import (same reason as server-register above): env-writer pulls in\n // the workspace picker and, through it, the Databricks SDK.\n const { collectBindingValues, reportEnvResolutions, syncEnv } =\n await import(\"./env-writer.js\");\n // Without --profile, fall back to the app's configured profile so the\n // picker and bundle validate reach the workspace the app runs against.\n const profile = opts.profile ?? profileFromEnv(cwd);\n console.log(pc.dim(\"\\nReconciling resource env vars into .env...\"));\n const resolutions = await syncEnv(allRequirements, {\n cwd,\n nonInteractive: Boolean(opts.yes),\n values: opts.env,\n profile,\n });\n reportEnvResolutions(resolutions);\n\n // Deploy config (app.yaml + databricks.yml). Values come from what the\n // user supplied for env fields (flags or prompts); other fields fall back\n // to their manifest defaults inside buildConfigPlan.\n const values: Record<string, string> = { ...(opts.env ?? {}) };\n for (const r of resolutions) {\n if (r.value !== undefined) values[r.env] = r.value;\n }\n // Binding fields with no env name (e.g. postgres project/branch/database)\n // never flow through .env, so collect them separately — else their\n // databricks.yml bundle variables stay unassigned and bundle validate fails.\n const bindingNeeds = collectBindingValueNeeds(allRequirements);\n if (bindingNeeds.length > 0) {\n const bindingValues = await collectBindingValues(bindingNeeds, {\n cwd,\n nonInteractive: Boolean(opts.yes),\n values: opts.env,\n profile,\n });\n Object.assign(values, bindingValues);\n }\n const plan = buildConfigPlan(allRequirements, values);\n if (planHasContent(plan)) {\n const result = writeConfig(cwd, plan);\n reportConfigWrite(result);\n if (result.databricksYmlChanged) validateBundle(cwd, profile);\n }\n warnScopeNeeding(allRequirements);\n }\n}\n\n/**\n * v1 does not write `user_api_scopes` (deferred to the manifest scope\n * extension). Warn when an added plugin's resource type is known to need one,\n * so the user adds it before deploy.\n */\n/** Resource types known to require a user_api_scope, and the scope each needs. */\nexport const SCOPE_BY_RESOURCE_TYPE: Record<string, string> = {\n genie_space: \"dashboards.genie\",\n serving_endpoint: \"serving.serving-endpoints\",\n // volumes/files-backed access uses files.files\n volume: \"files.files\",\n};\n\n/** Returns the user_api_scopes implied by a set of resource rows (deduped). */\nexport function scopesForResources(\n rows: ResourceRequirementRow[],\n): Map<string, string> {\n const needed = new Map<string, string>();\n for (const row of rows) {\n const scope = SCOPE_BY_RESOURCE_TYPE[row.type];\n if (scope) needed.set(row.type, scope);\n }\n return needed;\n}\n\nfunction warnScopeNeeding(rows: ResourceRequirementRow[]): void {\n const needed = scopesForResources(rows);\n if (needed.size === 0) return;\n const list = [...needed.entries()]\n .map(([type, scope]) => `${type} → ${scope}`)\n .join(\", \");\n console.warn(\n pc.yellow(\n `\\n Note: these resources may need a user_api_scope before deploy: ${list}.\\n` +\n \" Add it under resources.apps.app.user_api_scopes in databricks.yml.\",\n ),\n );\n}\n\n/** Commander reducer for repeatable `--env KEY=VALUE` flags. */\nfunction collectEnvFlag(\n raw: string,\n acc: Record<string, string>,\n): Record<string, string> {\n const eq = raw.indexOf(\"=\");\n if (eq === -1) {\n console.error(`Ignoring --env \"${raw}\" (expected KEY=VALUE).`);\n return acc;\n }\n const key = raw.slice(0, eq).trim();\n const value = raw.slice(eq + 1);\n if (key) acc[key] = value;\n return acc;\n}\n\nexport const addCommand = new Command(\"add\")\n .description(\"Add a UI component or server plugin from the AppKit registry\")\n .argument(\"<item...>\", \"Registry item name(s), e.g. metric-card or hello\")\n .option(\"-f, --force\", \"Overwrite existing files\")\n .option(\"-C, --cwd <dir>\", \"Run as if started in <dir>\")\n .option(\"--no-register\", \"Don't edit the server entry to register plugins\")\n .option(\"--no-resources\", \"Don't reconcile resource env vars into .env\")\n .option(\"-y, --yes\", \"Don't prompt; use --env values or leave vars unset\")\n .option(\n \"--env <KEY=VALUE>\",\n \"Pre-set a resource env var (repeatable)\",\n collectEnvFlag,\n {},\n )\n .option(\n \"-p, --profile <name>\",\n \"Databricks profile for the resource picker and bundle validate (defaults to the app's DATABRICKS_CONFIG_PROFILE)\",\n )\n .option(\n \"--allow-unverified\",\n \"Add items the registry doesn't mark verified (runs untrusted code)\",\n )\n .addHelpText(\n \"after\",\n `\nNo components.json is required. Item type is detected automatically:\n • UI components → <frontend>/src/components/appkit/ (client/ detected)\n • Server plugins → <server>/plugins/<name>/, runs plugin sync, and registers\n them in your createApp call (use --no-register to skip the server edit)\n\nServer plugins declare Databricks resources. On add, their env vars are\nreconciled into .env (and names into .env.example), and the deploy config\n(app.yaml + databricks.yml resource bindings) is patched to match — existing\nentries are never clobbered. Interactive by default; pass --yes for agents/CI\n(uses --env values, leaves the rest unset) and --env KEY=VALUE to supply\nvalues non-interactively. Pass --profile to validate the bundle after writing.\n\nThe frontend/server roots are detected from common layouts, so you can run\nthis from the repo root. While the registry repo is private, a read token is\nresolved from \\`gh auth token\\` or APPKIT_REGISTRY_TOKEN / GITHUB_TOKEN / GH_TOKEN.\n\nExamples:\n $ appkit add metric-card # UI component\n $ appkit add hello # server plugin\n $ appkit add metric-card hello # mix in one call\n $ appkit add analytics --yes --env DATABRICKS_WAREHOUSE_ID=abc123`,\n )\n .action((items: string[], opts: AddOptions) =>\n runAdd(items, opts).catch((err) => {\n console.error(err);\n process.exit(1);\n }),\n );\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"add.js","names":[],"sources":["../../../../src/cli/commands/registry/add.ts"],"sourcesContent":["import { spawnSync } from \"node:child_process\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport process from \"node:process\";\n\nimport { Command } from \"commander\";\nimport pc from \"picocolors\";\n\nimport {\n fetchRegistryItem,\n fetchVerifiedNames,\n isValidItemName,\n type RegistryItem,\n type RegistryItemFile,\n stripNamespace,\n} from \"./client\";\nimport {\n buildConfigPlan,\n collectBindingValueNeeds,\n planHasContent,\n} from \"./config-plan\";\nimport {\n reportConfigWrite,\n validateBundle,\n writeConfig,\n} from \"./config-writer\";\nimport {\n JS_IDENTIFIER,\n REGISTRY_REPO,\n type RegistryToken,\n resolveToken,\n} from \"./constants\";\nimport { parseEnv } from \"./env-reconcile\";\nimport {\n extractRequirements,\n type ResourceRequirementRow,\n renderRequirements,\n} from \"./requirements\";\n\n/** Subdirectories that commonly hold the frontend / server in an AppKit app. */\nconst FRONTEND_SUBDIRS = [\"client\", \"frontend\", \"web\", \"app\"];\nconst SERVER_SUBDIRS = [\"server\", \"api\", \"backend\"];\n\nfunction isDir(p: string): boolean {\n return fs.existsSync(p) && fs.statSync(p).isDirectory();\n}\n\n/** A registry item is a server plugin if it ships a manifest.json. */\nfunction isPluginItem(item: RegistryItem): boolean {\n return (item.files ?? []).some(\n (f) => path.basename(f.target ?? f.path) === \"manifest.json\",\n );\n}\n\n/**\n * Locates the frontend root for UI components. AppKit apps put the client in a\n * client/ subdir (with its own components.json + src/); the CLI is typically\n * run from the repo root. Prefer the dir with components.json, then a src/.\n */\nfunction findFrontendRoot(cwd: string): string {\n if (fs.existsSync(path.join(cwd, \"components.json\"))) return cwd;\n for (const sub of FRONTEND_SUBDIRS) {\n if (fs.existsSync(path.join(cwd, sub, \"components.json\"))) {\n return path.join(cwd, sub);\n }\n }\n if (isDir(path.join(cwd, \"src\"))) return cwd;\n for (const sub of FRONTEND_SUBDIRS) {\n if (isDir(path.join(cwd, sub, \"src\"))) return path.join(cwd, sub);\n }\n return cwd;\n}\n\n/** Locates the server root for plugins (the server/ subdir, else cwd). */\nfunction findServerRoot(cwd: string): string {\n for (const sub of SERVER_SUBDIRS) {\n if (isDir(path.join(cwd, sub))) return path.join(cwd, sub);\n }\n return cwd;\n}\n\n/** Nearest dir with a package.json, walking up from start (for dep install). */\nfunction findNearestPackageJson(start: string): string {\n let dir = start;\n for (;;) {\n if (fs.existsSync(path.join(dir, \"package.json\"))) return dir;\n const parent = path.dirname(dir);\n if (parent === dir) return start;\n dir = parent;\n }\n}\n\n/**\n * The Databricks profile the app is configured with, read from\n * `DATABRICKS_CONFIG_PROFILE` in `cwd/.env` via the same dotenv parser the app\n * loads its env with. The CLI's top-level `dotenv/config` only loads the launch\n * dir's `.env` into `process.env`; when `--cwd` points at a different app dir,\n * that file isn't loaded, so the workspace picker and bundle validate would\n * miss the app's profile — this reads it directly. Undefined if the file or key\n * is absent, so the SDK's own default resolution still applies.\n */\nexport function profileFromEnv(cwd: string): string | undefined {\n try {\n const content = fs.readFileSync(path.join(cwd, \".env\"), \"utf-8\");\n return parseEnv(content).DATABRICKS_CONFIG_PROFILE || undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Resolves a registry item's `target` under `base`, enforcing that the result\n * stays inside `base`. Registry items are untrusted remote data; a `target`\n * like `../../../.zshrc` or an absolute path could otherwise write files\n * anywhere on disk (arbitrary-write → RCE). Throws on any escape.\n */\nexport function resolveWithinBase(base: string, target: string): string {\n if (path.isAbsolute(target)) {\n throw new Error(`Refusing absolute file target from registry: ${target}`);\n }\n const baseResolved = path.resolve(base);\n const resolved = path.resolve(baseResolved, target);\n if (\n resolved !== baseResolved &&\n !resolved.startsWith(baseResolved + path.sep)\n ) {\n throw new Error(\n `Refusing file target that escapes the destination directory: ${target}`,\n );\n }\n return resolved;\n}\n\n/** UI file destination (relative to the frontend root): placed in src/ if present. */\nfunction uiTargetPath(base: string, file: RegistryItemFile): string {\n let target = file.target ?? path.join(\"components\", path.basename(file.path));\n if (!target.startsWith(\"src/\") && isDir(path.join(base, \"src\"))) {\n target = path.join(\"src\", target);\n }\n return target;\n}\n\n/** Best-effort: the `toPlugin` export name from the item's index.ts. Returns\n * null (caller falls back to printed instructions) unless the name is a plain\n * JS identifier — the item is untrusted remote content and the value is\n * interpolated into the user's server.ts. */\nexport function pluginExportName(item: RegistryItem): string | null {\n const index = (item.files ?? []).find(\n (f) => path.basename(f.target ?? f.path) === \"index.ts\",\n );\n const match = index?.content.match(/export\\s*\\{([^}]*)\\}/);\n if (!match) return null;\n const names = match[1].split(\",\").map((s) => s.trim());\n // Prefer the camelCase toPlugin instance over the PascalCase class.\n const chosen = names.find((n) => /^[a-z]/.test(n)) ?? names[0];\n return chosen && JS_IDENTIFIER.test(chosen) ? chosen : null;\n}\n\nfunction detectPackageManager(cwd: string): \"pnpm\" | \"yarn\" | \"bun\" | \"npm\" {\n if (fs.existsSync(path.join(cwd, \"pnpm-lock.yaml\"))) return \"pnpm\";\n if (fs.existsSync(path.join(cwd, \"yarn.lock\"))) return \"yarn\";\n if (fs.existsSync(path.join(cwd, \"bun.lockb\"))) return \"bun\";\n return \"npm\";\n}\n\n/**\n * A safe npm dependency spec: `[@scope/]name` with an optional `@version`\n * range. Registry `dependencies` are untrusted remote data passed to the\n * package manager, so we reject anything that isn't a plain name+range —\n * blocks tarball/git URL specs (install-script RCE) and `-`-prefixed entries\n * that the PM would parse as flags (argument injection).\n */\nconst SAFE_DEP_SPEC =\n /^(@[a-z0-9][\\w.-]*\\/)?[a-z0-9][\\w.-]*(@[\\w.\\-+~^><=|* ]+)?$/i;\n\n/** Splits deps into safe (installable) and rejected (surfaced to the user). */\nexport function partitionDeps(deps: string[]): {\n safe: string[];\n rejected: string[];\n} {\n const safe: string[] = [];\n const rejected: string[] = [];\n for (const dep of deps) {\n if (dep.startsWith(\"-\") || !SAFE_DEP_SPEC.test(dep)) rejected.push(dep);\n else safe.push(dep);\n }\n return { safe, rejected };\n}\n\nfunction installDependencies(deps: string[], cwd: string): void {\n if (deps.length === 0) return;\n\n const { safe, rejected } = partitionDeps(deps);\n if (rejected.length > 0) {\n console.warn(\n pc.yellow(\n `Skipping suspicious dependenc${rejected.length === 1 ? \"y\" : \"ies\"} from the registry (not a plain name@version): ${rejected.join(\", \")}. Install manually if you trust them.`,\n ),\n );\n }\n if (safe.length === 0) return;\n\n if (!fs.existsSync(path.join(cwd, \"package.json\"))) {\n console.warn(\n pc.yellow(\n `No package.json found — install these manually: ${safe.join(\" \")}`,\n ),\n );\n return;\n }\n const pm = detectPackageManager(cwd);\n const subcommand = pm === \"npm\" ? \"install\" : \"add\";\n console.log(`\\nInstalling dependencies with ${pm}: ${safe.join(\" \")}`);\n // `--` stops the PM from parsing any dep as a flag (defense in depth).\n const result = spawnSync(pm, [subcommand, \"--\", ...safe], {\n stdio: \"inherit\",\n cwd,\n });\n if (result.status !== 0) {\n console.warn(\n pc.yellow(\n `Dependency install exited with code ${result.status ?? \"unknown\"} — install manually if needed: ${safe.join(\" \")}`,\n ),\n );\n }\n}\n\n/** Runs `appkit plugin sync --write` via this same CLI binary. */\nfunction runPluginSync(cwd: string): void {\n const result = spawnSync(\n process.execPath,\n [process.argv[1], \"plugin\", \"sync\", \"--write\"],\n { stdio: \"inherit\", cwd },\n );\n if (result.status !== 0) {\n console.warn(\n pc.yellow(\n \" Plugin sync did not complete cleanly — run `appkit plugin sync --write` manually.\",\n ),\n );\n }\n}\n\nfunction writeItemFile(\n base: string,\n target: string,\n content: string,\n force: boolean,\n cwd: string,\n): void {\n const dest = resolveWithinBase(base, target);\n const existed = fs.existsSync(dest);\n if (existed && !force) {\n console.error(\n pc.red(\n `Refusing to overwrite ${path.relative(cwd, dest)} — pass --force to replace it.`,\n ),\n );\n process.exit(1);\n }\n fs.mkdirSync(path.dirname(dest), { recursive: true });\n fs.writeFileSync(dest, content);\n const label = existed ? pc.yellow(\"Updated\") : pc.green(\"Created\");\n console.log(`${label} ${path.relative(cwd, dest)}`);\n}\n\ninterface PluginSummary {\n importPath: string;\n exportName: string | null;\n}\n\n/**\n * Fetches the requested items plus their transitive registryDependencies.\n * Dependencies are resolved breadth-first and de-duplicated by name, so a\n * plugin that depends on another registry item pulls the whole graph in one\n * `add`. Explicitly-requested items keep their request order and come first.\n */\nexport async function resolveItems(\n names: string[],\n token: RegistryToken | null,\n fetchItem: (\n name: string,\n token: RegistryToken | null,\n ) => Promise<RegistryItem> = fetchRegistryItem,\n): Promise<RegistryItem[]> {\n const seen = new Set<string>();\n const ordered: RegistryItem[] = [];\n // A name is both the fetch path and the on-disk `plugins/<name>` dir, so\n // reject non-slug refs (`/`, `..`, control chars) before they reach either.\n const enqueue = (ref: string): string => {\n const name = stripNamespace(ref);\n if (!isValidItemName(name)) {\n throw new Error(`Invalid registry item name: ${JSON.stringify(ref)}`);\n }\n return name;\n };\n // Breadth-first over the dependency graph, one level per iteration. Items in\n // a level are fetched concurrently (fetch latency is additive otherwise), but\n // levels stay ordered and dedup/cycle handling is unchanged: a name is marked\n // seen before its level is fetched, so it's never fetched or queued twice.\n let level = names.map(enqueue).filter((name) => {\n if (seen.has(name)) return false;\n seen.add(name);\n return true;\n });\n\n while (level.length > 0) {\n const items = await Promise.all(\n level.map(async (key) => {\n const item = await fetchItem(key, token);\n // Pin to the fetch key: the body's self-reported `name` is untrusted\n // and could claim a verified name to slip past the gate. The key is the\n // trustworthy identity the index keys `verified` on.\n item.name = key;\n return item;\n }),\n );\n ordered.push(...items);\n const next: string[] = [];\n for (const item of items) {\n for (const dep of item.registryDependencies ?? []) {\n const depName = enqueue(dep);\n if (seen.has(depName)) continue;\n seen.add(depName);\n next.push(depName);\n }\n }\n level = next;\n }\n\n return ordered;\n}\n\ninterface AddOptions {\n force?: boolean;\n cwd?: string;\n register?: boolean;\n /** false = don't reconcile resource env vars into .env. */\n resources?: boolean;\n /** true = never prompt; use --env flags or leave unset (agent/CI). */\n yes?: boolean;\n /** Pre-supplied env values from repeated --env KEY=VALUE flags. */\n env?: Record<string, string>;\n /** Databricks profile passed to `bundle validate` after writing config. */\n profile?: string;\n /** true = install items the registry index doesn't mark verified. */\n allowUnverified?: boolean;\n}\n\n/**\n * Splits requested names into verified and unverified against the index's\n * verified set. When `verified` is null the index couldn't be read — we can't\n * prove anything is verified, so every name is treated as unverified (the gate\n * then decides whether to warn-and-continue or block). Names are compared with\n * the namespace stripped, matching how items are fetched.\n */\nexport function partitionVerified(\n refs: string[],\n verified: Set<string> | null,\n): { verified: string[]; unverified: string[] } {\n const ok: string[] = [];\n const bad: string[] = [];\n for (const ref of refs) {\n const name = stripNamespace(ref);\n if (verified?.has(name)) ok.push(name);\n else bad.push(name);\n }\n return { verified: ok, unverified: bad };\n}\n\nasync function runAdd(refs: string[], opts: AddOptions): Promise<void> {\n const cwd = opts.cwd ? path.resolve(opts.cwd) : process.cwd();\n const token = resolveToken();\n if (token) {\n console.log(\n `Using ${token.envName} to fetch from ${REGISTRY_REPO} (private).`,\n );\n }\n\n // Resolve the full graph and fetch the verified index concurrently (two\n // independent round-trips). Item resolution is read-only — nothing is written\n // or installed until after the gate below.\n const verifiedP = opts.allowUnverified\n ? Promise.resolve(null)\n : fetchVerifiedNames(token);\n const items = await resolveItems(refs, token);\n\n // Integrity gate over the *entire resolved set* (not just requested names, so\n // an unverified transitive dep can't ride in on a verified item). Fails closed:\n // a null verified set (unreadable index) makes every item count as unverified.\n if (!opts.allowUnverified) {\n const verified = await verifiedP;\n const { unverified } = partitionVerified(\n items.map((i) => i.name),\n verified,\n );\n if (unverified.length > 0) {\n const reason =\n verified === null\n ? \"could not read the registry index to verify these items\"\n : `not marked verified in ${REGISTRY_REPO}`;\n console.error(\n pc.red(\n `Refusing to add unverified item(s) (${reason}): ${unverified.join(\", \")}.`,\n ),\n );\n console.error(\n pc.dim(\n \" Re-run with --allow-unverified if you trust the source; unverified items run code in your app.\",\n ),\n );\n process.exit(1);\n }\n }\n\n const hasUi = items.some((i) => !isPluginItem(i));\n const hasPlugin = items.some(isPluginItem);\n const frontendRoot = hasUi ? findFrontendRoot(cwd) : cwd;\n const serverRoot = hasPlugin ? findServerRoot(cwd) : cwd;\n if (hasUi && frontendRoot !== cwd) {\n console.log(pc.dim(`UI components → ${path.relative(cwd, frontendRoot)}/`));\n }\n if (hasPlugin && serverRoot !== cwd) {\n console.log(pc.dim(`Plugins → ${path.relative(cwd, serverRoot)}/`));\n }\n\n const deps = new Set<string>();\n let wroteUi = false;\n const pluginSummaries: PluginSummary[] = [];\n const allRequirements: ResourceRequirementRow[] = [];\n\n for (const item of items) {\n for (const dep of item.dependencies ?? []) deps.add(dep);\n\n if (isPluginItem(item)) {\n let pluginRel = path.join(\"plugins\", item.name);\n for (const file of item.files ?? []) {\n const target =\n file.target ??\n path.join(\"plugins\", item.name, path.basename(file.path));\n writeItemFile(\n serverRoot,\n target,\n file.content,\n Boolean(opts.force),\n cwd,\n );\n if (path.basename(target) === \"manifest.json\") {\n pluginRel = path.dirname(target);\n }\n }\n const requirements = extractRequirements(item);\n if (requirements.length > 0) {\n console.log(`\\n${renderRequirements(item, requirements)}`);\n allRequirements.push(...requirements);\n }\n pluginSummaries.push({\n importPath: `./${pluginRel}`,\n exportName: pluginExportName(item),\n });\n } else {\n for (const file of item.files ?? []) {\n writeItemFile(\n frontendRoot,\n uiTargetPath(frontendRoot, file),\n file.content,\n Boolean(opts.force),\n cwd,\n );\n wroteUi = true;\n }\n }\n }\n\n installDependencies([...deps], findNearestPackageJson(cwd));\n\n if (hasPlugin) {\n console.log(pc.dim(\"\\nRegistering plugins (appkit plugin sync)...\"));\n runPluginSync(cwd);\n }\n\n if (wroteUi) {\n console.log(\n pc.dim(\n '\\nReminder: import \"@databricks/appkit-ui/styles.css\" once at your app root so components are themed.',\n ),\n );\n }\n // Lazy import: server-register pulls in @ast-grep/napi (a native addon), and\n // this CLI is imported eagerly by index.ts, so a static import would make\n // every unrelated command pay that cost.\n const registerPluginInServer =\n opts.register !== false && pluginSummaries.some((s) => s.exportName)\n ? (await import(\"./server-register.js\")).registerPluginInServer\n : null;\n for (const s of pluginSummaries) {\n // Try to wire the plugin into the server's createApp call automatically;\n // fall back to printing the snippet when the shape isn't the standard one.\n let wired = false;\n // Why auto-registration was skipped, so the fallback can say so (an empty\n // reason means the user opted out via --no-register — no nag then).\n let skipReason: string | undefined;\n if (opts.register === false) {\n // opted out — print the snippet without a \"couldn't\" message\n } else if (!s.exportName) {\n skipReason =\n \"couldn't read a plugin export name from the item's index.ts\";\n } else if (registerPluginInServer) {\n const result = registerPluginInServer(\n serverRoot,\n s.importPath,\n s.exportName,\n );\n // result.file is relative to serverRoot; show it from cwd for the user.\n const shown =\n result.file && path.relative(cwd, path.join(serverRoot, result.file));\n if (result.status === \"wired\") {\n console.log(`\\n${pc.green(\"Registered\")} ${s.exportName} in ${shown}`);\n wired = true;\n } else if (result.status === \"already\") {\n console.log(\n pc.dim(`\\n${s.exportName} is already registered in ${shown}`),\n );\n wired = true;\n } else {\n skipReason = result.reason;\n }\n }\n if (!wired) {\n const imp = s.exportName ?? \"<plugin>\";\n if (skipReason) {\n console.log(\n pc.yellow(`\\nCouldn't auto-register ${imp} — ${skipReason}.`),\n );\n }\n console.log(\n `${pc.bold(\"Add this to your server's createApp call:\")}\\n` +\n pc.dim(\n ` import { ${imp} } from \"${s.importPath}\";\\n` +\n ` const app = await createApp({ plugins: [${imp}(), /* ... */] });`,\n ),\n );\n }\n }\n\n if (opts.resources !== false && allRequirements.length > 0) {\n // Lazy import (same reason as server-register above): env-writer pulls in\n // the workspace picker and, through it, the Databricks SDK.\n const { collectBindingValues, reportEnvResolutions, syncEnv } =\n await import(\"./env-writer.js\");\n // Without --profile, fall back to the app's configured profile so the\n // picker and bundle validate reach the workspace the app runs against.\n const profile = opts.profile ?? profileFromEnv(cwd);\n console.log(pc.dim(\"\\nReconciling resource env vars into .env...\"));\n const resolutions = await syncEnv(allRequirements, {\n cwd,\n nonInteractive: Boolean(opts.yes),\n values: opts.env,\n profile,\n });\n reportEnvResolutions(resolutions);\n\n // Deploy config (app.yaml + databricks.yml). Values come from what the\n // user supplied for env fields (flags or prompts); other fields fall back\n // to their manifest defaults inside buildConfigPlan.\n const values: Record<string, string> = { ...(opts.env ?? {}) };\n for (const r of resolutions) {\n if (r.value !== undefined) values[r.env] = r.value;\n }\n // Binding fields with no env name (e.g. postgres project/branch/database)\n // never flow through .env, so collect them separately — else their\n // databricks.yml bundle variables stay unassigned and bundle validate fails.\n const bindingNeeds = collectBindingValueNeeds(allRequirements);\n if (bindingNeeds.length > 0) {\n const bindingValues = await collectBindingValues(bindingNeeds, {\n cwd,\n nonInteractive: Boolean(opts.yes),\n values: opts.env,\n profile,\n });\n Object.assign(values, bindingValues);\n }\n const plan = buildConfigPlan(allRequirements, values);\n if (planHasContent(plan)) {\n const result = writeConfig(cwd, plan);\n reportConfigWrite(result);\n if (result.databricksYmlChanged) validateBundle(cwd, profile);\n }\n warnScopeNeeding(allRequirements);\n }\n}\n\n/**\n * v1 does not write `user_api_scopes` (deferred to the manifest scope\n * extension). Warn when an added plugin's resource type is known to need one,\n * so the user adds it before deploy.\n */\n/** Resource types known to require a user_api_scope, and the scope each needs. */\nexport const SCOPE_BY_RESOURCE_TYPE: Record<string, string> = {\n genie_space: \"dashboards.genie\",\n serving_endpoint: \"serving.serving-endpoints\",\n // volumes/files-backed access uses files.files\n volume: \"files.files\",\n};\n\n/** Returns the user_api_scopes implied by a set of resource rows (deduped). */\nexport function scopesForResources(\n rows: ResourceRequirementRow[],\n): Map<string, string> {\n const needed = new Map<string, string>();\n for (const row of rows) {\n const scope = SCOPE_BY_RESOURCE_TYPE[row.type];\n if (scope) needed.set(row.type, scope);\n }\n return needed;\n}\n\nfunction warnScopeNeeding(rows: ResourceRequirementRow[]): void {\n const needed = scopesForResources(rows);\n if (needed.size === 0) return;\n const list = [...needed.entries()]\n .map(([type, scope]) => `${type} → ${scope}`)\n .join(\", \");\n console.warn(\n pc.yellow(\n `\\n Note: these resources may need a user_api_scope before deploy: ${list}.\\n` +\n \" Add it under resources.apps.app.user_api_scopes in databricks.yml.\",\n ),\n );\n}\n\n/** Commander reducer for repeatable `--env KEY=VALUE` flags. */\nfunction collectEnvFlag(\n raw: string,\n acc: Record<string, string>,\n): Record<string, string> {\n const eq = raw.indexOf(\"=\");\n if (eq === -1) {\n console.error(`Ignoring --env \"${raw}\" (expected KEY=VALUE).`);\n return acc;\n }\n const key = raw.slice(0, eq).trim();\n const value = raw.slice(eq + 1);\n if (key) acc[key] = value;\n return acc;\n}\n\nexport const addCommand = new Command(\"add\")\n .description(\"Add a UI component or server plugin from the AppKit registry\")\n .argument(\"<item...>\", \"Registry item name(s), e.g. metric-card or hello\")\n .option(\"-f, --force\", \"Overwrite existing files\")\n .option(\"-C, --cwd <dir>\", \"Run as if started in <dir>\")\n .option(\"--no-register\", \"Don't edit the server entry to register plugins\")\n .option(\"--no-resources\", \"Don't reconcile resource env vars into .env\")\n .option(\"-y, --yes\", \"Don't prompt; use --env values or leave vars unset\")\n .option(\n \"--env <KEY=VALUE>\",\n \"Pre-set a resource env var (repeatable)\",\n collectEnvFlag,\n {},\n )\n .option(\n \"-p, --profile <name>\",\n \"Databricks profile for the resource picker and bundle validate (defaults to the app's DATABRICKS_CONFIG_PROFILE)\",\n )\n .option(\n \"--allow-unverified\",\n \"Add items the registry doesn't mark verified (runs untrusted code)\",\n )\n .addHelpText(\n \"after\",\n `\nNo components.json is required. Item type is detected automatically:\n • UI components → <frontend>/src/components/appkit/ (client/ detected)\n • Server plugins → <server>/plugins/<name>/, runs plugin sync, and registers\n them in your createApp call (use --no-register to skip the server edit)\n\nServer plugins declare Databricks resources. On add, their env vars are\nreconciled into .env (and names into .env.example), and the deploy config\n(app.yaml + databricks.yml resource bindings) is patched to match — existing\nentries are never clobbered. Interactive by default; pass --yes for agents/CI\n(uses --env values, leaves the rest unset) and --env KEY=VALUE to supply\nvalues non-interactively. Pass --profile to validate the bundle after writing.\n\nThe frontend/server roots are detected from common layouts, so you can run\nthis from the repo root. While the registry repo is private, a read token is\nresolved from \\`gh auth token\\` or APPKIT_REGISTRY_TOKEN / GITHUB_TOKEN / GH_TOKEN.\n\nExamples:\n $ appkit add metric-card # UI component\n $ appkit add hello # server plugin\n $ appkit add metric-card hello # mix in one call\n $ appkit add analytics --yes --env DATABRICKS_WAREHOUSE_ID=abc123`,\n )\n .action((items: string[], opts: AddOptions) =>\n runAdd(items, opts).catch((err) => {\n console.error(err);\n process.exit(1);\n }),\n );\n"],"mappings":"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"}
|