@hanzogui/recipes 8.3.1 → 8.3.2

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.
Files changed (57) hide show
  1. package/dist/cjs/admin.js +91 -0
  2. package/dist/cjs/admin.js.map +1 -0
  3. package/dist/cjs/config.js +12 -0
  4. package/dist/cjs/config.js.map +1 -0
  5. package/dist/cjs/index.js +10 -0
  6. package/dist/cjs/index.js.map +1 -0
  7. package/dist/cjs/package.json +1 -0
  8. package/dist/cjs/project.js +46 -0
  9. package/dist/cjs/project.js.map +1 -0
  10. package/dist/cjs/read.js +62 -0
  11. package/dist/cjs/read.js.map +1 -0
  12. package/dist/cjs/types.js +3 -0
  13. package/dist/cjs/types.js.map +1 -0
  14. package/dist/cjs/write.js +37 -0
  15. package/dist/cjs/write.js.map +1 -0
  16. package/dist/esm/admin.js +86 -0
  17. package/dist/esm/admin.js.map +1 -0
  18. package/dist/esm/config.js +8 -0
  19. package/dist/esm/config.js.map +1 -0
  20. package/dist/esm/index.js +7 -0
  21. package/dist/esm/index.js.map +1 -0
  22. package/dist/esm/project.js +40 -0
  23. package/dist/esm/project.js.map +1 -0
  24. package/dist/esm/read.js +56 -0
  25. package/dist/esm/read.js.map +1 -0
  26. package/dist/esm/types.js +2 -0
  27. package/dist/esm/types.js.map +1 -0
  28. package/dist/esm/write.js +32 -0
  29. package/dist/esm/write.js.map +1 -0
  30. package/package.json +27 -16
  31. package/readme.md +1 -6
  32. package/src/admin.test.ts +116 -0
  33. package/src/config.test.ts +28 -0
  34. package/src/project.test.ts +63 -0
  35. package/src/read.test.ts +163 -0
  36. package/src/write.test.ts +52 -0
  37. package/types/admin.d.ts +17 -0
  38. package/types/admin.d.ts.map +1 -0
  39. package/types/config.d.ts +6 -0
  40. package/types/config.d.ts.map +1 -0
  41. package/types/index.d.ts +7 -0
  42. package/types/index.d.ts.map +1 -0
  43. package/types/project.d.ts +8 -0
  44. package/types/project.d.ts.map +1 -0
  45. package/types/read.d.ts +15 -0
  46. package/types/read.d.ts.map +1 -0
  47. package/types/types.d.ts +27 -0
  48. package/types/types.d.ts.map +1 -0
  49. package/types/write.d.ts +12 -0
  50. package/types/write.d.ts.map +1 -0
  51. package/dist/admin.js +0 -103
  52. package/dist/config.js +0 -18
  53. package/dist/index.js +0 -233
  54. package/dist/project.js +0 -51
  55. package/dist/read.js +0 -104
  56. package/dist/types.js +0 -0
  57. package/dist/write.js +0 -80
