Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
60652cc
feat(appkit): appkit plugin add with --auth-mode and --set resource c…
MarioCadenas Oct 20, 2026
1232c23
fix(appkit): plugin add OBO files auth, genie scopes, lakebase derive…
MarioCadenas Oct 20, 2026
6f1cb43
fix(appkit): plugin add sets aiSearch OBO auth on its default index
MarioCadenas Oct 20, 2026
aafef48
fix(appkit): plugin add writes aiSearch index columns so production s…
MarioCadenas Oct 20, 2026
f405058
fix(appkit): plugin add agents OBO, .env placeholders, defaults, app …
MarioCadenas Oct 20, 2026
ee2aba3
fix(appkit): plugin add agents OBO follows the model endpoint only
MarioCadenas Oct 20, 2026
b28a02a
fix(appkit): plugin add limits aiSearch columns to the index's synced…
MarioCadenas Oct 20, 2026
2959afc
fix(appkit): plugin add honors the columnsToIndex alias for aiSearch …
MarioCadenas Oct 20, 2026
4ba6f1a
Merge commit '8e43a7c2cf8fddca30a22078019b47436ca98d63' into work/2-a…
MarioCadenas Oct 23, 2026
749a53c
Merge commit '5a449a7be4448c4467193acf188ebda9c191c545' into work/2-a…
MarioCadenas Oct 23, 2026
92589fb
Merge commit '67b88e8f5d6f9cf97b8f6aec589ea8c431f6818f' into work/2-a…
MarioCadenas Oct 26, 2026
151c6df
fix(shared): plugin registration extends an existing import from the …
MarioCadenas Oct 27, 2026
17d641e
fix(shared): plugin add type-only import detection, agents model on s…
MarioCadenas Oct 27, 2026
836f89d
fix(shared): plugin add prints required config with --no-register, re…
MarioCadenas Oct 27, 2026
e4d5c20
feat(shared): appkit plugin add takes several plugins or a data plan …
MarioCadenas Oct 28, 2026
9313e0c
fix(shared): plugin add plans agents last; registry add skips the bun…
MarioCadenas Oct 28, 2026
0db52f0
ci: install the pnpm template smoke test with a fresh store
MarioCadenas Oct 28, 2026
a06e785
fix(shared): plugin add sets files auth per volume for mixed modes, m…
MarioCadenas Oct 28, 2026
9a4f213
Merge commit '4917a0c3b1c4b9eda48864db6354906cab1da70f' into work/2-a…
MarioCadenas Oct 28, 2026
be497de
fix(shared): plugin add --from-plan accepts set keys with the plugin …
MarioCadenas Oct 29, 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: "serving_endpoint",
alias: "LLM",
resourceKey: "agents-serving-endpoint",
scope: "model-serving",
};
expect(
pluginConfig(plugin("agents"), [endpoint, experiment], {
"serving_endpoint:agents-serving-endpoint": "obo",
}),
).toEqual({
auth: "on-behalf-of-user",
});
});

test("agents auth follows the model endpoint, not the skills volume", () => {
const endpoint: ManifestResource = {
type: "serving_endpoint",
alias: "LLM",
resourceKey: "agents-serving-endpoint",
scope: "model-serving",
};
const skills: ManifestResource = {
type: "volume",
alias: "Skills",
resourceKey: "agents-skills",
scope: "files",
};
const modelObo = {
"serving_endpoint:agents-serving-endpoint": "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-serving-endpoint" },
{ 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