clawchef 0.1.3 → 0.1.4
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/README.md +7 -0
- package/dist/recipe.js +130 -31
- package/package.json +1 -1
- package/src/recipe.ts +169 -28
package/README.md
CHANGED
|
@@ -39,6 +39,12 @@ Run recipe from URL:
|
|
|
39
39
|
clawchef cook https://example.com/recipes/sample.yaml --provider remote
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
+
Run recipe from GitHub repository root (`recipe.yaml` at repo root):
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
clawchef cook https://github.com/renorzr/loom-plus-recipe
|
|
46
|
+
```
|
|
47
|
+
|
|
42
48
|
Run recipe from archive (default `recipe.yaml`):
|
|
43
49
|
|
|
44
50
|
```bash
|
|
@@ -219,6 +225,7 @@ If `.env` exists in the directory where `clawchef` is executed, it is loaded bef
|
|
|
219
225
|
- `https://host/recipe.yaml`
|
|
220
226
|
- `https://host/archive.zip` (loads `recipe.yaml` from archive)
|
|
221
227
|
- `https://host/archive.zip:custom/recipe.yaml`
|
|
228
|
+
- `https://github.com/<owner>/<repo>` (loads `recipe.yaml` from repo root)
|
|
222
229
|
|
|
223
230
|
Supported archives: `.zip`, `.tar`, `.tar.gz`, `.tgz`.
|
|
224
231
|
|
package/dist/recipe.js
CHANGED
|
@@ -2,7 +2,7 @@ import path from "node:path";
|
|
|
2
2
|
import process from "node:process";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
|
-
import { readFile, mkdtemp, stat, writeFile, rm, mkdir } from "node:fs/promises";
|
|
5
|
+
import { readFile, mkdtemp, stat, writeFile, rm, mkdir, readdir } from "node:fs/promises";
|
|
6
6
|
import YAML from "js-yaml";
|
|
7
7
|
import { recipeSchema } from "./schema.js";
|
|
8
8
|
import { ClawChefError } from "./errors.js";
|
|
@@ -245,6 +245,128 @@ function parseUrlReference(input) {
|
|
|
245
245
|
directUrl: parsed.toString(),
|
|
246
246
|
};
|
|
247
247
|
}
|
|
248
|
+
function parseGitHubRepoReference(input) {
|
|
249
|
+
let parsed;
|
|
250
|
+
try {
|
|
251
|
+
parsed = new URL(input);
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
return undefined;
|
|
255
|
+
}
|
|
256
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
257
|
+
return undefined;
|
|
258
|
+
}
|
|
259
|
+
if (parsed.hostname.toLowerCase() !== "github.com") {
|
|
260
|
+
return undefined;
|
|
261
|
+
}
|
|
262
|
+
if (parsed.search || parsed.hash) {
|
|
263
|
+
return undefined;
|
|
264
|
+
}
|
|
265
|
+
const parts = parsed.pathname.split("/").filter((part) => part.length > 0);
|
|
266
|
+
if (parts.length !== 2) {
|
|
267
|
+
return undefined;
|
|
268
|
+
}
|
|
269
|
+
const owner = parts[0];
|
|
270
|
+
const repo = parts[1].endsWith(".git") ? parts[1].slice(0, -4) : parts[1];
|
|
271
|
+
if (!owner || !repo) {
|
|
272
|
+
return undefined;
|
|
273
|
+
}
|
|
274
|
+
return { owner, repo };
|
|
275
|
+
}
|
|
276
|
+
async function resolveRecipePathFromExtracted(extractDir, recipeInArchive, missingMessage) {
|
|
277
|
+
const directPath = path.join(extractDir, recipeInArchive);
|
|
278
|
+
try {
|
|
279
|
+
const directStat = await stat(directPath);
|
|
280
|
+
if (directStat.isFile()) {
|
|
281
|
+
return directPath;
|
|
282
|
+
}
|
|
283
|
+
throw new ClawChefError(`Recipe in archive is not a file: ${recipeInArchive}`);
|
|
284
|
+
}
|
|
285
|
+
catch (err) {
|
|
286
|
+
if (err instanceof ClawChefError) {
|
|
287
|
+
throw err;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
const entries = await readdir(extractDir, { withFileTypes: true });
|
|
291
|
+
const matched = [];
|
|
292
|
+
for (const entry of entries) {
|
|
293
|
+
if (!entry.isDirectory()) {
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
const candidate = path.join(extractDir, entry.name, recipeInArchive);
|
|
297
|
+
try {
|
|
298
|
+
const s = await stat(candidate);
|
|
299
|
+
if (s.isFile()) {
|
|
300
|
+
matched.push(candidate);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (matched.length === 1) {
|
|
308
|
+
return matched[0];
|
|
309
|
+
}
|
|
310
|
+
if (matched.length > 1) {
|
|
311
|
+
throw new ClawChefError(`Multiple recipe files found in extracted archive for ${recipeInArchive}; provide explicit selector`);
|
|
312
|
+
}
|
|
313
|
+
throw new ClawChefError(missingMessage);
|
|
314
|
+
}
|
|
315
|
+
async function resolveGitHubRepoReference(recipeInput) {
|
|
316
|
+
const repoRef = parseGitHubRepoReference(recipeInput);
|
|
317
|
+
if (!repoRef) {
|
|
318
|
+
return undefined;
|
|
319
|
+
}
|
|
320
|
+
const repoApiUrl = `https://api.github.com/repos/${repoRef.owner}/${repoRef.repo}`;
|
|
321
|
+
const repoHeaders = {
|
|
322
|
+
Accept: "application/vnd.github+json",
|
|
323
|
+
"User-Agent": "clawchef",
|
|
324
|
+
};
|
|
325
|
+
let repoResponse;
|
|
326
|
+
try {
|
|
327
|
+
repoResponse = await fetch(repoApiUrl, { headers: repoHeaders });
|
|
328
|
+
}
|
|
329
|
+
catch (err) {
|
|
330
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
331
|
+
throw new ClawChefError(`Failed to fetch GitHub repository metadata ${repoApiUrl}: ${message}`);
|
|
332
|
+
}
|
|
333
|
+
if (!repoResponse.ok) {
|
|
334
|
+
throw new ClawChefError(`Failed to fetch GitHub repository metadata ${repoApiUrl}: HTTP ${repoResponse.status}`);
|
|
335
|
+
}
|
|
336
|
+
const repoJson = (await repoResponse.json());
|
|
337
|
+
const defaultBranch = repoJson.default_branch?.trim();
|
|
338
|
+
if (!defaultBranch) {
|
|
339
|
+
throw new ClawChefError(`GitHub repository ${repoRef.owner}/${repoRef.repo} has no default branch`);
|
|
340
|
+
}
|
|
341
|
+
const tarballUrl = `https://api.github.com/repos/${repoRef.owner}/${repoRef.repo}/tarball/${encodeURIComponent(defaultBranch)}`;
|
|
342
|
+
let tarballResponse;
|
|
343
|
+
try {
|
|
344
|
+
tarballResponse = await fetch(tarballUrl, { headers: repoHeaders });
|
|
345
|
+
}
|
|
346
|
+
catch (err) {
|
|
347
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
348
|
+
throw new ClawChefError(`Failed to download GitHub repository tarball ${tarballUrl}: ${message}`);
|
|
349
|
+
}
|
|
350
|
+
if (!tarballResponse.ok) {
|
|
351
|
+
throw new ClawChefError(`Failed to download GitHub repository tarball ${tarballUrl}: HTTP ${tarballResponse.status}`);
|
|
352
|
+
}
|
|
353
|
+
const tempDir = await mkdtemp(path.join(tmpdir(), "clawchef-recipe-"));
|
|
354
|
+
const archivePath = path.join(tempDir, "repo.tar.gz");
|
|
355
|
+
await writeFile(archivePath, Buffer.from(await tarballResponse.arrayBuffer()));
|
|
356
|
+
const extractDir = path.join(tempDir, "extracted");
|
|
357
|
+
await mkdir(extractDir, { recursive: true });
|
|
358
|
+
await extractArchive(archivePath, extractDir);
|
|
359
|
+
const recipePath = await resolveRecipePathFromExtracted(extractDir, DEFAULT_RECIPE_FILE, `Recipe file not found in GitHub repository root: ${DEFAULT_RECIPE_FILE}`);
|
|
360
|
+
return {
|
|
361
|
+
recipePath,
|
|
362
|
+
origin: {
|
|
363
|
+
kind: "local",
|
|
364
|
+
recipePath,
|
|
365
|
+
recipeDir: path.dirname(recipePath),
|
|
366
|
+
},
|
|
367
|
+
cleanupPaths: [tempDir],
|
|
368
|
+
};
|
|
369
|
+
}
|
|
248
370
|
async function runCommand(command, args) {
|
|
249
371
|
await new Promise((resolve, reject) => {
|
|
250
372
|
const child = spawn(command, args, {
|
|
@@ -287,6 +409,10 @@ async function extractArchive(archivePath, extractDir) {
|
|
|
287
409
|
}
|
|
288
410
|
async function resolveRecipeReference(recipeInput) {
|
|
289
411
|
if (isHttpUrl(recipeInput)) {
|
|
412
|
+
const githubResolved = await resolveGitHubRepoReference(recipeInput);
|
|
413
|
+
if (githubResolved) {
|
|
414
|
+
return githubResolved;
|
|
415
|
+
}
|
|
290
416
|
const parsed = parseUrlReference(recipeInput);
|
|
291
417
|
if (parsed.directUrl) {
|
|
292
418
|
let response;
|
|
@@ -334,16 +460,7 @@ async function resolveRecipeReference(recipeInput) {
|
|
|
334
460
|
await rm(extractDir, { recursive: true, force: true });
|
|
335
461
|
await mkdir(extractDir, { recursive: true });
|
|
336
462
|
await extractArchive(downloadedArchivePath, extractDir);
|
|
337
|
-
const recipePath =
|
|
338
|
-
try {
|
|
339
|
-
const s = await stat(recipePath);
|
|
340
|
-
if (!s.isFile()) {
|
|
341
|
-
throw new ClawChefError(`Recipe in archive is not a file: ${recipeInArchive}`);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
catch {
|
|
345
|
-
throw new ClawChefError(`Recipe file not found in archive: ${recipeInArchive}`);
|
|
346
|
-
}
|
|
463
|
+
const recipePath = await resolveRecipePathFromExtracted(extractDir, recipeInArchive, `Recipe file not found in archive: ${recipeInArchive}`);
|
|
347
464
|
return {
|
|
348
465
|
recipePath,
|
|
349
466
|
origin: {
|
|
@@ -381,16 +498,7 @@ async function resolveRecipeReference(recipeInput) {
|
|
|
381
498
|
const extractDir = path.join(tempDir, "extracted");
|
|
382
499
|
await mkdir(extractDir, { recursive: true });
|
|
383
500
|
await extractArchive(localPath, extractDir);
|
|
384
|
-
const recipePath =
|
|
385
|
-
try {
|
|
386
|
-
const s = await stat(recipePath);
|
|
387
|
-
if (!s.isFile()) {
|
|
388
|
-
throw new ClawChefError(`Recipe in archive is not a file: ${DEFAULT_RECIPE_FILE}`);
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
catch {
|
|
392
|
-
throw new ClawChefError(`Recipe file not found in archive: ${DEFAULT_RECIPE_FILE}`);
|
|
393
|
-
}
|
|
501
|
+
const recipePath = await resolveRecipePathFromExtracted(extractDir, DEFAULT_RECIPE_FILE, `Recipe file not found in archive: ${DEFAULT_RECIPE_FILE}`);
|
|
394
502
|
return {
|
|
395
503
|
recipePath,
|
|
396
504
|
origin: {
|
|
@@ -445,16 +553,7 @@ async function resolveRecipeReference(recipeInput) {
|
|
|
445
553
|
const extractDir = path.join(tempDir, "extracted");
|
|
446
554
|
await mkdir(extractDir, { recursive: true });
|
|
447
555
|
await extractArchive(basePath, extractDir);
|
|
448
|
-
const recipePath =
|
|
449
|
-
try {
|
|
450
|
-
const s = await stat(recipePath);
|
|
451
|
-
if (!s.isFile()) {
|
|
452
|
-
throw new ClawChefError(`Recipe in archive is not a file: ${selector}`);
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
catch {
|
|
456
|
-
throw new ClawChefError(`Recipe file not found in archive: ${selector}`);
|
|
457
|
-
}
|
|
556
|
+
const recipePath = await resolveRecipePathFromExtracted(extractDir, selector, `Recipe file not found in archive: ${selector}`);
|
|
458
557
|
return {
|
|
459
558
|
recipePath,
|
|
460
559
|
origin: {
|
package/package.json
CHANGED
package/src/recipe.ts
CHANGED
|
@@ -2,7 +2,7 @@ import path from "node:path";
|
|
|
2
2
|
import process from "node:process";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
|
-
import { readFile, mkdtemp, stat, writeFile, rm, mkdir } from "node:fs/promises";
|
|
5
|
+
import { readFile, mkdtemp, stat, writeFile, rm, mkdir, readdir } from "node:fs/promises";
|
|
6
6
|
import YAML from "js-yaml";
|
|
7
7
|
import { recipeSchema } from "./schema.js";
|
|
8
8
|
import { ClawChefError } from "./errors.js";
|
|
@@ -266,6 +266,11 @@ interface ResolvedRecipeReference {
|
|
|
266
266
|
cleanupPaths: string[];
|
|
267
267
|
}
|
|
268
268
|
|
|
269
|
+
interface GitHubRepoRef {
|
|
270
|
+
owner: string;
|
|
271
|
+
repo: string;
|
|
272
|
+
}
|
|
273
|
+
|
|
269
274
|
function isHttpUrl(value: string): boolean {
|
|
270
275
|
try {
|
|
271
276
|
const url = new URL(value);
|
|
@@ -323,6 +328,149 @@ function parseUrlReference(input: string): { archiveUrl: string; inner?: string;
|
|
|
323
328
|
};
|
|
324
329
|
}
|
|
325
330
|
|
|
331
|
+
function parseGitHubRepoReference(input: string): GitHubRepoRef | undefined {
|
|
332
|
+
let parsed: URL;
|
|
333
|
+
try {
|
|
334
|
+
parsed = new URL(input);
|
|
335
|
+
} catch {
|
|
336
|
+
return undefined;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
340
|
+
return undefined;
|
|
341
|
+
}
|
|
342
|
+
if (parsed.hostname.toLowerCase() !== "github.com") {
|
|
343
|
+
return undefined;
|
|
344
|
+
}
|
|
345
|
+
if (parsed.search || parsed.hash) {
|
|
346
|
+
return undefined;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const parts = parsed.pathname.split("/").filter((part) => part.length > 0);
|
|
350
|
+
if (parts.length !== 2) {
|
|
351
|
+
return undefined;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const owner = parts[0];
|
|
355
|
+
const repo = parts[1].endsWith(".git") ? parts[1].slice(0, -4) : parts[1];
|
|
356
|
+
if (!owner || !repo) {
|
|
357
|
+
return undefined;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return { owner, repo };
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
async function resolveRecipePathFromExtracted(
|
|
364
|
+
extractDir: string,
|
|
365
|
+
recipeInArchive: string,
|
|
366
|
+
missingMessage: string,
|
|
367
|
+
): Promise<string> {
|
|
368
|
+
const directPath = path.join(extractDir, recipeInArchive);
|
|
369
|
+
try {
|
|
370
|
+
const directStat = await stat(directPath);
|
|
371
|
+
if (directStat.isFile()) {
|
|
372
|
+
return directPath;
|
|
373
|
+
}
|
|
374
|
+
throw new ClawChefError(`Recipe in archive is not a file: ${recipeInArchive}`);
|
|
375
|
+
} catch (err) {
|
|
376
|
+
if (err instanceof ClawChefError) {
|
|
377
|
+
throw err;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const entries = await readdir(extractDir, { withFileTypes: true });
|
|
382
|
+
const matched: string[] = [];
|
|
383
|
+
for (const entry of entries) {
|
|
384
|
+
if (!entry.isDirectory()) {
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
const candidate = path.join(extractDir, entry.name, recipeInArchive);
|
|
388
|
+
try {
|
|
389
|
+
const s = await stat(candidate);
|
|
390
|
+
if (s.isFile()) {
|
|
391
|
+
matched.push(candidate);
|
|
392
|
+
}
|
|
393
|
+
} catch {
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (matched.length === 1) {
|
|
399
|
+
return matched[0];
|
|
400
|
+
}
|
|
401
|
+
if (matched.length > 1) {
|
|
402
|
+
throw new ClawChefError(`Multiple recipe files found in extracted archive for ${recipeInArchive}; provide explicit selector`);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
throw new ClawChefError(missingMessage);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
async function resolveGitHubRepoReference(recipeInput: string): Promise<ResolvedRecipeReference | undefined> {
|
|
409
|
+
const repoRef = parseGitHubRepoReference(recipeInput);
|
|
410
|
+
if (!repoRef) {
|
|
411
|
+
return undefined;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const repoApiUrl = `https://api.github.com/repos/${repoRef.owner}/${repoRef.repo}`;
|
|
415
|
+
const repoHeaders = {
|
|
416
|
+
Accept: "application/vnd.github+json",
|
|
417
|
+
"User-Agent": "clawchef",
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
let repoResponse: Response;
|
|
421
|
+
try {
|
|
422
|
+
repoResponse = await fetch(repoApiUrl, { headers: repoHeaders });
|
|
423
|
+
} catch (err) {
|
|
424
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
425
|
+
throw new ClawChefError(`Failed to fetch GitHub repository metadata ${repoApiUrl}: ${message}`);
|
|
426
|
+
}
|
|
427
|
+
if (!repoResponse.ok) {
|
|
428
|
+
throw new ClawChefError(`Failed to fetch GitHub repository metadata ${repoApiUrl}: HTTP ${repoResponse.status}`);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const repoJson = (await repoResponse.json()) as { default_branch?: string };
|
|
432
|
+
const defaultBranch = repoJson.default_branch?.trim();
|
|
433
|
+
if (!defaultBranch) {
|
|
434
|
+
throw new ClawChefError(`GitHub repository ${repoRef.owner}/${repoRef.repo} has no default branch`);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const tarballUrl = `https://api.github.com/repos/${repoRef.owner}/${repoRef.repo}/tarball/${encodeURIComponent(defaultBranch)}`;
|
|
438
|
+
let tarballResponse: Response;
|
|
439
|
+
try {
|
|
440
|
+
tarballResponse = await fetch(tarballUrl, { headers: repoHeaders });
|
|
441
|
+
} catch (err) {
|
|
442
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
443
|
+
throw new ClawChefError(`Failed to download GitHub repository tarball ${tarballUrl}: ${message}`);
|
|
444
|
+
}
|
|
445
|
+
if (!tarballResponse.ok) {
|
|
446
|
+
throw new ClawChefError(`Failed to download GitHub repository tarball ${tarballUrl}: HTTP ${tarballResponse.status}`);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const tempDir = await mkdtemp(path.join(tmpdir(), "clawchef-recipe-"));
|
|
450
|
+
const archivePath = path.join(tempDir, "repo.tar.gz");
|
|
451
|
+
await writeFile(archivePath, Buffer.from(await tarballResponse.arrayBuffer()));
|
|
452
|
+
|
|
453
|
+
const extractDir = path.join(tempDir, "extracted");
|
|
454
|
+
await mkdir(extractDir, { recursive: true });
|
|
455
|
+
await extractArchive(archivePath, extractDir);
|
|
456
|
+
|
|
457
|
+
const recipePath = await resolveRecipePathFromExtracted(
|
|
458
|
+
extractDir,
|
|
459
|
+
DEFAULT_RECIPE_FILE,
|
|
460
|
+
`Recipe file not found in GitHub repository root: ${DEFAULT_RECIPE_FILE}`,
|
|
461
|
+
);
|
|
462
|
+
|
|
463
|
+
return {
|
|
464
|
+
recipePath,
|
|
465
|
+
origin: {
|
|
466
|
+
kind: "local",
|
|
467
|
+
recipePath,
|
|
468
|
+
recipeDir: path.dirname(recipePath),
|
|
469
|
+
},
|
|
470
|
+
cleanupPaths: [tempDir],
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
|
|
326
474
|
async function runCommand(command: string, args: string[]): Promise<void> {
|
|
327
475
|
await new Promise<void>((resolve, reject) => {
|
|
328
476
|
const child = spawn(command, args, {
|
|
@@ -370,6 +518,11 @@ async function extractArchive(archivePath: string, extractDir: string): Promise<
|
|
|
370
518
|
|
|
371
519
|
async function resolveRecipeReference(recipeInput: string): Promise<ResolvedRecipeReference> {
|
|
372
520
|
if (isHttpUrl(recipeInput)) {
|
|
521
|
+
const githubResolved = await resolveGitHubRepoReference(recipeInput);
|
|
522
|
+
if (githubResolved) {
|
|
523
|
+
return githubResolved;
|
|
524
|
+
}
|
|
525
|
+
|
|
373
526
|
const parsed = parseUrlReference(recipeInput);
|
|
374
527
|
if (parsed.directUrl) {
|
|
375
528
|
let response: Response;
|
|
@@ -421,15 +574,11 @@ async function resolveRecipeReference(recipeInput: string): Promise<ResolvedReci
|
|
|
421
574
|
await mkdir(extractDir, { recursive: true });
|
|
422
575
|
await extractArchive(downloadedArchivePath, extractDir);
|
|
423
576
|
|
|
424
|
-
const recipePath =
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
}
|
|
430
|
-
} catch {
|
|
431
|
-
throw new ClawChefError(`Recipe file not found in archive: ${recipeInArchive}`);
|
|
432
|
-
}
|
|
577
|
+
const recipePath = await resolveRecipePathFromExtracted(
|
|
578
|
+
extractDir,
|
|
579
|
+
recipeInArchive,
|
|
580
|
+
`Recipe file not found in archive: ${recipeInArchive}`,
|
|
581
|
+
);
|
|
433
582
|
|
|
434
583
|
return {
|
|
435
584
|
recipePath,
|
|
@@ -470,15 +619,11 @@ async function resolveRecipeReference(recipeInput: string): Promise<ResolvedReci
|
|
|
470
619
|
const extractDir = path.join(tempDir, "extracted");
|
|
471
620
|
await mkdir(extractDir, { recursive: true });
|
|
472
621
|
await extractArchive(localPath, extractDir);
|
|
473
|
-
const recipePath =
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
}
|
|
479
|
-
} catch {
|
|
480
|
-
throw new ClawChefError(`Recipe file not found in archive: ${DEFAULT_RECIPE_FILE}`);
|
|
481
|
-
}
|
|
622
|
+
const recipePath = await resolveRecipePathFromExtracted(
|
|
623
|
+
extractDir,
|
|
624
|
+
DEFAULT_RECIPE_FILE,
|
|
625
|
+
`Recipe file not found in archive: ${DEFAULT_RECIPE_FILE}`,
|
|
626
|
+
);
|
|
482
627
|
return {
|
|
483
628
|
recipePath,
|
|
484
629
|
origin: {
|
|
@@ -538,15 +683,11 @@ async function resolveRecipeReference(recipeInput: string): Promise<ResolvedReci
|
|
|
538
683
|
const extractDir = path.join(tempDir, "extracted");
|
|
539
684
|
await mkdir(extractDir, { recursive: true });
|
|
540
685
|
await extractArchive(basePath, extractDir);
|
|
541
|
-
const recipePath =
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
}
|
|
547
|
-
} catch {
|
|
548
|
-
throw new ClawChefError(`Recipe file not found in archive: ${selector}`);
|
|
549
|
-
}
|
|
686
|
+
const recipePath = await resolveRecipePathFromExtracted(
|
|
687
|
+
extractDir,
|
|
688
|
+
selector,
|
|
689
|
+
`Recipe file not found in archive: ${selector}`,
|
|
690
|
+
);
|
|
550
691
|
return {
|
|
551
692
|
recipePath,
|
|
552
693
|
origin: {
|