Skip to content

Commit c9d08f2

Browse files
fullstackjamclaude
andcommitted
feat: add alias resolution API endpoint for CLI
Add GET /api/configs/alias/[alias] that resolves a config alias to its full config JSON. Used by the CLI to resolve `openboot install <alias>` when no default config exists for that username. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent dc6f55d commit c9d08f2

File tree

1 file changed

+90
-0
lines changed

1 file changed

+90
-0
lines changed
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { json } from '@sveltejs/kit';
2+
import type { RequestHandler } from './$types';
3+
4+
export const GET: RequestHandler = async ({ platform, params }) => {
5+
const env = platform?.env;
6+
if (!env) {
7+
return json({ error: 'Platform env not available' }, { status: 500 });
8+
}
9+
10+
const config = await env.DB.prepare(
11+
`SELECT c.slug, c.name, c.base_preset, c.packages, c.snapshot, c.visibility, c.dotfiles_repo, c.custom_script,
12+
u.username
13+
FROM configs c
14+
JOIN users u ON c.user_id = u.id
15+
WHERE c.alias = ?`
16+
)
17+
.bind(params.alias)
18+
.first<{ slug: string; name: string; base_preset: string; packages: string; snapshot: string; visibility: string; dotfiles_repo: string; custom_script: string; username: string }>();
19+
20+
if (!config) {
21+
return json({ error: 'Alias not found' }, { status: 404 });
22+
}
23+
24+
if (config.visibility === 'private') {
25+
return json({ error: 'Config is private' }, { status: 403 });
26+
}
27+
28+
const tapsSet = new Set<string>();
29+
const snapshotCasks = new Set<string>();
30+
31+
if (config.snapshot) {
32+
try {
33+
const snapshot = JSON.parse(config.snapshot);
34+
for (const tap of snapshot.packages?.taps || []) {
35+
tapsSet.add(tap);
36+
}
37+
for (const cask of snapshot.packages?.casks || []) {
38+
snapshotCasks.add(cask);
39+
}
40+
} catch {
41+
}
42+
}
43+
44+
const rawPackages: any[] = JSON.parse(config.packages || '[]');
45+
const packageNames: string[] = [];
46+
const caskNames: string[] = [];
47+
const npmNames: string[] = [];
48+
49+
for (const pkg of rawPackages) {
50+
if (typeof pkg === 'string') {
51+
packageNames.push(pkg);
52+
if (snapshotCasks.has(pkg)) {
53+
caskNames.push(pkg);
54+
}
55+
} else {
56+
if (pkg.type === 'npm') {
57+
npmNames.push(pkg.name);
58+
} else {
59+
packageNames.push(pkg.name);
60+
if (pkg.type === 'cask') {
61+
caskNames.push(pkg.name);
62+
}
63+
}
64+
}
65+
}
66+
67+
for (const pkg of packageNames) {
68+
const parts = pkg.split('/');
69+
if (parts.length === 3) {
70+
tapsSet.add(`${parts[0]}/${parts[1]}`);
71+
}
72+
}
73+
74+
const taps = Array.from(tapsSet);
75+
76+
return json({
77+
username: config.username,
78+
slug: config.slug,
79+
name: config.name,
80+
preset: config.base_preset,
81+
packages: packageNames,
82+
casks: caskNames,
83+
taps: taps,
84+
npm: npmNames,
85+
dotfiles_repo: config.dotfiles_repo || '',
86+
post_install: config.custom_script
87+
? config.custom_script.split('\n').map((s: string) => s.trim()).filter((s: string) => s.length > 0)
88+
: []
89+
});
90+
};

0 commit comments

Comments
 (0)