@algolia/wizard 0.8.0-rc.49.42 → 0.8.0-rc.58.44
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 +149 -233
- package/package.json +2 -2
package/dist/main.js
CHANGED
|
@@ -532,6 +532,8 @@ var CANCEL = "cancel";
|
|
|
532
532
|
var ARROW_WIDTH = 4;
|
|
533
533
|
var COLUMN_GAP = 2;
|
|
534
534
|
var BAR_PADDING = 2;
|
|
535
|
+
var ROW_HEIGHT = 3;
|
|
536
|
+
var INDICATOR_ROWS = 2;
|
|
535
537
|
function fittedWidth(node, columns) {
|
|
536
538
|
let left = 0;
|
|
537
539
|
for (let n = node; n; n = n.parentNode) {
|
|
@@ -564,13 +566,18 @@ function SelectPrompt({
|
|
|
564
566
|
if (multi) hints.push({ key: "[space]", label: "select" });
|
|
565
567
|
hints.push({ key: "[enter]", label: "confirm" });
|
|
566
568
|
const containerRef = useRef2(null);
|
|
567
|
-
const
|
|
569
|
+
const viewportRef = useRef2(null);
|
|
570
|
+
const { columns, rows: windowRows } = useWindowSize3();
|
|
568
571
|
const [width, setWidth] = useState3(columns);
|
|
572
|
+
const [viewportHeight, setViewportHeight] = useState3(null);
|
|
569
573
|
useLayoutEffect(() => {
|
|
570
574
|
if (containerRef.current) {
|
|
571
575
|
setWidth(fittedWidth(containerRef.current, columns));
|
|
572
576
|
}
|
|
573
|
-
|
|
577
|
+
if (viewportRef.current) {
|
|
578
|
+
setViewportHeight(measureElement2(viewportRef.current).height);
|
|
579
|
+
}
|
|
580
|
+
}, [columns, windowRows, error, question, helpText, messages, table]);
|
|
574
581
|
const inner = Math.max(width - BAR_PADDING, 0);
|
|
575
582
|
const labelWidth = Math.min(
|
|
576
583
|
ARROW_WIDTH + (multi ? 2 : 0) + Math.max(0, ...rows.map((opt) => opt.length)) + COLUMN_GAP,
|
|
@@ -586,6 +593,22 @@ function SelectPrompt({
|
|
|
586
593
|
const barWidth = Math.min(labelWidth + badgeWidth + BAR_PADDING, width);
|
|
587
594
|
const barLabelWidth = Math.max(barWidth - BAR_PADDING - badgeWidth, 0);
|
|
588
595
|
const textWidth = inner - labelWidth;
|
|
596
|
+
const capacity = viewportHeight === null || rows.length * ROW_HEIGHT <= viewportHeight ? rows.length : Math.max(Math.floor((viewportHeight - INDICATOR_ROWS) / ROW_HEIGHT), 1);
|
|
597
|
+
const maxOffset = Math.max(rows.length - capacity, 0);
|
|
598
|
+
const [offset, setOffset] = useState3(0);
|
|
599
|
+
useLayoutEffect(() => {
|
|
600
|
+
setOffset((o) => {
|
|
601
|
+
const clamped = Math.min(o, maxOffset);
|
|
602
|
+
if (index < clamped) return index;
|
|
603
|
+
if (index >= clamped + capacity) {
|
|
604
|
+
return Math.min(index - capacity + 1, maxOffset);
|
|
605
|
+
}
|
|
606
|
+
return clamped;
|
|
607
|
+
});
|
|
608
|
+
}, [index, capacity, maxOffset]);
|
|
609
|
+
const visible = rows.slice(offset, offset + capacity);
|
|
610
|
+
const hiddenAbove = offset;
|
|
611
|
+
const hiddenBelow = rows.length - offset - visible.length;
|
|
589
612
|
useInput((input, key) => {
|
|
590
613
|
if (rows.length === 0) return;
|
|
591
614
|
if (key.upArrow || input === "k") {
|
|
@@ -610,56 +633,71 @@ function SelectPrompt({
|
|
|
610
633
|
}
|
|
611
634
|
});
|
|
612
635
|
return /* @__PURE__ */ jsx4(Box4, { ref: containerRef, flexGrow: 1, children: /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 1, width, children: [
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
636
|
+
/* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 1, flexShrink: 0, children: [
|
|
637
|
+
error && /* @__PURE__ */ jsx4(Text4, { color: COLORS.danger, children: error }),
|
|
638
|
+
messages?.map((m, i) => /* @__PURE__ */ jsx4(Text4, { color: COLORS.muted, children: m }, `msg-${i}`)),
|
|
639
|
+
table && /* @__PURE__ */ jsx4(Table, { columns: table.columns, rows: table.rows }),
|
|
640
|
+
/* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", children: [
|
|
641
|
+
question && /* @__PURE__ */ jsx4(Text4, { color: COLORS.muted, children: question }),
|
|
642
|
+
helpText && /* @__PURE__ */ jsx4(Text4, { color: COLORS.dim, children: helpText })
|
|
643
|
+
] })
|
|
619
644
|
] }),
|
|
620
|
-
/* @__PURE__ */
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
645
|
+
/* @__PURE__ */ jsxs3(Box4, { ref: viewportRef, flexDirection: "column", flexGrow: 1, children: [
|
|
646
|
+
hiddenAbove > 0 && /* @__PURE__ */ jsxs3(Text4, { color: COLORS.dim, children: [
|
|
647
|
+
"\u2191 ",
|
|
648
|
+
hiddenAbove,
|
|
649
|
+
" more"
|
|
650
|
+
] }),
|
|
651
|
+
visible.map((option, visibleIndex) => {
|
|
652
|
+
const i = offset + visibleIndex;
|
|
653
|
+
const highlighted = i === index;
|
|
654
|
+
const isCancel = i === cancelIndex;
|
|
655
|
+
const bullet = multi && !isCancel ? checked.has(i) ? "\u25CF " : "\u25CB " : "";
|
|
656
|
+
const sec = isCancel ? void 0 : secondary?.[i];
|
|
657
|
+
const labelColor = highlighted ? COLORS.highlight.fg : void 0;
|
|
658
|
+
const label = /* @__PURE__ */ jsxs3(Text4, { color: labelColor, wrap: "truncate", children: [
|
|
659
|
+
highlighted ? "\u276F " : " ",
|
|
660
|
+
bullet,
|
|
661
|
+
option
|
|
662
|
+
] });
|
|
663
|
+
const isText = sec?.kind === "text";
|
|
664
|
+
return /* @__PURE__ */ jsxs3(
|
|
665
|
+
Box4,
|
|
666
|
+
{
|
|
667
|
+
width: isText ? "100%" : barWidth,
|
|
668
|
+
paddingX: 1,
|
|
669
|
+
paddingY: 1,
|
|
670
|
+
backgroundColor: highlighted ? COLORS.highlight.bg : void 0,
|
|
671
|
+
children: [
|
|
672
|
+
/* @__PURE__ */ jsx4(Box4, { width: isText ? labelWidth : barLabelWidth, children: label }),
|
|
673
|
+
isText && textWidth > 0 && /* @__PURE__ */ jsx4(Box4, { width: textWidth, children: /* @__PURE__ */ jsx4(
|
|
674
|
+
Text4,
|
|
675
|
+
{
|
|
676
|
+
wrap: "truncate",
|
|
677
|
+
color: highlighted ? COLORS.primary : COLORS.muted,
|
|
678
|
+
children: sec.value
|
|
679
|
+
}
|
|
680
|
+
) }),
|
|
681
|
+
sec?.kind === "badge" && /* @__PURE__ */ jsx4(Box4, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx4(Text4, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
|
|
682
|
+
]
|
|
683
|
+
},
|
|
684
|
+
`row-${i}`
|
|
685
|
+
);
|
|
686
|
+
}),
|
|
687
|
+
hiddenBelow > 0 && /* @__PURE__ */ jsxs3(Text4, { color: COLORS.dim, children: [
|
|
688
|
+
"\u2193 ",
|
|
689
|
+
hiddenBelow,
|
|
690
|
+
" more"
|
|
691
|
+
] })
|
|
692
|
+
] }),
|
|
693
|
+
/* @__PURE__ */ jsx4(Box4, { flexShrink: 0, children: /* @__PURE__ */ jsx4(Text4, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs3(Text4, { children: [
|
|
656
694
|
i > 0 ? " " : "",
|
|
657
695
|
/* @__PURE__ */ jsx4(Text4, { color: COLORS.primary, children: key }),
|
|
658
696
|
/* @__PURE__ */ jsxs3(Text4, { color: COLORS.dim, children: [
|
|
659
697
|
" ",
|
|
660
698
|
label
|
|
661
699
|
] })
|
|
662
|
-
] }, label)) })
|
|
700
|
+
] }, label)) }) })
|
|
663
701
|
] }) });
|
|
664
702
|
}
|
|
665
703
|
|
|
@@ -692,7 +730,7 @@ function PromptInput() {
|
|
|
692
730
|
}
|
|
693
731
|
if (phase !== "awaitingInput" || !inputReq) return null;
|
|
694
732
|
if (inputReq.promptType === "multipleChoice") {
|
|
695
|
-
return /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(
|
|
733
|
+
return /* @__PURE__ */ jsx5(Box5, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
|
|
696
734
|
SelectPrompt,
|
|
697
735
|
{
|
|
698
736
|
question: inputReq.prompt,
|
|
@@ -709,7 +747,7 @@ function PromptInput() {
|
|
|
709
747
|
) });
|
|
710
748
|
}
|
|
711
749
|
if (inputReq.promptType === "multiSelect") {
|
|
712
|
-
return /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(
|
|
750
|
+
return /* @__PURE__ */ jsx5(Box5, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
|
|
713
751
|
SelectPrompt,
|
|
714
752
|
{
|
|
715
753
|
multi: true,
|
|
@@ -724,7 +762,7 @@ function PromptInput() {
|
|
|
724
762
|
) });
|
|
725
763
|
}
|
|
726
764
|
if (inputReq.promptType === "notice") {
|
|
727
|
-
return /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(
|
|
765
|
+
return /* @__PURE__ */ jsx5(Box5, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
|
|
728
766
|
SelectPrompt,
|
|
729
767
|
{
|
|
730
768
|
question: inputReq.prompt,
|
|
@@ -746,7 +784,7 @@ function PromptInput() {
|
|
|
746
784
|
}
|
|
747
785
|
if (inputReq.promptType === "acceptReject") {
|
|
748
786
|
const labels = inputReq.options?.length ? inputReq.options : ACCEPT_REJECT_OPTIONS;
|
|
749
|
-
return /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(
|
|
787
|
+
return /* @__PURE__ */ jsx5(Box5, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
|
|
750
788
|
SelectPrompt,
|
|
751
789
|
{
|
|
752
790
|
question: inputReq.prompt,
|
|
@@ -1527,7 +1565,10 @@ function App() {
|
|
|
1527
1565
|
justifyContent: "space-between",
|
|
1528
1566
|
children: [
|
|
1529
1567
|
showLogs ? /* @__PURE__ */ jsx13(Logs, {}) : (
|
|
1530
|
-
/* Fill the
|
|
1568
|
+
/* Fill the space the sidebar/ribbon leaves — width beside the
|
|
1569
|
+
sidebar, height above the ribbon. The height matters even
|
|
1570
|
+
stacked: it is what the prompt's scrolling list measures itself
|
|
1571
|
+
against (see SelectPrompt). */
|
|
1531
1572
|
/* @__PURE__ */ jsxs12(
|
|
1532
1573
|
Box13,
|
|
1533
1574
|
{
|
|
@@ -1535,7 +1576,7 @@ function App() {
|
|
|
1535
1576
|
paddingX: 4,
|
|
1536
1577
|
paddingY: 2,
|
|
1537
1578
|
width: showSidebar ? 70 : "100%",
|
|
1538
|
-
flexGrow:
|
|
1579
|
+
flexGrow: 1,
|
|
1539
1580
|
children: [
|
|
1540
1581
|
/* @__PURE__ */ jsx13(Notices, {}),
|
|
1541
1582
|
/* @__PURE__ */ jsx13(PromptInput, {}),
|
|
@@ -1568,8 +1609,7 @@ var configFile = () => join5(stateDir(), "config.json");
|
|
|
1568
1609
|
var DEFAULT_CONFIG = {
|
|
1569
1610
|
version: 1,
|
|
1570
1611
|
aiConsent: false,
|
|
1571
|
-
workflowsRun: []
|
|
1572
|
-
searchApiKeys: {}
|
|
1612
|
+
workflowsRun: []
|
|
1573
1613
|
};
|
|
1574
1614
|
async function loadConfig() {
|
|
1575
1615
|
try {
|
|
@@ -1588,38 +1628,6 @@ async function recordWorkflowRun(workflowId, completedAt) {
|
|
|
1588
1628
|
config.workflowsRun.push({ workflowId, completedAt });
|
|
1589
1629
|
await saveConfig(config);
|
|
1590
1630
|
}
|
|
1591
|
-
function isStoredSearchKey(value) {
|
|
1592
|
-
if (typeof value !== "object" || value === null) return false;
|
|
1593
|
-
const { appId, key } = value;
|
|
1594
|
-
return typeof appId === "string" && !!appId && typeof key === "string" && !!key;
|
|
1595
|
-
}
|
|
1596
|
-
function storedSearchKeys(config) {
|
|
1597
|
-
const stored = config.searchApiKeys;
|
|
1598
|
-
if (typeof stored !== "object" || stored === null || Array.isArray(stored)) {
|
|
1599
|
-
return {};
|
|
1600
|
-
}
|
|
1601
|
-
return stored;
|
|
1602
|
-
}
|
|
1603
|
-
async function getStoredSearchKey(index, appId) {
|
|
1604
|
-
const entry = storedSearchKeys(await loadConfig())[index];
|
|
1605
|
-
if (!isStoredSearchKey(entry) || entry.appId !== appId) return void 0;
|
|
1606
|
-
return entry.key;
|
|
1607
|
-
}
|
|
1608
|
-
async function storeSearchKey(index, appId, key) {
|
|
1609
|
-
const config = await loadConfig();
|
|
1610
|
-
config.searchApiKeys = {
|
|
1611
|
-
...storedSearchKeys(config),
|
|
1612
|
-
[index]: { appId, key }
|
|
1613
|
-
};
|
|
1614
|
-
await saveConfig(config);
|
|
1615
|
-
}
|
|
1616
|
-
async function forgetSearchKey(index) {
|
|
1617
|
-
const config = await loadConfig();
|
|
1618
|
-
const remaining = { ...storedSearchKeys(config) };
|
|
1619
|
-
delete remaining[index];
|
|
1620
|
-
config.searchApiKeys = remaining;
|
|
1621
|
-
await saveConfig(config);
|
|
1622
|
-
}
|
|
1623
1631
|
|
|
1624
1632
|
// src/core/orchestrator.ts
|
|
1625
1633
|
function defineStep(step) {
|
|
@@ -2734,7 +2742,7 @@ async function runAnalysis(mode, extraInstructions = []) {
|
|
|
2734
2742
|
// package.json
|
|
2735
2743
|
var package_default = {
|
|
2736
2744
|
name: "@algolia/wizard",
|
|
2737
|
-
version: "0.8.0-rc.
|
|
2745
|
+
version: "0.8.0-rc.58.44",
|
|
2738
2746
|
description: "Magically implement Algolia functionality in your codebase",
|
|
2739
2747
|
type: "module",
|
|
2740
2748
|
engines: {
|
|
@@ -2782,7 +2790,7 @@ var package_default = {
|
|
|
2782
2790
|
dependencies: {
|
|
2783
2791
|
"@ai-sdk/anthropic": "^3.0.81",
|
|
2784
2792
|
"@ai-sdk/openai-compatible": "^2.0.47",
|
|
2785
|
-
"@algolia/cli": "^5.
|
|
2793
|
+
"@algolia/cli": "^5.11.0",
|
|
2786
2794
|
"@hono/node-server": "^2.0.10",
|
|
2787
2795
|
"@mishieck/ink-titled-box": "^0.4.2",
|
|
2788
2796
|
"@segment/analytics-node": "^3.1.0",
|
|
@@ -3340,23 +3348,6 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
|
|
|
3340
3348
|
function hasEnvVar(content, name) {
|
|
3341
3349
|
return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
|
|
3342
3350
|
}
|
|
3343
|
-
async function readEnvVar(worktreePath, name) {
|
|
3344
|
-
let content;
|
|
3345
|
-
try {
|
|
3346
|
-
content = await readFile8(join10(worktreePath, ".env"), "utf8");
|
|
3347
|
-
} catch (err) {
|
|
3348
|
-
if (err.code !== "ENOENT") throw err;
|
|
3349
|
-
return void 0;
|
|
3350
|
-
}
|
|
3351
|
-
const match = new RegExp(
|
|
3352
|
-
`^[ \\t]*(?:export[ \\t]+)?${name}[ \\t]*=[ \\t]*(.*)$`,
|
|
3353
|
-
"m"
|
|
3354
|
-
).exec(content);
|
|
3355
|
-
if (!match) return void 0;
|
|
3356
|
-
const value = match[1].trim().replace(/^(['"])(.*)\1$/, "$2").trim();
|
|
3357
|
-
if (!value || value.startsWith("<")) return void 0;
|
|
3358
|
-
return value;
|
|
3359
|
-
}
|
|
3360
3351
|
async function writeSearchEnvValues(worktreePath, vars) {
|
|
3361
3352
|
const target = join10(worktreePath, ".env");
|
|
3362
3353
|
let existing = "";
|
|
@@ -3431,72 +3422,50 @@ async function confirmDirtyWorkingTree(ctx, repoRoot) {
|
|
|
3431
3422
|
|
|
3432
3423
|
// src/lib/algoliaApiKey.ts
|
|
3433
3424
|
import { z as z23 } from "zod";
|
|
3425
|
+
var SAFE_ACLS = /* @__PURE__ */ new Set(["search", "browse", "listIndexes"]);
|
|
3426
|
+
var apiKeySchema = z23.object({
|
|
3427
|
+
value: z23.string().min(1),
|
|
3428
|
+
acl: z23.array(z23.string()).default([]),
|
|
3429
|
+
indexes: z23.array(z23.string()).default([])
|
|
3430
|
+
});
|
|
3431
|
+
var apiKeyListSchema = z23.object({
|
|
3432
|
+
items: z23.array(apiKeySchema).optional(),
|
|
3433
|
+
keys: z23.array(apiKeySchema).optional()
|
|
3434
|
+
}).transform((o) => o.items ?? o.keys ?? []);
|
|
3434
3435
|
var createdKeySchema = z23.object({
|
|
3435
3436
|
key: z23.string().min(1).optional(),
|
|
3436
3437
|
value: z23.string().min(1).optional()
|
|
3437
|
-
})
|
|
3438
|
-
|
|
3439
|
-
|
|
3438
|
+
});
|
|
3439
|
+
function canReuse(key, index) {
|
|
3440
|
+
return key.acl.includes("search") && key.acl.every((acl) => SAFE_ACLS.has(acl)) && (key.indexes.length === 0 || key.indexes.includes("*") || key.indexes.includes(index));
|
|
3441
|
+
}
|
|
3442
|
+
async function createSearchKey(index) {
|
|
3440
3443
|
const stdout = await runAlgoliaCli([
|
|
3441
3444
|
"apikeys",
|
|
3442
3445
|
"create",
|
|
3443
|
-
"--acl",
|
|
3444
|
-
"search",
|
|
3445
3446
|
"--indices",
|
|
3446
3447
|
index,
|
|
3448
|
+
"--acl",
|
|
3449
|
+
"search,browse",
|
|
3447
3450
|
"--description",
|
|
3448
|
-
`
|
|
3451
|
+
`wizard search-only key for ${index}`,
|
|
3449
3452
|
"-o",
|
|
3450
3453
|
"json"
|
|
3451
3454
|
]);
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
payload = JSON.parse(stdout);
|
|
3455
|
-
} catch {
|
|
3456
|
-
throw new Error("apikeys create returned output that is not valid JSON");
|
|
3457
|
-
}
|
|
3458
|
-
const created = createdKeySchema.parse(payload);
|
|
3455
|
+
const { key, value } = createdKeySchema.parse(JSON.parse(stdout));
|
|
3456
|
+
const created = key ?? value;
|
|
3459
3457
|
if (!created) throw new Error("apikeys create returned no key value");
|
|
3460
3458
|
return created;
|
|
3461
3459
|
}
|
|
3462
|
-
async function
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
return
|
|
3468
|
-
}
|
|
3469
|
-
}
|
|
3470
|
-
async function resolveSearchOnlyKey(index, appId, envKey) {
|
|
3471
|
-
if (envKey) {
|
|
3472
|
-
await recordSearchKey(index, appId, envKey);
|
|
3473
|
-
return { key: envKey, source: "env" };
|
|
3474
|
-
}
|
|
3475
|
-
const stored = await getStoredSearchKey(index, appId);
|
|
3476
|
-
if (stored) {
|
|
3477
|
-
if (await apiKeyExists(stored)) {
|
|
3478
|
-
logger.info({ index, appId }, "reusing the stored search-only API key");
|
|
3479
|
-
return { key: stored, source: "config" };
|
|
3480
|
-
}
|
|
3481
|
-
logger.warn(
|
|
3482
|
-
{ index, appId },
|
|
3483
|
-
"the stored search-only API key no longer exists; creating a replacement"
|
|
3484
|
-
);
|
|
3485
|
-
await forgetSearchKey(index);
|
|
3486
|
-
}
|
|
3487
|
-
const key = await createSearchOnlyKey(index);
|
|
3488
|
-
await recordSearchKey(index, appId, key);
|
|
3489
|
-
return { key, source: "created" };
|
|
3490
|
-
}
|
|
3491
|
-
async function recordSearchKey(index, appId, key) {
|
|
3492
|
-
try {
|
|
3493
|
-
await storeSearchKey(index, appId, key);
|
|
3494
|
-
} catch (err) {
|
|
3495
|
-
logger.warn(
|
|
3496
|
-
{ err: err.message, index },
|
|
3497
|
-
"could not record the search-only API key; a later run may create another"
|
|
3498
|
-
);
|
|
3460
|
+
async function resolveSearchOnlyKey(index) {
|
|
3461
|
+
const stdout = await runAlgoliaCli(["apikeys", "list", "-o", "json"]);
|
|
3462
|
+
const existing = apiKeyListSchema.parse(JSON.parse(stdout)).find((key) => canReuse(key, index))?.value;
|
|
3463
|
+
if (existing) {
|
|
3464
|
+
logger.info({ index }, "reusing existing search-only API key");
|
|
3465
|
+
return existing;
|
|
3499
3466
|
}
|
|
3467
|
+
logger.info({ index }, "no reusable search-only key found; creating one");
|
|
3468
|
+
return createSearchKey(index);
|
|
3500
3469
|
}
|
|
3501
3470
|
|
|
3502
3471
|
// src/lib/algoliaDocs.ts
|
|
@@ -3651,22 +3620,15 @@ function publicEnvPrefix(language) {
|
|
|
3651
3620
|
}
|
|
3652
3621
|
return "PUBLIC_";
|
|
3653
3622
|
}
|
|
3654
|
-
var APP_ID_VAR_SUFFIX = "ALGOLIA_APP_ID";
|
|
3655
|
-
var SEARCH_KEY_VAR_SUFFIX = "ALGOLIA_SEARCH_API_KEY";
|
|
3656
|
-
function appIdVar(language) {
|
|
3657
|
-
return `${publicEnvPrefix(language)}${APP_ID_VAR_SUFFIX}`;
|
|
3658
|
-
}
|
|
3659
|
-
function searchKeyVar(language) {
|
|
3660
|
-
return `${publicEnvPrefix(language)}${SEARCH_KEY_VAR_SUFFIX}`;
|
|
3661
|
-
}
|
|
3662
3623
|
function searchEnvVars(language, appId, searchKey) {
|
|
3624
|
+
const prefix = publicEnvPrefix(language);
|
|
3663
3625
|
return [
|
|
3664
3626
|
{
|
|
3665
|
-
name:
|
|
3627
|
+
name: `${prefix}ALGOLIA_APP_ID`,
|
|
3666
3628
|
value: appId ?? "<your-algolia-app-id>"
|
|
3667
3629
|
},
|
|
3668
3630
|
{
|
|
3669
|
-
name:
|
|
3631
|
+
name: `${prefix}ALGOLIA_SEARCH_API_KEY`,
|
|
3670
3632
|
value: searchKey ?? "<your-algolia-search-only-api-key>"
|
|
3671
3633
|
}
|
|
3672
3634
|
];
|
|
@@ -3728,13 +3690,15 @@ function searchInstructions(input) {
|
|
|
3728
3690
|
"Follow the Algolia JS SDK reference below for client setup and InstantSearch wiring; prefer it over prior knowledge:",
|
|
3729
3691
|
doc,
|
|
3730
3692
|
`Add the search UI at ${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 SearchBox and Hits against the "${input.targetIndex}" index.`,
|
|
3731
|
-
// The key is provisioned only after verification passes, so the agent never
|
|
3732
|
-
// sees one. It must also leave .env alone: the wizard reads that file to
|
|
3733
|
-
// decide whether a key already exists, and an agent-invented value there
|
|
3734
|
-
// would be reused as if it were real.
|
|
3735
|
-
`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.`,
|
|
3736
|
-
`Use exactly these public env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
|
|
3737
3693
|
"Read the App ID and a search-only API key from public env vars; never hardcode them. A search-only key is safe to expose client-side.",
|
|
3694
|
+
// appId always resolves (loadActiveProfile throws otherwise); only the
|
|
3695
|
+
// search-only key is best-effort and can fall back to a placeholder.
|
|
3696
|
+
`Values: App ID "${input.appId}", search-only key ${input.searchKey ? `"${input.searchKey}"` : "(placeholder for the developer to fill in)"}.`,
|
|
3697
|
+
// Names are fixed, not the agent's to rename: the wizard writes the
|
|
3698
|
+
// resolved app id / search-only key into ".env" under these exact names
|
|
3699
|
+
// right after this step, so a renamed prefix here would leave the code
|
|
3700
|
+
// reading a var the wizard never wrote.
|
|
3701
|
+
`Use exactly these public env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
|
|
3738
3702
|
'Add any Algolia/InstantSearch packages you import to package.json "dependencies" with a valid version range; the wizard installs them in the worktree after you finish.',
|
|
3739
3703
|
"The summary should be extremely concise; do not mention env var setup or manual testing steps \u2014 the wizard writes the resolved credentials to .env and reports that separately."
|
|
3740
3704
|
];
|
|
@@ -3884,8 +3848,17 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3884
3848
|
const confirmed2 = normalized.confirmedEntities;
|
|
3885
3849
|
const searchLocation = normalized.searchImplementationAnalysis;
|
|
3886
3850
|
let appId;
|
|
3851
|
+
let searchKey;
|
|
3887
3852
|
if (useCases.includes("search")) {
|
|
3888
3853
|
appId = (await loadActiveProfile()).appId;
|
|
3854
|
+
try {
|
|
3855
|
+
searchKey = await resolveSearchOnlyKey(targetIndex);
|
|
3856
|
+
} catch (err) {
|
|
3857
|
+
logger.warn(
|
|
3858
|
+
{ err: err.message },
|
|
3859
|
+
"implement: could not resolve a search-only API key; the agent will scaffold a placeholder"
|
|
3860
|
+
);
|
|
3861
|
+
}
|
|
3889
3862
|
}
|
|
3890
3863
|
const worktree = existingWorktreePath ?? (await createWorktree(repoRoot)).path;
|
|
3891
3864
|
try {
|
|
@@ -3917,9 +3890,8 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3917
3890
|
targetIndex,
|
|
3918
3891
|
language,
|
|
3919
3892
|
appId,
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
searchEnvVars: searchEnvVars(language, appId),
|
|
3893
|
+
searchKey,
|
|
3894
|
+
searchEnvVars: searchEnvVars(language, appId, searchKey),
|
|
3923
3895
|
ingestDir: INGEST_DIR,
|
|
3924
3896
|
ingestionSource,
|
|
3925
3897
|
uploadFilePath,
|
|
@@ -3929,24 +3901,6 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3929
3901
|
};
|
|
3930
3902
|
const summaries = [];
|
|
3931
3903
|
if (uploadWarning) summaries.push(uploadWarning);
|
|
3932
|
-
let envSearchKey;
|
|
3933
|
-
let envAppIdMismatch = false;
|
|
3934
|
-
if (useCases.includes("search") && appId) {
|
|
3935
|
-
const envAppId = await readEnvVar(worktree, appIdVar(language));
|
|
3936
|
-
if (envAppId === appId) {
|
|
3937
|
-
envSearchKey = await readEnvVar(worktree, searchKeyVar(language));
|
|
3938
|
-
} else if (envAppId) {
|
|
3939
|
-
envAppIdMismatch = true;
|
|
3940
|
-
summaries.push(
|
|
3941
|
-
`\u26A0\uFE0F .env already sets ${appIdVar(language)}=${envAppId}, but the active Algolia application is ${appId}. The wizard left those values alone \u2014 update ${appIdVar(language)} and ${searchKeyVar(language)} by hand, or searches will fail.`
|
|
3942
|
-
);
|
|
3943
|
-
logger.warn(
|
|
3944
|
-
{ envAppId, appId },
|
|
3945
|
-
"implement: .env holds credentials for a different Algolia application; not reusing its search key"
|
|
3946
|
-
);
|
|
3947
|
-
}
|
|
3948
|
-
}
|
|
3949
|
-
let finalSearchEnvVars = input.searchEnvVars;
|
|
3950
3904
|
let agentRuns = 0;
|
|
3951
3905
|
let ingestRuntime;
|
|
3952
3906
|
let ingestEntrypoint;
|
|
@@ -4139,29 +4093,7 @@ ${run2.output}` : status;
|
|
|
4139
4093
|
}
|
|
4140
4094
|
extraInstructions = verificationRetryInstructions(verification);
|
|
4141
4095
|
}
|
|
4142
|
-
|
|
4143
|
-
let searchKeyError;
|
|
4144
|
-
if (appId) {
|
|
4145
|
-
try {
|
|
4146
|
-
const resolved = await resolveSearchOnlyKey(
|
|
4147
|
-
targetIndex,
|
|
4148
|
-
appId,
|
|
4149
|
-
envSearchKey
|
|
4150
|
-
);
|
|
4151
|
-
searchKey = resolved.key;
|
|
4152
|
-
summaries.push(
|
|
4153
|
-
resolved.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}.`
|
|
4154
|
-
);
|
|
4155
|
-
} catch (err) {
|
|
4156
|
-
searchKeyError = err.message;
|
|
4157
|
-
logger.warn(
|
|
4158
|
-
{ err: searchKeyError },
|
|
4159
|
-
"implement: could not provision a search-only API key; the .env value stays a placeholder"
|
|
4160
|
-
);
|
|
4161
|
-
}
|
|
4162
|
-
}
|
|
4163
|
-
finalSearchEnvVars = searchEnvVars(language, appId, searchKey);
|
|
4164
|
-
const resolvedSearchEnvVars = finalSearchEnvVars.filter(
|
|
4096
|
+
const resolvedSearchEnvVars = input.searchEnvVars.filter(
|
|
4165
4097
|
(v) => !v.value.startsWith("<")
|
|
4166
4098
|
);
|
|
4167
4099
|
if (resolvedSearchEnvVars.length > 0) {
|
|
@@ -4172,29 +4104,13 @@ ${run2.output}` : status;
|
|
|
4172
4104
|
if (written.length > 0) {
|
|
4173
4105
|
summaries.push(`Wrote ${written.join(", ")} to .env.`);
|
|
4174
4106
|
}
|
|
4175
|
-
const stale = [];
|
|
4176
|
-
for (const v of resolvedSearchEnvVars) {
|
|
4177
|
-
if (written.includes(v.name)) continue;
|
|
4178
|
-
const current = await readEnvVar(worktree, v.name);
|
|
4179
|
-
if (current && current !== v.value) stale.push(v);
|
|
4180
|
-
}
|
|
4181
|
-
if (stale.length > 0 && !envAppIdMismatch) {
|
|
4182
|
-
summaries.push(
|
|
4183
|
-
`\u26A0\uFE0F .env already assigns a different value to ${stale.map((v) => `${v.name} (should be ${v.value})`).join(", ")} \u2014 the wizard left it alone. Fix it by hand, or searches will fail.`
|
|
4184
|
-
);
|
|
4185
|
-
logger.warn(
|
|
4186
|
-
{ vars: stale.map((v) => v.name) },
|
|
4187
|
-
"implement: .env holds different values for the resolved search credentials; not overwriting them"
|
|
4188
|
-
);
|
|
4189
|
-
}
|
|
4190
4107
|
}
|
|
4191
|
-
const unresolvedSearchEnvVars =
|
|
4108
|
+
const unresolvedSearchEnvVars = input.searchEnvVars.filter(
|
|
4192
4109
|
(v) => v.value.startsWith("<")
|
|
4193
4110
|
);
|
|
4194
4111
|
if (unresolvedSearchEnvVars.length > 0) {
|
|
4195
4112
|
summaries.push(
|
|
4196
|
-
`Could not resolve a value for ${unresolvedSearchEnvVars.map((v) => v.name).join(", ")} \u2014 fill it in manually in .env.`
|
|
4197
|
-
(searchKeyError ? ` Reason: ${searchKeyError}` : "")
|
|
4113
|
+
`Could not resolve a value for ${unresolvedSearchEnvVars.map((v) => v.name).join(", ")} \u2014 fill it in manually in .env.`
|
|
4198
4114
|
);
|
|
4199
4115
|
}
|
|
4200
4116
|
} else {
|
|
@@ -4226,7 +4142,7 @@ ${run2.output}` : status;
|
|
|
4226
4142
|
...ingestRecordCount != null ? { ingestRecordCount } : {},
|
|
4227
4143
|
...ingestDurationMs != null ? { ingestDurationMs } : {}
|
|
4228
4144
|
} : {},
|
|
4229
|
-
...useCases.includes("search") ? { searchEnvVars:
|
|
4145
|
+
...useCases.includes("search") ? { searchEnvVars: input.searchEnvVars } : {}
|
|
4230
4146
|
};
|
|
4231
4147
|
} finally {
|
|
4232
4148
|
process.chdir(repoRoot);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@algolia/wizard",
|
|
3
|
-
"version": "0.8.0-rc.
|
|
3
|
+
"version": "0.8.0-rc.58.44",
|
|
4
4
|
"description": "Magically implement Algolia functionality in your codebase",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@ai-sdk/anthropic": "^3.0.81",
|
|
50
50
|
"@ai-sdk/openai-compatible": "^2.0.47",
|
|
51
|
-
"@algolia/cli": "^5.
|
|
51
|
+
"@algolia/cli": "^5.11.0",
|
|
52
52
|
"@hono/node-server": "^2.0.10",
|
|
53
53
|
"@mishieck/ink-titled-box": "^0.4.2",
|
|
54
54
|
"@segment/analytics-node": "^3.1.0",
|