Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
6d4c504
feat(appkit): appkit plugin add with --auth-mode and --set resource c…
MarioCadenas Oct 20, 2026
e28dab7
fix(appkit): plugin add OBO files auth, genie scopes, lakebase derive…
MarioCadenas Oct 20, 2026
4b1fd2f
fix(appkit): plugin add sets aiSearch OBO auth on its default index
MarioCadenas Oct 20, 2026
8f65302
fix(appkit): plugin add writes aiSearch index columns so production s…
MarioCadenas Oct 20, 2026
d3d568d
fix(appkit): plugin add agents OBO, .env placeholders, defaults, app …
MarioCadenas Oct 20, 2026
47b37d6
fix(appkit): plugin add agents OBO follows the model endpoint only
MarioCadenas Oct 20, 2026
5e4ca65
fix(appkit): plugin add limits aiSearch columns to the index's synced…
MarioCadenas Oct 20, 2026
49cc261
fix(appkit): plugin add honors the columnsToIndex alias for aiSearch …
MarioCadenas Oct 20, 2026
f5f984f
fix(shared): plugin registration extends an existing import from the …
MarioCadenas Oct 27, 2026
43a3df0
fix(shared): plugin add type-only import detection, agents model on s…
MarioCadenas Oct 27, 2026
e5eebb4
fix(shared): plugin add prints required config with --no-register, re…
MarioCadenas Oct 27, 2026
186e636
feat(shared): appkit plugin add takes several plugins or a data plan …
MarioCadenas Oct 28, 2026
eab9209
fix(shared): plugin add plans agents last; registry add skips the bun…
MarioCadenas Oct 28, 2026
d6b48e2
ci: install the pnpm template smoke test with a fresh store
MarioCadenas Oct 28, 2026
d5b3077
fix(shared): plugin add sets files auth per volume for mixed modes, m…
MarioCadenas Oct 28, 2026
7e32c94
fix(shared): plugin add --from-plan accepts set keys with the plugin …
MarioCadenas Oct 29, 2026
fb93b15
refactor(shared): simplify plugin add helpers
MarioCadenas Nov 5, 2026
fe0e514
refactor(shared): trim plugin add comments to one line each
MarioCadenas Nov 5, 2026
7502564
refactor(shared): return a Set from validSetKeys, inline byName
MarioCadenas Nov 5, 2026
ca66527
fix(shared): plugin add follows the agents model_service resource fro…
MarioCadenas Nov 11, 2026
36e4045
fix(shared): plugin add checks YAML and databricks.yml conflicts befo…
MarioCadenas Nov 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 134 additions & 0 deletions packages/shared/src/cli/commands/plugin/add/add.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";

import { expect, test } from "vitest";

import { envConflicts, pluginConfig, toSource } from "./add";
import type { ManifestPlugin, ManifestResource } from "./plan";

const index: ManifestResource = {
type: "vector_search_index",
alias: "Index",
resourceKey: "vector-search-index",
scope: "vector-search",
};
const plugin = (name: string): ManifestPlugin => ({
name,
package: "@databricks/appkit",
resources: { required: [index], optional: [] },
});
const obo = { "vector_search_index:vector-search-index": "obo" } as const;

test("aiSearch sets columns and OBO auth on the default index", () => {
const config = pluginConfig(plugin("aiSearch"), [index], obo, ["id", "t"]);
expect(toSource(config)).toBe(
`{ indexes: { default: { columns: ["id", "t"], auth: "on-behalf-of-user" } } }`,
);
expect(pluginConfig(plugin("aiSearch"), [index], {}, ["id"])).toEqual({
indexes: { default: { columns: ["id"] } },
});
});

test("files OBO sets the plugin-level auth", () => {
expect(pluginConfig(plugin("files"), [index], obo)).toEqual({
auth: "on-behalf-of-user",
});
});

test("no config for sp or plugins without an auth option", () => {
expect(pluginConfig(plugin("files"), [index], {})).toBeUndefined();
expect(pluginConfig(plugin("analytics"), [index], obo)).toBeUndefined();
});

test("toSource escapes strings and quotes non-identifier keys", () => {
expect(toSource({ "a-b": ['x"); evil()'] })).toBe(
`{ "a-b": ["x\\"); evil()"] }`,
);
});

test("agents OBO sets plugin auth; resources that can't be OBO don't block it", () => {
const experiment: ManifestResource = {
type: "experiment",
alias: "Experiment",
resourceKey: "agents-mlflow-experiment",
};
const endpoint: ManifestResource = {
type: "model_service",
alias: "LLM",
resourceKey: "agents-model-service",
scope: "model-serving",
};
expect(
pluginConfig(plugin("agents"), [endpoint, experiment], {
"model_service:agents-model-service": "obo",
}),
).toEqual({
auth: "on-behalf-of-user",
});
});

test("agents auth follows the model endpoint, not the skills volume", () => {
const endpoint: ManifestResource = {
type: "model_service",
alias: "LLM",
resourceKey: "agents-model-service",
scope: "model-serving",
};
const skills: ManifestResource = {
type: "volume",
alias: "Skills",
resourceKey: "agents-skills",
scope: "files",
};
const modelObo = {
"model_service:agents-model-service": "obo",
} as const;
expect(pluginConfig(plugin("agents"), [endpoint, skills], modelObo)).toEqual({
auth: "on-behalf-of-user",
});
expect(
pluginConfig(plugin("agents"), [endpoint, skills], {
"volume:agents-skills": "obo",
}),
).toBeUndefined();
});

test("envConflicts flags env names bound to another resource or value", () => {
const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "plugin-add-"));
fs.writeFileSync(path.join(cwd, ".env"), "A=old\nB=\n");
fs.writeFileSync(
path.join(cwd, "app.yaml"),
"env:\n - name: C\n valueFrom: serving-endpoint\n - name: D\n valueFrom: same\n",
);
const plan = {
dotEnv: [
{ env: "A", value: "new" },
{ env: "B", value: "x" },
],
appYamlEnv: [
{ name: "C", valueFrom: "agents-model-service" },
{ name: "D", valueFrom: "same" },
],
bundleVariables: [],
resourceBindings: [],
unverifiedTypes: [],
};
expect(envConflicts(cwd, plan)).toEqual(["A", "C"]);
fs.rmSync(cwd, { recursive: true, force: true });
});

test("files with mixed modes sets auth on the OBO volumes only", () => {
const vol = (key: string, env: string): ManifestResource => ({
type: "volume",
alias: key,
resourceKey: key,
scope: "files",
fields: { path: { env } },
});
const priv = vol("files", "DATABRICKS_VOLUME_FILES");
const exp = vol("exports", "DATABRICKS_VOLUME_EXPORTS");
expect(
pluginConfig(plugin("files"), [priv, exp], { "volume:files": "obo" }),
).toEqual({ volumes: { files: { auth: "on-behalf-of-user" } } });
});
Loading
Loading