@algolia/wizard 0.84.0-rc.202.644 → 0.84.0-rc.202.646

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
@@ -2315,7 +2315,7 @@ async function ensureApplication(resumeAppId) {
2315
2315
  // package.json
2316
2316
  var package_default = {
2317
2317
  name: "@algolia/wizard",
2318
- version: "0.84.0-rc.202.644",
2318
+ version: "0.84.0-rc.202.646",
2319
2319
  description: "Magically implement Algolia functionality in your codebase",
2320
2320
  type: "module",
2321
2321
  engines: {
@@ -3739,7 +3739,7 @@ import { execFile as execFile2 } from "node:child_process";
3739
3739
  import { tool as tool8 } from "ai";
3740
3740
  import z15 from "zod";
3741
3741
  import { lstat as lstat3, readdir as readdir2, readFile as readFile8, realpath, stat as stat2 } from "node:fs/promises";
3742
- import { isAbsolute as isAbsolute2, join as join8, relative as relative4, sep as sep2 } from "node:path";
3742
+ import { basename as basename2, isAbsolute as isAbsolute2, join as join8, relative as relative4, sep as sep2 } from "node:path";
3743
3743
  import { performance } from "node:perf_hooks";
3744
3744
  import { Worker } from "node:worker_threads";
3745
3745
  var MAX_GIT_OUTPUT_BYTES = 32e6;
@@ -4082,6 +4082,9 @@ async function safeSearchPath(rootPath, file) {
4082
4082
  }
4083
4083
  async function searchableFiles(root, target) {
4084
4084
  try {
4085
+ if (SKIP_FILES.has(basename2(target)) && (await lstat3(target)).isFile()) {
4086
+ return [target];
4087
+ }
4085
4088
  const gitRoot = await gitRootForTarget(root, target);
4086
4089
  const result = await gitVisibleFiles(gitRoot, target);
4087
4090
  return result.available ? expandNestedRepositories(result.files) : walkFiles(root, target);
@@ -4113,7 +4116,7 @@ function searchFilesTool(ctx) {
4113
4116
  description: "Search file contents for a JavaScript regular expression (RegExp syntax, not grep/PCRE). Returns matching lines as file:line:text.",
4114
4117
  inputSchema: z15.object({
4115
4118
  query: z15.string().describe("JavaScript RegExp pattern to search for"),
4116
- path: z15.string().optional().describe("Directory to search in (default: cwd)")
4119
+ path: z15.string().optional().describe("Directory or file to search in (default: cwd)")
4117
4120
  }),
4118
4121
  execute: async ({ query, path = "." }) => {
4119
4122
  logger.info({ query, path }, "called searchFiles tool");
@@ -4258,7 +4261,7 @@ import { tool as tool9 } from "ai";
4258
4261
  import z16 from "zod";
4259
4262
  import { createHash as createHash2 } from "node:crypto";
4260
4263
  import { readFile as readFile9, stat as stat3 } from "node:fs/promises";
4261
- import { basename as basename2, relative as relative5, resolve as resolve3 } from "node:path";
4264
+ import { basename as basename3, relative as relative5, resolve as resolve3 } from "node:path";
4262
4265
 
4263
4266
  // src/lib/tools/utils/prompt.ts
4264
4267
  var chain = Promise.resolve();
@@ -4356,7 +4359,7 @@ function commandDiffersFromReviewedScript(ctx, command, cwd) {
4356
4359
  if (approval.command === command && approval.cwd === cwd) return false;
4357
4360
  if (approval.command === command) return true;
4358
4361
  const scriptReferences = [
4359
- basename2(approval.filePath),
4362
+ basename3(approval.filePath),
4360
4363
  relative5(ctx.root, approval.filePath),
4361
4364
  ".algolia-wizard"
4362
4365
  ];
@@ -6009,7 +6012,7 @@ import {
6009
6012
  // src/lib/git.ts
6010
6013
  import { execFile as execFile3 } from "node:child_process";
6011
6014
  import { copyFile, mkdir as mkdir4, stat as stat4 } from "node:fs/promises";
6012
- import { basename as basename3, dirname as dirname3, isAbsolute as isAbsolute3, join as join9, resolve as resolve4 } from "node:path";
6015
+ import { basename as basename4, dirname as dirname3, isAbsolute as isAbsolute3, join as join9, resolve as resolve4 } from "node:path";
6013
6016
  var MAX_BUFFER = 32 * 1024 * 1024;
6014
6017
  function git(args) {
6015
6018
  return new Promise((resolve6, reject) => {
@@ -6046,7 +6049,7 @@ async function copyUploadIntoProject(repoRoot, ingestDir, sourcePath) {
6046
6049
  } catch {
6047
6050
  return { ok: false, reason: `"${sourcePath}" does not exist` };
6048
6051
  }
6049
- const relPath = join9(ingestDir, basename3(source));
6052
+ const relPath = join9(ingestDir, basename4(source));
6050
6053
  const dest = join9(repoRoot, relPath);
6051
6054
  if (resolve4(source) === resolve4(dest)) {
6052
6055
  return { ok: true, relPath };
@@ -6193,6 +6196,11 @@ var INGESTION_SOURCE = {
6193
6196
  var BUILD_CHECK_PROMPT = "Run type-check now? This can take several minutes on a large repository.";
6194
6197
  var MISSING_DEPENDENCIES_PROMPT = "Dependencies are not installed. Install them and run type-check now?";
6195
6198
  var UNRELATED_FAILURE_PROMPT = "These checks are still failing and appear unrelated to the changes. Continue the investigation?";
6199
+ var INGEST_RETRY_PROMPT = "Fix the cause (for example, start the database the script reads from), then retry.";
6200
+ var INGEST_RETRY = {
6201
+ retry: "Retry ingestion",
6202
+ skip: "Continue without records"
6203
+ };
6196
6204
  var FILE_UPLOAD_PATH_PROMPT = "Enter the path to your JSON, CSV, or TSV file (relative to the project root, or absolute):";
6197
6205
  var JS_LANGUAGES = ["javascript", "typescript", "jsx", "tsx", "node"];
6198
6206
  function lower(entries) {
@@ -6295,6 +6303,7 @@ function ingestionInstructions(input) {
6295
6303
  ] : [],
6296
6304
  `When the script is finished, decide the exact command and cwd you will use to run it. Make sure that the script path in the command resolves from that cwd to the filePath. Then call reviewScript with that filePath, command, cwd, the exact selected entityName, and usesGeneratedRecords set to ${input.ingestionSource === "generated"}. Wait for the developer to review it because running it writes records to a live index. Do not run it before that call returns. If you change the command or cwd after a failed run, call reviewScript again before runShell.`,
6297
6305
  'Then, without changing directories, run the script yourself via runShell using the exact same command and cwd. Report the command you ran as "ingestCommand" so the developer can re-run it. Its explanation must say that running it writes records to Algolia.',
6306
+ "When the script reads from a database or another service, find how the project connects to it locally (env files, README, socket or TCP settings). If that connection needs a local service, such as a database proxy (for example cloud-sql-proxy) or a tunnel, make the script check the connection before it reads, and exit with an error that names the service and the command from the project docs that starts it.",
6298
6307
  "If external data access fails, do not run authentication commands, change accounts or machine configuration, or start proxies, containers, or services.",
6299
6308
  "Do not troubleshoot unavailable external infrastructure. Preserve the script and command, report success, and let the developer prepare the data source.",
6300
6309
  "If the script cannot access an external data source because credentials or a service are unavailable, do not report status=fail. Report status=success with the completed script, ingestCommand, and a concise failure summary. The wizard handles this run failure and tells the developer how to retry.",
@@ -6427,6 +6436,7 @@ function validationInstructions(input) {
6427
6436
  "Inspect the CSS that actually applies to the generated DOM. Confirm the results panel and the element drawing the input frame have non-transparent surfaces, the input frame is distinguishable from its surrounding background in every supported theme, text meets 4.5:1 contrast, meaningful boundaries and focus indicators meet 3:1, and no state relies on color alone.",
6428
6437
  "Confirm the search experience reflows without horizontal page scrolling at 320 CSS pixels, keeps its panel on-screen, preserves visible focus, and respects reduced-motion preferences for any added animation.",
6429
6438
  "If the project imports an InstantSearch theme and overrides its palette, confirm that the palette overrides (--ais-*-rgb) win over the theme: they must use a higher specificity than the theme, for example `:root:root`, and for a dark mode `:root:root.dark` or `:root:root[data-theme=dark]`, because the theme sets its own blue in `:root` and in `:root[data-theme=dark], .dark`. `:root:root` alone ties with `:root[data-theme=dark]`. If an override can lose to the theme, set sufficient=false, because the search icon and focus ring then show the theme blue instead of the app accent color.",
6439
+ "Find how the app selects each theme it supports (a class or attribute on an ancestor, a prefers-color-scheme media query, or a theme that exists only in code). Confirm that the search input and results panel take their colors from the active app theme through that same switch. A search widget whose colors do not match the page theme, for example a light panel on a dark page, is a failure: set sufficient=false.",
6430
6440
  "When the project has a package.json, confirm that the theme CSS of the search widget (for InstantSearch, a theme from instantsearch.css) is imported once, from the component or the global stylesheet. If it is installed but not imported, set sufficient=false.",
6431
6441
  "Confirm that the search component is reachable from and fits the corresponding content task at every supported breakpoint; require app-wide reachability only when the search scope is genuinely app-wide.",
6432
6442
  "Do not run the test suite or any test command.",
@@ -6596,6 +6606,22 @@ function ingestFailure(attempt, executions) {
6596
6606
  }
6597
6607
  return { reason: `it failed (exit ${attempt.exitCode ?? "unknown"})`, detail };
6598
6608
  }
6609
+ function errorLine(output) {
6610
+ const lines = output?.split("\n").map((l) => l.trim()).filter(Boolean) ?? [];
6611
+ return [...lines].reverse().find((l) => /\w*(Error|Exception)\b/.test(l)) ?? lines.at(-1);
6612
+ }
6613
+ function ingestFailureMessages(headline, detail, command, cwd) {
6614
+ return [
6615
+ headline,
6616
+ // One message per line: Notices.tsx counts a message as one wrapped
6617
+ // line, so an embedded newline overflows the panel's height accounting.
6618
+ ...detail?.split("\n").filter((l) => l.trim()) ?? [],
6619
+ ...command ? [
6620
+ `Ingestion command: ${command}`,
6621
+ ...cwd !== "." ? [`Working directory: ${cwd}`] : []
6622
+ ] : []
6623
+ ];
6624
+ }
6599
6625
  function makeToolContext(root, appId, env = async () => ({}), secretEnv = async () => ({})) {
6600
6626
  return {
6601
6627
  ...createToolContext(
@@ -6903,11 +6929,45 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
6903
6929
  ingestCommand = result.ingestCommand;
6904
6930
  const ingestionContext = ingestionTools;
6905
6931
  const executions = ingestionContext.shell.executions;
6932
+ let outcome = ingestOutcome(executions, repoRoot, ingestCommand);
6933
+ let failureAcknowledged = false;
6934
+ while (!outcome.run && outcome.attempt?.approved) {
6935
+ const attempt = outcome.attempt;
6936
+ const { reason, detail } = ingestFailure(attempt, executions);
6937
+ const choice = await ctx.requestUserInput({
6938
+ prompt: INGEST_RETRY_PROMPT,
6939
+ promptType: "multipleChoice",
6940
+ options: [INGEST_RETRY.retry, INGEST_RETRY.skip],
6941
+ messages: ingestFailureMessages(
6942
+ `\u26A0\uFE0F Records were not indexed \u2014 ${reason}.`,
6943
+ errorLine(detail),
6944
+ attempt.command,
6945
+ relativeIngestCwd(attempt, repoRoot)
6946
+ )
6947
+ });
6948
+ if (choice !== INGEST_RETRY.retry) {
6949
+ failureAcknowledged = true;
6950
+ break;
6951
+ }
6952
+ const rerun = await runReviewedCommand(
6953
+ ingestionContext,
6954
+ attempt.command,
6955
+ attempt.cwd
6956
+ );
6957
+ if (rerun === void 0 || typeof rerun === "string") {
6958
+ logger.warn(
6959
+ { command: attempt.command, refusal: rerun },
6960
+ "implement: could not retry the ingestion command"
6961
+ );
6962
+ break;
6963
+ }
6964
+ outcome = ingestOutcome(executions, repoRoot, ingestCommand);
6965
+ }
6906
6966
  const {
6907
6967
  run: ingestRun,
6908
6968
  attempt: ingestAttempt,
6909
6969
  recordCount
6910
- } = ingestOutcome(executions, repoRoot, ingestCommand);
6970
+ } = outcome;
6911
6971
  let ingestCwd = ".";
6912
6972
  if (ingestAttempt) {
6913
6973
  ingestCommand = ingestAttempt.command;
@@ -6987,22 +7047,17 @@ ${detail}` : ""}`
6987
7047
  "When you're ready to continue, we'll move on to search implementation."
6988
7048
  ]
6989
7049
  });
6990
- } else {
6991
- const commandMessages = ingestCommand ? [
6992
- `Ingestion command: ${ingestCommand}`,
6993
- ...ingestCwd !== "." ? [`Working directory: ${ingestCwd}`] : []
6994
- ] : [];
7050
+ } else if (!failureAcknowledged) {
6995
7051
  await ctx.requestUserInput({
6996
7052
  prompt: "",
6997
7053
  promptType: "enterToContinue",
6998
7054
  options: [],
6999
- // One message per line: Notices.tsx counts a message as one wrapped
7000
- // line, so an embedded newline overflows the panel's height accounting.
7001
- messages: [
7055
+ messages: ingestFailureMessages(
7002
7056
  ingestOutcomeMessage,
7003
- ...ingestFailureDetail?.split("\n").filter((l) => l.trim()) ?? [],
7004
- ...commandMessages
7005
- ]
7057
+ ingestFailureDetail,
7058
+ ingestCommand,
7059
+ ingestCwd
7060
+ )
7006
7061
  });
7007
7062
  }
7008
7063
  }
@@ -9270,7 +9325,7 @@ async function resetProjectState() {
9270
9325
  // package.json with { type: 'json' }
9271
9326
  var package_default2 = {
9272
9327
  name: "@algolia/wizard",
9273
- version: "0.84.0-rc.202.644",
9328
+ version: "0.84.0-rc.202.646",
9274
9329
  description: "Magically implement Algolia functionality in your codebase",
9275
9330
  type: "module",
9276
9331
  engines: {
@@ -160,6 +160,25 @@ above). When the app has a dark mode, also override the dark values with `:root:
160
160
  a dark class or `:root:root[data-theme=dark]` for a `data-theme` attribute: `:root:root` alone
161
161
  ties with `:root[data-theme=dark]`. The override then wins whatever the import order. Then check that the search icon has the app's accent color, not the theme blue.
162
162
 
163
+ **Follow the app's theme switch.** The theme switches palette only through `:root[data-theme=dark], .dark`,
164
+ so find how the app selects each theme and set the `--ais-*-rgb` values through the same switch:
165
+
166
+ - A class or attribute on an ancestor: override under that selector, as above.
167
+ - A `prefers-color-scheme` media query: override inside the same query, for example
168
+ `@media (prefers-color-scheme: dark) { :root:root { … } }`.
169
+ - A theme that exists only in code (a theme object, a theme store), with no class or attribute
170
+ on the page: set the values from that theme as inline custom properties on the widget
171
+ container, and set them again when the theme changes.
172
+
173
+ The panel renders inside the widget, so values on the container apply to the whole widget. Make
174
+ triplets from opaque theme colors, because a triplet has no alpha:
175
+
176
+ ```js
177
+ container.style.setProperty('--ais-background-color-rgb', toRgbTriplet(theme.surface))
178
+ ```
179
+
180
+ Then check the input and the panel in each theme that the app supports.
181
+
163
182
  Everything else is plain CSS against the widget's classes, or `cssClasses` /
164
183
  `indices[].cssClasses` to add your own names to the same elements. Who owns what:
165
184
 
@@ -152,6 +152,25 @@ above). When the app has a dark mode, also override the dark values with `:root:
152
152
  a dark class or `:root:root[data-theme=dark]` for a `data-theme` attribute: `:root:root` alone
153
153
  ties with `:root[data-theme=dark]`. The override then wins whatever the import order. Then check that the search icon has the app's accent color, not the theme blue.
154
154
 
155
+ **Follow the app's theme switch.** The theme switches palette only through `:root[data-theme=dark], .dark`,
156
+ so find how the app selects each theme and set the `--ais-*-rgb` values through the same switch:
157
+
158
+ - A class or attribute on an ancestor: override under that selector, as above.
159
+ - A `prefers-color-scheme` media query: override inside the same query, for example
160
+ `@media (prefers-color-scheme: dark) { :root:root { … } }`.
161
+ - A theme that exists only in code (a theme object, a CSS-in-JS provider, a theme store), with
162
+ no class or attribute on the page: set the values from that theme as inline custom properties
163
+ on the element that wraps the widget, so they follow theme changes.
164
+
165
+ The panel renders inside the widget, so values on a wrapper apply to the whole widget. Make
166
+ triplets from opaque theme colors, because a triplet has no alpha:
167
+
168
+ ```tsx
169
+ <search style={{ '--ais-background-color-rgb': toRgbTriplet(theme.surface) } as React.CSSProperties}>
170
+ ```
171
+
172
+ Then check the input and the panel in each theme that the app supports.
173
+
155
174
  Everything else is plain CSS against the widget's classes, or the `classNames` /
156
175
  `indices[].classNames` props to add your own names to the same elements. Who owns what:
157
176
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@algolia/wizard",
3
- "version": "0.84.0-rc.202.644",
3
+ "version": "0.84.0-rc.202.646",
4
4
  "description": "Magically implement Algolia functionality in your codebase",
5
5
  "type": "module",
6
6
  "engines": {