@algolia/wizard 0.9.0-rc.82.72 → 0.9.0-rc.84.74
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/main.js +347 -1297
- package/docs/algolia-sdk/README.md +30 -53
- package/docs/algolia-sdk/search-single-index.md +42 -0
- package/package.json +2 -3
- package/docs/algolia-sdk/instantsearch-setup-templates.md +0 -92
- package/docs/algolia-sdk/save-records-csharp.md +0 -71
- package/docs/algolia-sdk/save-records-dart.md +0 -74
- package/docs/algolia-sdk/save-records-go.md +0 -62
- package/docs/algolia-sdk/save-records-java.md +0 -66
- package/docs/algolia-sdk/save-records-kotlin.md +0 -60
- package/docs/algolia-sdk/save-records-php.md +0 -50
- package/docs/algolia-sdk/save-records-python.md +0 -51
- package/docs/algolia-sdk/save-records-ruby.md +0 -48
- package/docs/algolia-sdk/save-records-scala.md +0 -68
- package/docs/algolia-sdk/save-records-swift.md +0 -88
package/dist/main.js
CHANGED
|
@@ -287,11 +287,13 @@ var useWizard = create((set, get) => ({
|
|
|
287
287
|
// may repeat or duplicate on-screen content and isn't the useful signal
|
|
288
288
|
// here.
|
|
289
289
|
submitInput: async (value) => {
|
|
290
|
-
|
|
291
|
-
|
|
290
|
+
const resolve4 = get()._resolve;
|
|
291
|
+
if (!resolve4) return;
|
|
292
292
|
set({ inputReq: null, _resolve: null, phase: "running" });
|
|
293
293
|
const id = get().logStart("prompt", `User input: ${describeInputValue(value)}`);
|
|
294
294
|
get().logEnd(id, "success");
|
|
295
|
+
await markInteraction();
|
|
296
|
+
resolve4(value);
|
|
295
297
|
},
|
|
296
298
|
setDone: () => set({ phase: "done" }),
|
|
297
299
|
setError: (message) => set({ phase: "error", error: message }),
|
|
@@ -892,12 +894,12 @@ var sidebarItems = [
|
|
|
892
894
|
description: "push 100 records to Algolia in seconds"
|
|
893
895
|
},
|
|
894
896
|
{
|
|
895
|
-
title: "detect your
|
|
896
|
-
description: "React, Vue, Angular,
|
|
897
|
+
title: "detect your framework",
|
|
898
|
+
description: "React, Vue, Angular, Vanilla JS"
|
|
897
899
|
},
|
|
898
900
|
{
|
|
899
901
|
title: "scaffold a search UI",
|
|
900
|
-
description: "a styled InstantSearch
|
|
902
|
+
description: "a styled InstantSearch component, wired into your app"
|
|
901
903
|
},
|
|
902
904
|
{
|
|
903
905
|
title: "ship it",
|
|
@@ -1003,7 +1005,7 @@ var accessItems = [
|
|
|
1003
1005
|
{
|
|
1004
1006
|
tag: "READ",
|
|
1005
1007
|
title: "Project files",
|
|
1006
|
-
description: "reads
|
|
1008
|
+
description: "reads package.json, configs & source to detect your stack. Read-only; nothing is uploaded."
|
|
1007
1009
|
},
|
|
1008
1010
|
{
|
|
1009
1011
|
tag: "WRITE",
|
|
@@ -2221,745 +2223,15 @@ function writeCredentialsTool(ctx) {
|
|
|
2221
2223
|
// src/lib/tools/searchFiles.ts
|
|
2222
2224
|
import { tool as tool7 } from "ai";
|
|
2223
2225
|
import z10 from "zod";
|
|
2224
|
-
import { readdir as
|
|
2225
|
-
import { join as join10 } from "node:path";
|
|
2226
|
-
|
|
2227
|
-
// src/lib/languages.ts
|
|
2228
|
-
import { readdir as readdir2, readFile as readFile7 } from "node:fs/promises";
|
|
2229
|
-
import { existsSync as existsSync2 } from "node:fs";
|
|
2230
|
-
import { join as join9 } from "node:path";
|
|
2231
|
-
|
|
2232
|
-
// src/lib/tools/utils/packageManager.ts
|
|
2233
|
-
import { readFile as readFile6 } from "node:fs/promises";
|
|
2234
|
-
import { existsSync } from "node:fs";
|
|
2226
|
+
import { readdir as readdir2, readFile as readFile6 } from "node:fs/promises";
|
|
2235
2227
|
import { join as join8 } from "node:path";
|
|
2236
|
-
var LOCKFILES = [
|
|
2237
|
-
["pnpm-lock.yaml", "pnpm"],
|
|
2238
|
-
["yarn.lock", "yarn"],
|
|
2239
|
-
["bun.lockb", "bun"],
|
|
2240
|
-
["bun.lock", "bun"],
|
|
2241
|
-
["package-lock.json", "npm"]
|
|
2242
|
-
];
|
|
2243
|
-
async function readPackageJson(cwd = process.cwd()) {
|
|
2244
|
-
return JSON.parse(await readFile6(join8(cwd, "package.json"), "utf8"));
|
|
2245
|
-
}
|
|
2246
|
-
function packageManagerFrom(pkg) {
|
|
2247
|
-
return pkg.packageManager?.split("@")[0] ?? "npm";
|
|
2248
|
-
}
|
|
2249
|
-
function packageManagerFromLockfile(cwd) {
|
|
2250
|
-
return LOCKFILES.find(([file]) => existsSync(join8(cwd, file)))?.[1];
|
|
2251
|
-
}
|
|
2252
|
-
async function detectPackageManager(cwd) {
|
|
2253
|
-
try {
|
|
2254
|
-
const pkg = await readPackageJson(cwd);
|
|
2255
|
-
if (pkg.packageManager) return packageManagerFrom(pkg);
|
|
2256
|
-
} catch {
|
|
2257
|
-
}
|
|
2258
|
-
return packageManagerFromLockfile(cwd) ?? "npm";
|
|
2259
|
-
}
|
|
2260
|
-
|
|
2261
|
-
// src/lib/shell.ts
|
|
2262
|
-
function shellQuote(value) {
|
|
2263
|
-
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
2264
|
-
}
|
|
2265
|
-
|
|
2266
|
-
// src/lib/languages.ts
|
|
2267
|
-
var ENTRYPOINT_TOKEN = "{entrypoint}";
|
|
2268
|
-
var INGEST_DIR = ".algolia-wizard";
|
|
2269
|
-
var SWIFT_PACKAGE_DIR = `${INGEST_DIR}/Ingest`;
|
|
2270
|
-
var CSHARP_PROJECT = `${INGEST_DIR}/ingest/ingest.csproj`;
|
|
2271
|
-
var VISIBLE_INGEST_DIR = "algolia-wizard";
|
|
2272
|
-
var PY_VENV = `${INGEST_DIR}/.venv`;
|
|
2273
|
-
var PY_VENV_PYTHON = `${PY_VENV}/bin/python`;
|
|
2274
|
-
var PY_REQUIREMENTS = `${INGEST_DIR}/requirements.txt`;
|
|
2275
|
-
var LANGUAGE_PROFILES = {
|
|
2276
|
-
javascript: {
|
|
2277
|
-
id: "javascript",
|
|
2278
|
-
displayName: "JavaScript/TypeScript",
|
|
2279
|
-
aliases: [
|
|
2280
|
-
"javascript",
|
|
2281
|
-
"js",
|
|
2282
|
-
"typescript",
|
|
2283
|
-
"ts",
|
|
2284
|
-
"node",
|
|
2285
|
-
"nodejs",
|
|
2286
|
-
"node.js",
|
|
2287
|
-
"bun",
|
|
2288
|
-
"deno",
|
|
2289
|
-
"ecmascript",
|
|
2290
|
-
"jsx",
|
|
2291
|
-
"tsx"
|
|
2292
|
-
],
|
|
2293
|
-
manifests: ["package.json"],
|
|
2294
|
-
// The concrete npm-family manager is resolved by detectPackageManager (it
|
|
2295
|
-
// honours the package.json `packageManager` field, which lockfiles can't
|
|
2296
|
-
// express), so one spec covers all four and `resolveToolchain` rewrites the
|
|
2297
|
-
// binary below.
|
|
2298
|
-
packageManagers: [
|
|
2299
|
-
{
|
|
2300
|
-
id: "npm",
|
|
2301
|
-
dependency: { mode: "agent-declares", file: "package.json" },
|
|
2302
|
-
installSteps: [{ argv: ["npm", "install"] }],
|
|
2303
|
-
ingest: {
|
|
2304
|
-
kind: "auto",
|
|
2305
|
-
argv: ["node", ENTRYPOINT_TOKEN],
|
|
2306
|
-
entrypointExtensions: [".mjs", ".cjs", ".js"]
|
|
2307
|
-
}
|
|
2308
|
-
}
|
|
2309
|
-
],
|
|
2310
|
-
sdk: { packageName: "algoliasearch", versionPin: "^5", docKey: "js" },
|
|
2311
|
-
ingestEntrypointExample: `${INGEST_DIR}/ingest.mjs`,
|
|
2312
|
-
// package.json scripts are repo-defined, so they're resolved at run time by
|
|
2313
|
-
// repoVerification rather than listed here.
|
|
2314
|
-
verification: [],
|
|
2315
|
-
envReadInstruction: "Read them from `process.env`.",
|
|
2316
|
-
skipDirs: ["node_modules", "dist", "build", "coverage", ".next", "out"]
|
|
2317
|
-
},
|
|
2318
|
-
python: {
|
|
2319
|
-
id: "python",
|
|
2320
|
-
displayName: "Python",
|
|
2321
|
-
aliases: ["python", "python3", "py", "cpython"],
|
|
2322
|
-
manifests: [
|
|
2323
|
-
"pyproject.toml",
|
|
2324
|
-
"requirements.txt",
|
|
2325
|
-
"setup.py",
|
|
2326
|
-
"setup.cfg",
|
|
2327
|
-
"Pipfile"
|
|
2328
|
-
],
|
|
2329
|
-
// Deliberately one path for every Python repo: a wizard-owned venv under
|
|
2330
|
-
// .algolia-wizard. Reusing the project's uv/poetry environment would mean
|
|
2331
|
-
// mutating the developer's real dependency manifest and lockfile, and the
|
|
2332
|
-
// declare-here/install-there split is the main way ingestion silently ends
|
|
2333
|
-
// up without the SDK installed. The tradeoff: the script can import the
|
|
2334
|
-
// Algolia client and anything it declares itself, but not the project's own
|
|
2335
|
-
// packages (see the optional root-requirements step below).
|
|
2336
|
-
packageManagers: [
|
|
2337
|
-
{
|
|
2338
|
-
id: "pip-venv",
|
|
2339
|
-
dependency: { mode: "agent-declares", file: PY_REQUIREMENTS },
|
|
2340
|
-
installSteps: [
|
|
2341
|
-
{ argv: ["python3", "-m", "venv", PY_VENV] },
|
|
2342
|
-
{
|
|
2343
|
-
argv: [PY_VENV_PYTHON, "-m", "pip", "install", "-r", PY_REQUIREMENTS]
|
|
2344
|
-
},
|
|
2345
|
-
// Best-effort access to the project's own dependencies (DB drivers,
|
|
2346
|
-
// ORMs) when the repo pins them the classic way.
|
|
2347
|
-
{
|
|
2348
|
-
argv: [
|
|
2349
|
-
PY_VENV_PYTHON,
|
|
2350
|
-
"-m",
|
|
2351
|
-
"pip",
|
|
2352
|
-
"install",
|
|
2353
|
-
"-r",
|
|
2354
|
-
"requirements.txt"
|
|
2355
|
-
],
|
|
2356
|
-
requiresFile: "requirements.txt",
|
|
2357
|
-
optional: true
|
|
2358
|
-
}
|
|
2359
|
-
],
|
|
2360
|
-
ingest: {
|
|
2361
|
-
kind: "auto",
|
|
2362
|
-
argv: [PY_VENV_PYTHON, ENTRYPOINT_TOKEN],
|
|
2363
|
-
entrypointExtensions: [".py"]
|
|
2364
|
-
}
|
|
2365
|
-
}
|
|
2366
|
-
],
|
|
2367
|
-
sdk: {
|
|
2368
|
-
packageName: "algoliasearch",
|
|
2369
|
-
versionPin: ">=4,<5",
|
|
2370
|
-
docKey: "python"
|
|
2371
|
-
},
|
|
2372
|
-
ingestEntrypointExample: `${INGEST_DIR}/ingest.py`,
|
|
2373
|
-
localSourceCaveat: {
|
|
2374
|
-
unless: "requirements.txt",
|
|
2375
|
-
message: "The ingestion script runs in its own environment under .algolia-wizard/, so it can install the Algolia client but not this project's packages (no requirements.txt to install from). If the script needs your database driver or ORM, add those packages to .algolia-wizard/requirements.txt and re-run the install."
|
|
2376
|
-
},
|
|
2377
|
-
verification: [
|
|
2378
|
-
{
|
|
2379
|
-
// -x skips the venv this same directory holds; without it the check
|
|
2380
|
-
// compiles every installed package instead of the generated script.
|
|
2381
|
-
label: "python compileall",
|
|
2382
|
-
argv: ["python3", "-m", "compileall", "-q", "-x", "[.]venv", INGEST_DIR],
|
|
2383
|
-
requiresFile: INGEST_DIR
|
|
2384
|
-
}
|
|
2385
|
-
],
|
|
2386
|
-
envReadInstruction: "Read them from `os.environ`.",
|
|
2387
|
-
skipDirs: [
|
|
2388
|
-
"venv",
|
|
2389
|
-
"__pycache__",
|
|
2390
|
-
"site-packages",
|
|
2391
|
-
"dist",
|
|
2392
|
-
"build",
|
|
2393
|
-
"htmlcov"
|
|
2394
|
-
]
|
|
2395
|
-
},
|
|
2396
|
-
ruby: {
|
|
2397
|
-
id: "ruby",
|
|
2398
|
-
displayName: "Ruby",
|
|
2399
|
-
aliases: ["ruby", "rb", "rails", "ruby on rails", "rubyonrails"],
|
|
2400
|
-
manifests: ["Gemfile", "*.gemspec"],
|
|
2401
|
-
packageManagers: [
|
|
2402
|
-
{
|
|
2403
|
-
id: "bundler",
|
|
2404
|
-
dependency: { mode: "agent-declares", file: "Gemfile" },
|
|
2405
|
-
installSteps: [{ argv: ["bundle", "install"] }],
|
|
2406
|
-
ingest: {
|
|
2407
|
-
kind: "auto",
|
|
2408
|
-
argv: ["bundle", "exec", "ruby", ENTRYPOINT_TOKEN],
|
|
2409
|
-
entrypointExtensions: [".rb"]
|
|
2410
|
-
}
|
|
2411
|
-
}
|
|
2412
|
-
],
|
|
2413
|
-
sdk: { packageName: "algolia", versionPin: "~> 3.0", docKey: "ruby" },
|
|
2414
|
-
ingestEntrypointExample: `${INGEST_DIR}/ingest.rb`,
|
|
2415
|
-
// Ruby has no directory-level syntax check (`ruby -c` is one file at a
|
|
2416
|
-
// time), so verification relies on the agent's own review here.
|
|
2417
|
-
verification: [],
|
|
2418
|
-
envReadInstruction: "Read them from `ENV.fetch('NAME')`.",
|
|
2419
|
-
skipDirs: ["vendor", "tmp", "log", "coverage"]
|
|
2420
|
-
},
|
|
2421
|
-
php: {
|
|
2422
|
-
id: "php",
|
|
2423
|
-
displayName: "PHP",
|
|
2424
|
-
aliases: ["php", "laravel", "symfony"],
|
|
2425
|
-
manifests: ["composer.json"],
|
|
2426
|
-
packageManagers: [
|
|
2427
|
-
{
|
|
2428
|
-
id: "composer",
|
|
2429
|
-
// `composer require` both declares and installs, and unlike editing
|
|
2430
|
-
// composer.json by hand it can't leave composer.lock out of date (which
|
|
2431
|
-
// makes a later `composer install` refuse to run).
|
|
2432
|
-
dependency: { mode: "wizard-installs" },
|
|
2433
|
-
installSteps: [
|
|
2434
|
-
{
|
|
2435
|
-
argv: [
|
|
2436
|
-
"composer",
|
|
2437
|
-
"require",
|
|
2438
|
-
"algolia/algoliasearch-client-php:^4",
|
|
2439
|
-
"--no-interaction",
|
|
2440
|
-
// Repo post-install scripts are the project's code, not ours to
|
|
2441
|
-
// trigger; Laravel's package:discover also fails in a bare tree.
|
|
2442
|
-
"--no-scripts"
|
|
2443
|
-
]
|
|
2444
|
-
}
|
|
2445
|
-
],
|
|
2446
|
-
ingest: {
|
|
2447
|
-
kind: "auto",
|
|
2448
|
-
argv: ["php", ENTRYPOINT_TOKEN],
|
|
2449
|
-
entrypointExtensions: [".php"]
|
|
2450
|
-
}
|
|
2451
|
-
}
|
|
2452
|
-
],
|
|
2453
|
-
sdk: {
|
|
2454
|
-
packageName: "algolia/algoliasearch-client-php",
|
|
2455
|
-
versionPin: "^4",
|
|
2456
|
-
docKey: "php"
|
|
2457
|
-
},
|
|
2458
|
-
ingestEntrypointExample: `${INGEST_DIR}/ingest.php`,
|
|
2459
|
-
verification: [],
|
|
2460
|
-
envReadInstruction: "Read them from `getenv('NAME')`.",
|
|
2461
|
-
skipDirs: ["vendor", "node_modules"]
|
|
2462
|
-
},
|
|
2463
|
-
go: {
|
|
2464
|
-
id: "go",
|
|
2465
|
-
displayName: "Go",
|
|
2466
|
-
aliases: ["go", "golang"],
|
|
2467
|
-
manifests: ["go.mod"],
|
|
2468
|
-
packageManagers: [
|
|
2469
|
-
{
|
|
2470
|
-
id: "gomod",
|
|
2471
|
-
// Imports in the generated file are the declaration; `go mod tidy`
|
|
2472
|
-
// resolves and fetches them — which only works because the script lives
|
|
2473
|
-
// outside INGEST_DIR (see VISIBLE_INGEST_DIR).
|
|
2474
|
-
dependency: { mode: "code-imports" },
|
|
2475
|
-
installSteps: [{ argv: ["go", "mod", "tidy"] }],
|
|
2476
|
-
ingest: {
|
|
2477
|
-
kind: "auto",
|
|
2478
|
-
argv: ["go", "run", ENTRYPOINT_TOKEN],
|
|
2479
|
-
entrypointExtensions: [".go"]
|
|
2480
|
-
}
|
|
2481
|
-
}
|
|
2482
|
-
],
|
|
2483
|
-
sdk: {
|
|
2484
|
-
packageName: "github.com/algolia/algoliasearch-client-go/v4",
|
|
2485
|
-
versionPin: "v4",
|
|
2486
|
-
docKey: "go"
|
|
2487
|
-
},
|
|
2488
|
-
ingestEntrypointExample: `${VISIBLE_INGEST_DIR}/ingest.go`,
|
|
2489
|
-
verification: [
|
|
2490
|
-
{ label: "go vet", argv: ["go", "vet", "./..."], requiresFile: "go.mod" }
|
|
2491
|
-
],
|
|
2492
|
-
envReadInstruction: "Read them from `os.Getenv`.",
|
|
2493
|
-
skipDirs: ["vendor", "bin"]
|
|
2494
|
-
},
|
|
2495
|
-
java: {
|
|
2496
|
-
id: "java",
|
|
2497
|
-
displayName: "Java",
|
|
2498
|
-
aliases: ["java"],
|
|
2499
|
-
manifests: ["pom.xml", "build.gradle", "build.gradle.kts"],
|
|
2500
|
-
packageManagers: [
|
|
2501
|
-
{
|
|
2502
|
-
id: "maven",
|
|
2503
|
-
detectFiles: ["pom.xml"],
|
|
2504
|
-
sdkVersionPin: "[4,5)",
|
|
2505
|
-
dependency: { mode: "agent-declares", file: "pom.xml" },
|
|
2506
|
-
installSteps: [{ argv: ["mvn", "-q", "-DskipTests", "compile"] }],
|
|
2507
|
-
// The main class is a wizard constant the instructions require the agent
|
|
2508
|
-
// to use, so execution can't be redirected by agent output. Runnable only
|
|
2509
|
-
// because the install step above compiles src/main/java first — which is
|
|
2510
|
-
// why the entrypoint lives there rather than under .algolia-wizard/.
|
|
2511
|
-
ingest: {
|
|
2512
|
-
kind: "auto",
|
|
2513
|
-
argv: [
|
|
2514
|
-
"mvn",
|
|
2515
|
-
"-q",
|
|
2516
|
-
"org.codehaus.mojo:exec-maven-plugin:3.5.0:java",
|
|
2517
|
-
"-Dexec.mainClass=AlgoliaWizardIngest"
|
|
2518
|
-
],
|
|
2519
|
-
entrypointExtensions: [".java"]
|
|
2520
|
-
}
|
|
2521
|
-
},
|
|
2522
|
-
{
|
|
2523
|
-
id: "gradle",
|
|
2524
|
-
detectFiles: ["build.gradle", "build.gradle.kts"],
|
|
2525
|
-
dependency: {
|
|
2526
|
-
mode: "agent-declares",
|
|
2527
|
-
file: "build.gradle",
|
|
2528
|
-
alternatives: ["build.gradle.kts"]
|
|
2529
|
-
},
|
|
2530
|
-
installSteps: [],
|
|
2531
|
-
// Auto-running means executing the repo's own ./gradlew wrapper; out of
|
|
2532
|
-
// scope for now, so the wizard writes the code and prints the command.
|
|
2533
|
-
ingest: {
|
|
2534
|
-
kind: "manual",
|
|
2535
|
-
entrypointExtensions: [".java"],
|
|
2536
|
-
runCommand: "./gradlew runAlgoliaIngest",
|
|
2537
|
-
requiresBuildTask: "runAlgoliaIngest"
|
|
2538
|
-
}
|
|
2539
|
-
}
|
|
2540
|
-
],
|
|
2541
|
-
sdk: {
|
|
2542
|
-
packageName: "com.algolia:algoliasearch",
|
|
2543
|
-
versionPin: "4.+",
|
|
2544
|
-
docKey: "java",
|
|
2545
|
-
alsoRequires: "The class must be named AlgoliaWizardIngest, in the default package (no `package` statement), with a `public static void main`."
|
|
2546
|
-
},
|
|
2547
|
-
// Not under .algolia-wizard/: Maven and Gradle only compile src/main/<lang>,
|
|
2548
|
-
// so a class outside it never makes it onto the classpath and the run command
|
|
2549
|
-
// fails with "class not found".
|
|
2550
|
-
ingestEntrypointExample: "src/main/java/AlgoliaWizardIngest.java",
|
|
2551
|
-
verification: [
|
|
2552
|
-
{
|
|
2553
|
-
label: "mvn compile",
|
|
2554
|
-
argv: ["mvn", "-q", "-DskipTests", "compile"],
|
|
2555
|
-
requiresFile: "pom.xml"
|
|
2556
|
-
}
|
|
2557
|
-
],
|
|
2558
|
-
envReadInstruction: "Read them from `System.getenv`.",
|
|
2559
|
-
skipDirs: ["target", "build", "out"]
|
|
2560
|
-
},
|
|
2561
|
-
kotlin: {
|
|
2562
|
-
id: "kotlin",
|
|
2563
|
-
displayName: "Kotlin",
|
|
2564
|
-
aliases: ["kotlin", "kt", "ktor"],
|
|
2565
|
-
manifests: ["build.gradle.kts", "build.gradle", "pom.xml"],
|
|
2566
|
-
packageManagers: [
|
|
2567
|
-
{
|
|
2568
|
-
id: "gradle",
|
|
2569
|
-
detectFiles: ["build.gradle.kts", "build.gradle"],
|
|
2570
|
-
dependency: {
|
|
2571
|
-
mode: "agent-declares",
|
|
2572
|
-
file: "build.gradle.kts",
|
|
2573
|
-
alternatives: ["build.gradle"]
|
|
2574
|
-
},
|
|
2575
|
-
installSteps: [],
|
|
2576
|
-
ingest: {
|
|
2577
|
-
kind: "manual",
|
|
2578
|
-
entrypointExtensions: [".kt"],
|
|
2579
|
-
runCommand: "./gradlew runAlgoliaIngest",
|
|
2580
|
-
requiresBuildTask: "runAlgoliaIngest"
|
|
2581
|
-
}
|
|
2582
|
-
},
|
|
2583
|
-
// Kotlin/Maven is rare but real, and pom.xml is a Kotlin manifest — without
|
|
2584
|
-
// this spec such a repo falls through to Gradle and is told to run a
|
|
2585
|
-
// ./gradlew task that doesn't exist. Compiling needs the repo's own
|
|
2586
|
-
// kotlin-maven-plugin, so the run stays the developer's step.
|
|
2587
|
-
{
|
|
2588
|
-
id: "maven",
|
|
2589
|
-
detectFiles: ["pom.xml"],
|
|
2590
|
-
sdkVersionPin: "[3,4)",
|
|
2591
|
-
dependency: { mode: "agent-declares", file: "pom.xml" },
|
|
2592
|
-
installSteps: [{ argv: ["mvn", "-q", "-DskipTests", "compile"] }],
|
|
2593
|
-
ingest: {
|
|
2594
|
-
kind: "manual",
|
|
2595
|
-
entrypointExtensions: [".kt"],
|
|
2596
|
-
runCommand: "mvn -q org.codehaus.mojo:exec-maven-plugin:3.5.0:java -Dexec.mainClass=AlgoliaWizardIngest"
|
|
2597
|
-
}
|
|
2598
|
-
}
|
|
2599
|
-
],
|
|
2600
|
-
sdk: {
|
|
2601
|
-
packageName: "com.algolia:algoliasearch-client-kotlin",
|
|
2602
|
-
versionPin: "3.+",
|
|
2603
|
-
docKey: "kotlin",
|
|
2604
|
-
// The published client's commonMain ships only ktor-client-core; without an
|
|
2605
|
-
// engine the script compiles and then fails at its first request.
|
|
2606
|
-
alsoRequires: "The Kotlin client bundles no HTTP engine, so also declare one (e.g. io.ktor:ktor-client-okhttp). Name the object AlgoliaWizardIngest in the default package, with a @JvmStatic main."
|
|
2607
|
-
},
|
|
2608
|
-
ingestEntrypointExample: "src/main/kotlin/AlgoliaWizardIngest.kt",
|
|
2609
|
-
verification: [],
|
|
2610
|
-
envReadInstruction: "Read them from `System.getenv`.",
|
|
2611
|
-
skipDirs: ["build", "out"]
|
|
2612
|
-
},
|
|
2613
|
-
scala: {
|
|
2614
|
-
id: "scala",
|
|
2615
|
-
displayName: "Scala",
|
|
2616
|
-
aliases: ["scala", "sbt"],
|
|
2617
|
-
manifests: ["build.sbt", "build.sc"],
|
|
2618
|
-
packageManagers: [
|
|
2619
|
-
{
|
|
2620
|
-
id: "sbt",
|
|
2621
|
-
dependency: { mode: "agent-declares", file: "build.sbt" },
|
|
2622
|
-
installSteps: [],
|
|
2623
|
-
ingest: {
|
|
2624
|
-
kind: "manual",
|
|
2625
|
-
entrypointExtensions: [".scala"],
|
|
2626
|
-
runCommand: 'sbt "runMain AlgoliaWizardIngest"'
|
|
2627
|
-
}
|
|
2628
|
-
}
|
|
2629
|
-
],
|
|
2630
|
-
sdk: {
|
|
2631
|
-
packageName: "com.algolia:algoliasearch-scala_2.13",
|
|
2632
|
-
versionPin: "2.+",
|
|
2633
|
-
docKey: "scala",
|
|
2634
|
-
alsoRequires: "Name the object AlgoliaWizardIngest in the default package (no `package` statement) so `runMain AlgoliaWizardIngest` resolves it."
|
|
2635
|
-
},
|
|
2636
|
-
ingestEntrypointExample: "src/main/scala/AlgoliaWizardIngest.scala",
|
|
2637
|
-
verification: [],
|
|
2638
|
-
envReadInstruction: "Read them from `sys.env`.",
|
|
2639
|
-
// `project/` holds sbt's build definition, but the name is generic enough
|
|
2640
|
-
// that some repos use it for source; scanning it is cheap, missing source
|
|
2641
|
-
// is not.
|
|
2642
|
-
skipDirs: ["target"]
|
|
2643
|
-
},
|
|
2644
|
-
csharp: {
|
|
2645
|
-
id: "csharp",
|
|
2646
|
-
displayName: "C#",
|
|
2647
|
-
aliases: ["c#", "csharp", "cs", ".net", "dotnet", "net", "asp.net"],
|
|
2648
|
-
manifests: ["*.csproj", "*.sln", "global.json"],
|
|
2649
|
-
packageManagers: [
|
|
2650
|
-
{
|
|
2651
|
-
id: "dotnet",
|
|
2652
|
-
// A self-contained project under .algolia-wizard keeps the ingest script
|
|
2653
|
-
// out of the repo's own build graph.
|
|
2654
|
-
dependency: { mode: "agent-declares", file: CSHARP_PROJECT },
|
|
2655
|
-
installSteps: [{ argv: ["dotnet", "restore", CSHARP_PROJECT] }],
|
|
2656
|
-
ingest: {
|
|
2657
|
-
kind: "auto",
|
|
2658
|
-
argv: ["dotnet", "run", "--project", ENTRYPOINT_TOKEN],
|
|
2659
|
-
entrypointExtensions: [".csproj"]
|
|
2660
|
-
}
|
|
2661
|
-
}
|
|
2662
|
-
],
|
|
2663
|
-
sdk: {
|
|
2664
|
-
packageName: "Algolia.Search",
|
|
2665
|
-
versionPin: "7.*",
|
|
2666
|
-
docKey: "csharp"
|
|
2667
|
-
},
|
|
2668
|
-
ingestEntrypointExample: CSHARP_PROJECT,
|
|
2669
|
-
verification: [
|
|
2670
|
-
{
|
|
2671
|
-
label: "dotnet build",
|
|
2672
|
-
argv: ["dotnet", "build", CSHARP_PROJECT, "--nologo"],
|
|
2673
|
-
requiresFile: CSHARP_PROJECT
|
|
2674
|
-
}
|
|
2675
|
-
],
|
|
2676
|
-
envReadInstruction: 'Read them from `Environment.GetEnvironmentVariable("NAME")`.',
|
|
2677
|
-
// Deliberately not `packages`: modern .NET uses PackageReference, and
|
|
2678
|
-
// `packages/` is where pnpm/Lerna/Turborepo monorepos keep all their source —
|
|
2679
|
-
// skipping it would hide the entities the scan is looking for.
|
|
2680
|
-
skipDirs: ["bin", "obj"]
|
|
2681
|
-
},
|
|
2682
|
-
swift: {
|
|
2683
|
-
id: "swift",
|
|
2684
|
-
displayName: "Swift",
|
|
2685
|
-
aliases: ["swift", "swiftui", "ios", "vapor"],
|
|
2686
|
-
manifests: ["Package.swift", "*.xcodeproj", "*.xcworkspace"],
|
|
2687
|
-
packageManagers: [
|
|
2688
|
-
{
|
|
2689
|
-
id: "swiftpm",
|
|
2690
|
-
dependency: {
|
|
2691
|
-
mode: "agent-declares",
|
|
2692
|
-
file: `${SWIFT_PACKAGE_DIR}/Package.swift`
|
|
2693
|
-
},
|
|
2694
|
-
// `swift build` resolves and fetches; a cold build of the client is slow
|
|
2695
|
-
// (minutes), which is why the caller degrades to the manual command when
|
|
2696
|
-
// this fails.
|
|
2697
|
-
installSteps: [
|
|
2698
|
-
{
|
|
2699
|
-
argv: ["swift", "build", "--package-path", SWIFT_PACKAGE_DIR],
|
|
2700
|
-
requiresFile: `${SWIFT_PACKAGE_DIR}/Package.swift`
|
|
2701
|
-
}
|
|
2702
|
-
],
|
|
2703
|
-
ingest: {
|
|
2704
|
-
kind: "auto",
|
|
2705
|
-
argv: ["swift", "run", "--package-path", SWIFT_PACKAGE_DIR],
|
|
2706
|
-
entrypointExtensions: [".swift"]
|
|
2707
|
-
}
|
|
2708
|
-
}
|
|
2709
|
-
],
|
|
2710
|
-
sdk: {
|
|
2711
|
-
packageName: "algoliasearch-client-swift",
|
|
2712
|
-
// SwiftPM range syntax, not an exact version — a bare "9.0.0" in a
|
|
2713
|
-
// Package.swift dependency pins the patch.
|
|
2714
|
-
versionPin: 'from: "9.0.0"',
|
|
2715
|
-
docKey: "swift"
|
|
2716
|
-
},
|
|
2717
|
-
ingestEntrypointExample: `${SWIFT_PACKAGE_DIR}/Sources/Ingest/main.swift`,
|
|
2718
|
-
verification: [
|
|
2719
|
-
{
|
|
2720
|
-
label: "swift build",
|
|
2721
|
-
argv: ["swift", "build", "--package-path", SWIFT_PACKAGE_DIR],
|
|
2722
|
-
requiresFile: `${SWIFT_PACKAGE_DIR}/Package.swift`
|
|
2723
|
-
}
|
|
2724
|
-
],
|
|
2725
|
-
envReadInstruction: "Read them from `ProcessInfo.processInfo.environment`.",
|
|
2726
|
-
skipDirs: ["Pods", "DerivedData", "Carthage", ".build"]
|
|
2727
|
-
},
|
|
2728
|
-
dart: {
|
|
2729
|
-
id: "dart",
|
|
2730
|
-
displayName: "Dart",
|
|
2731
|
-
aliases: ["dart", "flutter"],
|
|
2732
|
-
manifests: ["pubspec.yaml"],
|
|
2733
|
-
packageManagers: [
|
|
2734
|
-
{
|
|
2735
|
-
id: "flutter-pub",
|
|
2736
|
-
detectFiles: [".metadata"],
|
|
2737
|
-
dependency: { mode: "agent-declares", file: "pubspec.yaml" },
|
|
2738
|
-
installSteps: [{ argv: ["flutter", "pub", "get"] }],
|
|
2739
|
-
ingest: {
|
|
2740
|
-
kind: "auto",
|
|
2741
|
-
argv: ["dart", "run", ENTRYPOINT_TOKEN],
|
|
2742
|
-
entrypointExtensions: [".dart"]
|
|
2743
|
-
}
|
|
2744
|
-
},
|
|
2745
|
-
{
|
|
2746
|
-
id: "pub",
|
|
2747
|
-
dependency: { mode: "agent-declares", file: "pubspec.yaml" },
|
|
2748
|
-
installSteps: [{ argv: ["dart", "pub", "get"] }],
|
|
2749
|
-
ingest: {
|
|
2750
|
-
kind: "auto",
|
|
2751
|
-
argv: ["dart", "run", ENTRYPOINT_TOKEN],
|
|
2752
|
-
entrypointExtensions: [".dart"]
|
|
2753
|
-
}
|
|
2754
|
-
}
|
|
2755
|
-
],
|
|
2756
|
-
sdk: {
|
|
2757
|
-
packageName: "algolia_client_search",
|
|
2758
|
-
versionPin: "^1.0.0",
|
|
2759
|
-
docKey: "dart"
|
|
2760
|
-
},
|
|
2761
|
-
ingestEntrypointExample: `${INGEST_DIR}/ingest.dart`,
|
|
2762
|
-
verification: [
|
|
2763
|
-
{
|
|
2764
|
-
// Gated on the directory it analyzes, not just pubspec.yaml: a run that
|
|
2765
|
-
// only built a search UI never created it, and `dart analyze` on a
|
|
2766
|
-
// missing path fails the whole verification pass.
|
|
2767
|
-
label: "dart analyze",
|
|
2768
|
-
argv: ["dart", "analyze", INGEST_DIR],
|
|
2769
|
-
requiresFile: INGEST_DIR
|
|
2770
|
-
}
|
|
2771
|
-
],
|
|
2772
|
-
envReadInstruction: "Read them from `Platform.environment`.",
|
|
2773
|
-
skipDirs: ["build"]
|
|
2774
|
-
}
|
|
2775
|
-
};
|
|
2776
|
-
var DEFAULT_LANGUAGE_ID = "javascript";
|
|
2777
|
-
var JAVASCRIPT = "javascript";
|
|
2778
|
-
var CURATED_LANGUAGES = Object.values(
|
|
2779
|
-
LANGUAGE_PROFILES
|
|
2780
|
-
).map((profile) => profile.displayName);
|
|
2781
|
-
function isBackendLanguage(profile) {
|
|
2782
|
-
return profile.id !== JAVASCRIPT;
|
|
2783
|
-
}
|
|
2784
|
-
function normalizeLanguageName(name) {
|
|
2785
|
-
return name.toLowerCase().trim().replace(/[\s_-]+/g, "");
|
|
2786
|
-
}
|
|
2787
|
-
var ALIAS_TO_ID = /* @__PURE__ */ new Map();
|
|
2788
|
-
for (const profile of Object.values(LANGUAGE_PROFILES)) {
|
|
2789
|
-
for (const alias of [profile.id, profile.displayName, ...profile.aliases]) {
|
|
2790
|
-
ALIAS_TO_ID.set(normalizeLanguageName(alias), profile.id);
|
|
2791
|
-
}
|
|
2792
|
-
}
|
|
2793
|
-
function resolveLanguageProfile(name) {
|
|
2794
|
-
const id = ALIAS_TO_ID.get(normalizeLanguageName(name));
|
|
2795
|
-
return id ? LANGUAGE_PROFILES[id] : void 0;
|
|
2796
|
-
}
|
|
2797
|
-
function isSameLanguage(a, b) {
|
|
2798
|
-
const x = resolveLanguageProfile(a);
|
|
2799
|
-
const y = resolveLanguageProfile(b);
|
|
2800
|
-
if (x && y) return x.id === y.id;
|
|
2801
|
-
if (x || y) return false;
|
|
2802
|
-
const folded = normalizeLanguageName(a);
|
|
2803
|
-
return folded !== "" && folded === normalizeLanguageName(b);
|
|
2804
|
-
}
|
|
2805
|
-
var BASE_SKIP_DIRS = ["node_modules", ".git", "dist"];
|
|
2806
|
-
var ALL_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
2807
|
-
...BASE_SKIP_DIRS,
|
|
2808
|
-
...Object.values(LANGUAGE_PROFILES).flatMap((p) => p.skipDirs)
|
|
2809
|
-
]);
|
|
2810
|
-
var ALLOWED_BINARIES = new Set(
|
|
2811
|
-
Object.values(LANGUAGE_PROFILES).flatMap((profile) => [
|
|
2812
|
-
...profile.packageManagers.flatMap((pm) => [
|
|
2813
|
-
...pm.installSteps.map((s) => s.argv[0]),
|
|
2814
|
-
...pm.ingest.kind === "auto" ? [pm.ingest.argv[0]] : []
|
|
2815
|
-
]),
|
|
2816
|
-
...profile.verification.map((v) => v.argv[0])
|
|
2817
|
-
])
|
|
2818
|
-
);
|
|
2819
|
-
var JS_PACKAGE_MANAGERS = /* @__PURE__ */ new Set(["npm", "pnpm", "yarn", "bun"]);
|
|
2820
|
-
function isWorktreeRelativeCommand(command) {
|
|
2821
|
-
return command.includes("/");
|
|
2822
|
-
}
|
|
2823
|
-
function withCommand(argv, command) {
|
|
2824
|
-
return [command, ...argv.slice(1)];
|
|
2825
|
-
}
|
|
2826
|
-
function resolveDeclaredManifest(root, packageManager) {
|
|
2827
|
-
const { dependency } = packageManager;
|
|
2828
|
-
if (dependency.mode !== "agent-declares" || !dependency.alternatives?.length) {
|
|
2829
|
-
return packageManager;
|
|
2830
|
-
}
|
|
2831
|
-
const present = [dependency.file, ...dependency.alternatives].find(
|
|
2832
|
-
(file) => existsSync2(join9(root, file))
|
|
2833
|
-
);
|
|
2834
|
-
if (!present || present === dependency.file) return packageManager;
|
|
2835
|
-
return { ...packageManager, dependency: { ...dependency, file: present } };
|
|
2836
|
-
}
|
|
2837
|
-
async function manifestPresent(root, manifest, listing) {
|
|
2838
|
-
if (!manifest.startsWith("*.")) return existsSync2(join9(root, manifest));
|
|
2839
|
-
if (!listing.entries) {
|
|
2840
|
-
const entries = await readdir2(root).catch(() => []);
|
|
2841
|
-
listing.entries = Array.isArray(entries) ? entries : [];
|
|
2842
|
-
}
|
|
2843
|
-
const suffix = manifest.slice(1);
|
|
2844
|
-
return listing.entries.some((e) => e.endsWith(suffix));
|
|
2845
|
-
}
|
|
2846
|
-
async function profileManifestPresent(root, profile, listing) {
|
|
2847
|
-
for (const manifest of profile.manifests) {
|
|
2848
|
-
if (await manifestPresent(root, manifest, listing)) return true;
|
|
2849
|
-
}
|
|
2850
|
-
return false;
|
|
2851
|
-
}
|
|
2852
|
-
async function detectProfilesFromManifests(root) {
|
|
2853
|
-
const listing = {};
|
|
2854
|
-
const found = [];
|
|
2855
|
-
for (const profile of Object.values(LANGUAGE_PROFILES)) {
|
|
2856
|
-
if (await profileManifestPresent(root, profile, listing)) found.push(profile);
|
|
2857
|
-
}
|
|
2858
|
-
return found;
|
|
2859
|
-
}
|
|
2860
|
-
async function hasProfileManifest(root, profile) {
|
|
2861
|
-
return profileManifestPresent(root, profile, {});
|
|
2862
|
-
}
|
|
2863
|
-
async function pickIngestionCandidates(root, confirmedNames) {
|
|
2864
|
-
const confirmed3 = confirmedNames.map((name) => resolveLanguageProfile(name)).filter((p) => p !== void 0);
|
|
2865
|
-
const onDisk = await detectProfilesFromManifests(root);
|
|
2866
|
-
const onDiskIds = new Set(onDisk.map((p) => p.id));
|
|
2867
|
-
const candidates = [
|
|
2868
|
-
...new Map(
|
|
2869
|
-
confirmed3.filter((p) => onDiskIds.has(p.id)).map((p) => [p.id, p])
|
|
2870
|
-
).values()
|
|
2871
|
-
];
|
|
2872
|
-
return { candidates, confirmed: confirmed3, onDisk };
|
|
2873
|
-
}
|
|
2874
|
-
async function resolveToolchain(root, profile) {
|
|
2875
|
-
const signals = (pm) => [
|
|
2876
|
-
...pm.lockfiles ?? [],
|
|
2877
|
-
...pm.detectFiles ?? []
|
|
2878
|
-
];
|
|
2879
|
-
const matched = profile.packageManagers.find(
|
|
2880
|
-
(pm) => signals(pm).some((f) => existsSync2(join9(root, f)))
|
|
2881
|
-
);
|
|
2882
|
-
const fallback = profile.packageManagers.find((pm) => signals(pm).length === 0) ?? profile.packageManagers[0];
|
|
2883
|
-
const packageManager = resolveDeclaredManifest(root, matched ?? fallback);
|
|
2884
|
-
let { installSteps, ingest } = packageManager;
|
|
2885
|
-
installSteps = installSteps.map(
|
|
2886
|
-
(step) => isWorktreeRelativeCommand(step.argv[0]) ? { ...step, argv: withCommand(step.argv, join9(root, step.argv[0])) } : step
|
|
2887
|
-
);
|
|
2888
|
-
if (ingest.kind === "auto" && isWorktreeRelativeCommand(ingest.argv[0])) {
|
|
2889
|
-
ingest = {
|
|
2890
|
-
...ingest,
|
|
2891
|
-
argv: withCommand(ingest.argv, join9(root, ingest.argv[0]))
|
|
2892
|
-
};
|
|
2893
|
-
}
|
|
2894
|
-
if (profile.id === "javascript") {
|
|
2895
|
-
const pm = await detectPackageManager(root);
|
|
2896
|
-
if (JS_PACKAGE_MANAGERS.has(pm)) {
|
|
2897
|
-
installSteps = installSteps.map((step) => ({
|
|
2898
|
-
...step,
|
|
2899
|
-
argv: withCommand(step.argv, pm)
|
|
2900
|
-
}));
|
|
2901
|
-
if (pm === "bun" && ingest.kind === "auto") {
|
|
2902
|
-
ingest = { ...ingest, argv: withCommand(ingest.argv, "bun") };
|
|
2903
|
-
}
|
|
2904
|
-
}
|
|
2905
|
-
}
|
|
2906
|
-
return { profile, packageManager, installSteps, ingest };
|
|
2907
|
-
}
|
|
2908
|
-
function resolveIngestArgv(ingest, entrypoint) {
|
|
2909
|
-
if (ingest.kind !== "auto") {
|
|
2910
|
-
throw new Error("resolveIngestArgv called for a manual-run toolchain");
|
|
2911
|
-
}
|
|
2912
|
-
return ingest.argv.map(
|
|
2913
|
-
(part) => part === ENTRYPOINT_TOKEN ? entrypoint : part
|
|
2914
|
-
);
|
|
2915
|
-
}
|
|
2916
|
-
function describeIngestCommand(ingest, entrypoint) {
|
|
2917
|
-
if (ingest.kind !== "auto") return ingest.runCommand;
|
|
2918
|
-
return ingest.argv.map((part) => part === ENTRYPOINT_TOKEN ? shellQuote(entrypoint) : part).join(" ");
|
|
2919
|
-
}
|
|
2920
|
-
function ingestScriptDir(profile) {
|
|
2921
|
-
const parts = profile.ingestEntrypointExample.split("/");
|
|
2922
|
-
return parts.slice(0, -1).join("/") || ".";
|
|
2923
|
-
}
|
|
2924
|
-
function localSourceLimitation(root, profile) {
|
|
2925
|
-
const caveat = profile.localSourceCaveat;
|
|
2926
|
-
if (!caveat) return void 0;
|
|
2927
|
-
return existsSync2(join9(root, caveat.unless)) ? void 0 : caveat.message;
|
|
2928
|
-
}
|
|
2929
|
-
async function missingBuildTask(root, toolchain) {
|
|
2930
|
-
const { ingest, packageManager } = toolchain;
|
|
2931
|
-
if (ingest.kind !== "manual" || !ingest.requiresBuildTask) return void 0;
|
|
2932
|
-
if (packageManager.dependency.mode !== "agent-declares") return void 0;
|
|
2933
|
-
const buildFile = join9(root, packageManager.dependency.file);
|
|
2934
|
-
const contents = await readFile7(buildFile, "utf8").catch(() => void 0);
|
|
2935
|
-
if (contents === void 0) return void 0;
|
|
2936
|
-
return contents.includes(ingest.requiresBuildTask) ? void 0 : ingest.requiresBuildTask;
|
|
2937
|
-
}
|
|
2938
|
-
function sdkVersionPin(profile, packageManager) {
|
|
2939
|
-
return packageManager.sdkVersionPin ?? profile.sdk.versionPin;
|
|
2940
|
-
}
|
|
2941
|
-
function dependencyInstruction(toolchain) {
|
|
2942
|
-
const { profile, packageManager } = toolchain;
|
|
2943
|
-
const { packageName } = profile.sdk;
|
|
2944
|
-
const versionPin = sdkVersionPin(profile, packageManager);
|
|
2945
|
-
const also = profile.sdk.alsoRequires ? ` ${profile.sdk.alsoRequires}` : "";
|
|
2946
|
-
switch (packageManager.dependency.mode) {
|
|
2947
|
-
case "wizard-installs":
|
|
2948
|
-
return `The wizard installs ${packageName} ${versionPin} in the worktree after you finish \u2014 import it directly and do not edit dependency manifests for it.${also}`;
|
|
2949
|
-
case "code-imports":
|
|
2950
|
-
return `Import ${packageName} in the script; the wizard resolves and fetches it in the worktree after you finish. Do not edit dependency manifests by hand.${also}`;
|
|
2951
|
-
case "agent-declares":
|
|
2952
|
-
return `Declare ${packageName} ${versionPin} in "${packageManager.dependency.file}" (create the file if needed), plus any other dependency your script imports; the wizard installs them in the worktree after you finish.${also}`;
|
|
2953
|
-
}
|
|
2954
|
-
}
|
|
2955
|
-
|
|
2956
|
-
// src/lib/tools/searchFiles.ts
|
|
2957
2228
|
var MAX_QUERY_LENGTH = 1e3;
|
|
2958
2229
|
async function walkFiles(dir) {
|
|
2230
|
+
const skip = /* @__PURE__ */ new Set(["node_modules", ".git", "dist"]);
|
|
2959
2231
|
const out = [];
|
|
2960
|
-
for (const e of await
|
|
2961
|
-
if (e.name.startsWith(".") ||
|
|
2962
|
-
const full =
|
|
2232
|
+
for (const e of await readdir2(dir, { withFileTypes: true })) {
|
|
2233
|
+
if (e.name.startsWith(".") || skip.has(e.name)) continue;
|
|
2234
|
+
const full = join8(dir, e.name);
|
|
2963
2235
|
if (e.isDirectory()) out.push(...await walkFiles(full));
|
|
2964
2236
|
else if (e.isFile()) out.push(full);
|
|
2965
2237
|
}
|
|
@@ -2992,7 +2264,7 @@ function searchFilesTool(ctx) {
|
|
|
2992
2264
|
for (const file of await walkFiles(resolved.target)) {
|
|
2993
2265
|
let content;
|
|
2994
2266
|
try {
|
|
2995
|
-
content = await
|
|
2267
|
+
content = await readFile6(file, "utf8");
|
|
2996
2268
|
} catch {
|
|
2997
2269
|
continue;
|
|
2998
2270
|
}
|
|
@@ -3016,144 +2288,88 @@ function searchFilesTool(ctx) {
|
|
|
3016
2288
|
import { tool as tool8 } from "ai";
|
|
3017
2289
|
import z11 from "zod";
|
|
3018
2290
|
|
|
3019
|
-
// src/lib/tools/repoVerification.ts
|
|
3020
|
-
import { existsSync as existsSync3 } from "node:fs";
|
|
3021
|
-
import { join as join11 } from "node:path";
|
|
3022
|
-
|
|
3023
2291
|
// src/lib/tools/utils/runCommand.ts
|
|
3024
2292
|
import { spawn as spawn2 } from "node:child_process";
|
|
3025
|
-
|
|
3026
|
-
var INGEST_TIMEOUT_MS = 15 * 6e4;
|
|
3027
|
-
var VERIFY_TIMEOUT_MS = 10 * 6e4;
|
|
3028
|
-
var KILL_GRACE_MS = 5e3;
|
|
3029
|
-
function runCommand(command, args, options = {}) {
|
|
3030
|
-
const { cwd, env, timeoutMs = VERIFY_TIMEOUT_MS } = options;
|
|
2293
|
+
function runCommand(command, args, cwd) {
|
|
3031
2294
|
return new Promise((resolve4) => {
|
|
3032
2295
|
let output = "";
|
|
3033
|
-
let settled = false;
|
|
3034
2296
|
const child = spawn2(command, args, {
|
|
3035
2297
|
cwd,
|
|
3036
|
-
|
|
3037
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
3038
|
-
...env ? { env: { ...process.env, ...env } } : {}
|
|
2298
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
3039
2299
|
});
|
|
3040
|
-
const settle = (result) => {
|
|
3041
|
-
if (settled) return;
|
|
3042
|
-
settled = true;
|
|
3043
|
-
clearTimeout(timer);
|
|
3044
|
-
resolve4(result);
|
|
3045
|
-
};
|
|
3046
|
-
const timer = setTimeout(() => {
|
|
3047
|
-
child.kill("SIGTERM");
|
|
3048
|
-
setTimeout(() => child.kill("SIGKILL"), KILL_GRACE_MS).unref();
|
|
3049
|
-
const seconds = Math.round(timeoutMs / 1e3);
|
|
3050
|
-
settle({
|
|
3051
|
-
code: 1,
|
|
3052
|
-
output: `${output}
|
|
3053
|
-
Timed out after ${seconds}s: ${command} ${args.join(" ")}`.trim(),
|
|
3054
|
-
timedOut: true
|
|
3055
|
-
});
|
|
3056
|
-
}, timeoutMs);
|
|
3057
2300
|
child.stdout?.on("data", (d) => output += d);
|
|
3058
2301
|
child.stderr?.on("data", (d) => output += d);
|
|
3059
2302
|
child.on(
|
|
3060
2303
|
"error",
|
|
3061
|
-
(err) =>
|
|
3062
|
-
code: 1,
|
|
3063
|
-
output: `Failed to run ${command}: ${err.message}`,
|
|
3064
|
-
timedOut: false
|
|
3065
|
-
})
|
|
3066
|
-
);
|
|
3067
|
-
child.on(
|
|
3068
|
-
"close",
|
|
3069
|
-
(code) => settle({ code: code ?? 1, output, timedOut: false })
|
|
2304
|
+
(err) => resolve4({ code: 1, output: `Failed to run ${command}: ${err.message}` })
|
|
3070
2305
|
);
|
|
2306
|
+
child.on("close", (code) => resolve4({ code: code ?? 1, output }));
|
|
3071
2307
|
});
|
|
3072
2308
|
}
|
|
3073
2309
|
|
|
2310
|
+
// src/lib/tools/utils/packageManager.ts
|
|
2311
|
+
import { readFile as readFile7 } from "node:fs/promises";
|
|
2312
|
+
import { existsSync } from "node:fs";
|
|
2313
|
+
import { join as join9 } from "node:path";
|
|
2314
|
+
var LOCKFILES = [
|
|
2315
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
2316
|
+
["yarn.lock", "yarn"],
|
|
2317
|
+
["bun.lockb", "bun"],
|
|
2318
|
+
["bun.lock", "bun"],
|
|
2319
|
+
["package-lock.json", "npm"]
|
|
2320
|
+
];
|
|
2321
|
+
async function readPackageJson(cwd = process.cwd()) {
|
|
2322
|
+
return JSON.parse(await readFile7(join9(cwd, "package.json"), "utf8"));
|
|
2323
|
+
}
|
|
2324
|
+
function packageManagerFrom(pkg) {
|
|
2325
|
+
return pkg.packageManager?.split("@")[0] ?? "npm";
|
|
2326
|
+
}
|
|
2327
|
+
function packageManagerFromLockfile(cwd) {
|
|
2328
|
+
return LOCKFILES.find(([file]) => existsSync(join9(cwd, file)))?.[1];
|
|
2329
|
+
}
|
|
2330
|
+
async function detectPackageManager(cwd) {
|
|
2331
|
+
try {
|
|
2332
|
+
const pkg = await readPackageJson(cwd);
|
|
2333
|
+
if (pkg.packageManager) return packageManagerFrom(pkg);
|
|
2334
|
+
} catch {
|
|
2335
|
+
}
|
|
2336
|
+
return packageManagerFromLockfile(cwd) ?? "npm";
|
|
2337
|
+
}
|
|
2338
|
+
|
|
3074
2339
|
// src/lib/tools/repoVerification.ts
|
|
3075
2340
|
var VERIFICATION_SCRIPT_CANDIDATES = ["lint", "typecheck", "check"];
|
|
3076
|
-
async function
|
|
3077
|
-
const { code, output } = await runCommand(binary, args, {
|
|
3078
|
-
timeoutMs: VERIFY_TIMEOUT_MS
|
|
3079
|
-
});
|
|
3080
|
-
return { command, exitCode: code, ok: code === 0, output: output.trim() };
|
|
3081
|
-
}
|
|
3082
|
-
async function javascriptChecks() {
|
|
2341
|
+
async function runRepoVerificationCheck() {
|
|
3083
2342
|
let pkg;
|
|
3084
2343
|
try {
|
|
3085
2344
|
pkg = await readPackageJson();
|
|
3086
2345
|
} catch (err) {
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
};
|
|
2346
|
+
const limitation = `Could not read package.json to detect verification conventions: ${err.message}`;
|
|
2347
|
+
return { ok: false, checks: [], limitation };
|
|
3090
2348
|
}
|
|
3091
2349
|
const scripts = pkg.scripts ?? {};
|
|
3092
2350
|
const present = VERIFICATION_SCRIPT_CANDIDATES.filter((s) => s in scripts);
|
|
3093
2351
|
if (present.length === 0) {
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
};
|
|
2352
|
+
const limitation = `No verification script found in package.json (looked for: ${VERIFICATION_SCRIPT_CANDIDATES.join(", ")}).`;
|
|
2353
|
+
return { ok: false, checks: [], limitation };
|
|
3097
2354
|
}
|
|
3098
2355
|
const pm = await detectPackageManager(process.cwd());
|
|
3099
2356
|
const checks = [];
|
|
3100
2357
|
for (const script of present) {
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
);
|
|
3104
|
-
}
|
|
3105
|
-
return { checks };
|
|
3106
|
-
}
|
|
3107
|
-
async function registryChecks(id) {
|
|
3108
|
-
const profile = LANGUAGE_PROFILES[id];
|
|
3109
|
-
const runnable = profile.verification.filter(
|
|
3110
|
-
(spec) => !spec.requiresFile || existsSync3(join11(process.cwd(), spec.requiresFile))
|
|
3111
|
-
);
|
|
3112
|
-
if (runnable.length === 0) {
|
|
3113
|
-
return {
|
|
3114
|
-
limitation: `No mechanical verification available for ${profile.displayName} in this repo.`
|
|
3115
|
-
};
|
|
3116
|
-
}
|
|
3117
|
-
const checks = [];
|
|
3118
|
-
for (const spec of runnable) {
|
|
3119
|
-
checks.push(
|
|
3120
|
-
await runCheck(spec.argv.join(" "), spec.argv[0], [...spec.argv.slice(1)])
|
|
3121
|
-
);
|
|
3122
|
-
}
|
|
3123
|
-
return { checks };
|
|
3124
|
-
}
|
|
3125
|
-
async function runRepoVerificationCheck(languages = [DEFAULT_LANGUAGE_ID]) {
|
|
3126
|
-
const ids = [...new Set(languages)];
|
|
3127
|
-
if (ids.length === 0) ids.push(DEFAULT_LANGUAGE_ID);
|
|
3128
|
-
const checks = [];
|
|
3129
|
-
const limitations = [];
|
|
3130
|
-
for (const id of ids) {
|
|
3131
|
-
const result = id === JAVASCRIPT ? await javascriptChecks() : await registryChecks(id);
|
|
3132
|
-
if ("checks" in result) checks.push(...result.checks);
|
|
3133
|
-
else limitations.push(result.limitation);
|
|
2358
|
+
const command = `${pm} run ${script}`;
|
|
2359
|
+
const { code, output } = await runCommand(pm, ["run", script]);
|
|
2360
|
+
checks.push({ command, exitCode: code, ok: code === 0, output: output.trim() });
|
|
3134
2361
|
}
|
|
3135
|
-
|
|
3136
|
-
return {
|
|
3137
|
-
ok: false,
|
|
3138
|
-
checks: [],
|
|
3139
|
-
limitation: limitations.join(" ") || "No verification checks available."
|
|
3140
|
-
};
|
|
3141
|
-
}
|
|
3142
|
-
return {
|
|
3143
|
-
ok: checks.every((c) => c.ok),
|
|
3144
|
-
checks,
|
|
3145
|
-
...limitations.length ? { limitation: limitations.join(" ") } : {}
|
|
3146
|
-
};
|
|
2362
|
+
return { ok: checks.every((c) => c.ok), checks };
|
|
3147
2363
|
}
|
|
3148
2364
|
|
|
3149
2365
|
// src/lib/tools/verifyImplementation.ts
|
|
3150
|
-
function verifyImplementationTool(
|
|
2366
|
+
function verifyImplementationTool() {
|
|
3151
2367
|
return tool8({
|
|
3152
|
-
description: "Run the repo's mechanical verification
|
|
2368
|
+
description: "Run the repo's mechanical verification check for generated implementation changes. Detects lint/typecheck/check from package.json and returns structured pass/fail evidence for the verifier to interpret.",
|
|
3153
2369
|
inputSchema: z11.object(),
|
|
3154
2370
|
execute: async () => {
|
|
3155
|
-
logger.info(
|
|
3156
|
-
return runRepoVerificationCheck(
|
|
2371
|
+
logger.info("called verifyImplementation tool");
|
|
2372
|
+
return runRepoVerificationCheck();
|
|
3157
2373
|
}
|
|
3158
2374
|
});
|
|
3159
2375
|
}
|
|
@@ -3279,17 +2495,12 @@ var DEFAULT_TOOL_LIMITS = {
|
|
|
3279
2495
|
read: 20,
|
|
3280
2496
|
match: 100
|
|
3281
2497
|
};
|
|
3282
|
-
function createToolContext({
|
|
3283
|
-
limits = DEFAULT_TOOL_LIMITS,
|
|
3284
|
-
cwd = process.cwd(),
|
|
3285
|
-
languages = [DEFAULT_LANGUAGE_ID]
|
|
3286
|
-
} = {}) {
|
|
2498
|
+
function createToolContext(limits = DEFAULT_TOOL_LIMITS, cwd = process.cwd()) {
|
|
3287
2499
|
return {
|
|
3288
2500
|
root: cwd,
|
|
3289
2501
|
cwd,
|
|
3290
2502
|
limits,
|
|
3291
|
-
counts: { list: 0, search: 0, read: 0 }
|
|
3292
|
-
languages: languages.length ? languages : [DEFAULT_LANGUAGE_ID]
|
|
2503
|
+
counts: { list: 0, search: 0, read: 0 }
|
|
3293
2504
|
};
|
|
3294
2505
|
}
|
|
3295
2506
|
|
|
@@ -3326,7 +2537,7 @@ function createTools(ctx, { output, tools }) {
|
|
|
3326
2537
|
searchFiles: withLogging("searchFiles", searchFilesTool(ctx)),
|
|
3327
2538
|
verifyImplementation: withLogging(
|
|
3328
2539
|
"verifyImplementation",
|
|
3329
|
-
verifyImplementationTool(
|
|
2540
|
+
verifyImplementationTool()
|
|
3330
2541
|
),
|
|
3331
2542
|
generateRecord: withLogging("generateRecord", generateRecordTool(ctx)),
|
|
3332
2543
|
notifyUser: withLogging("notifyUser", notifyUserTool())
|
|
@@ -3362,7 +2573,7 @@ async function runAgent(req) {
|
|
|
3362
2573
|
baseURL: PROXY_BASE_URL,
|
|
3363
2574
|
fetch: proxyFetch
|
|
3364
2575
|
});
|
|
3365
|
-
const toolContext = createToolContext(
|
|
2576
|
+
const toolContext = createToolContext();
|
|
3366
2577
|
const readTools = ["readFile", "searchFiles", "listFiles"];
|
|
3367
2578
|
const hasReadTools = !req.tools || req.tools.some((t) => readTools.includes(t));
|
|
3368
2579
|
const instructions = [
|
|
@@ -3453,11 +2664,8 @@ var detectLanguageSchema = z16.object({
|
|
|
3453
2664
|
var detectLanguage = () => runAgent({
|
|
3454
2665
|
instructions: [
|
|
3455
2666
|
"Analyze the codebase and determine the programming languages and frameworks used",
|
|
3456
|
-
"
|
|
3457
|
-
"List the language that owns the backend/data code first \u2014 that is the one an ingestion script will be written in.",
|
|
3458
|
-
"If a superset language is found, exclude the subset language. TS-over-JS. Kotlin-over-Java when Kotlin is primary.",
|
|
2667
|
+
"If a superset language is found, exclude the subset language. TS-over-JS.",
|
|
3459
2668
|
"If a meta-framework is used, exclude the framework. Next-over-React.",
|
|
3460
|
-
"Frameworks include backend and server-rendering frameworks (e.g. Rails, Django, Laravel, Symfony, Spring Boot, ASP.NET Core, Flask, Gin, Ktor) as well as frontend ones (React, Vue, Angular, Svelte) and mobile ones (Flutter, SwiftUI).",
|
|
3461
2669
|
"Return the exact version",
|
|
3462
2670
|
"Exclude things like CSS frameworks, build tools, or testing frameworks",
|
|
3463
2671
|
'Use as few tools as possible, but do not guess. If you cant find the answer, say "unknown"',
|
|
@@ -3506,7 +2714,6 @@ var MODE_CONFIG = {
|
|
|
3506
2714
|
"Analyze the codebase to find the data entities (models) that should be ingested into Algolia.",
|
|
3507
2715
|
"For each entity, return its name, the file path(s) where it is defined, and its indexable attribute keys (the fields a user would search or filter on).",
|
|
3508
2716
|
"Inspect the source of each entity to extract real field names for attributes \u2014 do not guess or leave attributes empty.",
|
|
3509
|
-
"Entities live wherever the stack keeps them: TypeScript interfaces or a Prisma schema, Django models.py, Rails app/models, Laravel Eloquent models, JPA @Entity classes, Go structs, C# entity classes, Pydantic models.",
|
|
3510
2717
|
"Prefer domain models (e.g. Document, Product, User) over framework or infrastructure types.",
|
|
3511
2718
|
"Use as few tools as possible, but do not guess. If you cannot find any entities, return an empty array.",
|
|
3512
2719
|
"Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
|
|
@@ -3518,9 +2725,8 @@ var MODE_CONFIG = {
|
|
|
3518
2725
|
instructions: [
|
|
3519
2726
|
"Analyze the codebase to determine the single best location to add search UI functionality.",
|
|
3520
2727
|
"Prefer a shared, always-rendered layout location (e.g. a header or navigation component) so search is reachable across the app.",
|
|
3521
|
-
"
|
|
3522
|
-
|
|
3523
|
-
'Use as few tools as possible, but do not guess. If the project renders no UI at all (an API-only service), say "unknown".',
|
|
2728
|
+
"Return one file path as searchImplementationAnalysis (e.g. /layouts/header.tsx).",
|
|
2729
|
+
'Use as few tools as possible, but do not guess. If you cannot find a clear location, say "unknown".',
|
|
3524
2730
|
"Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
|
|
3525
2731
|
"When done, call reportStatus"
|
|
3526
2732
|
],
|
|
@@ -3529,8 +2735,8 @@ var MODE_CONFIG = {
|
|
|
3529
2735
|
verification: {
|
|
3530
2736
|
instructions: [
|
|
3531
2737
|
"Analyze the codebase to determine which code-quality tools are available to validate changes.",
|
|
3532
|
-
"Look at
|
|
3533
|
-
'Return the tool names as an array, e.g. ["eslint", "prettier", "tsc"]
|
|
2738
|
+
"Look at package.json scripts, config files (e.g. .eslintrc, tsconfig, prettier), and dev dependencies.",
|
|
2739
|
+
'Return the tool names as an array, e.g. ["eslint", "prettier", "tsc"].',
|
|
3534
2740
|
"Use as few tools as possible, but do not guess. If you cannot find any, return an empty array.",
|
|
3535
2741
|
"Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
|
|
3536
2742
|
"When done, call reportStatus"
|
|
@@ -3557,7 +2763,7 @@ async function runAnalysis(mode, extraInstructions = []) {
|
|
|
3557
2763
|
// package.json
|
|
3558
2764
|
var package_default = {
|
|
3559
2765
|
name: "@algolia/wizard",
|
|
3560
|
-
version: "0.9.0-rc.
|
|
2766
|
+
version: "0.9.0-rc.84.74",
|
|
3561
2767
|
description: "Magically implement Algolia functionality in your codebase",
|
|
3562
2768
|
type: "module",
|
|
3563
2769
|
engines: {
|
|
@@ -3579,7 +2785,7 @@ var package_default = {
|
|
|
3579
2785
|
prepare: "husky",
|
|
3580
2786
|
prepublishOnly: "pnpm build",
|
|
3581
2787
|
reset: "tsx ./scripts/reset-state.ts",
|
|
3582
|
-
"test:
|
|
2788
|
+
"test:fixtures": "touch .env && tsx --env-file=.env ./fixtures/run-fixtures.ts",
|
|
3583
2789
|
"test:tools": "tsx ./tool-evals/toolEval.ts",
|
|
3584
2790
|
test: "vitest",
|
|
3585
2791
|
typecheck: "tsc --noEmit -p tsconfig.json"
|
|
@@ -3607,7 +2813,6 @@ var package_default = {
|
|
|
3607
2813
|
"@ai-sdk/openai-compatible": "^2.0.47",
|
|
3608
2814
|
"@algolia/cli": "^5.11.0",
|
|
3609
2815
|
"@hono/node-server": "^2.0.10",
|
|
3610
|
-
"@mishieck/ink-titled-box": "^0.4.2",
|
|
3611
2816
|
"@segment/analytics-node": "^3.1.0",
|
|
3612
2817
|
ai: "^6.0.190",
|
|
3613
2818
|
dotenv: "^17.4.2",
|
|
@@ -3660,185 +2865,82 @@ function parseEntries(raw) {
|
|
|
3660
2865
|
return raw.split(",").map(clean).filter(Boolean).slice(0, MAX_ENTRIES).map((name) => ({ name, version: "unknown" }));
|
|
3661
2866
|
}
|
|
3662
2867
|
var summarize = (entries) => entries.length ? entries.map((e) => e.name).join(", ") : "none";
|
|
3663
|
-
|
|
3664
|
-
// src/actions/confirmLanguage.ts
|
|
3665
|
-
import z19 from "zod";
|
|
3666
|
-
var confirmLanguageSchema = z19.object({
|
|
3667
|
-
languages: detectLanguageSchema.shape.languages
|
|
3668
|
-
});
|
|
3669
|
-
var OTHER_OPTION = "Other";
|
|
3670
|
-
function confirmed(languages) {
|
|
3671
|
-
track("AI Wizard Language Confirmed", { languages });
|
|
3672
|
-
return { languages };
|
|
3673
|
-
}
|
|
3674
|
-
async function askOtherLanguage(ctx) {
|
|
3675
|
-
let prompt = "enter the language for your ingestion script";
|
|
2868
|
+
async function askList(ctx, prompt, { required = false } = {}) {
|
|
3676
2869
|
for (; ; ) {
|
|
3677
2870
|
const answer = await ctx.requestUserInput({
|
|
3678
2871
|
prompt,
|
|
3679
2872
|
promptType: "textInput",
|
|
3680
|
-
options: []
|
|
2873
|
+
options: [],
|
|
2874
|
+
helpText: 'Comma-separated, e.g. "TypeScript, Node".'
|
|
3681
2875
|
});
|
|
3682
2876
|
if (typeof answer !== "string") {
|
|
3683
|
-
throw new Error("
|
|
2877
|
+
throw new Error("askList received an unexpected non-text result");
|
|
3684
2878
|
}
|
|
3685
|
-
const
|
|
3686
|
-
if (
|
|
3687
|
-
prompt = "
|
|
2879
|
+
const entries = parseEntries(answer);
|
|
2880
|
+
if (entries.length || !required) return entries;
|
|
2881
|
+
prompt = "Please enter at least one entry:";
|
|
3688
2882
|
}
|
|
3689
2883
|
}
|
|
2884
|
+
|
|
2885
|
+
// src/actions/confirmLanguage.ts
|
|
2886
|
+
import z19 from "zod";
|
|
2887
|
+
var confirmLanguageSchema = z19.object({
|
|
2888
|
+
languages: detectLanguageSchema.shape.languages
|
|
2889
|
+
});
|
|
3690
2890
|
async function confirmLanguage(ctx) {
|
|
3691
2891
|
const detected = ctx.getStepOutput("project-scan");
|
|
3692
|
-
const
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
prompt: `Write the ingestion script in ${primary.name}?`,
|
|
3698
|
-
promptType: "acceptReject",
|
|
3699
|
-
options: [`Confirm ${primary.name}`, "Use a different language"],
|
|
3700
|
-
secondary: [{ kind: "badge", value: "[DETECTED]" }, void 0],
|
|
3701
|
-
messages: detectedLanguages.length > 1 ? [`Detected: ${summarize(detectedLanguages)}`] : []
|
|
3702
|
-
});
|
|
3703
|
-
if (accepted === true) return confirmed(detectedLanguages);
|
|
3704
|
-
}
|
|
3705
|
-
const options = [...CURATED_LANGUAGES];
|
|
3706
|
-
for (const language of detectedLanguages) {
|
|
3707
|
-
if (!options.some((o) => isSameLanguage(o, language.name))) {
|
|
3708
|
-
options.push(language.name);
|
|
3709
|
-
}
|
|
3710
|
-
}
|
|
3711
|
-
options.push(OTHER_OPTION);
|
|
3712
|
-
const detectedFor = (option) => detectedLanguages.find((l) => isSameLanguage(option, l.name));
|
|
3713
|
-
const secondary = options.map(
|
|
3714
|
-
(o) => detectedFor(o) ? { kind: "badge", value: "[DETECTED]" } : void 0
|
|
3715
|
-
);
|
|
3716
|
-
const defaultSelectedIndex = Math.max(
|
|
3717
|
-
options.findIndex((o) => detectedFor(o)),
|
|
3718
|
-
0
|
|
3719
|
-
);
|
|
3720
|
-
const selection = await ctx.requestUserInput({
|
|
3721
|
-
prompt: "select the language for your ingestion script",
|
|
3722
|
-
promptType: "multipleChoice",
|
|
3723
|
-
options,
|
|
3724
|
-
secondary,
|
|
3725
|
-
defaultSelectedIndex
|
|
2892
|
+
const answer = await ctx.requestUserInput({
|
|
2893
|
+
prompt: "Did we detect your language(s) correctly?",
|
|
2894
|
+
promptType: "acceptReject",
|
|
2895
|
+
options: ["Yes", "No"],
|
|
2896
|
+
messages: [`Languages: ${summarize(detected.languages)}`]
|
|
3726
2897
|
});
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
}
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
2898
|
+
const languages = answer === true ? detected.languages : await askList(ctx, "List the languages your project uses:", {
|
|
2899
|
+
required: true
|
|
2900
|
+
});
|
|
2901
|
+
track("AI Wizard Language Confirmed", {
|
|
2902
|
+
languages
|
|
2903
|
+
});
|
|
2904
|
+
return { languages };
|
|
3733
2905
|
}
|
|
3734
2906
|
|
|
3735
2907
|
// src/actions/confirmFramework.ts
|
|
3736
2908
|
import z20 from "zod";
|
|
3737
|
-
|
|
3738
|
-
// src/lib/frameworks.ts
|
|
3739
|
-
var BACKEND_ONLY_FRAMEWORK = "Backend only / API";
|
|
3740
|
-
var FRAMEWORKS = [
|
|
3741
|
-
// Frontend — InstantSearch component flavors.
|
|
3742
|
-
{ name: "Next.js", strategy: "react", aliases: ["next", "nextjs"] },
|
|
3743
|
-
{ name: "React", strategy: "react", aliases: ["reactjs"] },
|
|
3744
|
-
{ name: "Vue", strategy: "vue", aliases: ["vuejs", "nuxt", "nuxtjs"] },
|
|
3745
|
-
{ name: "Angular", strategy: "angular", aliases: ["angularjs"] },
|
|
3746
|
-
// No Svelte InstantSearch flavor exists, so it uses InstantSearch.js.
|
|
3747
|
-
{ name: "Svelte", strategy: "js", aliases: ["sveltekit"] },
|
|
3748
|
-
{
|
|
3749
|
-
name: "Vanilla JS",
|
|
3750
|
-
strategy: "js",
|
|
3751
|
-
aliases: ["vanilla", "javascript", "js", "astro", "vite"]
|
|
3752
|
-
},
|
|
3753
|
-
// Backend — Algolia's official framework integrations. Server-rendered
|
|
3754
|
-
// templates get InstantSearch.js from a CDN.
|
|
3755
|
-
{
|
|
3756
|
-
name: "Rails",
|
|
3757
|
-
strategy: "cdn-template",
|
|
3758
|
-
aliases: ["rubyonrails", "ruby on rails", "erb"]
|
|
3759
|
-
},
|
|
3760
|
-
{ name: "Django", strategy: "cdn-template", aliases: ["jinja", "jinja2"] },
|
|
3761
|
-
{ name: "Laravel", strategy: "cdn-template", aliases: ["blade"] },
|
|
3762
|
-
{ name: "Symfony", strategy: "cdn-template", aliases: ["twig"] },
|
|
3763
|
-
// Mobile — Algolia ships InstantSearch iOS/Android and Dart clients, but the
|
|
3764
|
-
// wizard can't scaffold a native UI, so it points at the docs instead.
|
|
3765
|
-
{ name: "Flutter", strategy: "none", aliases: [] },
|
|
3766
|
-
{ name: "iOS", strategy: "none", aliases: ["swiftui", "uikit"] },
|
|
3767
|
-
{ name: "Android", strategy: "none", aliases: ["jetpack compose", "compose"] },
|
|
3768
|
-
{ name: BACKEND_ONLY_FRAMEWORK, strategy: "cdn-template", aliases: [] }
|
|
3769
|
-
];
|
|
3770
|
-
var CURATED_FRAMEWORKS = FRAMEWORKS.map(
|
|
3771
|
-
(f) => f.name
|
|
3772
|
-
);
|
|
3773
|
-
var normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
3774
|
-
var ALIAS_TO_NAME = /* @__PURE__ */ new Map();
|
|
3775
|
-
for (const framework of FRAMEWORKS) {
|
|
3776
|
-
for (const alias of [framework.name, ...framework.aliases]) {
|
|
3777
|
-
ALIAS_TO_NAME.set(normalize(alias), framework.name);
|
|
3778
|
-
}
|
|
3779
|
-
}
|
|
3780
|
-
var STRATEGY_BY_NAME = new Map(FRAMEWORKS.map((f) => [f.name, f.strategy]));
|
|
3781
|
-
function canonicalFrameworkName(name) {
|
|
3782
|
-
return ALIAS_TO_NAME.get(normalize(name));
|
|
3783
|
-
}
|
|
3784
|
-
function isSameFramework(a, b) {
|
|
3785
|
-
const x = canonicalFrameworkName(a) ?? normalize(a);
|
|
3786
|
-
const y = canonicalFrameworkName(b) ?? normalize(b);
|
|
3787
|
-
return x !== "" && x === y;
|
|
3788
|
-
}
|
|
3789
|
-
function resolveSearchStrategy(frameworkName, hasJavaScriptInStack) {
|
|
3790
|
-
const canonical = frameworkName ? canonicalFrameworkName(frameworkName) : void 0;
|
|
3791
|
-
const strategy = canonical ? STRATEGY_BY_NAME.get(canonical) : void 0;
|
|
3792
|
-
if (strategy) return strategy;
|
|
3793
|
-
return hasJavaScriptInStack ? "js" : "cdn-template";
|
|
3794
|
-
}
|
|
3795
|
-
function searchDocKey(strategy) {
|
|
3796
|
-
return strategy === "cdn-template" ? "templates" : strategy;
|
|
3797
|
-
}
|
|
3798
|
-
function bundlesJavaScript(strategy) {
|
|
3799
|
-
return strategy !== "cdn-template" && strategy !== "none";
|
|
3800
|
-
}
|
|
3801
|
-
function canScaffoldSearchUI(strategy) {
|
|
3802
|
-
return strategy !== "none";
|
|
3803
|
-
}
|
|
3804
|
-
var ENV_PREFIXES = [
|
|
3805
|
-
{ aliases: ["next", "nextjs"], prefix: "NEXT_PUBLIC_" },
|
|
3806
|
-
{ aliases: ["nuxt", "nuxtjs"], prefix: "NUXT_PUBLIC_" },
|
|
3807
|
-
{ aliases: ["astro"], prefix: "PUBLIC_" },
|
|
3808
|
-
{ aliases: ["vite"], prefix: "VITE_" }
|
|
3809
|
-
];
|
|
3810
|
-
var DEFAULT_ENV_PREFIX = "PUBLIC_";
|
|
3811
|
-
function publicEnvPrefix(frameworkNames, strategy) {
|
|
3812
|
-
if (!bundlesJavaScript(strategy)) return "";
|
|
3813
|
-
const present = new Set(frameworkNames.map(normalize));
|
|
3814
|
-
for (const { aliases, prefix } of ENV_PREFIXES) {
|
|
3815
|
-
if (aliases.some((alias) => present.has(alias))) return prefix;
|
|
3816
|
-
}
|
|
3817
|
-
return DEFAULT_ENV_PREFIX;
|
|
3818
|
-
}
|
|
3819
|
-
function describeSearchTarget(strategy, frameworkName) {
|
|
3820
|
-
switch (strategy) {
|
|
3821
|
-
case "react":
|
|
3822
|
-
return "React (react-instantsearch)";
|
|
3823
|
-
case "vue":
|
|
3824
|
-
return "Vue (vue-instantsearch)";
|
|
3825
|
-
case "angular":
|
|
3826
|
-
return "Angular (angular-instantsearch)";
|
|
3827
|
-
case "js":
|
|
3828
|
-
return "plain JavaScript (InstantSearch.js)";
|
|
3829
|
-
case "cdn-template":
|
|
3830
|
-
return `${frameworkName ?? "server-rendered"} templates (InstantSearch.js via CDN)`;
|
|
3831
|
-
case "none":
|
|
3832
|
-
return frameworkName ?? "a native mobile app";
|
|
3833
|
-
}
|
|
3834
|
-
}
|
|
3835
|
-
|
|
3836
|
-
// src/actions/confirmFramework.ts
|
|
3837
2909
|
var confirmFrameworkSchema = z20.object({
|
|
3838
2910
|
frameworks: detectLanguageSchema.shape.frameworks
|
|
3839
2911
|
});
|
|
3840
|
-
var
|
|
3841
|
-
|
|
2912
|
+
var CURATED_FRAMEWORKS = [
|
|
2913
|
+
"Next.js",
|
|
2914
|
+
"React",
|
|
2915
|
+
"Vue",
|
|
2916
|
+
"Angular",
|
|
2917
|
+
"Svelte",
|
|
2918
|
+
"Vanilla JS"
|
|
2919
|
+
];
|
|
2920
|
+
var OTHER_OPTION = "Other";
|
|
2921
|
+
var normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
2922
|
+
var FRAMEWORK_ALIASES = {
|
|
2923
|
+
next: "nextjs",
|
|
2924
|
+
nextjs: "nextjs",
|
|
2925
|
+
react: "react",
|
|
2926
|
+
reactjs: "react",
|
|
2927
|
+
vue: "vue",
|
|
2928
|
+
vuejs: "vue",
|
|
2929
|
+
angular: "angular",
|
|
2930
|
+
angularjs: "angular",
|
|
2931
|
+
svelte: "svelte",
|
|
2932
|
+
sveltekit: "svelte",
|
|
2933
|
+
vanillajs: "vanillajs",
|
|
2934
|
+
vanilla: "vanillajs",
|
|
2935
|
+
javascript: "vanillajs",
|
|
2936
|
+
js: "vanillajs"
|
|
2937
|
+
};
|
|
2938
|
+
var isSameFramework = (a, b) => {
|
|
2939
|
+
const x = FRAMEWORK_ALIASES[normalize(a)] ?? normalize(a);
|
|
2940
|
+
const y = FRAMEWORK_ALIASES[normalize(b)] ?? normalize(b);
|
|
2941
|
+
return x !== "" && x === y;
|
|
2942
|
+
};
|
|
2943
|
+
function confirmed(name, version) {
|
|
3842
2944
|
const frameworks = [{ name, version: version ?? "unknown" }];
|
|
3843
2945
|
track("AI Wizard Frontend Framework Confirmed", { frameworks });
|
|
3844
2946
|
return { frameworks };
|
|
@@ -3866,7 +2968,7 @@ async function confirmFramework(ctx) {
|
|
|
3866
2968
|
for (const fw of detectedFrameworks) {
|
|
3867
2969
|
if (!options.some((o) => isSameFramework(o, fw.name))) options.push(fw.name);
|
|
3868
2970
|
}
|
|
3869
|
-
options.push(
|
|
2971
|
+
options.push(OTHER_OPTION);
|
|
3870
2972
|
const detectedFor = (option) => detectedFrameworks.find((fw) => isSameFramework(option, fw.name));
|
|
3871
2973
|
const primary = detectedFrameworks[0];
|
|
3872
2974
|
if (primary) {
|
|
@@ -3876,7 +2978,7 @@ async function confirmFramework(ctx) {
|
|
|
3876
2978
|
options: [`Confirm ${primary.name}`, "Use a different framework"],
|
|
3877
2979
|
secondary: [{ kind: "badge", value: "[DETECTED]" }, void 0]
|
|
3878
2980
|
});
|
|
3879
|
-
if (accepted === true) return
|
|
2981
|
+
if (accepted === true) return confirmed(primary.name, primary.version);
|
|
3880
2982
|
}
|
|
3881
2983
|
const secondary = options.map(
|
|
3882
2984
|
(o) => detectedFor(o) ? { kind: "badge", value: "[DETECTED]" } : void 0
|
|
@@ -3886,7 +2988,7 @@ async function confirmFramework(ctx) {
|
|
|
3886
2988
|
0
|
|
3887
2989
|
);
|
|
3888
2990
|
const selection = await ctx.requestUserInput({
|
|
3889
|
-
prompt: "select
|
|
2991
|
+
prompt: "select a framework",
|
|
3890
2992
|
promptType: "multipleChoice",
|
|
3891
2993
|
options,
|
|
3892
2994
|
secondary,
|
|
@@ -3895,10 +2997,10 @@ async function confirmFramework(ctx) {
|
|
|
3895
2997
|
if (typeof selection !== "string") {
|
|
3896
2998
|
throw new Error("confirmFramework received an unexpected non-text result");
|
|
3897
2999
|
}
|
|
3898
|
-
if (selection ===
|
|
3899
|
-
return
|
|
3000
|
+
if (selection === OTHER_OPTION) {
|
|
3001
|
+
return confirmed(await askOtherFramework(ctx));
|
|
3900
3002
|
}
|
|
3901
|
-
return
|
|
3003
|
+
return confirmed(selection, detectedFor(selection)?.version);
|
|
3902
3004
|
}
|
|
3903
3005
|
|
|
3904
3006
|
// src/actions/promptUser.ts
|
|
@@ -3991,15 +3093,15 @@ async function confirmEntities(ctx) {
|
|
|
3991
3093
|
onSubmit: () => {
|
|
3992
3094
|
}
|
|
3993
3095
|
});
|
|
3994
|
-
const
|
|
3995
|
-
if (
|
|
3096
|
+
const confirmed2 = typeof selection === "string" ? entities.filter((e) => e.name === selection) : [];
|
|
3097
|
+
if (confirmed2.length === 0) {
|
|
3996
3098
|
throw new Error("User cancelled entity selection \u2014 analysis halted.");
|
|
3997
3099
|
}
|
|
3998
|
-
ctx.setUserInput("confirmedEntities",
|
|
3100
|
+
ctx.setUserInput("confirmedEntities", confirmed2);
|
|
3999
3101
|
track("AI Wizard Entities Confirmed", {
|
|
4000
|
-
entities: toEntitySummary(
|
|
3102
|
+
entities: toEntitySummary(confirmed2)
|
|
4001
3103
|
});
|
|
4002
|
-
return { ingestionAnalysis: entities, confirmedEntities:
|
|
3104
|
+
return { ingestionAnalysis: entities, confirmedEntities: confirmed2 };
|
|
4003
3105
|
}
|
|
4004
3106
|
|
|
4005
3107
|
// src/actions/review.ts
|
|
@@ -4023,7 +3125,7 @@ ${JSON.stringify(s.output, null, 2)}`
|
|
|
4023
3125
|
}
|
|
4024
3126
|
function formatReviewSummary(result) {
|
|
4025
3127
|
const nextStepLines = result.nextSteps.map((step) => {
|
|
4026
|
-
const isIngestCommand = step.includes("algolia-wizard/
|
|
3128
|
+
const isIngestCommand = step.includes(".algolia-wizard/ingest.sh");
|
|
4027
3129
|
const isWorktreeCommand = step.includes("/worktrees/");
|
|
4028
3130
|
return {
|
|
4029
3131
|
text: `\u2192 ${step}`,
|
|
@@ -4065,14 +3167,13 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
|
|
|
4065
3167
|
import z24 from "zod";
|
|
4066
3168
|
|
|
4067
3169
|
// src/lib/worktree.ts
|
|
4068
|
-
import { execFile } from "node:child_process";
|
|
4069
|
-
import {
|
|
4070
|
-
import { copyFile, mkdir as mkdir6, readdir as readdir4, readFile as readFile9, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
|
|
3170
|
+
import { execFile, spawn as spawn3 } from "node:child_process";
|
|
3171
|
+
import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as readFile8, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
|
|
4071
3172
|
import {
|
|
4072
3173
|
basename as basename2,
|
|
4073
3174
|
dirname as dirname7,
|
|
4074
3175
|
isAbsolute as isAbsolute2,
|
|
4075
|
-
join as
|
|
3176
|
+
join as join10,
|
|
4076
3177
|
relative as relative2,
|
|
4077
3178
|
resolve as resolve3
|
|
4078
3179
|
} from "node:path";
|
|
@@ -4106,8 +3207,8 @@ async function isWorkingTreeDirty(repoRoot) {
|
|
|
4106
3207
|
return out.trim().length > 0;
|
|
4107
3208
|
}
|
|
4108
3209
|
async function pruneOldWorktrees(repoRoot) {
|
|
4109
|
-
const dir =
|
|
4110
|
-
const stale = (await
|
|
3210
|
+
const dir = join10(stateDir(repoRoot), "worktrees");
|
|
3211
|
+
const stale = (await readdir3(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
|
|
4111
3212
|
for (const slug of stale) {
|
|
4112
3213
|
const branch = slug.replace("wizard-implement-", WIZARD_BRANCH_PREFIX);
|
|
4113
3214
|
try {
|
|
@@ -4117,7 +3218,7 @@ async function pruneOldWorktrees(repoRoot) {
|
|
|
4117
3218
|
"worktree",
|
|
4118
3219
|
"remove",
|
|
4119
3220
|
"--force",
|
|
4120
|
-
|
|
3221
|
+
join10(dir, slug)
|
|
4121
3222
|
]);
|
|
4122
3223
|
await git(["-C", repoRoot, "branch", "-D", branch]);
|
|
4123
3224
|
} catch (err) {
|
|
@@ -4131,55 +3232,43 @@ async function pruneOldWorktrees(repoRoot) {
|
|
|
4131
3232
|
async function createWorktree(repoRoot) {
|
|
4132
3233
|
const branch = `${WIZARD_BRANCH_PREFIX}${Date.now()}`;
|
|
4133
3234
|
const dirSlug = branch.replace(/\//g, "-");
|
|
4134
|
-
const path =
|
|
3235
|
+
const path = join10(stateDir(repoRoot), "worktrees", dirSlug);
|
|
4135
3236
|
await git(["-C", repoRoot, "worktree", "prune"]);
|
|
4136
3237
|
await pruneOldWorktrees(repoRoot);
|
|
4137
3238
|
await mkdir6(dirname7(path), { recursive: true });
|
|
4138
3239
|
await git(["-C", repoRoot, "worktree", "add", "-b", branch, path, "HEAD"]);
|
|
4139
3240
|
return { path, branch };
|
|
4140
3241
|
}
|
|
4141
|
-
async function
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
return { ok: code === 0, output: output.trim() };
|
|
4147
|
-
}
|
|
4148
|
-
async function installWorktreeDeps(worktreePath, toolchain) {
|
|
4149
|
-
const { profile, installSteps, packageManager } = toolchain;
|
|
4150
|
-
const declared = packageManager.dependency.mode === "agent-declares" ? packageManager.dependency.file : void 0;
|
|
4151
|
-
const haveSomethingToInstall = await hasProfileManifest(worktreePath, profile) || declared !== void 0 && existsSync4(join12(worktreePath, declared));
|
|
4152
|
-
if (!haveSomethingToInstall) {
|
|
4153
|
-
return {
|
|
4154
|
-
ok: true,
|
|
4155
|
-
output: `no ${profile.displayName} manifest; skipped install`
|
|
4156
|
-
};
|
|
4157
|
-
}
|
|
4158
|
-
if (installSteps.length === 0) {
|
|
4159
|
-
return {
|
|
4160
|
-
ok: true,
|
|
4161
|
-
output: `${profile.displayName} (${toolchain.packageManager.id}) has no wizard-run install step`
|
|
4162
|
-
};
|
|
4163
|
-
}
|
|
4164
|
-
const outputs = [];
|
|
4165
|
-
for (const step of installSteps) {
|
|
4166
|
-
if (step.requiresFile && !existsSync4(join12(worktreePath, step.requiresFile)))
|
|
4167
|
-
continue;
|
|
4168
|
-
const result = await spawnStep(worktreePath, step.argv);
|
|
4169
|
-
if (result.output) outputs.push(result.output);
|
|
4170
|
-
if (result.ok) continue;
|
|
4171
|
-
if (step.optional) {
|
|
4172
|
-
logger.warn(
|
|
4173
|
-
{ step: step.argv.join(" "), output: result.output },
|
|
4174
|
-
"installWorktreeDeps: optional install step failed; continuing"
|
|
4175
|
-
);
|
|
4176
|
-
continue;
|
|
4177
|
-
}
|
|
4178
|
-
return { ok: false, output: outputs.join("\n").trim() };
|
|
3242
|
+
async function installWorktreeDeps(worktreePath) {
|
|
3243
|
+
try {
|
|
3244
|
+
await readPackageJson(worktreePath);
|
|
3245
|
+
} catch {
|
|
3246
|
+
return { ok: true, output: "no package.json; skipped install" };
|
|
4179
3247
|
}
|
|
4180
|
-
|
|
3248
|
+
const pm = await detectPackageManager(worktreePath);
|
|
3249
|
+
return new Promise((resolve4) => {
|
|
3250
|
+
let output = "";
|
|
3251
|
+
const child = spawn3(pm, ["install"], {
|
|
3252
|
+
cwd: worktreePath,
|
|
3253
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
3254
|
+
});
|
|
3255
|
+
child.stdout?.on("data", (d) => output += d);
|
|
3256
|
+
child.stderr?.on("data", (d) => output += d);
|
|
3257
|
+
child.on(
|
|
3258
|
+
"error",
|
|
3259
|
+
(err) => resolve4({
|
|
3260
|
+
ok: false,
|
|
3261
|
+
output: `Failed to run ${pm} install: ${err.message}`
|
|
3262
|
+
})
|
|
3263
|
+
);
|
|
3264
|
+
child.on(
|
|
3265
|
+
"close",
|
|
3266
|
+
(code) => resolve4({ ok: code === 0, output: output.trim() })
|
|
3267
|
+
);
|
|
3268
|
+
});
|
|
4181
3269
|
}
|
|
4182
|
-
|
|
3270
|
+
var INGEST_RUNTIMES = ["node", "python", "python3", "bun"];
|
|
3271
|
+
function validateIngestEntrypoint(worktreePath, entrypoint) {
|
|
4183
3272
|
if (!entrypoint || entrypoint.startsWith("-")) {
|
|
4184
3273
|
return {
|
|
4185
3274
|
ok: false,
|
|
@@ -4194,29 +3283,18 @@ function validateIngestEntrypoint(worktreePath, entrypoint, allowedExtensions) {
|
|
|
4194
3283
|
reason: `entrypoint "${entrypoint}" resolves outside the worktree`
|
|
4195
3284
|
};
|
|
4196
3285
|
}
|
|
4197
|
-
if (allowedExtensions?.length && !allowedExtensions.some((ext) => entrypoint.endsWith(ext))) {
|
|
4198
|
-
return {
|
|
4199
|
-
ok: false,
|
|
4200
|
-
reason: `entrypoint "${entrypoint}" is not one of ${allowedExtensions.join(", ")}`
|
|
4201
|
-
};
|
|
4202
|
-
}
|
|
4203
3286
|
return { ok: true, target };
|
|
4204
3287
|
}
|
|
4205
|
-
async function runIngestScript(worktreePath,
|
|
4206
|
-
|
|
4207
|
-
if (ingest.kind !== "auto") {
|
|
3288
|
+
async function runIngestScript(worktreePath, runtime, entrypoint, env = {}) {
|
|
3289
|
+
if (!INGEST_RUNTIMES.includes(runtime)) {
|
|
4208
3290
|
return {
|
|
4209
3291
|
ran: false,
|
|
4210
3292
|
ok: false,
|
|
4211
3293
|
output: "",
|
|
4212
|
-
reason:
|
|
3294
|
+
reason: `runtime "${runtime}" is not an allowed interpreter (${INGEST_RUNTIMES.join(", ")})`
|
|
4213
3295
|
};
|
|
4214
3296
|
}
|
|
4215
|
-
const validated = validateIngestEntrypoint(
|
|
4216
|
-
worktreePath,
|
|
4217
|
-
entrypoint,
|
|
4218
|
-
ingest.entrypointExtensions
|
|
4219
|
-
);
|
|
3297
|
+
const validated = validateIngestEntrypoint(worktreePath, entrypoint);
|
|
4220
3298
|
if (!validated.ok) {
|
|
4221
3299
|
return { ran: false, ok: false, output: "", reason: validated.reason };
|
|
4222
3300
|
}
|
|
@@ -4237,13 +3315,29 @@ async function runIngestScript(worktreePath, toolchain, entrypoint, env = {}) {
|
|
|
4237
3315
|
reason: `entrypoint "${entrypoint}" does not exist`
|
|
4238
3316
|
};
|
|
4239
3317
|
}
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
3318
|
+
return new Promise((resolveRun) => {
|
|
3319
|
+
let output = "";
|
|
3320
|
+
const child = spawn3(runtime, [entrypoint], {
|
|
3321
|
+
cwd: worktreePath,
|
|
3322
|
+
shell: false,
|
|
3323
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
3324
|
+
env: { ...process.env, ...env }
|
|
3325
|
+
});
|
|
3326
|
+
child.stdout?.on("data", (d) => output += d);
|
|
3327
|
+
child.stderr?.on("data", (d) => output += d);
|
|
3328
|
+
child.on(
|
|
3329
|
+
"error",
|
|
3330
|
+
(err) => resolveRun({
|
|
3331
|
+
ran: true,
|
|
3332
|
+
ok: false,
|
|
3333
|
+
output: `Failed to run ${runtime} ${entrypoint}: ${err.message}`
|
|
3334
|
+
})
|
|
3335
|
+
);
|
|
3336
|
+
child.on(
|
|
3337
|
+
"close",
|
|
3338
|
+
(code) => resolveRun({ ran: true, ok: code === 0, output: output.trim() })
|
|
3339
|
+
);
|
|
4245
3340
|
});
|
|
4246
|
-
return { ran: true, ok: code === 0, output: output.trim() };
|
|
4247
3341
|
}
|
|
4248
3342
|
async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourcePath) {
|
|
4249
3343
|
const trimmed = sourcePath.trim();
|
|
@@ -4258,8 +3352,8 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
|
|
|
4258
3352
|
} catch {
|
|
4259
3353
|
return { ok: false, reason: `"${sourcePath}" does not exist` };
|
|
4260
3354
|
}
|
|
4261
|
-
const relPath =
|
|
4262
|
-
const dest =
|
|
3355
|
+
const relPath = join10(ingestDir, basename2(source));
|
|
3356
|
+
const dest = join10(worktreePath, relPath);
|
|
4263
3357
|
try {
|
|
4264
3358
|
await mkdir6(dirname7(dest), { recursive: true });
|
|
4265
3359
|
await copyFile(source, dest);
|
|
@@ -4275,10 +3369,10 @@ function hasEnvVar(content, name) {
|
|
|
4275
3369
|
return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
|
|
4276
3370
|
}
|
|
4277
3371
|
async function writeSearchEnvValues(worktreePath, vars) {
|
|
4278
|
-
const target =
|
|
3372
|
+
const target = join10(worktreePath, ".env");
|
|
4279
3373
|
let existing = "";
|
|
4280
3374
|
try {
|
|
4281
|
-
existing = await
|
|
3375
|
+
existing = await readFile8(target, "utf8");
|
|
4282
3376
|
} catch (err) {
|
|
4283
3377
|
if (err.code !== "ENOENT") throw err;
|
|
4284
3378
|
}
|
|
@@ -4395,33 +3489,69 @@ async function resolveSearchOnlyKey(index) {
|
|
|
4395
3489
|
}
|
|
4396
3490
|
|
|
4397
3491
|
// src/lib/algoliaDocs.ts
|
|
4398
|
-
import { readFileSync, existsSync as
|
|
4399
|
-
import { dirname as dirname8, join as
|
|
3492
|
+
import { readFileSync, readdirSync, existsSync as existsSync2 } from "node:fs";
|
|
3493
|
+
import { dirname as dirname8, join as join11 } from "node:path";
|
|
4400
3494
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
4401
|
-
var DOCS_SUBPATH =
|
|
3495
|
+
var DOCS_SUBPATH = join11("docs", "algolia-sdk");
|
|
4402
3496
|
function findDocsDir() {
|
|
4403
3497
|
let dir = dirname8(fileURLToPath2(import.meta.url));
|
|
4404
3498
|
for (; ; ) {
|
|
4405
|
-
const candidate =
|
|
4406
|
-
if (
|
|
3499
|
+
const candidate = join11(dir, DOCS_SUBPATH);
|
|
3500
|
+
if (existsSync2(candidate)) return candidate;
|
|
4407
3501
|
const parent = dirname8(dir);
|
|
4408
3502
|
if (parent === dir) return void 0;
|
|
4409
3503
|
dir = parent;
|
|
4410
3504
|
}
|
|
4411
3505
|
}
|
|
4412
|
-
function
|
|
3506
|
+
function loadAlgoliaDoc(language) {
|
|
3507
|
+
const docsDir = findDocsDir();
|
|
3508
|
+
if (!docsDir) {
|
|
3509
|
+
logger.warn(
|
|
3510
|
+
"algoliaDocs: docs/algolia-sdk not found; skipping SDK reference"
|
|
3511
|
+
);
|
|
3512
|
+
return "";
|
|
3513
|
+
}
|
|
3514
|
+
const files = readdirSync(docsDir).filter((f) => f.includes(language));
|
|
3515
|
+
if (files.length === 0) {
|
|
3516
|
+
logger.warn(
|
|
3517
|
+
{ language },
|
|
3518
|
+
"algoliaDocs: no SDK reference found for language; skipping"
|
|
3519
|
+
);
|
|
3520
|
+
return "";
|
|
3521
|
+
}
|
|
3522
|
+
return readFileSync(join11(docsDir, files[0]), "utf8").trim();
|
|
3523
|
+
}
|
|
3524
|
+
function getNamedDoc(name, language) {
|
|
4413
3525
|
const docsDir = findDocsDir();
|
|
4414
3526
|
if (!docsDir) {
|
|
4415
3527
|
logger.warn("docs/algolia-sdk not found");
|
|
4416
3528
|
return "";
|
|
4417
3529
|
}
|
|
4418
|
-
const file =
|
|
4419
|
-
if (!
|
|
4420
|
-
logger.warn({ name,
|
|
3530
|
+
const file = join11(docsDir, `${name}-${language}.md`);
|
|
3531
|
+
if (!existsSync2(file)) {
|
|
3532
|
+
logger.warn({ name, language }, "named SDK reference not found");
|
|
4421
3533
|
return "";
|
|
4422
3534
|
}
|
|
4423
3535
|
return readFileSync(file, "utf8").trim();
|
|
4424
3536
|
}
|
|
3537
|
+
function getFrameworkSpecificDoc(frameworks) {
|
|
3538
|
+
const fw = frameworks.map((f) => f.toLowerCase());
|
|
3539
|
+
if (fw.includes("vue") || fw.includes("nuxt")) {
|
|
3540
|
+
return loadAlgoliaDoc("vue");
|
|
3541
|
+
}
|
|
3542
|
+
if (fw.includes("react") || fw.includes("next.js")) {
|
|
3543
|
+
return loadAlgoliaDoc("react");
|
|
3544
|
+
}
|
|
3545
|
+
if (fw.includes("angular")) {
|
|
3546
|
+
return loadAlgoliaDoc("angular");
|
|
3547
|
+
}
|
|
3548
|
+
return loadAlgoliaDoc("js");
|
|
3549
|
+
}
|
|
3550
|
+
|
|
3551
|
+
// src/lib/shell.ts
|
|
3552
|
+
function shellQuote(value) {
|
|
3553
|
+
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
3554
|
+
}
|
|
4425
3555
|
|
|
4426
3556
|
// src/actions/implement.ts
|
|
4427
3557
|
var implementSchema = z24.object({
|
|
@@ -4456,11 +3586,12 @@ var implementSchema = z24.object({
|
|
|
4456
3586
|
});
|
|
4457
3587
|
var implementationOutputSchema = z24.object({
|
|
4458
3588
|
summary: z24.string(),
|
|
4459
|
-
// Ingestion only:
|
|
4460
|
-
//
|
|
4461
|
-
//
|
|
4462
|
-
//
|
|
4463
|
-
//
|
|
3589
|
+
// Ingestion only: how to run the generated script, as a structured pair the
|
|
3590
|
+
// wizard turns into an argv (`<runtime> <entrypoint>`) — never a free-form
|
|
3591
|
+
// command string. `runtime` is constrained to an allowlisted interpreter and
|
|
3592
|
+
// `entrypoint` is validated to a worktree-relative path before execution, so
|
|
3593
|
+
// the agent cannot inject extra commands or swap the interpreter.
|
|
3594
|
+
runtime: z24.enum(INGEST_RUNTIMES).optional(),
|
|
4464
3595
|
entrypoint: z24.string().optional()
|
|
4465
3596
|
});
|
|
4466
3597
|
var verificationOutputSchema = z24.object({
|
|
@@ -4470,11 +3601,47 @@ var verificationOutputSchema = z24.object({
|
|
|
4470
3601
|
});
|
|
4471
3602
|
var MAX_IMPLEMENT_VERIFICATION_ATTEMPTS = 3;
|
|
4472
3603
|
var DEFAULT_IMPLEMENT_USE_CASES = ["ingestion", "search"];
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
3604
|
+
var INGEST_DIR = ".algolia-wizard";
|
|
3605
|
+
function detectUiFramework(language) {
|
|
3606
|
+
const names = language.frameworks.map((f) => f.name.toLowerCase());
|
|
3607
|
+
if (names.some((n) => n.includes("vue") || n.includes("nuxt"))) return "Vue";
|
|
3608
|
+
if (names.some((n) => n.includes("react") || n.includes("next")))
|
|
3609
|
+
return "React";
|
|
3610
|
+
if (names.some((n) => n.includes("angular"))) return "Angular";
|
|
3611
|
+
return "JavaScript";
|
|
3612
|
+
}
|
|
3613
|
+
function frameworksForDoc(framework) {
|
|
3614
|
+
switch (framework) {
|
|
3615
|
+
case "React":
|
|
3616
|
+
return ["react"];
|
|
3617
|
+
case "Vue":
|
|
3618
|
+
return ["vue"];
|
|
3619
|
+
case "Angular":
|
|
3620
|
+
return ["angular"];
|
|
3621
|
+
case "JavaScript":
|
|
3622
|
+
return [];
|
|
3623
|
+
}
|
|
3624
|
+
}
|
|
3625
|
+
function publicEnvPrefix(language) {
|
|
3626
|
+
const frameworkNames = language.frameworks.map(
|
|
3627
|
+
(framework) => framework.name.toLowerCase()
|
|
4477
3628
|
);
|
|
3629
|
+
if (frameworkNames.some((name) => name.includes("next"))) {
|
|
3630
|
+
return "NEXT_PUBLIC_";
|
|
3631
|
+
}
|
|
3632
|
+
if (frameworkNames.some((name) => name.includes("nuxt"))) {
|
|
3633
|
+
return "NUXT_PUBLIC_";
|
|
3634
|
+
}
|
|
3635
|
+
if (frameworkNames.some((name) => name.includes("astro"))) {
|
|
3636
|
+
return "PUBLIC_";
|
|
3637
|
+
}
|
|
3638
|
+
if (frameworkNames.some((name) => name.includes("vite"))) {
|
|
3639
|
+
return "VITE_";
|
|
3640
|
+
}
|
|
3641
|
+
return "PUBLIC_";
|
|
3642
|
+
}
|
|
3643
|
+
function searchEnvVars(language, appId, searchKey) {
|
|
3644
|
+
const prefix = publicEnvPrefix(language);
|
|
4478
3645
|
return [
|
|
4479
3646
|
{
|
|
4480
3647
|
name: `${prefix}ALGOLIA_APP_ID`,
|
|
@@ -4486,38 +3653,6 @@ function buildSearchEnvVars(language, strategy, appId, searchKey) {
|
|
|
4486
3653
|
}
|
|
4487
3654
|
];
|
|
4488
3655
|
}
|
|
4489
|
-
async function resolveIngestionProfile(ctx, language, repoRoot) {
|
|
4490
|
-
const { candidates, confirmed: confirmed3, onDisk } = await pickIngestionCandidates(
|
|
4491
|
-
repoRoot,
|
|
4492
|
-
language.languages.map((l) => l.name)
|
|
4493
|
-
);
|
|
4494
|
-
if (candidates.length === 0) {
|
|
4495
|
-
const chosen = confirmed3[0] ?? onDisk[0] ?? LANGUAGE_PROFILES[DEFAULT_LANGUAGE_ID];
|
|
4496
|
-
logger.warn(
|
|
4497
|
-
{
|
|
4498
|
-
confirmed: language.languages.map((l) => l.name),
|
|
4499
|
-
onDisk: onDisk.map((p) => p.id),
|
|
4500
|
-
chosen: chosen.id
|
|
4501
|
-
},
|
|
4502
|
-
"implement: no confirmed language matched a manifest on disk; falling back"
|
|
4503
|
-
);
|
|
4504
|
-
return chosen;
|
|
4505
|
-
}
|
|
4506
|
-
if (candidates.length === 1) return candidates[0];
|
|
4507
|
-
const backends = candidates.filter(isBackendLanguage);
|
|
4508
|
-
if (backends.length === 1) return backends[0];
|
|
4509
|
-
if (backends.length === 0) return candidates[0];
|
|
4510
|
-
if (isBackendLanguage(candidates[0])) return candidates[0];
|
|
4511
|
-
const options = backends.map((p) => p.displayName);
|
|
4512
|
-
const selection = await ctx.requestUserInput({
|
|
4513
|
-
prompt: "Which language should the ingestion script use?",
|
|
4514
|
-
promptType: "multipleChoice",
|
|
4515
|
-
options,
|
|
4516
|
-
defaultSelectedIndex: 0
|
|
4517
|
-
});
|
|
4518
|
-
const picked = typeof selection === "string" ? backends.find((p) => p.displayName === selection) : void 0;
|
|
4519
|
-
return picked ?? backends[0];
|
|
4520
|
-
}
|
|
4521
3656
|
function baseInstructions(input) {
|
|
4522
3657
|
return [
|
|
4523
3658
|
`Target Algolia index: ${input.targetIndex}`,
|
|
@@ -4545,48 +3680,37 @@ function sourceSpecificInstructions(input) {
|
|
|
4545
3680
|
generated: [
|
|
4546
3681
|
"No real data source exists; use sample records for each confirmed entity.",
|
|
4547
3682
|
"Call the generateRecord tool once per entity (entityName, attributes, count 20-50); it invents the values and unique objectIDs and writes them to a JSON file in the worktree, returning the file path. Do not write records or objectIDs yourself.",
|
|
4548
|
-
"In the script, read and parse each returned
|
|
3683
|
+
"In the script, read and parse each returned file path at runtime (e.g. JSON.parse(readFileSync(...)) in Node/Bun, json.load(open(...)) in Python) instead of inlining the records as literals.",
|
|
4549
3684
|
"Add a prominent TODO where the developer swaps the generated records (and the JSON file under `.algolia-wizard/data/`) for their real record source."
|
|
4550
3685
|
]
|
|
4551
3686
|
};
|
|
4552
3687
|
return byLine[input.ingestionSource];
|
|
4553
3688
|
}
|
|
4554
3689
|
function ingestionInstructions(input) {
|
|
4555
|
-
const { ingestionProfile: profile, toolchain } = input;
|
|
4556
|
-
const { ingest } = toolchain;
|
|
4557
|
-
const extensions = ingest.entrypointExtensions.join(", ");
|
|
4558
|
-
const runInstruction = ingest.kind === "auto" ? `Return "entrypoint": the script path relative to the worktree root (e.g. "${profile.ingestEntrypointExample}"), ending in one of ${extensions}. The wizard runs it with \`${describeIngestCommand(ingest, profile.ingestEntrypointExample)}\`, so it must be a plain path with no flags or arguments and must run as-is under that command.` : `Return "entrypoint": the script path relative to the worktree root (e.g. "${profile.ingestEntrypointExample}"), ending in one of ${extensions}. The wizard does NOT run ${profile.displayName} ${toolchain.packageManager.id} projects itself \u2014 it tells the developer to run \`${describeIngestCommand(ingest, profile.ingestEntrypointExample)}\`, so also add whatever build configuration that command needs${ingest.kind === "manual" && ingest.requiresBuildTask ? `, including a "${ingest.requiresBuildTask}" task in "${toolchain.packageManager.dependency.mode === "agent-declares" ? toolchain.packageManager.dependency.file : "the build file"}" that runs the script` : ""}.`;
|
|
4559
3690
|
return [
|
|
4560
3691
|
...input.confirmed && input.confirmed.length ? [
|
|
4561
|
-
`
|
|
3692
|
+
`Create an ingestion script under "${input.ingestDir}/" at the repo root.`,
|
|
4562
3693
|
`Ingest only these confirmed entities (name, source paths, attributes): ${JSON.stringify(input.confirmed)}.`,
|
|
4563
|
-
`Ingesting writes to Algolia, so the script needs a write API key and App ID \u2014 read them from the ${API_KEY_VAR} and ${APP_ID_VAR} environment variables rather than hardcoding them.
|
|
4564
|
-
|
|
3694
|
+
`Ingesting writes to Algolia, so the script needs a write API key and App ID \u2014 read them from the ${API_KEY_VAR} and ${APP_ID_VAR} environment variables rather than hardcoding them. The wizard sets these when it runs the script.`,
|
|
3695
|
+
"Use the appropriate Algolia package in the ingestion script. Do not use the raw HTTP API.",
|
|
4565
3696
|
"After a successful ingest, the script must print exactly one line to stdout in the form `ALGOLIA_WIZARD_RECORD_COUNT=<n>`, where <n> is the total number of records pushed to Algolia. Print it last, on its own line, with no surrounding text.",
|
|
4566
|
-
getNamedDoc("save-records",
|
|
4567
|
-
|
|
3697
|
+
getNamedDoc("save-records", "js"),
|
|
3698
|
+
'Add algoliasearch to package.json "dependencies" with a valid version range; the wizard installs the worktree deps after you finish.',
|
|
4568
3699
|
"The summary should be extremely concise.",
|
|
4569
|
-
|
|
3700
|
+
`Return how to run the script as two fields, not a command string: "runtime" (one of ${INGEST_RUNTIMES.join(", ")}) and "entrypoint" (the script path relative to the worktree root, e.g. "${input.ingestDir}/ingest.mjs"). The wizard runs \`<runtime> <entrypoint>\` directly, so the entrypoint must be a plain path with no flags or arguments. Write a script one of those interpreters can run as-is.`,
|
|
4570
3701
|
...sourceSpecificInstructions(input)
|
|
4571
3702
|
] : []
|
|
4572
3703
|
];
|
|
4573
3704
|
}
|
|
4574
3705
|
function searchInstructions(input) {
|
|
4575
|
-
const doc =
|
|
4576
|
-
"instantsearch-setup",
|
|
4577
|
-
searchDocKey(input.searchStrategy)
|
|
4578
|
-
);
|
|
4579
|
-
const isTemplate = input.searchStrategy === "cdn-template";
|
|
4580
|
-
const placement = isTemplate ? input.searchLocation ? `Add the search UI to the server-rendered template at "${input.searchLocation}" \u2014 ideally a shared layout, so it is reachable across the app.` : `This project has no shared template to host the UI, so create a standalone page at "${input.ingestDir}/search-demo.html" the developer can open directly, and add a TODO explaining how to move the snippet into their own layout.` : `Add the search UI at ${input.searchLocation ? `"${input.searchLocation}"` : "the best shared, always-rendered layout location (e.g. a header/nav component)"} so it is reachable across the app.`;
|
|
3706
|
+
const doc = getFrameworkSpecificDoc(frameworksForDoc(input.uiFramework));
|
|
4581
3707
|
return [
|
|
4582
3708
|
"Implement an in-app Algolia search experience.",
|
|
4583
|
-
`Build the search UI for ${
|
|
4584
|
-
"Follow the Algolia reference below for client setup and InstantSearch wiring; prefer it over prior knowledge:",
|
|
3709
|
+
`Build the search UI for ${input.uiFramework}.`,
|
|
3710
|
+
"Follow the Algolia JS SDK reference below for client setup and InstantSearch wiring; prefer it over prior knowledge:",
|
|
4585
3711
|
doc,
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
isTemplate ? "Load InstantSearch from a CDN with script tags as shown in the reference. Do not add JavaScript package dependencies, a bundler, or a build step." : 'Add any Algolia/InstantSearch packages you import to package.json "dependencies" with a valid version range; the wizard installs them in the worktree after you finish.',
|
|
4589
|
-
isTemplate ? "Read the App ID and search-only API key from server-side configuration/environment and render them into the page (e.g. as data- attributes the script reads); never hardcode them, and never put a write/admin key in HTML." : "Read the App ID and a search-only API key from public env vars; never hardcode them. A search-only key is safe to expose client-side.",
|
|
3712
|
+
`Add the search UI at ${input.searchLocation ? `"${input.searchLocation}"` : "the best shared, always-rendered layout location (e.g. a header/nav component)"} so it is reachable across the app \u2014 at least a working SearchBox and Hits against the "${input.targetIndex}" index.`,
|
|
3713
|
+
"Read the App ID and a search-only API key from public env vars; never hardcode them. A search-only key is safe to expose client-side.",
|
|
4590
3714
|
// appId always resolves (loadActiveProfile throws otherwise); only the
|
|
4591
3715
|
// search-only key is best-effort and can fall back to a placeholder.
|
|
4592
3716
|
`Values: App ID "${input.appId}", search-only key ${input.searchKey ? `"${input.searchKey}"` : "(placeholder for the developer to fill in)"}.`,
|
|
@@ -4594,22 +3718,20 @@ function searchInstructions(input) {
|
|
|
4594
3718
|
// resolved app id / search-only key into ".env" under these exact names
|
|
4595
3719
|
// right after this step, so a renamed prefix here would leave the code
|
|
4596
3720
|
// reading a var the wizard never wrote.
|
|
4597
|
-
`Use exactly these env var names: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
|
|
3721
|
+
`Use exactly these public env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
|
|
3722
|
+
'Add any Algolia/InstantSearch packages you import to package.json "dependencies" with a valid version range; the wizard installs them in the worktree after you finish.',
|
|
4598
3723
|
"The summary should be extremely concise; do not mention env var setup or manual testing steps \u2014 the wizard writes the resolved credentials to .env and reports that separately."
|
|
4599
3724
|
];
|
|
4600
3725
|
}
|
|
4601
3726
|
function verificationInstructions(input) {
|
|
4602
|
-
const protectedDirs = [
|
|
4603
|
-
.../* @__PURE__ */ new Set([input.ingestDir, ingestScriptDir(input.ingestionProfile)])
|
|
4604
|
-
];
|
|
4605
3727
|
return [
|
|
4606
3728
|
"Verify the Algolia implementation changes in the current worktree.",
|
|
4607
3729
|
`Verification tools found in the codebase: ${JSON.stringify(input.findings.verification ?? [])}.`,
|
|
4608
|
-
|
|
3730
|
+
"Call verifyImplementation at least once; it runs every repo-defined lint/typecheck/check script and returns per-check results plus an aggregate ok.",
|
|
4609
3731
|
"For issues caused by the implementation, make minimal fixes with writeFile and re-run verifyImplementation.",
|
|
4610
3732
|
"Do not make speculative fixes when verifyImplementation cannot run, no checks exist, or failures are unrelated to these changes \u2014 note the limitation in your summary.",
|
|
4611
3733
|
"Do not add new Algolia functionality here; only validate and make minimal correctness fixes.",
|
|
4612
|
-
`Do not modify ${
|
|
3734
|
+
`Do not modify "${input.ingestDir}/" unless verifyImplementation reports an actionable issue in its files.`,
|
|
4613
3735
|
"Always call reportStatus with status=success once verification has run, even when sufficient=false.",
|
|
4614
3736
|
"Set sufficient=true only when the implementation is complete and checks pass (or fail for a clearly unrelated reason).",
|
|
4615
3737
|
"Set sufficient=false when the implementation is incomplete or has implementation-caused failures; include concrete additionalInstructions for the next pass."
|
|
@@ -4618,17 +3740,14 @@ function verificationInstructions(input) {
|
|
|
4618
3740
|
var IMPLEMENT_CONFIG = {
|
|
4619
3741
|
ingestion: {
|
|
4620
3742
|
title: "Algolia ingestion",
|
|
4621
|
-
label: "Ingestion",
|
|
4622
3743
|
buildInstructions: ingestionInstructions
|
|
4623
3744
|
},
|
|
4624
3745
|
search: {
|
|
4625
3746
|
title: "Algolia search",
|
|
4626
|
-
label: "Search",
|
|
4627
3747
|
buildInstructions: searchInstructions
|
|
4628
3748
|
},
|
|
4629
3749
|
verification: {
|
|
4630
3750
|
title: "Algolia verification",
|
|
4631
|
-
label: "Verification",
|
|
4632
3751
|
buildInstructions: verificationInstructions
|
|
4633
3752
|
}
|
|
4634
3753
|
};
|
|
@@ -4660,10 +3779,11 @@ function buildAgentInstructions(useCase, input, extraInstructions = []) {
|
|
|
4660
3779
|
];
|
|
4661
3780
|
}
|
|
4662
3781
|
function formatSummary(useCase, summary) {
|
|
4663
|
-
|
|
3782
|
+
const label = useCase === "ingestion" ? "Ingestion" : useCase === "search" ? "Search" : "Verification";
|
|
3783
|
+
return `${label}: ${summary}`;
|
|
4664
3784
|
}
|
|
4665
|
-
function buildIngestCommand(worktree,
|
|
4666
|
-
return `cd ${shellQuote(worktree)} && ${
|
|
3785
|
+
function buildIngestCommand(worktree, runtime, entrypoint) {
|
|
3786
|
+
return `cd ${shellQuote(worktree)} && ${runtime} ${shellQuote(entrypoint)}`;
|
|
4667
3787
|
}
|
|
4668
3788
|
function parseIngestRecordCount(output) {
|
|
4669
3789
|
const match = output.match(/ALGOLIA_WIZARD_RECORD_COUNT=(\d+)/);
|
|
@@ -4745,7 +3865,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4745
3865
|
await confirmDirtyWorkingTree(ctx, repoRoot);
|
|
4746
3866
|
}
|
|
4747
3867
|
const normalized = normalizeFindingPaths(findings);
|
|
4748
|
-
const
|
|
3868
|
+
const confirmed2 = normalized.confirmedEntities;
|
|
4749
3869
|
const searchLocation = normalized.searchImplementationAnalysis;
|
|
4750
3870
|
let appId;
|
|
4751
3871
|
let searchKey;
|
|
@@ -4783,66 +3903,31 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4783
3903
|
);
|
|
4784
3904
|
}
|
|
4785
3905
|
}
|
|
4786
|
-
const ingestionProfile = await resolveIngestionProfile(
|
|
4787
|
-
ctx,
|
|
4788
|
-
language,
|
|
4789
|
-
worktree
|
|
4790
|
-
);
|
|
4791
|
-
const toolchain = await resolveToolchain(worktree, ingestionProfile);
|
|
4792
|
-
const verificationLanguages = [
|
|
4793
|
-
.../* @__PURE__ */ new Set([
|
|
4794
|
-
ingestionProfile.id,
|
|
4795
|
-
...(await detectProfilesFromManifests(worktree)).map((p) => p.id)
|
|
4796
|
-
])
|
|
4797
|
-
];
|
|
4798
|
-
const frameworkName = language.frameworks[0]?.name;
|
|
4799
|
-
const searchStrategy = resolveSearchStrategy(
|
|
4800
|
-
frameworkName,
|
|
4801
|
-
verificationLanguages.includes(JAVASCRIPT)
|
|
4802
|
-
);
|
|
4803
|
-
logger.info(
|
|
4804
|
-
{
|
|
4805
|
-
language: ingestionProfile.id,
|
|
4806
|
-
packageManager: toolchain.packageManager.id,
|
|
4807
|
-
ingest: toolchain.ingest.kind,
|
|
4808
|
-
framework: frameworkName,
|
|
4809
|
-
searchStrategy
|
|
4810
|
-
},
|
|
4811
|
-
"implement: resolved ingestion toolchain and search strategy"
|
|
4812
|
-
);
|
|
4813
3906
|
const input = {
|
|
4814
3907
|
findings: normalized,
|
|
4815
|
-
confirmed:
|
|
3908
|
+
confirmed: confirmed2,
|
|
4816
3909
|
searchLocation,
|
|
4817
3910
|
targetIndex,
|
|
4818
3911
|
language,
|
|
4819
3912
|
appId,
|
|
4820
3913
|
searchKey,
|
|
4821
|
-
searchEnvVars:
|
|
4822
|
-
language,
|
|
4823
|
-
searchStrategy,
|
|
4824
|
-
appId,
|
|
4825
|
-
searchKey
|
|
4826
|
-
),
|
|
3914
|
+
searchEnvVars: searchEnvVars(language, appId, searchKey),
|
|
4827
3915
|
ingestDir: INGEST_DIR,
|
|
4828
3916
|
ingestionSource,
|
|
4829
3917
|
uploadFilePath,
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
toolchain,
|
|
4834
|
-
verificationLanguages
|
|
3918
|
+
// language.frameworks already prefers the confirm-framework step output,
|
|
3919
|
+
// so the user's confirmed stack (not just raw detection) picks the flavor.
|
|
3920
|
+
uiFramework: detectUiFramework(language)
|
|
4835
3921
|
};
|
|
4836
|
-
const searchToolchain = !bundlesJavaScript(searchStrategy) ? void 0 : ingestionProfile.id === JAVASCRIPT ? toolchain : await resolveToolchain(worktree, LANGUAGE_PROFILES[JAVASCRIPT]);
|
|
4837
|
-
const toolchainForUseCase = (useCase) => useCase === "search" ? searchToolchain : toolchain;
|
|
4838
3922
|
const summaries = [];
|
|
4839
3923
|
if (uploadWarning) summaries.push(uploadWarning);
|
|
4840
3924
|
let agentRuns = 0;
|
|
3925
|
+
let ingestRuntime;
|
|
4841
3926
|
let ingestEntrypoint;
|
|
4842
3927
|
let ingestScriptRan = false;
|
|
4843
3928
|
let ingestRecordCount;
|
|
4844
3929
|
let ingestDurationMs;
|
|
4845
|
-
|
|
3930
|
+
let installFailed = false;
|
|
4846
3931
|
let ingestOutcomeMessage;
|
|
4847
3932
|
async function runImplementationUseCase(currentUseCase, extraInstructions = []) {
|
|
4848
3933
|
if (agentRuns > 0) ctx.recordStepExecution();
|
|
@@ -4856,19 +3941,16 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4856
3941
|
tools: toolsForUseCase(currentUseCase, input.ingestionSource),
|
|
4857
3942
|
outputSchema: implementationOutputSchema
|
|
4858
3943
|
});
|
|
4859
|
-
const useCaseToolchain = toolchainForUseCase(currentUseCase);
|
|
4860
|
-
if (!useCaseToolchain) return result;
|
|
4861
3944
|
ctx.notify({
|
|
4862
3945
|
messages: [`Installing dependencies for ${currentUseCase}\u2026`]
|
|
4863
3946
|
});
|
|
4864
3947
|
const installLogId = ctx.logStart("installWorktreeDeps", {
|
|
4865
|
-
useCase: currentUseCase
|
|
4866
|
-
language: useCaseToolchain.profile.id
|
|
3948
|
+
useCase: currentUseCase
|
|
4867
3949
|
});
|
|
4868
|
-
const install = await installWorktreeDeps(worktree
|
|
3950
|
+
const install = await installWorktreeDeps(worktree);
|
|
4869
3951
|
ctx.logEnd(installLogId, install.ok ? "success" : "error");
|
|
4870
3952
|
if (!install.ok) {
|
|
4871
|
-
|
|
3953
|
+
installFailed = true;
|
|
4872
3954
|
logger.warn(
|
|
4873
3955
|
{ useCase: currentUseCase, output: install.output },
|
|
4874
3956
|
"implement: dependency install in worktree failed; generated commands may not run until deps are installed"
|
|
@@ -4882,16 +3964,15 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4882
3964
|
return runAgent({
|
|
4883
3965
|
instructions: buildAgentInstructions("verification", input),
|
|
4884
3966
|
tools: toolsForUseCase("verification"),
|
|
4885
|
-
outputSchema: verificationOutputSchema
|
|
4886
|
-
// So verifyImplementation runs this repo's checks, not just npm scripts.
|
|
4887
|
-
languages: input.verificationLanguages
|
|
3967
|
+
outputSchema: verificationOutputSchema
|
|
4888
3968
|
});
|
|
4889
3969
|
}
|
|
4890
3970
|
if (useCases.includes("ingestion")) {
|
|
4891
|
-
const { summary, entrypoint } = await runImplementationUseCase("ingestion");
|
|
3971
|
+
const { summary, runtime, entrypoint } = await runImplementationUseCase("ingestion");
|
|
4892
3972
|
summaries.push(formatSummary("ingestion", summary));
|
|
3973
|
+
ingestRuntime = runtime;
|
|
4893
3974
|
ingestEntrypoint = entrypoint;
|
|
4894
|
-
if (
|
|
3975
|
+
if (ingestRuntime && ingestEntrypoint && !installFailed) {
|
|
4895
3976
|
ctx.clearNotices();
|
|
4896
3977
|
const runNow = await ctx.requestUserInput({
|
|
4897
3978
|
prompt: `Run the ingestion script now? This writes records to the "${targetIndex}" index.`,
|
|
@@ -4903,13 +3984,13 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4903
3984
|
const profile = await loadActiveProfile();
|
|
4904
3985
|
ctx.notify({ messages: [`Writing records to "${targetIndex}"\u2026`] });
|
|
4905
3986
|
const scriptLogId = ctx.logStart("runIngestScript", {
|
|
4906
|
-
|
|
3987
|
+
runtime: ingestRuntime,
|
|
4907
3988
|
entrypoint: ingestEntrypoint
|
|
4908
3989
|
});
|
|
4909
3990
|
const startedAt = Date.now();
|
|
4910
3991
|
const run2 = await runIngestScript(
|
|
4911
3992
|
worktree,
|
|
4912
|
-
|
|
3993
|
+
ingestRuntime,
|
|
4913
3994
|
ingestEntrypoint,
|
|
4914
3995
|
{
|
|
4915
3996
|
[APP_ID_VAR]: profile.appId,
|
|
@@ -4923,7 +4004,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4923
4004
|
ingestRecordCount = parseIngestRecordCount(run2.output);
|
|
4924
4005
|
if (ingestRecordCount != null) {
|
|
4925
4006
|
track("AI Wizard Ingest Successful", {
|
|
4926
|
-
entity_name:
|
|
4007
|
+
entity_name: confirmed2?.map((e) => e.name).join(", ") || "unknown",
|
|
4927
4008
|
record_count: ingestRecordCount,
|
|
4928
4009
|
duration_ms: ingestDurationMs
|
|
4929
4010
|
});
|
|
@@ -4936,7 +4017,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4936
4017
|
outcomeMessage = `\u26A0\uFE0F The ingestion script did not run: ${run2.reason}`;
|
|
4937
4018
|
logger.warn(
|
|
4938
4019
|
{
|
|
4939
|
-
|
|
4020
|
+
runtime: ingestRuntime,
|
|
4940
4021
|
entrypoint: ingestEntrypoint,
|
|
4941
4022
|
reason: run2.reason
|
|
4942
4023
|
},
|
|
@@ -4959,7 +4040,7 @@ ${run2.output}` : status;
|
|
|
4959
4040
|
outcomeMessage = `\u274C Ingestion failed.${run2.output ? ` ${run2.output}` : ""}`;
|
|
4960
4041
|
logger.warn(
|
|
4961
4042
|
{
|
|
4962
|
-
|
|
4043
|
+
runtime: ingestRuntime,
|
|
4963
4044
|
entrypoint: ingestEntrypoint,
|
|
4964
4045
|
output: run2.output
|
|
4965
4046
|
},
|
|
@@ -4976,28 +4057,10 @@ ${run2.output}` : status;
|
|
|
4976
4057
|
}
|
|
4977
4058
|
}
|
|
4978
4059
|
const commandMessages = [`Open the worktree: cd ${shellQuote(worktree)}`];
|
|
4979
|
-
if (ingestEntrypoint) {
|
|
4060
|
+
if (ingestRuntime && ingestEntrypoint) {
|
|
4980
4061
|
commandMessages.push(
|
|
4981
|
-
`Ingestion command: ${buildIngestCommand(worktree,
|
|
4062
|
+
`Ingestion command: ${buildIngestCommand(worktree, ingestRuntime, ingestEntrypoint)}`
|
|
4982
4063
|
);
|
|
4983
|
-
if (toolchain.ingest.kind === "manual") {
|
|
4984
|
-
commandMessages.push(
|
|
4985
|
-
`The wizard does not run ${ingestionProfile.displayName} ${toolchain.packageManager.id} projects \u2014 run the command above yourself to ingest.`
|
|
4986
|
-
);
|
|
4987
|
-
const missingTask = await missingBuildTask(worktree, toolchain);
|
|
4988
|
-
if (missingTask) {
|
|
4989
|
-
const warning = `\u26A0\uFE0F The command above needs a "${missingTask}" task, which is not in ${toolchain.packageManager.dependency.mode === "agent-declares" ? toolchain.packageManager.dependency.file : "the build file"} \u2014 add it before running, or run the script through your IDE instead.`;
|
|
4990
|
-
commandMessages.push(warning);
|
|
4991
|
-
summaries.push(warning);
|
|
4992
|
-
}
|
|
4993
|
-
}
|
|
4994
|
-
}
|
|
4995
|
-
if (ingestionSource === "local") {
|
|
4996
|
-
const limitation = localSourceLimitation(worktree, ingestionProfile);
|
|
4997
|
-
if (limitation) {
|
|
4998
|
-
commandMessages.push(`\u26A0\uFE0F ${limitation}`);
|
|
4999
|
-
summaries.push(`\u26A0\uFE0F ${limitation}`);
|
|
5000
|
-
}
|
|
5001
4064
|
}
|
|
5002
4065
|
await ctx.requestUserInput({
|
|
5003
4066
|
// No question being asked here, just an acknowledgement — the
|
|
@@ -5008,20 +4071,7 @@ ${run2.output}` : status;
|
|
|
5008
4071
|
messages: ingestOutcomeMessage ? [ingestOutcomeMessage, ...commandMessages] : commandMessages
|
|
5009
4072
|
});
|
|
5010
4073
|
}
|
|
5011
|
-
|
|
5012
|
-
if (skipSearch) {
|
|
5013
|
-
const target = describeSearchTarget(
|
|
5014
|
-
input.searchStrategy,
|
|
5015
|
-
input.frameworkName
|
|
5016
|
-
);
|
|
5017
|
-
summaries.push(
|
|
5018
|
-
`Search UI skipped: the wizard can't scaffold a native search UI for ${target}. Your records are in the "${targetIndex}" index \u2014 build the UI with Algolia's mobile InstantSearch libraries (https://www.algolia.com/doc/guides/building-search-ui/what-is-instantsearch/ios/ for iOS, .../android for Android).`
|
|
5019
|
-
);
|
|
5020
|
-
track("AI Wizard Search UI Skipped", {
|
|
5021
|
-
framework: input.frameworkName ?? "unknown"
|
|
5022
|
-
});
|
|
5023
|
-
}
|
|
5024
|
-
if (useCases.includes("search") && !skipSearch) {
|
|
4074
|
+
if (useCases.includes("search")) {
|
|
5025
4075
|
let extraInstructions = [];
|
|
5026
4076
|
const preSearchFiles = new Set(await listChangedFiles(worktree));
|
|
5027
4077
|
for (let attempt = 1; attempt <= MAX_IMPLEMENT_VERIFICATION_ATTEMPTS; attempt++) {
|
|
@@ -5092,9 +4142,9 @@ ${run2.output}` : status;
|
|
|
5092
4142
|
"implement: agent reported success but no files changed in the worktree"
|
|
5093
4143
|
);
|
|
5094
4144
|
}
|
|
5095
|
-
if (
|
|
4145
|
+
if (installFailed) {
|
|
5096
4146
|
summaries.push(
|
|
5097
|
-
|
|
4147
|
+
'\u26A0\uFE0F Dependency install in the worktree failed. Run your package manager install in the worktree before the command below, or it will fail with "Cannot find module".'
|
|
5098
4148
|
);
|
|
5099
4149
|
}
|
|
5100
4150
|
return {
|
|
@@ -5102,10 +4152,10 @@ ${run2.output}` : status;
|
|
|
5102
4152
|
filesChanged,
|
|
5103
4153
|
summary: summaries.join("\n\n"),
|
|
5104
4154
|
worktreePath: worktree,
|
|
5105
|
-
...useCases.includes("ingestion") && ingestEntrypoint ? {
|
|
4155
|
+
...useCases.includes("ingestion") && ingestRuntime && ingestEntrypoint ? {
|
|
5106
4156
|
ingestCommand: buildIngestCommand(
|
|
5107
4157
|
worktree,
|
|
5108
|
-
|
|
4158
|
+
ingestRuntime,
|
|
5109
4159
|
ingestEntrypoint
|
|
5110
4160
|
),
|
|
5111
4161
|
ingestScriptRan,
|
|
@@ -5434,20 +4484,20 @@ function parseCliArgs(argv) {
|
|
|
5434
4484
|
}
|
|
5435
4485
|
|
|
5436
4486
|
// src/lib/resetState.ts
|
|
5437
|
-
import { readdir as
|
|
5438
|
-
import { join as
|
|
4487
|
+
import { readdir as readdir4, rm as rm2 } from "node:fs/promises";
|
|
4488
|
+
import { join as join12 } from "node:path";
|
|
5439
4489
|
var KEEP = ["wizard.log"];
|
|
5440
4490
|
async function resetProjectState() {
|
|
5441
4491
|
const dir = stateDir();
|
|
5442
4492
|
let entries;
|
|
5443
4493
|
try {
|
|
5444
|
-
entries = await
|
|
4494
|
+
entries = await readdir4(dir);
|
|
5445
4495
|
} catch {
|
|
5446
4496
|
return { dir, removed: [] };
|
|
5447
4497
|
}
|
|
5448
4498
|
const targets = entries.filter((name) => !KEEP.includes(name));
|
|
5449
4499
|
await Promise.all(
|
|
5450
|
-
targets.map((name) => rm2(
|
|
4500
|
+
targets.map((name) => rm2(join12(dir, name), { recursive: true, force: true }))
|
|
5451
4501
|
);
|
|
5452
4502
|
return { dir, removed: targets };
|
|
5453
4503
|
}
|