@algolia/wizard 0.6.0-rc.51.25 → 0.6.0-rc.51.26

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 CHANGED
@@ -2200,6 +2200,7 @@ function shellQuote(value) {
2200
2200
  // src/lib/languages.ts
2201
2201
  var ENTRYPOINT_TOKEN = "{entrypoint}";
2202
2202
  var INGEST_DIR = ".algolia-wizard";
2203
+ var VISIBLE_INGEST_DIR = "algolia-wizard";
2203
2204
  var PY_VENV = `${INGEST_DIR}/.venv`;
2204
2205
  var PY_VENV_PYTHON = `${PY_VENV}/bin/python`;
2205
2206
  var PY_REQUIREMENTS = `${INGEST_DIR}/requirements.txt`;
@@ -2308,7 +2309,8 @@ var LANGUAGE_PROFILES = {
2308
2309
  // -x skips the venv this same directory holds; without it the check
2309
2310
  // compiles every installed package instead of the generated script.
2310
2311
  label: "python compileall",
2311
- argv: ["python3", "-m", "compileall", "-q", "-x", "[.]venv", INGEST_DIR]
2312
+ argv: ["python3", "-m", "compileall", "-q", "-x", "[.]venv", INGEST_DIR],
2313
+ requiresFile: INGEST_DIR
2312
2314
  }
2313
2315
  ],
2314
2316
  envReadInstruction: "Read them from `os.environ`.",
