@hanzogui/recipes 7.3.1 → 8.1.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/dist/admin.js +11 -3
- package/dist/index.js +42 -34
- package/package.json +2 -2
- package/src/admin.ts +13 -4
- package/src/project.ts +2 -1
- package/src/read.ts +10 -4
- package/src/types.ts +4 -1
- package/src/write.ts +8 -2
package/dist/admin.js
CHANGED
|
@@ -11,7 +11,11 @@ function resolveBaseUrl(override) {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
// src/admin.ts
|
|
14
|
-
async function authAdmin({
|
|
14
|
+
async function authAdmin({
|
|
15
|
+
baseUrl,
|
|
16
|
+
email,
|
|
17
|
+
password
|
|
18
|
+
}) {
|
|
15
19
|
const url = `${resolveBaseUrl(baseUrl)}/v1/base/admins/auth-with-password`;
|
|
16
20
|
const res = await fetch(url, {
|
|
17
21
|
method: "POST",
|
|
@@ -27,7 +31,9 @@ async function authAdmin({ baseUrl, email, password }) {
|
|
|
27
31
|
async function ensureCollection(auth) {
|
|
28
32
|
const base = resolveBaseUrl(auth.baseUrl);
|
|
29
33
|
const headers = { Authorization: auth.token, "Content-Type": "application/json" };
|
|
30
|
-
const probe = await fetch(`${base}/v1/base/collections/${RECIPES_COLLECTION}`, {
|
|
34
|
+
const probe = await fetch(`${base}/v1/base/collections/${RECIPES_COLLECTION}`, {
|
|
35
|
+
headers
|
|
36
|
+
});
|
|
31
37
|
if (probe.ok)
|
|
32
38
|
return "existed";
|
|
33
39
|
const schema = {
|
|
@@ -42,7 +48,9 @@ async function ensureCollection(auth) {
|
|
|
42
48
|
{ name: "recipeDeps", type: "json", required: false, options: {} },
|
|
43
49
|
{ name: "files", type: "json", required: true, options: {} }
|
|
44
50
|
],
|
|
45
|
-
indexes: [
|
|
51
|
+
indexes: [
|
|
52
|
+
`CREATE UNIQUE INDEX idx_${RECIPES_COLLECTION}_slug ON ${RECIPES_COLLECTION} (slug)`
|
|
53
|
+
],
|
|
46
54
|
listRule: "",
|
|
47
55
|
viewRule: "",
|
|
48
56
|
createRule: null,
|
package/dist/index.js
CHANGED
|
@@ -45,35 +45,6 @@ function resolveInstallDir(root, config) {
|
|
|
45
45
|
return path.join(root, config.installDir ?? DEFAULT_INSTALL_DIR);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
// src/write.ts
|
|
49
|
-
import { existsSync as existsSync2, promises as fs2 } from "node:fs";
|
|
50
|
-
import path2 from "node:path";
|
|
51
|
-
async function getInstallDir(opts) {
|
|
52
|
-
if (opts.installDir)
|
|
53
|
-
return opts.installDir;
|
|
54
|
-
const root = await findProjectRoot(opts.cwd);
|
|
55
|
-
return resolveInstallDir(root, await readConfig(root));
|
|
56
|
-
}
|
|
57
|
-
async function installRecipe(recipe, opts = {}) {
|
|
58
|
-
const installDir = await getInstallDir(opts);
|
|
59
|
-
const written = [];
|
|
60
|
-
for (const file of recipe.files) {
|
|
61
|
-
const dest = path2.join(installDir, file.path);
|
|
62
|
-
const dir = path2.dirname(dest);
|
|
63
|
-
if (!existsSync2(dir))
|
|
64
|
-
await fs2.mkdir(dir, { recursive: true });
|
|
65
|
-
await fs2.writeFile(dest, file.source);
|
|
66
|
-
written.push(dest);
|
|
67
|
-
}
|
|
68
|
-
return written;
|
|
69
|
-
}
|
|
70
|
-
async function installPlan(plan, opts = {}) {
|
|
71
|
-
const written = [];
|
|
72
|
-
for (const r of [plan.recipe, ...plan.transitiveRecipes]) {
|
|
73
|
-
written.push(...await installRecipe(r, opts));
|
|
74
|
-
}
|
|
75
|
-
return written;
|
|
76
|
-
}
|
|
77
48
|
// src/read.ts
|
|
78
49
|
async function getJSON(url) {
|
|
79
50
|
const res = await fetch(url, { headers: { Accept: "application/json" } });
|
|
@@ -122,13 +93,17 @@ async function resolveRecipe(slug, opts = {}) {
|
|
|
122
93
|
})();
|
|
123
94
|
const recipe = seen.get(slug);
|
|
124
95
|
const transitiveRecipes = Array.from(seen.values()).filter((r) => r.slug !== slug);
|
|
125
|
-
const
|
|
126
|
-
const targetPaths = [recipe, ...transitiveRecipes].flatMap((r) => r.files.map((f) =>
|
|
96
|
+
const path2 = await import("node:path");
|
|
97
|
+
const targetPaths = [recipe, ...transitiveRecipes].flatMap((r) => r.files.map((f) => path2.join(installDir, f.path)));
|
|
127
98
|
return { recipe, transitiveRecipes, targetPaths };
|
|
128
99
|
}
|
|
129
100
|
|
|
130
101
|
// src/admin.ts
|
|
131
|
-
async function authAdmin({
|
|
102
|
+
async function authAdmin({
|
|
103
|
+
baseUrl,
|
|
104
|
+
email,
|
|
105
|
+
password
|
|
106
|
+
}) {
|
|
132
107
|
const url = `${resolveBaseUrl(baseUrl)}/v1/base/admins/auth-with-password`;
|
|
133
108
|
const res = await fetch(url, {
|
|
134
109
|
method: "POST",
|
|
@@ -144,7 +119,9 @@ async function authAdmin({ baseUrl, email, password }) {
|
|
|
144
119
|
async function ensureCollection(auth) {
|
|
145
120
|
const base = resolveBaseUrl(auth.baseUrl);
|
|
146
121
|
const headers = { Authorization: auth.token, "Content-Type": "application/json" };
|
|
147
|
-
const probe = await fetch(`${base}/v1/base/collections/${RECIPES_COLLECTION}`, {
|
|
122
|
+
const probe = await fetch(`${base}/v1/base/collections/${RECIPES_COLLECTION}`, {
|
|
123
|
+
headers
|
|
124
|
+
});
|
|
148
125
|
if (probe.ok)
|
|
149
126
|
return "existed";
|
|
150
127
|
const schema = {
|
|
@@ -159,7 +136,9 @@ async function ensureCollection(auth) {
|
|
|
159
136
|
{ name: "recipeDeps", type: "json", required: false, options: {} },
|
|
160
137
|
{ name: "files", type: "json", required: true, options: {} }
|
|
161
138
|
],
|
|
162
|
-
indexes: [
|
|
139
|
+
indexes: [
|
|
140
|
+
`CREATE UNIQUE INDEX idx_${RECIPES_COLLECTION}_slug ON ${RECIPES_COLLECTION} (slug)`
|
|
141
|
+
],
|
|
163
142
|
listRule: "",
|
|
164
143
|
viewRule: "",
|
|
165
144
|
createRule: null,
|
|
@@ -205,6 +184,35 @@ async function upsertRecipe(auth, recipe) {
|
|
|
205
184
|
throw new Error(`POST ${recipe.slug}: ${res.status} ${await res.text()}`);
|
|
206
185
|
return "created";
|
|
207
186
|
}
|
|
187
|
+
// src/write.ts
|
|
188
|
+
import { existsSync as existsSync2, promises as fs2 } from "node:fs";
|
|
189
|
+
import path2 from "node:path";
|
|
190
|
+
async function getInstallDir(opts) {
|
|
191
|
+
if (opts.installDir)
|
|
192
|
+
return opts.installDir;
|
|
193
|
+
const root = await findProjectRoot(opts.cwd);
|
|
194
|
+
return resolveInstallDir(root, await readConfig(root));
|
|
195
|
+
}
|
|
196
|
+
async function installRecipe(recipe, opts = {}) {
|
|
197
|
+
const installDir = await getInstallDir(opts);
|
|
198
|
+
const written = [];
|
|
199
|
+
for (const file of recipe.files) {
|
|
200
|
+
const dest = path2.join(installDir, file.path);
|
|
201
|
+
const dir = path2.dirname(dest);
|
|
202
|
+
if (!existsSync2(dir))
|
|
203
|
+
await fs2.mkdir(dir, { recursive: true });
|
|
204
|
+
await fs2.writeFile(dest, file.source);
|
|
205
|
+
written.push(dest);
|
|
206
|
+
}
|
|
207
|
+
return written;
|
|
208
|
+
}
|
|
209
|
+
async function installPlan(plan, opts = {}) {
|
|
210
|
+
const written = [];
|
|
211
|
+
for (const r of [plan.recipe, ...plan.transitiveRecipes]) {
|
|
212
|
+
written.push(...await installRecipe(r, opts));
|
|
213
|
+
}
|
|
214
|
+
return written;
|
|
215
|
+
}
|
|
208
216
|
export {
|
|
209
217
|
upsertRecipe,
|
|
210
218
|
resolveRecipe,
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzogui/recipes",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.1.0",
|
|
4
4
|
"description": "Hanzo GUI recipe library: read from Base, resolve transitive deps, install into a project.",
|
|
5
5
|
"license": "BSD-3-Clause",
|
|
6
6
|
"author": "Hanzo AI <dev@hanzo.ai>",
|
|
7
7
|
"homepage": "https://gui.hanzo.ai",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
|
-
"url": "https://github.com/hanzoai/gui.git",
|
|
10
|
+
"url": "git+https://github.com/hanzoai/gui.git",
|
|
11
11
|
"directory": "pkgs/recipes"
|
|
12
12
|
},
|
|
13
13
|
"type": "module",
|
package/src/admin.ts
CHANGED
|
@@ -13,7 +13,11 @@ export type AdminPasswordAuth = {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
/** Exchanges admin email+password for a Base session token. */
|
|
16
|
-
export async function authAdmin({
|
|
16
|
+
export async function authAdmin({
|
|
17
|
+
baseUrl,
|
|
18
|
+
email,
|
|
19
|
+
password,
|
|
20
|
+
}: AdminPasswordAuth): Promise<string> {
|
|
17
21
|
const url = `${resolveBaseUrl(baseUrl)}/v1/base/admins/auth-with-password`
|
|
18
22
|
const res = await fetch(url, {
|
|
19
23
|
method: 'POST',
|
|
@@ -31,7 +35,9 @@ export async function authAdmin({ baseUrl, email, password }: AdminPasswordAuth)
|
|
|
31
35
|
export async function ensureCollection(auth: AdminAuth): Promise<'created' | 'existed'> {
|
|
32
36
|
const base = resolveBaseUrl(auth.baseUrl)
|
|
33
37
|
const headers = { Authorization: auth.token, 'Content-Type': 'application/json' }
|
|
34
|
-
const probe = await fetch(`${base}/v1/base/collections/${RECIPES_COLLECTION}`, {
|
|
38
|
+
const probe = await fetch(`${base}/v1/base/collections/${RECIPES_COLLECTION}`, {
|
|
39
|
+
headers,
|
|
40
|
+
})
|
|
35
41
|
if (probe.ok) return 'existed'
|
|
36
42
|
|
|
37
43
|
const schema = {
|
|
@@ -46,7 +52,9 @@ export async function ensureCollection(auth: AdminAuth): Promise<'created' | 'ex
|
|
|
46
52
|
{ name: 'recipeDeps', type: 'json', required: false, options: {} },
|
|
47
53
|
{ name: 'files', type: 'json', required: true, options: {} },
|
|
48
54
|
],
|
|
49
|
-
indexes: [
|
|
55
|
+
indexes: [
|
|
56
|
+
`CREATE UNIQUE INDEX idx_${RECIPES_COLLECTION}_slug ON ${RECIPES_COLLECTION} (slug)`,
|
|
57
|
+
],
|
|
50
58
|
listRule: '',
|
|
51
59
|
viewRule: '',
|
|
52
60
|
createRule: null,
|
|
@@ -90,7 +98,8 @@ export async function upsertRecipe(
|
|
|
90
98
|
`${base}/v1/base/collections/${RECIPES_COLLECTION}/records/${existing}`,
|
|
91
99
|
{ method: 'PATCH', headers, body }
|
|
92
100
|
)
|
|
93
|
-
if (!res.ok)
|
|
101
|
+
if (!res.ok)
|
|
102
|
+
throw new Error(`PATCH ${recipe.slug}: ${res.status} ${await res.text()}`)
|
|
94
103
|
return 'updated'
|
|
95
104
|
}
|
|
96
105
|
const res = await fetch(`${base}/v1/base/collections/${RECIPES_COLLECTION}/records`, {
|
package/src/project.ts
CHANGED
|
@@ -11,7 +11,8 @@ export async function findProjectRoot(start: string = process.cwd()): Promise<st
|
|
|
11
11
|
if (existsSync(pkgPath)) {
|
|
12
12
|
try {
|
|
13
13
|
const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf8'))
|
|
14
|
-
if (pkg.workspaces || existsSync(path.join(dir, 'pnpm-workspace.yaml')))
|
|
14
|
+
if (pkg.workspaces || existsSync(path.join(dir, 'pnpm-workspace.yaml')))
|
|
15
|
+
return dir
|
|
15
16
|
} catch {}
|
|
16
17
|
}
|
|
17
18
|
if (existsSync(path.join(dir, '.git'))) return dir
|
package/src/read.ts
CHANGED
|
@@ -17,9 +17,12 @@ export type ReaderOptions = {
|
|
|
17
17
|
async function getJSON<T>(url: string): Promise<T> {
|
|
18
18
|
const res = await fetch(url, { headers: { Accept: 'application/json' } })
|
|
19
19
|
if (!res.ok) {
|
|
20
|
-
throw Object.assign(
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
throw Object.assign(
|
|
21
|
+
new Error(`Base request failed: ${res.status} ${res.statusText}`),
|
|
22
|
+
{
|
|
23
|
+
status: res.status,
|
|
24
|
+
}
|
|
25
|
+
)
|
|
23
26
|
}
|
|
24
27
|
return (await res.json()) as T
|
|
25
28
|
}
|
|
@@ -53,7 +56,10 @@ export type ResolveOptions = ReaderOptions & {
|
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
/** Walks recipeDeps transitively, computes target paths under the resolved install dir. */
|
|
56
|
-
export async function resolveRecipe(
|
|
59
|
+
export async function resolveRecipe(
|
|
60
|
+
slug: string,
|
|
61
|
+
opts: ResolveOptions = {}
|
|
62
|
+
): Promise<InstallPlan> {
|
|
57
63
|
const seen = new Map<string, Recipe>()
|
|
58
64
|
const queue = [slug]
|
|
59
65
|
while (queue.length) {
|
package/src/types.ts
CHANGED
|
@@ -21,7 +21,10 @@ export type RecipeSummary = Pick<
|
|
|
21
21
|
'id' | 'slug' | 'name' | 'description' | 'category'
|
|
22
22
|
>
|
|
23
23
|
|
|
24
|
-
export type RecipeInput = Pick<
|
|
24
|
+
export type RecipeInput = Pick<
|
|
25
|
+
Recipe,
|
|
26
|
+
'slug' | 'name' | 'description' | 'category' | 'npmDeps' | 'recipeDeps' | 'files'
|
|
27
|
+
>
|
|
25
28
|
|
|
26
29
|
export type InstallPlan = {
|
|
27
30
|
recipe: Recipe
|
package/src/write.ts
CHANGED
|
@@ -17,7 +17,10 @@ async function getInstallDir(opts: WriteOptions): Promise<string> {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
/** Writes one recipe's files. Returns absolute paths written. */
|
|
20
|
-
export async function installRecipe(
|
|
20
|
+
export async function installRecipe(
|
|
21
|
+
recipe: Recipe,
|
|
22
|
+
opts: WriteOptions = {}
|
|
23
|
+
): Promise<string[]> {
|
|
21
24
|
const installDir = await getInstallDir(opts)
|
|
22
25
|
const written: string[] = []
|
|
23
26
|
for (const file of recipe.files) {
|
|
@@ -31,7 +34,10 @@ export async function installRecipe(recipe: Recipe, opts: WriteOptions = {}): Pr
|
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
/** Writes a recipe and all its transitive dependents from a resolved plan. */
|
|
34
|
-
export async function installPlan(
|
|
37
|
+
export async function installPlan(
|
|
38
|
+
plan: InstallPlan,
|
|
39
|
+
opts: WriteOptions = {}
|
|
40
|
+
): Promise<string[]> {
|
|
35
41
|
const written: string[] = []
|
|
36
42
|
for (const r of [plan.recipe, ...plan.transitiveRecipes]) {
|
|
37
43
|
written.push(...(await installRecipe(r, opts)))
|