@algolia/wizard 0.36.0 → 0.37.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.
package/dist/main.js
CHANGED
|
@@ -1128,7 +1128,7 @@ function PromptInput() {
|
|
|
1128
1128
|
}
|
|
1129
1129
|
|
|
1130
1130
|
// src/workflows/default.ts
|
|
1131
|
-
import { z as
|
|
1131
|
+
import { z as z29 } from "zod";
|
|
1132
1132
|
|
|
1133
1133
|
// src/core/orchestrator.ts
|
|
1134
1134
|
import "zod";
|
|
@@ -1696,17 +1696,23 @@ async function hasSymlinkParent(ctx, target) {
|
|
|
1696
1696
|
// src/lib/tools/listFiles.ts
|
|
1697
1697
|
function listFilesTool(ctx) {
|
|
1698
1698
|
return tool({
|
|
1699
|
-
description:
|
|
1700
|
-
inputSchema: z5.object(
|
|
1701
|
-
|
|
1702
|
-
|
|
1699
|
+
description: 'List files in a directory (default: the current working directory). Pass path to list a subdirectory directly \u2014 e.g. "packages/api" \u2014 without first changeDirectory-ing into it.',
|
|
1700
|
+
inputSchema: z5.object({
|
|
1701
|
+
path: z5.string().optional().describe("Directory to list, relative to cwd (default: cwd)")
|
|
1702
|
+
}),
|
|
1703
|
+
execute: async ({ path = "." }) => {
|
|
1704
|
+
logger.info({ path }, "called listFiles tool");
|
|
1703
1705
|
if (++ctx.counts.list > ctx.limits.list) {
|
|
1704
1706
|
return `Refused: list limit (${ctx.limits.list}) reached. Stop listing and proceed with the information you already have.`;
|
|
1705
1707
|
}
|
|
1706
|
-
const resolved2 = resolveInRoot(ctx,
|
|
1708
|
+
const resolved2 = resolveInRoot(ctx, path);
|
|
1707
1709
|
if (!resolved2.ok) return resolved2.error;
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
+
try {
|
|
1711
|
+
const entries = await readdir(resolved2.target, { withFileTypes: true });
|
|
1712
|
+
return entries.map((e) => e.isDirectory() ? `${e.name}/` : e.name).join("\n");
|
|
1713
|
+
} catch (err) {
|
|
1714
|
+
return `Error listing ${path}: ${err.message}`;
|
|
1715
|
+
}
|
|
1710
1716
|
}
|
|
1711
1717
|
});
|
|
1712
1718
|
}
|
|
@@ -1814,7 +1820,7 @@ import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
|
|
|
1814
1820
|
import { dirname as dirname3 } from "node:path";
|
|
1815
1821
|
function writeFileTool(ctx) {
|
|
1816
1822
|
return tool5({
|
|
1817
|
-
description: "Write content to a file at the given path, overwriting it.
|
|
1823
|
+
description: "Write content to a file at the given path, overwriting it. Writes to secret env files (.env, .env.local, etc.) are refused by this tool \u2014 call it anyway and the tool will tell you how to proceed.",
|
|
1818
1824
|
inputSchema: z9.object({
|
|
1819
1825
|
filePath: z9.string().describe("Path to the file to write"),
|
|
1820
1826
|
content: z9.string().describe("Content to write to the file")
|
|
@@ -1824,7 +1830,7 @@ function writeFileTool(ctx) {
|
|
|
1824
1830
|
const resolved2 = resolveInRoot(ctx, filePath);
|
|
1825
1831
|
if (resolved2.ok === false) return resolved2.error;
|
|
1826
1832
|
if (isSecretEnvFile(resolved2.target)) {
|
|
1827
|
-
return `Refused: ${filePath} holds secrets.
|
|
1833
|
+
return `Refused: ${filePath} holds secrets and cannot be written directly. If a writeCredentials tool is available, use it to set Algolia credentials. If not, tell the user to set the value manually \u2014 do not reproduce the secret value in your response.`;
|
|
1828
1834
|
}
|
|
1829
1835
|
try {
|
|
1830
1836
|
if (await hasSymlinkParent(ctx, resolved2.target)) {
|
|
@@ -2078,8 +2084,7 @@ function resolveWriteKey(index, appId) {
|
|
|
2078
2084
|
`Algolia Wizard write key for ${index} index`
|
|
2079
2085
|
);
|
|
2080
2086
|
}
|
|
2081
|
-
async function resolveSearchOnlyKey(index, appId
|
|
2082
|
-
if (envKey) return { key: envKey, source: "env" };
|
|
2087
|
+
async function resolveSearchOnlyKey(index, appId) {
|
|
2083
2088
|
return resolveKey(
|
|
2084
2089
|
"search",
|
|
2085
2090
|
index,
|
|
@@ -2118,6 +2123,12 @@ function isIgnoredByRule(root, relPath) {
|
|
|
2118
2123
|
function isTracked(root, relPath) {
|
|
2119
2124
|
return gitSucceeds(root, ["ls-files", "--error-unmatch", "--", relPath]);
|
|
2120
2125
|
}
|
|
2126
|
+
async function gitIgnoreStatus(root, target) {
|
|
2127
|
+
const { ignoredByRule, tracked } = await inspect(root, target);
|
|
2128
|
+
if (ignoredByRule === void 0) return "unknown";
|
|
2129
|
+
if (tracked) return "tracked";
|
|
2130
|
+
return ignoredByRule ? "covered" : "needsRule";
|
|
2131
|
+
}
|
|
2121
2132
|
async function inspect(root, target) {
|
|
2122
2133
|
const relPath = relative2(root, target);
|
|
2123
2134
|
if (!relPath || relPath.startsWith("..")) {
|
|
@@ -2163,31 +2174,9 @@ async function ensureGitIgnored(root, target) {
|
|
|
2163
2174
|
}
|
|
2164
2175
|
|
|
2165
2176
|
// src/lib/tools/writeAlgoliaCredentials.ts
|
|
2166
|
-
var APP_ID_VAR = "
|
|
2167
|
-
var API_KEY_VAR = "
|
|
2177
|
+
var APP_ID_VAR = "ALGOLIA_APP_ID";
|
|
2178
|
+
var API_KEY_VAR = "ALGOLIA_WRITE_KEY";
|
|
2168
2179
|
var INDEX_NAME_VAR = "ALGOLIA_INDEX_NAME";
|
|
2169
|
-
var PUBLIC_APP_ID_SUFFIX = "ALGOLIA_APP_ID";
|
|
2170
|
-
var PUBLIC_SEARCH_KEY_SUFFIX = "ALGOLIA_SEARCH_KEY";
|
|
2171
|
-
var PUBLIC_INDEX_NAME_SUFFIX = "ALGOLIA_INDEX_NAME";
|
|
2172
|
-
function publicAppIdVar(prefix) {
|
|
2173
|
-
return `${prefix}${PUBLIC_APP_ID_SUFFIX}`;
|
|
2174
|
-
}
|
|
2175
|
-
function publicSearchKeyVar(prefix) {
|
|
2176
|
-
return `${prefix}${PUBLIC_SEARCH_KEY_SUFFIX}`;
|
|
2177
|
-
}
|
|
2178
|
-
function publicIndexNameVar(prefix) {
|
|
2179
|
-
return `${prefix}${PUBLIC_INDEX_NAME_SUFFIX}`;
|
|
2180
|
-
}
|
|
2181
|
-
function publicSearchEnvVars(prefix, index, appId, searchKey) {
|
|
2182
|
-
return [
|
|
2183
|
-
{ name: publicAppIdVar(prefix), value: appId ?? "<your-algolia-app-id>" },
|
|
2184
|
-
{
|
|
2185
|
-
name: publicSearchKeyVar(prefix),
|
|
2186
|
-
value: searchKey ?? "<your-algolia-search-only-api-key>"
|
|
2187
|
-
},
|
|
2188
|
-
{ name: publicIndexNameVar(prefix), value: index }
|
|
2189
|
-
];
|
|
2190
|
-
}
|
|
2191
2180
|
function appendEnv(content, entries) {
|
|
2192
2181
|
const prefix = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
|
|
2193
2182
|
const lines = entries.map(([name, value]) => `${name}=${value}
|
|
@@ -2195,11 +2184,13 @@ function appendEnv(content, entries) {
|
|
|
2195
2184
|
return content + prefix + lines;
|
|
2196
2185
|
}
|
|
2197
2186
|
function hasEnv(content, name) {
|
|
2198
|
-
return new RegExp(`^(\\
|
|
2187
|
+
return new RegExp(`^([ \\t]*(?:export[ \\t]+)?${name})[ \\t]*=`, "m").test(
|
|
2188
|
+
content
|
|
2189
|
+
);
|
|
2199
2190
|
}
|
|
2200
2191
|
function readEnv(content, name) {
|
|
2201
2192
|
const found = content.match(
|
|
2202
|
-
new RegExp(
|
|
2193
|
+
new RegExp(`^[ \\t]*(?:export[ \\t]+)?${name}[ \\t]*=[ \\t]*(.*)$`, "m")
|
|
2203
2194
|
);
|
|
2204
2195
|
if (!found) return null;
|
|
2205
2196
|
const raw = found[1].trim();
|
|
@@ -2210,16 +2201,16 @@ function readEnv(content, name) {
|
|
|
2210
2201
|
function upsertEnv(content, name, value) {
|
|
2211
2202
|
if (!hasEnv(content, name)) return appendEnv(content, [[name, value]]);
|
|
2212
2203
|
return content.replace(
|
|
2213
|
-
new RegExp(
|
|
2204
|
+
new RegExp(`^[ \\t]*(?:export[ \\t]+)?${name}[ \\t]*=.*$`, "gm"),
|
|
2214
2205
|
() => `${name}=${value}`
|
|
2215
2206
|
);
|
|
2216
2207
|
}
|
|
2217
2208
|
function writeCredentialsTool(ctx) {
|
|
2218
2209
|
return tool6({
|
|
2219
|
-
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.`,
|
|
2210
|
+
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. If the script or app that reads these credentials lives in a subdirectory (e.g. a package in a monorepo), an env file at the repo root is the wrong default \u2014 a script only loads env vars from its own directory (or one it's explicitly configured to read), so check every directory from the script's own up to the repo root, not just those two: its own directory, each ancestor in between (a shared workspace-level directory above the immediate package is common), and the root. Use whichever of those already holds real credentials; only fall back to the repo root when none of them do. Never invent a brand-new file in one of those directories when a real one already exists in another \u2014 that leaves the real one stale and the new one wrong. Listing just the script's own directory and the very top-level root is not enough to find a workspace-level file in between; check the intermediate ones too. If instructions describe a location that doesn't match the project you actually find (e.g. a path outside the repo, or a convention the project doesn't follow), don't stop and ask before doing anything \u2014 call this tool on the real, in-repo file the script actually reads (that's always the safe default), then note the mismatch afterward. Ending your turn with only a question and no call to this tool leaves the project unconfigured.`,
|
|
2220
2211
|
inputSchema: z13.object({
|
|
2221
2212
|
filePath: z13.string().describe(
|
|
2222
|
-
'Path to the env file to write credentials into (e.g. ".env")'
|
|
2213
|
+
'Path to the env file to write credentials into, relative to the repo root (e.g. ".env", or "packages/api/.env" when the consuming script lives in that package)'
|
|
2223
2214
|
)
|
|
2224
2215
|
}),
|
|
2225
2216
|
execute: async ({ filePath }) => {
|
|
@@ -3180,6 +3171,7 @@ var detectLanguage = () => runAgent({
|
|
|
3180
3171
|
"Return the exact version",
|
|
3181
3172
|
"Exclude things like CSS frameworks, build tools, or testing frameworks",
|
|
3182
3173
|
`Determine publicEnvVarPrefix: 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 the detected framework's known convention for exposing env vars to client-side code; use "" when the project has no such convention (e.g. a backend-only project).`,
|
|
3174
|
+
"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 bundler'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.",
|
|
3183
3175
|
'Use as few tools as possible, but do not guess. If you cant find the answer, say "unknown"',
|
|
3184
3176
|
"When done, call reportStatus"
|
|
3185
3177
|
],
|
|
@@ -3275,7 +3267,7 @@ async function runAnalysis(mode, extraInstructions = []) {
|
|
|
3275
3267
|
// package.json
|
|
3276
3268
|
var package_default = {
|
|
3277
3269
|
name: "@algolia/wizard",
|
|
3278
|
-
version: "0.
|
|
3270
|
+
version: "0.37.0",
|
|
3279
3271
|
description: "Magically implement Algolia functionality in your codebase",
|
|
3280
3272
|
type: "module",
|
|
3281
3273
|
engines: {
|
|
@@ -3299,6 +3291,7 @@ var package_default = {
|
|
|
3299
3291
|
reset: "tsx ./scripts/reset-state.ts",
|
|
3300
3292
|
"test:fixtures": "touch .env && tsx --env-file=.env ./fixtures/run-fixtures.ts",
|
|
3301
3293
|
"test:tools": "tsx ./tool-evals/toolEval.ts",
|
|
3294
|
+
"test:tools:improve": "tsx ./tool-evals/improveFromPlan.ts",
|
|
3302
3295
|
test: "vitest",
|
|
3303
3296
|
typecheck: "tsc --noEmit -p tsconfig.json"
|
|
3304
3297
|
},
|
|
@@ -3680,13 +3673,13 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
|
|
|
3680
3673
|
};
|
|
3681
3674
|
|
|
3682
3675
|
// src/actions/implement.ts
|
|
3683
|
-
import
|
|
3676
|
+
import z28 from "zod";
|
|
3684
3677
|
import { mkdir as mkdir7 } from "node:fs/promises";
|
|
3685
3678
|
import { join as join10, relative as relative6 } from "node:path";
|
|
3686
3679
|
|
|
3687
3680
|
// src/lib/git.ts
|
|
3688
3681
|
import { execFile as execFile2 } from "node:child_process";
|
|
3689
|
-
import { copyFile, mkdir as mkdir6,
|
|
3682
|
+
import { copyFile, mkdir as mkdir6, stat as stat3 } from "node:fs/promises";
|
|
3690
3683
|
import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute2, join as join8, resolve as resolve3 } from "node:path";
|
|
3691
3684
|
var MAX_BUFFER = 32 * 1024 * 1024;
|
|
3692
3685
|
function git(args) {
|
|
@@ -3740,42 +3733,6 @@ async function copyUploadIntoProject(repoRoot, ingestDir, sourcePath) {
|
|
|
3740
3733
|
}
|
|
3741
3734
|
return { ok: true, relPath };
|
|
3742
3735
|
}
|
|
3743
|
-
function hasEnvVar(content, name) {
|
|
3744
|
-
return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
|
|
3745
|
-
}
|
|
3746
|
-
async function readEnvVar(repoRoot, name) {
|
|
3747
|
-
let content;
|
|
3748
|
-
try {
|
|
3749
|
-
content = await readFile7(join8(repoRoot, ".env"), "utf8");
|
|
3750
|
-
} catch (err) {
|
|
3751
|
-
if (err.code !== "ENOENT") throw err;
|
|
3752
|
-
return void 0;
|
|
3753
|
-
}
|
|
3754
|
-
const match = new RegExp(
|
|
3755
|
-
`^[ \\t]*(?:export[ \\t]+)?${name}[ \\t]*=[ \\t]*(.*)$`,
|
|
3756
|
-
"m"
|
|
3757
|
-
).exec(content);
|
|
3758
|
-
if (!match) return void 0;
|
|
3759
|
-
const value = match[1].trim().replace(/^(['"])(.*)\1$/, "$2").trim();
|
|
3760
|
-
if (!value || value.startsWith("<")) return void 0;
|
|
3761
|
-
return value;
|
|
3762
|
-
}
|
|
3763
|
-
async function writeSearchEnvValues(repoRoot, vars) {
|
|
3764
|
-
const target = join8(repoRoot, ".env");
|
|
3765
|
-
let existing = "";
|
|
3766
|
-
try {
|
|
3767
|
-
existing = await readFile7(target, "utf8");
|
|
3768
|
-
} catch (err) {
|
|
3769
|
-
if (err.code !== "ENOENT") throw err;
|
|
3770
|
-
}
|
|
3771
|
-
const missing = vars.filter((v) => !hasEnvVar(existing, v.name));
|
|
3772
|
-
if (missing.length === 0) return [];
|
|
3773
|
-
const prefix = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
3774
|
-
const lines = missing.map(({ name, value }) => `${name}=${value}
|
|
3775
|
-
`).join("");
|
|
3776
|
-
await writeFile7(target, existing + prefix + lines, "utf8");
|
|
3777
|
-
return missing.map((v) => v.name);
|
|
3778
|
-
}
|
|
3779
3736
|
function normalizeFindingPaths(findings) {
|
|
3780
3737
|
return {
|
|
3781
3738
|
...findings,
|
|
@@ -3856,46 +3813,36 @@ function getFrameworkSpecificDoc(frameworks) {
|
|
|
3856
3813
|
return loadAlgoliaDoc("js");
|
|
3857
3814
|
}
|
|
3858
3815
|
|
|
3859
|
-
// src/actions/resolveEnvVarPrefix.ts
|
|
3860
|
-
import z28 from "zod";
|
|
3861
|
-
var resolveEnvVarPrefixSchema = z28.object({
|
|
3862
|
-
publicEnvVarPrefix: detectLanguageSchema.shape.publicEnvVarPrefix
|
|
3863
|
-
});
|
|
3864
|
-
var resolveEnvVarPrefix = (frameworkName) => runAgent({
|
|
3865
|
-
instructions: [
|
|
3866
|
-
`The developer corrected the project's framework to "${frameworkName}".`,
|
|
3867
|
-
`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).`,
|
|
3868
|
-
'Use as few tools as possible, but do not guess. If you cant find the answer, say "unknown"',
|
|
3869
|
-
"When done, call reportStatus"
|
|
3870
|
-
],
|
|
3871
|
-
tools: ["listFiles", "changeDirectory", "readFile", "searchFiles"],
|
|
3872
|
-
outputSchema: resolveEnvVarPrefixSchema,
|
|
3873
|
-
modelSize: "small"
|
|
3874
|
-
});
|
|
3875
|
-
|
|
3876
3816
|
// src/actions/implement.ts
|
|
3877
|
-
var implementSchema =
|
|
3878
|
-
summary:
|
|
3879
|
-
ingestCommand:
|
|
3880
|
-
ingestScriptRan:
|
|
3881
|
-
ingestRecordCount:
|
|
3882
|
-
ingestDurationMs:
|
|
3883
|
-
ingestionSource:
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3817
|
+
var implementSchema = z28.object({
|
|
3818
|
+
summary: z28.string(),
|
|
3819
|
+
ingestCommand: z28.string().optional(),
|
|
3820
|
+
ingestScriptRan: z28.boolean().optional(),
|
|
3821
|
+
ingestRecordCount: z28.number().optional(),
|
|
3822
|
+
ingestDurationMs: z28.number().optional(),
|
|
3823
|
+
ingestionSource: z28.enum(["local", "fileUpload", "generated"]),
|
|
3824
|
+
searchConfig: z28.object({
|
|
3825
|
+
filePath: z28.string().optional(),
|
|
3826
|
+
vars: z28.array(
|
|
3827
|
+
z28.object({
|
|
3828
|
+
name: z28.string(),
|
|
3829
|
+
value: z28.string()
|
|
3830
|
+
})
|
|
3831
|
+
)
|
|
3832
|
+
}).optional()
|
|
3890
3833
|
});
|
|
3891
|
-
var implementationOutputSchema =
|
|
3892
|
-
summary:
|
|
3893
|
-
ingestCommand:
|
|
3834
|
+
var implementationOutputSchema = z28.object({
|
|
3835
|
+
summary: z28.string(),
|
|
3836
|
+
ingestCommand: z28.string().optional(),
|
|
3837
|
+
// Only for the search use case: the path of whatever module the agent
|
|
3838
|
+
// defined the Algolia config constants in, so the wizard can check it
|
|
3839
|
+
// won't end up gitignored (it's public, meant to be committed).
|
|
3840
|
+
searchConfigFile: z28.string().optional()
|
|
3894
3841
|
});
|
|
3895
|
-
var verificationOutputSchema =
|
|
3896
|
-
summary:
|
|
3897
|
-
sufficient:
|
|
3898
|
-
additionalInstructions:
|
|
3842
|
+
var verificationOutputSchema = z28.object({
|
|
3843
|
+
summary: z28.string(),
|
|
3844
|
+
sufficient: z28.boolean(),
|
|
3845
|
+
additionalInstructions: z28.string().optional()
|
|
3899
3846
|
});
|
|
3900
3847
|
var MAX_IMPLEMENT_VERIFICATION_ATTEMPTS = 3;
|
|
3901
3848
|
var DEFAULT_IMPLEMENT_USE_CASES = ["ingestion", "search"];
|
|
@@ -3909,6 +3856,10 @@ function isJsProject(language) {
|
|
|
3909
3856
|
(name) => JS_LANGUAGES.some((js) => name.includes(js))
|
|
3910
3857
|
);
|
|
3911
3858
|
}
|
|
3859
|
+
var SEARCH_CONFIG_APP_ID = "ALGOLIA_APP_ID";
|
|
3860
|
+
var SEARCH_CONFIG_SEARCH_KEY = "ALGOLIA_SEARCH_API_KEY";
|
|
3861
|
+
var SEARCH_CONFIG_INDEX_NAME = "ALGOLIA_INDEX_NAME";
|
|
3862
|
+
var SEARCH_KEY_PLACEHOLDER = "<your-algolia-search-only-api-key>";
|
|
3912
3863
|
var UI_FRAMEWORKS = [
|
|
3913
3864
|
{ match: ["vue", "nuxt"], target: "Vue", doc: "vue" },
|
|
3914
3865
|
{ match: ["react", "next"], target: "React", doc: "react" },
|
|
@@ -3979,6 +3930,7 @@ function ingestionInstructions(input) {
|
|
|
3979
3930
|
"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.",
|
|
3980
3931
|
...algoliaClientDoc(input),
|
|
3981
3932
|
"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.",
|
|
3933
|
+
`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.`,
|
|
3982
3934
|
"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.",
|
|
3983
3935
|
'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.',
|
|
3984
3936
|
"The summary should be extremely concise.",
|
|
@@ -4000,17 +3952,14 @@ function searchInstructions(input) {
|
|
|
4000
3952
|
`Create the search experience as its own component in a new file, following the project's existing component conventions (location, naming, styling approach). Do not write it inline into an existing file.`,
|
|
4001
3953
|
`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.`,
|
|
4002
3954
|
"If a search box already exists, replace its usage with an import and render of your new component; remove the old implementation.",
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
`Add Algolia App ID "${input.appId}"; leave the search-only key as a placeholder. Do not create or edit .env \u2014 the wizard writes the resolved key there itself.`,
|
|
4009
|
-
// The wizard writes these exact names into .env right after this step.
|
|
4010
|
-
`Use exactly these env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
|
|
3955
|
+
"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.",
|
|
3956
|
+
`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).`,
|
|
3957
|
+
`Set ${SEARCH_CONFIG_APP_ID} to "${input.appId}" and ${SEARCH_CONFIG_INDEX_NAME} to "${input.targetIndex}".`,
|
|
3958
|
+
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.`,
|
|
3959
|
+
'Report the repo-relative path of that module as "searchConfigFile" in your final status.',
|
|
4011
3960
|
"Install any Algolia packages you import with the project's own package manager via runShell, and declare them in the project's dependency manifest.",
|
|
4012
3961
|
"Match the styles of the application as closely as possible.",
|
|
4013
|
-
"The summary should be extremely concise; do not mention
|
|
3962
|
+
"The summary should be extremely concise; do not mention manual testing steps."
|
|
4014
3963
|
];
|
|
4015
3964
|
}
|
|
4016
3965
|
function verificationInstructions(input) {
|
|
@@ -4141,21 +4090,6 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
|
|
|
4141
4090
|
languages: ctx.getStepOutput("confirm-language")?.languages ?? scan.languages,
|
|
4142
4091
|
frameworks: ctx.getStepOutput("confirm-framework")?.frameworks ?? scan.frameworks
|
|
4143
4092
|
};
|
|
4144
|
-
const normalizeFrameworkName = (name) => name.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
4145
|
-
const confirmedPrimaryFramework = language.frameworks[0]?.name;
|
|
4146
|
-
const frameworkWasCorrected = confirmedPrimaryFramework !== void 0 && !scan.frameworks.some(
|
|
4147
|
-
(fw) => normalizeFrameworkName(fw.name) === normalizeFrameworkName(confirmedPrimaryFramework)
|
|
4148
|
-
);
|
|
4149
|
-
const publicEnvVarPrefixPromise = frameworkWasCorrected ? resolveEnvVarPrefix(confirmedPrimaryFramework).then(
|
|
4150
|
-
(r) => r.publicEnvVarPrefix,
|
|
4151
|
-
(err) => {
|
|
4152
|
-
logger.warn(
|
|
4153
|
-
{ err, framework: confirmedPrimaryFramework },
|
|
4154
|
-
"implement: could not re-resolve publicEnvVarPrefix after a framework correction; using the stale scan value"
|
|
4155
|
-
);
|
|
4156
|
-
return scan.publicEnvVarPrefix;
|
|
4157
|
-
}
|
|
4158
|
-
) : Promise.resolve(scan.publicEnvVarPrefix);
|
|
4159
4093
|
const selected = ctx.getStepOutput(
|
|
4160
4094
|
"select-index"
|
|
4161
4095
|
);
|
|
@@ -4239,49 +4173,42 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
|
|
|
4239
4173
|
);
|
|
4240
4174
|
}
|
|
4241
4175
|
}
|
|
4242
|
-
const
|
|
4176
|
+
const summaries = [];
|
|
4177
|
+
if (uploadWarning) summaries.push(uploadWarning);
|
|
4178
|
+
let searchKey;
|
|
4179
|
+
let searchKeyError;
|
|
4180
|
+
if (useCases.includes("search") && appId) {
|
|
4181
|
+
try {
|
|
4182
|
+
const resolved2 = await resolveSearchOnlyKey(targetIndex, appId);
|
|
4183
|
+
searchKey = resolved2.key;
|
|
4184
|
+
summaries.push(
|
|
4185
|
+
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}.`
|
|
4186
|
+
);
|
|
4187
|
+
} catch (err) {
|
|
4188
|
+
searchKeyError = err.message;
|
|
4189
|
+
summaries.push(
|
|
4190
|
+
`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.`
|
|
4191
|
+
);
|
|
4192
|
+
logger.warn(
|
|
4193
|
+
{ err: searchKeyError },
|
|
4194
|
+
"implement: could not provision a search-only API key; the agent will scaffold a placeholder"
|
|
4195
|
+
);
|
|
4196
|
+
}
|
|
4197
|
+
}
|
|
4243
4198
|
const input = {
|
|
4244
4199
|
findings: normalized,
|
|
4245
4200
|
confirmed: confirmed2,
|
|
4246
4201
|
searchLocation,
|
|
4247
4202
|
targetIndex,
|
|
4248
4203
|
language,
|
|
4249
|
-
publicEnvVarPrefix,
|
|
4250
4204
|
appId,
|
|
4251
|
-
|
|
4205
|
+
searchKey,
|
|
4206
|
+
searchKeyError,
|
|
4252
4207
|
ingestDir: INGEST_DIR,
|
|
4253
4208
|
ingestionSource,
|
|
4254
4209
|
uploadFilePath,
|
|
4255
4210
|
searchUiTarget: searchUiTarget(language)
|
|
4256
4211
|
};
|
|
4257
|
-
const summaries = [];
|
|
4258
|
-
if (uploadWarning) summaries.push(uploadWarning);
|
|
4259
|
-
let envSearchKey;
|
|
4260
|
-
let envAppIdMismatch = false;
|
|
4261
|
-
if (useCases.includes("search") && appId) {
|
|
4262
|
-
const envAppId = await readEnvVar(
|
|
4263
|
-
repoRoot,
|
|
4264
|
-
publicAppIdVar(publicEnvVarPrefix)
|
|
4265
|
-
);
|
|
4266
|
-
if (envAppId === appId) {
|
|
4267
|
-
envSearchKey = await readEnvVar(
|
|
4268
|
-
repoRoot,
|
|
4269
|
-
publicSearchKeyVar(publicEnvVarPrefix)
|
|
4270
|
-
);
|
|
4271
|
-
} else if (envAppId) {
|
|
4272
|
-
envAppIdMismatch = true;
|
|
4273
|
-
const appIdVarName = publicAppIdVar(publicEnvVarPrefix);
|
|
4274
|
-
const searchKeyVarName = publicSearchKeyVar(publicEnvVarPrefix);
|
|
4275
|
-
summaries.push(
|
|
4276
|
-
`\u26A0\uFE0F .env already sets ${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.`
|
|
4277
|
-
);
|
|
4278
|
-
logger.warn(
|
|
4279
|
-
{ envAppId, appId },
|
|
4280
|
-
"implement: .env holds credentials for a different Algolia application; not reusing its search key"
|
|
4281
|
-
);
|
|
4282
|
-
}
|
|
4283
|
-
}
|
|
4284
|
-
let finalSearchEnvVars = input.searchEnvVars;
|
|
4285
4212
|
let agentRuns = 0;
|
|
4286
4213
|
let ingestCommand;
|
|
4287
4214
|
let ingestScriptRan = false;
|
|
@@ -4391,6 +4318,7 @@ ${detail}` : ""}`
|
|
|
4391
4318
|
]
|
|
4392
4319
|
});
|
|
4393
4320
|
}
|
|
4321
|
+
let searchConfigFile;
|
|
4394
4322
|
if (useCases.includes("search")) {
|
|
4395
4323
|
let extraInstructions = [];
|
|
4396
4324
|
useWizard.getState().clearWrittenFiles();
|
|
@@ -4405,11 +4333,14 @@ ${detail}` : ""}`
|
|
|
4405
4333
|
"implement: retrying search implementation after failed verification"
|
|
4406
4334
|
);
|
|
4407
4335
|
}
|
|
4408
|
-
const
|
|
4336
|
+
const searchResult = await runImplementationUseCase(
|
|
4409
4337
|
"search",
|
|
4410
4338
|
extraInstructions
|
|
4411
4339
|
);
|
|
4412
|
-
summaries.push(formatSummary("search", summary));
|
|
4340
|
+
summaries.push(formatSummary("search", searchResult.summary));
|
|
4341
|
+
if (searchResult.searchConfigFile) {
|
|
4342
|
+
searchConfigFile = searchResult.searchConfigFile;
|
|
4343
|
+
}
|
|
4413
4344
|
const verification = await runVerificationUseCase();
|
|
4414
4345
|
summaries.push(formatSummary("verification", verification.summary));
|
|
4415
4346
|
if (verification.sufficient) {
|
|
@@ -4433,76 +4364,17 @@ ${detail}` : ""}`
|
|
|
4433
4364
|
}
|
|
4434
4365
|
extraInstructions = verificationRetryInstructions(verification);
|
|
4435
4366
|
}
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
if (appId) {
|
|
4439
|
-
try {
|
|
4440
|
-
const resolved2 = await resolveSearchOnlyKey(
|
|
4441
|
-
targetIndex,
|
|
4442
|
-
appId,
|
|
4443
|
-
envSearchKey
|
|
4444
|
-
);
|
|
4445
|
-
searchKey = resolved2.key;
|
|
4446
|
-
summaries.push(
|
|
4447
|
-
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}.`
|
|
4448
|
-
);
|
|
4449
|
-
} catch (err) {
|
|
4450
|
-
searchKeyError = err.message;
|
|
4451
|
-
logger.warn(
|
|
4452
|
-
{ err: searchKeyError },
|
|
4453
|
-
"implement: could not provision a search-only API key; the .env value stays a placeholder"
|
|
4454
|
-
);
|
|
4455
|
-
}
|
|
4456
|
-
}
|
|
4457
|
-
finalSearchEnvVars = publicSearchEnvVars(
|
|
4458
|
-
publicEnvVarPrefix,
|
|
4459
|
-
targetIndex,
|
|
4460
|
-
appId,
|
|
4461
|
-
searchKey
|
|
4462
|
-
);
|
|
4463
|
-
const resolvedSearchEnvVars = finalSearchEnvVars.filter(
|
|
4464
|
-
(v) => !v.value.startsWith("<")
|
|
4465
|
-
);
|
|
4466
|
-
if (resolvedSearchEnvVars.length > 0) {
|
|
4467
|
-
const written = await writeSearchEnvValues(
|
|
4367
|
+
if (searchConfigFile) {
|
|
4368
|
+
const ignoreStatus = await gitIgnoreStatus(
|
|
4468
4369
|
repoRoot,
|
|
4469
|
-
|
|
4370
|
+
join10(repoRoot, searchConfigFile)
|
|
4470
4371
|
);
|
|
4471
|
-
if (
|
|
4472
|
-
summaries.push(`Wrote ${written.join(", ")} to .env.`);
|
|
4473
|
-
}
|
|
4474
|
-
const ignored = await ensureGitIgnored(repoRoot, join10(repoRoot, ".env"));
|
|
4475
|
-
if (ignored === "added") {
|
|
4476
|
-
summaries.push("Added .env to .gitignore.");
|
|
4477
|
-
} else if (ignored === "tracked") {
|
|
4478
|
-
summaries.push(
|
|
4479
|
-
'\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.'
|
|
4480
|
-
);
|
|
4481
|
-
}
|
|
4482
|
-
const stale = [];
|
|
4483
|
-
for (const v of resolvedSearchEnvVars) {
|
|
4484
|
-
if (written.includes(v.name)) continue;
|
|
4485
|
-
const current = await readEnvVar(repoRoot, v.name);
|
|
4486
|
-
if (current && current !== v.value) stale.push(v);
|
|
4487
|
-
}
|
|
4488
|
-
if (stale.length > 0 && !envAppIdMismatch) {
|
|
4372
|
+
if (ignoreStatus === "covered") {
|
|
4489
4373
|
summaries.push(
|
|
4490
|
-
`\u26A0\uFE0F
|
|
4491
|
-
);
|
|
4492
|
-
logger.warn(
|
|
4493
|
-
{ vars: stale.map((v) => v.name) },
|
|
4494
|
-
"implement: .env holds different values for the resolved search credentials; not overwriting them"
|
|
4374
|
+
`\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.`
|
|
4495
4375
|
);
|
|
4496
4376
|
}
|
|
4497
4377
|
}
|
|
4498
|
-
const unresolvedSearchEnvVars = finalSearchEnvVars.filter(
|
|
4499
|
-
(v) => v.value.startsWith("<")
|
|
4500
|
-
);
|
|
4501
|
-
if (unresolvedSearchEnvVars.length > 0) {
|
|
4502
|
-
summaries.push(
|
|
4503
|
-
`Could not resolve a value for ${unresolvedSearchEnvVars.map((v) => v.name).join(", ")} \u2014 fill it in manually in .env.` + (searchKeyError ? ` Reason: ${searchKeyError}` : "")
|
|
4504
|
-
);
|
|
4505
|
-
}
|
|
4506
4378
|
} else {
|
|
4507
4379
|
ctx.setUserInput("implementation", "success");
|
|
4508
4380
|
}
|
|
@@ -4515,7 +4387,19 @@ ${detail}` : ""}`
|
|
|
4515
4387
|
...ingestRecordCount != null ? { ingestRecordCount } : {},
|
|
4516
4388
|
...ingestDurationMs != null ? { ingestDurationMs } : {}
|
|
4517
4389
|
} : {},
|
|
4518
|
-
...useCases.includes("search") ? {
|
|
4390
|
+
...useCases.includes("search") ? {
|
|
4391
|
+
searchConfig: {
|
|
4392
|
+
filePath: searchConfigFile,
|
|
4393
|
+
vars: [
|
|
4394
|
+
{ name: SEARCH_CONFIG_APP_ID, value: appId ?? "" },
|
|
4395
|
+
{
|
|
4396
|
+
name: SEARCH_CONFIG_SEARCH_KEY,
|
|
4397
|
+
value: searchKey ?? SEARCH_KEY_PLACEHOLDER
|
|
4398
|
+
},
|
|
4399
|
+
{ name: SEARCH_CONFIG_INDEX_NAME, value: targetIndex }
|
|
4400
|
+
]
|
|
4401
|
+
}
|
|
4402
|
+
} : {}
|
|
4519
4403
|
};
|
|
4520
4404
|
}
|
|
4521
4405
|
|
|
@@ -4561,8 +4445,8 @@ var defaultWorkflow = {
|
|
|
4561
4445
|
defineStep({
|
|
4562
4446
|
id: "select-index",
|
|
4563
4447
|
title: "Set up index",
|
|
4564
|
-
outputSchema:
|
|
4565
|
-
selection:
|
|
4448
|
+
outputSchema: z29.object({
|
|
4449
|
+
selection: z29.string()
|
|
4566
4450
|
}),
|
|
4567
4451
|
run: (ctx) => selectIndexStep(ctx)
|
|
4568
4452
|
}),
|
|
@@ -5622,7 +5506,7 @@ function App() {
|
|
|
5622
5506
|
}
|
|
5623
5507
|
|
|
5624
5508
|
// src/lib/envAppId.ts
|
|
5625
|
-
import { readFile as
|
|
5509
|
+
import { readFile as readFile7 } from "node:fs/promises";
|
|
5626
5510
|
import { join as join12 } from "node:path";
|
|
5627
5511
|
var ENV_FILES = [".env", ".env.local"];
|
|
5628
5512
|
var APP_ID_LINE = /^[ \t]*(?:export[ \t]+)?([A-Z0-9_]*ALGOLIA_APP(?:LICATION)?_ID)[ \t]*=[ \t]*(.*)$/gm;
|
|
@@ -5630,7 +5514,7 @@ async function findEnvApplicationId(root = process.cwd()) {
|
|
|
5630
5514
|
for (const file of ENV_FILES) {
|
|
5631
5515
|
let content;
|
|
5632
5516
|
try {
|
|
5633
|
-
content = await
|
|
5517
|
+
content = await readFile7(join12(root, file), "utf8");
|
|
5634
5518
|
} catch (err) {
|
|
5635
5519
|
if (err.code !== "ENOENT") {
|
|
5636
5520
|
logger.warn(
|
|
@@ -5799,10 +5683,14 @@ var confirmFramework2 = {
|
|
|
5799
5683
|
var search = {
|
|
5800
5684
|
summary: "Added an InstantSearch-powered search box and results list, mounted in the shared header component.",
|
|
5801
5685
|
ingestionSource: "generated",
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
5686
|
+
searchConfig: {
|
|
5687
|
+
filePath: "src/algolia.config.ts",
|
|
5688
|
+
vars: [
|
|
5689
|
+
{ name: "ALGOLIA_APP_ID", value: "SEEDAPPID" },
|
|
5690
|
+
{ name: "ALGOLIA_SEARCH_API_KEY", value: "seedsearchkey" },
|
|
5691
|
+
{ name: "ALGOLIA_INDEX_NAME", value: "wizard_seed_products" }
|
|
5692
|
+
]
|
|
5693
|
+
}
|
|
5806
5694
|
};
|
|
5807
5695
|
var review = {
|
|
5808
5696
|
summaryPoints: [
|
|
@@ -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(
|
|
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(
|
|
33
|
+
const searchClient = algoliasearch(ALGOLIA_APP_ID, ALGOLIA_SEARCH_API_KEY)
|
|
24
34
|
|
|
25
35
|
const search = instantsearch({
|
|
26
|
-
indexName:
|
|
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:
|
|
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(
|
|
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
|
-
|
|
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=
|
|
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(
|
|
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="
|
|
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(
|
|
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:
|
|
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:
|
|
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.
|
|
3
|
+
"version": "0.37.0",
|
|
4
4
|
"description": "Magically implement Algolia functionality in your codebase",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"reset": "tsx ./scripts/reset-state.ts",
|
|
25
25
|
"test:fixtures": "touch .env && tsx --env-file=.env ./fixtures/run-fixtures.ts",
|
|
26
26
|
"test:tools": "tsx ./tool-evals/toolEval.ts",
|
|
27
|
+
"test:tools:improve": "tsx ./tool-evals/improveFromPlan.ts",
|
|
27
28
|
"test": "vitest",
|
|
28
29
|
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
29
30
|
},
|