@algolia/wizard 0.85.0 → 0.87.0-rc.211.681

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 +182 -49
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -2332,7 +2332,7 @@ async function ensureApplication(resumeAppId) {
2332
2332
  // package.json
2333
2333
  var package_default = {
2334
2334
  name: "@algolia/wizard",
2335
- version: "0.85.0",
2335
+ version: "0.87.0-rc.211.681",
2336
2336
  description: "Magically implement Algolia functionality in your codebase",
2337
2337
  type: "module",
2338
2338
  engines: {
@@ -2805,6 +2805,9 @@ async function runWorkflow(workflow, startWorkflowAppId, options) {
2805
2805
  }
2806
2806
  }
2807
2807
 
2808
+ // src/actions/selectIndex.ts
2809
+ import { basename } from "node:path";
2810
+
2808
2811
  // src/actions/listIndices.ts
2809
2812
  import { z as z5 } from "zod";
2810
2813
  var indicesListSchema = z5.object({
@@ -2839,11 +2842,28 @@ function indexNameError(name) {
2839
2842
  return "Index name cannot end with .txt, .bin, or .idx.";
2840
2843
  return void 0;
2841
2844
  }
2845
+ function slug(text) {
2846
+ return text.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
2847
+ }
2848
+ function suggestIndexName(project, entity, taken) {
2849
+ const base = ([slug(project), slug(entity ?? "")].filter(Boolean).join("_") || "records").slice(0, MAX_INDEX_NAME_LENGTH);
2850
+ let name = base;
2851
+ for (let n = 2; taken.includes(name); n++) {
2852
+ const suffix = `_${n}`;
2853
+ name = base.slice(0, MAX_INDEX_NAME_LENGTH - suffix.length) + suffix;
2854
+ }
2855
+ return name;
2856
+ }
2842
2857
  var selectIndexStep = async (ctx) => {
2843
2858
  await requireApplication(ctx.appId);
2844
2859
  const indices = await listIndices();
2845
2860
  const names = indices.map((i) => i.name);
2846
2861
  const hasIndices = names.length > 0;
2862
+ const entity = ctx.getStepOutput(
2863
+ "confirm-entities"
2864
+ )?.confirmedEntities?.[0]?.name;
2865
+ const suggested = suggestIndexName(basename(process.cwd()), entity, names);
2866
+ const suggestionHint = `Press Enter to use "${suggested}".`;
2847
2867
  let error;
2848
2868
  let chosen;
2849
2869
  while (chosen === void 0) {
@@ -2858,7 +2878,8 @@ var selectIndexStep = async (ctx) => {
2858
2878
  promptType: "textInput",
2859
2879
  options: [],
2860
2880
  messages: [
2861
- "This app has no existing indices \u2014 enter a name to create one."
2881
+ "This app has no existing indices \u2014 enter a name to create one.",
2882
+ suggestionHint
2862
2883
  ],
2863
2884
  error
2864
2885
  });
@@ -2868,17 +2889,18 @@ var selectIndexStep = async (ctx) => {
2868
2889
  let candidate;
2869
2890
  let isNewName = true;
2870
2891
  if (!hasIndices) {
2871
- candidate = selection.trim();
2892
+ candidate = selection.trim() || suggested;
2872
2893
  } else if (selection === CREATE_NEW_INDEX) {
2873
2894
  const name = await ctx.requestUserInput({
2874
2895
  prompt: "Name the new index:",
2875
2896
  promptType: "textInput",
2876
- options: []
2897
+ options: [],
2898
+ messages: [suggestionHint]
2877
2899
  });
2878
2900
  if (typeof name !== "string") {
2879
2901
  throw new Error("selectIndex received an unexpected non-text result");
2880
2902
  }
2881
- candidate = name.trim();
2903
+ candidate = name.trim() || suggested;
2882
2904
  } else {
2883
2905
  candidate = selection;
2884
2906
  isNewName = false;
@@ -3114,14 +3136,14 @@ import z9 from "zod";
3114
3136
  import { readFile as readFile4 } from "node:fs/promises";
3115
3137
 
3116
3138
  // src/lib/tools/env.ts
3117
- import { basename } from "node:path";
3139
+ import { basename as basename2 } from "node:path";
3118
3140
  function isEnvFile(filePath) {
3119
- const name = basename(filePath);
3141
+ const name = basename2(filePath);
3120
3142
  return name === ".env" || name.startsWith(".env.");
3121
3143
  }
3122
3144
  function isSecretEnvFile(filePath) {
3123
3145
  if (!isEnvFile(filePath)) return false;
3124
- const name = basename(filePath);
3146
+ const name = basename2(filePath);
3125
3147
  return !/\.(example|sample|template)$/.test(name);
3126
3148
  }
3127
3149
  function secretEnvWriteRefusal(filePath) {
@@ -3756,7 +3778,7 @@ import { execFile as execFile2 } from "node:child_process";
3756
3778
  import { tool as tool8 } from "ai";
3757
3779
  import z15 from "zod";
3758
3780
  import { lstat as lstat3, readdir as readdir2, readFile as readFile8, realpath, stat as stat2 } from "node:fs/promises";
3759
- import { basename as basename2, isAbsolute as isAbsolute2, join as join8, relative as relative4, sep as sep2 } from "node:path";
3781
+ import { basename as basename3, isAbsolute as isAbsolute2, join as join8, relative as relative4, sep as sep2 } from "node:path";
3760
3782
  import { performance } from "node:perf_hooks";
3761
3783
  import { Worker } from "node:worker_threads";
3762
3784
  var MAX_GIT_OUTPUT_BYTES = 32e6;
@@ -4099,7 +4121,7 @@ async function safeSearchPath(rootPath, file) {
4099
4121
  }
4100
4122
  async function searchableFiles(root, target) {
4101
4123
  try {
4102
- if (SKIP_FILES.has(basename2(target)) && (await lstat3(target)).isFile()) {
4124
+ if (SKIP_FILES.has(basename3(target)) && (await lstat3(target)).isFile()) {
4103
4125
  return [target];
4104
4126
  }
4105
4127
  const gitRoot = await gitRootForTarget(root, target);
@@ -4278,7 +4300,7 @@ import { tool as tool9 } from "ai";
4278
4300
  import z16 from "zod";
4279
4301
  import { createHash as createHash2 } from "node:crypto";
4280
4302
  import { readFile as readFile9, stat as stat3 } from "node:fs/promises";
4281
- import { basename as basename3, relative as relative5, resolve as resolve3 } from "node:path";
4303
+ import { basename as basename4, relative as relative5, resolve as resolve3 } from "node:path";
4282
4304
 
4283
4305
  // src/lib/tools/utils/prompt.ts
4284
4306
  var chain = Promise.resolve();
@@ -4376,7 +4398,7 @@ function commandDiffersFromReviewedScript(ctx, command, cwd) {
4376
4398
  if (approval.command === command && approval.cwd === cwd) return false;
4377
4399
  if (approval.command === command) return true;
4378
4400
  const scriptReferences = [
4379
- basename3(approval.filePath),
4401
+ basename4(approval.filePath),
4380
4402
  relative5(ctx.root, approval.filePath),
4381
4403
  ".algolia-wizard"
4382
4404
  ];
@@ -5097,8 +5119,8 @@ function generateRecordTool(ctx, createModel = defaultCreateModel2) {
5097
5119
  ...record,
5098
5120
  objectID: nanoid2()
5099
5121
  }));
5100
- const slug = entityName.toLowerCase().replace(/[^a-z0-9]+/g, "-");
5101
- const relPath = `${DATA_DIR}/${slug}.json`;
5122
+ const slug2 = entityName.toLowerCase().replace(/[^a-z0-9]+/g, "-");
5123
+ const relPath = `${DATA_DIR}/${slug2}.json`;
5102
5124
  const resolved2 = resolveInRoot(ctx, relPath);
5103
5125
  if (resolved2.ok === false) return resolved2.error;
5104
5126
  const writeError = await safeWriteFile(
@@ -6051,7 +6073,7 @@ import {
6051
6073
  // src/lib/git.ts
6052
6074
  import { execFile as execFile3 } from "node:child_process";
6053
6075
  import { copyFile, mkdir as mkdir4, stat as stat4 } from "node:fs/promises";
6054
- import { basename as basename4, dirname as dirname3, isAbsolute as isAbsolute3, join as join9, resolve as resolve4 } from "node:path";
6076
+ import { basename as basename5, dirname as dirname3, isAbsolute as isAbsolute3, join as join9, resolve as resolve4 } from "node:path";
6055
6077
  var MAX_BUFFER = 32 * 1024 * 1024;
6056
6078
  function git(args) {
6057
6079
  return new Promise((resolve6, reject) => {
@@ -6088,7 +6110,7 @@ async function copyUploadIntoProject(repoRoot, ingestDir, sourcePath) {
6088
6110
  } catch {
6089
6111
  return { ok: false, reason: `"${sourcePath}" does not exist` };
6090
6112
  }
6091
- const relPath = join9(ingestDir, basename4(source));
6113
+ const relPath = join9(ingestDir, basename5(source));
6092
6114
  const dest = join9(repoRoot, relPath);
6093
6115
  if (resolve4(source) === resolve4(dest)) {
6094
6116
  return { ok: true, relPath };
@@ -6246,6 +6268,7 @@ var INGEST_RETRY = {
6246
6268
  retry: "Retry ingestion",
6247
6269
  skip: "Continue without records"
6248
6270
  };
6271
+ var INGEST_AGENT_RETRY_PROMPT = "Retry to let the wizard write and run the ingestion script again.";
6249
6272
  var FILE_UPLOAD_PATH_PROMPT = "Enter the path to your JSON, CSV, or TSV file (relative to the project root, or absolute):";
6250
6273
  var JS_LANGUAGES = ["javascript", "typescript", "jsx", "tsx", "node"];
6251
6274
  function lower(entries) {
@@ -6472,9 +6495,29 @@ function cdnSearchInstructions(input) {
6472
6495
  input.searchKey ? `Set window.${SEARCH_CONFIG_SEARCH_KEY} to "${input.searchKey}".` : `A real search-only key could not be provisioned${input.searchKeyError ? ` (${input.searchKeyError})` : ""} \u2014 set window.${SEARCH_CONFIG_SEARCH_KEY} to the placeholder "${SEARCH_KEY_PLACEHOLDER}" and add a prominent TODO for the developer to fill in a real one.`,
6473
6496
  'Load these exact pinned tags in order before the config and search scripts: <script src="https://cdn.jsdelivr.net/npm/algoliasearch@5.59.0/dist/lite/builds/browser.umd.js" integrity="sha256-pduQHl1jn0IaN/BIpSotQm7Y5THdpKcX3Bw6xdRteRw=" crossorigin="anonymous"></script> then <script src="https://cdn.jsdelivr.net/npm/instantsearch.js@4.117.0/dist/instantsearch.production.min.js" integrity="sha256-5zhKxAGeH7ThWfnmdD4pS1gsmafE2b8aWRsq8set/dc=" crossorigin="anonymous"></script>.',
6474
6497
  'When using the InstantSearch theme, load this exact tag before the application styles: <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/instantsearch.css@8.22.1/themes/satellite-min.css" integrity="sha256-5/eHPZl63VYJSDVOKrOgGe/5+owUkX3uPAW6+XYeLNc=" crossorigin="anonymous">. Never use an unversioned or differently versioned theme URL, and never omit its integrity attribute.',
6475
- `In that classic search script, use window['algoliasearch/lite'].liteClient and window.instantsearch. Add window.instantsearch.widgets.autocomplete as the only search widget. Configure it with container '#autocomplete', the content-specific placeholder ${JSON.stringify(placeholder)}, and an indices array containing one object whose indexName is window.${SEARCH_CONFIG_INDEX_NAME}; put item and noResults functions in its templates option, implement getURL only for an existing route, and match the item template to the entity schema. Keep insights: true and call search.start(). Do not substitute searchBox or hits, omit indices, use imports or exports, or use type="module".`
6498
+ `In that classic search script, use window['algoliasearch/lite'].liteClient and window.instantsearch. Add window.instantsearch.widgets.autocomplete as the only search widget. Configure it with container '#autocomplete', the content-specific placeholder ${JSON.stringify(placeholder)}, and an indices array containing one object whose indexName is window.${SEARCH_CONFIG_INDEX_NAME}. Put the item and noResults functions in the templates option of that indices entry, implement getURL only for an existing route, and match the item template to the entity schema. Keep insights: true and call search.start(). Do not substitute searchBox or hits, omit indices, use imports or exports, or use type="module".`,
6499
+ CDN_AUTOCOMPLETE_SHAPE
6476
6500
  ];
6477
6501
  }
6502
+ var CDN_AUTOCOMPLETE_SHAPE = `Use this shape for the autocomplete call. Replace the item fields and the getURL route with the real record fields and an existing route:
6503
+ \`\`\`js
6504
+ window.instantsearch.widgets.autocomplete({
6505
+ container: '#autocomplete',
6506
+ placeholder: '...',
6507
+ indices: [
6508
+ {
6509
+ indexName: window.${SEARCH_CONFIG_INDEX_NAME},
6510
+ getURL: (item) => '/existing-route/' + item.slug,
6511
+ templates: {
6512
+ item: ({ item, onSelect }, { html, components }) =>
6513
+ html\`<div onClick=\${onSelect}>\${components.Highlight({ hit: item, attribute: 'name' })}</div>\`,
6514
+ noResults: (_, { html }) => html\`<span>No results</span>\`,
6515
+ },
6516
+ },
6517
+ ],
6518
+ })
6519
+ \`\`\`
6520
+ The widget reads the item and noResults templates only from each indices entry. Its own top-level templates option accepts only panel. If item is not inside indices[].templates, the widget throws "Template must be 'string' or 'function', was 'undefined' (key: item)" on the first search and shows no results.`;
6478
6521
  function validationInstructions(input) {
6479
6522
  return [
6480
6523
  "Validate the Algolia search implementation by inspecting the project source.",
@@ -6495,7 +6538,8 @@ function validationInstructions(input) {
6495
6538
  "Do not modify files. Report each implementation issue as a concrete instruction for the next implementation pass.",
6496
6539
  `Detected verification tools: ${JSON.stringify(input.findings.verification ?? [])}. Use only a type checker; ignore any linter or formatter in this list.`,
6497
6540
  ...!input.frontendHasPackageJson ? [
6498
- "The frontend has no package.json: never create one or run npm, pnpm, yarn, or bun. Validate the CDN implementation by inspecting its HTML or template and classic scripts for the search container, Algolia \u2192 InstantSearch \u2192 config \u2192 search load order, browser globals, autocomplete widget configuration, search.start(), and exact pinned CDN URLs and integrity attributes for every Algolia script or stylesheet. Source inspection is required even when the project has no automated checks. window.instantsearch.widgets.autocomplete is a real widget in the pinned InstantSearch build; do not report its use as an error, and do not fetch a remote bundle or URL to verify it."
6541
+ "The frontend has no package.json: never create one or run npm, pnpm, yarn, or bun. Validate the CDN implementation by inspecting its HTML or template and classic scripts for the search container, Algolia \u2192 InstantSearch \u2192 config \u2192 search load order, browser globals, autocomplete widget configuration, search.start(), and exact pinned CDN URLs and integrity attributes for every Algolia script or stylesheet. Source inspection is required even when the project has no automated checks. window.instantsearch.widgets.autocomplete is a real widget in the pinned InstantSearch build; do not report its use as an error, and do not fetch a remote bundle or URL to verify it.",
6542
+ "In the autocomplete call, confirm that the item and noResults templates are inside the templates option of the indices entry, not in the top-level templates option of the widget. The top-level templates option accepts only panel, so an item template there throws on the first search and no results show. If item is missing from indices[].templates, set sufficient=false and tell the next pass to move the templates into the indices entry."
6499
6543
  ] : [],
6500
6544
  "Always call reportStatus with status=success after validation, even when sufficient=false.",
6501
6545
  "Set sufficient=false if the input's computed accessible name is empty or generic, including when aria-labelledby references a missing or empty element; a placeholder does not count as the label.",
@@ -6635,8 +6679,8 @@ async function firstRecordIn(filePath) {
6635
6679
  async function ingestedRecordSample(repoRoot, entityName, uploadFilePath) {
6636
6680
  const candidates = [];
6637
6681
  if (entityName) {
6638
- const slug = entityName.toLowerCase().replace(/[^a-z0-9]+/g, "-");
6639
- candidates.push(join11(repoRoot, INGEST_DIR, "data", `${slug}.json`));
6682
+ const slug2 = entityName.toLowerCase().replace(/[^a-z0-9]+/g, "-");
6683
+ candidates.push(join11(repoRoot, INGEST_DIR, "data", `${slug2}.json`));
6640
6684
  }
6641
6685
  if (uploadFilePath?.toLowerCase().endsWith(".json")) {
6642
6686
  candidates.push(join11(repoRoot, uploadFilePath));
@@ -6655,12 +6699,25 @@ function recordSchema(sample, attributes) {
6655
6699
  }
6656
6700
  return Object.fromEntries(attributes.map((key) => [key, "unknown"]));
6657
6701
  }
6658
- function ingestFailure(attempt, executions) {
6659
- if (!attempt) {
6660
- return {
6661
- reason: executions.some((e) => !e.approved) ? "a command it needed was declined" : "no successful run was recorded"
6662
- };
6702
+ function noRunReason({
6703
+ agentFailure,
6704
+ executions,
6705
+ ingestCommand,
6706
+ scriptPath
6707
+ }) {
6708
+ if (agentFailure !== void 0) {
6709
+ return `the wizard stopped before it finished the ingestion script (${agentFailure || "no reason given"})`;
6710
+ }
6711
+ if (executions.some((e) => !e.approved)) {
6712
+ return "a command it needed was declined";
6663
6713
  }
6714
+ if (!ingestCommand) {
6715
+ return scriptPath ? `the ingestion script ${scriptPath} was written but not run` : "no ingestion script was written";
6716
+ }
6717
+ return "no successful run was recorded";
6718
+ }
6719
+ function ingestFailure(attempt, missingRunReason = "no successful run was recorded") {
6720
+ if (!attempt) return { reason: missingRunReason };
6664
6721
  if (!attempt.approved) return { reason: "you declined to run it" };
6665
6722
  const detail = errorLine(attempt.output);
6666
6723
  if (attempt.timedOut) {
@@ -6702,6 +6759,9 @@ function briefSummary(summary) {
6702
6759
  if (!firstLine) return "no summary given";
6703
6760
  return firstLine.length > ERROR_SUMMARY_MAX_CHARS ? `${firstLine.slice(0, ERROR_SUMMARY_MAX_CHARS - 1)}\u2026` : firstLine;
6704
6761
  }
6762
+ function ingestionRetryInstruction(reason) {
6763
+ return `The previous ingestion attempt ended with no successful run: ${reason}. Look in "${INGEST_DIR}/" for a script from that attempt and finish or fix it instead of starting over. Then call reviewScript and run the script as described above.`;
6764
+ }
6705
6765
  function validationRetryInstructions(validation) {
6706
6766
  return [
6707
6767
  `Implementation insufficient. Address these findings before reporting completion: ${validation.additionalInstructions ?? validation.summary}`
@@ -6988,16 +7048,70 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
6988
7048
  }
6989
7049
  if (useCases.includes("ingestion")) {
6990
7050
  let ingestFailureDetail;
6991
- const result = await runImplementationUseCase("ingestion");
6992
- summaries.push(formatSummary("ingestion", result.summary));
6993
- ingestCommand = result.ingestCommand;
6994
7051
  const ingestionContext = ingestionTools;
6995
7052
  const executions = ingestionContext.shell.executions;
7053
+ let agentFailure;
7054
+ const runIngestionAgent = async (extraInstructions = []) => {
7055
+ const isRetry = extraInstructions.length > 0;
7056
+ if (isRetry) {
7057
+ resetToolCounts(ingestionContext);
7058
+ ingestionContext.cwd = ingestionContext.root;
7059
+ }
7060
+ try {
7061
+ const result = await runImplementationUseCase(
7062
+ "ingestion",
7063
+ extraInstructions,
7064
+ isRetry
7065
+ );
7066
+ summaries.push(formatSummary("ingestion", result.summary));
7067
+ ingestCommand = result.ingestCommand ?? ingestCommand;
7068
+ agentFailure = void 0;
7069
+ } catch (err) {
7070
+ if (!(err instanceof AgentReportedFailureError)) throw err;
7071
+ agentFailure = err.reason ?? "";
7072
+ summaries.push(
7073
+ formatSummary(
7074
+ "ingestion",
7075
+ `Stopped before the script was finished: ${err.reason ?? "no reason given"}`
7076
+ )
7077
+ );
7078
+ logger.warn(
7079
+ { reason: err.reason, counts: ingestionContext.counts },
7080
+ "implement: ingestion agent reported fail"
7081
+ );
7082
+ }
7083
+ };
7084
+ const currentNoRunReason = async () => noRunReason({
7085
+ agentFailure,
7086
+ executions,
7087
+ ingestCommand,
7088
+ scriptPath: await findIngestionScript(
7089
+ repoRoot,
7090
+ INGEST_DIR,
7091
+ ingestCommand
7092
+ )
7093
+ });
7094
+ await runIngestionAgent();
6996
7095
  let outcome = ingestOutcome(executions, repoRoot, ingestCommand);
6997
7096
  let failureAcknowledged = false;
6998
- while (!outcome.run && outcome.attempt?.approved) {
7097
+ while (!outcome.run && !outcome.attempt) {
7098
+ const reason = await currentNoRunReason();
7099
+ const choice = await ctx.requestUserInput({
7100
+ prompt: INGEST_AGENT_RETRY_PROMPT,
7101
+ promptType: "multipleChoice",
7102
+ options: [INGEST_RETRY.retry, INGEST_RETRY.skip],
7103
+ messages: [`\u26A0\uFE0F Records were not indexed \u2014 ${reason}.`]
7104
+ });
7105
+ if (choice !== INGEST_RETRY.retry) {
7106
+ failureAcknowledged = true;
7107
+ break;
7108
+ }
7109
+ await runIngestionAgent([ingestionRetryInstruction(reason)]);
7110
+ outcome = ingestOutcome(executions, repoRoot, ingestCommand);
7111
+ }
7112
+ while (!failureAcknowledged && !outcome.run && outcome.attempt?.approved) {
6999
7113
  const attempt = outcome.attempt;
7000
- const { reason, detail } = ingestFailure(attempt, executions);
7114
+ const { reason, detail } = ingestFailure(attempt);
7001
7115
  const choice = await ctx.requestUserInput({
7002
7116
  prompt: INGEST_RETRY_PROMPT,
7003
7117
  promptType: "multipleChoice",
@@ -7073,7 +7187,10 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
7073
7187
  );
7074
7188
  }
7075
7189
  } else {
7076
- const { reason, detail } = ingestFailure(ingestAttempt, executions);
7190
+ const { reason, detail } = ingestFailure(
7191
+ ingestAttempt,
7192
+ await currentNoRunReason()
7193
+ );
7077
7194
  ingestOutcomeMessage = `\u26A0\uFE0F Records were not indexed \u2014 ${reason}.${ingestCommand ? " Run the command below when you are ready." : ""}`;
7078
7195
  ingestFailureDetail = detail;
7079
7196
  summaries.push(
@@ -7360,14 +7477,25 @@ var defaultWorkflow = {
7360
7477
  "ingestion"
7361
7478
  );
7362
7479
  return reviewStep(ctx, {
7363
- // ingestCommand was already shown verbatim as a notice; an
7364
- // LLM-paraphrased restatement in nextSteps risks being wrong.
7365
- nextStepsGuidance: ingestion2?.ingestScriptRan ? "The wizard already ran the ingestion script and records are in the index. Do NOT tell the user to run it again; instead point them at the target index to confirm the records. Do not restate the ingestion command \u2014 the wizard already showed it to them." : "Tell the user to run the ingestion script; do not restate the exact command \u2014 the wizard already showed it to them above."
7480
+ nextStepsGuidance: ingestionGuidance(ingestion2)
7366
7481
  });
7367
7482
  }
7368
7483
  })
7369
7484
  ]
7370
7485
  };
7486
+ function ingestionGuidance(ingestion2) {
7487
+ if (ingestion2?.ingestScriptRan) {
7488
+ return "The wizard already ran the ingestion script and records are in the index. Do NOT tell the user to run it again; instead point them at the target index to confirm the records. Do not restate the ingestion command \u2014 the wizard already showed it to them.";
7489
+ }
7490
+ const partialCount = ingestion2?.ingestRecordCount ?? 0;
7491
+ if (partialCount > 0) {
7492
+ return `The ingestion script indexed ${partialCount} record(s) and then failed before it finished. Say that plainly. Do not say that ingestion succeeded or that no records were indexed. Tell the user to fix the error shown above and run the ingestion script again. Do not restate the exact command, because the wizard already showed it to them above.`;
7493
+ }
7494
+ if (ingestion2?.ingestCommand) {
7495
+ return "No records were indexed in this run. Say that plainly, and do not say that the wizard created or populated an index. Tell the user to run the ingestion script; do not restate the exact command \u2014 the wizard already showed it to them above.";
7496
+ }
7497
+ return "No ingestion script ran and no records were indexed in this run. Say that plainly. Do not say that the wizard created, selected, or populated an index, and do not tell the user to run an ingestion script, because the wizard has no tested command for one. Tell the user to run the wizard again to create and run the ingestion script.";
7498
+ }
7371
7499
 
7372
7500
  // src/actions/playbookSteps/selectPlaybook.ts
7373
7501
  import { z as z33 } from "zod";
@@ -7500,9 +7628,9 @@ async function getPlaybooks() {
7500
7628
  );
7501
7629
  return data;
7502
7630
  }
7503
- async function createPlaybookExecution(appId, slug, indexName, options) {
7631
+ async function createPlaybookExecution(appId, slug2, indexName, options) {
7504
7632
  const { data } = await dashboardRequest(
7505
- `/api/1/playbook-integrations/applications/${appId}/playbooks/${slug}/executions`,
7633
+ `/api/1/playbook-integrations/applications/${appId}/playbooks/${slug2}/executions`,
7506
7634
  playbookExecutionCreateResponseSchema,
7507
7635
  {
7508
7636
  method: "POST",
@@ -7543,9 +7671,9 @@ async function assessPlaybookExecution(uuid) {
7543
7671
  var selectPlaybookSchema = z33.object({
7544
7672
  slug: z33.string()
7545
7673
  });
7546
- function persistSlug(ctx, slug) {
7547
- ctx.setUserInput("playbookSlug", slug);
7548
- return { slug };
7674
+ function persistSlug(ctx, slug2) {
7675
+ ctx.setUserInput("playbookSlug", slug2);
7676
+ return { slug: slug2 };
7549
7677
  }
7550
7678
  async function selectPlaybookStep(ctx, requestedSlug) {
7551
7679
  const playbooks = await getPlaybooks();
@@ -7676,13 +7804,13 @@ async function confirmIndexStep(ctx) {
7676
7804
  if (!sampleIndex) {
7677
7805
  return null;
7678
7806
  }
7679
- const { slug } = ctx.getStepOutput("select-playbook");
7807
+ const { slug: slug2 } = ctx.getStepOutput("select-playbook");
7680
7808
  const selection = await ctx.requestUserInput({
7681
7809
  prompt: "Ready to create sample index?",
7682
7810
  promptType: "multipleChoice",
7683
7811
  options: [CONTINUE_OPTION, GO_BACK_OPTION2],
7684
7812
  messages: [
7685
- `A new index called "algolia_playbooks_${slug}_sample_index" will be created with sample ${slug} data.`
7813
+ `A new index called "algolia_playbooks_${slug2}_sample_index" will be created with sample ${slug2} data.`
7686
7814
  ],
7687
7815
  helpText: "Going back lets you choose a different index."
7688
7816
  });
@@ -7834,13 +7962,13 @@ var POLL_NOTICES = [
7834
7962
  { message: "detecting required fields\u2026" }
7835
7963
  ];
7836
7964
  async function dataAssessmentStep(ctx) {
7837
- const { slug } = ctx.getStepOutput("select-playbook");
7965
+ const { slug: slug2 } = ctx.getStepOutput("select-playbook");
7838
7966
  const { indexName, existingExecution } = indexForAssessment(ctx);
7839
7967
  if (existingExecution) return existingExecution;
7840
7968
  try {
7841
7969
  showNotice(ctx, CREATING_EXECUTION_NOTICE);
7842
7970
  const { id: appId } = await requireApplication();
7843
- const execution = await resolveExecution(ctx, appId, slug, indexName);
7971
+ const execution = await resolveExecution(ctx, appId, slug2, indexName);
7844
7972
  const assessed = await runAssessment(ctx, execution, indexName);
7845
7973
  ctx.clearNotices();
7846
7974
  await promptReady(ctx, assessed);
@@ -7945,24 +8073,24 @@ function unexpectedAssessmentStatus(status) {
7945
8073
  `data-assessment received an unexpected assessment status: ${status}`
7946
8074
  );
7947
8075
  }
7948
- async function resolveExecution(ctx, appId, slug, indexName) {
7949
- const created = await createWithRecovery(ctx, appId, slug, indexName);
8076
+ async function resolveExecution(ctx, appId, slug2, indexName) {
8077
+ const created = await createWithRecovery(ctx, appId, slug2, indexName);
7950
8078
  if (!created.resumed) return created;
7951
8079
  const choice = await promptResumeExecution(ctx, indexName);
7952
8080
  if (choice === CONTINUE_EXECUTION_OPTION) return created;
7953
8081
  if (choice === RESTART_OPTION) {
7954
- return createWithRecovery(ctx, appId, slug, indexName, { forceNew: true });
8082
+ return createWithRecovery(ctx, appId, slug2, indexName, { forceNew: true });
7955
8083
  }
7956
8084
  if (choice === GO_BACK_OPTION3) {
7957
8085
  return ctx.returnToStep("select-index");
7958
8086
  }
7959
8087
  throw new Error("data-assessment received an unexpected execution choice");
7960
8088
  }
7961
- async function createWithRecovery(ctx, appId, slug, indexName, options) {
8089
+ async function createWithRecovery(ctx, appId, slug2, indexName, options) {
7962
8090
  return retryOnDashboardError(
7963
8091
  () => {
7964
8092
  showNotice(ctx, CREATING_EXECUTION_NOTICE);
7965
- return createPlaybookExecution(appId, slug, indexName, options);
8093
+ return createPlaybookExecution(appId, slug2, indexName, options);
7966
8094
  },
7967
8095
  onRequestFailure(ctx, indexName)
7968
8096
  );
@@ -9125,6 +9253,11 @@ function App() {
9125
9253
  useRepaintOnResize();
9126
9254
  const compactRun = isCompactTerminal(columns, rows);
9127
9255
  const [showLogs, setShowLogs] = useState12(false);
9256
+ const [seenInputReq, setSeenInputReq] = useState12(inputReq);
9257
+ if (inputReq !== seenInputReq) {
9258
+ setSeenInputReq(inputReq);
9259
+ if (inputReq) setShowLogs(false);
9260
+ }
9128
9261
  const [tipState, setTipState] = useState12("idle");
9129
9262
  const [tipsComplete, setTipsComplete] = useState12(false);
9130
9263
  const finished = phase === "done" || phase === "error";
@@ -9492,7 +9625,7 @@ async function resetProjectState() {
9492
9625
  // package.json with { type: 'json' }
9493
9626
  var package_default2 = {
9494
9627
  name: "@algolia/wizard",
9495
- version: "0.85.0",
9628
+ version: "0.87.0-rc.211.681",
9496
9629
  description: "Magically implement Algolia functionality in your codebase",
9497
9630
  type: "module",
9498
9631
  engines: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@algolia/wizard",
3
- "version": "0.85.0",
3
+ "version": "0.87.0-rc.211.681",
4
4
  "description": "Magically implement Algolia functionality in your codebase",
5
5
  "type": "module",
6
6
  "engines": {