@openpalm/lib 0.11.0-rc.1 → 0.11.0-rc.18
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/package.json +1 -1
- package/src/control-plane/akm-sources.ts +1 -1
- package/src/control-plane/compose-args.test.ts +4 -2
- package/src/control-plane/compose-args.ts +2 -3
- package/src/control-plane/config-persistence.ts +8 -1
- package/src/control-plane/core-assets.ts +24 -16
- package/src/control-plane/defaults.ts +16 -0
- package/src/control-plane/env.ts +15 -0
- package/src/control-plane/home.ts +3 -3
- package/src/control-plane/install-edge-cases.test.ts +2 -31
- package/src/control-plane/lifecycle.ts +8 -4
- package/src/control-plane/migrations.test.ts +272 -0
- package/src/control-plane/migrations.ts +423 -0
- package/src/control-plane/paths.ts +1 -1
- package/src/control-plane/registry.ts +22 -10
- package/src/control-plane/setup.test.ts +2 -22
- package/src/control-plane/setup.ts +0 -4
- package/src/control-plane/skeleton-guardrail.test.ts +3 -2
- package/src/control-plane/spec-to-env.ts +2 -2
- package/src/control-plane/ui-assets.test.ts +205 -2
- package/src/control-plane/ui-assets.ts +167 -94
- package/src/index.ts +10 -10
- package/src/control-plane/stack-spec.test.ts +0 -98
- package/src/control-plane/stack-spec.ts +0 -88
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Stack spec parser tests.
|
|
3
|
-
*
|
|
4
|
-
* Verifies that readStackSpec / writeStackSpec produce consistent results.
|
|
5
|
-
*/
|
|
6
|
-
import { describe, it, expect, beforeEach, afterEach } from "bun:test";
|
|
7
|
-
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
8
|
-
import { join } from "node:path";
|
|
9
|
-
import { tmpdir } from "node:os";
|
|
10
|
-
import {
|
|
11
|
-
readStackSpec,
|
|
12
|
-
writeStackSpec,
|
|
13
|
-
STACK_SPEC_FILENAME,
|
|
14
|
-
} from "./stack-spec.js";
|
|
15
|
-
import type { StackSpec } from "./stack-spec.js";
|
|
16
|
-
|
|
17
|
-
let configDir: string;
|
|
18
|
-
|
|
19
|
-
beforeEach(() => {
|
|
20
|
-
configDir = mkdtempSync(join(tmpdir(), "stack-spec-test-"));
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
afterEach(() => {
|
|
24
|
-
rmSync(configDir, { recursive: true, force: true });
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
const MINIMAL_SPEC: StackSpec = { version: 2 };
|
|
28
|
-
|
|
29
|
-
// ── readStackSpec / writeStackSpec round-trip ────────────────────────────
|
|
30
|
-
|
|
31
|
-
describe("readStackSpec / writeStackSpec round-trip", () => {
|
|
32
|
-
it("round-trips a minimal spec", () => {
|
|
33
|
-
writeStackSpec(configDir, MINIMAL_SPEC);
|
|
34
|
-
const read = readStackSpec(configDir);
|
|
35
|
-
expect(read).not.toBeNull();
|
|
36
|
-
expect(read!.version).toBe(2);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it("round-trips enabled addons", () => {
|
|
40
|
-
writeStackSpec(configDir, { version: 2, addons: ['chat', 'api'] });
|
|
41
|
-
expect(readStackSpec(configDir)).toEqual({ version: 2, addons: ['api', 'chat'] });
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it("writes to the canonical filename", () => {
|
|
45
|
-
writeStackSpec(configDir, MINIMAL_SPEC);
|
|
46
|
-
const expectedPath = join(configDir, STACK_SPEC_FILENAME);
|
|
47
|
-
expect(expectedPath).toBe(join(configDir, "stack.yml"));
|
|
48
|
-
expect(readStackSpec(configDir)).not.toBeNull();
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it("ignores legacy capabilities fields on read", () => {
|
|
52
|
-
// On upgraded installs, old stack.yml may have capabilities — should still parse
|
|
53
|
-
writeFileSync(join(configDir, STACK_SPEC_FILENAME),
|
|
54
|
-
"version: 2\ncapabilities:\n llm: openai/gpt-4o\n embeddings:\n provider: openai\n model: text-embedding-3-small\n dims: 1536\n"
|
|
55
|
-
);
|
|
56
|
-
const read = readStackSpec(configDir);
|
|
57
|
-
expect(read).not.toBeNull();
|
|
58
|
-
expect(read!.version).toBe(2);
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
// ── readStackSpec edge cases ────────────────────────────────────────────
|
|
63
|
-
|
|
64
|
-
describe("readStackSpec edge cases", () => {
|
|
65
|
-
it("returns null for missing file", () => {
|
|
66
|
-
expect(readStackSpec(configDir)).toBeNull();
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it("returns null for v1 format (connections array)", () => {
|
|
70
|
-
writeFileSync(join(configDir, STACK_SPEC_FILENAME), "version: 1\nconnections: []\n");
|
|
71
|
-
expect(readStackSpec(configDir)).toBeNull();
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it("returns null for corrupt YAML", () => {
|
|
75
|
-
writeFileSync(join(configDir, STACK_SPEC_FILENAME), "{{invalid yaml");
|
|
76
|
-
expect(readStackSpec(configDir)).toBeNull();
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it("returns valid spec for version 2 with no other fields", () => {
|
|
80
|
-
writeFileSync(join(configDir, STACK_SPEC_FILENAME), "version: 2\n");
|
|
81
|
-
const spec = readStackSpec(configDir);
|
|
82
|
-
expect(spec).not.toBeNull();
|
|
83
|
-
expect(spec!.version).toBe(2);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it("ignores malformed addon names", () => {
|
|
87
|
-
writeFileSync(join(configDir, STACK_SPEC_FILENAME), "version: 2\naddons:\n - chat\n - ../bad\n - API\n");
|
|
88
|
-
expect(readStackSpec(configDir)).toEqual({ version: 2, addons: ['chat'] });
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
// ── STACK_SPEC_FILENAME ───────────────────────────────────────────────────
|
|
93
|
-
|
|
94
|
-
describe("STACK_SPEC_FILENAME", () => {
|
|
95
|
-
it("is stack.yml", () => {
|
|
96
|
-
expect(STACK_SPEC_FILENAME).toBe("stack.yml");
|
|
97
|
-
});
|
|
98
|
-
});
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Stack specification file (stack.yml) management.
|
|
3
|
-
*
|
|
4
|
-
* The stack spec is a YAML document used as a version marker for the
|
|
5
|
-
* OpenPalm installation schema. AI provider configuration lives in
|
|
6
|
-
* config/akm/config.json (managed via the admin AKM tab).
|
|
7
|
-
*
|
|
8
|
-
* v2: capabilities removed — LLM/embedding now live in akm config.
|
|
9
|
-
*/
|
|
10
|
-
import { mkdirSync, writeFileSync, readFileSync, existsSync } from "node:fs";
|
|
11
|
-
import { stringify as yamlStringify, parse as yamlParse } from "yaml";
|
|
12
|
-
|
|
13
|
-
// ── StackSpec v2 ────────────────────────────────────────────────────────
|
|
14
|
-
|
|
15
|
-
export type StackSpec = {
|
|
16
|
-
version: 2;
|
|
17
|
-
addons?: string[];
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const ADDON_NAME_RE = /^[a-z0-9][a-z0-9-]{0,62}$/;
|
|
21
|
-
|
|
22
|
-
// ── Constants ───────────────────────────────────────────────────────────
|
|
23
|
-
|
|
24
|
-
export const STACK_SPEC_FILENAME = "stack.yml";
|
|
25
|
-
|
|
26
|
-
export const SPEC_DEFAULTS = {
|
|
27
|
-
ports: {
|
|
28
|
-
assistant: 3800,
|
|
29
|
-
hostUi: 3880,
|
|
30
|
-
assistantSsh: 2222,
|
|
31
|
-
},
|
|
32
|
-
image: {
|
|
33
|
-
namespace: "openpalm",
|
|
34
|
-
tag: "latest",
|
|
35
|
-
},
|
|
36
|
-
} as const;
|
|
37
|
-
|
|
38
|
-
// ── Read / Write ────────────────────────────────────────────────────────
|
|
39
|
-
|
|
40
|
-
export function writeStackSpec(configDir: string, spec: StackSpec): void {
|
|
41
|
-
mkdirSync(configDir, { recursive: true });
|
|
42
|
-
const content = yamlStringify(spec, { indent: 2 });
|
|
43
|
-
writeFileSync(`${configDir}/${STACK_SPEC_FILENAME}`, content);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Read the stack spec. Returns null for missing or corrupt files.
|
|
48
|
-
* Only the version field is checked; legacy capability fields are ignored.
|
|
49
|
-
*/
|
|
50
|
-
export function readStackSpec(configDir: string): StackSpec | null {
|
|
51
|
-
const path = `${configDir}/${STACK_SPEC_FILENAME}`;
|
|
52
|
-
if (!existsSync(path)) return null;
|
|
53
|
-
|
|
54
|
-
let raw: unknown;
|
|
55
|
-
try {
|
|
56
|
-
raw = yamlParse(readFileSync(path, "utf-8"), { maxAliasCount: 100 });
|
|
57
|
-
} catch {
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
if (typeof raw !== "object" || raw === null) return null;
|
|
61
|
-
const obj = raw as Record<string, unknown>;
|
|
62
|
-
if (obj.version !== 2) return null;
|
|
63
|
-
const spec: StackSpec = { version: 2 };
|
|
64
|
-
if (Array.isArray(obj.addons)) {
|
|
65
|
-
const addons = obj.addons
|
|
66
|
-
.filter((value): value is string => typeof value === 'string' && ADDON_NAME_RE.test(value))
|
|
67
|
-
.filter((value, index, all) => all.indexOf(value) === index)
|
|
68
|
-
.sort();
|
|
69
|
-
if (addons.length > 0) spec.addons = addons;
|
|
70
|
-
}
|
|
71
|
-
return spec;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export function listStackSpecAddons(configDir: string): string[] {
|
|
75
|
-
return readStackSpec(configDir)?.addons ?? [];
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export function setStackSpecAddon(configDir: string, name: string, enabled: boolean): void {
|
|
79
|
-
if (!ADDON_NAME_RE.test(name)) throw new Error(`Invalid addon name: ${name}`);
|
|
80
|
-
const current = readStackSpec(configDir) ?? { version: 2 };
|
|
81
|
-
const addons = new Set(current.addons ?? []);
|
|
82
|
-
if (enabled) addons.add(name);
|
|
83
|
-
else addons.delete(name);
|
|
84
|
-
const next: StackSpec = { version: 2 };
|
|
85
|
-
const sorted = [...addons].sort();
|
|
86
|
-
if (sorted.length > 0) next.addons = sorted;
|
|
87
|
-
writeStackSpec(configDir, next);
|
|
88
|
-
}
|