@algolia/wizard 0.15.0 → 0.16.0

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 +132 -35
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -2507,8 +2507,8 @@ function writeFileTool(ctx) {
2507
2507
  // src/lib/tools/writeAlgoliaCredentials.ts
2508
2508
  import { tool as tool6 } from "ai";
2509
2509
  import z13 from "zod";
2510
- import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile4 } from "node:fs/promises";
2511
- import { dirname as dirname5 } from "node:path";
2510
+ import { mkdir as mkdir4, readFile as readFile6, writeFile as writeFile5 } from "node:fs/promises";
2511
+ import { dirname as dirname5, relative as relative3 } from "node:path";
2512
2512
 
2513
2513
  // src/lib/algoliaApiKey.ts
2514
2514
  import { z as z12 } from "zod";
@@ -2690,6 +2690,79 @@ async function resolveSearchOnlyKey(index, appId, envKey) {
2690
2690
  );
2691
2691
  }
2692
2692
 
2693
+ // src/lib/gitignore.ts
2694
+ import { execFile } from "node:child_process";
2695
+ import { lstat as lstat2, readFile as readFile5, writeFile as writeFile4 } from "node:fs/promises";
2696
+ import { join as join8, relative as relative2 } from "node:path";
2697
+ var GIT_ENV_OVERRIDES = [
2698
+ "GIT_DIR",
2699
+ "GIT_WORK_TREE",
2700
+ "GIT_INDEX_FILE",
2701
+ "GIT_OBJECT_DIRECTORY",
2702
+ "GIT_COMMON_DIR"
2703
+ ];
2704
+ function gitSucceeds(root, args) {
2705
+ const env = { ...process.env };
2706
+ for (const key of GIT_ENV_OVERRIDES) delete env[key];
2707
+ return new Promise((resolve4) => {
2708
+ execFile("git", ["-C", root, ...args], { env }, (err) => {
2709
+ if (!err) return resolve4(true);
2710
+ resolve4(
2711
+ err.code === 1 ? false : void 0
2712
+ );
2713
+ });
2714
+ });
2715
+ }
2716
+ function isIgnoredByRule(root, relPath) {
2717
+ return gitSucceeds(root, ["check-ignore", "-q", "--no-index", "--", relPath]);
2718
+ }
2719
+ function isTracked(root, relPath) {
2720
+ return gitSucceeds(root, ["ls-files", "--error-unmatch", "--", relPath]);
2721
+ }
2722
+ async function inspect(root, target) {
2723
+ const relPath = relative2(root, target);
2724
+ if (!relPath || relPath.startsWith("..")) {
2725
+ return { ignoredByRule: void 0, tracked: false };
2726
+ }
2727
+ const ignoredByRule = await isIgnoredByRule(root, relPath);
2728
+ if (ignoredByRule === void 0) {
2729
+ logger.warn(
2730
+ { root, relPath },
2731
+ "gitignore: git check-ignore could not answer; not reporting on this path"
2732
+ );
2733
+ return { ignoredByRule, tracked: false };
2734
+ }
2735
+ return { ignoredByRule, tracked: await isTracked(root, relPath) };
2736
+ }
2737
+ async function ensureGitIgnored(root, target) {
2738
+ const { ignoredByRule, tracked } = await inspect(root, target);
2739
+ if (ignoredByRule === void 0) return "unknown";
2740
+ if (ignoredByRule) return tracked ? "tracked" : "covered";
2741
+ const pattern = relative2(root, target);
2742
+ const gitIgnore = join8(root, ".gitignore");
2743
+ try {
2744
+ const link = await lstat2(gitIgnore).catch(() => null);
2745
+ if (link?.isSymbolicLink()) {
2746
+ logger.warn(
2747
+ { gitIgnore },
2748
+ "gitignore: root .gitignore is a symlink; not writing to it"
2749
+ );
2750
+ return "unknown";
2751
+ }
2752
+ const existing = link ? await readFile5(gitIgnore, "utf8") : "";
2753
+ const prefix = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
2754
+ await writeFile4(gitIgnore, `${existing}${prefix}${pattern}
2755
+ `, "utf8");
2756
+ return tracked ? "tracked" : "added";
2757
+ } catch (err) {
2758
+ logger.warn(
2759
+ { gitIgnore, pattern, err: err.message },
2760
+ "gitignore: could not add the pattern; continuing"
2761
+ );
2762
+ return "unknown";
2763
+ }
2764
+ }
2765
+
2693
2766
  // src/lib/tools/writeAlgoliaCredentials.ts
2694
2767
  var APP_ID_VAR = "ALGOLIA_APPLICATION_ID";