package/dist/admin.js DELETED
@@ -1,103 +0,0 @@
1
- import { createRequire } from "node:module";
2
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
-
4
- // src/config.ts
5
- var DEFAULT_BASE_URL = "https://base.hanzo.ai";
6
- var RECIPES_COLLECTION = "gui_recipes";
7
- var DEFAULT_INSTALL_DIR = "src/gui";
8
- var DEFAULT_CONFIG_FILENAMES = ["gui.config.json", ".guirc.json"];
9
- function resolveBaseUrl(override) {
10
- return override ?? process.env.HANZO_BASE_URL ?? DEFAULT_BASE_URL;
11
- }
12
-
13
- // src/admin.ts
14
- async function authAdmin({
15
- baseUrl,
16
- email,
17
- password
18
- }) {
19
- const url = `${resolveBaseUrl(baseUrl)}/v1/base/admins/auth-with-password`;
20
- const res = await fetch(url, {
21
- method: "POST",
22
- headers: { "Content-Type": "application/json" },
23
- body: JSON.stringify({ identity: email, password })
24
- });
25
- if (!res.ok) {
26
- throw new Error(`Admin auth failed: ${res.status} ${await res.text()}`);
27
- }
28
- const data = await res.json();
29
- return data.token;
30
- }
31
- async function ensureCollection(auth) {
32
- const base = resolveBaseUrl(auth.baseUrl);
33
- const headers = { Authorization: auth.token, "Content-Type": "application/json" };
34
- const probe = await fetch(`${base}/v1/base/collections/${RECIPES_COLLECTION}`, {
35
- headers
36
- });
37
- if (probe.ok)
38
- return "existed";
39
- const schema = {
40
- name: RECIPES_COLLECTION,
41
- type: "base",
42
- schema: [
43
- { name: "slug", type: "text", required: true, unique: true, options: {} },
44
- { name: "name", type: "text", required: true, options: {} },
45
- { name: "description", type: "text", required: false, options: {} },
46
- { name: "category", type: "text", required: false, options: {} },
47
- { name: "npmDeps", type: "json", required: false, options: {} },
48
- { name: "recipeDeps", type: "json", required: false, options: {} },
49
- { name: "files", type: "json", required: true, options: {} }
50
- ],
51
- indexes: [
52
- `CREATE UNIQUE INDEX idx_${RECIPES_COLLECTION}_slug ON ${RECIPES_COLLECTION} (slug)`
53
- ],
54
- listRule: "",
55
- viewRule: "",
56
- createRule: null,
57
- updateRule: null,
58
- deleteRule: null
59
- };
60
- const res = await fetch(`${base}/v1/base/collections`, {
61
- method: "POST",
62
- headers,
63
- body: JSON.stringify(schema)
64
- });
65
- if (!res.ok) {
66
- throw new Error(`Failed to create collection: ${res.status} ${await res.text()}`);
67
- }
68
- return "created";
69
- }
70
- async function findExisting(auth, slug) {
71
- const base = resolveBaseUrl(auth.baseUrl);
72
- const filter = encodeURIComponent(`slug="${slug}"`);
73
- const res = await fetch(`${base}/v1/base/collections/${RECIPES_COLLECTION}/records?perPage=1&filter=${filter}`, { headers: { Authorization: auth.token } });
74
- if (!res.ok)
75
- return null;
76
- const data = await res.json();
77
- return data.items[0]?.id ?? null;
78
- }
79
- async function upsertRecipe(auth, recipe) {
80
- const base = resolveBaseUrl(auth.baseUrl);
81
- const headers = { Authorization: auth.token, "Content-Type": "application/json" };
82
- const body = JSON.stringify(recipe);
83
- const existing = await findExisting(auth, recipe.slug);
84
- if (existing) {
85
- const res2 = await fetch(`${base}/v1/base/collections/${RECIPES_COLLECTION}/records/${existing}`, { method: "PATCH", headers, body });
86
- if (!res2.ok)
87
- throw new Error(`PATCH ${recipe.slug}: ${res2.status} ${await res2.text()}`);
88
- return "updated";
89
- }
90
- const res = await fetch(`${base}/v1/base/collections/${RECIPES_COLLECTION}/records`, {
91
- method: "POST",
92
- headers,
93
- body
94
- });
95
- if (!res.ok)
96
- throw new Error(`POST ${recipe.slug}: ${res.status} ${await res.text()}`);
97
- return "created";
98
- }
99
- export {
100
- upsertRecipe,
101
- ensureCollection,
102
- authAdmin
103
- };
package/dist/config.js DELETED
@@ -1,18 +0,0 @@
1
- import { createRequire } from "node:module";
2
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
-
4
- // src/config.ts
5
- var DEFAULT_BASE_URL = "https://base.hanzo.ai";
6
- var RECIPES_COLLECTION = "gui_recipes";
7
- var DEFAULT_INSTALL_DIR = "src/gui";
8
- var DEFAULT_CONFIG_FILENAMES = ["gui.config.json", ".guirc.json"];
9
- function resolveBaseUrl(override) {
10
- return override ?? process.env.HANZO_BASE_URL ?? DEFAULT_BASE_URL;
11
- }
12
- export {
13
- resolveBaseUrl,
14
- RECIPES_COLLECTION,
15
- DEFAULT_INSTALL_DIR,
16
- DEFAULT_CONFIG_FILENAMES,
17
- DEFAULT_BASE_URL
18
- };
package/dist/index.js DELETED
@@ -1,233 +0,0 @@
1
- import { createRequire } from "node:module";
2
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
-
4
- // src/config.ts
5
- var DEFAULT_BASE_URL = "https://base.hanzo.ai";
6
- var RECIPES_COLLECTION = "gui_recipes";
7
- var DEFAULT_INSTALL_DIR = "src/gui";
8
- var DEFAULT_CONFIG_FILENAMES = ["gui.config.json", ".guirc.json"];
9
- function resolveBaseUrl(override) {
10
- return override ?? process.env.HANZO_BASE_URL ?? DEFAULT_BASE_URL;
11
- }
12
-
13
- // src/project.ts
14
- import { existsSync, promises as fs } from "node:fs";
15
- import path from "node:path";
16
- async function findProjectRoot(start = process.cwd()) {
17
- let dir = start;
18
- while (dir !== path.parse(dir).root) {
19
- const pkgPath = path.join(dir, "package.json");
20
- if (existsSync(pkgPath)) {
21
- try {
22
- const pkg = JSON.parse(await fs.readFile(pkgPath, "utf8"));
23
- if (pkg.workspaces || existsSync(path.join(dir, "pnpm-workspace.yaml")))
24
- return dir;
25
- } catch {}
26
- }
27
- if (existsSync(path.join(dir, ".git")))
28
- return dir;
29
- dir = path.dirname(dir);
30
- }
31
- return start;
32
- }
33
- async function readConfig(root) {
34
- for (const name of DEFAULT_CONFIG_FILENAMES) {
35
- const p = path.join(root, name);
36
- if (existsSync(p)) {
37
- try {
38
- return JSON.parse(await fs.readFile(p, "utf8"));
39
- } catch {}
40
- }
41
- }
42
- return {};
43
- }
44
- function resolveInstallDir(root, config) {
45
- return path.join(root, config.installDir ?? DEFAULT_INSTALL_DIR);
46
- }
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
- // src/read.ts
78
- async function getJSON(url) {
79
- const res = await fetch(url, { headers: { Accept: "application/json" } });
80
- if (!res.ok) {
81
- throw Object.assign(new Error(`Base request failed: ${res.status} ${res.statusText}`), {
82
- status: res.status
83
- });
84
- }
85
- return await res.json();
86
- }
87
- function records(baseUrl) {
88
- return `${resolveBaseUrl(baseUrl)}/v1/base/collections/${RECIPES_COLLECTION}/records`;
89
- }
90
- async function listRecipes(opts = {}) {
91
- const url = `${records(opts.baseUrl)}?perPage=200&sort=name&fields=id,slug,name,description,category`;
92
- const data = await getJSON(url);
93
- return data.items;
94
- }
95
- async function getRecipe(slug, opts = {}) {
96
- const filter = encodeURIComponent(`slug="${slug}"`);
97
- const url = `${records(opts.baseUrl)}?perPage=1&filter=${filter}`;
98
- const data = await getJSON(url);
99
- const recipe = data.items[0];
100
- if (!recipe) {
101
- throw Object.assign(new Error(`Recipe not found: ${slug}`), { status: 404 });
102
- }
103
- return recipe;
104
- }
105
- async function resolveRecipe(slug, opts = {}) {
106
- const seen = new Map;
107
- const queue = [slug];
108
- while (queue.length) {
109
- const next = queue.shift();
110
- if (seen.has(next))
111
- continue;
112
- const recipe2 = await getRecipe(next, opts);
113
- seen.set(next, recipe2);
114
- for (const dep of recipe2.recipeDeps ?? []) {
115
- if (!seen.has(dep))
116
- queue.push(dep);
117
- }
118
- }
119
- const installDir = opts.installDir ?? await (async () => {
120
- const root = await findProjectRoot(opts.cwd);
121
- return resolveInstallDir(root, await readConfig(root));
122
- })();
123
- const recipe = seen.get(slug);
124
- const transitiveRecipes = Array.from(seen.values()).filter((r) => r.slug !== slug);
125
- const path3 = await import("node:path");
126
- const targetPaths = [recipe, ...transitiveRecipes].flatMap((r) => r.files.map((f) => path3.join(installDir, f.path)));
127
- return { recipe, transitiveRecipes, targetPaths };
128
- }
129
-
130
- // src/admin.ts
131
- async function authAdmin({
132
- baseUrl,
133
- email,
134
- password
135
- }) {
136
- const url = `${resolveBaseUrl(baseUrl)}/v1/base/admins/auth-with-password`;
137
- const res = await fetch(url, {
138
- method: "POST",
139
- headers: { "Content-Type": "application/json" },
140
- body: JSON.stringify({ identity: email, password })
141
- });
142
- if (!res.ok) {
143
- throw new Error(`Admin auth failed: ${res.status} ${await res.text()}`);
144
- }
145
- const data = await res.json();
146
- return data.token;
147
- }
148
- async function ensureCollection(auth) {
149
- const base = resolveBaseUrl(auth.baseUrl);
150
- const headers = { Authorization: auth.token, "Content-Type": "application/json" };
151
- const probe = await fetch(`${base}/v1/base/collections/${RECIPES_COLLECTION}`, {
152
- headers
153
- });
154
- if (probe.ok)
155
- return "existed";
156
- const schema = {
157
- name: RECIPES_COLLECTION,
158
- type: "base",
159
- schema: [
160
- { name: "slug", type: "text", required: true, unique: true, options: {} },
161
- { name: "name", type: "text", required: true, options: {} },
162
- { name: "description", type: "text", required: false, options: {} },
163
- { name: "category", type: "text", required: false, options: {} },
164
- { name: "npmDeps", type: "json", required: false, options: {} },
165
- { name: "recipeDeps", type: "json", required: false, options: {} },
166
- { name: "files", type: "json", required: true, options: {} }
167
- ],
168
- indexes: [
169
- `CREATE UNIQUE INDEX idx_${RECIPES_COLLECTION}_slug ON ${RECIPES_COLLECTION} (slug)`
170
- ],
171
- listRule: "",
172
- viewRule: "",
173
- createRule: null,
174
- updateRule: null,
175
- deleteRule: null
176
- };
177
- const res = await fetch(`${base}/v1/base/collections`, {
178
- method: "POST",
179
- headers,
180
- body: JSON.stringify(schema)
181
- });
182
- if (!res.ok) {
183
- throw new Error(`Failed to create collection: ${res.status} ${await res.text()}`);
184
- }
185
- return "created";
186
- }
187
- async function findExisting(auth, slug) {
188
- const base = resolveBaseUrl(auth.baseUrl);
189
- const filter = encodeURIComponent(`slug="${slug}"`);
190
- const res = await fetch(`${base}/v1/base/collections/${RECIPES_COLLECTION}/records?perPage=1&filter=${filter}`, { headers: { Authorization: auth.token } });
191
- if (!res.ok)
192
- return null;
193
- const data = await res.json();
194
- return data.items[0]?.id ?? null;
195
- }
196
- async function upsertRecipe(auth, recipe) {
197
- const base = resolveBaseUrl(auth.baseUrl);
198
- const headers = { Authorization: auth.token, "Content-Type": "application/json" };
199
- const body = JSON.stringify(recipe);
200
- const existing = await findExisting(auth, recipe.slug);
201
- if (existing) {
202
- const res2 = await fetch(`${base}/v1/base/collections/${RECIPES_COLLECTION}/records/${existing}`, { method: "PATCH", headers, body });
203
- if (!res2.ok)
204
- throw new Error(`PATCH ${recipe.slug}: ${res2.status} ${await res2.text()}`);
205
- return "updated";
206
- }
207
- const res = await fetch(`${base}/v1/base/collections/${RECIPES_COLLECTION}/records`, {
208
- method: "POST",
209
- headers,
210
- body
211
- });
212
- if (!res.ok)
213
- throw new Error(`POST ${recipe.slug}: ${res.status} ${await res.text()}`);
214
- return "created";
215
- }
216
- export {
217
- upsertRecipe,
218
- resolveRecipe,
219
- resolveInstallDir,
220
- resolveBaseUrl,
221
- readConfig,
222
- listRecipes,
223
- installRecipe,
224
- installPlan,
225
- getRecipe,
226
- findProjectRoot,
227
- ensureCollection,
228
- authAdmin,
229
- RECIPES_COLLECTION,
230
- DEFAULT_INSTALL_DIR,
231
- DEFAULT_CONFIG_FILENAMES,
232
- DEFAULT_BASE_URL
233
- };
package/dist/project.js DELETED
@@ -1,51 +0,0 @@
1
- import { createRequire } from "node:module";
2
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
-
4
- // src/config.ts
5
- var DEFAULT_BASE_URL = "https://base.hanzo.ai";
6
- var RECIPES_COLLECTION = "gui_recipes";
7
- var DEFAULT_INSTALL_DIR = "src/gui";
8
- var DEFAULT_CONFIG_FILENAMES = ["gui.config.json", ".guirc.json"];
9
- function resolveBaseUrl(override) {
10
- return override ?? process.env.HANZO_BASE_URL ?? DEFAULT_BASE_URL;
11
- }
12
-
13
- // src/project.ts
14
- import { existsSync, promises as fs } from "node:fs";
15
- import path from "node:path";
16
- async function findProjectRoot(start = process.cwd()) {
17
- let dir = start;
18
- while (dir !== path.parse(dir).root) {
19
- const pkgPath = path.join(dir, "package.json");
20
- if (existsSync(pkgPath)) {
21
- try {
22
- const pkg = JSON.parse(await fs.readFile(pkgPath, "utf8"));
23
- if (pkg.workspaces || existsSync(path.join(dir, "pnpm-workspace.yaml")))
24
- return dir;
25
- } catch {}
26
- }
27
- if (existsSync(path.join(dir, ".git")))
28
- return dir;
29
- dir = path.dirname(dir);
30
- }
31
- return start;
32
- }
33
- async function readConfig(root) {
34
- for (const name of DEFAULT_CONFIG_FILENAMES) {
35
- const p = path.join(root, name);
36
- if (existsSync(p)) {
37
- try {
38
- return JSON.parse(await fs.readFile(p, "utf8"));
39
- } catch {}
40
- }
41
- }
42
- return {};
43
- }
44
- function resolveInstallDir(root, config) {
45
- return path.join(root, config.installDir ?? DEFAULT_INSTALL_DIR);
46
- }
47
- export {
48
- resolveInstallDir,
49
- readConfig,
50
- findProjectRoot
51
- };
package/dist/read.js DELETED
@@ -1,104 +0,0 @@
1
- import { createRequire } from "node:module";
2
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
-
4
- // src/config.ts
5
- var DEFAULT_BASE_URL = "https://base.hanzo.ai";
6
- var RECIPES_COLLECTION = "gui_recipes";
7
- var DEFAULT_INSTALL_DIR = "src/gui";
8
- var DEFAULT_CONFIG_FILENAMES = ["gui.config.json", ".guirc.json"];
9
- function resolveBaseUrl(override) {
10
- return override ?? process.env.HANZO_BASE_URL ?? DEFAULT_BASE_URL;
11
- }
12
-
13
- // src/project.ts
14
- import { existsSync, promises as fs } from "node:fs";
15
- import path from "node:path";
16
- async function findProjectRoot(start = process.cwd()) {
17
- let dir = start;
18
- while (dir !== path.parse(dir).root) {
19
- const pkgPath = path.join(dir, "package.json");
20
- if (existsSync(pkgPath)) {
21
- try {
22
- const pkg = JSON.parse(await fs.readFile(pkgPath, "utf8"));
23
- if (pkg.workspaces || existsSync(path.join(dir, "pnpm-workspace.yaml")))
24
- return dir;
25
- } catch {}
26
- }
27
- if (existsSync(path.join(dir, ".git")))
28
- return dir;
29
- dir = path.dirname(dir);
30
- }
31
- return start;
32
- }
33
- async function readConfig(root) {
34
- for (const name of DEFAULT_CONFIG_FILENAMES) {
35
- const p = path.join(root, name);
36
- if (existsSync(p)) {
37
- try {
38
- return JSON.parse(await fs.readFile(p, "utf8"));
39
- } catch {}
40
- }
41
- }
42
- return {};
43
- }
44
- function resolveInstallDir(root, config) {
45
- return path.join(root, config.installDir ?? DEFAULT_INSTALL_DIR);
46
- }
47
-
48
- // src/read.ts
49
- async function getJSON(url) {
50
- const res = await fetch(url, { headers: { Accept: "application/json" } });
51
- if (!res.ok) {
52
- throw Object.assign(new Error(`Base request failed: ${res.status} ${res.statusText}`), {
53
- status: res.status
54
- });
55
- }
56
- return await res.json();
57
- }
58
- function records(baseUrl) {
59
- return `${resolveBaseUrl(baseUrl)}/v1/base/collections/${RECIPES_COLLECTION}/records`;
60
- }
61
- async function listRecipes(opts = {}) {
62
- const url = `${records(opts.baseUrl)}?perPage=200&sort=name&fields=id,slug,name,description,category`;
63
- const data = await getJSON(url);
64
- return data.items;
65
- }
66
- async function getRecipe(slug, opts = {}) {
67
- const filter = encodeURIComponent(`slug="${slug}"`);
68
- const url = `${records(opts.baseUrl)}?perPage=1&filter=${filter}`;
69
- const data = await getJSON(url);
70
- const recipe = data.items[0];
71
- if (!recipe) {
72
- throw Object.assign(new Error(`Recipe not found: ${slug}`), { status: 404 });
73
- }
74
- return recipe;
75
- }
76
- async function resolveRecipe(slug, opts = {}) {
77
- const seen = new Map;
78
- const queue = [slug];
79
- while (queue.length) {
80
- const next = queue.shift();
81
- if (seen.has(next))
82
- continue;
83
- const recipe2 = await getRecipe(next, opts);
84
- seen.set(next, recipe2);
85
- for (const dep of recipe2.recipeDeps ?? []) {
86
- if (!seen.has(dep))
87
- queue.push(dep);
88
- }
89
- }
90
- const installDir = opts.installDir ?? await (async () => {
91
- const root = await findProjectRoot(opts.cwd);
92
- return resolveInstallDir(root, await readConfig(root));
93
- })();
94
- const recipe = seen.get(slug);
95
- const transitiveRecipes = Array.from(seen.values()).filter((r) => r.slug !== slug);
96
- const path2 = await import("node:path");
97
- const targetPaths = [recipe, ...transitiveRecipes].flatMap((r) => r.files.map((f) => path2.join(installDir, f.path)));
98
- return { recipe, transitiveRecipes, targetPaths };
99
- }
100
- export {
101
- resolveRecipe,
102
- listRecipes,
103
- getRecipe
104
- };
package/dist/types.js DELETED
File without changes
package/dist/write.js DELETED
@@ -1,80 +0,0 @@
1
- import { createRequire } from "node:module";
2
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
-
4
- // src/config.ts
5
- var DEFAULT_BASE_URL = "https://base.hanzo.ai";
6
- var RECIPES_COLLECTION = "gui_recipes";
7
- var DEFAULT_INSTALL_DIR = "src/gui";
8
- var DEFAULT_CONFIG_FILENAMES = ["gui.config.json", ".guirc.json"];
9
- function resolveBaseUrl(override) {
10
- return override ?? process.env.HANZO_BASE_URL ?? DEFAULT_BASE_URL;
11
- }
12
-
13
- // src/project.ts
14
- import { existsSync, promises as fs } from "node:fs";
15
- import path from "node:path";
16
- async function findProjectRoot(start = process.cwd()) {
17
- let dir = start;
18
- while (dir !== path.parse(dir).root) {
19
- const pkgPath = path.join(dir, "package.json");
20
- if (existsSync(pkgPath)) {
21
- try {
22
- const pkg = JSON.parse(await fs.readFile(pkgPath, "utf8"));
23
- if (pkg.workspaces || existsSync(path.join(dir, "pnpm-workspace.yaml")))
24
- return dir;
25
- } catch {}
26
- }
27
- if (existsSync(path.join(dir, ".git")))
28
- return dir;
29
- dir = path.dirname(dir);
30
- }
31
- return start;
32
- }
33
- async function readConfig(root) {
34
- for (const name of DEFAULT_CONFIG_FILENAMES) {
35
- const p = path.join(root, name);
36
- if (existsSync(p)) {
37
- try {
38
- return JSON.parse(await fs.readFile(p, "utf8"));
39
- } catch {}
40
- }
41
- }
42
- return {};
43
- }
44
- function resolveInstallDir(root, config) {
45
- return path.join(root, config.installDir ?? DEFAULT_INSTALL_DIR);
46
- }
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
- export {
78
- installRecipe,
79
- installPlan
80
- };