@algolia/wizard 0.34.0-rc.125.243 → 0.34.0-rc.125.245

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
@@ -2645,7 +2645,7 @@ async function ensureApplication() {
2645
2645
  }
2646
2646
 
2647
2647
  // src/workflows/default.ts
2648
- import { z as z30 } from "zod";
2648
+ import { z as z29 } from "zod";
2649
2649
 
2650
2650
  // src/actions/listIndices.ts
2651
2651
  import { z as z5 } from "zod";
@@ -3085,8 +3085,7 @@ function resolveWriteKey(index, appId) {
3085
3085
  `Algolia Wizard write key for ${index} index`
3086
3086
  );
3087
3087
  }
3088
- async function resolveSearchOnlyKey(index, appId, envKey) {
3089
- if (envKey) return { key: envKey, source: "env" };
3088
+ async function resolveSearchOnlyKey(index, appId) {
3090
3089
  return resolveKey(
3091
3090
  "search",
3092
3091
  index,
@@ -3125,6 +3124,12 @@ function isIgnoredByRule(root, relPath) {
3125
3124
  function isTracked(root, relPath) {
3126
3125
  return gitSucceeds(root, ["ls-files", "--error-unmatch", "--", relPath]);
3127
3126
  }
3127
+ async function gitIgnoreStatus(root, target) {
3128
+ const { ignoredByRule, tracked } = await inspect(root, target);
3129
+ if (ignoredByRule === void 0) return "unknown";
3130
+ if (tracked) return "tracked";
3131
+ return ignoredByRule ? "covered" : "needsRule";
3132
+ }
3128
3133
  async function inspect(root, target) {
3129
3134
  const relPath = relative2(root, target);
3130
3135
  if (!relPath || relPath.startsWith("..")) {
@@ -3170,31 +3175,9 @@ async function ensureGitIgnored(root, target) {
3170
3175
  }
3171
3176
 
3172
3177
  // src/lib/tools/writeAlgoliaCredentials.ts
3173
- var APP_ID_VAR = "ALGOLIA_APPLICATION_ID";
3174
- var API_KEY_VAR = "ALGOLIA_WRITE_API_KEY";
3178
+ var APP_ID_VAR = "ALGOLIA_APP_ID";
3179
+ var API_KEY_VAR = "ALGOLIA_WRITE_KEY";
3175
3180
  var INDEX_NAME_VAR = "ALGOLIA_INDEX_NAME";
3176
- var PUBLIC_APP_ID_SUFFIX = "ALGOLIA_APP_ID";
3177
- var PUBLIC_SEARCH_KEY_SUFFIX = "ALGOLIA_SEARCH_KEY";
3178
- var PUBLIC_INDEX_NAME_SUFFIX = "ALGOLIA_INDEX_NAME";
3179
- function publicAppIdVar(prefix) {
3180
- return `${prefix}${PUBLIC_APP_ID_SUFFIX}`;
3181
- }
3182
- function publicSearchKeyVar(prefix) {
3183
- return `${prefix}${PUBLIC_SEARCH_KEY_SUFFIX}`;
3184
- }
3185
- function publicIndexNameVar(prefix) {
3186
- return `${prefix}${PUBLIC_INDEX_NAME_SUFFIX}`;
3187
- }
3188
- function publicSearchEnvVars(prefix, index, appId, searchKey) {
3189
- return [
3190
- { name: publicAppIdVar(prefix), value: appId ?? "<your-algolia-app-id>" },
3191
- {
3192
- name: publicSearchKeyVar(prefix),
3193
- value: searchKey ?? "<your-algolia-search-only-api-key>"
3194
- },
3195
- { name: publicIndexNameVar(prefix), value: index }
3196
- ];
3197
- }
3198
3181
  function appendEnv(content, entries) {
3199
3182
  const prefix = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
3200
3183
  const lines = entries.map(([name, value]) => `${name}=${value}
@@ -4078,7 +4061,7 @@ async function runAnalysis(mode, extraInstructions = []) {
4078
4061
  // package.json
4079
4062
  var package_default = {
4080
4063
  name: "@algolia/wizard",
4081
- version: "0.34.0-rc.125.243",
4064
+ version: "0.34.0-rc.125.245",
4082
4065
  description: "Magically implement Algolia functionality in your codebase",
4083
4066
  type: "module",
4084
4067
  engines: {
@@ -4483,12 +4466,12 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
4483
4466
  };
4484
4467
 
4485
4468
  // src/actions/implement.ts
4486
- import z29 from "zod";
4487
- import { relative as relative6 } from "node:path";
4469
+ import z28 from "zod";
4470
+ import { relative as relative6, join as join12 } from "node:path";
4488
4471
 
4489
4472
  // src/lib/git.ts
4490
4473
  import { execFile as execFile2 } from "node:child_process";
4491
- import { copyFile, mkdir as mkdir6, readFile as readFile8, stat as stat3, writeFile as writeFile7 } from "node:fs/promises";
4474
+ import { copyFile, mkdir as mkdir6, stat as stat3 } from "node:fs/promises";
4492
4475
  import { basename as basename2, dirname as dirname7, isAbsolute as isAbsolute2, join as join10, resolve as resolve3 } from "node:path";
4493
4476
  var MAX_BUFFER = 32 * 1024 * 1024;
4494
4477
  function git(args) {
@@ -4542,44 +4525,6 @@ async function copyUploadIntoProject(repoRoot, ingestDir, sourcePath) {
4542
4525
  }
4543
4526
  return { ok: true, relPath };
4544
4527
  }
4545
- var ENV_FILE_PRECEDENCE = [".env", ".env.local"];
4546
- async function readEnvFileIfExists(path) {
4547
- try {
4548
- return await readFile8(path, "utf8");
4549
- } catch (err) {
4550
- if (err.code !== "ENOENT") throw err;
4551
- return void 0;
4552
- }
4553
- }
4554
- async function definingEnvFile(repoRoot, name) {
4555
- let found;
4556
- for (const file of ENV_FILE_PRECEDENCE) {
4557
- const path = join10(repoRoot, file);
4558
- const content = await readEnvFileIfExists(path);
4559
- if (content !== void 0 && hasEnv(content, name)) found = { path, content };
4560
- }
4561
- return found;
4562
- }
4563
- function usableEnvValue(content, name) {
4564
- const value = readEnv(content, name);
4565
- return value && !value.startsWith("<") ? value : void 0;
4566
- }
4567
- async function readEnvVar(repoRoot, name) {
4568
- const found = await definingEnvFile(repoRoot, name);
4569
- return found ? usableEnvValue(found.content, name) : void 0;
4570
- }
4571
- async function writeSearchEnvValues(repoRoot, vars, defaultFileName = ".env") {
4572
- const written = [];
4573
- for (const { name, value } of vars) {
4574
- const found = await definingEnvFile(repoRoot, name);
4575
- if (found && usableEnvValue(found.content, name) !== void 0) continue;
4576
- const path = found?.path ?? join10(repoRoot, defaultFileName);
4577
- const existing = found?.content ?? await readEnvFileIfExists(path) ?? "";
4578
- await writeFile7(path, upsertEnv(existing, name, value), "utf8");
4579
- written.push({ name, file: path });
4580
- }
4581
- return written;
4582
- }
4583
4528
  function normalizeFindingPaths(findings) {
4584
4529
  return {
4585
4530
  ...findings,
@@ -4660,53 +4605,41 @@ function getFrameworkSpecificDoc(frameworks) {
4660
4605
  return loadAlgoliaDoc("js");
4661
4606
  }
4662
4607
 
4663
- // src/actions/resolveEnvVarPrefix.ts
4664
- import z28 from "zod";
4665
- var resolveEnvVarPrefixSchema = z28.object({
4666
- publicEnvVarPrefix: detectLanguageSchema.shape.publicEnvVarPrefix
4667
- });
4668
- var resolveEnvVarPrefix = (frameworkName) => runAgent({
4669
- instructions: [
4670
- `The developer corrected the project's framework to "${frameworkName}".`,
4671
- `Determine publicEnvVarPrefix for this framework: check the project's own env var usage first (e.g. names already referenced in code, .env/.env.example); if none exists, fall back to this framework's known convention for exposing env vars to client-side code; use "" when the framework has no such convention (e.g. a backend-only framework).`,
4672
- "A brand-new project has no existing env var usage to find \u2014 one or two targeted checks (e.g. .env/.env.example, or a grep for the framework's public-prefix convention) are enough to confirm that. Do not keep searching once those turn up nothing; fall back to the framework convention immediately.",
4673
- 'Use as few tools as possible, but do not guess. If you cant find the answer, say "unknown"',
4674
- "When done, call reportStatus"
4675
- ],
4676
- tools: ["listFiles", "changeDirectory", "readFile", "searchFiles"],
4677
- outputSchema: resolveEnvVarPrefixSchema,
4678
- modelSize: "small"
4679
- });
4680
-
4681
4608
  // src/actions/implement.ts
4682
- var implementSchema = z29.object({
4683
- summary: z29.string(),
4684
- ingestCommand: z29.string().optional(),
4685
- ingestScriptRan: z29.boolean().optional(),
4686
- ingestRecordCount: z29.number().optional(),
4687
- ingestDurationMs: z29.number().optional(),
4688
- ingestionSource: z29.enum(["local", "fileUpload", "generated"]),
4689
- searchEnvVars: z29.array(
4690
- z29.object({
4691
- name: z29.string(),
4692
- value: z29.string()
4693
- })
4694
- ).optional()
4609
+ var implementSchema = z28.object({
4610
+ summary: z28.string(),
4611
+ ingestCommand: z28.string().optional(),
4612
+ ingestScriptRan: z28.boolean().optional(),
4613
+ ingestRecordCount: z28.number().optional(),
4614
+ ingestDurationMs: z28.number().optional(),
4615
+ ingestionSource: z28.enum(["local", "fileUpload", "generated"]),
4616
+ searchConfig: z28.object({
4617
+ filePath: z28.string().optional(),
4618
+ vars: z28.array(
4619
+ z28.object({
4620
+ name: z28.string(),
4621
+ value: z28.string()
4622
+ })
4623
+ )
4624
+ }).optional()
4695
4625
  });
4696
- var implementationOutputSchema = z29.object({
4697
- summary: z29.string(),
4698
- ingestCommand: z29.string().optional()
4626
+ var implementationOutputSchema = z28.object({
4627
+ summary: z28.string(),
4628
+ ingestCommand: z28.string().optional(),
4629
+ // Only for the search use case: the path of whatever module the agent
4630
+ // defined the Algolia config constants in, so the wizard can check it
4631
+ // won't end up gitignored (it's public, meant to be committed).
4632
+ searchConfigFile: z28.string().optional()
4699
4633
  });
4700
- var verificationOutputSchema = z29.object({
4701
- summary: z29.string(),
4702
- sufficient: z29.boolean(),
4703
- additionalInstructions: z29.string().optional()
4634
+ var verificationOutputSchema = z28.object({
4635
+ summary: z28.string(),
4636
+ sufficient: z28.boolean(),
4637
+ additionalInstructions: z28.string().optional()
4704
4638
  });
4705
4639
  var MAX_IMPLEMENT_VERIFICATION_ATTEMPTS = 3;
4706
4640
  var DEFAULT_IMPLEMENT_USE_CASES = ["ingestion", "search"];
4707
4641
  var INGEST_DIR = ".algolia-wizard";
4708
4642
  var JS_LANGUAGES = ["javascript", "typescript", "jsx", "tsx", "node"];
4709
- var ENV_LOCAL_AWARE_PREFIXES = ["VITE_", "NEXT_PUBLIC_", "REACT_APP_"];
4710
4643
  function lower(entries) {
4711
4644
  return entries.map((entry) => entry.name.toLowerCase());
4712
4645
  }
@@ -4715,6 +4648,10 @@ function isJsProject(language) {
4715
4648
  (name) => JS_LANGUAGES.some((js) => name.includes(js))
4716
4649
  );
4717
4650
  }
4651
+ var SEARCH_CONFIG_APP_ID = "ALGOLIA_APP_ID";
4652
+ var SEARCH_CONFIG_SEARCH_KEY = "ALGOLIA_SEARCH_API_KEY";
4653
+ var SEARCH_CONFIG_INDEX_NAME = "ALGOLIA_INDEX_NAME";
4654
+ var SEARCH_KEY_PLACEHOLDER = "<your-algolia-search-only-api-key>";
4718
4655
  var UI_FRAMEWORKS = [
4719
4656
  { match: ["vue", "nuxt"], target: "Vue", doc: "vue" },
4720
4657
  { match: ["react", "next"], target: "React", doc: "react" },
@@ -4785,6 +4722,7 @@ function ingestionInstructions(input) {
4785
4722
  "After a successful ingest, the script must print exactly one line to stdout in the form `ALGOLIA_WIZARD_RECORD_COUNT=<n>`, where <n> is the total number of records pushed to Algolia. Print it last, on its own line, with no surrounding text.",
4786
4723
  ...algoliaClientDoc(input),
4787
4724
  "Install the Algolia client with the project's own package manager via runShell, declaring it in whatever manifest the project uses (e.g. package.json, requirements.txt, Gemfile, go.mod, composer.json) so the dependency is not just installed ad hoc.",
4725
+ `If the script loads its env vars from a file (e.g. via dotenv or an equivalent for its language) rather than the process environment directly, decide that up front and call writeCredentials on that file before you finish the script \u2014 do not wait to discover the need for it by having a writeFile call refused.`,
4788
4726
  "When the script is finished, call reviewScript with its path and wait: running it writes records to a live index, so the developer reads it first. Do not run it before that call returns.",
4789
4727
  'Then run the script yourself via runShell, and 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.',
4790
4728
  "The summary should be extremely concise.",
@@ -4807,18 +4745,13 @@ function searchInstructions(input) {
4807
4745
  `Import and render that new component from ${input.searchLocation ? `"${input.searchLocation}"` : "the best shared, always-rendered layout location (e.g. a header/nav component)"} so it is reachable across the app \u2014 at least a working search input and results list against the target index.`,
4808
4746
  "If a search box already exists, replace its usage with an import and render of your new component; remove the old implementation.",
4809
4747
  "When rendering results with an existing shared component (e.g. a card), import and reuse that component rather than inlining its markup \u2014 inlining silently drops the styles and behavior its own file provides.",
4810
- `Read the index name from the ${publicIndexNameVar(input.publicEnvVarPrefix)} env var, which the wizard sets to "${input.targetIndex}". Never hardcode an index name or derive one from the project, file, or component name.`,
4811
- "Read the App ID, the search-only API key, and the index name from env vars; never hardcode them. A search-only key is safe to expose client-side.",
4812
- // The key is provisioned only after verification passes, and the wizard
4813
- // reads the project's env files to decide whether a key already exists
4814
- // an agent-scaffolded value there, even a blank one, would be treated as
4815
- // real and silently override whatever the wizard writes.
4816
- `Add Algolia App ID "${input.appId}"; leave the search-only key as a placeholder in the code only. Do not create or edit any .env* file (.env, .env.local, etc.) yourself, not even to add a blank placeholder line \u2014 the wizard resolves the real key and writes it there itself.`,
4817
- // The wizard writes these exact names into the project's env files right after this step.
4818
- `Use exactly these env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
4748
+ `Define ${SEARCH_CONFIG_APP_ID}, ${SEARCH_CONFIG_SEARCH_KEY}, and ${SEARCH_CONFIG_INDEX_NAME} as exported constants in a module that fits this project's existing conventions for shared client-side config \u2014 reuse an existing one if it already holds config like this, or add a small new one otherwise. These are PUBLIC values, safe to commit and expose client-side: never read them from an environment variable or a .env* file, and never hardcode them anywhere except in that one module (import them wherever the search client needs them).`,
4749
+ `Set ${SEARCH_CONFIG_APP_ID} to "${input.appId}" and ${SEARCH_CONFIG_INDEX_NAME} to "${input.targetIndex}".`,
4750
+ input.searchKey ? `Set ${SEARCH_CONFIG_SEARCH_KEY} to "${input.searchKey}".` : `A real search-only key could not be provisioned${input.searchKeyError ? ` (${input.searchKeyError})` : ""} \u2014 set ${SEARCH_CONFIG_SEARCH_KEY} to the placeholder "${SEARCH_KEY_PLACEHOLDER}" and add a prominent TODO for the developer to fill in a real one.`,
4751
+ 'Report the repo-relative path of that module as "searchConfigFile" in your final status.',
4819
4752
  "Install any Algolia packages you import with the project's own package manager via runShell, and declare them in the project's dependency manifest.",
4820
4753
  "Match the styles of the application as closely as possible.",
4821
- "The summary should be extremely concise; do not mention env var setup or manual testing steps \u2014 the wizard writes the resolved credentials to the project env files and reports that separately."
4754
+ "The summary should be extremely concise; do not mention manual testing steps."
4822
4755
  ];
4823
4756
  }
4824
4757
  function verificationInstructions(input) {
@@ -4949,21 +4882,6 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
4949
4882
  languages: ctx.getStepOutput("confirm-language")?.languages ?? scan.languages,
4950
4883
  frameworks: ctx.getStepOutput("confirm-framework")?.frameworks ?? scan.frameworks
4951
4884
  };
4952
- const normalizeFrameworkName = (name) => name.toLowerCase().replace(/[^a-z0-9]/g, "");
4953
- const confirmedPrimaryFramework = language.frameworks[0]?.name;
4954
- const frameworkWasCorrected = confirmedPrimaryFramework !== void 0 && !scan.frameworks.some(
4955
- (fw) => normalizeFrameworkName(fw.name) === normalizeFrameworkName(confirmedPrimaryFramework)
4956
- );
4957
- const publicEnvVarPrefixPromise = frameworkWasCorrected ? resolveEnvVarPrefix(confirmedPrimaryFramework).then(
4958
- (r) => r.publicEnvVarPrefix,
4959
- (err) => {
4960
- logger.warn(
4961
- { err, framework: confirmedPrimaryFramework },
4962
- "implement: could not re-resolve publicEnvVarPrefix after a framework correction; using the stale scan value"
4963
- );
4964
- return scan.publicEnvVarPrefix;
4965
- }
4966
- ) : Promise.resolve(scan.publicEnvVarPrefix);
4967
4885
  const selected = ctx.getStepOutput(
4968
4886
  "select-index"
4969
4887
  );
@@ -5044,49 +4962,42 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
5044
4962
  );
5045
4963
  }
5046
4964
  }
5047
- const publicEnvVarPrefix = await publicEnvVarPrefixPromise;
4965
+ const summaries = [];
4966
+ if (uploadWarning) summaries.push(uploadWarning);
4967
+ let searchKey;
4968
+ let searchKeyError;
4969
+ if (useCases.includes("search") && appId) {
4970
+ try {
4971
+ const resolved2 = await resolveSearchOnlyKey(targetIndex, appId);
4972
+ searchKey = resolved2.key;
4973
+ summaries.push(
4974
+ resolved2.source === "created" ? `Created a new search-only Algolia API key for the "${targetIndex}" index in app ${appId} \u2014 safe to expose in frontend code.` : `Reused the existing search-only Algolia API key for the "${targetIndex}" index in app ${appId}.`
4975
+ );
4976
+ } catch (err) {
4977
+ searchKeyError = err.message;
4978
+ summaries.push(
4979
+ `Could not provision a search-only Algolia API key (${searchKeyError}) \u2014 the search agent will scaffold a placeholder with a TODO for you to fill in.`
4980
+ );
4981
+ logger.warn(
4982
+ { err: searchKeyError },
4983
+ "implement: could not provision a search-only API key; the agent will scaffold a placeholder"
4984
+ );
4985
+ }
4986
+ }
5048
4987
  const input = {
5049
4988
  findings: normalized,
5050
4989
  confirmed: confirmed2,
5051
4990
  searchLocation,
5052
4991
  targetIndex,
5053
4992
  language,
5054
- publicEnvVarPrefix,
5055
4993
  appId,
5056
- searchEnvVars: publicSearchEnvVars(publicEnvVarPrefix, targetIndex, appId),
4994
+ searchKey,
4995
+ searchKeyError,
5057
4996
  ingestDir: INGEST_DIR,
5058
4997
  ingestionSource,
5059
4998
  uploadFilePath,
5060
4999
  searchUiTarget: searchUiTarget(language)
5061
5000
  };
5062
- const summaries = [];
5063
- if (uploadWarning) summaries.push(uploadWarning);
5064
- let envSearchKey;
5065
- let envAppIdMismatch = false;
5066
- if (useCases.includes("search") && appId) {
5067
- const envAppId = await readEnvVar(
5068
- repoRoot,
5069
- publicAppIdVar(publicEnvVarPrefix)
5070
- );
5071
- if (envAppId === appId) {
5072
- envSearchKey = await readEnvVar(
5073
- repoRoot,
5074
- publicSearchKeyVar(publicEnvVarPrefix)
5075
- );
5076
- } else if (envAppId) {
5077
- envAppIdMismatch = true;
5078
- const appIdVarName = publicAppIdVar(publicEnvVarPrefix);
5079
- const searchKeyVarName = publicSearchKeyVar(publicEnvVarPrefix);
5080
- summaries.push(
5081
- `\u26A0\uFE0F Your project's env files already set ${appIdVarName}=${envAppId}, but the active Algolia application is ${appId}. The wizard left those values alone \u2014 update ${appIdVarName} and ${searchKeyVarName} by hand, or searches will fail.`
5082
- );
5083
- logger.warn(
5084
- { envAppId, appId },
5085
- "implement: env files hold credentials for a different Algolia application; not reusing its search key"
5086
- );
5087
- }
5088
- }
5089
- let finalSearchEnvVars = input.searchEnvVars;
5090
5001
  let agentRuns = 0;
5091
5002
  let ingestCommand;
5092
5003
  let ingestScriptRan = false;
@@ -5196,6 +5107,7 @@ ${detail}` : ""}`
5196
5107
  ]
5197
5108
  });
5198
5109
  }
5110
+ let searchConfigFile;
5199
5111
  if (useCases.includes("search")) {
5200
5112
  let extraInstructions = [];
5201
5113
  useWizard.getState().clearWrittenFiles();
@@ -5210,11 +5122,14 @@ ${detail}` : ""}`
5210
5122
  "implement: retrying search implementation after failed verification"
5211
5123
  );
5212
5124
  }
5213
- const { summary } = await runImplementationUseCase(
5125
+ const searchResult = await runImplementationUseCase(
5214
5126
  "search",
5215
5127
  extraInstructions
5216
5128
  );
5217
- summaries.push(formatSummary("search", summary));
5129
+ summaries.push(formatSummary("search", searchResult.summary));
5130
+ if (searchResult.searchConfigFile) {
5131
+ searchConfigFile = searchResult.searchConfigFile;
5132
+ }
5218
5133
  const verification = await runVerificationUseCase();
5219
5134
  summaries.push(formatSummary("verification", verification.summary));
5220
5135
  if (verification.sufficient) {
@@ -5238,84 +5153,17 @@ ${detail}` : ""}`
5238
5153
  }
5239
5154
  extraInstructions = verificationRetryInstructions(verification);
5240
5155
  }
5241
- let searchKey;
5242
- let searchKeyError;
5243
- if (appId) {
5244
- try {
5245
- const resolved2 = await resolveSearchOnlyKey(
5246
- targetIndex,
5247
- appId,
5248
- envSearchKey
5249
- );
5250
- searchKey = resolved2.key;
5251
- summaries.push(
5252
- resolved2.source === "created" ? `Created a new search-only Algolia API key for the "${targetIndex}" index in app ${appId} \u2014 safe to expose in frontend code.` : `Reused the existing search-only Algolia API key for the "${targetIndex}" index in app ${appId}.`
5253
- );
5254
- } catch (err) {
5255
- searchKeyError = err.message;
5256
- logger.warn(
5257
- { err: searchKeyError },
5258
- "implement: could not provision a search-only API key; the env value stays a placeholder"
5259
- );
5260
- }
5261
- }
5262
- finalSearchEnvVars = publicSearchEnvVars(
5263
- publicEnvVarPrefix,
5264
- targetIndex,
5265
- appId,
5266
- searchKey
5267
- );
5268
- const resolvedSearchEnvVars = finalSearchEnvVars.filter(
5269
- (v) => !v.value.startsWith("<")
5270
- );
5271
- if (resolvedSearchEnvVars.length > 0) {
5272
- const defaultEnvFile = ENV_LOCAL_AWARE_PREFIXES.includes(
5273
- publicEnvVarPrefix
5274
- ) ? ".env.local" : ".env";
5275
- const written = await writeSearchEnvValues(
5156
+ if (searchConfigFile) {
5157
+ const ignoreStatus = await gitIgnoreStatus(
5276
5158
  repoRoot,
5277
- resolvedSearchEnvVars,
5278
- defaultEnvFile
5159
+ join12(repoRoot, searchConfigFile)
5279
5160
  );
5280
- const writtenFiles = [...new Set(written.map((w) => w.file))];
5281
- for (const file of writtenFiles) {
5282
- const label = relative6(repoRoot, file);
5283
- const names = written.filter((w) => w.file === file).map((w) => w.name);
5284
- summaries.push(`Wrote ${names.join(", ")} to ${label}.`);
5285
- const ignored = await ensureGitIgnored(repoRoot, file);
5286
- if (ignored === "added") {
5287
- summaries.push(`Added ${label} to .gitignore.`);
5288
- } else if (ignored === "tracked") {
5289
- summaries.push(
5290
- `\u26A0\uFE0F ${label} is tracked by git, so a .gitignore rule cannot un-stage it. Run "git rm --cached ${label}" before committing, or the credentials go into history.`
5291
- );
5292
- }
5293
- }
5294
- const writtenNames = new Set(written.map((w) => w.name));
5295
- const stale = [];
5296
- for (const v of resolvedSearchEnvVars) {
5297
- if (writtenNames.has(v.name)) continue;
5298
- const current = await readEnvVar(repoRoot, v.name);
5299
- if (current && current !== v.value) stale.push(v);
5300
- }
5301
- if (stale.length > 0 && !envAppIdMismatch) {
5161
+ if (ignoreStatus === "covered") {
5302
5162
  summaries.push(
5303
- `\u26A0\uFE0F Your project's env files already assign a different value to ${stale.map((v) => `${v.name} (should be ${v.value})`).join(", ")} \u2014 the wizard left it alone. Fix it by hand, or searches will fail.`
5304
- );
5305
- logger.warn(
5306
- { vars: stale.map((v) => v.name) },
5307
- "implement: env files hold different values for the resolved search credentials; not overwriting them"
5163
+ `\u26A0\uFE0F ${searchConfigFile} is gitignored, so this public, safe-to-share search config won't reach teammates or CI. Remove whatever .gitignore rule covers it.`
5308
5164
  );
5309
5165
  }
5310
5166
  }
5311
- const unresolvedSearchEnvVars = finalSearchEnvVars.filter(
5312
- (v) => v.value.startsWith("<")
5313
- );
5314
- if (unresolvedSearchEnvVars.length > 0) {
5315
- summaries.push(
5316
- `Could not resolve a value for ${unresolvedSearchEnvVars.map((v) => v.name).join(", ")} \u2014 fill it in manually in .env or .env.local.` + (searchKeyError ? ` Reason: ${searchKeyError}` : "")
5317
- );
5318
- }
5319
5167
  } else {
5320
5168
  ctx.setUserInput("implementation", "success");
5321
5169
  }
@@ -5328,7 +5176,19 @@ ${detail}` : ""}`
5328
5176
  ...ingestRecordCount != null ? { ingestRecordCount } : {},
5329
5177
  ...ingestDurationMs != null ? { ingestDurationMs } : {}
5330
5178
  } : {},
5331
- ...useCases.includes("search") ? { searchEnvVars: finalSearchEnvVars } : {}
5179
+ ...useCases.includes("search") ? {
5180
+ searchConfig: {
5181
+ filePath: searchConfigFile,
5182
+ vars: [
5183
+ { name: SEARCH_CONFIG_APP_ID, value: appId ?? "" },
5184
+ {
5185
+ name: SEARCH_CONFIG_SEARCH_KEY,
5186
+ value: searchKey ?? SEARCH_KEY_PLACEHOLDER
5187
+ },
5188
+ { name: SEARCH_CONFIG_INDEX_NAME, value: targetIndex }
5189
+ ]
5190
+ }
5191
+ } : {}
5332
5192
  };
5333
5193
  }
5334
5194
 
@@ -5368,8 +5228,8 @@ var defaultWorkflow = {
5368
5228
  defineStep({
5369
5229
  id: "select-index",
5370
5230
  title: "Set up index",
5371
- outputSchema: z30.object({
5372
- selection: z30.string()
5231
+ outputSchema: z29.object({
5232
+ selection: z29.string()
5373
5233
  }),
5374
5234
  run: (ctx) => selectIndexStep(ctx)
5375
5235
  }),
@@ -5477,10 +5337,14 @@ var confirmFramework2 = {
5477
5337
  var search = {
5478
5338
  summary: "Added an InstantSearch-powered search box and results list, mounted in the shared header component.",
5479
5339
  ingestionSource: "generated",
5480
- searchEnvVars: [
5481
- { name: "NEXT_PUBLIC_ALGOLIA_APP_ID", value: "SEEDAPPID" },
5482
- { name: "NEXT_PUBLIC_ALGOLIA_SEARCH_KEY", value: "seedsearchkey" }
5483
- ]
5340
+ searchConfig: {
5341
+ filePath: "src/algolia.config.ts",
5342
+ vars: [
5343
+ { name: "ALGOLIA_APP_ID", value: "SEEDAPPID" },
5344
+ { name: "ALGOLIA_SEARCH_API_KEY", value: "seedsearchkey" },
5345
+ { name: "ALGOLIA_INDEX_NAME", value: "wizard_seed_products" }
5346
+ ]
5347
+ }
5484
5348
  };
5485
5349
  var review = {
5486
5350
  summaryPoints: [
@@ -5647,7 +5511,7 @@ function parseCliArgs(argv) {
5647
5511
 
5648
5512
  // src/lib/resetState.ts
5649
5513
  import { readdir as readdir3, rm as rm2 } from "node:fs/promises";
5650
- import { join as join12 } from "node:path";
5514
+ import { join as join13 } from "node:path";
5651
5515
  var KEEP = ["wizard.log"];
5652
5516
  async function resetProjectState() {
5653
5517
  const dir = stateDir();
@@ -5661,7 +5525,7 @@ async function resetProjectState() {
5661
5525
  const targets = entries.filter((name) => !KEEP.includes(name));
5662
5526
  await Promise.all(
5663
5527
  targets.map(
5664
- (name) => rm2(join12(dir, name), { recursive: true, force: true })
5528
+ (name) => rm2(join13(dir, name), { recursive: true, force: true })
5665
5529
  )
5666
5530
  );
5667
5531
  return { dir, removed: targets };
@@ -4,13 +4,18 @@ Install `instantsearch.js` (v4)
4
4
 
5
5
  ## Search client: always use the lite client
6
6
 
7
- Import the client from `algoliasearch/lite` and alias it to `algoliasearch`
7
+ Import the client from `algoliasearch/lite` and alias it to `algoliasearch`.
8
+ `ALGOLIA_APP_ID`, `ALGOLIA_SEARCH_API_KEY`, and `ALGOLIA_INDEX_NAME` below are
9
+ the constants exported from the project's shared client-side config module —
10
+ these are public values, safe to commit; never hardcode them inline or read
11
+ them from an env var.
8
12
 
9
13
  ```ts
10
14
  import { liteClient as algoliasearch } from 'algoliasearch/lite'
15
+ import { ALGOLIA_APP_ID, ALGOLIA_SEARCH_API_KEY } from '<the project's shared config module>'
11
16
 
12
17
  // Instantiate ONCE, outside components, with a stable reference.
13
- const searchClient = algoliasearch(APP_ID, SEARCH_ONLY_KEY)
18
+ const searchClient = algoliasearch(ALGOLIA_APP_ID, ALGOLIA_SEARCH_API_KEY)
14
19
  ```
15
20
 
16
21
  ## Vanilla (`instantsearch.js` v4)
@@ -19,11 +24,16 @@ const searchClient = algoliasearch(APP_ID, SEARCH_ONLY_KEY)
19
24
  import { liteClient as algoliasearch } from 'algoliasearch/lite'
20
25
  import instantsearch from 'instantsearch.js'
21
26
  import { searchBox, hits } from 'instantsearch.js/es/widgets'
27
+ import {
28
+ ALGOLIA_APP_ID,
29
+ ALGOLIA_SEARCH_API_KEY,
30
+ ALGOLIA_INDEX_NAME,
31
+ } from '<the project's shared config module>'
22
32
 
23
- const searchClient = algoliasearch(APP_ID, SEARCH_ONLY_KEY)
33
+ const searchClient = algoliasearch(ALGOLIA_APP_ID, ALGOLIA_SEARCH_API_KEY)
24
34
 
25
35
  const search = instantsearch({
26
- indexName: 'INDEX_NAME',
36
+ indexName: ALGOLIA_INDEX_NAME,
27
37
  searchClient,
28
38
  insights: true,
29
39
  })
@@ -53,7 +63,7 @@ case, install `search-insights` from npm and pass its default export as
53
63
  import aa from 'search-insights'
54
64
 
55
65
  const search = instantsearch({
56
- indexName: 'INDEX_NAME',
66
+ indexName: ALGOLIA_INDEX_NAME,
57
67
  searchClient,
58
68
  insights: { insightsClient: aa },
59
69
  })
@@ -4,13 +4,18 @@ Install `react-instantsearch` (v7)
4
4
 
5
5
  ## Search client: always use the lite client
6
6
 
7
- Import the client from `algoliasearch/lite` and alias it to `algoliasearch`
7
+ Import the client from `algoliasearch/lite` and alias it to `algoliasearch`.
8
+ `ALGOLIA_APP_ID`, `ALGOLIA_SEARCH_API_KEY`, and `ALGOLIA_INDEX_NAME` below are
9
+ the constants exported from the project's shared client-side config module —
10
+ these are public values, safe to commit; never hardcode them inline or read
11
+ them from an env var.
8
12
 
9
13
  ```ts
10
14
  import { liteClient as algoliasearch } from 'algoliasearch/lite'
15
+ import { ALGOLIA_APP_ID, ALGOLIA_SEARCH_API_KEY } from '<the project's shared config module>'
11
16
 
12
17
  // Instantiate ONCE, outside components, with a stable reference.
13
- const searchClient = algoliasearch(APP_ID, SEARCH_ONLY_KEY)
18
+ const searchClient = algoliasearch(ALGOLIA_APP_ID, ALGOLIA_SEARCH_API_KEY)
14
19
  ```
15
20
 
16
21
  ## React (`react-instantsearch` v7)
@@ -18,19 +23,19 @@ const searchClient = algoliasearch(APP_ID, SEARCH_ONLY_KEY)
18
23
  ```tsx
19
24
  import { liteClient as algoliasearch } from 'algoliasearch/lite'
20
25
  import { InstantSearch, SearchBox, Hits } from 'react-instantsearch'
26
+ import {
27
+ ALGOLIA_APP_ID,
28
+ ALGOLIA_SEARCH_API_KEY,
29
+ ALGOLIA_INDEX_NAME,
30
+ } from '<the project's shared config module>'
21
31
 
22
- // Read from PUBLIC env vars; never hardcode. (Vite shown; use the framework's
23
- // convention: NEXT_PUBLIC_* for Next.js, etc.)
24
- const searchClient = algoliasearch(
25
- import.meta.env.VITE_ALGOLIA_APP_ID,
26
- import.meta.env.VITE_ALGOLIA_SEARCH_API_KEY,
27
- )
32
+ const searchClient = algoliasearch(ALGOLIA_APP_ID, ALGOLIA_SEARCH_API_KEY)
28
33
 
29
34
  export function Search() {
30
35
  return (
31
36
  <InstantSearch
32
37
  searchClient={searchClient}
33
- indexName="INDEX_NAME"
38
+ indexName={ALGOLIA_INDEX_NAME}
34
39
  insights={true}
35
40
  >
36
41
  <SearchBox />
@@ -4,13 +4,18 @@ Install `vue-instantsearch` (v4)
4
4
 
5
5
  ## Search client: always use the lite client
6
6
 
7
- Import the client from `algoliasearch/lite` and alias it to `algoliasearch`
7
+ Import the client from `algoliasearch/lite` and alias it to `algoliasearch`.
8
+ `ALGOLIA_APP_ID`, `ALGOLIA_SEARCH_API_KEY`, and `ALGOLIA_INDEX_NAME` below are
9
+ the constants exported from the project's shared client-side config module —
10
+ these are public values, safe to commit; never hardcode them inline or read
11
+ them from an env var.
8
12
 
9
13
  ```ts
10
14
  import { liteClient as algoliasearch } from 'algoliasearch/lite'
15
+ import { ALGOLIA_APP_ID, ALGOLIA_SEARCH_API_KEY } from '<the project's shared config module>'
11
16
 
12
17
  // Instantiate ONCE, outside components, with a stable reference.
13
- const searchClient = algoliasearch(APP_ID, SEARCH_ONLY_KEY)
18
+ const searchClient = algoliasearch(ALGOLIA_APP_ID, ALGOLIA_SEARCH_API_KEY)
14
19
  ```
15
20
 
16
21
  ## Vue (`vue-instantsearch` v4)
@@ -19,7 +24,7 @@ const searchClient = algoliasearch(APP_ID, SEARCH_ONLY_KEY)
19
24
  <template>
20
25
  <ais-instant-search
21
26
  :search-client="searchClient"
22
- index-name="INDEX_NAME"
27
+ :index-name="ALGOLIA_INDEX_NAME"
23
28
  :insights="true"
24
29
  >
25
30
  <ais-search-box />
@@ -29,11 +34,13 @@ const searchClient = algoliasearch(APP_ID, SEARCH_ONLY_KEY)
29
34
 
30
35
  <script setup>
31
36
  import { liteClient as algoliasearch } from 'algoliasearch/lite'
37
+ import {
38
+ ALGOLIA_APP_ID,
39
+ ALGOLIA_SEARCH_API_KEY,
40
+ ALGOLIA_INDEX_NAME,
41
+ } from '<the project's shared config module>'
32
42
 
33
- const searchClient = algoliasearch(
34
- import.meta.env.VITE_ALGOLIA_APP_ID,
35
- import.meta.env.VITE_ALGOLIA_SEARCH_API_KEY,
36
- )
43
+ const searchClient = algoliasearch(ALGOLIA_APP_ID, ALGOLIA_SEARCH_API_KEY)
37
44
  </script>
38
45
  ```
39
46
 
@@ -5,10 +5,19 @@ route, or programmatic queries). For UI, prefer `instantsearch-setup-<framework>
5
5
 
6
6
  ## Client
7
7
 
8
+ `ALGOLIA_APP_ID`, `ALGOLIA_SEARCH_API_KEY`, and `ALGOLIA_INDEX_NAME` below are
9
+ the constants exported from the project's shared client-side config module —
10
+ these are public values, safe to commit; never hardcode them inline or read
11
+ them from an env var.
12
+
8
13
  ```ts
9
14
  import { algoliasearch } from 'algoliasearch'
15
+ import {
16
+ ALGOLIA_APP_ID,
17
+ ALGOLIA_SEARCH_API_KEY,
18
+ } from '<the project's shared config module>'
10
19
 
11
- const client = algoliasearch(APP_ID, SEARCH_ONLY_KEY)
20
+ const client = algoliasearch(ALGOLIA_APP_ID, ALGOLIA_SEARCH_API_KEY)
12
21
  ```
13
22
 
14
23
  In v5 there is no `client.initIndex(...)`. Index methods take the index name as a
@@ -18,7 +27,7 @@ parameter on the client. Every method takes a single options object.
18
27
 
19
28
  ```ts
20
29
  const { hits, nbHits } = await client.searchSingleIndex({
21
- indexName: 'INDEX_NAME',
30
+ indexName: ALGOLIA_INDEX_NAME,
22
31
  searchParams: { query: 'shoes', hitsPerPage: 20, page: 0 },
23
32
  })
24
33
  ```
@@ -28,7 +37,7 @@ const { hits, nbHits } = await client.searchSingleIndex({
28
37
  ```ts
29
38
  const { results } = await client.search({
30
39
  requests: [
31
- { indexName: 'INDEX_NAME', query: 'shoes' },
40
+ { indexName: ALGOLIA_INDEX_NAME, query: 'shoes' },
32
41
  { indexName: 'OTHER_INDEX', query: 'shoes' },
33
42
  ],
34
43
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@algolia/wizard",
3
- "version": "0.34.0-rc.125.243",
3
+ "version": "0.34.0-rc.125.245",
4
4
  "description": "Magically implement Algolia functionality in your codebase",
5
5
  "type": "module",
6
6
  "engines": {