@databricks/appkit-ui 0.83.0 → 0.84.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 +15 -1
- package/dist/cli/commands/agent/eval.js +1 -1
- package/dist/cli/commands/generate-types.js +1 -1
- package/dist/cli/commands/plugin/sync/sync.js +28 -15
- package/dist/cli/commands/plugin/sync/sync.js.map +1 -1
- package/dist/cli/commands/registry/add.js +3 -11
- package/dist/cli/commands/registry/add.js.map +1 -1
- package/dist/cli/commands/registry/config-writer.js +1 -1
- package/dist/react/ui/button.d.ts +1 -1
- package/dist/schemas/manifest.d.ts +35 -1
- package/dist/schemas/manifest.d.ts.map +1 -1
- package/dist/schemas/manifest.js +112 -4
- package/dist/schemas/manifest.js.map +1 -1
- package/dist/shared/src/plugin.d.ts.map +1 -1
- package/docs/api/appkit/Class.AppKitError.md +2 -1
- package/docs/api/appkit/Class.AuthenticationError.md +1 -1
- package/docs/api/appkit/Class.ConfigurationError.md +1 -1
- package/docs/api/appkit/Class.ConnectionError.md +1 -1
- package/docs/api/appkit/Class.DatabaseValidationError.md +1 -1
- package/docs/api/appkit/Class.ExecutionError.md +1 -1
- package/docs/api/appkit/Class.IdentityExpiredError.md +190 -0
- package/docs/api/appkit/Class.InitializationError.md +1 -1
- package/docs/api/appkit/Class.Plugin.md +8 -12
- package/docs/api/appkit/Class.ServerError.md +1 -1
- package/docs/api/appkit/Class.ServiceContext.md +169 -0
- package/docs/api/appkit/Class.TunnelError.md +1 -1
- package/docs/api/appkit/Class.ValidationError.md +1 -1
- package/docs/api/appkit/Function.createApp.md +12 -12
- package/docs/api/appkit/Function.getCallerContext.md +12 -0
- package/docs/api/appkit/Function.getCurrentUserId.md +14 -0
- package/docs/api/appkit/Function.getExecutionContext.md +1 -1
- package/docs/api/appkit/Function.getUserContext.md +16 -0
- package/docs/api/appkit/Function.isInUserContext.md +12 -0
- package/docs/api/appkit/Function.isUserContext.md +20 -0
- package/docs/api/appkit/Function.runAgent.md +1 -1
- package/docs/api/appkit/Function.runInCallerContext.md +27 -0
- package/docs/api/appkit/Function.runInUserContext.md +29 -0
- package/docs/api/appkit/Interface.AgentDefinition.md +11 -0
- package/docs/api/appkit/Interface.AgentsPluginConfig.md +11 -0
- package/docs/api/appkit/Interface.CallerContext.md +13 -0
- package/docs/api/appkit/Interface.IndexConfig.md +1 -1
- package/docs/api/appkit/Interface.PluginManifest.md +9 -1
- package/docs/api/appkit/Interface.RegisteredAgent.md +11 -0
- package/docs/api/appkit/Interface.RunAgentInput.md +45 -1
- package/docs/api/appkit/TypeAlias.AgentAuth.md +8 -0
- package/docs/api/appkit/TypeAlias.AppKitApi.md +35 -0
- package/docs/api/appkit/TypeAlias.ExecutionContext.md +4 -1
- package/docs/api/appkit/TypeAlias.ExecutionResult.md +65 -0
- package/docs/api/appkit/TypeAlias.ScopedPluginMap.md +12 -0
- package/docs/api/appkit/TypeAlias.UserContext.md +109 -0
- package/docs/api/appkit/TypeAlias.UserScopedApp.md +39 -0
- package/docs/api/appkit.md +14 -0
- package/docs/plugins/agents.md +45 -0
- package/docs/plugins/execution-context.md +101 -50
- package/docs/plugins/lakebase.md +6 -82
- package/llms.txt +15 -1
- package/package.json +1 -1
- package/sbom.cdx.json +1 -1
|
@@ -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\";\n\nimport { Command } from \"commander\";\nimport pc from \"picocolors\";\n\nimport { detectPackageManager } from \"../../package-manager\";\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\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\n/** Splits an npm spec into `name` and optional version `range`, handling the\n * leading `@` of scoped names (`@databricks/appkit-ui@^0.41.0` → name\n * `@databricks/appkit-ui`, range `^0.41.0`; `lodash` → no range). */\nexport function parseDepSpec(spec: string): { name: string; range?: string } {\n const at = spec.lastIndexOf(\"@\");\n // at <= 0 means either no `@` (bare name) or the scope's `@` at index 0.\n if (at <= 0) return { name: spec };\n return { name: spec.slice(0, at), range: spec.slice(at + 1) };\n}\n\n/**\n * Registry `dependencies` are floors (\"needs at least this version\"), not pins.\n * Against the versions already in the target `package.json`, returns which specs\n * to install: missing packages (as declared) and genuine upgrades (installed\n * floor below the requested floor). A package already at a same-or-newer version\n * is skipped so the package manager never downgrades it; ranges that can't be\n * compared (workspace:*, catalog:, git) are skipped for the same reason.\n *\n * `semver` is injected so the caller can lazy-load it (see `installDependencies`).\n */\nexport function reconcileDeps(\n specs: string[],\n installed: Record<string, string>,\n { gte, minVersion }: Pick<typeof import(\"semver\"), \"gte\" | \"minVersion\">,\n): { install: string[]; skipped: string[] } {\n // minVersion() throws on ranges it can't parse (workspace:*, catalog:, git);\n // treat those as uncomparable and skip rather than risk a downgrade.\n const floor = (range: string): string | null => {\n try {\n return minVersion(range)?.version ?? null;\n } catch {\n return null;\n }\n };\n const install: string[] = [];\n const skipped: string[] = [];\n for (const spec of specs) {\n const { name, range } = parseDepSpec(spec);\n const have = installed[name];\n if (!have) {\n install.push(spec); // missing → install as declared\n continue;\n }\n if (!range) {\n skipped.push(spec); // present, no version asked → keep what's there\n continue;\n }\n const reqFloor = floor(range);\n const haveFloor = floor(have);\n if (!reqFloor || !haveFloor) {\n skipped.push(spec); // can't compare safely → don't touch\n } else if (gte(haveFloor, reqFloor)) {\n skipped.push(spec); // installed is same-or-newer → keep (no downgrade)\n } else {\n install.push(spec); // installed is older than required → upgrade\n }\n }\n return { install, skipped };\n}\n\n/** Merged dependencies + devDependencies (name → declared range) of a\n * package.json, tolerating a missing/unreadable file (returns {}). */\nfunction readInstalledRanges(pkgPath: string): Record<string, string> {\n try {\n const pkg = JSON.parse(fs.readFileSync(pkgPath, \"utf-8\")) as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n };\n return { ...pkg.dependencies, ...pkg.devDependencies };\n } catch {\n return {};\n }\n}\n\nasync function installDependencies(deps: string[], cwd: string): Promise<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 const pkgPath = path.join(cwd, \"package.json\");\n if (!fs.existsSync(pkgPath)) {\n console.warn(\n pc.yellow(\n `No package.json found — install these manually: ${safe.join(\" \")}`,\n ),\n );\n return;\n }\n\n // Lazy import: this file is loaded eagerly by the CLI entry, so keep semver\n // (only needed here) off the startup path — as with server-register/env-writer.\n const semver = await import(\"semver\");\n const { install, skipped } = reconcileDeps(\n safe,\n readInstalledRanges(pkgPath),\n semver,\n );\n if (skipped.length > 0) {\n console.log(\n pc.dim(\n `Keeping already-installed (registry asked for an older/equal range): ${skipped.join(\", \")}`,\n ),\n );\n }\n if (install.length === 0) return;\n\n const pm = detectPackageManager(cwd, \"npm\");\n const subcommand = pm === \"npm\" ? \"install\" : \"add\";\n console.log(`\\nInstalling dependencies with ${pm}: ${install.join(\" \")}`);\n // `--` stops the PM from parsing any dep as a flag (defense in depth).\n const result = spawnSync(pm, [subcommand, \"--\", ...install], {\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: ${install.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 await 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 { SCOPE_BY_TYPE } from \"../../../schemas/manifest\";\nimport { detectPackageManager } from \"../../package-manager\";\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\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\n/** Splits an npm spec into `name` and optional version `range`, handling the\n * leading `@` of scoped names (`@databricks/appkit-ui@^0.41.0` → name\n * `@databricks/appkit-ui`, range `^0.41.0`; `lodash` → no range). */\nexport function parseDepSpec(spec: string): { name: string; range?: string } {\n const at = spec.lastIndexOf(\"@\");\n // at <= 0 means either no `@` (bare name) or the scope's `@` at index 0.\n if (at <= 0) return { name: spec };\n return { name: spec.slice(0, at), range: spec.slice(at + 1) };\n}\n\n/**\n * Registry `dependencies` are floors (\"needs at least this version\"), not pins.\n * Against the versions already in the target `package.json`, returns which specs\n * to install: missing packages (as declared) and genuine upgrades (installed\n * floor below the requested floor). A package already at a same-or-newer version\n * is skipped so the package manager never downgrades it; ranges that can't be\n * compared (workspace:*, catalog:, git) are skipped for the same reason.\n *\n * `semver` is injected so the caller can lazy-load it (see `installDependencies`).\n */\nexport function reconcileDeps(\n specs: string[],\n installed: Record<string, string>,\n { gte, minVersion }: Pick<typeof import(\"semver\"), \"gte\" | \"minVersion\">,\n): { install: string[]; skipped: string[] } {\n // minVersion() throws on ranges it can't parse (workspace:*, catalog:, git);\n // treat those as uncomparable and skip rather than risk a downgrade.\n const floor = (range: string): string | null => {\n try {\n return minVersion(range)?.version ?? null;\n } catch {\n return null;\n }\n };\n const install: string[] = [];\n const skipped: string[] = [];\n for (const spec of specs) {\n const { name, range } = parseDepSpec(spec);\n const have = installed[name];\n if (!have) {\n install.push(spec); // missing → install as declared\n continue;\n }\n if (!range) {\n skipped.push(spec); // present, no version asked → keep what's there\n continue;\n }\n const reqFloor = floor(range);\n const haveFloor = floor(have);\n if (!reqFloor || !haveFloor) {\n skipped.push(spec); // can't compare safely → don't touch\n } else if (gte(haveFloor, reqFloor)) {\n skipped.push(spec); // installed is same-or-newer → keep (no downgrade)\n } else {\n install.push(spec); // installed is older than required → upgrade\n }\n }\n return { install, skipped };\n}\n\n/** Merged dependencies + devDependencies (name → declared range) of a\n * package.json, tolerating a missing/unreadable file (returns {}). */\nfunction readInstalledRanges(pkgPath: string): Record<string, string> {\n try {\n const pkg = JSON.parse(fs.readFileSync(pkgPath, \"utf-8\")) as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n };\n return { ...pkg.dependencies, ...pkg.devDependencies };\n } catch {\n return {};\n }\n}\n\nasync function installDependencies(deps: string[], cwd: string): Promise<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 const pkgPath = path.join(cwd, \"package.json\");\n if (!fs.existsSync(pkgPath)) {\n console.warn(\n pc.yellow(\n `No package.json found — install these manually: ${safe.join(\" \")}`,\n ),\n );\n return;\n }\n\n // Lazy import: this file is loaded eagerly by the CLI entry, so keep semver\n // (only needed here) off the startup path — as with server-register/env-writer.\n const semver = await import(\"semver\");\n const { install, skipped } = reconcileDeps(\n safe,\n readInstalledRanges(pkgPath),\n semver,\n );\n if (skipped.length > 0) {\n console.log(\n pc.dim(\n `Keeping already-installed (registry asked for an older/equal range): ${skipped.join(\", \")}`,\n ),\n );\n }\n if (install.length === 0) return;\n\n const pm = detectPackageManager(cwd, \"npm\");\n const subcommand = pm === \"npm\" ? \"install\" : \"add\";\n console.log(`\\nInstalling dependencies with ${pm}: ${install.join(\" \")}`);\n // `--` stops the PM from parsing any dep as a flag (defense in depth).\n const result = spawnSync(pm, [subcommand, \"--\", ...install], {\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: ${install.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 await 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/** 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 if (Object.hasOwn(SCOPE_BY_TYPE, row.type)) {\n needed.set(\n row.type,\n SCOPE_BY_TYPE[row.type as keyof typeof SCOPE_BY_TYPE],\n );\n }\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,8 +1,8 @@
|
|
|
1
1
|
import { APP_YAML_FILE, DATABRICKS_YML_FILE, bindingToNode } from "../../deploy-config.js";
|
|
2
2
|
import { planHasContent } from "./config-plan.js";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
3
4
|
import fs from "node:fs";
|
|
4
5
|
import path from "node:path";
|
|
5
|
-
import { spawnSync } from "node:child_process";
|
|
6
6
|
import pc from "picocolors";
|
|
7
7
|
import { parseDocument } from "yaml";
|
|
8
8
|
|
|
@@ -5,7 +5,7 @@ import * as class_variance_authority_types0 from "class-variance-authority/types
|
|
|
5
5
|
|
|
6
6
|
//#region src/react/ui/button.d.ts
|
|
7
7
|
declare const buttonVariants: (props?: ({
|
|
8
|
-
variant?: "default" | "
|
|
8
|
+
variant?: "default" | "destructive" | "secondary" | "outline" | "ghost" | "link" | null | undefined;
|
|
9
9
|
size?: "default" | "sm" | "lg" | "icon" | "icon-sm" | "icon-lg" | null | undefined;
|
|
10
10
|
} & class_variance_authority_types0.ClassProp) | undefined) => string;
|
|
11
11
|
/** Clickable button with multiple variants and sizes */
|
|
@@ -1,6 +1,31 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
|
|
3
3
|
//#region src/schemas/manifest.d.ts
|
|
4
|
+
declare const resourceTypeSchema: z.ZodEnum<{
|
|
5
|
+
secret: "secret";
|
|
6
|
+
job: "job";
|
|
7
|
+
sql_warehouse: "sql_warehouse";
|
|
8
|
+
serving_endpoint: "serving_endpoint";
|
|
9
|
+
volume: "volume";
|
|
10
|
+
vector_search_index: "vector_search_index";
|
|
11
|
+
uc_function: "uc_function";
|
|
12
|
+
uc_connection: "uc_connection";
|
|
13
|
+
database: "database";
|
|
14
|
+
postgres: "postgres";
|
|
15
|
+
genie_space: "genie_space";
|
|
16
|
+
experiment: "experiment";
|
|
17
|
+
app: "app";
|
|
18
|
+
}>;
|
|
19
|
+
/** Apps user_api_scopes for resource types with confirmed OBO support. */
|
|
20
|
+
declare const SCOPE_BY_TYPE: {
|
|
21
|
+
readonly sql_warehouse: "sql";
|
|
22
|
+
readonly serving_endpoint: "model-serving";
|
|
23
|
+
readonly genie_space: "genie";
|
|
24
|
+
readonly volume: "files";
|
|
25
|
+
readonly vector_search_index: "vector-search";
|
|
26
|
+
readonly uc_connection: "catalog.connections";
|
|
27
|
+
};
|
|
28
|
+
declare const APP_ONLY_RESOURCE_TYPES: ReadonlySet<ResourceType>;
|
|
4
29
|
declare const discoveryDescriptorSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
5
30
|
type: z.ZodLiteral<"kind">;
|
|
6
31
|
resourceKind: z.ZodEnum<{
|
|
@@ -61,6 +86,13 @@ declare const pluginScaffoldingRulesSchema: z.ZodObject<{
|
|
|
61
86
|
}, z.core.$strict>;
|
|
62
87
|
declare const pluginManifestSchema: z.ZodObject<{
|
|
63
88
|
scopes: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
89
|
+
postgres: "postgres";
|
|
90
|
+
sql: "sql";
|
|
91
|
+
"model-serving": "model-serving";
|
|
92
|
+
genie: "genie";
|
|
93
|
+
files: "files";
|
|
94
|
+
"vector-search": "vector-search";
|
|
95
|
+
"catalog.connections": "catalog.connections";
|
|
64
96
|
"ai-gateway": "ai-gateway";
|
|
65
97
|
"mcp.external": "mcp.external";
|
|
66
98
|
"mcp.functions": "mcp.functions";
|
|
@@ -68,6 +100,7 @@ declare const pluginManifestSchema: z.ZodObject<{
|
|
|
68
100
|
"catalog.catalogs:read": "catalog.catalogs:read";
|
|
69
101
|
"catalog.schemas:read": "catalog.schemas:read";
|
|
70
102
|
"catalog.tables:read": "catalog.tables:read";
|
|
103
|
+
"sql:restricted-query": "sql:restricted-query";
|
|
71
104
|
}>>>;
|
|
72
105
|
$schema: z.ZodOptional<z.ZodString>;
|
|
73
106
|
name: z.ZodString;
|
|
@@ -1141,10 +1174,11 @@ declare const pluginManifestSchema: z.ZodObject<{
|
|
|
1141
1174
|
}, z.core.$strict>>;
|
|
1142
1175
|
}, z.core.$strict>>;
|
|
1143
1176
|
}, z.core.$strict>;
|
|
1177
|
+
type ResourceType = z.infer<typeof resourceTypeSchema>;
|
|
1144
1178
|
type DiscoveryDescriptor = z.infer<typeof discoveryDescriptorSchema>;
|
|
1145
1179
|
type ResourceFieldEntry = z.infer<typeof resourceFieldEntrySchema>;
|
|
1146
1180
|
type PluginScaffoldingRules = z.infer<typeof pluginScaffoldingRulesSchema>;
|
|
1147
1181
|
type PluginManifest = z.infer<typeof pluginManifestSchema>;
|
|
1148
1182
|
//#endregion
|
|
1149
|
-
export { DiscoveryDescriptor, PluginManifest, PluginScaffoldingRules, ResourceFieldEntry, pluginManifestSchema };
|
|
1183
|
+
export { APP_ONLY_RESOURCE_TYPES, DiscoveryDescriptor, PluginManifest, PluginScaffoldingRules, ResourceFieldEntry, SCOPE_BY_TYPE, pluginManifestSchema };
|
|
1150
1184
|
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.d.ts","names":[],"sources":["../../src/schemas/manifest.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","names":[],"sources":["../../src/schemas/manifest.ts"],"mappings":";;;cAoCa,kBAAA,EAAkB,CAAA,CAAA,OAAA;;;;;;;;;;;;;;;;cAmBlB,aAAA;EAAA;;;;;;;cAaA,uBAAA,EAAyB,WAAA,CAAY,YAAA;AAAA,cAsUrC,yBAAA,EAAyB,CAAA,CAAA,qBAAA,EAAA,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;cA8FzB,wBAAA,EAAwB,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAkQxB,4BAAA,EAA4B,CAAA,CAAA,SAAA;;;;;cAoE5B,oBAAA,EAAoB,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAmiBrB,YAAA,GAAe,CAAA,CAAE,KAAA,QAAa,kBAAA;AAAA,KA6B9B,mBAAA,GAAsB,CAAA,CAAE,KAAA,QAAa,yBAAA;AAAA,KACrC,kBAAA,GAAqB,CAAA,CAAE,KAAA,QAAa,wBAAA;AAAA,KAIpC,sBAAA,GAAyB,CAAA,CAAE,KAAA,QAC9B,4BAAA;AAAA,KAEG,cAAA,GAAiB,CAAA,CAAE,KAAA,QAAa,oBAAA"}
|
package/dist/schemas/manifest.js
CHANGED
|
@@ -46,6 +46,89 @@ const resourceTypeSchema = z.enum([
|
|
|
46
46
|
"experiment",
|
|
47
47
|
"app"
|
|
48
48
|
]).describe("Type of Databricks resource");
|
|
49
|
+
/** Apps user_api_scopes for resource types with confirmed OBO support. */
|
|
50
|
+
const SCOPE_BY_TYPE = {
|
|
51
|
+
sql_warehouse: "sql",
|
|
52
|
+
serving_endpoint: "model-serving",
|
|
53
|
+
genie_space: "genie",
|
|
54
|
+
volume: "files",
|
|
55
|
+
vector_search_index: "vector-search",
|
|
56
|
+
uc_connection: "catalog.connections"
|
|
57
|
+
};
|
|
58
|
+
const APP_ONLY_RESOURCE_TYPES = new Set([
|
|
59
|
+
"secret",
|
|
60
|
+
"database",
|
|
61
|
+
"postgres"
|
|
62
|
+
]);
|
|
63
|
+
/**
|
|
64
|
+
* DABs binding spec per resource type. Faithful port of the CLI's
|
|
65
|
+
* `appResourceSpecs`. App-only types still bind (as the service principal). The
|
|
66
|
+
* `app` type is intentionally absent: bundles do not yet support it as an app
|
|
67
|
+
* resource, matching the commented-out CLI entry.
|
|
68
|
+
*
|
|
69
|
+
* TODO(sdk-migration): anchor these yamlKeys to the Apps SDK once the modular migration
|
|
70
|
+
* (analytics-migration-sdk / #562) adds @databricks/sdk-apps:
|
|
71
|
+
* 1. add @databricks/sdk-apps; re-export AppResource via packages/shared/src/workspace-client/modular.ts
|
|
72
|
+
* (direct @databricks/sdk-* imports are banned by the repo lint rule)
|
|
73
|
+
* 2. the new AppResource is a $case union: kinds = NonNullable<AppResource["resource"]>["$case"]
|
|
74
|
+
* (camelCase: sqlWarehouse | servingEndpoint | genieSpace | ucSecurable | ...)
|
|
75
|
+
* 3. map camelCase $case -> snake_case yamlKey and assert every table entry is covered
|
|
76
|
+
* (the new SDK models postgres/experiment/app, so the old skew exceptions are not needed)
|
|
77
|
+
*/
|
|
78
|
+
const DABS_BINDING_BY_TYPE = {
|
|
79
|
+
sql_warehouse: {
|
|
80
|
+
yamlKey: "sql_warehouse",
|
|
81
|
+
varFields: [["id", "id"]]
|
|
82
|
+
},
|
|
83
|
+
job: {
|
|
84
|
+
yamlKey: "job",
|
|
85
|
+
varFields: [["id", "id"]]
|
|
86
|
+
},
|
|
87
|
+
serving_endpoint: {
|
|
88
|
+
yamlKey: "serving_endpoint",
|
|
89
|
+
varFields: [["name", "name"]]
|
|
90
|
+
},
|
|
91
|
+
experiment: {
|
|
92
|
+
yamlKey: "experiment",
|
|
93
|
+
varFields: [["experimentId", "experiment_id"]]
|
|
94
|
+
},
|
|
95
|
+
secret: {
|
|
96
|
+
yamlKey: "secret",
|
|
97
|
+
varFields: [["scope", "scope"], ["key", "key"]]
|
|
98
|
+
},
|
|
99
|
+
database: {
|
|
100
|
+
yamlKey: "database",
|
|
101
|
+
varFields: [["instance_name", "instance_name"], ["database_name", "database_name"]]
|
|
102
|
+
},
|
|
103
|
+
postgres: {
|
|
104
|
+
yamlKey: "postgres",
|
|
105
|
+
varFields: [["branch", "branch"], ["database", "database"]]
|
|
106
|
+
},
|
|
107
|
+
genie_space: {
|
|
108
|
+
yamlKey: "genie_space",
|
|
109
|
+
varFields: [["name", "name"], ["id", "space_id"]]
|
|
110
|
+
},
|
|
111
|
+
volume: {
|
|
112
|
+
yamlKey: "uc_securable",
|
|
113
|
+
varFields: [["path", "securable_full_name"]],
|
|
114
|
+
staticFields: [["securable_type", "VOLUME"]]
|
|
115
|
+
},
|
|
116
|
+
uc_function: {
|
|
117
|
+
yamlKey: "uc_securable",
|
|
118
|
+
varFields: [["id", "securable_full_name"]],
|
|
119
|
+
staticFields: [["securable_type", "FUNCTION"]]
|
|
120
|
+
},
|
|
121
|
+
uc_connection: {
|
|
122
|
+
yamlKey: "uc_securable",
|
|
123
|
+
varFields: [["id", "securable_full_name"]],
|
|
124
|
+
staticFields: [["securable_type", "CONNECTION"]]
|
|
125
|
+
},
|
|
126
|
+
vector_search_index: {
|
|
127
|
+
yamlKey: "uc_securable",
|
|
128
|
+
varFields: [["id", "securable_full_name"]],
|
|
129
|
+
staticFields: [["securable_type", "TABLE"]]
|
|
130
|
+
}
|
|
131
|
+
};
|
|
49
132
|
/** Capabilities that need a user_api_scope but have no resource ID. */
|
|
50
133
|
const capabilityScopeSchema = z.enum([
|
|
51
134
|
"ai-gateway",
|
|
@@ -56,6 +139,23 @@ const capabilityScopeSchema = z.enum([
|
|
|
56
139
|
"catalog.schemas:read",
|
|
57
140
|
"catalog.tables:read"
|
|
58
141
|
]);
|
|
142
|
+
/**
|
|
143
|
+
* Every Apps user_api_scope (short names; the long forms such as
|
|
144
|
+
* `dashboards.genie` are deprecated aliases). A plugin declares one in its
|
|
145
|
+
* `scopes` when it always calls on behalf of the user, or when the capability
|
|
146
|
+
* has no resource ID.
|
|
147
|
+
*/
|
|
148
|
+
const userApiScopeSchema = z.enum([
|
|
149
|
+
"sql",
|
|
150
|
+
"sql:restricted-query",
|
|
151
|
+
"genie",
|
|
152
|
+
"postgres",
|
|
153
|
+
"model-serving",
|
|
154
|
+
"files",
|
|
155
|
+
"vector-search",
|
|
156
|
+
"catalog.connections",
|
|
157
|
+
...capabilityScopeSchema.options
|
|
158
|
+
]);
|
|
59
159
|
const secretPermissionSchema = z.enum([
|
|
60
160
|
"READ",
|
|
61
161
|
"WRITE",
|
|
@@ -329,7 +429,7 @@ const pluginScaffoldingRulesSchema = z.object({
|
|
|
329
429
|
}
|
|
330
430
|
}).describe("Structured rules for scaffolding agents declared at the plugin level. Each rule is a short directive (≤120 chars).");
|
|
331
431
|
const pluginManifestSchema = z.object({
|
|
332
|
-
scopes: z.array(
|
|
432
|
+
scopes: z.array(userApiScopeSchema).optional().describe("user_api_scopes the plugin always needs, whatever its resources are bound as: calls it makes on behalf of the user unconditionally, or capabilities with no resource ID."),
|
|
333
433
|
$schema: z.string().optional().describe("Reference to the JSON Schema for validation"),
|
|
334
434
|
name: z.string().regex(PLUGIN_NAME_PATTERN).describe("Plugin identifier and JS binding. Must start with a lowercase letter; camelCase for multi-word names (e.g. aiSearch)."),
|
|
335
435
|
displayName: z.string().min(1).describe("Human-readable display name for UI and CLI"),
|
|
@@ -392,7 +492,14 @@ const templateResourceRequirementBaseShape = {
|
|
|
392
492
|
alias: z.string().min(1).describe("Human-readable label for UI/display only."),
|
|
393
493
|
resourceKey: z.string().regex(/^[a-z][a-z0-9-]*$/).describe("Stable key for machine use: deduplication, env naming, composite keys."),
|
|
394
494
|
description: z.string().min(1).describe("Human-readable description of why this resource is needed"),
|
|
395
|
-
fields: z.record(z.string(), templateFieldEntrySchema).refine((obj) => Object.keys(obj).length >= 1, { message: "fields must contain at least one entry" }).optional().describe("Map of field name to field entry with computed origin.")
|
|
495
|
+
fields: z.record(z.string(), templateFieldEntrySchema).refine((obj) => Object.keys(obj).length >= 1, { message: "fields must contain at least one entry" }).optional().describe("Map of field name to field entry with computed origin."),
|
|
496
|
+
scope: z.string().min(1).optional().describe("Apps user_api_scope for this resource type. Present only when the type can run on behalf of the user. Resolved by sync from SCOPE_BY_TYPE."),
|
|
497
|
+
appOnly: z.literal(true).optional().describe("Present only when the type always runs as the app service principal and must be bound (secret, database, postgres). Resolved by sync from APP_ONLY_RESOURCE_TYPES."),
|
|
498
|
+
binding: z.object({
|
|
499
|
+
yamlKey: z.string().min(1).describe("DABs YAML key under the resource entry."),
|
|
500
|
+
varFields: z.array(z.tuple([z.string(), z.string()])).describe("[manifestField, dabsField] pairs. Each becomes ${var.<resourceKey>_<manifestField>} written to dabsField."),
|
|
501
|
+
staticFields: z.array(z.tuple([z.string(), z.string()])).optional().describe("[dabsField, value] constant pairs.")
|
|
502
|
+
}).strict().optional().describe("How this resource type binds in databricks.yml as a DABs app resource. Resolved by sync from DABS_BINDING_BY_TYPE.")
|
|
396
503
|
};
|
|
397
504
|
function makeTemplateResourceVariant(typeLiteral, permission) {
|
|
398
505
|
return z.object({
|
|
@@ -429,7 +536,8 @@ const templatePluginSchema = z.object({
|
|
|
429
536
|
resources: z.object({
|
|
430
537
|
required: z.array(templateResourceRequirementSchema).describe("Resources that must be available for the plugin to function"),
|
|
431
538
|
optional: z.array(templateResourceRequirementSchema).describe("Resources that enhance functionality but are not mandatory")
|
|
432
|
-
}).strict().describe("Databricks resource requirements for this plugin")
|
|
539
|
+
}).strict().describe("Databricks resource requirements for this plugin"),
|
|
540
|
+
scopes: z.array(userApiScopeSchema).min(1).optional().describe("user_api_scopes the plugin always needs, copied from the plugin manifest. Omitted when empty.")
|
|
433
541
|
}).strict().describe("Plugin manifest with package source information");
|
|
434
542
|
const scaffoldingFlagSchema = z.object({
|
|
435
543
|
description: z.string().describe("Human-readable description of the flag."),
|
|
@@ -535,5 +643,5 @@ const templatePluginsManifestSchema = z.object({
|
|
|
535
643
|
}).describe("Aggregated plugin manifest for AppKit templates. Read by Databricks CLI during init to discover available plugins and their resource requirements.");
|
|
536
644
|
|
|
537
645
|
//#endregion
|
|
538
|
-
export { TEMPLATE_SCAFFOLDING, appPermissionSchema, computeOriginFromField, databasePermissionSchema, experimentPermissionSchema, genieSpacePermissionSchema, jobPermissionSchema, pluginManifestSchema, postgresPermissionSchema, resourceTypeSchema, secretPermissionSchema, servingEndpointPermissionSchema, sqlWarehousePermissionSchema, templateFieldEntrySchema, templatePluginsManifestSchema, ucConnectionPermissionSchema, ucFunctionPermissionSchema, vectorSearchIndexPermissionSchema, volumePermissionSchema };
|
|
646
|
+
export { APP_ONLY_RESOURCE_TYPES, DABS_BINDING_BY_TYPE, SCOPE_BY_TYPE, TEMPLATE_SCAFFOLDING, appPermissionSchema, computeOriginFromField, databasePermissionSchema, experimentPermissionSchema, genieSpacePermissionSchema, jobPermissionSchema, pluginManifestSchema, postgresPermissionSchema, resourceTypeSchema, secretPermissionSchema, servingEndpointPermissionSchema, sqlWarehousePermissionSchema, templateFieldEntrySchema, templatePluginsManifestSchema, ucConnectionPermissionSchema, ucFunctionPermissionSchema, vectorSearchIndexPermissionSchema, volumePermissionSchema };
|
|
539
647
|
//# sourceMappingURL=manifest.js.map
|