2695
2768
  var API_KEY_VAR = "ALGOLIA_WRITE_API_KEY";
@@ -2722,7 +2795,7 @@ function upsertEnv(content, name, value) {
2722
2795
  }
2723
2796
  function writeCredentialsTool(ctx) {
2724
2797
  return tool6({
2725
- description: `Write the active Algolia credentials (${APP_ID_VAR} and ${API_KEY_VAR}) and the target index name (${INDEX_NAME_VAR}) into the given env file. The credentials come from the selected Algolia application, with a write key scoped to the target index; you only pass the path to the env file. ${INDEX_NAME_VAR} is always set to this run's target index, replacing any value already there. An ${APP_ID_VAR} or ${API_KEY_VAR} the file already gives a value is left untouched; a missing or blank one is filled in when it can be paired with the selected application.`,
2798
+ description: `Write the active Algolia credentials (${APP_ID_VAR} and ${API_KEY_VAR}) and the target index name (${INDEX_NAME_VAR}) into the given env file. The credentials come from the selected Algolia application, with a write key scoped to the target index; you only pass the path to the env file. ${INDEX_NAME_VAR} is always set to this run's target index, replacing any value already there. An ${APP_ID_VAR} or ${API_KEY_VAR} the file already gives a value is left untouched; a missing or blank one is filled in when it can be paired with the selected application. The env file is added to .gitignore automatically; do not edit .gitignore yourself.`,
2726
2799
  inputSchema: z13.object({
2727
2800
  filePath: z13.string().describe(
2728
2801
  'Path to the env file to write credentials into (e.g. ".env")'
@@ -2743,7 +2816,7 @@ function writeCredentialsTool(ctx) {
2743
2816
  return `Refused: ${resolved2.target} is outside the repo root (${ctx.root}).`;
2744
2817
  }
2745
2818
  try {
2746
- existing = await readFile5(resolved2.target, "utf8");
2819
+ existing = await readFile6(resolved2.target, "utf8");
2747
2820
  } catch (err) {
2748
2821
  if (err.code !== "ENOENT") throw err;
2749
2822
  }
@@ -2782,6 +2855,7 @@ function writeCredentialsTool(ctx) {
2782
2855
  return `Error: could not resolve Algolia credentials (${err.message}). Ask the user to authenticate with the Algolia CLI first.`;
2783
2856
  }
2784
2857
  }
2858
+ let wrote;
2785
2859
  try {
2786
2860
  const updated = [
2787
2861
  ...credentials,
@@ -2793,7 +2867,7 @@ function writeCredentialsTool(ctx) {
2793
2867
  existing
2794
2868
  );
2795
2869
  await mkdir4(dirname5(resolved2.target), { recursive: true });
2796
- await writeFile4(resolved2.target, updated, "utf8");
2870
+ await writeFile5(resolved2.target, updated, "utf8");
2797
2871
  const sentences = [
2798
2872
  `Wrote ${[...credentials.map(([name]) => name), INDEX_NAME_VAR].join(", ")} to ${filePath}.`
2799
2873
  ];
@@ -2807,19 +2881,33 @@ function writeCredentialsTool(ctx) {
2807
2881
  `Skipped ${present.join(" and ")}: already defined there.`
2808
2882
  );
2809
2883
  }
2810
- return [...sentences, ...notes].join(" ");
2884
+ wrote = [...sentences, ...notes].join(" ");
2811
2885
  } catch (err) {
2812
2886
  return `Error writing credentials to ${filePath}: ${err.message}`;
2813
2887
  }
2888
+ return wrote + await gitIgnoreOutcome(ctx, resolved2.target);
2814
2889
  }
2815
2890
  });
2816
2891
  }
2892
+ async function gitIgnoreOutcome(ctx, target) {
2893
+ const name = relative3(ctx.root, target);
2894
+ switch (await ensureGitIgnored(ctx.root, target)) {
2895
+ case "added":
2896
+ return ` Added "${name}" to .gitignore so the credentials are not stageable.`;
2897
+ case "tracked":
2898
+ return ` Warning: ${name} is already tracked by git, and a .gitignore rule cannot un-stage it. Tell the user with notifyUser to run "git rm --cached ${name}" before committing.`;
2899
+ case "unknown":
2900
+ return ` Warning: could not confirm ${name} is gitignored. Tell the user with notifyUser to check before committing.`;
2901
+ case "covered":
2902
+ return "";
2903
+ }
2904
+ }
2817
2905
 
2818
2906
  // src/lib/tools/searchFiles.ts
2819
2907
  import { tool as tool7 } from "ai";
2820
2908
  import z14 from "zod";
2821
- import { readdir as readdir2, readFile as readFile6 } from "node:fs/promises";
2822
- import { join as join8 } from "node:path";
2909
+ import { readdir as readdir2, readFile as readFile7 } from "node:fs/promises";
2910
+ import { join as join9 } from "node:path";
2823
2911
  var MAX_QUERY_LENGTH = 1e3;
2824
2912
  var SKIP_DIRS = /* @__PURE__ */ new Set([
2825
2913
  "node_modules",
@@ -2834,7 +2922,7 @@ async function walkFiles(dir) {
2834
2922
  const out = [];
2835
2923
  for (const e of await readdir2(dir, { withFileTypes: true })) {
2836
2924
  if (e.name.startsWith(".") || SKIP_DIRS.has(e.name)) continue;
2837
- const full = join8(dir, e.name);
2925
+ const full = join9(dir, e.name);
2838
2926
  if (e.isDirectory()) out.push(...await walkFiles(full));
2839
2927
  else if (e.isFile()) out.push(full);
2840
2928
  }
@@ -2867,7 +2955,7 @@ function searchFilesTool(ctx) {
2867
2955
  for (const file of await walkFiles(resolved2.target)) {
2868
2956
  let content;
2869
2957
  try {
2870
- content = await readFile6(file, "utf8");
2958
+ content = await readFile7(file, "utf8");
2871
2959
  } catch {
2872
2960
  continue;
2873
2961
  }
@@ -2890,7 +2978,7 @@ function searchFilesTool(ctx) {
2890
2978
  // src/lib/tools/runShell.ts
2891
2979
  import { tool as tool8 } from "ai";
2892
2980
  import z15 from "zod";
2893
- import { relative as relative2 } from "node:path";
2981
+ import { relative as relative4 } from "node:path";
2894
2982
 
2895
2983
  // src/lib/tools/utils/runShell.ts
2896
2984
  import { spawn as spawn2 } from "node:child_process";
@@ -2987,7 +3075,7 @@ function runShell(command, opts) {
2987
3075
  // src/lib/tools/runShell.ts
2988
3076
  function storeApproval(root) {
2989
3077
  return async (req) => {
2990
- const rel = relative2(root, req.cwd);
3078
+ const rel = relative4(root, req.cwd);
2991
3079
  const answer = await useWizard.getState().requestUserInput({
2992
3080
  prompt: "Run this command?",
2993
3081
  promptType: "commandApproval",
@@ -3081,7 +3169,7 @@ function runShellTool(ctx) {
3081
3169
  import { tool as tool9, generateText, Output, NoObjectGeneratedError } from "ai";
3082
3170
  import { createAnthropic } from "@ai-sdk/anthropic";
3083
3171
  import { nanoid as nanoid2 } from "nanoid";
3084
- import { mkdir as mkdir5, writeFile as writeFile5 } from "node:fs/promises";
3172
+ import { mkdir as mkdir5, writeFile as writeFile6 } from "node:fs/promises";
3085
3173
  import { dirname as dirname6 } from "node:path";
3086
3174
  import z16 from "zod";
3087
3175
  var DATA_DIR = ".algolia-wizard/data";
@@ -3158,7 +3246,7 @@ function generateRecordTool(ctx) {
3158
3246
  return `Refused: ${resolved2.target} is outside the repo root (${ctx.root}).`;
3159
3247
  }
3160
3248
  await mkdir5(dirname6(resolved2.target), { recursive: true });
3161
- await writeFile5(resolved2.target, JSON.stringify(records, null, 2), "utf8");
3249
+ await writeFile6(resolved2.target, JSON.stringify(records, null, 2), "utf8");
3162
3250
  logger.info({ entityName, count: records.length, relPath }, "generateRecord wrote records to disk");
3163
3251
  return {
3164
3252
  filePath: relPath,
@@ -3451,7 +3539,7 @@ async function runAnalysis(mode, extraInstructions = []) {
3451
3539
  // package.json
3452
3540
  var package_default = {
3453
3541
  name: "@algolia/wizard",
3454
- version: "0.15.0",
3542
+ version: "0.16.0",
3455
3543
  description: "Magically implement Algolia functionality in your codebase",
3456
3544
  type: "module",
3457
3545
  engines: {
@@ -3857,17 +3945,18 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
3857
3945
 
3858
3946
  // src/actions/implement.ts
3859
3947
  import z27 from "zod";
3948
+ import { join as join12 } from "node:path";
3860
3949
 
3861
3950
  // src/lib/worktree.ts
3862
- import { execFile } from "node:child_process";
3863
- import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as readFile7, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
3864
- import { basename as basename2, dirname as dirname7, isAbsolute as isAbsolute2, join as join9, resolve as resolve3 } from "node:path";
3951
+ import { execFile as execFile2 } from "node:child_process";
3952
+ import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as readFile8, stat as stat2, writeFile as writeFile7 } from "node:fs/promises";
3953
+ import { basename as basename2, dirname as dirname7, isAbsolute as isAbsolute2, join as join10, resolve as resolve3 } from "node:path";
3865
3954
  var MAX_BUFFER = 32 * 1024 * 1024;
3866
3955
  var MAX_WIZARD_WORKTREES = 3;
3867
3956
  var WIZARD_BRANCH_PREFIX = "wizard/implement-";
3868
3957
  function git(args) {
3869
3958
  return new Promise((resolve4, reject) => {
3870
- execFile("git", args, { maxBuffer: MAX_BUFFER }, (err, stdout, stderr) => {
3959
+ execFile2("git", args, { maxBuffer: MAX_BUFFER }, (err, stdout, stderr) => {
3871
3960
  if (err)
3872
3961
  return reject(
3873
3962
  new Error(
@@ -3892,7 +3981,7 @@ async function isWorkingTreeDirty(repoRoot) {
3892
3981
  return out.trim().length > 0;
3893
3982
  }
3894
3983
  async function pruneOldWorktrees(repoRoot) {
3895
- const dir = join9(stateDir(repoRoot), "worktrees");
3984
+ const dir = join10(stateDir(repoRoot), "worktrees");
3896
3985
  const stale = (await readdir3(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
3897
3986
  for (const slug of stale) {
3898
3987
  const branch = slug.replace("wizard-implement-", WIZARD_BRANCH_PREFIX);
@@ -3903,7 +3992,7 @@ async function pruneOldWorktrees(repoRoot) {
3903
3992
  "worktree",
3904
3993
  "remove",
3905
3994
  "--force",
3906
- join9(dir, slug)
3995
+ join10(dir, slug)
3907
3996
  ]);
3908
3997
  await git(["-C", repoRoot, "branch", "-D", branch]);
3909
3998
  } catch (err) {
@@ -3917,7 +4006,7 @@ async function pruneOldWorktrees(repoRoot) {
3917
4006
  async function createWorktree(repoRoot) {
3918
4007
  const branch = `${WIZARD_BRANCH_PREFIX}${Date.now()}`;
3919
4008
  const dirSlug = branch.replace(/\//g, "-");
3920
- const path = join9(stateDir(repoRoot), "worktrees", dirSlug);
4009
+ const path = join10(stateDir(repoRoot), "worktrees", dirSlug);
3921
4010
  await git(["-C", repoRoot, "worktree", "prune"]);
3922
4011
  await pruneOldWorktrees(repoRoot);
3923
4012
  await mkdir6(dirname7(path), { recursive: true });
@@ -3937,8 +4026,8 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
3937
4026
  } catch {
3938
4027
  return { ok: false, reason: `"${sourcePath}" does not exist` };
3939
4028
  }
3940
- const relPath = join9(ingestDir, basename2(source));
3941
- const dest = join9(worktreePath, relPath);
4029
+ const relPath = join10(ingestDir, basename2(source));
4030
+ const dest = join10(worktreePath, relPath);
3942
4031
  try {
3943
4032
  await mkdir6(dirname7(dest), { recursive: true });
3944
4033
  await copyFile(source, dest);
@@ -3956,7 +4045,7 @@ function hasEnvVar(content, name) {
3956
4045
  async function readEnvVar(worktreePath, name) {
3957
4046
  let content;
3958
4047
  try {
3959
- content = await readFile7(join9(worktreePath, ".env"), "utf8");
4048
+ content = await readFile8(join10(worktreePath, ".env"), "utf8");
3960
4049
  } catch (err) {
3961
4050
  if (err.code !== "ENOENT") throw err;
3962
4051
  return void 0;
@@ -3971,10 +4060,10 @@ async function readEnvVar(worktreePath, name) {
3971
4060
  return value;
3972
4061
  }
3973
4062
  async function writeSearchEnvValues(worktreePath, vars) {
3974
- const target = join9(worktreePath, ".env");
4063
+ const target = join10(worktreePath, ".env");
3975
4064
  let existing = "";
3976
4065
  try {
3977
- existing = await readFile7(target, "utf8");
4066
+ existing = await readFile8(target, "utf8");
3978
4067
  } catch (err) {
3979
4068
  if (err.code !== "ENOENT") throw err;
3980
4069
  }
@@ -3983,7 +4072,7 @@ async function writeSearchEnvValues(worktreePath, vars) {
3983
4072
  const prefix = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
3984
4073
  const lines = missing.map(({ name, value }) => `${name}=${value}
3985
4074
  `).join("");
3986
- await writeFile6(target, existing + prefix + lines, "utf8");
4075
+ await writeFile7(target, existing + prefix + lines, "utf8");
3987
4076
  return missing.map((v) => v.name);
3988
4077
  }
3989
4078
  async function listChangedFiles(worktreePath) {
@@ -4044,13 +4133,13 @@ async function confirmDirtyWorkingTree(ctx, repoRoot) {
4044
4133
 
4045
4134
  // src/lib/algoliaDocs.ts
4046
4135
  import { readFileSync, readdirSync, existsSync } from "node:fs";
4047
- import { dirname as dirname8, join as join10 } from "node:path";
4136
+ import { dirname as dirname8, join as join11 } from "node:path";
4048
4137
  import { fileURLToPath as fileURLToPath2 } from "node:url";
4049
- var DOCS_SUBPATH = join10("docs", "algolia-sdk");
4138
+ var DOCS_SUBPATH = join11("docs", "algolia-sdk");
4050
4139
  function findDocsDir() {
4051
4140
  let dir = dirname8(fileURLToPath2(import.meta.url));
4052
4141
  for (; ; ) {
4053
- const candidate = join10(dir, DOCS_SUBPATH);
4142
+ const candidate = join11(dir, DOCS_SUBPATH);
4054
4143
  if (existsSync(candidate)) return candidate;
4055
4144
  const parent = dirname8(dir);
4056
4145
  if (parent === dir) return void 0;
@@ -4073,7 +4162,7 @@ function loadAlgoliaDoc(language) {
4073
4162
  );
4074
4163
  return "";
4075
4164
  }
4076
- return readFileSync(join10(docsDir, files[0]), "utf8").trim();
4165
+ return readFileSync(join11(docsDir, files[0]), "utf8").trim();
4077
4166
  }
4078
4167
  function getNamedDoc(name, language) {
4079
4168
  const docsDir = findDocsDir();
@@ -4081,7 +4170,7 @@ function getNamedDoc(name, language) {
4081
4170
  logger.warn("docs/algolia-sdk not found");
4082
4171
  return "";
4083
4172
  }
4084
- const file = join10(docsDir, `${name}-${language}.md`);
4173
+ const file = join11(docsDir, `${name}-${language}.md`);
4085
4174
  if (!existsSync(file)) {
4086
4175
  logger.warn({ name, language }, "named SDK reference not found");
4087
4176
  return "";
@@ -4728,6 +4817,14 @@ ${detail}` : ""}`
4728
4817
  if (written.length > 0) {
4729
4818
  summaries.push(`Wrote ${written.join(", ")} to .env.`);
4730
4819
  }
4820
+ const ignored = await ensureGitIgnored(worktree, join12(worktree, ".env"));
4821
+ if (ignored === "added") {
4822
+ summaries.push("Added .env to .gitignore.");
4823
+ } else if (ignored === "tracked") {
4824
+ summaries.push(
4825
+ '\u26A0\uFE0F .env is tracked by git, so a .gitignore rule cannot un-stage it. Run "git rm --cached .env" before committing, or the credentials go into history.'
4826
+ );
4827
+ }
4731
4828
  const stale = [];
4732
4829
  for (const v of resolvedSearchEnvVars) {
4733
4830
  if (written.includes(v.name)) continue;
@@ -5100,7 +5197,7 @@ function parseCliArgs(argv) {
5100
5197
 
5101
5198
  // src/lib/resetState.ts
5102
5199
  import { readdir as readdir4, rm as rm2 } from "node:fs/promises";
5103
- import { join as join11 } from "node:path";
5200
+ import { join as join13 } from "node:path";
5104
5201
  var KEEP = ["wizard.log"];
5105
5202
  async function resetProjectState() {
5106
5203
  const dir = stateDir();
@@ -5114,7 +5211,7 @@ async function resetProjectState() {
5114
5211
  const targets = entries.filter((name) => !KEEP.includes(name));
5115
5212
  await Promise.all(
5116
5213
  targets.map(
5117
- (name) => rm2(join11(dir, name), { recursive: true, force: true })
5214
+ (name) => rm2(join13(dir, name), { recursive: true, force: true })
5118
5215
  )
5119
5216
  );
5120
5217
  return { dir, removed: targets };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@algolia/wizard",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "Magically implement Algolia functionality in your codebase",
5
5
  "type": "module",
6
6
  "engines": {