@algolia/wizard 0.115.0 → 0.117.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 +687 -135
- package/docs/algolia-sdk/README.md +3 -4
- package/docs/algolia-sdk/instantsearch-setup-js.md +4 -4
- package/docs/algolia-sdk/instantsearch-setup-react.md +4 -4
- package/docs/algolia-sdk/instantsearch-setup-vue.md +4 -4
- package/docs/algolia-sdk/search-single-index.md +4 -4
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -1245,7 +1245,7 @@ function SelectPrompt({
|
|
|
1245
1245
|
helpText,
|
|
1246
1246
|
messages,
|
|
1247
1247
|
table,
|
|
1248
|
-
list,
|
|
1248
|
+
list: list2,
|
|
1249
1249
|
error,
|
|
1250
1250
|
multi,
|
|
1251
1251
|
cancelable,
|
|
@@ -1334,7 +1334,7 @@ function SelectPrompt({
|
|
|
1334
1334
|
error && /* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: error }),
|
|
1335
1335
|
messages?.map((m, i) => /* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: m }, `msg-${i}`)),
|
|
1336
1336
|
table && /* @__PURE__ */ jsx8(Table, { columns: table.columns, rows: table.rows }),
|
|
1337
|
-
|
|
1337
|
+
list2 && list2.length > 0 && /* @__PURE__ */ jsx8(Box9, { flexDirection: "column", children: list2.map((item, i) => /* @__PURE__ */ jsxs9(Text10, { children: [
|
|
1338
1338
|
/* @__PURE__ */ jsx8(
|
|
1339
1339
|
Text10,
|
|
1340
1340
|
{
|
|
@@ -1566,7 +1566,7 @@ function PromptInput() {
|
|
|
1566
1566
|
}
|
|
1567
1567
|
|
|
1568
1568
|
// src/workflows/default.ts
|
|
1569
|
-
import { z as
|
|
1569
|
+
import { z as z31 } from "zod";
|
|
1570
1570
|
|
|
1571
1571
|
// src/core/orchestrator.ts
|
|
1572
1572
|
import "zod";
|
|
@@ -2504,7 +2504,7 @@ async function ensureApplication(resumeAppId) {
|
|
|
2504
2504
|
// package.json
|
|
2505
2505
|
var package_default = {
|
|
2506
2506
|
name: "@algolia/wizard",
|
|
2507
|
-
version: "0.
|
|
2507
|
+
version: "0.117.0",
|
|
2508
2508
|
description: "Magically implement Algolia functionality in your codebase",
|
|
2509
2509
|
type: "module",
|
|
2510
2510
|
engines: {
|
|
@@ -6421,9 +6421,9 @@ Output:
|
|
|
6421
6421
|
${JSON.stringify(s.output, null, 2)}`
|
|
6422
6422
|
).join("\n\n");
|
|
6423
6423
|
}
|
|
6424
|
-
async function runReviewAgent(
|
|
6424
|
+
async function runReviewAgent(request2) {
|
|
6425
6425
|
try {
|
|
6426
|
-
return await runAgent(
|
|
6426
|
+
return await runAgent(request2);
|
|
6427
6427
|
} catch (err) {
|
|
6428
6428
|
if (!(err instanceof AgentReportedFailureError)) throw err;
|
|
6429
6429
|
const parsed = reviewSchema.safeParse(err.output);
|
|
@@ -6461,7 +6461,7 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
|
|
|
6461
6461
|
};
|
|
6462
6462
|
|
|
6463
6463
|
// src/actions/implement.ts
|
|
6464
|
-
import
|
|
6464
|
+
import z30 from "zod";
|
|
6465
6465
|
import { access, mkdir as mkdir5, readdir as readdir3, readFile as readFile10 } from "node:fs/promises";
|
|
6466
6466
|
import {
|
|
6467
6467
|
basename as basename6,
|
|
@@ -6564,6 +6564,271 @@ function copyToClipboard(text, stream = process.stdout) {
|
|
|
6564
6564
|
stream.write(`\x1B]52;c;${encoded}\x07`);
|
|
6565
6565
|
}
|
|
6566
6566
|
|
|
6567
|
+
// src/lib/publicSearchSettings.ts
|
|
6568
|
+
var SETTINGS_TIMEOUT_MS = 15e3;
|
|
6569
|
+
var WRAPPER = /^(unordered|searchable|filterOnly|afterDistinct)\((.*)\)$/;
|
|
6570
|
+
function attributeKey(value) {
|
|
6571
|
+
return attributeKeys(value)[0] ?? "";
|
|
6572
|
+
}
|
|
6573
|
+
function attributeKeys(value) {
|
|
6574
|
+
const wrapped = WRAPPER.exec(value.trim());
|
|
6575
|
+
const inner = (wrapped?.[2] ?? value).trim();
|
|
6576
|
+
return inner.split(",").map((part) => part.trim().split(":")[0]?.trim()).filter((part) => Boolean(part));
|
|
6577
|
+
}
|
|
6578
|
+
function settingsThatHideAttributes(current, hide, searchableFallback) {
|
|
6579
|
+
if (hide.length === 0) return null;
|
|
6580
|
+
const hidden = unique(hide);
|
|
6581
|
+
const safeSearchableFallback = filterSettingsValues(
|
|
6582
|
+
searchableFallback,
|
|
6583
|
+
hidden
|
|
6584
|
+
);
|
|
6585
|
+
const drop = (value) => hasHiddenAttribute(value, hidden);
|
|
6586
|
+
const patch = {
|
|
6587
|
+
unretrievableAttributes: unique([
|
|
6588
|
+
...(current.unretrievableAttributes ?? []).filter((value) => !drop(value)),
|
|
6589
|
+
...hide
|
|
6590
|
+
])
|
|
6591
|
+
};
|
|
6592
|
+
const currentSearchable = list(current.searchableAttributes);
|
|
6593
|
+
const filteredCurrent = currentSearchable ? filterSettingsValues(currentSearchable, hidden) : void 0;
|
|
6594
|
+
patch.searchableAttributes = filteredCurrent && filteredCurrent.length > 0 ? filteredCurrent : safeSearchableFallback;
|
|
6595
|
+
if (patch.searchableAttributes.length === 0) {
|
|
6596
|
+
throw new Error(
|
|
6597
|
+
`Cannot make public search safe: no searchable attributes remain after hiding ${hidden.join(", ")}.`
|
|
6598
|
+
);
|
|
6599
|
+
}
|
|
6600
|
+
const facets = list(current.attributesForFaceting);
|
|
6601
|
+
if (facets) patch.attributesForFaceting = filterSettingsValues(facets, hidden);
|
|
6602
|
+
const highlight = list(current.attributesToHighlight);
|
|
6603
|
+
if (highlight) {
|
|
6604
|
+
patch.attributesToHighlight = filterSettingsValues(highlight, hidden);
|
|
6605
|
+
}
|
|
6606
|
+
const snippet = list(current.attributesToSnippet);
|
|
6607
|
+
if (snippet) {
|
|
6608
|
+
patch.attributesToSnippet = filterSettingsValues(snippet, hidden);
|
|
6609
|
+
}
|
|
6610
|
+
return patch;
|
|
6611
|
+
}
|
|
6612
|
+
async function hideAttributesFromPublicSearch(args) {
|
|
6613
|
+
const patch = settingsThatHideAttributes(
|
|
6614
|
+
await readSettings(args),
|
|
6615
|
+
args.hide,
|
|
6616
|
+
args.searchableFallback
|
|
6617
|
+
);
|
|
6618
|
+
if (!patch) return;
|
|
6619
|
+
await writeSettings(args, patch);
|
|
6620
|
+
}
|
|
6621
|
+
async function readSettings(args) {
|
|
6622
|
+
const response = await request(args, "GET");
|
|
6623
|
+
if (response.status === 404 && args.createIfMissing) return {};
|
|
6624
|
+
assertOk(response, args.apiKey);
|
|
6625
|
+
const body = await response.json();
|
|
6626
|
+
if (!body || typeof body !== "object") return {};
|
|
6627
|
+
const record = body;
|
|
6628
|
+
return {
|
|
6629
|
+
searchableAttributes: record.searchableAttributes,
|
|
6630
|
+
attributesForFaceting: record.attributesForFaceting,
|
|
6631
|
+
unretrievableAttributes: record.unretrievableAttributes,
|
|
6632
|
+
attributesToHighlight: record.attributesToHighlight,
|
|
6633
|
+
attributesToSnippet: record.attributesToSnippet
|
|
6634
|
+
};
|
|
6635
|
+
}
|
|
6636
|
+
async function writeSettings(args, patch) {
|
|
6637
|
+
const response = await request(args, "PUT", patch);
|
|
6638
|
+
assertOk(response, args.apiKey);
|
|
6639
|
+
}
|
|
6640
|
+
async function request(args, method, body) {
|
|
6641
|
+
const fetchImpl = args.fetch ?? fetch;
|
|
6642
|
+
try {
|
|
6643
|
+
return await fetchImpl(settingsUrl(args.appId, args.indexName), {
|
|
6644
|
+
method,
|
|
6645
|
+
headers: {
|
|
6646
|
+
"X-Algolia-Application-Id": args.appId,
|
|
6647
|
+
"X-Algolia-API-Key": args.apiKey,
|
|
6648
|
+
"Content-Type": "application/json"
|
|
6649
|
+
},
|
|
6650
|
+
...body ? { body: JSON.stringify(body) } : {},
|
|
6651
|
+
signal: AbortSignal.timeout(SETTINGS_TIMEOUT_MS)
|
|
6652
|
+
});
|
|
6653
|
+
} catch (err) {
|
|
6654
|
+
throw settingsError(err.message, args.apiKey);
|
|
6655
|
+
}
|
|
6656
|
+
}
|
|
6657
|
+
function assertOk(response, apiKey) {
|
|
6658
|
+
if (!response.ok) throw settingsError(String(response.status), apiKey);
|
|
6659
|
+
}
|
|
6660
|
+
function settingsError(detail, apiKey) {
|
|
6661
|
+
return new Error(
|
|
6662
|
+
`Algolia index settings request failed (${detail.replaceAll(apiKey, "***")})`
|
|
6663
|
+
);
|
|
6664
|
+
}
|
|
6665
|
+
function settingsUrl(appId, indexName) {
|
|
6666
|
+
return `https://${appId}.algolia.net/1/indexes/${encodeURIComponent(indexName)}/settings`;
|
|
6667
|
+
}
|
|
6668
|
+
function list(value) {
|
|
6669
|
+
if (!value || value.length === 0) return void 0;
|
|
6670
|
+
return value;
|
|
6671
|
+
}
|
|
6672
|
+
function unique(values) {
|
|
6673
|
+
return [...new Set(values)];
|
|
6674
|
+
}
|
|
6675
|
+
function filterSettingsValues(values, hidden) {
|
|
6676
|
+
return values.map((value) => filterSettingsValue(value, hidden)).filter((value) => value !== void 0);
|
|
6677
|
+
}
|
|
6678
|
+
function filterSettingsValue(value, hidden) {
|
|
6679
|
+
const trimmed = value.trim();
|
|
6680
|
+
const wrapped = WRAPPER.exec(trimmed);
|
|
6681
|
+
const wrapper = wrapped?.[1];
|
|
6682
|
+
const inner = (wrapped?.[2] ?? trimmed).trim();
|
|
6683
|
+
const parts = inner.split(",").map((part) => part.trim()).filter(Boolean);
|
|
6684
|
+
const kept = parts.filter(
|
|
6685
|
+
(part) => !attributeIsHidden(attributeKey(part), hidden)
|
|
6686
|
+
);
|
|
6687
|
+
if (kept.length === 0) return void 0;
|
|
6688
|
+
if (kept.length === parts.length) return value;
|
|
6689
|
+
return wrapper ? `${wrapper}(${kept.join(",")})` : kept.join(",");
|
|
6690
|
+
}
|
|
6691
|
+
function hasHiddenAttribute(value, hidden) {
|
|
6692
|
+
return attributeKeys(value).some((key) => attributeIsHidden(key, hidden));
|
|
6693
|
+
}
|
|
6694
|
+
function attributeIsHidden(key, hidden) {
|
|
6695
|
+
return hidden.some(
|
|
6696
|
+
(hiddenKey) => key === hiddenKey || key.startsWith(`${hiddenKey}.`) || hiddenKey.startsWith(`${key}.`)
|
|
6697
|
+
);
|
|
6698
|
+
}
|
|
6699
|
+
|
|
6700
|
+
// src/lib/sensitiveAttributes.ts
|
|
6701
|
+
import { z as z29 } from "zod";
|
|
6702
|
+
var TOKEN_SPLIT = /[^a-z0-9]+/;
|
|
6703
|
+
var recordExposureSchema = z29.object({
|
|
6704
|
+
omittedAttributes: z29.array(z29.string()),
|
|
6705
|
+
hiddenAttributes: z29.array(z29.string()),
|
|
6706
|
+
publicSearchSafe: z29.boolean()
|
|
6707
|
+
});
|
|
6708
|
+
function publicSearchKeyIsSafe(exposure) {
|
|
6709
|
+
return exposure?.publicSearchSafe === true;
|
|
6710
|
+
}
|
|
6711
|
+
function classifyAttributes(names) {
|
|
6712
|
+
const blocked = [];
|
|
6713
|
+
const personal = [];
|
|
6714
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6715
|
+
for (const name of names) {
|
|
6716
|
+
if (seen.has(name)) continue;
|
|
6717
|
+
seen.add(name);
|
|
6718
|
+
const kind = classifyAttribute(name);
|
|
6719
|
+
if (kind === "blocked") blocked.push(name);
|
|
6720
|
+
else if (kind === "personal") personal.push(name);
|
|
6721
|
+
}
|
|
6722
|
+
return { blocked, personal };
|
|
6723
|
+
}
|
|
6724
|
+
function withheldAttributes(exposure) {
|
|
6725
|
+
if (!exposure) return [];
|
|
6726
|
+
return [
|
|
6727
|
+
.../* @__PURE__ */ new Set([...exposure.omittedAttributes, ...exposure.hiddenAttributes])
|
|
6728
|
+
];
|
|
6729
|
+
}
|
|
6730
|
+
function visibleAttributes(attributes, exposure) {
|
|
6731
|
+
if (!exposure) return [...attributes];
|
|
6732
|
+
const drop = new Set(withheldAttributes(exposure));
|
|
6733
|
+
return attributes.filter((name) => !drop.has(name));
|
|
6734
|
+
}
|
|
6735
|
+
function ingestionExposureInstructions(exposure) {
|
|
6736
|
+
if (!exposure) return [];
|
|
6737
|
+
const lines = [];
|
|
6738
|
+
if (exposure.omittedAttributes.length > 0) {
|
|
6739
|
+
lines.push(
|
|
6740
|
+
`Do not write these attributes into records, sample data, or index settings: ${exposure.omittedAttributes.join(", ")}. Omit them even if the source has them.`
|
|
6741
|
+
);
|
|
6742
|
+
}
|
|
6743
|
+
if (exposure.hiddenAttributes.length > 0) {
|
|
6744
|
+
lines.push(
|
|
6745
|
+
`The user chose to index these personal attributes: ${exposure.hiddenAttributes.join(", ")}. Write them on the records, but do not add them to searchableAttributes, attributesForFaceting, attributesToHighlight, or attributesToSnippet. Wizard applies the public-search settings that hide them from browser search.`
|
|
6746
|
+
);
|
|
6747
|
+
}
|
|
6748
|
+
return lines;
|
|
6749
|
+
}
|
|
6750
|
+
function searchExposureInstructions(exposure) {
|
|
6751
|
+
const withheld = withheldAttributes(exposure);
|
|
6752
|
+
if (!exposure || withheld.length === 0) return [];
|
|
6753
|
+
const lines = [
|
|
6754
|
+
`Do not display, highlight, snippet, filter, or facet these attributes, even if a source file mentions them: ${withheld.join(", ")}.`
|
|
6755
|
+
];
|
|
6756
|
+
if (exposure.hiddenAttributes.length > 0) {
|
|
6757
|
+
const names = exposure.hiddenAttributes.join(", ");
|
|
6758
|
+
lines.push(
|
|
6759
|
+
exposure.publicSearchSafe ? `These personal attributes may be stored on records, but a public hit does not include them: ${names}.` : `These personal attributes may be stored on records, and Wizard could not confirm that public search hides them: ${names}. Do not edit the ingestion script, the records, or the index settings to change that; it is outside the search step.`
|
|
6760
|
+
);
|
|
6761
|
+
}
|
|
6762
|
+
return lines;
|
|
6763
|
+
}
|
|
6764
|
+
function classifyAttribute(name) {
|
|
6765
|
+
const tokens = attributeTokens(name);
|
|
6766
|
+
if (tokens.length === 0) return null;
|
|
6767
|
+
if (isBlocked(tokens)) return "blocked";
|
|
6768
|
+
if (isPersonal(tokens)) return "personal";
|
|
6769
|
+
return null;
|
|
6770
|
+
}
|
|
6771
|
+
function attributeTokens(name) {
|
|
6772
|
+
const spaced = name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").toLowerCase();
|
|
6773
|
+
return spaced.split(TOKEN_SPLIT).filter(Boolean);
|
|
6774
|
+
}
|
|
6775
|
+
function has(tokens, ...options) {
|
|
6776
|
+
return options.some((option) => tokens.includes(option));
|
|
6777
|
+
}
|
|
6778
|
+
function hasAll(tokens, ...need) {
|
|
6779
|
+
return need.every((token) => tokens.includes(token));
|
|
6780
|
+
}
|
|
6781
|
+
function isBlocked(tokens) {
|
|
6782
|
+
if (has(tokens, "ssn", "itin", "cvv", "cvc", "passport")) return true;
|
|
6783
|
+
if (has(tokens, "password", "secret", "token", "iban")) return true;
|
|
6784
|
+
if (has(tokens, "api", "private") && has(tokens, "key", "keys")) return true;
|
|
6785
|
+
if (hasAll(tokens, "account", "number")) return true;
|
|
6786
|
+
if (hasAll(tokens, "social", "security")) return true;
|
|
6787
|
+
if (hasAll(tokens, "social", "insurance")) return true;
|
|
6788
|
+
if (tokens.length === 1 && tokens[0] === "sin") return true;
|
|
6789
|
+
if (hasAll(tokens, "sin", "number")) return true;
|
|
6790
|
+
if (hasAll(tokens, "national", "id") || hasAll(tokens, "government", "id")) {
|
|
6791
|
+
return true;
|
|
6792
|
+
}
|
|
6793
|
+
if (has(tokens, "driver", "drivers", "driving") && has(tokens, "license", "licence")) {
|
|
6794
|
+
return true;
|
|
6795
|
+
}
|
|
6796
|
+
if (hasAll(tokens, "tax", "id") || hasAll(tokens, "taxpayer", "id")) {
|
|
6797
|
+
return true;
|
|
6798
|
+
}
|
|
6799
|
+
if (has(tokens, "tin") && (tokens.length === 1 || has(tokens, "number", "tax"))) {
|
|
6800
|
+
return true;
|
|
6801
|
+
}
|
|
6802
|
+
if (hasAll(tokens, "credit", "card") || hasAll(tokens, "card", "number")) {
|
|
6803
|
+
return true;
|
|
6804
|
+
}
|
|
6805
|
+
return false;
|
|
6806
|
+
}
|
|
6807
|
+
function isPersonal(tokens) {
|
|
6808
|
+
if (has(tokens, "email", "emails")) return true;
|
|
6809
|
+
if (hasAll(tokens, "e", "mail")) return true;
|
|
6810
|
+
if (has(tokens, "phone", "phones", "telephone", "tel")) return true;
|
|
6811
|
+
if (hasAll(tokens, "mobile", "phone") || hasAll(tokens, "mobile", "number")) {
|
|
6812
|
+
return true;
|
|
6813
|
+
}
|
|
6814
|
+
if (has(tokens, "stripe") && !has(tokens, "price", "product", "plan", "sku")) {
|
|
6815
|
+
return true;
|
|
6816
|
+
}
|
|
6817
|
+
if (["address", "addresses"].includes(tokens.at(-1) ?? "") || tokens.length === 1 && tokens[0] === "zip" || has(tokens, "street", "postcode", "zipcode") || hasAll(tokens, "address", "line") || hasAll(tokens, "address", "city") || hasAll(tokens, "address", "postal")) {
|
|
6818
|
+
return true;
|
|
6819
|
+
}
|
|
6820
|
+
if (hasAll(tokens, "postal", "code") || hasAll(tokens, "zip", "code")) {
|
|
6821
|
+
return true;
|
|
6822
|
+
}
|
|
6823
|
+
if (has(tokens, "dob", "birthday", "birthdate")) return true;
|
|
6824
|
+
if (hasAll(tokens, "birth", "date")) return true;
|
|
6825
|
+
if (has(tokens, "ip", "ipaddress")) return true;
|
|
6826
|
+
if (has(tokens, "lat", "lng", "latitude", "longitude") || hasAll(tokens, "geo", "point")) {
|
|
6827
|
+
return true;
|
|
6828
|
+
}
|
|
6829
|
+
return false;
|
|
6830
|
+
}
|
|
6831
|
+
|
|
6567
6832
|
// src/lib/algoliaDocs.ts
|
|
6568
6833
|
import { readFileSync, readdirSync, existsSync } from "node:fs";
|
|
6569
6834
|
import { dirname as dirname4, join as join10 } from "node:path";
|
|
@@ -6623,51 +6888,52 @@ function getFrameworkSpecificDoc(frameworks) {
|
|
|
6623
6888
|
}
|
|
6624
6889
|
|
|
6625
6890
|
// src/actions/implement.ts
|
|
6626
|
-
var implementSchema =
|
|
6627
|
-
summary:
|
|
6628
|
-
ingestCommand:
|
|
6629
|
-
ingestScriptRan:
|
|
6630
|
-
ingestRecordCount:
|
|
6631
|
-
ingestDurationMs:
|
|
6632
|
-
ingestionSource:
|
|
6633
|
-
|
|
6634
|
-
|
|
6635
|
-
|
|
6636
|
-
|
|
6637
|
-
|
|
6638
|
-
|
|
6891
|
+
var implementSchema = z30.object({
|
|
6892
|
+
summary: z30.string(),
|
|
6893
|
+
ingestCommand: z30.string().optional(),
|
|
6894
|
+
ingestScriptRan: z30.boolean().optional(),
|
|
6895
|
+
ingestRecordCount: z30.number().optional(),
|
|
6896
|
+
ingestDurationMs: z30.number().optional(),
|
|
6897
|
+
ingestionSource: z30.enum(["local", "fileUpload", "generated"]),
|
|
6898
|
+
recordExposure: recordExposureSchema.optional(),
|
|
6899
|
+
searchConfig: z30.object({
|
|
6900
|
+
filePath: z30.string().optional(),
|
|
6901
|
+
vars: z30.array(
|
|
6902
|
+
z30.object({
|
|
6903
|
+
name: z30.string(),
|
|
6904
|
+
value: z30.string()
|
|
6639
6905
|
})
|
|
6640
6906
|
)
|
|
6641
6907
|
}).optional(),
|
|
6642
|
-
uiReport:
|
|
6643
|
-
files:
|
|
6644
|
-
|
|
6645
|
-
label:
|
|
6646
|
-
path:
|
|
6908
|
+
uiReport: z30.object({
|
|
6909
|
+
files: z30.array(
|
|
6910
|
+
z30.object({
|
|
6911
|
+
label: z30.string(),
|
|
6912
|
+
path: z30.string()
|
|
6647
6913
|
})
|
|
6648
6914
|
),
|
|
6649
|
-
checkPassed:
|
|
6650
|
-
typecheckCommand:
|
|
6651
|
-
devCommand:
|
|
6915
|
+
checkPassed: z30.boolean(),
|
|
6916
|
+
typecheckCommand: z30.string().optional(),
|
|
6917
|
+
devCommand: z30.string().optional()
|
|
6652
6918
|
}).optional()
|
|
6653
6919
|
});
|
|
6654
|
-
var implementationOutputSchema =
|
|
6655
|
-
summary:
|
|
6656
|
-
ingestCommand:
|
|
6920
|
+
var implementationOutputSchema = z30.object({
|
|
6921
|
+
summary: z30.string(),
|
|
6922
|
+
ingestCommand: z30.string().optional(),
|
|
6657
6923
|
// Only for the search use case: the path of whatever module the agent
|
|
6658
6924
|
// defined the Algolia config constants in, so the wizard can check it
|
|
6659
6925
|
// won't end up gitignored (it's public, meant to be committed).
|
|
6660
|
-
searchConfigFile:
|
|
6661
|
-
uiFiles:
|
|
6662
|
-
devCommand:
|
|
6926
|
+
searchConfigFile: z30.string().optional(),
|
|
6927
|
+
uiFiles: z30.array(z30.object({ label: z30.string(), path: z30.string() })).optional(),
|
|
6928
|
+
devCommand: z30.string().optional()
|
|
6663
6929
|
});
|
|
6664
|
-
var validationOutputSchema =
|
|
6665
|
-
summary:
|
|
6666
|
-
sufficient:
|
|
6667
|
-
additionalInstructions:
|
|
6668
|
-
unrelatedFailure:
|
|
6669
|
-
styleOnly:
|
|
6670
|
-
typecheckCommand:
|
|
6930
|
+
var validationOutputSchema = z30.object({
|
|
6931
|
+
summary: z30.string(),
|
|
6932
|
+
sufficient: z30.boolean(),
|
|
6933
|
+
additionalInstructions: z30.string().optional(),
|
|
6934
|
+
unrelatedFailure: z30.string().optional(),
|
|
6935
|
+
styleOnly: z30.boolean().optional(),
|
|
6936
|
+
typecheckCommand: z30.string().optional()
|
|
6671
6937
|
});
|
|
6672
6938
|
var MAX_IMPLEMENT_VALIDATION_ATTEMPTS = 3;
|
|
6673
6939
|
var INGESTION_MAX_STEPS = 96;
|
|
@@ -6689,6 +6955,16 @@ var INGEST_RETRY = {
|
|
|
6689
6955
|
skip: "Continue without records"
|
|
6690
6956
|
};
|
|
6691
6957
|
var INGEST_AGENT_RETRY_PROMPT = "Retry to let the wizard write and run the ingestion script again.";
|
|
6958
|
+
var PERSONAL_DATA_PROMPT = "Index this personal data?";
|
|
6959
|
+
var PERSONAL_DATA = {
|
|
6960
|
+
omit: "No, do not index personal data",
|
|
6961
|
+
ingest: "Yes, index personal data, but hide it from public search"
|
|
6962
|
+
};
|
|
6963
|
+
var HIDE_PERSONAL_DATA_RETRY_PROMPT = "Fix the cause (for example, check that the write key can edit index settings), then retry.";
|
|
6964
|
+
var HIDE_PERSONAL_DATA_RETRY = {
|
|
6965
|
+
retry: "Retry hiding personal data",
|
|
6966
|
+
skip: "Continue with a placeholder search key"
|
|
6967
|
+
};
|
|
6692
6968
|
var FILE_UPLOAD_PATH_PROMPT = "Enter the path to your JSON, CSV, or TSV file (relative to the project root, or absolute):";
|
|
6693
6969
|
var JS_LANGUAGES = ["javascript", "typescript", "jsx", "tsx", "node"];
|
|
6694
6970
|
function lower(entries) {
|
|
@@ -6703,6 +6979,171 @@ var SEARCH_CONFIG_APP_ID = "ALGOLIA_APP_ID";
|
|
|
6703
6979
|
var SEARCH_CONFIG_SEARCH_KEY = "ALGOLIA_SEARCH_API_KEY";
|
|
6704
6980
|
var SEARCH_CONFIG_INDEX_NAME = "ALGOLIA_INDEX_NAME";
|
|
6705
6981
|
var SEARCH_KEY_PLACEHOLDER = "<your-algolia-search-only-api-key>";
|
|
6982
|
+
function clientKeySentence(safe) {
|
|
6983
|
+
const placement = "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).";
|
|
6984
|
+
if (safe) {
|
|
6985
|
+
return `These are PUBLIC values, safe to commit and expose client-side: ${placement}`;
|
|
6986
|
+
}
|
|
6987
|
+
const never = `${placement[0].toUpperCase()}${placement.slice(1)}`;
|
|
6988
|
+
return `Do not describe these values as safe to commit or safe to expose. Public search can still return personal data. ${never}`;
|
|
6989
|
+
}
|
|
6990
|
+
function hiddenAttributeSummary(exposure) {
|
|
6991
|
+
if (exposure.publicSearchSafe) {
|
|
6992
|
+
if (exposure.hiddenAttributes.length === 0) return void 0;
|
|
6993
|
+
return `Public hits cannot return ${exposure.hiddenAttributes.join(", ")}, so the search key is safe to commit.`;
|
|
6994
|
+
}
|
|
6995
|
+
const names = withheldAttributes(exposure);
|
|
6996
|
+
if (names.length === 0) return void 0;
|
|
6997
|
+
return `The search key is not safe to commit until public search cannot return ${names.join(", ")}.`;
|
|
6998
|
+
}
|
|
6999
|
+
function createdSearchKeySummary(index, appId, safe) {
|
|
7000
|
+
const base = `Created a new search-only Algolia API key for the "${index}" index in app ${appId}`;
|
|
7001
|
+
return safe ? `${base} \u2014 safe to expose in frontend code.` : `${base}.`;
|
|
7002
|
+
}
|
|
7003
|
+
function exposureWarning(classified) {
|
|
7004
|
+
const lines = [];
|
|
7005
|
+
if (classified.personal.length > 0) {
|
|
7006
|
+
lines.push(
|
|
7007
|
+
`These attributes look like personal data: ${classified.personal.join(", ")}.`,
|
|
7008
|
+
"Search runs in the browser with a public key, so anything it can return is visible to anyone."
|
|
7009
|
+
);
|
|
7010
|
+
}
|
|
7011
|
+
if (classified.blocked.length > 0) {
|
|
7012
|
+
lines.push(
|
|
7013
|
+
`These attributes look like identity or payment-card numbers and will not be indexed: ${classified.blocked.join(", ")}.`
|
|
7014
|
+
);
|
|
7015
|
+
}
|
|
7016
|
+
return lines;
|
|
7017
|
+
}
|
|
7018
|
+
async function resolveRecordExposure(ctx, attributes) {
|
|
7019
|
+
const classified = classifyAttributes(attributes);
|
|
7020
|
+
if (classified.blocked.length === 0 && classified.personal.length === 0) {
|
|
7021
|
+
return {
|
|
7022
|
+
omittedAttributes: [],
|
|
7023
|
+
hiddenAttributes: [],
|
|
7024
|
+
publicSearchSafe: true
|
|
7025
|
+
};
|
|
7026
|
+
}
|
|
7027
|
+
const messages = exposureWarning(classified);
|
|
7028
|
+
if (classified.personal.length === 0) {
|
|
7029
|
+
await ctx.requestUserInput({
|
|
7030
|
+
prompt: "Continue",
|
|
7031
|
+
promptType: "notice",
|
|
7032
|
+
options: [],
|
|
7033
|
+
messages
|
|
7034
|
+
});
|
|
7035
|
+
return {
|
|
7036
|
+
omittedAttributes: classified.blocked,
|
|
7037
|
+
hiddenAttributes: [],
|
|
7038
|
+
publicSearchSafe: true
|
|
7039
|
+
};
|
|
7040
|
+
}
|
|
7041
|
+
const choice = await ctx.requestUserInput({
|
|
7042
|
+
prompt: PERSONAL_DATA_PROMPT,
|
|
7043
|
+
promptType: "multipleChoice",
|
|
7044
|
+
options: [PERSONAL_DATA.omit, PERSONAL_DATA.ingest],
|
|
7045
|
+
messages
|
|
7046
|
+
});
|
|
7047
|
+
if (choice !== PERSONAL_DATA.omit && choice !== PERSONAL_DATA.ingest) {
|
|
7048
|
+
throw new Error("User cancelled personal-data selection. Wizard halted.");
|
|
7049
|
+
}
|
|
7050
|
+
const ingestPersonal = choice === PERSONAL_DATA.ingest;
|
|
7051
|
+
return {
|
|
7052
|
+
omittedAttributes: ingestPersonal ? classified.blocked : [...classified.blocked, ...classified.personal],
|
|
7053
|
+
hiddenAttributes: ingestPersonal ? classified.personal : [],
|
|
7054
|
+
publicSearchSafe: !ingestPersonal
|
|
7055
|
+
};
|
|
7056
|
+
}
|
|
7057
|
+
async function restrictPublicSearch(exposure, args) {
|
|
7058
|
+
const hide = withheldAttributes(exposure);
|
|
7059
|
+
let applied;
|
|
7060
|
+
let failure;
|
|
7061
|
+
if (args.wroteRecords && hide.length > 0 && args.writeKey && args.appId) {
|
|
7062
|
+
try {
|
|
7063
|
+
await hideAttributesFromPublicSearch({
|
|
7064
|
+
appId: args.appId,
|
|
7065
|
+
indexName: args.indexName,
|
|
7066
|
+
apiKey: args.writeKey,
|
|
7067
|
+
hide,
|
|
7068
|
+
searchableFallback: searchableFallbackAttributes(
|
|
7069
|
+
args.attributes,
|
|
7070
|
+
exposure,
|
|
7071
|
+
args.recordSample
|
|
7072
|
+
),
|
|
7073
|
+
...args.createIndexSettings ? { createIfMissing: true } : {}
|
|
7074
|
+
});
|
|
7075
|
+
applied = true;
|
|
7076
|
+
} catch (err) {
|
|
7077
|
+
applied = false;
|
|
7078
|
+
failure = err.message;
|
|
7079
|
+
logger.warn(
|
|
7080
|
+
{ err: failure, index: args.indexName },
|
|
7081
|
+
"implement: could not hide attributes from public search"
|
|
7082
|
+
);
|
|
7083
|
+
}
|
|
7084
|
+
}
|
|
7085
|
+
if (hide.length === 0) return {};
|
|
7086
|
+
if (applied === false) {
|
|
7087
|
+
exposure.publicSearchSafe = false;
|
|
7088
|
+
const names = hide.join(", ");
|
|
7089
|
+
const reason = failure ?? "unknown error";
|
|
7090
|
+
return {
|
|
7091
|
+
note: `Could not hide ${names} from public search (${reason}). The search key is not safe to commit.`,
|
|
7092
|
+
failure: reason
|
|
7093
|
+
};
|
|
7094
|
+
}
|
|
7095
|
+
if (applied === true) exposure.publicSearchSafe = true;
|
|
7096
|
+
const note = hiddenAttributeSummary(exposure);
|
|
7097
|
+
return note ? { note } : {};
|
|
7098
|
+
}
|
|
7099
|
+
function exposedPersonalAttributes(exposure, recordsWritten) {
|
|
7100
|
+
if (!exposure || !recordsWritten || exposure.publicSearchSafe) return [];
|
|
7101
|
+
return withheldAttributes(exposure);
|
|
7102
|
+
}
|
|
7103
|
+
function withheldSearchKeySummary(names) {
|
|
7104
|
+
return `\u26A0\uFE0F Used a placeholder search key in client code because public search can still return ${names.join(", ")}. Hide those attributes from public search, then replace the placeholder with a search-only key.`;
|
|
7105
|
+
}
|
|
7106
|
+
function searchKeyInstruction(input, target) {
|
|
7107
|
+
if (input.searchKey) return `Set ${target} to "${input.searchKey}".`;
|
|
7108
|
+
if (input.searchKeyWithheldFor?.length) {
|
|
7109
|
+
return `Public search can still return ${input.searchKeyWithheldFor.join(", ")}, so the wizard withheld the real search key \u2014 set ${target} to the placeholder "${SEARCH_KEY_PLACEHOLDER}" and add a prominent TODO for the developer to fill in a search-only key once those attributes are hidden from public search. Do not edit the ingestion script, the records, or the index settings to work around this.`;
|
|
7110
|
+
}
|
|
7111
|
+
return `A real search-only key could not be provisioned${input.searchKeyError ? ` (${input.searchKeyError})` : ""} \u2014 set ${target} to the placeholder "${SEARCH_KEY_PLACEHOLDER}" and add a prominent TODO for the developer to fill in a real one.`;
|
|
7112
|
+
}
|
|
7113
|
+
function placeholderKeyValidationInstruction(input) {
|
|
7114
|
+
if (input.searchKey) return null;
|
|
7115
|
+
const reason = input.searchKeyWithheldFor?.length ? `the wizard withheld the real key because public search can still return ${input.searchKeyWithheldFor.join(", ")}` : "the wizard could not provision a real search-only key";
|
|
7116
|
+
return `${SEARCH_CONFIG_SEARCH_KEY} is intentionally the placeholder "${SEARCH_KEY_PLACEHOLDER}": ${reason}. Confirm the placeholder and a TODO to replace it are present and that no other Algolia API key is hardcoded. Do not set sufficient=false because of the placeholder, and do not ask for a real key.`;
|
|
7117
|
+
}
|
|
7118
|
+
function searchableFallbackAttributes(attributes, exposure, sample) {
|
|
7119
|
+
const visible = visibleAttributes(attributes, exposure);
|
|
7120
|
+
if (sample) {
|
|
7121
|
+
return visible.filter(
|
|
7122
|
+
(attribute) => isSearchableSampleValue(sample[attribute])
|
|
7123
|
+
);
|
|
7124
|
+
}
|
|
7125
|
+
return visible.filter((attribute) => !looksNonTextAttribute(attribute));
|
|
7126
|
+
}
|
|
7127
|
+
function isSearchableSampleValue(value) {
|
|
7128
|
+
if (typeof value === "string") return true;
|
|
7129
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
7130
|
+
}
|
|
7131
|
+
function looksNonTextAttribute(attribute) {
|
|
7132
|
+
const tokens = attribute.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
|
|
7133
|
+
if (tokens.length === 0) return true;
|
|
7134
|
+
if (tokens.includes("id") || tokens.at(-1) === "id") return true;
|
|
7135
|
+
if (tokens.some(
|
|
7136
|
+
(token) => ["count", "price", "amount", "total"].includes(token)
|
|
7137
|
+
)) {
|
|
7138
|
+
return true;
|
|
7139
|
+
}
|
|
7140
|
+
if (tokens.some(
|
|
7141
|
+
(token) => ["is", "has", "enabled", "available"].includes(token)
|
|
7142
|
+
)) {
|
|
7143
|
+
return true;
|
|
7144
|
+
}
|
|
7145
|
+
return false;
|
|
7146
|
+
}
|
|
6706
7147
|
var UI_FRAMEWORKS = [
|
|
6707
7148
|
{ match: ["vue", "nuxt"], target: "Vue", doc: "vue" },
|
|
6708
7149
|
{ match: ["react", "next"], target: "React", doc: "react" },
|
|
@@ -6777,6 +7218,7 @@ function ingestionInstructions(input) {
|
|
|
6777
7218
|
`Create an ingestion script under "${input.ingestDir}/" at the repo root. That directory already exists \u2014 writeFile creates any nested path itself, so never run a shell command just to create a directory.`,
|
|
6778
7219
|
FILE_EDIT_INSTRUCTION,
|
|
6779
7220
|
`Ingest only the confirmed entity (name, source paths, attributes): ${JSON.stringify(input.confirmed)}.`,
|
|
7221
|
+
...ingestionExposureInstructions(input.exposure),
|
|
6780
7222
|
`Ingesting writes to Algolia, so the script needs a write API key and App ID \u2014 read them from the ${API_KEY_VAR} and ${APP_ID_VAR} environment variables rather than hardcoding them. The wizard sets these when it runs the script.`,
|
|
6781
7223
|
`Read the index name from the ${INDEX_NAME_VAR} environment variable, which the wizard sets to "${input.targetIndex}". Never hardcode an index name or derive one from the project, file, or entity name \u2014 the write key only works for that exact index. Exit with an error if ${INDEX_NAME_VAR} is unset.`,
|
|
6782
7224
|
"Write the script in the project's primary language, using Algolia's official client for that language. Do not use the raw HTTP API.",
|
|
@@ -6807,8 +7249,16 @@ function ingestionInstructions(input) {
|
|
|
6807
7249
|
] : []
|
|
6808
7250
|
];
|
|
6809
7251
|
}
|
|
7252
|
+
function searchIndexedEntity(input) {
|
|
7253
|
+
const entity = input.findings.confirmedEntities?.[0];
|
|
7254
|
+
if (!entity) return void 0;
|
|
7255
|
+
return {
|
|
7256
|
+
...entity,
|
|
7257
|
+
attributes: visibleAttributes(entity.attributes, input.exposure)
|
|
7258
|
+
};
|
|
7259
|
+
}
|
|
6810
7260
|
function searchInstructions(input) {
|
|
6811
|
-
const indexedEntity = input
|
|
7261
|
+
const indexedEntity = searchIndexedEntity(input);
|
|
6812
7262
|
const templateMessage = input.uiTemplate ? `UI template: ${JSON.stringify(input.uiTemplate)}. Implement its layout, search placeholder, hit fields, filters, and sort controls. Use only the template's index attributes. Do not infer or invent record fields from the project.` : null;
|
|
6813
7263
|
const indexedEntityMessage = indexedEntity ? `Indexed content context: ${JSON.stringify({
|
|
6814
7264
|
entity: indexedEntity.name,
|
|
@@ -6830,6 +7280,7 @@ function searchInstructions(input) {
|
|
|
6830
7280
|
return [
|
|
6831
7281
|
"Implement an in-app Algolia search experience.",
|
|
6832
7282
|
indexedContentMessage ?? "",
|
|
7283
|
+
...searchExposureInstructions(input.exposure),
|
|
6833
7284
|
`Build the search UI for ${input.searchUiTarget}.`,
|
|
6834
7285
|
"Create search UI only. Do not create or modify ingestion scripts, rake/manage/CLI tasks, migrations, seeders, or other data-loading code, even if the data looks incomplete.",
|
|
6835
7286
|
"Prefer Highlight (or the plain attribute) for titles and short fields. Use Snippet only for longer text, and only when attributesToSnippet is set for that attribute \u2014 via the index searchParameters (for example attributesToSnippet: ['body:20']) or index settings. Without it, _snippetResult is missing and the result panel looks empty even though search returns hits.",
|
|
@@ -6908,9 +7359,10 @@ function packageSearchInstructions(input) {
|
|
|
6908
7359
|
`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.`,
|
|
6909
7360
|
"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.",
|
|
6910
7361
|
"Vendor theme CSS, and any CSS you write against vendor class names, must be global: put overrides in a global block (Astro <style is:global>, an unscoped Vue block, a plain global CSS file). Never a framework-scoped style block or a CSS Module \u2014 scoping rewrites the vendor selectors and the widget's runtime DOM carries no scope attribute, so not one rule matches: styling silently does nothing and the build still passes. Import the theme and those global overrides only where the framework allows first-party global CSS: Next.js Pages Router only from pages/_app (or _app.tsx), Next.js App Router only from the root layout, otherwise from the app's global stylesheet or the component module when that is valid. Do not import a plain .css file from a Next.js component \u2014 that fails the build.",
|
|
6911
|
-
`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
|
|
7362
|
+
`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.`,
|
|
7363
|
+
clientKeySentence(publicSearchKeyIsSafe(input.exposure)),
|
|
6912
7364
|
`Set ${SEARCH_CONFIG_APP_ID} to "${input.appId}" and ${SEARCH_CONFIG_INDEX_NAME} to "${input.targetIndex}".`,
|
|
6913
|
-
input
|
|
7365
|
+
searchKeyInstruction(input, SEARCH_CONFIG_SEARCH_KEY),
|
|
6914
7366
|
'Report the repo-relative path of that module as "searchConfigFile" in your final status.',
|
|
6915
7367
|
"Install any Algolia packages you import with the project's own package manager via runShell, and declare them in the project's dependency manifest."
|
|
6916
7368
|
];
|
|
@@ -6944,7 +7396,10 @@ function cdnSearchInstructions(input) {
|
|
|
6944
7396
|
return [
|
|
6945
7397
|
`The target frontend has no package.json. Add the search container to its existing HTML or template, create separate classic config and search scripts, define window.${SEARCH_CONFIG_APP_ID}, window.${SEARCH_CONFIG_SEARCH_KEY}, and window.${SEARCH_CONFIG_INDEX_NAME} in the config script, and report that script as "searchConfigFile". Never create package.json, create a component, use imports or exports, or run npm, pnpm, yarn, or bun.`,
|
|
6946
7398
|
`Set window.${SEARCH_CONFIG_APP_ID} to "${input.appId}" and window.${SEARCH_CONFIG_INDEX_NAME} to "${input.targetIndex}".`,
|
|
6947
|
-
input
|
|
7399
|
+
searchKeyInstruction(input, `window.${SEARCH_CONFIG_SEARCH_KEY}`),
|
|
7400
|
+
...publicSearchKeyIsSafe(input.exposure) ? [] : [
|
|
7401
|
+
`Do not describe window.${SEARCH_CONFIG_SEARCH_KEY} as safe to commit or safe to expose. Public search can still return personal data.`
|
|
7402
|
+
],
|
|
6948
7403
|
'Load these exact pinned tags in order before the config and search scripts: <script src="https://cdn.jsdelivr.net/npm/algoliasearch@5.59.0/dist/lite/builds/browser.umd.js" integrity="sha256-pduQHl1jn0IaN/BIpSotQm7Y5THdpKcX3Bw6xdRteRw=" crossorigin="anonymous"></script> then <script src="https://cdn.jsdelivr.net/npm/instantsearch.js@4.117.0/dist/instantsearch.production.min.js" integrity="sha256-5zhKxAGeH7ThWfnmdD4pS1gsmafE2b8aWRsq8set/dc=" crossorigin="anonymous"></script>.',
|
|
6949
7404
|
'When using the InstantSearch theme, load this exact tag before the application styles: <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/instantsearch.css@8.22.1/themes/satellite-min.css" integrity="sha256-5/eHPZl63VYJSDVOKrOgGe/5+owUkX3uPAW6+XYeLNc=" crossorigin="anonymous">. Never use an unversioned or differently versioned theme URL, and never omit its integrity attribute.',
|
|
6950
7405
|
`In that classic search script, use window['algoliasearch/lite'].liteClient and window.instantsearch. Add window.instantsearch.widgets.autocomplete as the only search widget. Configure it with container '#autocomplete', the content-specific placeholder ${JSON.stringify(placeholder)}, and an indices array containing one object whose indexName is window.${SEARCH_CONFIG_INDEX_NAME}. Put the item and noResults functions in the templates option of that indices entry, implement getURL only for an existing route, and match the item template to the entity schema. Keep insights: true and call search.start(). Do not substitute searchBox or hits, omit indices, use imports or exports, or use type="module".`,
|
|
@@ -6953,11 +7408,19 @@ function cdnSearchInstructions(input) {
|
|
|
6953
7408
|
}
|
|
6954
7409
|
function validationInstructions(input) {
|
|
6955
7410
|
const templateMessage = input.uiTemplate ? `UI template: ${JSON.stringify(input.uiTemplate)}. Confirm the implementation uses its layout, search placeholder, hit fields, filters, and sort controls with the template's exact index attributes.` : null;
|
|
6956
|
-
const
|
|
7411
|
+
const indexedEntity = searchIndexedEntity(input);
|
|
7412
|
+
const indexedEntityMessage = indexedEntity ? `Indexed content context: ${JSON.stringify(indexedEntity)}.` : null;
|
|
6957
7413
|
const contentMessage = (templateMessage || indexedEntityMessage) ?? "";
|
|
7414
|
+
const withheld = searchExposureInstructions(input.exposure);
|
|
7415
|
+
const placeholderKey = placeholderKeyValidationInstruction(input);
|
|
6958
7416
|
return [
|
|
6959
7417
|
"Validate the Algolia search implementation by inspecting the project source.",
|
|
6960
7418
|
contentMessage,
|
|
7419
|
+
...placeholderKey ? [placeholderKey] : [],
|
|
7420
|
+
...withheld,
|
|
7421
|
+
...withheld.length > 0 ? [
|
|
7422
|
+
"Do not set sufficient=false because the UI omits the personal or blocked attributes named above."
|
|
7423
|
+
] : [],
|
|
6961
7424
|
`The confirmed placement candidate was "${input.searchLocation ?? "unknown"}", but verify the actual mount rather than assuming the candidate was used.`,
|
|
6962
7425
|
"Inspect the generated search component, its actual mount file, that file's parent layout and sibling controls, and the relevant content UI. Do not rely only on type checks or tests.",
|
|
6963
7426
|
`Confirm that the application renders exactly one search control for the indexed content, and that the new component is not mounted twice. ${SEARCH_SCOPE_RULE} If a same-scope search control is still mounted, set sufficient=false and name the file that mounts it. Do not set sufficient=false for a differently scoped search control.`,
|
|
@@ -7393,6 +7856,23 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, options =
|
|
|
7393
7856
|
});
|
|
7394
7857
|
const confirmed2 = findings.confirmedEntities;
|
|
7395
7858
|
const searchLocation = findings.searchImplementationAnalysis;
|
|
7859
|
+
let exposure;
|
|
7860
|
+
let searchKeyWithheldFor = [];
|
|
7861
|
+
if (useCases.includes("ingestion")) {
|
|
7862
|
+
exposure = await resolveRecordExposure(
|
|
7863
|
+
ctx,
|
|
7864
|
+
(confirmed2 ?? []).flatMap((entity) => entity.attributes)
|
|
7865
|
+
);
|
|
7866
|
+
} else if (useCases.includes("search")) {
|
|
7867
|
+
const ingestion2 = ctx.getStepOutput(
|
|
7868
|
+
"ingestion"
|
|
7869
|
+
);
|
|
7870
|
+
exposure = ingestion2?.recordExposure;
|
|
7871
|
+
searchKeyWithheldFor = exposedPersonalAttributes(
|
|
7872
|
+
exposure,
|
|
7873
|
+
Boolean(ingestion2?.ingestScriptRan) || (ingestion2?.ingestRecordCount ?? 0) > 0
|
|
7874
|
+
);
|
|
7875
|
+
}
|
|
7396
7876
|
let appId;
|
|
7397
7877
|
let ingestAppId;
|
|
7398
7878
|
let appPlan;
|
|
@@ -7413,13 +7893,33 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, options =
|
|
|
7413
7893
|
const summaries = [];
|
|
7414
7894
|
let searchKey;
|
|
7415
7895
|
let searchKeyError;
|
|
7416
|
-
|
|
7896
|
+
let createdSearchKey;
|
|
7897
|
+
const pushCreatedSearchKeySummary = () => {
|
|
7898
|
+
if (!createdSearchKey) return;
|
|
7899
|
+
summaries.unshift(
|
|
7900
|
+
createdSearchKeySummary(
|
|
7901
|
+
createdSearchKey.index,
|
|
7902
|
+
createdSearchKey.appId,
|
|
7903
|
+
publicSearchKeyIsSafe(exposure)
|
|
7904
|
+
)
|
|
7905
|
+
);
|
|
7906
|
+
createdSearchKey = void 0;
|
|
7907
|
+
};
|
|
7908
|
+
if (searchKeyWithheldFor.length > 0) {
|
|
7909
|
+
summaries.push(withheldSearchKeySummary(searchKeyWithheldFor));
|
|
7910
|
+
} else if (useCases.includes("search") && appId) {
|
|
7417
7911
|
try {
|
|
7418
7912
|
const resolved2 = await resolveSearchOnlyKey(targetIndex, appId);
|
|
7419
7913
|
searchKey = resolved2.key;
|
|
7420
|
-
|
|
7421
|
-
|
|
7422
|
-
|
|
7914
|
+
if (resolved2.source === "created") {
|
|
7915
|
+
createdSearchKey = { index: targetIndex, appId };
|
|
7916
|
+
} else {
|
|
7917
|
+
summaries.push(
|
|
7918
|
+
`Reused the existing search-only Algolia API key for the "${targetIndex}" index in app ${appId}.`
|
|
7919
|
+
);
|
|
7920
|
+
}
|
|
7921
|
+
const exposureNote = !useCases.includes("ingestion") && exposure ? hiddenAttributeSummary(exposure) : void 0;
|
|
7922
|
+
if (exposureNote) summaries.push(exposureNote);
|
|
7423
7923
|
} catch (err) {
|
|
7424
7924
|
searchKeyError = err.message;
|
|
7425
7925
|
summaries.push(
|
|
@@ -7431,6 +7931,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, options =
|
|
|
7431
7931
|
);
|
|
7432
7932
|
}
|
|
7433
7933
|
}
|
|
7934
|
+
if (!useCases.includes("ingestion")) pushCreatedSearchKeySummary();
|
|
7434
7935
|
const input = {
|
|
7435
7936
|
findings,
|
|
7436
7937
|
confirmed: confirmed2,
|
|
@@ -7441,6 +7942,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, options =
|
|
|
7441
7942
|
appPlan,
|
|
7442
7943
|
searchKey,
|
|
7443
7944
|
searchKeyError,
|
|
7945
|
+
searchKeyWithheldFor,
|
|
7444
7946
|
ingestDir: INGEST_DIR,
|
|
7445
7947
|
ingestionSource,
|
|
7446
7948
|
uploadFilePath,
|
|
@@ -7449,7 +7951,8 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, options =
|
|
|
7449
7951
|
repoRoot,
|
|
7450
7952
|
searchLocation
|
|
7451
7953
|
),
|
|
7452
|
-
uiTemplate: options.uiTemplate
|
|
7954
|
+
uiTemplate: options.uiTemplate,
|
|
7955
|
+
exposure
|
|
7453
7956
|
};
|
|
7454
7957
|
let agentRuns = 0;
|
|
7455
7958
|
let ingestCommand;
|
|
@@ -7458,6 +7961,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, options =
|
|
|
7458
7961
|
let ingestRecordCount;
|
|
7459
7962
|
let ingestDurationMs;
|
|
7460
7963
|
let ingestOutcomeMessage;
|
|
7964
|
+
let ingestRecordSample;
|
|
7461
7965
|
const ingestKeyAppId = ingestAppId;
|
|
7462
7966
|
const ingestionTools = ingestKeyAppId && ingestWriteKey ? makeToolContext(
|
|
7463
7967
|
repoRoot,
|
|
@@ -7570,6 +8074,16 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, options =
|
|
|
7570
8074
|
)
|
|
7571
8075
|
});
|
|
7572
8076
|
const ingestStartedAt = Date.now();
|
|
8077
|
+
if (exposure && withheldAttributes(exposure).length && ingestAppId && ingestWriteKey) {
|
|
8078
|
+
await restrictPublicSearch(exposure, {
|
|
8079
|
+
wroteRecords: true,
|
|
8080
|
+
appId: ingestAppId,
|
|
8081
|
+
writeKey: ingestWriteKey,
|
|
8082
|
+
indexName: targetIndex,
|
|
8083
|
+
attributes: (confirmed2 ?? []).flatMap((entity) => entity.attributes),
|
|
8084
|
+
createIndexSettings: true
|
|
8085
|
+
});
|
|
8086
|
+
}
|
|
7573
8087
|
await runIngestionAgent();
|
|
7574
8088
|
let outcome = ingestOutcome(executions, repoRoot, ingestCommand);
|
|
7575
8089
|
let failureAcknowledged = false;
|
|
@@ -7659,7 +8173,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, options =
|
|
|
7659
8173
|
}
|
|
7660
8174
|
if (ingestRecordCount != null) {
|
|
7661
8175
|
const data_model = confirmed2?.[0]?.name ?? "unknown";
|
|
7662
|
-
|
|
8176
|
+
ingestRecordSample = await ingestedRecordSample(
|
|
7663
8177
|
repoRoot,
|
|
7664
8178
|
confirmed2?.[0]?.name,
|
|
7665
8179
|
uploadFilePath
|
|
@@ -7669,7 +8183,10 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, options =
|
|
|
7669
8183
|
record_count: ingestRecordCount,
|
|
7670
8184
|
duration_ms: Date.now() - ingestStartedAt,
|
|
7671
8185
|
index_name: targetIndex,
|
|
7672
|
-
record_schema: recordSchema(
|
|
8186
|
+
record_schema: recordSchema(
|
|
8187
|
+
ingestRecordSample,
|
|
8188
|
+
confirmed2?.[0]?.attributes ?? []
|
|
8189
|
+
),
|
|
7673
8190
|
attempts: ingestRunAttempts
|
|
7674
8191
|
});
|
|
7675
8192
|
} else {
|
|
@@ -7711,6 +8228,40 @@ ${detail}` : ""}`
|
|
|
7711
8228
|
...ingestAttempt?.exitCode != null ? { error_code: String(ingestAttempt.exitCode) } : {}
|
|
7712
8229
|
});
|
|
7713
8230
|
}
|
|
8231
|
+
if (exposure) {
|
|
8232
|
+
const wroteRecords = ingestScriptRan || (ingestRecordCount ?? 0) > 0;
|
|
8233
|
+
const restrictArgs = {
|
|
8234
|
+
wroteRecords,
|
|
8235
|
+
appId: ingestAppId,
|
|
8236
|
+
writeKey: ingestWriteKey,
|
|
8237
|
+
indexName: targetIndex,
|
|
8238
|
+
attributes: (confirmed2 ?? []).flatMap((entity) => entity.attributes),
|
|
8239
|
+
recordSample: ingestRecordSample
|
|
8240
|
+
};
|
|
8241
|
+
let restricted = await restrictPublicSearch(exposure, restrictArgs);
|
|
8242
|
+
while (restricted.failure) {
|
|
8243
|
+
const choice = await ctx.requestUserInput({
|
|
8244
|
+
prompt: HIDE_PERSONAL_DATA_RETRY_PROMPT,
|
|
8245
|
+
promptType: "multipleChoice",
|
|
8246
|
+
options: [HIDE_PERSONAL_DATA_RETRY.retry, HIDE_PERSONAL_DATA_RETRY.skip],
|
|
8247
|
+
messages: [
|
|
8248
|
+
`\u26A0\uFE0F Could not hide ${withheldAttributes(exposure).join(", ")} from public search \u2014 ${restricted.failure}.`,
|
|
8249
|
+
"These attributes may already be in the index, so until they are hidden the wizard will put a placeholder search key in client code instead of the real one."
|
|
8250
|
+
]
|
|
8251
|
+
});
|
|
8252
|
+
if (choice !== HIDE_PERSONAL_DATA_RETRY.retry) break;
|
|
8253
|
+
restricted = await restrictPublicSearch(exposure, restrictArgs);
|
|
8254
|
+
}
|
|
8255
|
+
if (restricted.note) summaries.push(restricted.note);
|
|
8256
|
+
searchKeyWithheldFor = exposedPersonalAttributes(exposure, wroteRecords);
|
|
8257
|
+
if (searchKeyWithheldFor.length > 0 && useCases.includes("search")) {
|
|
8258
|
+
searchKey = void 0;
|
|
8259
|
+
input.searchKey = void 0;
|
|
8260
|
+
input.searchKeyWithheldFor = searchKeyWithheldFor;
|
|
8261
|
+
summaries.push(withheldSearchKeySummary(searchKeyWithheldFor));
|
|
8262
|
+
}
|
|
8263
|
+
}
|
|
8264
|
+
pushCreatedSearchKeySummary();
|
|
7714
8265
|
if (ingestScriptRan) {
|
|
7715
8266
|
await ctx.requestUserInput({
|
|
7716
8267
|
prompt: "Continue",
|
|
@@ -7873,7 +8424,7 @@ ${detail}` : ""}`
|
|
|
7873
8424
|
);
|
|
7874
8425
|
if (ignoreStatus === "covered") {
|
|
7875
8426
|
summaries.push(
|
|
7876
|
-
`\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.`
|
|
8427
|
+
publicSearchKeyIsSafe(input.exposure) ? `\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.` : `\u26A0\uFE0F ${searchConfigFile} is gitignored. This search key is not safe to commit. Remove whatever .gitignore rule covers it once public search cannot return personal data.`
|
|
7877
8428
|
);
|
|
7878
8429
|
}
|
|
7879
8430
|
}
|
|
@@ -7883,6 +8434,7 @@ ${detail}` : ""}`
|
|
|
7883
8434
|
return {
|
|
7884
8435
|
ingestionSource,
|
|
7885
8436
|
summary: summaries.join("\n\n"),
|
|
8437
|
+
...exposure ? { recordExposure: exposure } : {},
|
|
7886
8438
|
...useCases.includes("ingestion") && ingestCommand ? {
|
|
7887
8439
|
ingestCommand,
|
|
7888
8440
|
ingestScriptRan,
|
|
@@ -7955,8 +8507,8 @@ var defaultWorkflow = {
|
|
|
7955
8507
|
defineStep({
|
|
7956
8508
|
id: "select-index",
|
|
7957
8509
|
title: "Set up index",
|
|
7958
|
-
outputSchema:
|
|
7959
|
-
selection:
|
|
8510
|
+
outputSchema: z31.object({
|
|
8511
|
+
selection: z31.string()
|
|
7960
8512
|
}),
|
|
7961
8513
|
run: (ctx) => selectIndexStep(ctx)
|
|
7962
8514
|
}),
|
|
@@ -8025,7 +8577,7 @@ function ingestionGuidance(ingestion2) {
|
|
|
8025
8577
|
}
|
|
8026
8578
|
|
|
8027
8579
|
// src/actions/playbookSteps/selectPlaybook.ts
|
|
8028
|
-
import { z as
|
|
8580
|
+
import { z as z34 } from "zod";
|
|
8029
8581
|
|
|
8030
8582
|
// src/lib/dashboardApi/client.ts
|
|
8031
8583
|
import "zod";
|
|
@@ -8084,80 +8636,80 @@ async function dashboardError(res) {
|
|
|
8084
8636
|
}
|
|
8085
8637
|
|
|
8086
8638
|
// src/lib/dashboardApi/schemas.ts
|
|
8087
|
-
import { z as
|
|
8088
|
-
var messageResponseSchema =
|
|
8089
|
-
message:
|
|
8639
|
+
import { z as z33 } from "zod";
|
|
8640
|
+
var messageResponseSchema = z33.object({
|
|
8641
|
+
message: z33.string()
|
|
8090
8642
|
});
|
|
8091
|
-
var playbookSchema =
|
|
8092
|
-
slug:
|
|
8093
|
-
category:
|
|
8094
|
-
title:
|
|
8095
|
-
description:
|
|
8643
|
+
var playbookSchema = z33.object({
|
|
8644
|
+
slug: z33.string(),
|
|
8645
|
+
category: z33.string().optional(),
|
|
8646
|
+
title: z33.string(),
|
|
8647
|
+
description: z33.string().optional()
|
|
8096
8648
|
});
|
|
8097
|
-
var playbooksListSchema =
|
|
8098
|
-
var playbookExecutionStepSchema =
|
|
8649
|
+
var playbooksListSchema = z33.array(playbookSchema);
|
|
8650
|
+
var playbookExecutionStepSchema = z33.enum([
|
|
8099
8651
|
"assessment",
|
|
8100
8652
|
"optimization",
|
|
8101
8653
|
"implement"
|
|
8102
8654
|
]);
|
|
8103
|
-
var playbookExecutionStepStatusSchema =
|
|
8655
|
+
var playbookExecutionStepStatusSchema = z33.enum([
|
|
8104
8656
|
"not_started",
|
|
8105
8657
|
"in_progress",
|
|
8106
8658
|
"done",
|
|
8107
8659
|
"error"
|
|
8108
8660
|
]);
|
|
8109
|
-
var schemaComparisonEntrySchema =
|
|
8110
|
-
canonical_attr:
|
|
8111
|
-
canonical_type:
|
|
8112
|
-
canonical_roles:
|
|
8113
|
-
canonical_required:
|
|
8114
|
-
index_attr:
|
|
8115
|
-
index_type:
|
|
8116
|
-
index_required:
|
|
8117
|
-
status:
|
|
8118
|
-
mapping_confidence:
|
|
8661
|
+
var schemaComparisonEntrySchema = z33.object({
|
|
8662
|
+
canonical_attr: z33.string(),
|
|
8663
|
+
canonical_type: z33.array(z33.string()),
|
|
8664
|
+
canonical_roles: z33.array(z33.string()),
|
|
8665
|
+
canonical_required: z33.boolean(),
|
|
8666
|
+
index_attr: z33.string().nullable(),
|
|
8667
|
+
index_type: z33.array(z33.string()).nullable(),
|
|
8668
|
+
index_required: z33.boolean().nullable(),
|
|
8669
|
+
status: z33.string(),
|
|
8670
|
+
mapping_confidence: z33.number()
|
|
8119
8671
|
});
|
|
8120
|
-
var settingRecommendationSchema =
|
|
8121
|
-
current:
|
|
8122
|
-
recommended:
|
|
8672
|
+
var settingRecommendationSchema = z33.object({
|
|
8673
|
+
current: z33.any(),
|
|
8674
|
+
recommended: z33.any()
|
|
8123
8675
|
});
|
|
8124
|
-
var settingsRecommendationEntriesSchema =
|
|
8125
|
-
|
|
8676
|
+
var settingsRecommendationEntriesSchema = z33.record(
|
|
8677
|
+
z33.string(),
|
|
8126
8678
|
settingRecommendationSchema
|
|
8127
8679
|
);
|
|
8128
|
-
var assessmentResultSchema =
|
|
8129
|
-
ready:
|
|
8130
|
-
schema_comparison:
|
|
8680
|
+
var assessmentResultSchema = z33.object({
|
|
8681
|
+
ready: z33.boolean(),
|
|
8682
|
+
schema_comparison: z33.array(schemaComparisonEntrySchema),
|
|
8131
8683
|
settings_recommendation: settingsRecommendationEntriesSchema
|
|
8132
8684
|
});
|
|
8133
|
-
var playbookExecutionSchema =
|
|
8134
|
-
uuid:
|
|
8135
|
-
playbook_slug:
|
|
8136
|
-
application_id:
|
|
8137
|
-
source_index_name:
|
|
8685
|
+
var playbookExecutionSchema = z33.object({
|
|
8686
|
+
uuid: z33.string(),
|
|
8687
|
+
playbook_slug: z33.string(),
|
|
8688
|
+
application_id: z33.string(),
|
|
8689
|
+
source_index_name: z33.string(),
|
|
8138
8690
|
current_step: playbookExecutionStepSchema,
|
|
8139
8691
|
step_status: playbookExecutionStepStatusSchema,
|
|
8140
|
-
status_reason:
|
|
8141
|
-
completed:
|
|
8692
|
+
status_reason: z33.string().nullable(),
|
|
8693
|
+
completed: z33.boolean(),
|
|
8142
8694
|
assessment_result: assessmentResultSchema.nullable(),
|
|
8143
|
-
settings_snapshot:
|
|
8144
|
-
created_at:
|
|
8695
|
+
settings_snapshot: z33.json().nullable(),
|
|
8696
|
+
created_at: z33.iso.datetime()
|
|
8145
8697
|
});
|
|
8146
8698
|
var playbookExecutionCreateResponseSchema = playbookExecutionSchema.extend({
|
|
8147
|
-
resumed:
|
|
8148
|
-
sample_data:
|
|
8699
|
+
resumed: z33.boolean(),
|
|
8700
|
+
sample_data: z33.boolean()
|
|
8149
8701
|
});
|
|
8150
|
-
var uiTemplateFieldSchema =
|
|
8151
|
-
var uiTemplateSchema =
|
|
8152
|
-
layout:
|
|
8153
|
-
record_label:
|
|
8154
|
-
singular:
|
|
8155
|
-
plural:
|
|
8702
|
+
var uiTemplateFieldSchema = z33.record(z33.string(), z33.unknown());
|
|
8703
|
+
var uiTemplateSchema = z33.looseObject({
|
|
8704
|
+
layout: z33.string(),
|
|
8705
|
+
record_label: z33.object({
|
|
8706
|
+
singular: z33.string(),
|
|
8707
|
+
plural: z33.string()
|
|
8156
8708
|
}),
|
|
8157
|
-
search_placeholder:
|
|
8709
|
+
search_placeholder: z33.string().optional(),
|
|
8158
8710
|
hit: uiTemplateFieldSchema,
|
|
8159
|
-
filters:
|
|
8160
|
-
sort_by:
|
|
8711
|
+
filters: z33.array(uiTemplateFieldSchema),
|
|
8712
|
+
sort_by: z33.array(uiTemplateFieldSchema)
|
|
8161
8713
|
});
|
|
8162
8714
|
|
|
8163
8715
|
// src/lib/dashboardApi/api.ts
|
|
@@ -8247,8 +8799,8 @@ async function getPlaybookUiTemplate(uuid) {
|
|
|
8247
8799
|
}
|
|
8248
8800
|
|
|
8249
8801
|
// src/actions/playbookSteps/selectPlaybook.ts
|
|
8250
|
-
var selectPlaybookSchema =
|
|
8251
|
-
slug:
|
|
8802
|
+
var selectPlaybookSchema = z34.object({
|
|
8803
|
+
slug: z34.string()
|
|
8252
8804
|
});
|
|
8253
8805
|
function persistSlug(ctx, slug2) {
|
|
8254
8806
|
ctx.setUserInput("playbookSlug", slug2);
|
|
@@ -8287,10 +8839,10 @@ async function selectPlaybookStep(ctx, requestedSlug) {
|
|
|
8287
8839
|
}
|
|
8288
8840
|
|
|
8289
8841
|
// src/actions/playbookSteps/selectIndex.ts
|
|
8290
|
-
import { z as
|
|
8291
|
-
var selectIndexSchema =
|
|
8292
|
-
indexName:
|
|
8293
|
-
sampleIndex:
|
|
8842
|
+
import { z as z35 } from "zod";
|
|
8843
|
+
var selectIndexSchema = z35.object({
|
|
8844
|
+
indexName: z35.string(),
|
|
8845
|
+
sampleIndex: z35.boolean()
|
|
8294
8846
|
});
|
|
8295
8847
|
var CREATE_SAMPLE_INDEX = "Create a sample index";
|
|
8296
8848
|
var GO_BACK_OPTION = "Go back to playbook selection";
|
|
@@ -8967,7 +9519,7 @@ function requestFailureMessage2(err) {
|
|
|
8967
9519
|
}
|
|
8968
9520
|
|
|
8969
9521
|
// src/actions/playbookSteps/generateUi.ts
|
|
8970
|
-
import
|
|
9522
|
+
import z37 from "zod";
|
|
8971
9523
|
|
|
8972
9524
|
// src/actions/playbookSteps/generateUiPrompts.ts
|
|
8973
9525
|
var SWITCH_APPLICATION_OPTION3 = "Switch application";
|
|
@@ -8975,24 +9527,24 @@ var SWITCH_INDEX_OPTION3 = "Switch index";
|
|
|
8975
9527
|
var RETRY_OPTION3 = "Retry";
|
|
8976
9528
|
async function promptUiGenerated(ctx, report) {
|
|
8977
9529
|
const { files, checkPassed, devCommand } = report ?? {};
|
|
8978
|
-
const
|
|
9530
|
+
const list2 = [];
|
|
8979
9531
|
if (files) {
|
|
8980
9532
|
const editedFiles = files.map((file) => ({
|
|
8981
9533
|
label: file.label,
|
|
8982
9534
|
helpText: file.path,
|
|
8983
9535
|
color: "success"
|
|
8984
9536
|
}));
|
|
8985
|
-
|
|
9537
|
+
list2.push(...editedFiles);
|
|
8986
9538
|
}
|
|
8987
9539
|
if (checkPassed) {
|
|
8988
|
-
|
|
9540
|
+
list2.push({ label: "build & typecheck passed", color: "success" });
|
|
8989
9541
|
}
|
|
8990
|
-
|
|
9542
|
+
list2.push({
|
|
8991
9543
|
label: "playbook UI completed",
|
|
8992
9544
|
color: "success"
|
|
8993
9545
|
});
|
|
8994
9546
|
if (devCommand) {
|
|
8995
|
-
|
|
9547
|
+
list2.push({
|
|
8996
9548
|
label: "view it locally",
|
|
8997
9549
|
helpText: devCommand,
|
|
8998
9550
|
color: "success"
|
|
@@ -9003,7 +9555,7 @@ async function promptUiGenerated(ctx, report) {
|
|
|
9003
9555
|
promptType: "notice",
|
|
9004
9556
|
options: [],
|
|
9005
9557
|
messages: ["implemented the playbook UI"],
|
|
9006
|
-
list
|
|
9558
|
+
list: list2
|
|
9007
9559
|
});
|
|
9008
9560
|
}
|
|
9009
9561
|
async function promptUiRecovery(ctx, message) {
|
|
@@ -9025,10 +9577,10 @@ async function promptUiRecovery(ctx, message) {
|
|
|
9025
9577
|
}
|
|
9026
9578
|
|
|
9027
9579
|
// src/actions/playbookSteps/generateUi.ts
|
|
9028
|
-
var generatePlaybookUiSchema =
|
|
9029
|
-
summary:
|
|
9030
|
-
framework:
|
|
9031
|
-
layoutName:
|
|
9580
|
+
var generatePlaybookUiSchema = z37.object({
|
|
9581
|
+
summary: z37.string(),
|
|
9582
|
+
framework: z37.string().optional(),
|
|
9583
|
+
layoutName: z37.string().optional()
|
|
9032
9584
|
});
|
|
9033
9585
|
async function generatePlaybookUiStep(ctx) {
|
|
9034
9586
|
const execution = await requireExecution2(ctx);
|
|
@@ -9105,20 +9657,20 @@ function isUiEligible(execution) {
|
|
|
9105
9657
|
return current_step === "optimization" && step_status === "done" || current_step === "implement" && step_status !== "done";
|
|
9106
9658
|
}
|
|
9107
9659
|
async function loadTemplateWithRecovery(ctx, uuid) {
|
|
9108
|
-
const
|
|
9660
|
+
const request2 = () => getPlaybookUiTemplate(uuid);
|
|
9109
9661
|
const requestRecoveryPrompt = (err) => promptUiRecovery(
|
|
9110
9662
|
ctx,
|
|
9111
9663
|
requestFailureMessage3(err, "Couldn't load the playbook UI template.")
|
|
9112
9664
|
);
|
|
9113
|
-
return retryOnDashboardError(
|
|
9665
|
+
return retryOnDashboardError(request2, requestRecoveryPrompt);
|
|
9114
9666
|
}
|
|
9115
9667
|
async function markDoneWithRecovery(ctx, uuid) {
|
|
9116
|
-
const
|
|
9668
|
+
const request2 = () => updatePlaybookStepStatus(uuid, "done");
|
|
9117
9669
|
const requestRecoveryPrompt = (err) => promptUiRecovery(
|
|
9118
9670
|
ctx,
|
|
9119
9671
|
requestFailureMessage3(err, "Couldn't update the playbook UI step.")
|
|
9120
9672
|
);
|
|
9121
|
-
await retryOnDashboardError(
|
|
9673
|
+
await retryOnDashboardError(request2, requestRecoveryPrompt);
|
|
9122
9674
|
}
|
|
9123
9675
|
async function markPlaybookUiStepFailed(execution, err) {
|
|
9124
9676
|
const reason = err instanceof Error ? err.message : String(err);
|
|
@@ -9138,12 +9690,12 @@ function requestFailureMessage3(err, fallback) {
|
|
|
9138
9690
|
}
|
|
9139
9691
|
|
|
9140
9692
|
// src/actions/playbookSteps/summary.ts
|
|
9141
|
-
import { z as
|
|
9142
|
-
var playbookSummarySchema =
|
|
9143
|
-
slug:
|
|
9144
|
-
applicationId:
|
|
9145
|
-
indexName:
|
|
9146
|
-
writtenFileCount:
|
|
9693
|
+
import { z as z38 } from "zod";
|
|
9694
|
+
var playbookSummarySchema = z38.object({
|
|
9695
|
+
slug: z38.string(),
|
|
9696
|
+
applicationId: z38.string(),
|
|
9697
|
+
indexName: z38.string(),
|
|
9698
|
+
writtenFileCount: z38.number().int().nonnegative()
|
|
9147
9699
|
});
|
|
9148
9700
|
async function playbookSummaryStep(ctx) {
|
|
9149
9701
|
const { slug: slug2 } = ctx.getStepOutput("select-playbook");
|
|
@@ -9585,7 +10137,7 @@ var accessItems = [
|
|
|
9585
10137
|
{
|
|
9586
10138
|
tag: "KEY",
|
|
9587
10139
|
title: "Credentials",
|
|
9588
|
-
description: "writes
|
|
10140
|
+
description: "writes the Algolia app id, index name, and write key to a gitignored env file. A search-only key goes in client code only when results cannot include personal data."
|
|
9589
10141
|
}
|
|
9590
10142
|
];
|
|
9591
10143
|
var neverItems = [
|
|
@@ -10953,7 +11505,7 @@ async function promptResumeOrStartNew(session, next) {
|
|
|
10953
11505
|
// package.json with { type: 'json' }
|
|
10954
11506
|
var package_default2 = {
|
|
10955
11507
|
name: "@algolia/wizard",
|
|
10956
|
-
version: "0.
|
|
11508
|
+
version: "0.117.0",
|
|
10957
11509
|
description: "Magically implement Algolia functionality in your codebase",
|
|
10958
11510
|
type: "module",
|
|
10959
11511
|
engines: {
|
|
@@ -18,11 +18,10 @@ Always install the **latest stable** within the major (use a caret range like `^
|
|
|
18
18
|
|
|
19
19
|
## Golden rules
|
|
20
20
|
|
|
21
|
-
- Use a **search-only API key** in any browser
|
|
22
|
-
|
|
21
|
+
- Use a **search-only API key** in any browser or client code. Commit it only when a public search hit cannot return personal data.
|
|
22
|
+
Never ship an admin or write-capable key to the client.
|
|
23
23
|
- Import the Application ID and search-only key from the project's shared client-side
|
|
24
|
-
config module as plain exported constants.
|
|
25
|
-
them from an env var or `.env*` file, and never hardcode them elsewhere.
|
|
24
|
+
config module as plain exported constants. Never read them from an env var or a `.env*` file, and never hardcode them anywhere else.
|
|
26
25
|
- Instantiate the search client **once, outside your components**, and pass a stable
|
|
27
26
|
reference. Do not inline `algoliasearch(...)` as a prop value — it breaks the
|
|
28
27
|
client cache and causes re-renders.
|
|
@@ -5,10 +5,10 @@ Install `instantsearch.js` (v4)
|
|
|
5
5
|
## Search client: always use the lite client
|
|
6
6
|
|
|
7
7
|
Import the client from `algoliasearch/lite` and alias it to `algoliasearch`.
|
|
8
|
-
`ALGOLIA_APP_ID`, `ALGOLIA_SEARCH_API_KEY`, and `ALGOLIA_INDEX_NAME`
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
them from an env var.
|
|
8
|
+
`ALGOLIA_APP_ID`, `ALGOLIA_SEARCH_API_KEY`, and `ALGOLIA_INDEX_NAME` are exported
|
|
9
|
+
constants in the project's shared client-side config module. Commit them only
|
|
10
|
+
when a public search hit cannot return personal data. Never hardcode them inline
|
|
11
|
+
or read them from an env var.
|
|
12
12
|
|
|
13
13
|
```ts
|
|
14
14
|
import { liteClient as algoliasearch } from 'algoliasearch/lite'
|
|
@@ -5,10 +5,10 @@ Install `react-instantsearch` (v7)
|
|
|
5
5
|
## Search client: always use the lite client
|
|
6
6
|
|
|
7
7
|
Import the client from `algoliasearch/lite` and alias it to `algoliasearch`.
|
|
8
|
-
`ALGOLIA_APP_ID`, `ALGOLIA_SEARCH_API_KEY`, and `ALGOLIA_INDEX_NAME`
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
them from an env var.
|
|
8
|
+
`ALGOLIA_APP_ID`, `ALGOLIA_SEARCH_API_KEY`, and `ALGOLIA_INDEX_NAME` are exported
|
|
9
|
+
constants in the project's shared client-side config module. Commit them only
|
|
10
|
+
when a public search hit cannot return personal data. Never hardcode them inline
|
|
11
|
+
or read them from an env var.
|
|
12
12
|
|
|
13
13
|
```ts
|
|
14
14
|
import { liteClient as algoliasearch } from 'algoliasearch/lite'
|
|
@@ -5,10 +5,10 @@ Install `vue-instantsearch` (v4)
|
|
|
5
5
|
## Search client: always use the lite client
|
|
6
6
|
|
|
7
7
|
Import the client from `algoliasearch/lite` and alias it to `algoliasearch`.
|
|
8
|
-
`ALGOLIA_APP_ID`, `ALGOLIA_SEARCH_API_KEY`, and `ALGOLIA_INDEX_NAME`
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
them from an env var.
|
|
8
|
+
`ALGOLIA_APP_ID`, `ALGOLIA_SEARCH_API_KEY`, and `ALGOLIA_INDEX_NAME` are exported
|
|
9
|
+
constants in the project's shared client-side config module. Commit them only
|
|
10
|
+
when a public search hit cannot return personal data. Never hardcode them inline
|
|
11
|
+
or read them from an env var.
|
|
12
12
|
|
|
13
13
|
```ts
|
|
14
14
|
import { liteClient as algoliasearch } from 'algoliasearch/lite'
|
|
@@ -5,10 +5,10 @@ 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`
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
them from an env var.
|
|
8
|
+
`ALGOLIA_APP_ID`, `ALGOLIA_SEARCH_API_KEY`, and `ALGOLIA_INDEX_NAME` are exported
|
|
9
|
+
constants in the project's shared client-side config module. Commit them only
|
|
10
|
+
when a public search hit cannot return personal data. Never hardcode them inline
|
|
11
|
+
or read them from an env var.
|
|
12
12
|
|
|
13
13
|
```ts
|
|
14
14
|
import { algoliasearch } from 'algoliasearch'
|