next-anteater 0.1.0 → 0.1.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.
- package/bin/cli.mjs +12 -0
- package/bin/uninstall-anteater.mjs +7 -0
- package/lib/scaffold.mjs +97 -0
- package/lib/uninstall.mjs +112 -0
- package/package.json +4 -2
package/bin/cli.mjs
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const command = process.argv[2];
|
|
4
|
+
|
|
5
|
+
if (command === "uninstall") {
|
|
6
|
+
const { main } = await import("../lib/uninstall.mjs");
|
|
7
|
+
await main();
|
|
8
|
+
} else {
|
|
9
|
+
// Default to setup (handles "setup" arg or no arg)
|
|
10
|
+
const { main } = await import("../lib/setup.mjs");
|
|
11
|
+
await main();
|
|
12
|
+
}
|
package/lib/scaffold.mjs
CHANGED
|
@@ -432,6 +432,95 @@ jobs:
|
|
|
432
432
|
`;
|
|
433
433
|
}
|
|
434
434
|
|
|
435
|
+
/**
|
|
436
|
+
* Generate the /api/anteater/runs route handler for multi-run discovery.
|
|
437
|
+
*/
|
|
438
|
+
export function generateRunsRoute({ isTypeScript }) {
|
|
439
|
+
const ext = isTypeScript ? "ts" : "js";
|
|
440
|
+
const TS = isTypeScript;
|
|
441
|
+
const lines = [];
|
|
442
|
+
const add = (s) => lines.push(s);
|
|
443
|
+
|
|
444
|
+
add('import { NextResponse } from "next/server";');
|
|
445
|
+
if (TS) add('import type { AnteaterRun, AnteaterRunsResponse } from "@anteater/next";');
|
|
446
|
+
add("");
|
|
447
|
+
add("function getRepo()" + (TS ? ": string | undefined" : "") + " {");
|
|
448
|
+
add(" if (process.env.ANTEATER_GITHUB_REPO) return process.env.ANTEATER_GITHUB_REPO;");
|
|
449
|
+
add(" const owner = process.env.VERCEL_GIT_REPO_OWNER;");
|
|
450
|
+
add(" const slug = process.env.VERCEL_GIT_REPO_SLUG;");
|
|
451
|
+
add(" if (owner && slug) return `${owner}/${slug}`;");
|
|
452
|
+
add(" return undefined;");
|
|
453
|
+
add("}");
|
|
454
|
+
add("");
|
|
455
|
+
add("export async function GET() {");
|
|
456
|
+
add(" const repo = getRepo();");
|
|
457
|
+
add(" const token = process.env.GITHUB_TOKEN;");
|
|
458
|
+
add(" if (!repo || !token) {");
|
|
459
|
+
add(" return NextResponse.json" + (TS ? "<AnteaterRunsResponse>" : "") + "({ runs: [], deploymentId: process.env.VERCEL_DEPLOYMENT_ID });");
|
|
460
|
+
add(" }");
|
|
461
|
+
add("");
|
|
462
|
+
add(" const gh = (url" + (TS ? ": string" : "") + ") =>");
|
|
463
|
+
add(" fetch(url, {");
|
|
464
|
+
add(" headers: {");
|
|
465
|
+
add(" Authorization: `Bearer ${token}`,");
|
|
466
|
+
add(' Accept: "application/vnd.github+json",');
|
|
467
|
+
add(' "X-GitHub-Api-Version": "2022-11-28",');
|
|
468
|
+
add(" },");
|
|
469
|
+
add(' cache: "no-store",');
|
|
470
|
+
add(" });");
|
|
471
|
+
add("");
|
|
472
|
+
add(" try {");
|
|
473
|
+
add(" const owner = repo.split(\"/\")[0];");
|
|
474
|
+
add(" const refsRes = await gh(`https://api.github.com/repos/${repo}/git/matching-refs/heads/anteater/`);");
|
|
475
|
+
add(" const refs" + (TS ? ": Array<{ ref: string }>" : "") + " = refsRes.ok ? await refsRes.json() : [];");
|
|
476
|
+
add("");
|
|
477
|
+
add(" const prsRes = await gh(`https://api.github.com/repos/${repo}/pulls?state=all&per_page=20&sort=created&direction=desc`);");
|
|
478
|
+
add(" const allPrs" + (TS ? ": any[]" : "") + " = prsRes.ok ? await prsRes.json() : [];");
|
|
479
|
+
add(" const anteaterPrs = allPrs.filter((pr" + (TS ? ": any" : "") + ") => pr.head.ref.startsWith(\"anteater/\"));");
|
|
480
|
+
add(" const prByBranch = new Map(anteaterPrs.map((pr" + (TS ? ": any" : "") + ") => [pr.head.ref, pr]));");
|
|
481
|
+
add("");
|
|
482
|
+
add(" const runs" + (TS ? ": AnteaterRun[]" : "") + " = [];");
|
|
483
|
+
add(" const branchNames = refs.map((r) => r.ref.replace(\"refs/heads/\", \"\"));");
|
|
484
|
+
add(" for (const pr of anteaterPrs) {");
|
|
485
|
+
add(" if (!branchNames.includes(pr.head.ref)) branchNames.push(pr.head.ref);");
|
|
486
|
+
add(" }");
|
|
487
|
+
add("");
|
|
488
|
+
add(" for (const branch of branchNames) {");
|
|
489
|
+
add(" const pr = prByBranch.get(branch);");
|
|
490
|
+
add(" const parts = branch.split(\"-\");");
|
|
491
|
+
add(" const requestId = parts[parts.length - 1] || \"\";");
|
|
492
|
+
add(" const mode = branch.includes(\"friend-\") ? \"copy\" : \"prod\";");
|
|
493
|
+
add(" let step" + (TS ? ": string" : "") + ";");
|
|
494
|
+
add("");
|
|
495
|
+
add(" if (pr?.merged_at) {");
|
|
496
|
+
add(" const mergedAgo = Date.now() - new Date(pr.merged_at).getTime();");
|
|
497
|
+
add(" if (mergedAgo > 300000) continue;");
|
|
498
|
+
add(" step = mergedAgo > 150000 ? \"done\" : \"redeploying\";");
|
|
499
|
+
add(" } else if (pr?.state === \"closed\") {");
|
|
500
|
+
add(" continue;");
|
|
501
|
+
add(" } else if (pr) {");
|
|
502
|
+
add(" step = \"merging\";");
|
|
503
|
+
add(" } else {");
|
|
504
|
+
add(" step = \"working\";");
|
|
505
|
+
add(" }");
|
|
506
|
+
add("");
|
|
507
|
+
add(" const prompt = pr?.title?.replace(/^anteater:\\\\s*/i, \"\") || \"Starting...\";");
|
|
508
|
+
add(" runs.push({ branch, requestId, prompt, step, mode });");
|
|
509
|
+
add(" if (runs.length >= 5) break;");
|
|
510
|
+
add(" }");
|
|
511
|
+
add("");
|
|
512
|
+
add(" return NextResponse.json" + (TS ? "<AnteaterRunsResponse>" : "") + "({ runs, deploymentId: process.env.VERCEL_DEPLOYMENT_ID });");
|
|
513
|
+
add(" } catch {");
|
|
514
|
+
add(" return NextResponse.json" + (TS ? "<AnteaterRunsResponse>" : "") + "({ runs: [], deploymentId: process.env.VERCEL_DEPLOYMENT_ID });");
|
|
515
|
+
add(" }");
|
|
516
|
+
add("}");
|
|
517
|
+
|
|
518
|
+
return {
|
|
519
|
+
filename: `route.${ext}`,
|
|
520
|
+
content: lines.join("\n") + "\n",
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
|
|
435
524
|
/**
|
|
436
525
|
* Generate the AI apply script.
|
|
437
526
|
*/
|
|
@@ -606,6 +695,14 @@ export async function scaffoldFiles(cwd, options) {
|
|
|
606
695
|
results.push(join(routeDir, route.filename));
|
|
607
696
|
}
|
|
608
697
|
|
|
698
|
+
// Runs API route (multi-run discovery)
|
|
699
|
+
const runsRoute = generateRunsRoute(options);
|
|
700
|
+
const runsDir = options.isAppRouter ? "app/api/anteater/runs" : "pages/api/anteater/runs";
|
|
701
|
+
const runsPath = join(cwd, runsDir, runsRoute.filename);
|
|
702
|
+
if (await writeIfNotExists(runsPath, runsRoute.content)) {
|
|
703
|
+
results.push(join(runsDir, runsRoute.filename));
|
|
704
|
+
}
|
|
705
|
+
|
|
609
706
|
// GitHub Action workflow
|
|
610
707
|
const workflowPath = join(cwd, ".github/workflows/anteater.yml");
|
|
611
708
|
if (await writeIfNotExists(workflowPath, generateWorkflow(options))) {
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* anteater uninstall — Removes all scaffolded Anteater files from the project.
|
|
3
|
+
*/
|
|
4
|
+
import { readFile, writeFile, rm, access } from "node:fs/promises";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { bold, green, red, dim, ok, fail, heading, blank } from "./ui.mjs";
|
|
7
|
+
|
|
8
|
+
const cwd = process.cwd();
|
|
9
|
+
|
|
10
|
+
async function fileExists(path) {
|
|
11
|
+
try {
|
|
12
|
+
await access(path);
|
|
13
|
+
return true;
|
|
14
|
+
} catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function removeFile(path, label) {
|
|
20
|
+
if (await fileExists(path)) {
|
|
21
|
+
await rm(path, { recursive: true });
|
|
22
|
+
ok(`Removed ${label}`);
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function unpatchLayout() {
|
|
29
|
+
for (const layoutFile of ["app/layout.tsx", "app/layout.js"]) {
|
|
30
|
+
const fullPath = join(cwd, layoutFile);
|
|
31
|
+
if (!(await fileExists(fullPath))) continue;
|
|
32
|
+
|
|
33
|
+
let content = await readFile(fullPath, "utf-8");
|
|
34
|
+
if (!content.includes("AnteaterBar")) return false;
|
|
35
|
+
|
|
36
|
+
// Remove import lines
|
|
37
|
+
content = content.replace(/^.*AnteaterBar.*\n/gm, "");
|
|
38
|
+
// Remove component usage
|
|
39
|
+
content = content.replace(/^\s*<AnteaterBar[^]*?\/>\s*\n?/gm, "");
|
|
40
|
+
content = content.replace(/^\s*<AnteaterBarWrapper\s*\/>\s*\n?/gm, "");
|
|
41
|
+
|
|
42
|
+
await writeFile(fullPath, content, "utf-8");
|
|
43
|
+
ok(`Unpatched ${layoutFile}`);
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function removeDependency() {
|
|
50
|
+
const pkgPath = join(cwd, "package.json");
|
|
51
|
+
if (!(await fileExists(pkgPath))) return false;
|
|
52
|
+
|
|
53
|
+
const pkg = JSON.parse(await readFile(pkgPath, "utf-8"));
|
|
54
|
+
let changed = false;
|
|
55
|
+
|
|
56
|
+
if (pkg.dependencies?.["@anteater/next"]) {
|
|
57
|
+
delete pkg.dependencies["@anteater/next"];
|
|
58
|
+
changed = true;
|
|
59
|
+
}
|
|
60
|
+
if (pkg.devDependencies?.["@anteater/next"]) {
|
|
61
|
+
delete pkg.devDependencies["@anteater/next"];
|
|
62
|
+
changed = true;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (changed) {
|
|
66
|
+
await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
67
|
+
ok("Removed @anteater/next from package.json");
|
|
68
|
+
}
|
|
69
|
+
return changed;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function main() {
|
|
73
|
+
console.log();
|
|
74
|
+
console.log(` ${bold("\u{1F41C} Anteater Uninstall")}`);
|
|
75
|
+
console.log(` ${"\u2500".repeat(20)}`);
|
|
76
|
+
blank();
|
|
77
|
+
|
|
78
|
+
heading("Removing scaffolded files");
|
|
79
|
+
|
|
80
|
+
let removed = 0;
|
|
81
|
+
|
|
82
|
+
// Config file
|
|
83
|
+
if (await removeFile(join(cwd, "anteater.config.ts"), "anteater.config.ts")) removed++;
|
|
84
|
+
if (await removeFile(join(cwd, "anteater.config.js"), "anteater.config.js")) removed++;
|
|
85
|
+
|
|
86
|
+
// API routes
|
|
87
|
+
if (await removeFile(join(cwd, "app/api/anteater"), "app/api/anteater/")) removed++;
|
|
88
|
+
if (await removeFile(join(cwd, "pages/api/anteater"), "pages/api/anteater/")) removed++;
|
|
89
|
+
|
|
90
|
+
// Wrapper component
|
|
91
|
+
if (await removeFile(join(cwd, "components/anteater-bar-wrapper.tsx"), "components/anteater-bar-wrapper.tsx")) removed++;
|
|
92
|
+
if (await removeFile(join(cwd, "components/anteater-bar-wrapper.js"), "components/anteater-bar-wrapper.js")) removed++;
|
|
93
|
+
|
|
94
|
+
// GitHub workflow
|
|
95
|
+
if (await removeFile(join(cwd, ".github/workflows/anteater.yml"), ".github/workflows/anteater.yml")) removed++;
|
|
96
|
+
|
|
97
|
+
// Unpatch layout
|
|
98
|
+
heading("Cleaning up layout");
|
|
99
|
+
await unpatchLayout();
|
|
100
|
+
|
|
101
|
+
// Remove dependency
|
|
102
|
+
heading("Removing dependency");
|
|
103
|
+
await removeDependency();
|
|
104
|
+
|
|
105
|
+
blank();
|
|
106
|
+
if (removed > 0) {
|
|
107
|
+
console.log(` ${green("\u2713")} Anteater uninstalled. Run ${dim("npx next-anteater setup")} to reinstall.`);
|
|
108
|
+
} else {
|
|
109
|
+
console.log(` ${dim("No Anteater files found — nothing to remove.")}`);
|
|
110
|
+
}
|
|
111
|
+
console.log();
|
|
112
|
+
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "next-anteater",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "AI-powered live editing for your Next.js app",
|
|
5
5
|
"bin": {
|
|
6
|
-
"anteater": "
|
|
6
|
+
"anteater": "bin/setup-anteater.mjs",
|
|
7
|
+
"anteater-uninstall": "bin/uninstall-anteater.mjs",
|
|
8
|
+
"next-anteater": "bin/cli.mjs"
|
|
7
9
|
},
|
|
8
10
|
"type": "module",
|
|
9
11
|
"files": ["bin/", "lib/"],
|