@algolia/wizard 0.9.0-rc.70.61 → 0.9.0-rc.72.60

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/main.js +359 -154
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -892,12 +892,12 @@ var sidebarItems = [
892
892
  description: "push 100 records to Algolia in seconds"
893
893
  },
894
894
  {
895
- title: "detect your framework",
896
- description: "React, Vue, Angular, Vanilla JS"
895
+ title: "detect your stack",
896
+ description: "React, Vue, Angular, Rails, Django, Laravel & more"
897
897
  },
898
898
  {
899
899
  title: "scaffold a search UI",
900
- description: "a styled InstantSearch component, wired into your app"
900
+ description: "a styled InstantSearch UI, wired into your app or templates"
901
901
  },
902
902
  {
903
903
  title: "ship it",
@@ -1003,7 +1003,7 @@ var accessItems = [
1003
1003
  {
1004
1004
  tag: "READ",
1005
1005
  title: "Project files",
1006
- description: "reads package.json, configs & source to detect your stack. Read-only; nothing is uploaded."
1006
+ description: "reads your dependency manifests (package.json\u2026), configs & source to detect your stack. Read-only; nothing is uploaded."
1007
1007
  },
1008
1008
  {
1009
1009
  tag: "WRITE",
@@ -2221,15 +2221,138 @@ function writeCredentialsTool(ctx) {
2221
2221
  // src/lib/tools/searchFiles.ts
2222
2222
  import { tool as tool7 } from "ai";
2223
2223
  import z10 from "zod";
2224
- import { readdir as readdir2, readFile as readFile6 } from "node:fs/promises";
2224
+ import { readdir as readdir2, readFile as readFile7 } from "node:fs/promises";
2225
+ import { join as join9 } from "node:path";
2226
+
2227
+ // src/lib/tools/utils/packageManager.ts
2228
+ import { readFile as readFile6 } from "node:fs/promises";
2229
+ import { existsSync } from "node:fs";
2225
2230
  import { join as join8 } from "node:path";
2231
+ var LOCKFILES = [
2232
+ ["pnpm-lock.yaml", "pnpm"],
2233
+ ["yarn.lock", "yarn"],
2234
+ ["bun.lockb", "bun"],
2235
+ ["bun.lock", "bun"],
2236
+ ["package-lock.json", "npm"]
2237
+ ];
2238
+ async function readPackageJson(cwd = process.cwd()) {
2239
+ return JSON.parse(await readFile6(join8(cwd, "package.json"), "utf8"));
2240
+ }
2241
+ function packageManagerFrom(pkg) {
2242
+ return pkg.packageManager?.split("@")[0] ?? "npm";
2243
+ }
2244
+ function packageManagerFromLockfile(cwd) {
2245
+ return LOCKFILES.find(([file]) => existsSync(join8(cwd, file)))?.[1];
2246
+ }
2247
+ async function detectPackageManager(cwd) {
2248
+ try {
2249
+ const pkg = await readPackageJson(cwd);
2250
+ if (pkg.packageManager) return packageManagerFrom(pkg);
2251
+ } catch {
2252
+ }
2253
+ return packageManagerFromLockfile(cwd) ?? "npm";
2254
+ }
2255
+
2256
+ // src/lib/shell.ts
2257
+ function shellQuote(value) {
2258
+ return "'" + value.replace(/'/g, "'\\''") + "'";
2259
+ }
2260
+
2261
+ // src/lib/languages.ts
2262
+ var ENTRYPOINT_TOKEN = "{entrypoint}";
2263
+ var INGEST_DIR = ".algolia-wizard";
2264
+ var LANGUAGE_PROFILES = {
2265
+ javascript: {
2266
+ id: "javascript",
2267
+ displayName: "JavaScript/TypeScript",
2268
+ aliases: [
2269
+ "javascript",
2270
+ "js",
2271
+ "typescript",
2272
+ "ts",
2273
+ "node",
2274
+ "nodejs",
2275
+ "node.js",
2276
+ "bun",
2277
+ "deno",
2278
+ "ecmascript",
2279
+ "jsx",
2280
+ "tsx"
2281
+ ],
2282
+ manifests: ["package.json"],
2283
+ // The concrete npm-family manager is resolved by detectPackageManager (it
2284
+ // honours the package.json `packageManager` field, which lockfiles can't
2285
+ // express), so one spec covers all four and `resolveToolchain` rewrites the
2286
+ // binary below.
2287
+ packageManagers: [
2288
+ {
2289
+ id: "npm",
2290
+ dependency: { mode: "agent-declares", file: "package.json" },
2291
+ installSteps: [{ argv: ["npm", "install"] }],
2292
+ ingest: {
2293
+ kind: "auto",
2294
+ argv: ["node", ENTRYPOINT_TOKEN],
2295
+ entrypointExtensions: [".mjs", ".cjs", ".js"]
2296
+ }
2297
+ }
2298
+ ],
2299
+ sdk: { packageName: "algoliasearch", versionPin: "^5", docKey: "js" },
2300
+ ingestEntrypointExample: `${INGEST_DIR}/ingest.mjs`,
2301
+ // package.json scripts are repo-defined, so they're resolved at run time by
2302
+ // repoVerification rather than listed here.
2303
+ verification: [],
2304
+ envReadInstruction: "Read them from `process.env`.",
2305
+ skipDirs: ["node_modules", "dist", "build", "coverage", ".next", "out"]
2306
+ }
2307
+ };
2308
+ var DEFAULT_LANGUAGE_ID = "javascript";
2309
+ var JAVASCRIPT = "javascript";
2310
+ var CURATED_LANGUAGES = Object.values(
2311
+ LANGUAGE_PROFILES
2312
+ ).map((profile) => profile.displayName);
2313
+ function normalizeLanguageName(name) {
2314
+ return name.toLowerCase().trim().replace(/[\s_-]+/g, "");
2315
+ }
2316
+ var ALIAS_TO_ID = /* @__PURE__ */ new Map();
2317
+ for (const profile of Object.values(LANGUAGE_PROFILES)) {
2318
+ for (const alias of [profile.id, profile.displayName, ...profile.aliases]) {
2319
+ ALIAS_TO_ID.set(normalizeLanguageName(alias), profile.id);
2320
+ }
2321
+ }
2322
+ function resolveLanguageProfile(name) {
2323
+ const id = ALIAS_TO_ID.get(normalizeLanguageName(name));
2324
+ return id ? LANGUAGE_PROFILES[id] : void 0;
2325
+ }
2326
+ function isSameLanguage(a, b) {
2327
+ const x = resolveLanguageProfile(a);
2328
+ const y = resolveLanguageProfile(b);
2329
+ if (x && y) return x.id === y.id;
2330
+ if (x || y) return false;
2331
+ const folded = normalizeLanguageName(a);
2332
+ return folded !== "" && folded === normalizeLanguageName(b);
2333
+ }
2334
+ var BASE_SKIP_DIRS = ["node_modules", ".git", "dist"];
2335
+ var ALL_SKIP_DIRS = /* @__PURE__ */ new Set([
2336
+ ...BASE_SKIP_DIRS,
2337
+ ...Object.values(LANGUAGE_PROFILES).flatMap((p) => p.skipDirs)
2338
+ ]);
2339
+ var ALLOWED_BINARIES = new Set(
2340
+ Object.values(LANGUAGE_PROFILES).flatMap((profile) => [
2341
+ ...profile.packageManagers.flatMap((pm) => [
2342
+ ...pm.installSteps.map((s) => s.argv[0]),
2343
+ ...pm.ingest.kind === "auto" ? [pm.ingest.argv[0]] : []
2344
+ ]),
2345
+ ...profile.verification.map((v) => v.argv[0])
2346
+ ])
2347
+ );
2348
+
2349
+ // src/lib/tools/searchFiles.ts
2226
2350
  var MAX_QUERY_LENGTH = 1e3;
2227
2351
  async function walkFiles(dir) {
2228
- const skip = /* @__PURE__ */ new Set(["node_modules", ".git", "dist"]);
2229
2352
  const out = [];
2230
2353
  for (const e of await readdir2(dir, { withFileTypes: true })) {
2231
- if (e.name.startsWith(".") || skip.has(e.name)) continue;
2232
- const full = join8(dir, e.name);
2354
+ if (e.name.startsWith(".") || ALL_SKIP_DIRS.has(e.name)) continue;
2355
+ const full = join9(dir, e.name);
2233
2356
  if (e.isDirectory()) out.push(...await walkFiles(full));
2234
2357
  else if (e.isFile()) out.push(full);
2235
2358
  }
@@ -2262,7 +2385,7 @@ function searchFilesTool(ctx) {
2262
2385
  for (const file of await walkFiles(resolved.target)) {
2263
2386
  let content;
2264
2387
  try {
2265
- content = await readFile6(file, "utf8");
2388
+ content = await readFile7(file, "utf8");
2266
2389
  } catch {
2267
2390
  continue;
2268
2391
  }
@@ -2286,6 +2409,10 @@ function searchFilesTool(ctx) {
2286
2409
  import { tool as tool8 } from "ai";
2287
2410
  import z11 from "zod";
2288
2411
 
2412
+ // src/lib/tools/repoVerification.ts
2413
+ import { existsSync as existsSync2 } from "node:fs";
2414
+ import { join as join10 } from "node:path";
2415
+
2289
2416
  // src/lib/tools/utils/runCommand.ts
2290
2417
  import { spawn as spawn2 } from "node:child_process";
2291
2418
  var INSTALL_TIMEOUT_MS = 15 * 6e4;
@@ -2337,71 +2464,89 @@ Timed out after ${seconds}s: ${command} ${args.join(" ")}`.trim(),
2337
2464
  });
2338
2465
  }
2339
2466
 
2340
- // src/lib/tools/utils/packageManager.ts
2341
- import { readFile as readFile7 } from "node:fs/promises";
2342
- import { existsSync } from "node:fs";
2343
- import { join as join9 } from "node:path";
2344
- var LOCKFILES = [
2345
- ["pnpm-lock.yaml", "pnpm"],
2346
- ["yarn.lock", "yarn"],
2347
- ["bun.lockb", "bun"],
2348
- ["bun.lock", "bun"],
2349
- ["package-lock.json", "npm"]
2350
- ];
2351
- async function readPackageJson(cwd = process.cwd()) {
2352
- return JSON.parse(await readFile7(join9(cwd, "package.json"), "utf8"));
2353
- }
2354
- function packageManagerFrom(pkg) {
2355
- return pkg.packageManager?.split("@")[0] ?? "npm";
2356
- }
2357
- function packageManagerFromLockfile(cwd) {
2358
- return LOCKFILES.find(([file]) => existsSync(join9(cwd, file)))?.[1];
2359
- }
2360
- async function detectPackageManager(cwd) {
2361
- try {
2362
- const pkg = await readPackageJson(cwd);
2363
- if (pkg.packageManager) return packageManagerFrom(pkg);
2364
- } catch {
2365
- }
2366
- return packageManagerFromLockfile(cwd) ?? "npm";
2367
- }
2368
-
2369
2467
  // src/lib/tools/repoVerification.ts
2370
2468
  var VERIFICATION_SCRIPT_CANDIDATES = ["lint", "typecheck", "check"];
2371
- async function runRepoVerificationCheck() {
2469
+ async function runCheck(command, binary, args) {
2470
+ const { code, output } = await runCommand(binary, args, {
2471
+ timeoutMs: VERIFY_TIMEOUT_MS
2472
+ });
2473
+ return { command, exitCode: code, ok: code === 0, output: output.trim() };
2474
+ }
2475
+ async function javascriptChecks() {
2372
2476
  let pkg;
2373
2477
  try {
2374
2478
  pkg = await readPackageJson();
2375
2479
  } catch (err) {
2376
- const limitation = `Could not read package.json to detect verification conventions: ${err.message}`;
2377
- return { ok: false, checks: [], limitation };
2480
+ return {
2481
+ limitation: `Could not read package.json to detect verification conventions: ${err.message}`
2482
+ };
2378
2483
  }
2379
2484
  const scripts = pkg.scripts ?? {};
2380
2485
  const present = VERIFICATION_SCRIPT_CANDIDATES.filter((s) => s in scripts);
2381
2486
  if (present.length === 0) {
2382
- const limitation = `No verification script found in package.json (looked for: ${VERIFICATION_SCRIPT_CANDIDATES.join(", ")}).`;
2383
- return { ok: false, checks: [], limitation };
2487
+ return {
2488
+ limitation: `No verification script found in package.json (looked for: ${VERIFICATION_SCRIPT_CANDIDATES.join(", ")}).`
2489
+ };
2384
2490
  }
2385
2491
  const pm = await detectPackageManager(process.cwd());
2386
2492
  const checks = [];
2387
2493
  for (const script of present) {
2388
- const command = `${pm} run ${script}`;
2389
- const { code, output } = await runCommand(pm, ["run", script], {
2390
- timeoutMs: VERIFY_TIMEOUT_MS
2391
- });
2392
- checks.push({ command, exitCode: code, ok: code === 0, output: output.trim() });
2494
+ checks.push(
2495
+ await runCheck(`${pm} run ${script}`, pm, ["run", script])
2496
+ );
2497
+ }
2498
+ return { checks };
2499
+ }
2500
+ async function registryChecks(id) {
2501
+ const profile = LANGUAGE_PROFILES[id];
2502
+ const runnable = profile.verification.filter(
2503
+ (spec) => !spec.requiresFile || existsSync2(join10(process.cwd(), spec.requiresFile))
2504
+ );
2505
+ if (runnable.length === 0) {
2506
+ return {
2507
+ limitation: `No mechanical verification available for ${profile.displayName} in this repo.`
2508
+ };
2393
2509
  }
2394
- return { ok: checks.every((c) => c.ok), checks };
2510
+ const checks = [];
2511
+ for (const spec of runnable) {
2512
+ checks.push(
2513
+ await runCheck(spec.argv.join(" "), spec.argv[0], [...spec.argv.slice(1)])
2514
+ );
2515
+ }
2516
+ return { checks };
2517
+ }
2518
+ async function runRepoVerificationCheck(languages = [DEFAULT_LANGUAGE_ID]) {
2519
+ const ids = [...new Set(languages)];
2520
+ if (ids.length === 0) ids.push(DEFAULT_LANGUAGE_ID);
2521
+ const checks = [];
2522
+ const limitations = [];
2523
+ for (const id of ids) {
2524
+ const result = id === JAVASCRIPT ? await javascriptChecks() : await registryChecks(id);
2525
+ if ("checks" in result) checks.push(...result.checks);
2526
+ else limitations.push(result.limitation);
2527
+ }
2528
+ if (checks.length === 0) {
2529
+ return {
2530
+ ok: false,
2531
+ checks: [],
2532
+ limitation: limitations.join(" ") || "No verification checks available."
2533
+ };
2534
+ }
2535
+ return {
2536
+ ok: checks.every((c) => c.ok),
2537
+ checks,
2538
+ ...limitations.length ? { limitation: limitations.join(" ") } : {}
2539
+ };
2395
2540
  }
2396
2541
 
2397
2542
  // src/lib/tools/verifyImplementation.ts
2398
- function verifyImplementationTool() {
2543
+ function verifyImplementationTool(ctx) {
2399
2544
  return tool8({
2400
- 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.",
2545
+ description: "Run the repo's mechanical verification checks for generated implementation changes. Uses the conventions of the repo's languages (package.json lint/typecheck/check scripts for JavaScript, the equivalent compile/analyze command elsewhere) and returns structured pass/fail evidence for the verifier to interpret.",
2401
2546
  inputSchema: z11.object(),
2402
2547
  execute: async () => {
2403
- logger.info("called verifyImplementation tool");
2404
- return runRepoVerificationCheck();
2548
+ logger.info({ languages: ctx.languages }, "called verifyImplementation tool");
2549
+ return runRepoVerificationCheck(ctx.languages);
2405
2550
  }
2406
2551
  });
2407
2552
  }
@@ -2527,12 +2672,17 @@ var DEFAULT_TOOL_LIMITS = {
2527
2672
  read: 20,
2528
2673
  match: 100
2529
2674
  };
2530
- function createToolContext(limits = DEFAULT_TOOL_LIMITS, cwd = process.cwd()) {
2675
+ function createToolContext({
2676
+ limits = DEFAULT_TOOL_LIMITS,
2677
+ cwd = process.cwd(),
2678
+ languages = [DEFAULT_LANGUAGE_ID]
2679
+ } = {}) {
2531
2680
  return {
2532
2681
  root: cwd,
2533
2682
  cwd,
2534
2683
  limits,
2535
- counts: { list: 0, search: 0, read: 0 }
2684
+ counts: { list: 0, search: 0, read: 0 },
2685
+ languages: languages.length ? languages : [DEFAULT_LANGUAGE_ID]
2536
2686
  };
2537
2687
  }
2538
2688
 
@@ -2569,7 +2719,7 @@ function createTools(ctx, { output, tools }) {
2569
2719
  searchFiles: withLogging("searchFiles", searchFilesTool(ctx)),
2570
2720
  verifyImplementation: withLogging(
2571
2721
  "verifyImplementation",
2572
- verifyImplementationTool()
2722
+ verifyImplementationTool(ctx)
2573
2723
  ),
2574
2724
  generateRecord: withLogging("generateRecord", generateRecordTool(ctx)),
2575
2725
  notifyUser: withLogging("notifyUser", notifyUserTool())
@@ -2605,7 +2755,7 @@ async function runAgent(req) {
2605
2755
  baseURL: PROXY_BASE_URL,
2606
2756
  fetch: proxyFetch
2607
2757
  });
2608
- const toolContext = createToolContext();
2758
+ const toolContext = createToolContext({ languages: req.languages });
2609
2759
  const readTools = ["readFile", "searchFiles", "listFiles"];
2610
2760
  const hasReadTools = !req.tools || req.tools.some((t) => readTools.includes(t));
2611
2761
  const instructions = [
@@ -2696,8 +2846,11 @@ var detectLanguageSchema = z16.object({
2696
2846
  var detectLanguage = () => runAgent({
2697
2847
  instructions: [
2698
2848
  "Analyze the codebase and determine the programming languages and frameworks used",
2849
+ "Start from the dependency manifests: package.json.",
2850
+ "List the language that owns the backend/data code first \u2014 that is the one an ingestion script will be written in.",
2699
2851
  "If a superset language is found, exclude the subset language. TS-over-JS.",
2700
2852
  "If a meta-framework is used, exclude the framework. Next-over-React.",
2853
+ "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).",
2701
2854
  "Return the exact version",
2702
2855
  "Exclude things like CSS frameworks, build tools, or testing frameworks",
2703
2856
  'Use as few tools as possible, but do not guess. If you cant find the answer, say "unknown"',
@@ -2795,7 +2948,7 @@ async function runAnalysis(mode, extraInstructions = []) {
2795
2948
  // package.json
2796
2949
  var package_default = {
2797
2950
  name: "@algolia/wizard",
2798
- version: "0.9.0-rc.70.61",
2951
+ version: "0.9.0-rc.72.60",
2799
2952
  description: "Magically implement Algolia functionality in your codebase",
2800
2953
  type: "module",
2801
2954
  engines: {
@@ -2898,82 +3051,139 @@ function parseEntries(raw) {
2898
3051
  return raw.split(",").map(clean).filter(Boolean).slice(0, MAX_ENTRIES).map((name) => ({ name, version: "unknown" }));
2899
3052
  }
2900
3053
  var summarize = (entries) => entries.length ? entries.map((e) => e.name).join(", ") : "none";
2901
- async function askList(ctx, prompt, { required = false } = {}) {
3054
+
3055
+ // src/actions/confirmLanguage.ts
3056
+ import z19 from "zod";
3057
+ var confirmLanguageSchema = z19.object({
3058
+ languages: detectLanguageSchema.shape.languages
3059
+ });
3060
+ var OTHER_OPTION = "Other";
3061
+ function confirmed(languages) {
3062
+ track("AI Wizard Language Confirmed", { languages });
3063
+ return { languages };
3064
+ }
3065
+ async function askOtherLanguage(ctx) {
3066
+ let prompt = "enter the language for your ingestion script";
2902
3067
  for (; ; ) {
2903
3068
  const answer = await ctx.requestUserInput({
2904
3069
  prompt,
2905
3070
  promptType: "textInput",
2906
- options: [],
2907
- helpText: 'Comma-separated, e.g. "TypeScript, Node".'
3071
+ options: []
2908
3072
  });
2909
3073
  if (typeof answer !== "string") {
2910
- throw new Error("askList received an unexpected non-text result");
3074
+ throw new Error("confirmLanguage received an unexpected non-text result");
2911
3075
  }
2912
- const entries = parseEntries(answer);
2913
- if (entries.length || !required) return entries;
2914
- prompt = "Please enter at least one entry:";
3076
+ const name = parseEntries(answer)[0]?.name;
3077
+ if (name) return name;
3078
+ prompt = "please enter a language name:";
2915
3079
  }
2916
3080
  }
2917
-
2918
- // src/actions/confirmLanguage.ts
2919
- import z19 from "zod";
2920
- var confirmLanguageSchema = z19.object({
2921
- languages: detectLanguageSchema.shape.languages
2922
- });
2923
3081
  async function confirmLanguage(ctx) {
2924
3082
  const detected = ctx.getStepOutput("project-scan");
2925
- const answer = await ctx.requestUserInput({
2926
- prompt: "Did we detect your language(s) correctly?",
2927
- promptType: "acceptReject",
2928
- options: ["Yes", "No"],
2929
- messages: [`Languages: ${summarize(detected.languages)}`]
2930
- });
2931
- const languages = answer === true ? detected.languages : await askList(ctx, "List the languages your project uses:", {
2932
- required: true
2933
- });
2934
- track("AI Wizard Language Confirmed", {
2935
- languages
3083
+ const detectedLanguages = detected.languages ?? [];
3084
+ const others = (chosen) => detectedLanguages.filter((l) => !isSameLanguage(l.name, chosen));
3085
+ const primary = detectedLanguages[0];
3086
+ if (primary) {
3087
+ const accepted = await ctx.requestUserInput({
3088
+ prompt: `Write the ingestion script in ${primary.name}?`,
3089
+ promptType: "acceptReject",
3090
+ options: [`Confirm ${primary.name}`, "Use a different language"],
3091
+ secondary: [{ kind: "badge", value: "[DETECTED]" }, void 0],
3092
+ messages: detectedLanguages.length > 1 ? [`Detected: ${summarize(detectedLanguages)}`] : []
3093
+ });
3094
+ if (accepted === true) return confirmed(detectedLanguages);
3095
+ }
3096
+ const options = [...CURATED_LANGUAGES];
3097
+ for (const language of detectedLanguages) {
3098
+ if (!options.some((o) => isSameLanguage(o, language.name))) {
3099
+ options.push(language.name);
3100
+ }
3101
+ }
3102
+ options.push(OTHER_OPTION);
3103
+ const detectedFor = (option) => detectedLanguages.find((l) => isSameLanguage(option, l.name));
3104
+ const secondary = options.map(
3105
+ (o) => detectedFor(o) ? { kind: "badge", value: "[DETECTED]" } : void 0
3106
+ );
3107
+ const defaultSelectedIndex = Math.max(
3108
+ options.findIndex((o) => detectedFor(o)),
3109
+ 0
3110
+ );
3111
+ const selection = await ctx.requestUserInput({
3112
+ prompt: "select the language for your ingestion script",
3113
+ promptType: "multipleChoice",
3114
+ options,
3115
+ secondary,
3116
+ defaultSelectedIndex
2936
3117
  });
2937
- return { languages };
3118
+ if (typeof selection !== "string") {
3119
+ throw new Error("confirmLanguage received an unexpected non-text result");
3120
+ }
3121
+ const name = selection === OTHER_OPTION ? await askOtherLanguage(ctx) : selection;
3122
+ const version = detectedFor(name)?.version ?? "unknown";
3123
+ return confirmed([{ name, version }, ...others(name)]);
2938
3124
  }
2939
3125
 
2940
3126
  // src/actions/confirmFramework.ts
2941
3127
  import z20 from "zod";
2942
- var confirmFrameworkSchema = z20.object({
2943
- frameworks: detectLanguageSchema.shape.frameworks
2944
- });
2945
- var CURATED_FRAMEWORKS = [
2946
- "Next.js",
2947
- "React",
2948
- "Vue",
2949
- "Angular",
2950
- "Svelte",
2951
- "Vanilla JS"
3128
+
3129
+ // src/lib/frameworks.ts
3130
+ var BACKEND_ONLY_FRAMEWORK = "Backend only / API";
3131
+ var FRAMEWORKS = [
3132
+ // Frontend — InstantSearch component flavors.
3133
+ { name: "Next.js", strategy: "react", aliases: ["next", "nextjs"] },
3134
+ { name: "React", strategy: "react", aliases: ["reactjs"] },
3135
+ { name: "Vue", strategy: "vue", aliases: ["vuejs", "nuxt", "nuxtjs"] },
3136
+ { name: "Angular", strategy: "angular", aliases: ["angularjs"] },
3137
+ // No Svelte InstantSearch flavor exists, so it uses InstantSearch.js.
3138
+ { name: "Svelte", strategy: "js", aliases: ["sveltekit"] },
3139
+ {
3140
+ name: "Vanilla JS",
3141
+ strategy: "js",
3142
+ aliases: ["vanilla", "javascript", "js", "astro", "vite"]
3143
+ },
3144
+ // Backend — Algolia's official framework integrations. Server-rendered
3145
+ // templates get InstantSearch.js from a CDN.
3146
+ {
3147
+ name: "Rails",
3148
+ strategy: "cdn-template",
3149
+ aliases: ["rubyonrails", "ruby on rails", "erb"]
3150
+ },
3151
+ { name: "Django", strategy: "cdn-template", aliases: ["jinja", "jinja2"] },
3152
+ { name: "Laravel", strategy: "cdn-template", aliases: ["blade"] },
3153
+ { name: "Symfony", strategy: "cdn-template", aliases: ["twig"] },
3154
+ // Mobile — Algolia ships InstantSearch iOS/Android and Dart clients, but the
3155
+ // wizard can't scaffold a native UI, so it points at the docs instead.
3156
+ { name: "Flutter", strategy: "none", aliases: [] },
3157
+ { name: "iOS", strategy: "none", aliases: ["swiftui", "uikit"] },
3158
+ { name: "Android", strategy: "none", aliases: ["jetpack compose", "compose"] },
3159
+ { name: BACKEND_ONLY_FRAMEWORK, strategy: "cdn-template", aliases: [] }
2952
3160
  ];
2953
- var OTHER_OPTION = "Other";
3161
+ var CURATED_FRAMEWORKS = FRAMEWORKS.map(
3162
+ (f) => f.name
3163
+ );
2954
3164
  var normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
2955
- var FRAMEWORK_ALIASES = {
2956
- next: "nextjs",
2957
- nextjs: "nextjs",
2958
- react: "react",
2959
- reactjs: "react",
2960
- vue: "vue",
2961
- vuejs: "vue",
2962
- angular: "angular",
2963
- angularjs: "angular",
2964
- svelte: "svelte",
2965
- sveltekit: "svelte",
2966
- vanillajs: "vanillajs",
2967
- vanilla: "vanillajs",
2968
- javascript: "vanillajs",
2969
- js: "vanillajs"
2970
- };
2971
- var isSameFramework = (a, b) => {
2972
- const x = FRAMEWORK_ALIASES[normalize(a)] ?? normalize(a);
2973
- const y = FRAMEWORK_ALIASES[normalize(b)] ?? normalize(b);
3165
+ var ALIAS_TO_NAME = /* @__PURE__ */ new Map();
3166
+ for (const framework of FRAMEWORKS) {
3167
+ for (const alias of [framework.name, ...framework.aliases]) {
3168
+ ALIAS_TO_NAME.set(normalize(alias), framework.name);
3169
+ }
3170
+ }
3171
+ var STRATEGY_BY_NAME = new Map(FRAMEWORKS.map((f) => [f.name, f.strategy]));
3172
+ function canonicalFrameworkName(name) {
3173
+ return ALIAS_TO_NAME.get(normalize(name));
3174
+ }
3175
+ function isSameFramework(a, b) {
3176
+ const x = canonicalFrameworkName(a) ?? normalize(a);
3177
+ const y = canonicalFrameworkName(b) ?? normalize(b);
2974
3178
  return x !== "" && x === y;
2975
- };
2976
- function confirmed(name, version) {
3179
+ }
3180
+
3181
+ // src/actions/confirmFramework.ts
3182
+ var confirmFrameworkSchema = z20.object({
3183
+ frameworks: detectLanguageSchema.shape.frameworks
3184
+ });
3185
+ var OTHER_OPTION2 = "Other";
3186
+ function confirmed2(name, version) {
2977
3187
  const frameworks = [{ name, version: version ?? "unknown" }];
2978
3188
  track("AI Wizard Frontend Framework Confirmed", { frameworks });
2979
3189
  return { frameworks };
@@ -3001,7 +3211,7 @@ async function confirmFramework(ctx) {
3001
3211
  for (const fw of detectedFrameworks) {
3002
3212
  if (!options.some((o) => isSameFramework(o, fw.name))) options.push(fw.name);
3003
3213
  }
3004
- options.push(OTHER_OPTION);
3214
+ options.push(OTHER_OPTION2);
3005
3215
  const detectedFor = (option) => detectedFrameworks.find((fw) => isSameFramework(option, fw.name));
3006
3216
  const primary = detectedFrameworks[0];
3007
3217
  if (primary) {
@@ -3011,7 +3221,7 @@ async function confirmFramework(ctx) {
3011
3221
  options: [`Confirm ${primary.name}`, "Use a different framework"],
3012
3222
  secondary: [{ kind: "badge", value: "[DETECTED]" }, void 0]
3013
3223
  });
3014
- if (accepted === true) return confirmed(primary.name, primary.version);
3224
+ if (accepted === true) return confirmed2(primary.name, primary.version);
3015
3225
  }
3016
3226
  const secondary = options.map(
3017
3227
  (o) => detectedFor(o) ? { kind: "badge", value: "[DETECTED]" } : void 0
@@ -3021,7 +3231,7 @@ async function confirmFramework(ctx) {
3021
3231
  0
3022
3232
  );
3023
3233
  const selection = await ctx.requestUserInput({
3024
- prompt: "select a framework",
3234
+ prompt: "select the framework that renders your UI",
3025
3235
  promptType: "multipleChoice",
3026
3236
  options,
3027
3237
  secondary,
@@ -3030,10 +3240,10 @@ async function confirmFramework(ctx) {
3030
3240
  if (typeof selection !== "string") {
3031
3241
  throw new Error("confirmFramework received an unexpected non-text result");
3032
3242
  }
3033
- if (selection === OTHER_OPTION) {
3034
- return confirmed(await askOtherFramework(ctx));
3243
+ if (selection === OTHER_OPTION2) {
3244
+ return confirmed2(await askOtherFramework(ctx));
3035
3245
  }
3036
- return confirmed(selection, detectedFor(selection)?.version);
3246
+ return confirmed2(selection, detectedFor(selection)?.version);
3037
3247
  }
3038
3248
 
3039
3249
  // src/actions/promptUser.ts
@@ -3126,15 +3336,15 @@ async function confirmEntities(ctx) {
3126
3336
  onSubmit: () => {
3127
3337
  }
3128
3338
  });
3129
- const confirmed2 = typeof selection === "string" ? entities.filter((e) => e.name === selection) : [];
3130
- if (confirmed2.length === 0) {
3339
+ const confirmed3 = typeof selection === "string" ? entities.filter((e) => e.name === selection) : [];
3340
+ if (confirmed3.length === 0) {
3131
3341
  throw new Error("User cancelled entity selection \u2014 analysis halted.");
3132
3342
  }
3133
- ctx.setUserInput("confirmedEntities", confirmed2);
3343
+ ctx.setUserInput("confirmedEntities", confirmed3);
3134
3344
  track("AI Wizard Entities Confirmed", {
3135
- entities: toEntitySummary(confirmed2)
3345
+ entities: toEntitySummary(confirmed3)
3136
3346
  });
3137
- return { ingestionAnalysis: entities, confirmedEntities: confirmed2 };
3347
+ return { ingestionAnalysis: entities, confirmedEntities: confirmed3 };
3138
3348
  }
3139
3349
 
3140
3350
  // src/actions/review.ts
@@ -3206,7 +3416,7 @@ import {
3206
3416
  basename as basename2,
3207
3417
  dirname as dirname7,
3208
3418
  isAbsolute as isAbsolute2,
3209
- join as join10,
3419
+ join as join11,
3210
3420
  relative as relative2,
3211
3421
  resolve as resolve3
3212
3422
  } from "node:path";
@@ -3240,7 +3450,7 @@ async function isWorkingTreeDirty(repoRoot) {
3240
3450
  return out.trim().length > 0;
3241
3451
  }
3242
3452
  async function pruneOldWorktrees(repoRoot) {
3243
- const dir = join10(stateDir(repoRoot), "worktrees");
3453
+ const dir = join11(stateDir(repoRoot), "worktrees");
3244
3454
  const stale = (await readdir3(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
3245
3455
  for (const slug of stale) {
3246
3456
  const branch = slug.replace("wizard-implement-", WIZARD_BRANCH_PREFIX);
@@ -3251,7 +3461,7 @@ async function pruneOldWorktrees(repoRoot) {
3251
3461
  "worktree",
3252
3462
  "remove",
3253
3463
  "--force",
3254
- join10(dir, slug)
3464
+ join11(dir, slug)
3255
3465
  ]);
3256
3466
  await git(["-C", repoRoot, "branch", "-D", branch]);
3257
3467
  } catch (err) {
@@ -3265,7 +3475,7 @@ async function pruneOldWorktrees(repoRoot) {
3265
3475
  async function createWorktree(repoRoot) {
3266
3476
  const branch = `${WIZARD_BRANCH_PREFIX}${Date.now()}`;
3267
3477
  const dirSlug = branch.replace(/\//g, "-");
3268
- const path = join10(stateDir(repoRoot), "worktrees", dirSlug);
3478
+ const path = join11(stateDir(repoRoot), "worktrees", dirSlug);
3269
3479
  await git(["-C", repoRoot, "worktree", "prune"]);
3270
3480
  await pruneOldWorktrees(repoRoot);
3271
3481
  await mkdir6(dirname7(path), { recursive: true });
@@ -3385,8 +3595,8 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
3385
3595
  } catch {
3386
3596
  return { ok: false, reason: `"${sourcePath}" does not exist` };
3387
3597
  }
3388
- const relPath = join10(ingestDir, basename2(source));
3389
- const dest = join10(worktreePath, relPath);
3598
+ const relPath = join11(ingestDir, basename2(source));
3599
+ const dest = join11(worktreePath, relPath);
3390
3600
  try {
3391
3601
  await mkdir6(dirname7(dest), { recursive: true });
3392
3602
  await copyFile(source, dest);
@@ -3402,7 +3612,7 @@ function hasEnvVar(content, name) {
3402
3612
  return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
3403
3613
  }
3404
3614
  async function writeSearchEnvValues(worktreePath, vars) {
3405
- const target = join10(worktreePath, ".env");
3615
+ const target = join11(worktreePath, ".env");
3406
3616
  let existing = "";
3407
3617
  try {
3408
3618
  existing = await readFile8(target, "utf8");
@@ -3522,15 +3732,15 @@ async function resolveSearchOnlyKey(index) {
3522
3732
  }
3523
3733
 
3524
3734
  // src/lib/algoliaDocs.ts
3525
- import { readFileSync, readdirSync, existsSync as existsSync2 } from "node:fs";
3526
- import { dirname as dirname8, join as join11 } from "node:path";
3735
+ import { readFileSync, readdirSync, existsSync as existsSync3 } from "node:fs";
3736
+ import { dirname as dirname8, join as join12 } from "node:path";
3527
3737
  import { fileURLToPath as fileURLToPath2 } from "node:url";
3528
- var DOCS_SUBPATH = join11("docs", "algolia-sdk");
3738
+ var DOCS_SUBPATH = join12("docs", "algolia-sdk");
3529
3739
  function findDocsDir() {
3530
3740
  let dir = dirname8(fileURLToPath2(import.meta.url));
3531
3741
  for (; ; ) {
3532
- const candidate = join11(dir, DOCS_SUBPATH);
3533
- if (existsSync2(candidate)) return candidate;
3742
+ const candidate = join12(dir, DOCS_SUBPATH);
3743
+ if (existsSync3(candidate)) return candidate;
3534
3744
  const parent = dirname8(dir);
3535
3745
  if (parent === dir) return void 0;
3536
3746
  dir = parent;
@@ -3552,7 +3762,7 @@ function loadAlgoliaDoc(language) {
3552
3762
  );
3553
3763
  return "";
3554
3764
  }
3555
- return readFileSync(join11(docsDir, files[0]), "utf8").trim();
3765
+ return readFileSync(join12(docsDir, files[0]), "utf8").trim();
3556
3766
  }
3557
3767
  function getNamedDoc(name, language) {
3558
3768
  const docsDir = findDocsDir();
@@ -3560,8 +3770,8 @@ function getNamedDoc(name, language) {
3560
3770
  logger.warn("docs/algolia-sdk not found");
3561
3771
  return "";
3562
3772
  }
3563
- const file = join11(docsDir, `${name}-${language}.md`);
3564
- if (!existsSync2(file)) {
3773
+ const file = join12(docsDir, `${name}-${language}.md`);
3774
+ if (!existsSync3(file)) {
3565
3775
  logger.warn({ name, language }, "named SDK reference not found");
3566
3776
  return "";
3567
3777
  }
@@ -3581,11 +3791,6 @@ function getFrameworkSpecificDoc(frameworks) {
3581
3791
  return loadAlgoliaDoc("js");
3582
3792
  }
3583
3793
 
3584
- // src/lib/shell.ts
3585
- function shellQuote(value) {
3586
- return "'" + value.replace(/'/g, "'\\''") + "'";
3587
- }
3588
-
3589
3794
  // src/actions/implement.ts
3590
3795
  var implementSchema = z24.object({
3591
3796
  filesChanged: z24.array(z24.string()),
@@ -3634,7 +3839,7 @@ var verificationOutputSchema = z24.object({
3634
3839
  });
3635
3840
  var MAX_IMPLEMENT_VERIFICATION_ATTEMPTS = 3;
3636
3841
  var DEFAULT_IMPLEMENT_USE_CASES = ["ingestion", "search"];
3637
- var INGEST_DIR = ".algolia-wizard";
3842
+ var INGEST_DIR2 = ".algolia-wizard";
3638
3843
  function detectUiFramework(language) {
3639
3844
  const names = language.frameworks.map((f) => f.name.toLowerCase());
3640
3845
  if (names.some((n) => n.includes("vue") || n.includes("nuxt"))) return "Vue";
@@ -3898,7 +4103,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
3898
4103
  await confirmDirtyWorkingTree(ctx, repoRoot);
3899
4104
  }
3900
4105
  const normalized = normalizeFindingPaths(findings);
3901
- const confirmed2 = normalized.confirmedEntities;
4106
+ const confirmed3 = normalized.confirmedEntities;
3902
4107
  const searchLocation = normalized.searchImplementationAnalysis;
3903
4108
  let appId;
3904
4109
  let searchKey;
@@ -3922,7 +4127,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
3922
4127
  const copied = await copyUploadIntoWorktree(
3923
4128
  repoRoot,
3924
4129
  worktree,
3925
- INGEST_DIR,
4130
+ INGEST_DIR2,
3926
4131
  uploadSourcePath ?? ""
3927
4132
  );
3928
4133
  if (copied.ok) {
@@ -3938,14 +4143,14 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
3938
4143
  }
3939
4144
  const input = {
3940
4145
  findings: normalized,
3941
- confirmed: confirmed2,
4146
+ confirmed: confirmed3,
3942
4147
  searchLocation,
3943
4148
  targetIndex,
3944
4149
  language,
3945
4150
  appId,
3946
4151
  searchKey,
3947
4152
  searchEnvVars: searchEnvVars(language, appId, searchKey),
3948
- ingestDir: INGEST_DIR,
4153
+ ingestDir: INGEST_DIR2,
3949
4154
  ingestionSource,
3950
4155
  uploadFilePath,
3951
4156
  // language.frameworks already prefers the confirm-framework step output,
@@ -4037,7 +4242,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4037
4242
  ingestRecordCount = parseIngestRecordCount(run2.output);
4038
4243
  if (ingestRecordCount != null) {
4039
4244
  track("AI Wizard Ingest Successful", {
4040
- entity_name: confirmed2?.map((e) => e.name).join(", ") || "unknown",
4245
+ entity_name: confirmed3?.map((e) => e.name).join(", ") || "unknown",
4041
4246
  record_count: ingestRecordCount,
4042
4247
  duration_ms: ingestDurationMs
4043
4248
  });
@@ -4518,7 +4723,7 @@ function parseCliArgs(argv) {
4518
4723
 
4519
4724
  // src/lib/resetState.ts
4520
4725
  import { readdir as readdir4, rm as rm2 } from "node:fs/promises";
4521
- import { join as join12 } from "node:path";
4726
+ import { join as join13 } from "node:path";
4522
4727
  var KEEP = ["wizard.log"];
4523
4728
  async function resetProjectState() {
4524
4729
  const dir = stateDir();
@@ -4530,7 +4735,7 @@ async function resetProjectState() {
4530
4735
  }
4531
4736
  const targets = entries.filter((name) => !KEEP.includes(name));
4532
4737
  await Promise.all(
4533
- targets.map((name) => rm2(join12(dir, name), { recursive: true, force: true }))
4738
+ targets.map((name) => rm2(join13(dir, name), { recursive: true, force: true }))
4534
4739
  );
4535
4740
  return { dir, removed: targets };
4536
4741
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@algolia/wizard",
3
- "version": "0.9.0-rc.70.61",
3
+ "version": "0.9.0-rc.72.60",
4
4
  "description": "Magically implement Algolia functionality in your codebase",
5
5
  "type": "module",
6
6
  "engines": {