@@ -2397,7 +2399,8 @@ var LANGUAGE_PROFILES = {
2397
2399
  {
2398
2400
  id: "gomod",
2399
2401
  // Imports in the generated file are the declaration; `go mod tidy`
2400
- // resolves and fetches them.
2402
+ // resolves and fetches them — which only works because the script lives
2403
+ // outside INGEST_DIR (see VISIBLE_INGEST_DIR).
2401
2404
  dependency: { mode: "code-imports" },
2402
2405
  installSteps: [{ argv: ["go", "mod", "tidy"] }],
2403
2406
  ingest: {
@@ -2412,7 +2415,7 @@ var LANGUAGE_PROFILES = {
2412
2415
  versionPin: "v4",
2413
2416
  docKey: "go"
2414
2417
  },
2415
- ingestEntrypointExample: `${INGEST_DIR}/ingest.go`,
2418
+ ingestEntrypointExample: `${VISIBLE_INGEST_DIR}/ingest.go`,
2416
2419
  verification: [
2417
2420
  { label: "go vet", argv: ["go", "vet", "./..."], requiresFile: "go.mod" }
2418
2421
  ],
@@ -2448,7 +2451,11 @@ var LANGUAGE_PROFILES = {
2448
2451
  {
2449
2452
  id: "gradle",
2450
2453
  detectFiles: ["build.gradle", "build.gradle.kts"],
2451
- dependency: { mode: "agent-declares", file: "build.gradle" },
2454
+ dependency: {
2455
+ mode: "agent-declares",
2456
+ file: "build.gradle",
2457
+ alternatives: ["build.gradle.kts"]
2458
+ },
2452
2459
  installSteps: [],
2453
2460
  // Auto-running means executing the repo's own ./gradlew wrapper; out of
2454
2461
  // scope for now, so the wizard writes the code and prints the command.
@@ -2487,13 +2494,33 @@ var LANGUAGE_PROFILES = {
2487
2494
  packageManagers: [
2488
2495
  {
2489
2496
  id: "gradle",
2490
- dependency: { mode: "agent-declares", file: "build.gradle.kts" },
2497
+ detectFiles: ["build.gradle.kts", "build.gradle"],
2498
+ dependency: {
2499
+ mode: "agent-declares",
2500
+ file: "build.gradle.kts",
2501
+ alternatives: ["build.gradle"]
2502
+ },
2491
2503
  installSteps: [],
2492
2504
  ingest: {
2493
2505
  kind: "manual",
2494
2506
  entrypointExtensions: [".kt"],
2495
2507
  runCommand: "./gradlew runAlgoliaIngest"
2496
2508
  }
2509
+ },
2510
+ // Kotlin/Maven is rare but real, and pom.xml is a Kotlin manifest — without
2511
+ // this spec such a repo falls through to Gradle and is told to run a
2512
+ // ./gradlew task that doesn't exist. Compiling needs the repo's own
2513
+ // kotlin-maven-plugin, so the run stays the developer's step.
2514
+ {
2515
+ id: "maven",
2516
+ detectFiles: ["pom.xml"],
2517
+ dependency: { mode: "agent-declares", file: "pom.xml" },
2518
+ installSteps: [{ argv: ["mvn", "-q", "-DskipTests", "compile"] }],
2519
+ ingest: {
2520
+ kind: "manual",
2521
+ entrypointExtensions: [".kt"],
2522
+ runCommand: "mvn -q org.codehaus.mojo:exec-maven-plugin:3.5.0:java -Dexec.mainClass=AlgoliaWizardIngest"
2523
+ }
2497
2524
  }
2498
2525
  ],
2499
2526
  sdk: {
@@ -2660,9 +2687,12 @@ var LANGUAGE_PROFILES = {
2660
2687
  ingestEntrypointExample: `${INGEST_DIR}/ingest.dart`,
2661
2688
  verification: [
2662
2689
  {
2690
+ // Gated on the directory it analyzes, not just pubspec.yaml: a run that
2691
+ // only built a search UI never created it, and `dart analyze` on a
2692
+ // missing path fails the whole verification pass.
2663
2693
  label: "dart analyze",
2664
2694
  argv: ["dart", "analyze", INGEST_DIR],
2665
- requiresFile: "pubspec.yaml"
2695
+ requiresFile: INGEST_DIR
2666
2696
  }
2667
2697
  ],
2668
2698
  envReadInstruction: "Read them from `Platform.environment`.",
@@ -2670,6 +2700,13 @@ var LANGUAGE_PROFILES = {
2670
2700
  }
2671
2701
  };
2672
2702
  var DEFAULT_LANGUAGE_ID = "javascript";
2703
+ var JAVASCRIPT = "javascript";
2704
+ var CURATED_LANGUAGES = Object.values(
2705
+ LANGUAGE_PROFILES
2706
+ ).map((profile2) => profile2.displayName);
2707
+ function isBackendLanguage(profile2) {
2708
+ return profile2.id !== JAVASCRIPT;
2709
+ }
2673
2710
  function normalizeLanguageName(name) {
2674
2711
  return name.toLowerCase().trim().replace(/[\s_-]+/g, "");
2675
2712
  }
@@ -2683,6 +2720,14 @@ function resolveLanguageProfile(name) {
2683
2720
  const id = ALIAS_TO_ID.get(normalizeLanguageName(name));
2684
2721
  return id ? LANGUAGE_PROFILES[id] : void 0;
2685
2722
  }
2723
+ function isSameLanguage(a, b) {
2724
+ const x = resolveLanguageProfile(a);
2725
+ const y = resolveLanguageProfile(b);
2726
+ if (x && y) return x.id === y.id;
2727
+ if (x || y) return false;
2728
+ const folded = normalizeLanguageName(a);
2729
+ return folded !== "" && folded === normalizeLanguageName(b);
2730
+ }
2686
2731
  var BASE_SKIP_DIRS = ["node_modules", ".git", "dist"];
2687
2732
  var ALL_SKIP_DIRS = /* @__PURE__ */ new Set([
2688
2733
  ...BASE_SKIP_DIRS,
@@ -2704,6 +2749,17 @@ function isWorktreeRelativeCommand(command) {
2704
2749
  function withCommand(argv, command) {
2705
2750
  return [command, ...argv.slice(1)];
2706
2751
  }
2752
+ function resolveDeclaredManifest(root, packageManager) {
2753
+ const { dependency } = packageManager;
2754
+ if (dependency.mode !== "agent-declares" || !dependency.alternatives?.length) {
2755
+ return packageManager;
2756
+ }
2757
+ const present = [dependency.file, ...dependency.alternatives].find(
2758
+ (file) => existsSync2(join9(root, file))
2759
+ );
2760
+ if (!present || present === dependency.file) return packageManager;
2761
+ return { ...packageManager, dependency: { ...dependency, file: present } };
2762
+ }
2707
2763
  async function manifestPresent(root, manifest, listing) {
2708
2764
  if (!manifest.startsWith("*.")) return existsSync2(join9(root, manifest));
2709
2765
  if (!listing.entries) {
@@ -2730,13 +2786,27 @@ async function detectProfilesFromManifests(root) {
2730
2786
  async function hasProfileManifest(root, profile2) {
2731
2787
  return profileManifestPresent(root, profile2, {});
2732
2788
  }
2789
+ async function pickIngestionCandidates(root, confirmedNames) {
2790
+ const confirmed3 = confirmedNames.map((name) => resolveLanguageProfile(name)).filter((p) => p !== void 0);
2791
+ const onDisk = await detectProfilesFromManifests(root);
2792
+ const onDiskIds = new Set(onDisk.map((p) => p.id));
2793
+ const candidates = [
2794
+ ...new Map(
2795
+ confirmed3.filter((p) => onDiskIds.has(p.id)).map((p) => [p.id, p])
2796
+ ).values()
2797
+ ];
2798
+ return { candidates, confirmed: confirmed3, onDisk };
2799
+ }
2733
2800
  async function resolveToolchain(root, profile2) {
2734
2801
  const matched = profile2.packageManagers.find(
2735
2802
  (pm) => [...pm.lockfiles ?? [], ...pm.detectFiles ?? []].some(
2736
2803
  (f) => existsSync2(join9(root, f))
2737
2804
  )
2738
2805
  );
2739
- const packageManager = matched ?? profile2.packageManagers[0];
2806
+ const packageManager = resolveDeclaredManifest(
2807
+ root,
2808
+ matched ?? profile2.packageManagers[0]
2809
+ );
2740
2810
  let { installSteps, ingest } = packageManager;
2741
2811
  installSteps = installSteps.map(
2742
2812
  (step) => isWorktreeRelativeCommand(step.argv[0]) ? { ...step, argv: withCommand(step.argv, join9(root, step.argv[0])) } : step
@@ -2860,27 +2930,61 @@ import { join as join11 } from "node:path";
2860
2930
 
2861
2931
  // src/lib/tools/utils/runCommand.ts
2862
2932
  import { spawn as spawn2 } from "node:child_process";
2863
- function runCommand(command, args, cwd) {
2933
+ var INSTALL_TIMEOUT_MS = 15 * 6e4;
2934
+ var INGEST_TIMEOUT_MS = 15 * 6e4;
2935
+ var VERIFY_TIMEOUT_MS = 10 * 6e4;
2936
+ var KILL_GRACE_MS = 5e3;
2937
+ function runCommand(command, args, options = {}) {
2938
+ const { cwd, env, timeoutMs = VERIFY_TIMEOUT_MS } = options;
2864
2939
  return new Promise((resolve4) => {
2865
2940
  let output = "";
2941
+ let settled = false;
2866
2942
  const child = spawn2(command, args, {
2867
2943
  cwd,
2868
- stdio: ["ignore", "pipe", "pipe"]
2944
+ shell: false,
2945
+ stdio: ["ignore", "pipe", "pipe"],
2946
+ ...env ? { env: { ...process.env, ...env } } : {}
2869
2947
  });
2948
+ const settle = (result) => {
2949
+ if (settled) return;
2950
+ settled = true;
2951
+ clearTimeout(timer);
2952
+ resolve4(result);
2953
+ };
2954
+ const timer = setTimeout(() => {
2955
+ child.kill("SIGTERM");
2956
+ setTimeout(() => child.kill("SIGKILL"), KILL_GRACE_MS).unref();
2957
+ const seconds = Math.round(timeoutMs / 1e3);
2958
+ settle({
2959
+ code: 1,
2960
+ output: `${output}
2961
+ Timed out after ${seconds}s: ${command} ${args.join(" ")}`.trim(),
2962
+ timedOut: true
2963
+ });
2964
+ }, timeoutMs);
2870
2965
  child.stdout?.on("data", (d) => output += d);
2871
2966
  child.stderr?.on("data", (d) => output += d);
2872
2967
  child.on(
2873
2968
  "error",
2874
- (err) => resolve4({ code: 1, output: `Failed to run ${command}: ${err.message}` })
2969
+ (err) => settle({
2970
+ code: 1,
2971
+ output: `Failed to run ${command}: ${err.message}`,
2972
+ timedOut: false
2973
+ })
2974
+ );
2975
+ child.on(
2976
+ "close",
2977
+ (code) => settle({ code: code ?? 1, output, timedOut: false })
2875
2978
  );
2876
- child.on("close", (code) => resolve4({ code: code ?? 1, output }));
2877
2979
  });
2878
2980
  }
2879
2981
 
2880
2982
  // src/lib/tools/repoVerification.ts
2881
2983
  var VERIFICATION_SCRIPT_CANDIDATES = ["lint", "typecheck", "check"];
2882
2984
  async function runCheck(command, binary, args) {
2883
- const { code, output } = await runCommand(binary, args);
2985
+ const { code, output } = await runCommand(binary, args, {
2986
+ timeoutMs: VERIFY_TIMEOUT_MS
2987
+ });
2884
2988
  return { command, exitCode: code, ok: code === 0, output: output.trim() };
2885
2989
  }
2886
2990
  async function javascriptChecks() {
@@ -2914,7 +3018,7 @@ async function runRepoVerificationCheck(languages = [DEFAULT_LANGUAGE_ID]) {
2914
3018
  const checks = [];
2915
3019
  const limitations = [];
2916
3020
  for (const id of ids) {
2917
- if (id === DEFAULT_LANGUAGE_ID) {
3021
+ if (id === JAVASCRIPT) {
2918
3022
  const result = await javascriptChecks();
2919
3023
  if ("checks" in result) checks.push(...result.checks);
2920
3024
  else limitations.push(result.limitation);
@@ -3085,7 +3189,11 @@ var DEFAULT_TOOL_LIMITS = {
3085
3189
  read: 20,
3086
3190
  match: 100
3087
3191
  };
3088
- function createToolContext(limits = DEFAULT_TOOL_LIMITS, cwd = process.cwd(), languages = [DEFAULT_LANGUAGE_ID]) {
3192
+ function createToolContext({
3193
+ limits = DEFAULT_TOOL_LIMITS,
3194
+ cwd = process.cwd(),
3195
+ languages = [DEFAULT_LANGUAGE_ID]
3196
+ } = {}) {
3089
3197
  return {
3090
3198
  root: cwd,
3091
3199
  cwd,
@@ -3164,11 +3272,7 @@ async function runAgent(req) {
3164
3272
  baseURL: PROXY_BASE_URL,
3165
3273
  fetch: proxyFetch
3166
3274
  });
3167
- const toolContext = createToolContext(
3168
- void 0,
3169
- void 0,
3170
- req.languages
3171
- );
3275
+ const toolContext = createToolContext({ languages: req.languages });
3172
3276
  const readTools = ["readFile", "searchFiles", "listFiles"];
3173
3277
  const hasReadTools = !req.tools || req.tools.some((t) => readTools.includes(t));
3174
3278
  const instructions = [
@@ -3363,7 +3467,7 @@ async function runAnalysis(mode, extraInstructions = []) {
3363
3467
  // package.json
3364
3468
  var package_default = {
3365
3469
  name: "@algolia/wizard",
3366
- version: "0.6.0-rc.51.25",
3470
+ version: "0.6.0-rc.51.26",
3367
3471
  description: "Magically implement Algolia functionality in your codebase",
3368
3472
  type: "module",
3369
3473
  engines: {
@@ -3473,17 +3577,6 @@ var confirmLanguageSchema = z19.object({
3473
3577
  languages: detectLanguageSchema.shape.languages
3474
3578
  });
3475
3579
  var OTHER_OPTION = "Other";
3476
- var CURATED_LANGUAGES = Object.values(LANGUAGE_PROFILES).map(
3477
- (profile2) => profile2.displayName
3478
- );
3479
- function isSameLanguage(a, b) {
3480
- const x = resolveLanguageProfile(a);
3481
- const y = resolveLanguageProfile(b);
3482
- if (x && y) return x.id === y.id;
3483
- if (x || y) return false;
3484
- const fold = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
3485
- return fold(a) !== "" && fold(a) === fold(b);
3486
- }
3487
3580
  function confirmed(languages) {
3488
3581
  track("AI Wizard Language Confirmed", { languages });
3489
3582
  return { languages };
@@ -3612,6 +3705,27 @@ function resolveSearchStrategy(frameworkName, hasJavaScriptInStack) {
3612
3705
  function searchDocKey(strategy) {
3613
3706
  return strategy === "cdn-template" ? "templates" : strategy;
3614
3707
  }
3708
+ function bundlesJavaScript(strategy) {
3709
+ return strategy !== "cdn-template" && strategy !== "none";
3710
+ }
3711
+ function canScaffoldSearchUI(strategy) {
3712
+ return strategy !== "none";
3713
+ }
3714
+ var ENV_PREFIXES = [
3715
+ { aliases: ["next", "nextjs"], prefix: "NEXT_PUBLIC_" },
3716
+ { aliases: ["nuxt", "nuxtjs"], prefix: "NUXT_PUBLIC_" },
3717
+ { aliases: ["astro"], prefix: "PUBLIC_" },
3718
+ { aliases: ["vite"], prefix: "VITE_" }
3719
+ ];
3720
+ var DEFAULT_ENV_PREFIX = "PUBLIC_";
3721
+ function publicEnvPrefix(frameworkNames, strategy) {
3722
+ if (!bundlesJavaScript(strategy)) return "";
3723
+ const present = new Set(frameworkNames.map(normalize));
3724
+ for (const { aliases, prefix } of ENV_PREFIXES) {
3725
+ if (aliases.some((alias) => present.has(alias))) return prefix;
3726
+ }
3727
+ return DEFAULT_ENV_PREFIX;
3728
+ }
3615
3729
  function describeSearchTarget(strategy, frameworkName) {
3616
3730
  switch (strategy) {
3617
3731
  case "react":
@@ -3819,7 +3933,7 @@ ${JSON.stringify(s.output, null, 2)}`
3819
3933
  }
3820
3934
  function formatReviewSummary(result) {
3821
3935
  const nextStepLines = result.nextSteps.map((step) => {
3822
- const isIngestCommand = step.includes(".algolia-wizard/") || step.includes("AlgoliaWizardIngest");
3936
+ const isIngestCommand = step.includes("algolia-wizard/") || step.includes("AlgoliaWizardIngest");
3823
3937
  const isWorktreeCommand = step.includes("/worktrees/");
3824
3938
  return {
3825
3939
  text: `\u2192 ${step}`,
@@ -3861,7 +3975,7 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
3861
3975
  import z24 from "zod";
3862
3976
 
3863
3977
  // src/lib/worktree.ts
3864
- import { execFile, spawn as spawn3 } from "node:child_process";
3978
+ import { execFile } from "node:child_process";
3865
3979
  import { existsSync as existsSync4 } from "node:fs";
3866
3980
  import { copyFile, mkdir as mkdir6, readdir as readdir4, readFile as readFile8, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
3867
3981
  import {
@@ -3934,28 +4048,12 @@ async function createWorktree(repoRoot) {
3934
4048
  await git(["-C", repoRoot, "worktree", "add", "-b", branch, path, "HEAD"]);
3935
4049
  return { path, branch };
3936
4050
  }
3937
- function spawnStep(worktreePath, argv) {
3938
- return new Promise((resolve4) => {
3939
- let output = "";
3940
- const child = spawn3(argv[0], [...argv.slice(1)], {
3941
- cwd: worktreePath,
3942
- shell: false,
3943
- stdio: ["ignore", "pipe", "pipe"]
3944
- });
3945
- child.stdout?.on("data", (d) => output += d);
3946
- child.stderr?.on("data", (d) => output += d);
3947
- child.on(
3948
- "error",
3949
- (err) => resolve4({
3950
- ok: false,
3951
- output: `Failed to run ${argv.join(" ")}: ${err.message}`
3952
- })
3953
- );
3954
- child.on(
3955
- "close",
3956
- (code) => resolve4({ ok: code === 0, output: output.trim() })
3957
- );
4051
+ async function spawnStep(worktreePath, argv) {
4052
+ const { code, output } = await runCommand(argv[0], [...argv.slice(1)], {
4053
+ cwd: worktreePath,
4054
+ timeoutMs: INSTALL_TIMEOUT_MS
3958
4055
  });
4056
+ return { ok: code === 0, output: output.trim() };
3959
4057
  }
3960
4058
  async function installWorktreeDeps(worktreePath, toolchain) {
3961
4059
  const { profile: profile2, installSteps, packageManager } = toolchain;
@@ -4050,29 +4148,12 @@ async function runIngestScript(worktreePath, toolchain, entrypoint, env = {}) {
4050
4148
  };
4051
4149
  }
4052
4150
  const argv = resolveIngestArgv(ingest, entrypoint);
4053
- return new Promise((resolveRun) => {
4054
- let output = "";
4055
- const child = spawn3(argv[0], argv.slice(1), {
4056
- cwd: worktreePath,
4057
- shell: false,
4058
- stdio: ["ignore", "pipe", "pipe"],
4059
- env: { ...process.env, ...env }
4060
- });
4061
- child.stdout?.on("data", (d) => output += d);
4062
- child.stderr?.on("data", (d) => output += d);
4063
- child.on(
4064
- "error",
4065
- (err) => resolveRun({
4066
- ran: true,
4067
- ok: false,
4068
- output: `Failed to run ${argv.join(" ")}: ${err.message}`
4069
- })
4070
- );
4071
- child.on(
4072
- "close",
4073
- (code) => resolveRun({ ran: true, ok: code === 0, output: output.trim() })
4074
- );
4151
+ const { code, output } = await runCommand(argv[0], argv.slice(1), {
4152
+ cwd: worktreePath,
4153
+ env,
4154
+ timeoutMs: INGEST_TIMEOUT_MS
4075
4155
  });
4156
+ return { ran: true, ok: code === 0, output: output.trim() };
4076
4157
  }
4077
4158
  async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourcePath) {
4078
4159
  const trimmed = sourcePath.trim();
@@ -4299,28 +4380,11 @@ var verificationOutputSchema = z24.object({
4299
4380
  });
4300
4381
  var MAX_IMPLEMENT_VERIFICATION_ATTEMPTS = 3;
4301
4382
  var DEFAULT_IMPLEMENT_USE_CASES = ["ingestion", "search"];
4302
- var INGEST_DIR2 = ".algolia-wizard";
4303
- function publicEnvPrefix(language, strategy) {
4304
- if (strategy === "cdn-template" || strategy === "none") return "";
4305
- const frameworkNames = language.frameworks.map(
4306
- (framework) => framework.name.toLowerCase()
4383
+ function buildSearchEnvVars(language, strategy, appId, searchKey) {
4384
+ const prefix = publicEnvPrefix(
4385
+ language.frameworks.map((framework) => framework.name),
4386
+ strategy
4307
4387
  );
4308
- if (frameworkNames.some((name) => name.includes("next"))) {
4309
- return "NEXT_PUBLIC_";
4310
- }
4311
- if (frameworkNames.some((name) => name.includes("nuxt"))) {
4312
- return "NUXT_PUBLIC_";
4313
- }
4314
- if (frameworkNames.some((name) => name.includes("astro"))) {
4315
- return "PUBLIC_";
4316
- }
4317
- if (frameworkNames.some((name) => name.includes("vite"))) {
4318
- return "VITE_";
4319
- }
4320
- return "PUBLIC_";
4321
- }
4322
- function searchEnvVars(language, strategy, appId, searchKey) {
4323
- const prefix = publicEnvPrefix(language, strategy);
4324
4388
  return [
4325
4389
  {
4326
4390
  name: `${prefix}ALGOLIA_APP_ID`,
@@ -4333,17 +4397,12 @@ function searchEnvVars(language, strategy, appId, searchKey) {
4333
4397
  ];
4334
4398
  }
4335
4399
  async function resolveIngestionProfile(ctx, language, repoRoot) {
4336
- const fallback = LANGUAGE_PROFILES[DEFAULT_LANGUAGE_ID];
4337
- const confirmed3 = language.languages.map((l) => resolveLanguageProfile(l.name)).filter((p) => p !== void 0);
4338
- const onDisk = await detectProfilesFromManifests(repoRoot);
4339
- const onDiskIds = new Set(onDisk.map((p) => p.id));
4340
- const candidates = [
4341
- ...new Map(
4342
- confirmed3.filter((p) => onDiskIds.has(p.id)).map((p) => [p.id, p])
4343
- ).values()
4344
- ];
4400
+ const { candidates, confirmed: confirmed3, onDisk } = await pickIngestionCandidates(
4401
+ repoRoot,
4402
+ language.languages.map((l) => l.name)
4403
+ );
4345
4404
  if (candidates.length === 0) {
4346
- const chosen = confirmed3[0] ?? onDisk[0] ?? fallback;
4405
+ const chosen = confirmed3[0] ?? onDisk[0] ?? LANGUAGE_PROFILES[DEFAULT_LANGUAGE_ID];
4347
4406
  logger.warn(
4348
4407
  {
4349
4408
  confirmed: language.languages.map((l) => l.name),
@@ -4355,9 +4414,10 @@ async function resolveIngestionProfile(ctx, language, repoRoot) {
4355
4414
  return chosen;
4356
4415
  }
4357
4416
  if (candidates.length === 1) return candidates[0];
4358
- const backends = candidates.filter((p) => p.id !== DEFAULT_LANGUAGE_ID);
4417
+ const backends = candidates.filter(isBackendLanguage);
4359
4418
  if (backends.length === 1) return backends[0];
4360
4419
  if (backends.length === 0) return candidates[0];
4420
+ if (isBackendLanguage(candidates[0])) return candidates[0];
4361
4421
  const options = backends.map((p) => p.displayName);
4362
4422
  const selection = await ctx.requestUserInput({
4363
4423
  prompt: "Which language should the ingestion script use?",
@@ -4449,6 +4509,9 @@ function searchInstructions(input) {
4449
4509
  ];
4450
4510
  }
4451
4511
  function verificationInstructions(input) {
4512
+ const protectedDirs = [
4513
+ .../* @__PURE__ */ new Set([input.ingestDir, ingestScriptDir(input.ingestionProfile)])
4514
+ ];
4452
4515
  return [
4453
4516
  "Verify the Algolia implementation changes in the current worktree.",
4454
4517
  `Verification tools found in the codebase: ${JSON.stringify(input.findings.verification ?? [])}.`,
@@ -4456,7 +4519,7 @@ function verificationInstructions(input) {
4456
4519
  "For issues caused by the implementation, make minimal fixes with writeFile and re-run verifyImplementation.",
4457
4520
  "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.",
4458
4521
  "Do not add new Algolia functionality here; only validate and make minimal correctness fixes.",
4459
- `Do not modify "${input.ingestDir}/" unless verifyImplementation reports an actionable issue in its files.`,
4522
+ `Do not modify ${protectedDirs.map((dir) => `"${dir}/"`).join(" or ")} unless verifyImplementation reports an actionable issue in its files.`,
4460
4523
  "Always call reportStatus with status=success once verification has run, even when sufficient=false.",
4461
4524
  "Set sufficient=true only when the implementation is complete and checks pass (or fail for a clearly unrelated reason).",
4462
4525
  "Set sufficient=false when the implementation is incomplete or has implementation-caused failures; include concrete additionalInstructions for the next pass."
@@ -4465,14 +4528,17 @@ function verificationInstructions(input) {
4465
4528
  var IMPLEMENT_CONFIG = {
4466
4529
  ingestion: {
4467
4530
  title: "Algolia ingestion",
4531
+ label: "Ingestion",
4468
4532
  buildInstructions: ingestionInstructions
4469
4533
  },
4470
4534
  search: {
4471
4535
  title: "Algolia search",
4536
+ label: "Search",
4472
4537
  buildInstructions: searchInstructions
4473
4538
  },
4474
4539
  verification: {
4475
4540
  title: "Algolia verification",
4541
+ label: "Verification",
4476
4542
  buildInstructions: verificationInstructions
4477
4543
  }
4478
4544
  };
@@ -4504,8 +4570,7 @@ function buildAgentInstructions(useCase, input, extraInstructions = []) {
4504
4570
  ];
4505
4571
  }
4506
4572
  function formatSummary(useCase, summary) {
4507
- const label = useCase === "ingestion" ? "Ingestion" : useCase === "search" ? "Search" : "Verification";
4508
- return `${label}: ${summary}`;
4573
+ return `${IMPLEMENT_CONFIG[useCase].label}: ${summary}`;
4509
4574
  }
4510
4575
  function buildIngestCommand(worktree, toolchain, entrypoint) {
4511
4576
  return `cd ${shellQuote(worktree)} && ${describeIngestCommand(toolchain.ingest, entrypoint)}`;
@@ -4614,7 +4679,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4614
4679
  const copied = await copyUploadIntoWorktree(
4615
4680
  repoRoot,
4616
4681
  worktree,
4617
- INGEST_DIR2,
4682
+ INGEST_DIR,
4618
4683
  uploadSourcePath ?? ""
4619
4684
  );
4620
4685
  if (copied.ok) {
@@ -4643,7 +4708,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4643
4708
  const frameworkName = language.frameworks[0]?.name;
4644
4709
  const searchStrategy = resolveSearchStrategy(
4645
4710
  frameworkName,
4646
- verificationLanguages.includes(DEFAULT_LANGUAGE_ID)
4711
+ verificationLanguages.includes(JAVASCRIPT)
4647
4712
  );
4648
4713
  logger.info(
4649
4714
  {
@@ -4663,8 +4728,13 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4663
4728
  language,
4664
4729
  appId,
4665
4730
  searchKey,
4666
- searchEnvVars: searchEnvVars(language, searchStrategy, appId, searchKey),
4667
- ingestDir: INGEST_DIR2,
4731
+ searchEnvVars: buildSearchEnvVars(
4732
+ language,
4733
+ searchStrategy,
4734
+ appId,
4735
+ searchKey
4736
+ ),
4737
+ ingestDir: INGEST_DIR,
4668
4738
  ingestionSource,
4669
4739
  uploadFilePath,
4670
4740
  searchStrategy,
@@ -4673,10 +4743,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4673
4743
  toolchain,
4674
4744
  verificationLanguages
4675
4745
  };
4676
- const searchToolchain = searchStrategy === "cdn-template" || searchStrategy === "none" ? void 0 : ingestionProfile.id === DEFAULT_LANGUAGE_ID ? toolchain : await resolveToolchain(
4677
- worktree,
4678
- LANGUAGE_PROFILES[DEFAULT_LANGUAGE_ID]
4679
- );
4746
+ const searchToolchain = !bundlesJavaScript(searchStrategy) ? void 0 : ingestionProfile.id === JAVASCRIPT ? toolchain : await resolveToolchain(worktree, LANGUAGE_PROFILES[JAVASCRIPT]);
4680
4747
  const toolchainForUseCase = (useCase) => useCase === "search" ? searchToolchain : toolchain;
4681
4748
  const summaries = [];
4682
4749
  if (uploadWarning) summaries.push(uploadWarning);
@@ -4685,7 +4752,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4685
4752
  let ingestScriptRan = false;
4686
4753
  let ingestRecordCount;
4687
4754
  let ingestDurationMs;
4688
- let installFailed = false;
4755
+ const failedInstalls = /* @__PURE__ */ new Set();
4689
4756
  let ingestOutcomeMessage;
4690
4757
  async function runImplementationUseCase(currentUseCase, extraInstructions = []) {
4691
4758
  if (agentRuns > 0) ctx.recordStepExecution();
@@ -4711,7 +4778,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4711
4778
  const install = await installWorktreeDeps(worktree, useCaseToolchain);
4712
4779
  ctx.logEnd(installLogId, install.ok ? "success" : "error");
4713
4780
  if (!install.ok) {
4714
- installFailed = true;
4781
+ failedInstalls.add(useCaseToolchain.profile.displayName);
4715
4782
  logger.warn(
4716
4783
  { useCase: currentUseCase, output: install.output },
4717
4784
  "implement: dependency install in worktree failed; generated commands may not run until deps are installed"
@@ -4734,7 +4801,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4734
4801
  const { summary, entrypoint } = await runImplementationUseCase("ingestion");
4735
4802
  summaries.push(formatSummary("ingestion", summary));
4736
4803
  ingestEntrypoint = entrypoint;
4737
- if (ingestEntrypoint && toolchain.ingest.kind === "auto" && !installFailed) {
4804
+ if (ingestEntrypoint && toolchain.ingest.kind === "auto" && failedInstalls.size === 0) {
4738
4805
  ctx.clearNotices();
4739
4806
  const runNow = await ctx.requestUserInput({
4740
4807
  prompt: `Run the ingestion script now? This writes records to the "${targetIndex}" index.`,
@@ -4838,7 +4905,7 @@ ${run.output}` : status;
4838
4905
  messages: ingestOutcomeMessage ? [ingestOutcomeMessage, ...commandMessages] : commandMessages
4839
4906
  });
4840
4907
  }
4841
- const skipSearch = useCases.includes("search") && input.searchStrategy === "none";
4908
+ const skipSearch = useCases.includes("search") && !canScaffoldSearchUI(input.searchStrategy);
4842
4909
  if (skipSearch) {
4843
4910
  const target = describeSearchTarget(
4844
4911
  input.searchStrategy,
@@ -4847,7 +4914,6 @@ ${run.output}` : status;
4847
4914
  summaries.push(
4848
4915
  `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).`
4849
4916
  );
4850
- ctx.setUserInput("implementation", "success");
4851
4917
  track("AI Wizard Search UI Skipped", {
4852
4918
  framework: input.frameworkName ?? "unknown"
4853
4919
  });
@@ -4923,9 +4989,9 @@ ${run.output}` : status;
4923
4989
  "implement: agent reported success but no files changed in the worktree"
4924
4990
  );
4925
4991
  }
4926
- if (installFailed) {
4992
+ if (failedInstalls.size > 0) {
4927
4993
  summaries.push(
4928
- `\u26A0\uFE0F Dependency install in the worktree failed. Install the ${ingestionProfile.displayName} dependencies in the worktree before the command below, or it will fail on a missing package.`
4994
+ `\u26A0\uFE0F Dependency install in the worktree failed. Install the ${[...failedInstalls].join(" and ")} dependencies in the worktree before the command below, or it will fail on a missing package.`
4929
4995
  );
4930
4996
  }
4931
4997
  return {
@@ -57,8 +57,3 @@ Django, Laravel, Symfony — and any backend with no JavaScript build.
57
57
  - For InstantSearch, import the client from `algoliasearch/lite` (smaller bundle and
58
58
  correct types).
59
59
  - Every record needs an `objectID`. `saveObjects` auto-batches in groups of 1,000.
60
-
61
- ## Other files
62
-
63
- - `search-single-index.md` — direct/manual search via the core client
64
- (`searchSingleIndex`), for cases where InstantSearch is not used.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@algolia/wizard",
3
- "version": "0.6.0-rc.51.25",
3
+ "version": "0.6.0-rc.51.26",
4
4
  "description": "Magically implement Algolia functionality in your codebase",
5
5
  "type": "module",
6
6
  "engines": {
@@ -1,42 +0,0 @@
1
- # Direct search with the core client (v5)
2
-
3
- Use this only when NOT using InstantSearch (e.g. a custom search box, a server
4
- route, or programmatic queries). For UI, prefer `instantsearch-setup-<framework>.md`.
5
-
6
- ## Client
7
-
8
- ```ts
9
- import { algoliasearch } from 'algoliasearch'
10
-
11
- const client = algoliasearch(APP_ID, SEARCH_ONLY_KEY)
12
- ```
13
-
14
- In v5 there is no `client.initIndex(...)`. Index methods take the index name as a
15
- parameter on the client. Every method takes a single options object.
16
-
17
- ## Search a single index
18
-
19
- ```ts
20
- const { hits, nbHits } = await client.searchSingleIndex({
21
- indexName: 'INDEX_NAME',
22
- searchParams: { query: 'shoes', hitsPerPage: 20, page: 0 },
23
- })
24
- ```
25
-
26
- ## Search multiple indices / queries in one request
27
-
28
- ```ts
29
- const { results } = await client.search({
30
- requests: [
31
- { indexName: 'INDEX_NAME', query: 'shoes' },
32
- { indexName: 'OTHER_INDEX', query: 'shoes' },
33
- ],
34
- })
35
- ```
36
-
37
- ## Notes
38
-
39
- - `searchSingleIndex` returns up to 1,000 hits. For larger exports use the `browse`
40
- operation instead.
41
- - Keep using a **search-only** key for any client-exposed search. Use an admin key
42
- only in trusted server-side code.