@algolia/wizard 0.8.0-rc.65.53 → 0.8.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 +620 -1417
- package/docs/algolia-sdk/README.md +26 -47
- package/docs/algolia-sdk/search-single-index.md +42 -0
- package/package.json +2 -2
- package/docs/algolia-sdk/instantsearch-setup-templates.md +0 -92
- package/docs/algolia-sdk/save-records-csharp.md +0 -71
- package/docs/algolia-sdk/save-records-go.md +0 -62
- package/docs/algolia-sdk/save-records-java.md +0 -66
- package/docs/algolia-sdk/save-records-kotlin.md +0 -60
- package/docs/algolia-sdk/save-records-php.md +0 -50
- package/docs/algolia-sdk/save-records-python.md +0 -51
- package/docs/algolia-sdk/save-records-ruby.md +0 -48
- package/docs/algolia-sdk/save-records-scala.md +0 -68
package/dist/main.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { render } from "ink";
|
|
5
5
|
|
|
6
6
|
// src/ui/App.tsx
|
|
7
|
-
import { Box as
|
|
7
|
+
import { Box as Box14, Text as Text14, useApp, useInput as useInput6, useWindowSize as useWindowSize7 } from "ink";
|
|
8
8
|
|
|
9
9
|
// src/core/store.ts
|
|
10
10
|
import { create } from "zustand";
|
|
@@ -497,9 +497,9 @@ function Notices() {
|
|
|
497
497
|
}
|
|
498
498
|
|
|
499
499
|
// src/ui/PromptInput.tsx
|
|
500
|
-
import { Box as
|
|
500
|
+
import { Box as Box6, Text as Text6, useInput as useInput2 } from "ink";
|
|
501
501
|
import TextInput from "ink-text-input";
|
|
502
|
-
import { useState as
|
|
502
|
+
import { useState as useState5 } from "react";
|
|
503
503
|
|
|
504
504
|
// src/ui/NextAction.tsx
|
|
505
505
|
import { Box as Box3, Text as Text3 } from "ink";
|
|
@@ -525,13 +525,14 @@ function NextAction({
|
|
|
525
525
|
}
|
|
526
526
|
|
|
527
527
|
// src/ui/SelectPrompt.tsx
|
|
528
|
-
import { Box as
|
|
529
|
-
import { useLayoutEffect, useRef as
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
528
|
+
import { Box as Box5, Text as Text5, useInput, useWindowSize as useWindowSize4 } from "ink";
|
|
529
|
+
import { useLayoutEffect as useLayoutEffect2, useRef as useRef3, useState as useState4 } from "react";
|
|
530
|
+
|
|
531
|
+
// src/ui/ScrollView.tsx
|
|
532
|
+
import { Box as Box4, Text as Text4, measureElement as measureElement2, useWindowSize as useWindowSize3 } from "ink";
|
|
533
|
+
import { useCallback, useLayoutEffect, useRef as useRef2, useState as useState3 } from "react";
|
|
534
|
+
import { jsxs as jsxs3 } from "react/jsx-runtime";
|
|
535
|
+
var INDICATOR_ROWS = 2;
|
|
535
536
|
function fittedWidth(node, columns) {
|
|
536
537
|
let left = 0;
|
|
537
538
|
for (let n = node; n; n = n.parentNode) {
|
|
@@ -539,6 +540,89 @@ function fittedWidth(node, columns) {
|
|
|
539
540
|
}
|
|
540
541
|
return Math.max(Math.min(measureElement2(node).width, columns - left), 0);
|
|
541
542
|
}
|
|
543
|
+
function useScrollWindow({
|
|
544
|
+
itemCount,
|
|
545
|
+
rowHeight = 1,
|
|
546
|
+
followBottom = false
|
|
547
|
+
}) {
|
|
548
|
+
const viewportRef = useRef2(null);
|
|
549
|
+
const { columns } = useWindowSize3();
|
|
550
|
+
const [size, setSize] = useState3(
|
|
551
|
+
null
|
|
552
|
+
);
|
|
553
|
+
useLayoutEffect(() => {
|
|
554
|
+
if (!viewportRef.current) return;
|
|
555
|
+
const width = fittedWidth(viewportRef.current, columns);
|
|
556
|
+
const { height } = measureElement2(viewportRef.current);
|
|
557
|
+
setSize(
|
|
558
|
+
(prev) => prev?.width === width && prev.height === height ? prev : { width, height }
|
|
559
|
+
);
|
|
560
|
+
});
|
|
561
|
+
const capacity = size === null || itemCount * rowHeight <= size.height ? itemCount : Math.max(Math.floor((size.height - INDICATOR_ROWS) / rowHeight), 1);
|
|
562
|
+
const maxOffset = Math.max(itemCount - capacity, 0);
|
|
563
|
+
const [offset, setOffset] = useState3(0);
|
|
564
|
+
const prevMaxOffsetRef = useRef2(0);
|
|
565
|
+
useLayoutEffect(() => {
|
|
566
|
+
const wasAtBottom = offset >= prevMaxOffsetRef.current;
|
|
567
|
+
prevMaxOffsetRef.current = maxOffset;
|
|
568
|
+
setOffset(
|
|
569
|
+
(o) => followBottom && wasAtBottom ? maxOffset : Math.min(o, maxOffset)
|
|
570
|
+
);
|
|
571
|
+
}, [maxOffset, followBottom]);
|
|
572
|
+
const scrollBy = useCallback(
|
|
573
|
+
(delta) => {
|
|
574
|
+
setOffset((o) => Math.min(Math.max(o + delta, 0), maxOffset));
|
|
575
|
+
},
|
|
576
|
+
[maxOffset]
|
|
577
|
+
);
|
|
578
|
+
const revealIndex = useCallback(
|
|
579
|
+
(index) => {
|
|
580
|
+
setOffset((o) => {
|
|
581
|
+
if (index < o) return index;
|
|
582
|
+
if (index >= o + capacity) {
|
|
583
|
+
return Math.min(index - capacity + 1, maxOffset);
|
|
584
|
+
}
|
|
585
|
+
return o;
|
|
586
|
+
});
|
|
587
|
+
},
|
|
588
|
+
[capacity, maxOffset]
|
|
589
|
+
);
|
|
590
|
+
const visibleCount = Math.min(capacity, Math.max(itemCount - offset, 0));
|
|
591
|
+
return {
|
|
592
|
+
viewportRef,
|
|
593
|
+
width: size?.width ?? columns,
|
|
594
|
+
offset,
|
|
595
|
+
capacity,
|
|
596
|
+
maxOffset,
|
|
597
|
+
hiddenAbove: Math.min(offset, itemCount),
|
|
598
|
+
hiddenBelow: Math.max(itemCount - offset - visibleCount, 0),
|
|
599
|
+
scrollBy,
|
|
600
|
+
revealIndex
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
function ScrollView({ scroll, children }) {
|
|
604
|
+
return /* @__PURE__ */ jsxs3(Box4, { ref: scroll.viewportRef, flexDirection: "column", flexGrow: 1, children: [
|
|
605
|
+
scroll.hiddenAbove > 0 && /* @__PURE__ */ jsxs3(Text4, { color: COLORS.dim, children: [
|
|
606
|
+
"\u2191 ",
|
|
607
|
+
scroll.hiddenAbove,
|
|
608
|
+
" more"
|
|
609
|
+
] }),
|
|
610
|
+
children,
|
|
611
|
+
scroll.hiddenBelow > 0 && /* @__PURE__ */ jsxs3(Text4, { color: COLORS.dim, children: [
|
|
612
|
+
"\u2193 ",
|
|
613
|
+
scroll.hiddenBelow,
|
|
614
|
+
" more"
|
|
615
|
+
] })
|
|
616
|
+
] });
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// src/ui/SelectPrompt.tsx
|
|
620
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
621
|
+
var CANCEL = "cancel";
|
|
622
|
+
var ARROW_WIDTH = 4;
|
|
623
|
+
var COLUMN_GAP = 2;
|
|
624
|
+
var BAR_PADDING = 2;
|
|
625
|
+
var ROW_HEIGHT = 3;
|
|
542
626
|
function SelectPrompt({
|
|
543
627
|
options,
|
|
544
628
|
onSelect,
|
|
@@ -552,10 +636,10 @@ function SelectPrompt({
|
|
|
552
636
|
secondary,
|
|
553
637
|
defaultSelectedIndex = 0
|
|
554
638
|
}) {
|
|
555
|
-
const [index, setIndex] =
|
|
639
|
+
const [index, setIndex] = useState4(
|
|
556
640
|
() => defaultSelectedIndex > 0 && defaultSelectedIndex < options.length ? defaultSelectedIndex : 0
|
|
557
641
|
);
|
|
558
|
-
const [checked, setChecked] =
|
|
642
|
+
const [checked, setChecked] = useState4(() => /* @__PURE__ */ new Set());
|
|
559
643
|
const hasCancel = Boolean(multi || cancelable);
|
|
560
644
|
const rows = hasCancel ? [...options, "Cancel"] : options;
|
|
561
645
|
const cancelIndex = hasCancel ? options.length : -1;
|
|
@@ -563,14 +647,14 @@ function SelectPrompt({
|
|
|
563
647
|
if (rows.length > 1) hints.push({ key: "[\u2191] [\u2193]", label: "move" });
|
|
564
648
|
if (multi) hints.push({ key: "[space]", label: "select" });
|
|
565
649
|
hints.push({ key: "[enter]", label: "confirm" });
|
|
566
|
-
const containerRef =
|
|
567
|
-
const { columns } =
|
|
568
|
-
const [width, setWidth] =
|
|
569
|
-
|
|
570
|
-
if (containerRef.current)
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
}
|
|
650
|
+
const containerRef = useRef3(null);
|
|
651
|
+
const { columns } = useWindowSize4();
|
|
652
|
+
const [width, setWidth] = useState4(columns);
|
|
653
|
+
useLayoutEffect2(() => {
|
|
654
|
+
if (!containerRef.current) return;
|
|
655
|
+
const measured = fittedWidth(containerRef.current, columns);
|
|
656
|
+
setWidth((prev) => prev === measured ? prev : measured);
|
|
657
|
+
});
|
|
574
658
|
const inner = Math.max(width - BAR_PADDING, 0);
|
|
575
659
|
const labelWidth = Math.min(
|
|
576
660
|
ARROW_WIDTH + (multi ? 2 : 0) + Math.max(0, ...rows.map((opt) => opt.length)) + COLUMN_GAP,
|
|
@@ -586,6 +670,15 @@ function SelectPrompt({
|
|
|
586
670
|
const barWidth = Math.min(labelWidth + badgeWidth + BAR_PADDING, width);
|
|
587
671
|
const barLabelWidth = Math.max(barWidth - BAR_PADDING - badgeWidth, 0);
|
|
588
672
|
const textWidth = inner - labelWidth;
|
|
673
|
+
const scroll = useScrollWindow({
|
|
674
|
+
itemCount: rows.length,
|
|
675
|
+
rowHeight: ROW_HEIGHT
|
|
676
|
+
});
|
|
677
|
+
const { revealIndex } = scroll;
|
|
678
|
+
useLayoutEffect2(() => {
|
|
679
|
+
revealIndex(index);
|
|
680
|
+
}, [index, revealIndex]);
|
|
681
|
+
const visible = rows.slice(scroll.offset, scroll.offset + scroll.capacity);
|
|
589
682
|
useInput((input, key) => {
|
|
590
683
|
if (rows.length === 0) return;
|
|
591
684
|
if (key.upArrow || input === "k") {
|
|
@@ -609,62 +702,65 @@ function SelectPrompt({
|
|
|
609
702
|
}
|
|
610
703
|
}
|
|
611
704
|
});
|
|
612
|
-
return /* @__PURE__ */ jsx4(
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
705
|
+
return /* @__PURE__ */ jsx4(Box5, { ref: containerRef, flexGrow: 1, children: /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", gap: 1, width, children: [
|
|
706
|
+
/* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", gap: 1, flexShrink: 0, children: [
|
|
707
|
+
error && /* @__PURE__ */ jsx4(Text5, { color: COLORS.danger, children: error }),
|
|
708
|
+
messages?.map((m, i) => /* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, children: m }, `msg-${i}`)),
|
|
709
|
+
table && /* @__PURE__ */ jsx4(Table, { columns: table.columns, rows: table.rows }),
|
|
710
|
+
/* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
|
|
711
|
+
question && /* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, children: question }),
|
|
712
|
+
helpText && /* @__PURE__ */ jsx4(Text5, { color: COLORS.dim, children: helpText })
|
|
713
|
+
] })
|
|
619
714
|
] }),
|
|
620
|
-
/* @__PURE__ */ jsx4(
|
|
715
|
+
/* @__PURE__ */ jsx4(ScrollView, { scroll, children: visible.map((option, visibleIndex) => {
|
|
716
|
+
const i = scroll.offset + visibleIndex;
|
|
621
717
|
const highlighted = i === index;
|
|
622
718
|
const isCancel = i === cancelIndex;
|
|
623
719
|
const bullet = multi && !isCancel ? checked.has(i) ? "\u25CF " : "\u25CB " : "";
|
|
624
720
|
const sec = isCancel ? void 0 : secondary?.[i];
|
|
625
721
|
const labelColor = highlighted ? COLORS.highlight.fg : void 0;
|
|
626
|
-
const label = /* @__PURE__ */
|
|
722
|
+
const label = /* @__PURE__ */ jsxs4(Text5, { color: labelColor, wrap: "truncate", children: [
|
|
627
723
|
highlighted ? "\u276F " : " ",
|
|
628
724
|
bullet,
|
|
629
725
|
option
|
|
630
726
|
] });
|
|
631
727
|
const isText = sec?.kind === "text";
|
|
632
|
-
return /* @__PURE__ */
|
|
633
|
-
|
|
728
|
+
return /* @__PURE__ */ jsxs4(
|
|
729
|
+
Box5,
|
|
634
730
|
{
|
|
635
731
|
width: isText ? "100%" : barWidth,
|
|
636
732
|
paddingX: 1,
|
|
637
733
|
paddingY: 1,
|
|
638
734
|
backgroundColor: highlighted ? COLORS.highlight.bg : void 0,
|
|
639
735
|
children: [
|
|
640
|
-
/* @__PURE__ */ jsx4(
|
|
641
|
-
isText && textWidth > 0 && /* @__PURE__ */ jsx4(
|
|
642
|
-
|
|
736
|
+
/* @__PURE__ */ jsx4(Box5, { width: isText ? labelWidth : barLabelWidth, children: label }),
|
|
737
|
+
isText && textWidth > 0 && /* @__PURE__ */ jsx4(Box5, { width: textWidth, children: /* @__PURE__ */ jsx4(
|
|
738
|
+
Text5,
|
|
643
739
|
{
|
|
644
740
|
wrap: "truncate",
|
|
645
741
|
color: highlighted ? COLORS.primary : COLORS.muted,
|
|
646
742
|
children: sec.value
|
|
647
743
|
}
|
|
648
744
|
) }),
|
|
649
|
-
sec?.kind === "badge" && /* @__PURE__ */ jsx4(
|
|
745
|
+
sec?.kind === "badge" && /* @__PURE__ */ jsx4(Box5, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx4(Text5, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
|
|
650
746
|
]
|
|
651
747
|
},
|
|
652
748
|
`row-${i}`
|
|
653
749
|
);
|
|
654
750
|
}) }),
|
|
655
|
-
/* @__PURE__ */ jsx4(
|
|
751
|
+
/* @__PURE__ */ jsx4(Box5, { flexShrink: 0, children: /* @__PURE__ */ jsx4(Text5, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs4(Text5, { children: [
|
|
656
752
|
i > 0 ? " " : "",
|
|
657
|
-
/* @__PURE__ */ jsx4(
|
|
658
|
-
/* @__PURE__ */
|
|
753
|
+
/* @__PURE__ */ jsx4(Text5, { color: COLORS.primary, children: key }),
|
|
754
|
+
/* @__PURE__ */ jsxs4(Text5, { color: COLORS.dim, children: [
|
|
659
755
|
" ",
|
|
660
756
|
label
|
|
661
757
|
] })
|
|
662
|
-
] }, label)) })
|
|
758
|
+
] }, label)) }) })
|
|
663
759
|
] }) });
|
|
664
760
|
}
|
|
665
761
|
|
|
666
762
|
// src/ui/PromptInput.tsx
|
|
667
|
-
import { jsx as jsx5, jsxs as
|
|
763
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
668
764
|
var ACCEPT_REJECT_OPTIONS = ["Accept", "Reject"];
|
|
669
765
|
function EnterToContinuePrompt({
|
|
670
766
|
question,
|
|
@@ -675,10 +771,10 @@ function EnterToContinuePrompt({
|
|
|
675
771
|
if (key.return) onDecide(true);
|
|
676
772
|
else if (key.escape) onDecide(false);
|
|
677
773
|
});
|
|
678
|
-
return /* @__PURE__ */
|
|
679
|
-
messages?.map((m, i) => /* @__PURE__ */ jsx5(
|
|
680
|
-
question && /* @__PURE__ */ jsx5(
|
|
681
|
-
/* @__PURE__ */
|
|
774
|
+
return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", gap: 1, children: [
|
|
775
|
+
messages?.map((m, i) => /* @__PURE__ */ jsx5(Text6, { color: COLORS.muted, children: m }, `msg-${i}`)),
|
|
776
|
+
question && /* @__PURE__ */ jsx5(Text6, { color: COLORS.primary, children: question }),
|
|
777
|
+
/* @__PURE__ */ jsxs5(Box6, { gap: 1, flexDirection: "column", children: [
|
|
682
778
|
/* @__PURE__ */ jsx5(NextAction, { action: "continue", keyHint: "enter" }),
|
|
683
779
|
/* @__PURE__ */ jsx5(NextAction, { action: "decline", keyHint: "esc", hierarchy: "secondary" })
|
|
684
780
|
] })
|
|
@@ -686,13 +782,13 @@ function EnterToContinuePrompt({
|
|
|
686
782
|
}
|
|
687
783
|
function PromptInput() {
|
|
688
784
|
const { phase, inputReq, submitInput } = useWizard();
|
|
689
|
-
const [draft, setDraft] =
|
|
785
|
+
const [draft, setDraft] = useState5("");
|
|
690
786
|
if (phase === "done" || phase === "error") {
|
|
691
|
-
return /* @__PURE__ */ jsx5(
|
|
787
|
+
return /* @__PURE__ */ jsx5(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text6, { color: "gray", dimColor: true, children: "Press Enter or Esc to exit" }) });
|
|
692
788
|
}
|
|
693
789
|
if (phase !== "awaitingInput" || !inputReq) return null;
|
|
694
790
|
if (inputReq.promptType === "multipleChoice") {
|
|
695
|
-
return /* @__PURE__ */ jsx5(
|
|
791
|
+
return /* @__PURE__ */ jsx5(Box6, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
|
|
696
792
|
SelectPrompt,
|
|
697
793
|
{
|
|
698
794
|
question: inputReq.prompt,
|
|
@@ -709,7 +805,7 @@ function PromptInput() {
|
|
|
709
805
|
) });
|
|
710
806
|
}
|
|
711
807
|
if (inputReq.promptType === "multiSelect") {
|
|
712
|
-
return /* @__PURE__ */ jsx5(
|
|
808
|
+
return /* @__PURE__ */ jsx5(Box6, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
|
|
713
809
|
SelectPrompt,
|
|
714
810
|
{
|
|
715
811
|
multi: true,
|
|
@@ -724,7 +820,7 @@ function PromptInput() {
|
|
|
724
820
|
) });
|
|
725
821
|
}
|
|
726
822
|
if (inputReq.promptType === "notice") {
|
|
727
|
-
return /* @__PURE__ */ jsx5(
|
|
823
|
+
return /* @__PURE__ */ jsx5(Box6, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
|
|
728
824
|
SelectPrompt,
|
|
729
825
|
{
|
|
730
826
|
question: inputReq.prompt,
|
|
@@ -746,7 +842,7 @@ function PromptInput() {
|
|
|
746
842
|
}
|
|
747
843
|
if (inputReq.promptType === "acceptReject") {
|
|
748
844
|
const labels = inputReq.options?.length ? inputReq.options : ACCEPT_REJECT_OPTIONS;
|
|
749
|
-
return /* @__PURE__ */ jsx5(
|
|
845
|
+
return /* @__PURE__ */ jsx5(Box6, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
|
|
750
846
|
SelectPrompt,
|
|
751
847
|
{
|
|
752
848
|
question: inputReq.prompt,
|
|
@@ -757,11 +853,11 @@ function PromptInput() {
|
|
|
757
853
|
}
|
|
758
854
|
) });
|
|
759
855
|
}
|
|
760
|
-
return /* @__PURE__ */
|
|
761
|
-
inputReq.error && /* @__PURE__ */ jsx5(
|
|
762
|
-
inputReq.messages?.map((m, i) => /* @__PURE__ */ jsx5(
|
|
763
|
-
/* @__PURE__ */
|
|
764
|
-
/* @__PURE__ */
|
|
856
|
+
return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
|
|
857
|
+
inputReq.error && /* @__PURE__ */ jsx5(Text6, { color: COLORS.danger, children: inputReq.error }),
|
|
858
|
+
inputReq.messages?.map((m, i) => /* @__PURE__ */ jsx5(Text6, { color: COLORS.muted, children: m }, `msg-${i}`)),
|
|
859
|
+
/* @__PURE__ */ jsxs5(Box6, { children: [
|
|
860
|
+
/* @__PURE__ */ jsxs5(Text6, { color: COLORS.primary, children: [
|
|
765
861
|
inputReq.prompt,
|
|
766
862
|
" "
|
|
767
863
|
] }),
|
|
@@ -783,7 +879,7 @@ function PromptInput() {
|
|
|
783
879
|
// src/ui/Welcome.tsx
|
|
784
880
|
import { dirname as dirname2, join as join3 } from "node:path";
|
|
785
881
|
import { fileURLToPath } from "node:url";
|
|
786
|
-
import { Box as
|
|
882
|
+
import { Box as Box7, Spacer, Text as Text7, useInput as useInput3, useWindowSize as useWindowSize5 } from "ink";
|
|
787
883
|
|
|
788
884
|
// src/ui/copy/welcome.ts
|
|
789
885
|
var sidebarItems = [
|
|
@@ -796,12 +892,12 @@ var sidebarItems = [
|
|
|
796
892
|
description: "push 100 records to Algolia in seconds"
|
|
797
893
|
},
|
|
798
894
|
{
|
|
799
|
-
title: "detect your
|
|
800
|
-
description: "React, Vue, Angular,
|
|
895
|
+
title: "detect your framework",
|
|
896
|
+
description: "React, Vue, Angular, Vanilla JS"
|
|
801
897
|
},
|
|
802
898
|
{
|
|
803
899
|
title: "scaffold a search UI",
|
|
804
|
-
description: "a styled InstantSearch
|
|
900
|
+
description: "a styled InstantSearch component, wired into your app"
|
|
805
901
|
},
|
|
806
902
|
{
|
|
807
903
|
title: "ship it",
|
|
@@ -811,27 +907,27 @@ var sidebarItems = [
|
|
|
811
907
|
|
|
812
908
|
// src/ui/Welcome.tsx
|
|
813
909
|
import Image, { InkPictureProvider } from "ink-picture";
|
|
814
|
-
import { jsx as jsx6, jsxs as
|
|
910
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
815
911
|
var IMAGE_PATH = join3(dirname2(fileURLToPath(import.meta.url)), "algolia.png");
|
|
816
912
|
function SidebarItem({
|
|
817
913
|
title,
|
|
818
914
|
description
|
|
819
915
|
}) {
|
|
820
|
-
return /* @__PURE__ */
|
|
821
|
-
/* @__PURE__ */
|
|
822
|
-
/* @__PURE__ */ jsx6(
|
|
823
|
-
/* @__PURE__ */ jsx6(
|
|
916
|
+
return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
|
|
917
|
+
/* @__PURE__ */ jsxs6(Box7, { gap: 1, children: [
|
|
918
|
+
/* @__PURE__ */ jsx6(Text7, { color: COLORS.success, children: "\u2192" }),
|
|
919
|
+
/* @__PURE__ */ jsx6(Text7, { color: COLORS.strong, bold: true, children: title })
|
|
824
920
|
] }),
|
|
825
|
-
/* @__PURE__ */
|
|
921
|
+
/* @__PURE__ */ jsxs6(Box7, { flexDirection: "row", gap: 2, children: [
|
|
826
922
|
/* @__PURE__ */ jsx6(Spacer, {}),
|
|
827
|
-
/* @__PURE__ */ jsx6(
|
|
923
|
+
/* @__PURE__ */ jsx6(Text7, { color: COLORS.muted, children: description })
|
|
828
924
|
] })
|
|
829
925
|
] });
|
|
830
926
|
}
|
|
831
927
|
function Welcome() {
|
|
832
928
|
const confirmStart = useWizard((s) => s.confirmStart);
|
|
833
929
|
const openLearnMore = useWizard((s) => s.openLearnMore);
|
|
834
|
-
const { rows } =
|
|
930
|
+
const { rows } = useWindowSize5();
|
|
835
931
|
useInput3((input, key) => {
|
|
836
932
|
if (key.return) confirmStart();
|
|
837
933
|
else if (input === "i") openLearnMore();
|
|
@@ -850,15 +946,15 @@ function Welcome() {
|
|
|
850
946
|
if (rows < 30) {
|
|
851
947
|
layout = scales["small"];
|
|
852
948
|
}
|
|
853
|
-
return /* @__PURE__ */
|
|
949
|
+
return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
|
|
854
950
|
/* @__PURE__ */ jsx6(
|
|
855
|
-
|
|
951
|
+
Box7,
|
|
856
952
|
{
|
|
857
953
|
paddingY: layout.main.padding.y,
|
|
858
954
|
paddingX: layout.main.padding.x,
|
|
859
955
|
flexDirection: "column",
|
|
860
956
|
justifyContent: "center",
|
|
861
|
-
children: /* @__PURE__ */
|
|
957
|
+
children: /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", gap: 2, children: [
|
|
862
958
|
/* @__PURE__ */ jsx6(InkPictureProvider, { children: /* @__PURE__ */ jsx6(
|
|
863
959
|
Image,
|
|
864
960
|
{
|
|
@@ -870,16 +966,16 @@ function Welcome() {
|
|
|
870
966
|
protocol: "halfBlock"
|
|
871
967
|
}
|
|
872
968
|
) }),
|
|
873
|
-
/* @__PURE__ */ jsx6(
|
|
874
|
-
/* @__PURE__ */
|
|
969
|
+
/* @__PURE__ */ jsx6(Text7, { color: COLORS.muted, children: "\u2726 From zero \u2192 working search in ~10 minutes" }),
|
|
970
|
+
/* @__PURE__ */ jsxs6(Box7, { gap: 1, flexDirection: "column", children: [
|
|
875
971
|
/* @__PURE__ */ jsx6(NextAction, { action: "start wizard", keyHint: "enter" }),
|
|
876
972
|
/* @__PURE__ */ jsx6(NextAction, { action: "learn more", keyHint: "i", hierarchy: "secondary" })
|
|
877
973
|
] })
|
|
878
974
|
] })
|
|
879
975
|
}
|
|
880
976
|
),
|
|
881
|
-
/* @__PURE__ */
|
|
882
|
-
|
|
977
|
+
/* @__PURE__ */ jsxs6(
|
|
978
|
+
Box7,
|
|
883
979
|
{
|
|
884
980
|
backgroundColor: COLORS.bg.sidebar,
|
|
885
981
|
width: 40,
|
|
@@ -889,7 +985,7 @@ function Welcome() {
|
|
|
889
985
|
flexDirection: "column",
|
|
890
986
|
justifyContent: "center",
|
|
891
987
|
children: [
|
|
892
|
-
/* @__PURE__ */ jsx6(
|
|
988
|
+
/* @__PURE__ */ jsx6(Text7, { color: COLORS.muted, children: "WHAT THIS WIZARD WILL DO" }),
|
|
893
989
|
sidebarItems.map((i, idx) => /* @__PURE__ */ jsx6(SidebarItem, { title: i.title, description: i.description }, idx))
|
|
894
990
|
]
|
|
895
991
|
}
|
|
@@ -899,7 +995,7 @@ function Welcome() {
|
|
|
899
995
|
|
|
900
996
|
// src/ui/LearnMore.tsx
|
|
901
997
|
import { Fragment as Fragment2 } from "react";
|
|
902
|
-
import { Box as
|
|
998
|
+
import { Box as Box8, Text as Text8, useInput as useInput4, useWindowSize as useWindowSize6 } from "ink";
|
|
903
999
|
|
|
904
1000
|
// src/ui/copy/learn-more.ts
|
|
905
1001
|
var accessIntro = "Everything runs locally on your machine. Nothing is written or sent without an explicit yes from you.";
|
|
@@ -907,7 +1003,7 @@ var accessItems = [
|
|
|
907
1003
|
{
|
|
908
1004
|
tag: "READ",
|
|
909
1005
|
title: "Project files",
|
|
910
|
-
description: "reads
|
|
1006
|
+
description: "reads package.json, configs & source to detect your stack. Read-only; nothing is uploaded."
|
|
911
1007
|
},
|
|
912
1008
|
{
|
|
913
1009
|
tag: "WRITE",
|
|
@@ -936,7 +1032,7 @@ var policyLinks = [
|
|
|
936
1032
|
];
|
|
937
1033
|
|
|
938
1034
|
// src/ui/LearnMore.tsx
|
|
939
|
-
import { jsx as jsx7, jsxs as
|
|
1035
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
940
1036
|
var TAG_COLORS = {
|
|
941
1037
|
READ: COLORS.success,
|
|
942
1038
|
WRITE: COLORS.badge,
|
|
@@ -952,25 +1048,25 @@ function NeverLine({
|
|
|
952
1048
|
}) {
|
|
953
1049
|
const used = segments.reduce((n, s) => n + s.text.length, 0);
|
|
954
1050
|
const rightPad = Math.max(0, width - 2 - NEVER_BOX_PAD_X - used);
|
|
955
|
-
return /* @__PURE__ */
|
|
956
|
-
/* @__PURE__ */ jsx7(
|
|
1051
|
+
return /* @__PURE__ */ jsxs7(Text8, { children: [
|
|
1052
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.danger, children: "\u2502" }),
|
|
957
1053
|
" ".repeat(NEVER_BOX_PAD_X),
|
|
958
|
-
segments.map((s, i) => /* @__PURE__ */ jsx7(
|
|
1054
|
+
segments.map((s, i) => /* @__PURE__ */ jsx7(Text8, { color: s.color, bold: s.bold, children: s.text }, i)),
|
|
959
1055
|
" ".repeat(rightPad),
|
|
960
|
-
/* @__PURE__ */ jsx7(
|
|
1056
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.danger, children: "\u2502" })
|
|
961
1057
|
] });
|
|
962
1058
|
}
|
|
963
1059
|
function LearnMore() {
|
|
964
1060
|
const confirmStart = useWizard((s) => s.confirmStart);
|
|
965
1061
|
const backToHome = useWizard((s) => s.backToHome);
|
|
966
|
-
const { columns } =
|
|
1062
|
+
const { columns } = useWindowSize6();
|
|
967
1063
|
const dividerWidth = Math.max(0, columns - PADDING_X * 2);
|
|
968
1064
|
useInput4((_input, key) => {
|
|
969
1065
|
if (key.escape) backToHome();
|
|
970
1066
|
else if (key.return) confirmStart();
|
|
971
1067
|
});
|
|
972
|
-
return /* @__PURE__ */
|
|
973
|
-
|
|
1068
|
+
return /* @__PURE__ */ jsxs7(
|
|
1069
|
+
Box8,
|
|
974
1070
|
{
|
|
975
1071
|
flexDirection: "column",
|
|
976
1072
|
paddingX: PADDING_X,
|
|
@@ -978,20 +1074,20 @@ function LearnMore() {
|
|
|
978
1074
|
width: "100%",
|
|
979
1075
|
gap: 1,
|
|
980
1076
|
children: [
|
|
981
|
-
/* @__PURE__ */ jsx7(
|
|
982
|
-
/* @__PURE__ */ jsx7(
|
|
983
|
-
/* @__PURE__ */ jsx7(
|
|
984
|
-
/* @__PURE__ */ jsx7(
|
|
985
|
-
/* @__PURE__ */
|
|
986
|
-
/* @__PURE__ */ jsx7(
|
|
987
|
-
/* @__PURE__ */ jsx7(
|
|
988
|
-
/* @__PURE__ */ jsx7(
|
|
989
|
-
/* @__PURE__ */ jsx7(
|
|
1077
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.strong, bold: true, children: "What algolia wizard accesses" }),
|
|
1078
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.muted, children: accessIntro }),
|
|
1079
|
+
/* @__PURE__ */ jsx7(Box8, { flexDirection: "column", children: accessItems.map((item) => /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", marginTop: 1, children: [
|
|
1080
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.border, children: "\u2500".repeat(dividerWidth) }),
|
|
1081
|
+
/* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 1, marginTop: 1, children: [
|
|
1082
|
+
/* @__PURE__ */ jsx7(Box8, { width: TAG_COLUMN_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx7(Text8, { color: TAG_COLORS[item.tag], bold: true, children: `[${item.tag}]` }) }),
|
|
1083
|
+
/* @__PURE__ */ jsx7(Box8, { flexDirection: "column", children: /* @__PURE__ */ jsxs7(Text8, { children: [
|
|
1084
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.strong, bold: true, children: item.title }),
|
|
1085
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.muted, children: ` \u2014 ${item.description}` })
|
|
990
1086
|
] }) })
|
|
991
1087
|
] })
|
|
992
1088
|
] }, item.tag)) }),
|
|
993
|
-
/* @__PURE__ */
|
|
994
|
-
/* @__PURE__ */ jsx7(
|
|
1089
|
+
/* @__PURE__ */ jsxs7(Box8, { marginTop: 1, flexDirection: "column", children: [
|
|
1090
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.danger, children: `\u256D${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256E` }),
|
|
995
1091
|
/* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
|
|
996
1092
|
/* @__PURE__ */ jsx7(
|
|
997
1093
|
NeverLine,
|
|
@@ -1000,7 +1096,7 @@ function LearnMore() {
|
|
|
1000
1096
|
segments: [{ text: "I NEVER", color: COLORS.danger, bold: true }]
|
|
1001
1097
|
}
|
|
1002
1098
|
),
|
|
1003
|
-
neverItems.map((item) => /* @__PURE__ */
|
|
1099
|
+
neverItems.map((item) => /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
1004
1100
|
/* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
|
|
1005
1101
|
/* @__PURE__ */ jsx7(
|
|
1006
1102
|
NeverLine,
|
|
@@ -1015,23 +1111,23 @@ function LearnMore() {
|
|
|
1015
1111
|
)
|
|
1016
1112
|
] }, item)),
|
|
1017
1113
|
/* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
|
|
1018
|
-
/* @__PURE__ */ jsx7(
|
|
1114
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
|
|
1019
1115
|
] }),
|
|
1020
|
-
/* @__PURE__ */ jsx7(
|
|
1021
|
-
/* @__PURE__ */ jsx7(
|
|
1022
|
-
/* @__PURE__ */ jsx7(
|
|
1116
|
+
/* @__PURE__ */ jsx7(Box8, { marginTop: 1, flexDirection: "column", children: policyLinks.map((link) => /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 1, children: [
|
|
1117
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.strong, bold: true, children: `${link.label}:` }),
|
|
1118
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.accent, children: link.url })
|
|
1023
1119
|
] }, link.label)) }),
|
|
1024
|
-
/* @__PURE__ */
|
|
1025
|
-
/* @__PURE__ */
|
|
1026
|
-
/* @__PURE__ */ jsx7(
|
|
1027
|
-
/* @__PURE__ */ jsx7(
|
|
1028
|
-
/* @__PURE__ */ jsx7(
|
|
1120
|
+
/* @__PURE__ */ jsxs7(Box8, { marginTop: 1, flexDirection: "row", gap: 3, children: [
|
|
1121
|
+
/* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 1, children: [
|
|
1122
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.muted, children: "[" }),
|
|
1123
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.primary, children: "esc" }),
|
|
1124
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.muted, children: "] back" })
|
|
1029
1125
|
] }),
|
|
1030
|
-
/* @__PURE__ */
|
|
1031
|
-
/* @__PURE__ */ jsx7(
|
|
1032
|
-
/* @__PURE__ */ jsx7(
|
|
1033
|
-
/* @__PURE__ */ jsx7(
|
|
1034
|
-
/* @__PURE__ */ jsx7(
|
|
1126
|
+
/* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 1, children: [
|
|
1127
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.muted, children: "[" }),
|
|
1128
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.primary, children: "enter" }),
|
|
1129
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.muted, children: "]" }),
|
|
1130
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.success, bold: true, children: "start wizard" })
|
|
1035
1131
|
] })
|
|
1036
1132
|
] })
|
|
1037
1133
|
]
|
|
@@ -1040,10 +1136,10 @@ function LearnMore() {
|
|
|
1040
1136
|
}
|
|
1041
1137
|
|
|
1042
1138
|
// src/ui/Sidebar.tsx
|
|
1043
|
-
import { Box as
|
|
1139
|
+
import { Box as Box11, Text as Text11 } from "ink";
|
|
1044
1140
|
|
|
1045
1141
|
// src/ui/Steps.tsx
|
|
1046
|
-
import { Box as
|
|
1142
|
+
import { Box as Box9, Text as Text9 } from "ink";
|
|
1047
1143
|
import Spinner from "ink-spinner";
|
|
1048
1144
|
|
|
1049
1145
|
// src/core/persistence.ts
|
|
@@ -1072,11 +1168,11 @@ async function clearWorkflowState(workflowId) {
|
|
|
1072
1168
|
}
|
|
1073
1169
|
|
|
1074
1170
|
// src/ui/Steps.tsx
|
|
1075
|
-
import { jsx as jsx8, jsxs as
|
|
1171
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1076
1172
|
function Steps() {
|
|
1077
1173
|
const { steps } = useWizard();
|
|
1078
1174
|
const visibleSteps = steps.filter(isStepVisible);
|
|
1079
|
-
return /* @__PURE__ */ jsx8(
|
|
1175
|
+
return /* @__PURE__ */ jsx8(Box9, { flexDirection: "column", gap: 1, children: visibleSteps.map((s) => /* @__PURE__ */ jsx8(Box9, { flexDirection: "column", children: /* @__PURE__ */ jsxs8(Text9, { color: COLORS.status[s.status], children: [
|
|
1080
1176
|
s.status === "running" ? /* @__PURE__ */ jsx8(Spinner, { type: "dots" }) : MARKER[s.status],
|
|
1081
1177
|
" ",
|
|
1082
1178
|
s.title
|
|
@@ -1086,7 +1182,7 @@ function CurrentStep() {
|
|
|
1086
1182
|
const { steps } = useWizard();
|
|
1087
1183
|
const currentStep = steps.filter(isStepVisible).find((s) => s.status === "running");
|
|
1088
1184
|
if (!currentStep) return null;
|
|
1089
|
-
return /* @__PURE__ */
|
|
1185
|
+
return /* @__PURE__ */ jsxs8(Text9, { color: COLORS.status.running, children: [
|
|
1090
1186
|
/* @__PURE__ */ jsx8(Spinner, { type: "dots" }),
|
|
1091
1187
|
" ",
|
|
1092
1188
|
` ${currentStep.title}`
|
|
@@ -1094,19 +1190,19 @@ function CurrentStep() {
|
|
|
1094
1190
|
}
|
|
1095
1191
|
|
|
1096
1192
|
// src/ui/Progress.tsx
|
|
1097
|
-
import { Box as
|
|
1098
|
-
import { jsx as jsx9, jsxs as
|
|
1193
|
+
import { Box as Box10, Text as Text10 } from "ink";
|
|
1194
|
+
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1099
1195
|
function Progress() {
|
|
1100
1196
|
const { steps, currentStepIndex } = useWizard();
|
|
1101
1197
|
const visibleSteps = steps.filter(isStepVisible);
|
|
1102
1198
|
if (visibleSteps.length === 0) return null;
|
|
1103
1199
|
const visibleCountThroughCurrent = steps.slice(0, currentStepIndex + 1).filter(isStepVisible).length;
|
|
1104
1200
|
const activeStepNumber = Math.max(1, visibleCountThroughCurrent);
|
|
1105
|
-
return /* @__PURE__ */
|
|
1106
|
-
/* @__PURE__ */ jsx9(
|
|
1107
|
-
/* @__PURE__ */ jsx9(
|
|
1108
|
-
/* @__PURE__ */ jsx9(
|
|
1109
|
-
/* @__PURE__ */ jsx9(
|
|
1201
|
+
return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 1, children: [
|
|
1202
|
+
/* @__PURE__ */ jsx9(Text10, { color: COLORS.muted, children: "STEP" }),
|
|
1203
|
+
/* @__PURE__ */ jsx9(Text10, { bold: true, children: activeStepNumber }),
|
|
1204
|
+
/* @__PURE__ */ jsx9(Text10, { bold: true, children: "/" }),
|
|
1205
|
+
/* @__PURE__ */ jsx9(Text10, { bold: true, children: visibleSteps.length })
|
|
1110
1206
|
] });
|
|
1111
1207
|
}
|
|
1112
1208
|
|
|
@@ -1117,10 +1213,10 @@ var sidebarCommands = [
|
|
|
1117
1213
|
];
|
|
1118
1214
|
|
|
1119
1215
|
// src/ui/Sidebar.tsx
|
|
1120
|
-
import { jsx as jsx10, jsxs as
|
|
1216
|
+
import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1121
1217
|
function Sidebar() {
|
|
1122
|
-
return /* @__PURE__ */
|
|
1123
|
-
|
|
1218
|
+
return /* @__PURE__ */ jsxs10(
|
|
1219
|
+
Box11,
|
|
1124
1220
|
{
|
|
1125
1221
|
backgroundColor: "#14171E",
|
|
1126
1222
|
width: 30,
|
|
@@ -1129,16 +1225,16 @@ function Sidebar() {
|
|
|
1129
1225
|
flexDirection: "column",
|
|
1130
1226
|
justifyContent: "space-between",
|
|
1131
1227
|
children: [
|
|
1132
|
-
/* @__PURE__ */
|
|
1133
|
-
/* @__PURE__ */ jsx10(
|
|
1228
|
+
/* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", gap: 1, children: [
|
|
1229
|
+
/* @__PURE__ */ jsx10(Text11, { color: COLORS.muted, children: "PROGRESS" }),
|
|
1134
1230
|
/* @__PURE__ */ jsx10(Steps, {})
|
|
1135
1231
|
] }),
|
|
1136
|
-
/* @__PURE__ */
|
|
1232
|
+
/* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", gap: 1, children: [
|
|
1137
1233
|
/* @__PURE__ */ jsx10(Progress, {}),
|
|
1138
|
-
/* @__PURE__ */ jsx10(
|
|
1139
|
-
return /* @__PURE__ */
|
|
1140
|
-
/* @__PURE__ */ jsx10(
|
|
1141
|
-
/* @__PURE__ */ jsx10(
|
|
1234
|
+
/* @__PURE__ */ jsx10(Box11, { flexDirection: "column", children: sidebarCommands.map((c) => {
|
|
1235
|
+
return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", gap: 1, children: [
|
|
1236
|
+
/* @__PURE__ */ jsx10(Text11, { color: COLORS.primary, children: `[${c.keyHint}]` }),
|
|
1237
|
+
/* @__PURE__ */ jsx10(Text11, { color: COLORS.muted, children: c.description })
|
|
1142
1238
|
] });
|
|
1143
1239
|
}) })
|
|
1144
1240
|
] })
|
|
@@ -1148,12 +1244,12 @@ function Sidebar() {
|
|
|
1148
1244
|
}
|
|
1149
1245
|
|
|
1150
1246
|
// src/ui/Ribbon.tsx
|
|
1151
|
-
import { Box as
|
|
1152
|
-
import { jsx as jsx11, jsxs as
|
|
1247
|
+
import { Box as Box12, Text as Text12 } from "ink";
|
|
1248
|
+
import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1153
1249
|
function Ribbon() {
|
|
1154
1250
|
const firstCommand = sidebarCommands[0];
|
|
1155
|
-
return /* @__PURE__ */
|
|
1156
|
-
|
|
1251
|
+
return /* @__PURE__ */ jsxs11(
|
|
1252
|
+
Box12,
|
|
1157
1253
|
{
|
|
1158
1254
|
backgroundColor: "#14171E",
|
|
1159
1255
|
flexDirection: "row",
|
|
@@ -1163,9 +1259,9 @@ function Ribbon() {
|
|
|
1163
1259
|
children: [
|
|
1164
1260
|
/* @__PURE__ */ jsx11(Progress, {}),
|
|
1165
1261
|
/* @__PURE__ */ jsx11(CurrentStep, {}),
|
|
1166
|
-
/* @__PURE__ */
|
|
1167
|
-
/* @__PURE__ */ jsx11(
|
|
1168
|
-
/* @__PURE__ */ jsx11(
|
|
1262
|
+
/* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", gap: 1, children: [
|
|
1263
|
+
/* @__PURE__ */ jsx11(Text12, { color: COLORS.primary, children: `[${firstCommand.keyHint}]` }),
|
|
1264
|
+
/* @__PURE__ */ jsx11(Text12, { color: COLORS.muted, children: firstCommand.description })
|
|
1169
1265
|
] })
|
|
1170
1266
|
]
|
|
1171
1267
|
}
|
|
@@ -1176,9 +1272,8 @@ function Ribbon() {
|
|
|
1176
1272
|
import { useState as useState6 } from "react";
|
|
1177
1273
|
|
|
1178
1274
|
// src/ui/Logs.tsx
|
|
1179
|
-
import {
|
|
1180
|
-
import {
|
|
1181
|
-
import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1275
|
+
import { Box as Box13, Text as Text13, useInput as useInput5 } from "ink";
|
|
1276
|
+
import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1182
1277
|
var KIND_COLOR = {
|
|
1183
1278
|
tool: COLORS.primary,
|
|
1184
1279
|
prompt: COLORS.badge
|
|
@@ -1208,75 +1303,32 @@ function formatTimestamp(ms) {
|
|
|
1208
1303
|
}
|
|
1209
1304
|
function Logs() {
|
|
1210
1305
|
const logs = useWizard((s) => s.logs);
|
|
1211
|
-
const {
|
|
1212
|
-
const viewportRef = useRef3(null);
|
|
1213
|
-
const [viewportHeight, setViewportHeight] = useState5(0);
|
|
1214
|
-
const [viewportWidth, setViewportWidth] = useState5(0);
|
|
1215
|
-
const [scrollOffset, setScrollOffset] = useState5(0);
|
|
1216
|
-
const prevMaxOffsetRef = useRef3(0);
|
|
1217
|
-
useLayoutEffect2(() => {
|
|
1218
|
-
if (!viewportRef.current) return;
|
|
1219
|
-
const { width, height } = measureElement3(viewportRef.current);
|
|
1220
|
-
setViewportHeight(height);
|
|
1221
|
-
setViewportWidth(width);
|
|
1222
|
-
}, [rows, columns, logs.length === 0]);
|
|
1223
|
-
let capacity = viewportHeight;
|
|
1224
|
-
for (let i = 0; i < 2; i++) {
|
|
1225
|
-
const hasAbove = scrollOffset > 0;
|
|
1226
|
-
const hasBelow = scrollOffset + capacity < logs.length;
|
|
1227
|
-
capacity = Math.max(
|
|
1228
|
-
viewportHeight - (hasAbove ? 1 : 0) - (hasBelow ? 1 : 0),
|
|
1229
|
-
0
|
|
1230
|
-
);
|
|
1231
|
-
}
|
|
1232
|
-
const capacityAtBottom = logs.length > viewportHeight ? Math.max(viewportHeight - 1, 0) : viewportHeight;
|
|
1233
|
-
const maxOffset = Math.max(logs.length - capacityAtBottom, 0);
|
|
1234
|
-
useLayoutEffect2(() => {
|
|
1235
|
-
const wasAtBottom = scrollOffset >= prevMaxOffsetRef.current;
|
|
1236
|
-
prevMaxOffsetRef.current = maxOffset;
|
|
1237
|
-
setScrollOffset((o) => wasAtBottom ? maxOffset : Math.min(o, maxOffset));
|
|
1238
|
-
}, [maxOffset]);
|
|
1306
|
+
const scroll = useScrollWindow({ itemCount: logs.length, followBottom: true });
|
|
1239
1307
|
useInput5((_input, key) => {
|
|
1240
|
-
if (
|
|
1241
|
-
|
|
1242
|
-
(o) => key.upArrow ? Math.max(o - 1, 0) : Math.min(o + 1, maxOffset)
|
|
1243
|
-
);
|
|
1308
|
+
if (key.upArrow) scroll.scrollBy(-1);
|
|
1309
|
+
else if (key.downArrow) scroll.scrollBy(1);
|
|
1244
1310
|
});
|
|
1245
|
-
const visible = logs.slice(
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", gap: ROW_GAP, children: [
|
|
1267
|
-
/* @__PURE__ */ jsx12(Text12, { color: COLORS.dim, children: timestamp }),
|
|
1268
|
-
/* @__PURE__ */ jsx12(Text12, { color: logNameColor(entry), wrap: "truncate", children: name }),
|
|
1269
|
-
preview && /* @__PURE__ */ jsx12(Text12, { color: COLORS.dim, wrap: "truncate", children: preview }),
|
|
1270
|
-
durationText && /* @__PURE__ */ jsx12(Text12, { color: COLORS.dim, children: durationText })
|
|
1271
|
-
] }, entry.id);
|
|
1272
|
-
}),
|
|
1273
|
-
hiddenBelow > 0 && /* @__PURE__ */ jsxs11(Text12, { color: COLORS.dim, children: [
|
|
1274
|
-
"\u2193 ",
|
|
1275
|
-
hiddenBelow,
|
|
1276
|
-
" more"
|
|
1277
|
-
] })
|
|
1278
|
-
] }),
|
|
1279
|
-
/* @__PURE__ */ jsx12(Text12, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
|
|
1311
|
+
const visible = logs.slice(scroll.offset, scroll.offset + scroll.capacity);
|
|
1312
|
+
return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", paddingX: 4, paddingY: 2, flexGrow: 1, children: [
|
|
1313
|
+
logs.length === 0 && /* @__PURE__ */ jsx12(Text13, { color: COLORS.dim, children: "No logs yet." }),
|
|
1314
|
+
/* @__PURE__ */ jsx12(ScrollView, { scroll, children: visible.map((entry) => {
|
|
1315
|
+
const timestamp = `[${formatTimestamp(entry.startedAt)}]`;
|
|
1316
|
+
const durationText = entry.kind === "tool" && entry.durationMs !== void 0 ? `${entry.durationMs}ms` : "";
|
|
1317
|
+
const rawPreview = rawInputText(entry.input);
|
|
1318
|
+
const partCount = 2 + (rawPreview ? 1 : 0) + (durationText ? 1 : 0);
|
|
1319
|
+
const gaps = (partCount - 1) * ROW_GAP;
|
|
1320
|
+
let budget = scroll.width - timestamp.length - durationText.length - gaps;
|
|
1321
|
+
const name = truncate2(entry.name, budget);
|
|
1322
|
+
budget -= name.length;
|
|
1323
|
+
const preview = rawPreview ? truncate2(rawPreview, budget) : "";
|
|
1324
|
+
return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "row", gap: ROW_GAP, children: [
|
|
1325
|
+
/* @__PURE__ */ jsx12(Text13, { color: COLORS.dim, children: timestamp }),
|
|
1326
|
+
/* @__PURE__ */ jsx12(Text13, { color: logNameColor(entry), wrap: "truncate", children: name }),
|
|
1327
|
+
preview && /* @__PURE__ */ jsx12(Text13, { color: COLORS.dim, wrap: "truncate", children: preview }),
|
|
1328
|
+
durationText && /* @__PURE__ */ jsx12(Text13, { color: COLORS.dim, children: durationText })
|
|
1329
|
+
] }, entry.id);
|
|
1330
|
+
}) }),
|
|
1331
|
+
/* @__PURE__ */ jsx12(Text13, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
|
|
1280
1332
|
] });
|
|
1281
1333
|
}
|
|
1282
1334
|
|
|
@@ -1468,7 +1520,7 @@ function track(event, payload) {
|
|
|
1468
1520
|
}
|
|
1469
1521
|
|
|
1470
1522
|
// src/ui/App.tsx
|
|
1471
|
-
import { jsx as jsx13, jsxs as
|
|
1523
|
+
import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1472
1524
|
function App() {
|
|
1473
1525
|
const { phase, error, homeScreen, currentStepIndex, steps, inputReq } = useWizard();
|
|
1474
1526
|
const { exit } = useApp();
|
|
@@ -1511,36 +1563,44 @@ function App() {
|
|
|
1511
1563
|
const mainWindowVisible = phase === "running" || phase === "awaitingInput" || phase === "error" || phase === "done";
|
|
1512
1564
|
const flexDirection = columns > 90 ? "row" : "column";
|
|
1513
1565
|
const showSidebar = flexDirection === "row";
|
|
1514
|
-
return /* @__PURE__ */
|
|
1515
|
-
|
|
1566
|
+
return /* @__PURE__ */ jsxs13(
|
|
1567
|
+
Box14,
|
|
1516
1568
|
{
|
|
1517
1569
|
backgroundColor: COLORS.bg.main,
|
|
1518
1570
|
flexDirection: "row",
|
|
1519
1571
|
width: columns,
|
|
1520
1572
|
minHeight: rows,
|
|
1521
1573
|
children: [
|
|
1522
|
-
mainWindowVisible &&
|
|
1523
|
-
|
|
1574
|
+
mainWindowVisible && // Ink sizes the root by width only, so without a cap the scrolling
|
|
1575
|
+
// lists in here grow to their content instead of windowing (see
|
|
1576
|
+
// `useScrollWindow`). The home screens below stay uncapped: they are
|
|
1577
|
+
// long static copy that would be clipped rather than windowed.
|
|
1578
|
+
/* @__PURE__ */ jsxs13(
|
|
1579
|
+
Box14,
|
|
1524
1580
|
{
|
|
1525
1581
|
flexDirection,
|
|
1526
1582
|
width: "100%",
|
|
1583
|
+
maxHeight: rows,
|
|
1527
1584
|
justifyContent: "space-between",
|
|
1528
1585
|
children: [
|
|
1529
1586
|
showLogs ? /* @__PURE__ */ jsx13(Logs, {}) : (
|
|
1530
|
-
/* Fill the
|
|
1531
|
-
|
|
1532
|
-
|
|
1587
|
+
/* Fill the space the sidebar/ribbon leaves — width beside the
|
|
1588
|
+
sidebar, height above the ribbon. The height matters even
|
|
1589
|
+
stacked: it is what the prompt's scrolling list measures itself
|
|
1590
|
+
against (see SelectPrompt). */
|
|
1591
|
+
/* @__PURE__ */ jsxs13(
|
|
1592
|
+
Box14,
|
|
1533
1593
|
{
|
|
1534
1594
|
flexDirection: "column",
|
|
1535
1595
|
paddingX: 4,
|
|
1536
1596
|
paddingY: 2,
|
|
1537
1597
|
width: showSidebar ? 70 : "100%",
|
|
1538
|
-
flexGrow:
|
|
1598
|
+
flexGrow: 1,
|
|
1539
1599
|
children: [
|
|
1540
1600
|
/* @__PURE__ */ jsx13(Notices, {}),
|
|
1541
1601
|
/* @__PURE__ */ jsx13(PromptInput, {}),
|
|
1542
|
-
phase === "running" && showSidebar && /* @__PURE__ */ jsx13(
|
|
1543
|
-
phase === "error" && error && /* @__PURE__ */ jsx13(
|
|
1602
|
+
phase === "running" && showSidebar && /* @__PURE__ */ jsx13(Box14, { marginTop: 1, children: /* @__PURE__ */ jsx13(CurrentStep, {}) }),
|
|
1603
|
+
phase === "error" && error && /* @__PURE__ */ jsx13(Box14, { marginTop: 1, children: /* @__PURE__ */ jsxs13(Text14, { color: COLORS.status.error, children: [
|
|
1544
1604
|
"\u2716 ",
|
|
1545
1605
|
error
|
|
1546
1606
|
] }) })
|
|
@@ -2161,651 +2221,15 @@ function writeCredentialsTool(ctx) {
|
|
|
2161
2221
|
// src/lib/tools/searchFiles.ts
|
|
2162
2222
|
import { tool as tool7 } from "ai";
|
|
2163
2223
|
import z10 from "zod";
|
|
2164
|
-
import { readdir as
|
|
2165
|
-
import { join as join10 } from "node:path";
|
|
2166
|
-
|
|
2167
|
-
// src/lib/languages.ts
|
|
2168
|
-
import { readdir as readdir2, readFile as readFile7 } from "node:fs/promises";
|
|
2169
|
-
import { existsSync as existsSync2 } from "node:fs";
|
|
2170
|
-
import { join as join9 } from "node:path";
|
|
2171
|
-
|
|
2172
|
-
// src/lib/tools/utils/packageManager.ts
|
|
2173
|
-
import { readFile as readFile6 } from "node:fs/promises";
|
|
2174
|
-
import { existsSync } from "node:fs";
|
|
2224
|
+
import { readdir as readdir2, readFile as readFile6 } from "node:fs/promises";
|
|
2175
2225
|
import { join as join8 } from "node:path";
|
|
2176
|
-
var LOCKFILES = [
|
|
2177
|
-
["pnpm-lock.yaml", "pnpm"],
|
|
2178
|
-
["yarn.lock", "yarn"],
|
|
2179
|
-
["bun.lockb", "bun"],
|
|
2180
|
-
["bun.lock", "bun"],
|
|
2181
|
-
["package-lock.json", "npm"]
|
|
2182
|
-
];
|
|
2183
|
-
async function readPackageJson(cwd = process.cwd()) {
|
|
2184
|
-
return JSON.parse(await readFile6(join8(cwd, "package.json"), "utf8"));
|
|
2185
|
-
}
|
|
2186
|
-
function packageManagerFrom(pkg) {
|
|
2187
|
-
return pkg.packageManager?.split("@")[0] ?? "npm";
|
|
2188
|
-
}
|
|
2189
|
-
function packageManagerFromLockfile(cwd) {
|
|
2190
|
-
return LOCKFILES.find(([file]) => existsSync(join8(cwd, file)))?.[1];
|
|
2191
|
-
}
|
|
2192
|
-
async function detectPackageManager(cwd) {
|
|
2193
|
-
try {
|
|
2194
|
-
const pkg = await readPackageJson(cwd);
|
|
2195
|
-
if (pkg.packageManager) return packageManagerFrom(pkg);
|
|
2196
|
-
} catch {
|
|
2197
|
-
}
|
|
2198
|
-
return packageManagerFromLockfile(cwd) ?? "npm";
|
|
2199
|
-
}
|
|
2200
|
-
|
|
2201
|
-
// src/lib/shell.ts
|
|
2202
|
-
function shellQuote(value) {
|
|
2203
|
-
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
2204
|
-
}
|
|
2205
|
-
|
|
2206
|
-
// src/lib/languages.ts
|
|
2207
|
-
var ENTRYPOINT_TOKEN = "{entrypoint}";
|
|
2208
|
-
var INGEST_DIR = ".algolia-wizard";
|
|
2209
|
-
var CSHARP_PROJECT = `${INGEST_DIR}/ingest/ingest.csproj`;
|
|
2210
|
-
var VISIBLE_INGEST_DIR = "algolia-wizard";
|
|
2211
|
-
var PY_VENV = `${INGEST_DIR}/.venv`;
|
|
2212
|
-
var PY_VENV_PYTHON = `${PY_VENV}/bin/python`;
|
|
2213
|
-
var PY_REQUIREMENTS = `${INGEST_DIR}/requirements.txt`;
|
|
2214
|
-
var LANGUAGE_PROFILES = {
|
|
2215
|
-
javascript: {
|
|
2216
|
-
id: "javascript",
|
|
2217
|
-
displayName: "JavaScript/TypeScript",
|
|
2218
|
-
aliases: [
|
|
2219
|
-
"javascript",
|
|
2220
|
-
"js",
|
|
2221
|
-
"typescript",
|
|
2222
|
-
"ts",
|
|
2223
|
-
"node",
|
|
2224
|
-
"nodejs",
|
|
2225
|
-
"node.js",
|
|
2226
|
-
"bun",
|
|
2227
|
-
"deno",
|
|
2228
|
-
"ecmascript",
|
|
2229
|
-
"jsx",
|
|
2230
|
-
"tsx"
|
|
2231
|
-
],
|
|
2232
|
-
manifests: ["package.json"],
|
|
2233
|
-
// The concrete npm-family manager is resolved by detectPackageManager (it
|
|
2234
|
-
// honours the package.json `packageManager` field, which lockfiles can't
|
|
2235
|
-
// express), so one spec covers all four and `resolveToolchain` rewrites the
|
|
2236
|
-
// binary below.
|
|
2237
|
-
packageManagers: [
|
|
2238
|
-
{
|
|
2239
|
-
id: "npm",
|
|
2240
|
-
dependency: { mode: "agent-declares", file: "package.json" },
|
|
2241
|
-
installSteps: [{ argv: ["npm", "install"] }],
|
|
2242
|
-
ingest: {
|
|
2243
|
-
kind: "auto",
|
|
2244
|
-
argv: ["node", ENTRYPOINT_TOKEN],
|
|
2245
|
-
entrypointExtensions: [".mjs", ".cjs", ".js"]
|
|
2246
|
-
}
|
|
2247
|
-
}
|
|
2248
|
-
],
|
|
2249
|
-
sdk: { packageName: "algoliasearch", versionPin: "^5", docKey: "js" },
|
|
2250
|
-
ingestEntrypointExample: `${INGEST_DIR}/ingest.mjs`,
|
|
2251
|
-
// package.json scripts are repo-defined, so they're resolved at run time by
|
|
2252
|
-
// repoVerification rather than listed here.
|
|
2253
|
-
verification: [],
|
|
2254
|
-
envReadInstruction: "Read them from `process.env`.",
|
|
2255
|
-
skipDirs: ["node_modules", "dist", "build", "coverage", ".next", "out"]
|
|
2256
|
-
},
|
|
2257
|
-
python: {
|
|
2258
|
-
id: "python",
|
|
2259
|
-
displayName: "Python",
|
|
2260
|
-
aliases: ["python", "python3", "py", "cpython"],
|
|
2261
|
-
manifests: [
|
|
2262
|
-
"pyproject.toml",
|
|
2263
|
-
"requirements.txt",
|
|
2264
|
-
"setup.py",
|
|
2265
|
-
"setup.cfg",
|
|
2266
|
-
"Pipfile"
|
|
2267
|
-
],
|
|
2268
|
-
// Deliberately one path for every Python repo: a wizard-owned venv under
|
|
2269
|
-
// .algolia-wizard. Reusing the project's uv/poetry environment would mean
|
|
2270
|
-
// mutating the developer's real dependency manifest and lockfile, and the
|
|
2271
|
-
// declare-here/install-there split is the main way ingestion silently ends
|
|
2272
|
-
// up without the SDK installed. The tradeoff: the script can import the
|
|
2273
|
-
// Algolia client and anything it declares itself, but not the project's own
|
|
2274
|
-
// packages (see the optional root-requirements step below).
|
|
2275
|
-
packageManagers: [
|
|
2276
|
-
{
|
|
2277
|
-
id: "pip-venv",
|
|
2278
|
-
dependency: { mode: "agent-declares", file: PY_REQUIREMENTS },
|
|
2279
|
-
installSteps: [
|
|
2280
|
-
{ argv: ["python3", "-m", "venv", PY_VENV] },
|
|
2281
|
-
{
|
|
2282
|
-
argv: [PY_VENV_PYTHON, "-m", "pip", "install", "-r", PY_REQUIREMENTS]
|
|
2283
|
-
},
|
|
2284
|
-
// Best-effort access to the project's own dependencies (DB drivers,
|
|
2285
|
-
// ORMs) when the repo pins them the classic way.
|
|
2286
|
-
{
|
|
2287
|
-
argv: [
|
|
2288
|
-
PY_VENV_PYTHON,
|
|
2289
|
-
"-m",
|
|
2290
|
-
"pip",
|
|
2291
|
-
"install",
|
|
2292
|
-
"-r",
|
|
2293
|
-
"requirements.txt"
|
|
2294
|
-
],
|
|
2295
|
-
requiresFile: "requirements.txt",
|
|
2296
|
-
optional: true
|
|
2297
|
-
}
|
|
2298
|
-
],
|
|
2299
|
-
ingest: {
|
|
2300
|
-
kind: "auto",
|
|
2301
|
-
argv: [PY_VENV_PYTHON, ENTRYPOINT_TOKEN],
|
|
2302
|
-
entrypointExtensions: [".py"]
|
|
2303
|
-
}
|
|
2304
|
-
}
|
|
2305
|
-
],
|
|
2306
|
-
sdk: {
|
|
2307
|
-
packageName: "algoliasearch",
|
|
2308
|
-
versionPin: ">=4,<5",
|
|
2309
|
-
docKey: "python"
|
|
2310
|
-
},
|
|
2311
|
-
ingestEntrypointExample: `${INGEST_DIR}/ingest.py`,
|
|
2312
|
-
localSourceCaveat: {
|
|
2313
|
-
unless: "requirements.txt",
|
|
2314
|
-
message: "The ingestion script runs in its own environment under .algolia-wizard/, so it can install the Algolia client but not this project's packages (no requirements.txt to install from). If the script needs your database driver or ORM, add those packages to .algolia-wizard/requirements.txt and re-run the install."
|
|
2315
|
-
},
|
|
2316
|
-
verification: [
|
|
2317
|
-
{
|
|
2318
|
-
// -x skips the venv this same directory holds; without it the check
|
|
2319
|
-
// compiles every installed package instead of the generated script.
|
|
2320
|
-
label: "python compileall",
|
|
2321
|
-
argv: ["python3", "-m", "compileall", "-q", "-x", "[.]venv", INGEST_DIR],
|
|
2322
|
-
requiresFile: INGEST_DIR
|
|
2323
|
-
}
|
|
2324
|
-
],
|
|
2325
|
-
envReadInstruction: "Read them from `os.environ`.",
|
|
2326
|
-
skipDirs: [
|
|
2327
|
-
"venv",
|
|
2328
|
-
"__pycache__",
|
|
2329
|
-
"site-packages",
|
|
2330
|
-
"dist",
|
|
2331
|
-
"build",
|
|
2332
|
-
"htmlcov"
|
|
2333
|
-
]
|
|
2334
|
-
},
|
|
2335
|
-
ruby: {
|
|
2336
|
-
id: "ruby",
|
|
2337
|
-
displayName: "Ruby",
|
|
2338
|
-
aliases: ["ruby", "rb", "rails", "ruby on rails", "rubyonrails"],
|
|
2339
|
-
manifests: ["Gemfile", "*.gemspec"],
|
|
2340
|
-
packageManagers: [
|
|
2341
|
-
{
|
|
2342
|
-
id: "bundler",
|
|
2343
|
-
dependency: { mode: "agent-declares", file: "Gemfile" },
|
|
2344
|
-
installSteps: [{ argv: ["bundle", "install"] }],
|
|
2345
|
-
ingest: {
|
|
2346
|
-
kind: "auto",
|
|
2347
|
-
argv: ["bundle", "exec", "ruby", ENTRYPOINT_TOKEN],
|
|
2348
|
-
entrypointExtensions: [".rb"]
|
|
2349
|
-
}
|
|
2350
|
-
}
|
|
2351
|
-
],
|
|
2352
|
-
sdk: { packageName: "algolia", versionPin: "~> 3.0", docKey: "ruby" },
|
|
2353
|
-
ingestEntrypointExample: `${INGEST_DIR}/ingest.rb`,
|
|
2354
|
-
// Ruby has no directory-level syntax check (`ruby -c` is one file at a
|
|
2355
|
-
// time), so verification relies on the agent's own review here.
|
|
2356
|
-
verification: [],
|
|
2357
|
-
envReadInstruction: "Read them from `ENV.fetch('NAME')`.",
|
|
2358
|
-
skipDirs: ["vendor", "tmp", "log", "coverage"]
|
|
2359
|
-
},
|
|
2360
|
-
php: {
|
|
2361
|
-
id: "php",
|
|
2362
|
-
displayName: "PHP",
|
|
2363
|
-
aliases: ["php", "laravel", "symfony"],
|
|
2364
|
-
manifests: ["composer.json"],
|
|
2365
|
-
packageManagers: [
|
|
2366
|
-
{
|
|
2367
|
-
id: "composer",
|
|
2368
|
-
// `composer require` both declares and installs, and unlike editing
|
|
2369
|
-
// composer.json by hand it can't leave composer.lock out of date (which
|
|
2370
|
-
// makes a later `composer install` refuse to run).
|
|
2371
|
-
dependency: { mode: "wizard-installs" },
|
|
2372
|
-
installSteps: [
|
|
2373
|
-
{
|
|
2374
|
-
argv: [
|
|
2375
|
-
"composer",
|
|
2376
|
-
"require",
|
|
2377
|
-
"algolia/algoliasearch-client-php:^4",
|
|
2378
|
-
"--no-interaction",
|
|
2379
|
-
// Repo post-install scripts are the project's code, not ours to
|
|
2380
|
-
// trigger; Laravel's package:discover also fails in a bare tree.
|
|
2381
|
-
"--no-scripts"
|
|
2382
|
-
]
|
|
2383
|
-
}
|
|
2384
|
-
],
|
|
2385
|
-
ingest: {
|
|
2386
|
-
kind: "auto",
|
|
2387
|
-
argv: ["php", ENTRYPOINT_TOKEN],
|
|
2388
|
-
entrypointExtensions: [".php"]
|
|
2389
|
-
}
|
|
2390
|
-
}
|
|
2391
|
-
],
|
|
2392
|
-
sdk: {
|
|
2393
|
-
packageName: "algolia/algoliasearch-client-php",
|
|
2394
|
-
versionPin: "^4",
|
|
2395
|
-
docKey: "php"
|
|
2396
|
-
},
|
|
2397
|
-
ingestEntrypointExample: `${INGEST_DIR}/ingest.php`,
|
|
2398
|
-
verification: [],
|
|
2399
|
-
envReadInstruction: "Read them from `getenv('NAME')`.",
|
|
2400
|
-
skipDirs: ["vendor", "node_modules"]
|
|
2401
|
-
},
|
|
2402
|
-
go: {
|
|
2403
|
-
id: "go",
|
|
2404
|
-
displayName: "Go",
|
|
2405
|
-
aliases: ["go", "golang"],
|
|
2406
|
-
manifests: ["go.mod"],
|
|
2407
|
-
packageManagers: [
|
|
2408
|
-
{
|
|
2409
|
-
id: "gomod",
|
|
2410
|
-
// Imports in the generated file are the declaration; `go mod tidy`
|
|
2411
|
-
// resolves and fetches them — which only works because the script lives
|
|
2412
|
-
// outside INGEST_DIR (see VISIBLE_INGEST_DIR).
|
|
2413
|
-
dependency: { mode: "code-imports" },
|
|
2414
|
-
installSteps: [{ argv: ["go", "mod", "tidy"] }],
|
|
2415
|
-
ingest: {
|
|
2416
|
-
kind: "auto",
|
|
2417
|
-
argv: ["go", "run", ENTRYPOINT_TOKEN],
|
|
2418
|
-
entrypointExtensions: [".go"]
|
|
2419
|
-
}
|
|
2420
|
-
}
|
|
2421
|
-
],
|
|
2422
|
-
sdk: {
|
|
2423
|
-
packageName: "github.com/algolia/algoliasearch-client-go/v4",
|
|
2424
|
-
versionPin: "v4",
|
|
2425
|
-
docKey: "go"
|
|
2426
|
-
},
|
|
2427
|
-
ingestEntrypointExample: `${VISIBLE_INGEST_DIR}/ingest.go`,
|
|
2428
|
-
verification: [
|
|
2429
|
-
{ label: "go vet", argv: ["go", "vet", "./..."], requiresFile: "go.mod" }
|
|
2430
|
-
],
|
|
2431
|
-
envReadInstruction: "Read them from `os.Getenv`.",
|
|
2432
|
-
skipDirs: ["vendor", "bin"]
|
|
2433
|
-
},
|
|
2434
|
-
java: {
|
|
2435
|
-
id: "java",
|
|
2436
|
-
displayName: "Java",
|
|
2437
|
-
aliases: ["java"],
|
|
2438
|
-
manifests: ["pom.xml", "build.gradle", "build.gradle.kts"],
|
|
2439
|
-
packageManagers: [
|
|
2440
|
-
{
|
|
2441
|
-
id: "maven",
|
|
2442
|
-
detectFiles: ["pom.xml"],
|
|
2443
|
-
sdkVersionPin: "[4,5)",
|
|
2444
|
-
dependency: { mode: "agent-declares", file: "pom.xml" },
|
|
2445
|
-
installSteps: [{ argv: ["mvn", "-q", "-DskipTests", "compile"] }],
|
|
2446
|
-
// The main class is a wizard constant the instructions require the agent
|
|
2447
|
-
// to use, so execution can't be redirected by agent output. Runnable only
|
|
2448
|
-
// because the install step above compiles src/main/java first — which is
|
|
2449
|
-
// why the entrypoint lives there rather than under .algolia-wizard/.
|
|
2450
|
-
ingest: {
|
|
2451
|
-
kind: "auto",
|
|
2452
|
-
argv: [
|
|
2453
|
-
"mvn",
|
|
2454
|
-
"-q",
|
|
2455
|
-
"org.codehaus.mojo:exec-maven-plugin:3.5.0:java",
|
|
2456
|
-
"-Dexec.mainClass=AlgoliaWizardIngest"
|
|
2457
|
-
],
|
|
2458
|
-
entrypointExtensions: [".java"]
|
|
2459
|
-
}
|
|
2460
|
-
},
|
|
2461
|
-
{
|
|
2462
|
-
id: "gradle",
|
|
2463
|
-
detectFiles: ["build.gradle", "build.gradle.kts"],
|
|
2464
|
-
dependency: {
|
|
2465
|
-
mode: "agent-declares",
|
|
2466
|
-
file: "build.gradle",
|
|
2467
|
-
alternatives: ["build.gradle.kts"]
|
|
2468
|
-
},
|
|
2469
|
-
installSteps: [],
|
|
2470
|
-
// Auto-running means executing the repo's own ./gradlew wrapper; out of
|
|
2471
|
-
// scope for now, so the wizard writes the code and prints the command.
|
|
2472
|
-
ingest: {
|
|
2473
|
-
kind: "manual",
|
|
2474
|
-
entrypointExtensions: [".java"],
|
|
2475
|
-
runCommand: "./gradlew runAlgoliaIngest",
|
|
2476
|
-
requiresBuildTask: "runAlgoliaIngest"
|
|
2477
|
-
}
|
|
2478
|
-
}
|
|
2479
|
-
],
|
|
2480
|
-
sdk: {
|
|
2481
|
-
packageName: "com.algolia:algoliasearch",
|
|
2482
|
-
versionPin: "4.+",
|
|
2483
|
-
docKey: "java",
|
|
2484
|
-
alsoRequires: "The class must be named AlgoliaWizardIngest, in the default package (no `package` statement), with a `public static void main`."
|
|
2485
|
-
},
|
|
2486
|
-
// Not under .algolia-wizard/: Maven and Gradle only compile src/main/<lang>,
|
|
2487
|
-
// so a class outside it never makes it onto the classpath and the run command
|
|
2488
|
-
// fails with "class not found".
|
|
2489
|
-
ingestEntrypointExample: "src/main/java/AlgoliaWizardIngest.java",
|
|
2490
|
-
verification: [
|
|
2491
|
-
{
|
|
2492
|
-
label: "mvn compile",
|
|
2493
|
-
argv: ["mvn", "-q", "-DskipTests", "compile"],
|
|
2494
|
-
requiresFile: "pom.xml"
|
|
2495
|
-
}
|
|
2496
|
-
],
|
|
2497
|
-
envReadInstruction: "Read them from `System.getenv`.",
|
|
2498
|
-
skipDirs: ["target", "build", "out"]
|
|
2499
|
-
},
|
|
2500
|
-
kotlin: {
|
|
2501
|
-
id: "kotlin",
|
|
2502
|
-
displayName: "Kotlin",
|
|
2503
|
-
aliases: ["kotlin", "kt", "ktor"],
|
|
2504
|
-
manifests: ["build.gradle.kts", "build.gradle", "pom.xml"],
|
|
2505
|
-
packageManagers: [
|
|
2506
|
-
{
|
|
2507
|
-
id: "gradle",
|
|
2508
|
-
detectFiles: ["build.gradle.kts", "build.gradle"],
|
|
2509
|
-
dependency: {
|
|
2510
|
-
mode: "agent-declares",
|
|
2511
|
-
file: "build.gradle.kts",
|
|
2512
|
-
alternatives: ["build.gradle"]
|
|
2513
|
-
},
|
|
2514
|
-
installSteps: [],
|
|
2515
|
-
ingest: {
|
|
2516
|
-
kind: "manual",
|
|
2517
|
-
entrypointExtensions: [".kt"],
|
|
2518
|
-
runCommand: "./gradlew runAlgoliaIngest",
|
|
2519
|
-
requiresBuildTask: "runAlgoliaIngest"
|
|
2520
|
-
}
|
|
2521
|
-
},
|
|
2522
|
-
// Kotlin/Maven is rare but real, and pom.xml is a Kotlin manifest — without
|
|
2523
|
-
// this spec such a repo falls through to Gradle and is told to run a
|
|
2524
|
-
// ./gradlew task that doesn't exist. Compiling needs the repo's own
|
|
2525
|
-
// kotlin-maven-plugin, so the run stays the developer's step.
|
|
2526
|
-
{
|
|
2527
|
-
id: "maven",
|
|
2528
|
-
detectFiles: ["pom.xml"],
|
|
2529
|
-
sdkVersionPin: "[3,4)",
|
|
2530
|
-
dependency: { mode: "agent-declares", file: "pom.xml" },
|
|
2531
|
-
installSteps: [{ argv: ["mvn", "-q", "-DskipTests", "compile"] }],
|
|
2532
|
-
ingest: {
|
|
2533
|
-
kind: "manual",
|
|
2534
|
-
entrypointExtensions: [".kt"],
|
|
2535
|
-
runCommand: "mvn -q org.codehaus.mojo:exec-maven-plugin:3.5.0:java -Dexec.mainClass=AlgoliaWizardIngest"
|
|
2536
|
-
}
|
|
2537
|
-
}
|
|
2538
|
-
],
|
|
2539
|
-
sdk: {
|
|
2540
|
-
packageName: "com.algolia:algoliasearch-client-kotlin",
|
|
2541
|
-
versionPin: "3.+",
|
|
2542
|
-
docKey: "kotlin",
|
|
2543
|
-
// The published client's commonMain ships only ktor-client-core; without an
|
|
2544
|
-
// engine the script compiles and then fails at its first request.
|
|
2545
|
-
alsoRequires: "The Kotlin client bundles no HTTP engine, so also declare one (e.g. io.ktor:ktor-client-okhttp). Name the object AlgoliaWizardIngest in the default package, with a @JvmStatic main."
|
|
2546
|
-
},
|
|
2547
|
-
ingestEntrypointExample: "src/main/kotlin/AlgoliaWizardIngest.kt",
|
|
2548
|
-
verification: [],
|
|
2549
|
-
envReadInstruction: "Read them from `System.getenv`.",
|
|
2550
|
-
skipDirs: ["build", "out"]
|
|
2551
|
-
},
|
|
2552
|
-
scala: {
|
|
2553
|
-
id: "scala",
|
|
2554
|
-
displayName: "Scala",
|
|
2555
|
-
aliases: ["scala", "sbt"],
|
|
2556
|
-
manifests: ["build.sbt", "build.sc"],
|
|
2557
|
-
packageManagers: [
|
|
2558
|
-
{
|
|
2559
|
-
id: "sbt",
|
|
2560
|
-
dependency: { mode: "agent-declares", file: "build.sbt" },
|
|
2561
|
-
installSteps: [],
|
|
2562
|
-
ingest: {
|
|
2563
|
-
kind: "manual",
|
|
2564
|
-
entrypointExtensions: [".scala"],
|
|
2565
|
-
runCommand: 'sbt "runMain AlgoliaWizardIngest"'
|
|
2566
|
-
}
|
|
2567
|
-
}
|
|
2568
|
-
],
|
|
2569
|
-
sdk: {
|
|
2570
|
-
packageName: "com.algolia:algoliasearch-scala_2.13",
|
|
2571
|
-
versionPin: "2.+",
|
|
2572
|
-
docKey: "scala",
|
|
2573
|
-
alsoRequires: "Name the object AlgoliaWizardIngest in the default package (no `package` statement) so `runMain AlgoliaWizardIngest` resolves it."
|
|
2574
|
-
},
|
|
2575
|
-
ingestEntrypointExample: "src/main/scala/AlgoliaWizardIngest.scala",
|
|
2576
|
-
verification: [],
|
|
2577
|
-
envReadInstruction: "Read them from `sys.env`.",
|
|
2578
|
-
// `project/` holds sbt's build definition, but the name is generic enough
|
|
2579
|
-
// that some repos use it for source; scanning it is cheap, missing source
|
|
2580
|
-
// is not.
|
|
2581
|
-
skipDirs: ["target"]
|
|
2582
|
-
},
|
|
2583
|
-
csharp: {
|
|
2584
|
-
id: "csharp",
|
|
2585
|
-
displayName: "C#",
|
|
2586
|
-
aliases: ["c#", "csharp", "cs", ".net", "dotnet", "net", "asp.net"],
|
|
2587
|
-
manifests: ["*.csproj", "*.sln", "global.json"],
|
|
2588
|
-
packageManagers: [
|
|
2589
|
-
{
|
|
2590
|
-
id: "dotnet",
|
|
2591
|
-
// A self-contained project under .algolia-wizard keeps the ingest script
|
|
2592
|
-
// out of the repo's own build graph.
|
|
2593
|
-
dependency: { mode: "agent-declares", file: CSHARP_PROJECT },
|
|
2594
|
-
installSteps: [{ argv: ["dotnet", "restore", CSHARP_PROJECT] }],
|
|
2595
|
-
ingest: {
|
|
2596
|
-
kind: "auto",
|
|
2597
|
-
argv: ["dotnet", "run", "--project", ENTRYPOINT_TOKEN],
|
|
2598
|
-
entrypointExtensions: [".csproj"]
|
|
2599
|
-
}
|
|
2600
|
-
}
|
|
2601
|
-
],
|
|
2602
|
-
sdk: {
|
|
2603
|
-
packageName: "Algolia.Search",
|
|
2604
|
-
versionPin: "7.*",
|
|
2605
|
-
docKey: "csharp"
|
|
2606
|
-
},
|
|
2607
|
-
ingestEntrypointExample: CSHARP_PROJECT,
|
|
2608
|
-
verification: [
|
|
2609
|
-
{
|
|
2610
|
-
label: "dotnet build",
|
|
2611
|
-
argv: ["dotnet", "build", CSHARP_PROJECT, "--nologo"],
|
|
2612
|
-
requiresFile: CSHARP_PROJECT
|
|
2613
|
-
}
|
|
2614
|
-
],
|
|
2615
|
-
envReadInstruction: 'Read them from `Environment.GetEnvironmentVariable("NAME")`.',
|
|
2616
|
-
// Deliberately not `packages`: modern .NET uses PackageReference, and
|
|
2617
|
-
// `packages/` is where pnpm/Lerna/Turborepo monorepos keep all their source —
|
|
2618
|
-
// skipping it would hide the entities the scan is looking for.
|
|
2619
|
-
skipDirs: ["bin", "obj"]
|
|
2620
|
-
}
|
|
2621
|
-
};
|
|
2622
|
-
var DEFAULT_LANGUAGE_ID = "javascript";
|
|
2623
|
-
var JAVASCRIPT = "javascript";
|
|
2624
|
-
var CURATED_LANGUAGES = Object.values(
|
|
2625
|
-
LANGUAGE_PROFILES
|
|
2626
|
-
).map((profile) => profile.displayName);
|
|
2627
|
-
function isBackendLanguage(profile) {
|
|
2628
|
-
return profile.id !== JAVASCRIPT;
|
|
2629
|
-
}
|
|
2630
|
-
function normalizeLanguageName(name) {
|
|
2631
|
-
return name.toLowerCase().trim().replace(/[\s_-]+/g, "");
|
|
2632
|
-
}
|
|
2633
|
-
var ALIAS_TO_ID = /* @__PURE__ */ new Map();
|
|
2634
|
-
for (const profile of Object.values(LANGUAGE_PROFILES)) {
|
|
2635
|
-
for (const alias of [profile.id, profile.displayName, ...profile.aliases]) {
|
|
2636
|
-
ALIAS_TO_ID.set(normalizeLanguageName(alias), profile.id);
|
|
2637
|
-
}
|
|
2638
|
-
}
|
|
2639
|
-
function resolveLanguageProfile(name) {
|
|
2640
|
-
const id = ALIAS_TO_ID.get(normalizeLanguageName(name));
|
|
2641
|
-
return id ? LANGUAGE_PROFILES[id] : void 0;
|
|
2642
|
-
}
|
|
2643
|
-
function isSameLanguage(a, b) {
|
|
2644
|
-
const x = resolveLanguageProfile(a);
|
|
2645
|
-
const y = resolveLanguageProfile(b);
|
|
2646
|
-
if (x && y) return x.id === y.id;
|
|
2647
|
-
if (x || y) return false;
|
|
2648
|
-
const folded = normalizeLanguageName(a);
|
|
2649
|
-
return folded !== "" && folded === normalizeLanguageName(b);
|
|
2650
|
-
}
|
|
2651
|
-
var BASE_SKIP_DIRS = ["node_modules", ".git", "dist"];
|
|
2652
|
-
var ALL_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
2653
|
-
...BASE_SKIP_DIRS,
|
|
2654
|
-
...Object.values(LANGUAGE_PROFILES).flatMap((p) => p.skipDirs)
|
|
2655
|
-
]);
|
|
2656
|
-
var ALLOWED_BINARIES = new Set(
|
|
2657
|
-
Object.values(LANGUAGE_PROFILES).flatMap((profile) => [
|
|
2658
|
-
...profile.packageManagers.flatMap((pm) => [
|
|
2659
|
-
...pm.installSteps.map((s) => s.argv[0]),
|
|
2660
|
-
...pm.ingest.kind === "auto" ? [pm.ingest.argv[0]] : []
|
|
2661
|
-
]),
|
|
2662
|
-
...profile.verification.map((v) => v.argv[0])
|
|
2663
|
-
])
|
|
2664
|
-
);
|
|
2665
|
-
var JS_PACKAGE_MANAGERS = /* @__PURE__ */ new Set(["npm", "pnpm", "yarn", "bun"]);
|
|
2666
|
-
function isWorktreeRelativeCommand(command) {
|
|
2667
|
-
return command.includes("/");
|
|
2668
|
-
}
|
|
2669
|
-
function withCommand(argv, command) {
|
|
2670
|
-
return [command, ...argv.slice(1)];
|
|
2671
|
-
}
|
|
2672
|
-
function resolveDeclaredManifest(root, packageManager) {
|
|
2673
|
-
const { dependency } = packageManager;
|
|
2674
|
-
if (dependency.mode !== "agent-declares" || !dependency.alternatives?.length) {
|
|
2675
|
-
return packageManager;
|
|
2676
|
-
}
|
|
2677
|
-
const present = [dependency.file, ...dependency.alternatives].find(
|
|
2678
|
-
(file) => existsSync2(join9(root, file))
|
|
2679
|
-
);
|
|
2680
|
-
if (!present || present === dependency.file) return packageManager;
|
|
2681
|
-
return { ...packageManager, dependency: { ...dependency, file: present } };
|
|
2682
|
-
}
|
|
2683
|
-
async function manifestPresent(root, manifest, listing) {
|
|
2684
|
-
if (!manifest.startsWith("*.")) return existsSync2(join9(root, manifest));
|
|
2685
|
-
if (!listing.entries) {
|
|
2686
|
-
const entries = await readdir2(root).catch(() => []);
|
|
2687
|
-
listing.entries = Array.isArray(entries) ? entries : [];
|
|
2688
|
-
}
|
|
2689
|
-
const suffix = manifest.slice(1);
|
|
2690
|
-
return listing.entries.some((e) => e.endsWith(suffix));
|
|
2691
|
-
}
|
|
2692
|
-
async function profileManifestPresent(root, profile, listing) {
|
|
2693
|
-
for (const manifest of profile.manifests) {
|
|
2694
|
-
if (await manifestPresent(root, manifest, listing)) return true;
|
|
2695
|
-
}
|
|
2696
|
-
return false;
|
|
2697
|
-
}
|
|
2698
|
-
async function detectProfilesFromManifests(root) {
|
|
2699
|
-
const listing = {};
|
|
2700
|
-
const found = [];
|
|
2701
|
-
for (const profile of Object.values(LANGUAGE_PROFILES)) {
|
|
2702
|
-
if (await profileManifestPresent(root, profile, listing)) found.push(profile);
|
|
2703
|
-
}
|
|
2704
|
-
return found;
|
|
2705
|
-
}
|
|
2706
|
-
async function hasProfileManifest(root, profile) {
|
|
2707
|
-
return profileManifestPresent(root, profile, {});
|
|
2708
|
-
}
|
|
2709
|
-
async function pickIngestionCandidates(root, confirmedNames) {
|
|
2710
|
-
const confirmed3 = confirmedNames.map((name) => resolveLanguageProfile(name)).filter((p) => p !== void 0);
|
|
2711
|
-
const onDisk = await detectProfilesFromManifests(root);
|
|
2712
|
-
const onDiskIds = new Set(onDisk.map((p) => p.id));
|
|
2713
|
-
const candidates = [
|
|
2714
|
-
...new Map(
|
|
2715
|
-
confirmed3.filter((p) => onDiskIds.has(p.id)).map((p) => [p.id, p])
|
|
2716
|
-
).values()
|
|
2717
|
-
];
|
|
2718
|
-
return { candidates, confirmed: confirmed3, onDisk };
|
|
2719
|
-
}
|
|
2720
|
-
async function resolveToolchain(root, profile) {
|
|
2721
|
-
const signals = (pm) => [
|
|
2722
|
-
...pm.lockfiles ?? [],
|
|
2723
|
-
...pm.detectFiles ?? []
|
|
2724
|
-
];
|
|
2725
|
-
const matched = profile.packageManagers.find(
|
|
2726
|
-
(pm) => signals(pm).some((f) => existsSync2(join9(root, f)))
|
|
2727
|
-
);
|
|
2728
|
-
const fallback = profile.packageManagers.find((pm) => signals(pm).length === 0) ?? profile.packageManagers[0];
|
|
2729
|
-
const packageManager = resolveDeclaredManifest(root, matched ?? fallback);
|
|
2730
|
-
let { installSteps, ingest } = packageManager;
|
|
2731
|
-
installSteps = installSteps.map(
|
|
2732
|
-
(step) => isWorktreeRelativeCommand(step.argv[0]) ? { ...step, argv: withCommand(step.argv, join9(root, step.argv[0])) } : step
|
|
2733
|
-
);
|
|
2734
|
-
if (ingest.kind === "auto" && isWorktreeRelativeCommand(ingest.argv[0])) {
|
|
2735
|
-
ingest = {
|
|
2736
|
-
...ingest,
|
|
2737
|
-
argv: withCommand(ingest.argv, join9(root, ingest.argv[0]))
|
|
2738
|
-
};
|
|
2739
|
-
}
|
|
2740
|
-
if (profile.id === "javascript") {
|
|
2741
|
-
const pm = await detectPackageManager(root);
|
|
2742
|
-
if (JS_PACKAGE_MANAGERS.has(pm)) {
|
|
2743
|
-
installSteps = installSteps.map((step) => ({
|
|
2744
|
-
...step,
|
|
2745
|
-
argv: withCommand(step.argv, pm)
|
|
2746
|
-
}));
|
|
2747
|
-
if (pm === "bun" && ingest.kind === "auto") {
|
|
2748
|
-
ingest = { ...ingest, argv: withCommand(ingest.argv, "bun") };
|
|
2749
|
-
}
|
|
2750
|
-
}
|
|
2751
|
-
}
|
|
2752
|
-
return { profile, packageManager, installSteps, ingest };
|
|
2753
|
-
}
|
|
2754
|
-
function resolveIngestArgv(ingest, entrypoint) {
|
|
2755
|
-
if (ingest.kind !== "auto") {
|
|
2756
|
-
throw new Error("resolveIngestArgv called for a manual-run toolchain");
|
|
2757
|
-
}
|
|
2758
|
-
return ingest.argv.map(
|
|
2759
|
-
(part) => part === ENTRYPOINT_TOKEN ? entrypoint : part
|
|
2760
|
-
);
|
|
2761
|
-
}
|
|
2762
|
-
function describeIngestCommand(ingest, entrypoint) {
|
|
2763
|
-
if (ingest.kind !== "auto") return ingest.runCommand;
|
|
2764
|
-
return ingest.argv.map((part) => part === ENTRYPOINT_TOKEN ? shellQuote(entrypoint) : part).join(" ");
|
|
2765
|
-
}
|
|
2766
|
-
function ingestScriptDir(profile) {
|
|
2767
|
-
const parts = profile.ingestEntrypointExample.split("/");
|
|
2768
|
-
return parts.slice(0, -1).join("/") || ".";
|
|
2769
|
-
}
|
|
2770
|
-
function localSourceLimitation(root, profile) {
|
|
2771
|
-
const caveat = profile.localSourceCaveat;
|
|
2772
|
-
if (!caveat) return void 0;
|
|
2773
|
-
return existsSync2(join9(root, caveat.unless)) ? void 0 : caveat.message;
|
|
2774
|
-
}
|
|
2775
|
-
async function missingBuildTask(root, toolchain) {
|
|
2776
|
-
const { ingest, packageManager } = toolchain;
|
|
2777
|
-
if (ingest.kind !== "manual" || !ingest.requiresBuildTask) return void 0;
|
|
2778
|
-
if (packageManager.dependency.mode !== "agent-declares") return void 0;
|
|
2779
|
-
const buildFile = join9(root, packageManager.dependency.file);
|
|
2780
|
-
const contents = await readFile7(buildFile, "utf8").catch(() => void 0);
|
|
2781
|
-
if (contents === void 0) return void 0;
|
|
2782
|
-
return contents.includes(ingest.requiresBuildTask) ? void 0 : ingest.requiresBuildTask;
|
|
2783
|
-
}
|
|
2784
|
-
function sdkVersionPin(profile, packageManager) {
|
|
2785
|
-
return packageManager.sdkVersionPin ?? profile.sdk.versionPin;
|
|
2786
|
-
}
|
|
2787
|
-
function dependencyInstruction(toolchain) {
|
|
2788
|
-
const { profile, packageManager } = toolchain;
|
|
2789
|
-
const { packageName } = profile.sdk;
|
|
2790
|
-
const versionPin = sdkVersionPin(profile, packageManager);
|
|
2791
|
-
const also = profile.sdk.alsoRequires ? ` ${profile.sdk.alsoRequires}` : "";
|
|
2792
|
-
switch (packageManager.dependency.mode) {
|
|
2793
|
-
case "wizard-installs":
|
|
2794
|
-
return `The wizard installs ${packageName} ${versionPin} in the worktree after you finish \u2014 import it directly and do not edit dependency manifests for it.${also}`;
|
|
2795
|
-
case "code-imports":
|
|
2796
|
-
return `Import ${packageName} in the script; the wizard resolves and fetches it in the worktree after you finish. Do not edit dependency manifests by hand.${also}`;
|
|
2797
|
-
case "agent-declares":
|
|
2798
|
-
return `Declare ${packageName} ${versionPin} in "${packageManager.dependency.file}" (create the file if needed), plus any other dependency your script imports; the wizard installs them in the worktree after you finish.${also}`;
|
|
2799
|
-
}
|
|
2800
|
-
}
|
|
2801
|
-
|
|
2802
|
-
// src/lib/tools/searchFiles.ts
|
|
2803
2226
|
var MAX_QUERY_LENGTH = 1e3;
|
|
2804
2227
|
async function walkFiles(dir) {
|
|
2228
|
+
const skip = /* @__PURE__ */ new Set(["node_modules", ".git", "dist"]);
|
|
2805
2229
|
const out = [];
|
|
2806
|
-
for (const e of await
|
|
2807
|
-
if (e.name.startsWith(".") ||
|
|
2808
|
-
const full =
|
|
2230
|
+
for (const e of await readdir2(dir, { withFileTypes: true })) {
|
|
2231
|
+
if (e.name.startsWith(".") || skip.has(e.name)) continue;
|
|
2232
|
+
const full = join8(dir, e.name);
|
|
2809
2233
|
if (e.isDirectory()) out.push(...await walkFiles(full));
|
|
2810
2234
|
else if (e.isFile()) out.push(full);
|
|
2811
2235
|
}
|
|
@@ -2838,7 +2262,7 @@ function searchFilesTool(ctx) {
|
|
|
2838
2262
|
for (const file of await walkFiles(resolved.target)) {
|
|
2839
2263
|
let content;
|
|
2840
2264
|
try {
|
|
2841
|
-
content = await
|
|
2265
|
+
content = await readFile6(file, "utf8");
|
|
2842
2266
|
} catch {
|
|
2843
2267
|
continue;
|
|
2844
2268
|
}
|
|
@@ -2862,144 +2286,88 @@ function searchFilesTool(ctx) {
|
|
|
2862
2286
|
import { tool as tool8 } from "ai";
|
|
2863
2287
|
import z11 from "zod";
|
|
2864
2288
|
|
|
2865
|
-
// src/lib/tools/repoVerification.ts
|
|
2866
|
-
import { existsSync as existsSync3 } from "node:fs";
|
|
2867
|
-
import { join as join11 } from "node:path";
|
|
2868
|
-
|
|
2869
2289
|
// src/lib/tools/utils/runCommand.ts
|
|
2870
2290
|
import { spawn as spawn2 } from "node:child_process";
|
|
2871
|
-
|
|
2872
|
-
var INGEST_TIMEOUT_MS = 15 * 6e4;
|
|
2873
|
-
var VERIFY_TIMEOUT_MS = 10 * 6e4;
|
|
2874
|
-
var KILL_GRACE_MS = 5e3;
|
|
2875
|
-
function runCommand(command, args, options = {}) {
|
|
2876
|
-
const { cwd, env, timeoutMs = VERIFY_TIMEOUT_MS } = options;
|
|
2291
|
+
function runCommand(command, args, cwd) {
|
|
2877
2292
|
return new Promise((resolve4) => {
|
|
2878
2293
|
let output = "";
|
|
2879
|
-
let settled = false;
|
|
2880
2294
|
const child = spawn2(command, args, {
|
|
2881
2295
|
cwd,
|
|
2882
|
-
|
|
2883
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
2884
|
-
...env ? { env: { ...process.env, ...env } } : {}
|
|
2296
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2885
2297
|
});
|
|
2886
|
-
const settle = (result) => {
|
|
2887
|
-
if (settled) return;
|
|
2888
|
-
settled = true;
|
|
2889
|
-
clearTimeout(timer);
|
|
2890
|
-
resolve4(result);
|
|
2891
|
-
};
|
|
2892
|
-
const timer = setTimeout(() => {
|
|
2893
|
-
child.kill("SIGTERM");
|
|
2894
|
-
setTimeout(() => child.kill("SIGKILL"), KILL_GRACE_MS).unref();
|
|
2895
|
-
const seconds = Math.round(timeoutMs / 1e3);
|
|
2896
|
-
settle({
|
|
2897
|
-
code: 1,
|
|
2898
|
-
output: `${output}
|
|
2899
|
-
Timed out after ${seconds}s: ${command} ${args.join(" ")}`.trim(),
|
|
2900
|
-
timedOut: true
|
|
2901
|
-
});
|
|
2902
|
-
}, timeoutMs);
|
|
2903
2298
|
child.stdout?.on("data", (d) => output += d);
|
|
2904
2299
|
child.stderr?.on("data", (d) => output += d);
|
|
2905
2300
|
child.on(
|
|
2906
2301
|
"error",
|
|
2907
|
-
(err) =>
|
|
2908
|
-
code: 1,
|
|
2909
|
-
output: `Failed to run ${command}: ${err.message}`,
|
|
2910
|
-
timedOut: false
|
|
2911
|
-
})
|
|
2912
|
-
);
|
|
2913
|
-
child.on(
|
|
2914
|
-
"close",
|
|
2915
|
-
(code) => settle({ code: code ?? 1, output, timedOut: false })
|
|
2302
|
+
(err) => resolve4({ code: 1, output: `Failed to run ${command}: ${err.message}` })
|
|
2916
2303
|
);
|
|
2304
|
+
child.on("close", (code) => resolve4({ code: code ?? 1, output }));
|
|
2917
2305
|
});
|
|
2918
2306
|
}
|
|
2919
2307
|
|
|
2308
|
+
// src/lib/tools/utils/packageManager.ts
|
|
2309
|
+
import { readFile as readFile7 } from "node:fs/promises";
|
|
2310
|
+
import { existsSync } from "node:fs";
|
|
2311
|
+
import { join as join9 } from "node:path";
|
|
2312
|
+
var LOCKFILES = [
|
|
2313
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
2314
|
+
["yarn.lock", "yarn"],
|
|
2315
|
+
["bun.lockb", "bun"],
|
|
2316
|
+
["bun.lock", "bun"],
|
|
2317
|
+
["package-lock.json", "npm"]
|
|
2318
|
+
];
|
|
2319
|
+
async function readPackageJson(cwd = process.cwd()) {
|
|
2320
|
+
return JSON.parse(await readFile7(join9(cwd, "package.json"), "utf8"));
|
|
2321
|
+
}
|
|
2322
|
+
function packageManagerFrom(pkg) {
|
|
2323
|
+
return pkg.packageManager?.split("@")[0] ?? "npm";
|
|
2324
|
+
}
|
|
2325
|
+
function packageManagerFromLockfile(cwd) {
|
|
2326
|
+
return LOCKFILES.find(([file]) => existsSync(join9(cwd, file)))?.[1];
|
|
2327
|
+
}
|
|
2328
|
+
async function detectPackageManager(cwd) {
|
|
2329
|
+
try {
|
|
2330
|
+
const pkg = await readPackageJson(cwd);
|
|
2331
|
+
if (pkg.packageManager) return packageManagerFrom(pkg);
|
|
2332
|
+
} catch {
|
|
2333
|
+
}
|
|
2334
|
+
return packageManagerFromLockfile(cwd) ?? "npm";
|
|
2335
|
+
}
|
|
2336
|
+
|
|
2920
2337
|
// src/lib/tools/repoVerification.ts
|
|
2921
2338
|
var VERIFICATION_SCRIPT_CANDIDATES = ["lint", "typecheck", "check"];
|
|
2922
|
-
async function
|
|
2923
|
-
const { code, output } = await runCommand(binary, args, {
|
|
2924
|
-
timeoutMs: VERIFY_TIMEOUT_MS
|
|
2925
|
-
});
|
|
2926
|
-
return { command, exitCode: code, ok: code === 0, output: output.trim() };
|
|
2927
|
-
}
|
|
2928
|
-
async function javascriptChecks() {
|
|
2339
|
+
async function runRepoVerificationCheck() {
|
|
2929
2340
|
let pkg;
|
|
2930
2341
|
try {
|
|
2931
2342
|
pkg = await readPackageJson();
|
|
2932
2343
|
} catch (err) {
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
};
|
|
2344
|
+
const limitation = `Could not read package.json to detect verification conventions: ${err.message}`;
|
|
2345
|
+
return { ok: false, checks: [], limitation };
|
|
2936
2346
|
}
|
|
2937
2347
|
const scripts = pkg.scripts ?? {};
|
|
2938
2348
|
const present = VERIFICATION_SCRIPT_CANDIDATES.filter((s) => s in scripts);
|
|
2939
2349
|
if (present.length === 0) {
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
};
|
|
2350
|
+
const limitation = `No verification script found in package.json (looked for: ${VERIFICATION_SCRIPT_CANDIDATES.join(", ")}).`;
|
|
2351
|
+
return { ok: false, checks: [], limitation };
|
|
2943
2352
|
}
|
|
2944
2353
|
const pm = await detectPackageManager(process.cwd());
|
|
2945
2354
|
const checks = [];
|
|
2946
2355
|
for (const script of present) {
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
);
|
|
2356
|
+
const command = `${pm} run ${script}`;
|
|
2357
|
+
const { code, output } = await runCommand(pm, ["run", script]);
|
|
2358
|
+
checks.push({ command, exitCode: code, ok: code === 0, output: output.trim() });
|
|
2950
2359
|
}
|
|
2951
|
-
return { checks };
|
|
2952
|
-
}
|
|
2953
|
-
async function registryChecks(id) {
|
|
2954
|
-
const profile = LANGUAGE_PROFILES[id];
|
|
2955
|
-
const runnable = profile.verification.filter(
|
|
2956
|
-
(spec) => !spec.requiresFile || existsSync3(join11(process.cwd(), spec.requiresFile))
|
|
2957
|
-
);
|
|
2958
|
-
if (runnable.length === 0) {
|
|
2959
|
-
return {
|
|
2960
|
-
limitation: `No mechanical verification available for ${profile.displayName} in this repo.`
|
|
2961
|
-
};
|
|
2962
|
-
}
|
|
2963
|
-
const checks = [];
|
|
2964
|
-
for (const spec of runnable) {
|
|
2965
|
-
checks.push(
|
|
2966
|
-
await runCheck(spec.argv.join(" "), spec.argv[0], [...spec.argv.slice(1)])
|
|
2967
|
-
);
|
|
2968
|
-
}
|
|
2969
|
-
return { checks };
|
|
2970
|
-
}
|
|
2971
|
-
async function runRepoVerificationCheck(languages = [DEFAULT_LANGUAGE_ID]) {
|
|
2972
|
-
const ids = [...new Set(languages)];
|
|
2973
|
-
if (ids.length === 0) ids.push(DEFAULT_LANGUAGE_ID);
|
|
2974
|
-
const checks = [];
|
|
2975
|
-
const limitations = [];
|
|
2976
|
-
for (const id of ids) {
|
|
2977
|
-
const result = id === JAVASCRIPT ? await javascriptChecks() : await registryChecks(id);
|
|
2978
|
-
if ("checks" in result) checks.push(...result.checks);
|
|
2979
|
-
else limitations.push(result.limitation);
|
|
2980
|
-
}
|
|
2981
|
-
if (checks.length === 0) {
|
|
2982
|
-
return {
|
|
2983
|
-
ok: false,
|
|
2984
|
-
checks: [],
|
|
2985
|
-
limitation: limitations.join(" ") || "No verification checks available."
|
|
2986
|
-
};
|
|
2987
|
-
}
|
|
2988
|
-
return {
|
|
2989
|
-
ok: checks.every((c) => c.ok),
|
|
2990
|
-
checks,
|
|
2991
|
-
...limitations.length ? { limitation: limitations.join(" ") } : {}
|
|
2992
|
-
};
|
|
2360
|
+
return { ok: checks.every((c) => c.ok), checks };
|
|
2993
2361
|
}
|
|
2994
2362
|
|
|
2995
2363
|
// src/lib/tools/verifyImplementation.ts
|
|
2996
|
-
function verifyImplementationTool(
|
|
2364
|
+
function verifyImplementationTool() {
|
|
2997
2365
|
return tool8({
|
|
2998
|
-
description: "Run the repo's mechanical verification
|
|
2366
|
+
description: "Run the repo's mechanical verification check for generated implementation changes. Detects lint/typecheck/check from package.json and returns structured pass/fail evidence for the verifier to interpret.",
|
|
2999
2367
|
inputSchema: z11.object(),
|
|
3000
2368
|
execute: async () => {
|
|
3001
|
-
logger.info(
|
|
3002
|
-
return runRepoVerificationCheck(
|
|
2369
|
+
logger.info("called verifyImplementation tool");
|
|
2370
|
+
return runRepoVerificationCheck();
|
|
3003
2371
|
}
|
|
3004
2372
|
});
|
|
3005
2373
|
}
|
|
@@ -3125,17 +2493,12 @@ var DEFAULT_TOOL_LIMITS = {
|
|
|
3125
2493
|
read: 20,
|
|
3126
2494
|
match: 100
|
|
3127
2495
|
};
|
|
3128
|
-
function createToolContext({
|
|
3129
|
-
limits = DEFAULT_TOOL_LIMITS,
|
|
3130
|
-
cwd = process.cwd(),
|
|
3131
|
-
languages = [DEFAULT_LANGUAGE_ID]
|
|
3132
|
-
} = {}) {
|
|
2496
|
+
function createToolContext(limits = DEFAULT_TOOL_LIMITS, cwd = process.cwd()) {
|
|
3133
2497
|
return {
|
|
3134
2498
|
root: cwd,
|
|
3135
2499
|
cwd,
|
|
3136
2500
|
limits,
|
|
3137
|
-
counts: { list: 0, search: 0, read: 0 }
|
|
3138
|
-
languages: languages.length ? languages : [DEFAULT_LANGUAGE_ID]
|
|
2501
|
+
counts: { list: 0, search: 0, read: 0 }
|
|
3139
2502
|
};
|
|
3140
2503
|
}
|
|
3141
2504
|
|
|
@@ -3172,7 +2535,7 @@ function createTools(ctx, { output, tools }) {
|
|
|
3172
2535
|
searchFiles: withLogging("searchFiles", searchFilesTool(ctx)),
|
|
3173
2536
|
verifyImplementation: withLogging(
|
|
3174
2537
|
"verifyImplementation",
|
|
3175
|
-
verifyImplementationTool(
|
|
2538
|
+
verifyImplementationTool()
|
|
3176
2539
|
),
|
|
3177
2540
|
generateRecord: withLogging("generateRecord", generateRecordTool(ctx)),
|
|
3178
2541
|
notifyUser: withLogging("notifyUser", notifyUserTool())
|
|
@@ -3208,7 +2571,7 @@ async function runAgent(req) {
|
|
|
3208
2571
|
baseURL: PROXY_BASE_URL,
|
|
3209
2572
|
fetch: proxyFetch
|
|
3210
2573
|
});
|
|
3211
|
-
const toolContext = createToolContext(
|
|
2574
|
+
const toolContext = createToolContext();
|
|
3212
2575
|
const readTools = ["readFile", "searchFiles", "listFiles"];
|
|
3213
2576
|
const hasReadTools = !req.tools || req.tools.some((t) => readTools.includes(t));
|
|
3214
2577
|
const instructions = [
|
|
@@ -3299,11 +2662,8 @@ var detectLanguageSchema = z16.object({
|
|
|
3299
2662
|
var detectLanguage = () => runAgent({
|
|
3300
2663
|
instructions: [
|
|
3301
2664
|
"Analyze the codebase and determine the programming languages and frameworks used",
|
|
3302
|
-
"
|
|
3303
|
-
"List the language that owns the backend/data code first \u2014 that is the one an ingestion script will be written in.",
|
|
3304
|
-
"If a superset language is found, exclude the subset language. TS-over-JS. Kotlin-over-Java when Kotlin is primary.",
|
|
2665
|
+
"If a superset language is found, exclude the subset language. TS-over-JS.",
|
|
3305
2666
|
"If a meta-framework is used, exclude the framework. Next-over-React.",
|
|
3306
|
-
"Frameworks include backend and server-rendering frameworks (e.g. Rails, Django, Laravel, Symfony, Spring Boot, ASP.NET Core, Flask, Gin, Ktor) as well as frontend ones (React, Vue, Angular, Svelte) and mobile ones (Flutter, SwiftUI).",
|
|
3307
2667
|
"Return the exact version",
|
|
3308
2668
|
"Exclude things like CSS frameworks, build tools, or testing frameworks",
|
|
3309
2669
|
'Use as few tools as possible, but do not guess. If you cant find the answer, say "unknown"',
|
|
@@ -3352,7 +2712,6 @@ var MODE_CONFIG = {
|
|
|
3352
2712
|
"Analyze the codebase to find the data entities (models) that should be ingested into Algolia.",
|
|
3353
2713
|
"For each entity, return its name, the file path(s) where it is defined, and its indexable attribute keys (the fields a user would search or filter on).",
|
|
3354
2714
|
"Inspect the source of each entity to extract real field names for attributes \u2014 do not guess or leave attributes empty.",
|
|
3355
|
-
"Entities live wherever the stack keeps them: TypeScript interfaces or a Prisma schema, Django models.py, Rails app/models, Laravel Eloquent models, JPA @Entity classes, Go structs, C# entity classes, Pydantic models.",
|
|
3356
2715
|
"Prefer domain models (e.g. Document, Product, User) over framework or infrastructure types.",
|
|
3357
2716
|
"Use as few tools as possible, but do not guess. If you cannot find any entities, return an empty array.",
|
|
3358
2717
|
"Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
|
|
@@ -3364,9 +2723,8 @@ var MODE_CONFIG = {
|
|
|
3364
2723
|
instructions: [
|
|
3365
2724
|
"Analyze the codebase to determine the single best location to add search UI functionality.",
|
|
3366
2725
|
"Prefer a shared, always-rendered layout location (e.g. a header or navigation component) so search is reachable across the app.",
|
|
3367
|
-
"
|
|
3368
|
-
|
|
3369
|
-
'Use as few tools as possible, but do not guess. If the project renders no UI at all (an API-only service), say "unknown".',
|
|
2726
|
+
"Return one file path as searchImplementationAnalysis (e.g. /layouts/header.tsx).",
|
|
2727
|
+
'Use as few tools as possible, but do not guess. If you cannot find a clear location, say "unknown".',
|
|
3370
2728
|
"Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
|
|
3371
2729
|
"When done, call reportStatus"
|
|
3372
2730
|
],
|
|
@@ -3375,8 +2733,8 @@ var MODE_CONFIG = {
|
|
|
3375
2733
|
verification: {
|
|
3376
2734
|
instructions: [
|
|
3377
2735
|
"Analyze the codebase to determine which code-quality tools are available to validate changes.",
|
|
3378
|
-
"Look at
|
|
3379
|
-
'Return the tool names as an array, e.g. ["eslint", "prettier", "tsc"]
|
|
2736
|
+
"Look at package.json scripts, config files (e.g. .eslintrc, tsconfig, prettier), and dev dependencies.",
|
|
2737
|
+
'Return the tool names as an array, e.g. ["eslint", "prettier", "tsc"].',
|
|
3380
2738
|
"Use as few tools as possible, but do not guess. If you cannot find any, return an empty array.",
|
|
3381
2739
|
"Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
|
|
3382
2740
|
"When done, call reportStatus"
|
|
@@ -3403,7 +2761,7 @@ async function runAnalysis(mode, extraInstructions = []) {
|
|
|
3403
2761
|
// package.json
|
|
3404
2762
|
var package_default = {
|
|
3405
2763
|
name: "@algolia/wizard",
|
|
3406
|
-
version: "0.8.0
|
|
2764
|
+
version: "0.8.0",
|
|
3407
2765
|
description: "Magically implement Algolia functionality in your codebase",
|
|
3408
2766
|
type: "module",
|
|
3409
2767
|
engines: {
|
|
@@ -3425,7 +2783,7 @@ var package_default = {
|
|
|
3425
2783
|
prepare: "husky",
|
|
3426
2784
|
prepublishOnly: "pnpm build",
|
|
3427
2785
|
reset: "tsx ./scripts/reset-state.ts",
|
|
3428
|
-
"test:
|
|
2786
|
+
"test:fixtures": "touch .env && tsx --env-file=.env ./fixtures/run-fixtures.ts",
|
|
3429
2787
|
"test:tools": "tsx ./tool-evals/toolEval.ts",
|
|
3430
2788
|
test: "vitest",
|
|
3431
2789
|
typecheck: "tsc --noEmit -p tsconfig.json"
|
|
@@ -3506,185 +2864,82 @@ function parseEntries(raw) {
|
|
|
3506
2864
|
return raw.split(",").map(clean).filter(Boolean).slice(0, MAX_ENTRIES).map((name) => ({ name, version: "unknown" }));
|
|
3507
2865
|
}
|
|
3508
2866
|
var summarize = (entries) => entries.length ? entries.map((e) => e.name).join(", ") : "none";
|
|
3509
|
-
|
|
3510
|
-
// src/actions/confirmLanguage.ts
|
|
3511
|
-
import z19 from "zod";
|
|
3512
|
-
var confirmLanguageSchema = z19.object({
|
|
3513
|
-
languages: detectLanguageSchema.shape.languages
|
|
3514
|
-
});
|
|
3515
|
-
var OTHER_OPTION = "Other";
|
|
3516
|
-
function confirmed(languages) {
|
|
3517
|
-
track("AI Wizard Language Confirmed", { languages });
|
|
3518
|
-
return { languages };
|
|
3519
|
-
}
|
|
3520
|
-
async function askOtherLanguage(ctx) {
|
|
3521
|
-
let prompt = "enter the language for your ingestion script";
|
|
2867
|
+
async function askList(ctx, prompt, { required = false } = {}) {
|
|
3522
2868
|
for (; ; ) {
|
|
3523
2869
|
const answer = await ctx.requestUserInput({
|
|
3524
2870
|
prompt,
|
|
3525
2871
|
promptType: "textInput",
|
|
3526
|
-
options: []
|
|
2872
|
+
options: [],
|
|
2873
|
+
helpText: 'Comma-separated, e.g. "TypeScript, Node".'
|
|
3527
2874
|
});
|
|
3528
2875
|
if (typeof answer !== "string") {
|
|
3529
|
-
throw new Error("
|
|
2876
|
+
throw new Error("askList received an unexpected non-text result");
|
|
3530
2877
|
}
|
|
3531
|
-
const
|
|
3532
|
-
if (
|
|
3533
|
-
prompt = "
|
|
2878
|
+
const entries = parseEntries(answer);
|
|
2879
|
+
if (entries.length || !required) return entries;
|
|
2880
|
+
prompt = "Please enter at least one entry:";
|
|
3534
2881
|
}
|
|
3535
2882
|
}
|
|
2883
|
+
|
|
2884
|
+
// src/actions/confirmLanguage.ts
|
|
2885
|
+
import z19 from "zod";
|
|
2886
|
+
var confirmLanguageSchema = z19.object({
|
|
2887
|
+
languages: detectLanguageSchema.shape.languages
|
|
2888
|
+
});
|
|
3536
2889
|
async function confirmLanguage(ctx) {
|
|
3537
2890
|
const detected = ctx.getStepOutput("project-scan");
|
|
3538
|
-
const
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
prompt: `Write the ingestion script in ${primary.name}?`,
|
|
3544
|
-
promptType: "acceptReject",
|
|
3545
|
-
options: [`Confirm ${primary.name}`, "Use a different language"],
|
|
3546
|
-
secondary: [{ kind: "badge", value: "[DETECTED]" }, void 0],
|
|
3547
|
-
messages: detectedLanguages.length > 1 ? [`Detected: ${summarize(detectedLanguages)}`] : []
|
|
3548
|
-
});
|
|
3549
|
-
if (accepted === true) return confirmed(detectedLanguages);
|
|
3550
|
-
}
|
|
3551
|
-
const options = [...CURATED_LANGUAGES];
|
|
3552
|
-
for (const language of detectedLanguages) {
|
|
3553
|
-
if (!options.some((o) => isSameLanguage(o, language.name))) {
|
|
3554
|
-
options.push(language.name);
|
|
3555
|
-
}
|
|
3556
|
-
}
|
|
3557
|
-
options.push(OTHER_OPTION);
|
|
3558
|
-
const detectedFor = (option) => detectedLanguages.find((l) => isSameLanguage(option, l.name));
|
|
3559
|
-
const secondary = options.map(
|
|
3560
|
-
(o) => detectedFor(o) ? { kind: "badge", value: "[DETECTED]" } : void 0
|
|
3561
|
-
);
|
|
3562
|
-
const defaultSelectedIndex = Math.max(
|
|
3563
|
-
options.findIndex((o) => detectedFor(o)),
|
|
3564
|
-
0
|
|
3565
|
-
);
|
|
3566
|
-
const selection = await ctx.requestUserInput({
|
|
3567
|
-
prompt: "select the language for your ingestion script",
|
|
3568
|
-
promptType: "multipleChoice",
|
|
3569
|
-
options,
|
|
3570
|
-
secondary,
|
|
3571
|
-
defaultSelectedIndex
|
|
2891
|
+
const answer = await ctx.requestUserInput({
|
|
2892
|
+
prompt: "Did we detect your language(s) correctly?",
|
|
2893
|
+
promptType: "acceptReject",
|
|
2894
|
+
options: ["Yes", "No"],
|
|
2895
|
+
messages: [`Languages: ${summarize(detected.languages)}`]
|
|
3572
2896
|
});
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
}
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
2897
|
+
const languages = answer === true ? detected.languages : await askList(ctx, "List the languages your project uses:", {
|
|
2898
|
+
required: true
|
|
2899
|
+
});
|
|
2900
|
+
track("AI Wizard Language Confirmed", {
|
|
2901
|
+
languages
|
|
2902
|
+
});
|
|
2903
|
+
return { languages };
|
|
3579
2904
|
}
|
|
3580
2905
|
|
|
3581
2906
|
// src/actions/confirmFramework.ts
|
|
3582
2907
|
import z20 from "zod";
|
|
3583
|
-
|
|
3584
|
-
// src/lib/frameworks.ts
|
|
3585
|
-
var BACKEND_ONLY_FRAMEWORK = "Backend only / API";
|
|
3586
|
-
var FRAMEWORKS = [
|
|
3587
|
-
// Frontend — InstantSearch component flavors.
|
|
3588
|
-
{ name: "Next.js", strategy: "react", aliases: ["next", "nextjs"] },
|
|
3589
|
-
{ name: "React", strategy: "react", aliases: ["reactjs"] },
|
|
3590
|
-
{ name: "Vue", strategy: "vue", aliases: ["vuejs", "nuxt", "nuxtjs"] },
|
|
3591
|
-
{ name: "Angular", strategy: "angular", aliases: ["angularjs"] },
|
|
3592
|
-
// No Svelte InstantSearch flavor exists, so it uses InstantSearch.js.
|
|
3593
|
-
{ name: "Svelte", strategy: "js", aliases: ["sveltekit"] },
|
|
3594
|
-
{
|
|
3595
|
-
name: "Vanilla JS",
|
|
3596
|
-
strategy: "js",
|
|
3597
|
-
aliases: ["vanilla", "javascript", "js", "astro", "vite"]
|
|
3598
|
-
},
|
|
3599
|
-
// Backend — Algolia's official framework integrations. Server-rendered
|
|
3600
|
-
// templates get InstantSearch.js from a CDN.
|
|
3601
|
-
{
|
|
3602
|
-
name: "Rails",
|
|
3603
|
-
strategy: "cdn-template",
|
|
3604
|
-
aliases: ["rubyonrails", "ruby on rails", "erb"]
|
|
3605
|
-
},
|
|
3606
|
-
{ name: "Django", strategy: "cdn-template", aliases: ["jinja", "jinja2"] },
|
|
3607
|
-
{ name: "Laravel", strategy: "cdn-template", aliases: ["blade"] },
|
|
3608
|
-
{ name: "Symfony", strategy: "cdn-template", aliases: ["twig"] },
|
|
3609
|
-
// Mobile — Algolia ships InstantSearch iOS/Android and Dart clients, but the
|
|
3610
|
-
// wizard can't scaffold a native UI, so it points at the docs instead.
|
|
3611
|
-
{ name: "Flutter", strategy: "none", aliases: [] },
|
|
3612
|
-
{ name: "iOS", strategy: "none", aliases: ["swiftui", "uikit"] },
|
|
3613
|
-
{ name: "Android", strategy: "none", aliases: ["jetpack compose", "compose"] },
|
|
3614
|
-
{ name: BACKEND_ONLY_FRAMEWORK, strategy: "cdn-template", aliases: [] }
|
|
3615
|
-
];
|
|
3616
|
-
var CURATED_FRAMEWORKS = FRAMEWORKS.map(
|
|
3617
|
-
(f) => f.name
|
|
3618
|
-
);
|
|
3619
|
-
var normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
3620
|
-
var ALIAS_TO_NAME = /* @__PURE__ */ new Map();
|
|
3621
|
-
for (const framework of FRAMEWORKS) {
|
|
3622
|
-
for (const alias of [framework.name, ...framework.aliases]) {
|
|
3623
|
-
ALIAS_TO_NAME.set(normalize(alias), framework.name);
|
|
3624
|
-
}
|
|
3625
|
-
}
|
|
3626
|
-
var STRATEGY_BY_NAME = new Map(FRAMEWORKS.map((f) => [f.name, f.strategy]));
|
|
3627
|
-
function canonicalFrameworkName(name) {
|
|
3628
|
-
return ALIAS_TO_NAME.get(normalize(name));
|
|
3629
|
-
}
|
|
3630
|
-
function isSameFramework(a, b) {
|
|
3631
|
-
const x = canonicalFrameworkName(a) ?? normalize(a);
|
|
3632
|
-
const y = canonicalFrameworkName(b) ?? normalize(b);
|
|
3633
|
-
return x !== "" && x === y;
|
|
3634
|
-
}
|
|
3635
|
-
function resolveSearchStrategy(frameworkName, hasJavaScriptInStack) {
|
|
3636
|
-
const canonical = frameworkName ? canonicalFrameworkName(frameworkName) : void 0;
|
|
3637
|
-
const strategy = canonical ? STRATEGY_BY_NAME.get(canonical) : void 0;
|
|
3638
|
-
if (strategy) return strategy;
|
|
3639
|
-
return hasJavaScriptInStack ? "js" : "cdn-template";
|
|
3640
|
-
}
|
|
3641
|
-
function searchDocKey(strategy) {
|
|
3642
|
-
return strategy === "cdn-template" ? "templates" : strategy;
|
|
3643
|
-
}
|
|
3644
|
-
function bundlesJavaScript(strategy) {
|
|
3645
|
-
return strategy !== "cdn-template" && strategy !== "none";
|
|
3646
|
-
}
|
|
3647
|
-
function canScaffoldSearchUI(strategy) {
|
|
3648
|
-
return strategy !== "none";
|
|
3649
|
-
}
|
|
3650
|
-
var ENV_PREFIXES = [
|
|
3651
|
-
{ aliases: ["next", "nextjs"], prefix: "NEXT_PUBLIC_" },
|
|
3652
|
-
{ aliases: ["nuxt", "nuxtjs"], prefix: "NUXT_PUBLIC_" },
|
|
3653
|
-
{ aliases: ["astro"], prefix: "PUBLIC_" },
|
|
3654
|
-
{ aliases: ["vite"], prefix: "VITE_" }
|
|
3655
|
-
];
|
|
3656
|
-
var DEFAULT_ENV_PREFIX = "PUBLIC_";
|
|
3657
|
-
function publicEnvPrefix(frameworkNames, strategy) {
|
|
3658
|
-
if (!bundlesJavaScript(strategy)) return "";
|
|
3659
|
-
const present = new Set(frameworkNames.map(normalize));
|
|
3660
|
-
for (const { aliases, prefix } of ENV_PREFIXES) {
|
|
3661
|
-
if (aliases.some((alias) => present.has(alias))) return prefix;
|
|
3662
|
-
}
|
|
3663
|
-
return DEFAULT_ENV_PREFIX;
|
|
3664
|
-
}
|
|
3665
|
-
function describeSearchTarget(strategy, frameworkName) {
|
|
3666
|
-
switch (strategy) {
|
|
3667
|
-
case "react":
|
|
3668
|
-
return "React (react-instantsearch)";
|
|
3669
|
-
case "vue":
|
|
3670
|
-
return "Vue (vue-instantsearch)";
|
|
3671
|
-
case "angular":
|
|
3672
|
-
return "Angular (angular-instantsearch)";
|
|
3673
|
-
case "js":
|
|
3674
|
-
return "plain JavaScript (InstantSearch.js)";
|
|
3675
|
-
case "cdn-template":
|
|
3676
|
-
return `${frameworkName ?? "server-rendered"} templates (InstantSearch.js via CDN)`;
|
|
3677
|
-
case "none":
|
|
3678
|
-
return frameworkName ?? "a native mobile app";
|
|
3679
|
-
}
|
|
3680
|
-
}
|
|
3681
|
-
|
|
3682
|
-
// src/actions/confirmFramework.ts
|
|
3683
2908
|
var confirmFrameworkSchema = z20.object({
|
|
3684
2909
|
frameworks: detectLanguageSchema.shape.frameworks
|
|
3685
2910
|
});
|
|
3686
|
-
var
|
|
3687
|
-
|
|
2911
|
+
var CURATED_FRAMEWORKS = [
|
|
2912
|
+
"Next.js",
|
|
2913
|
+
"React",
|
|
2914
|
+
"Vue",
|
|
2915
|
+
"Angular",
|
|
2916
|
+
"Svelte",
|
|
2917
|
+
"Vanilla JS"
|
|
2918
|
+
];
|
|
2919
|
+
var OTHER_OPTION = "Other";
|
|
2920
|
+
var normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
2921
|
+
var FRAMEWORK_ALIASES = {
|
|
2922
|
+
next: "nextjs",
|
|
2923
|
+
nextjs: "nextjs",
|
|
2924
|
+
react: "react",
|
|
2925
|
+
reactjs: "react",
|
|
2926
|
+
vue: "vue",
|
|
2927
|
+
vuejs: "vue",
|
|
2928
|
+
angular: "angular",
|
|
2929
|
+
angularjs: "angular",
|
|
2930
|
+
svelte: "svelte",
|
|
2931
|
+
sveltekit: "svelte",
|
|
2932
|
+
vanillajs: "vanillajs",
|
|
2933
|
+
vanilla: "vanillajs",
|
|
2934
|
+
javascript: "vanillajs",
|
|
2935
|
+
js: "vanillajs"
|
|
2936
|
+
};
|
|
2937
|
+
var isSameFramework = (a, b) => {
|
|
2938
|
+
const x = FRAMEWORK_ALIASES[normalize(a)] ?? normalize(a);
|
|
2939
|
+
const y = FRAMEWORK_ALIASES[normalize(b)] ?? normalize(b);
|
|
2940
|
+
return x !== "" && x === y;
|
|
2941
|
+
};
|
|
2942
|
+
function confirmed(name, version) {
|
|
3688
2943
|
const frameworks = [{ name, version: version ?? "unknown" }];
|
|
3689
2944
|
track("AI Wizard Frontend Framework Confirmed", { frameworks });
|
|
3690
2945
|
return { frameworks };
|
|
@@ -3712,7 +2967,7 @@ async function confirmFramework(ctx) {
|
|
|
3712
2967
|
for (const fw of detectedFrameworks) {
|
|
3713
2968
|
if (!options.some((o) => isSameFramework(o, fw.name))) options.push(fw.name);
|
|
3714
2969
|
}
|
|
3715
|
-
options.push(
|
|
2970
|
+
options.push(OTHER_OPTION);
|
|
3716
2971
|
const detectedFor = (option) => detectedFrameworks.find((fw) => isSameFramework(option, fw.name));
|
|
3717
2972
|
const primary = detectedFrameworks[0];
|
|
3718
2973
|
if (primary) {
|
|
@@ -3722,7 +2977,7 @@ async function confirmFramework(ctx) {
|
|
|
3722
2977
|
options: [`Confirm ${primary.name}`, "Use a different framework"],
|
|
3723
2978
|
secondary: [{ kind: "badge", value: "[DETECTED]" }, void 0]
|
|
3724
2979
|
});
|
|
3725
|
-
if (accepted === true) return
|
|
2980
|
+
if (accepted === true) return confirmed(primary.name, primary.version);
|
|
3726
2981
|
}
|
|
3727
2982
|
const secondary = options.map(
|
|
3728
2983
|
(o) => detectedFor(o) ? { kind: "badge", value: "[DETECTED]" } : void 0
|
|
@@ -3732,7 +2987,7 @@ async function confirmFramework(ctx) {
|
|
|
3732
2987
|
0
|
|
3733
2988
|
);
|
|
3734
2989
|
const selection = await ctx.requestUserInput({
|
|
3735
|
-
prompt: "select
|
|
2990
|
+
prompt: "select a framework",
|
|
3736
2991
|
promptType: "multipleChoice",
|
|
3737
2992
|
options,
|
|
3738
2993
|
secondary,
|
|
@@ -3741,10 +2996,10 @@ async function confirmFramework(ctx) {
|
|
|
3741
2996
|
if (typeof selection !== "string") {
|
|
3742
2997
|
throw new Error("confirmFramework received an unexpected non-text result");
|
|
3743
2998
|
}
|
|
3744
|
-
if (selection ===
|
|
3745
|
-
return
|
|
2999
|
+
if (selection === OTHER_OPTION) {
|
|
3000
|
+
return confirmed(await askOtherFramework(ctx));
|
|
3746
3001
|
}
|
|
3747
|
-
return
|
|
3002
|
+
return confirmed(selection, detectedFor(selection)?.version);
|
|
3748
3003
|
}
|
|
3749
3004
|
|
|
3750
3005
|
// src/actions/promptUser.ts
|
|
@@ -3837,15 +3092,15 @@ async function confirmEntities(ctx) {
|
|
|
3837
3092
|
onSubmit: () => {
|
|
3838
3093
|
}
|
|
3839
3094
|
});
|
|
3840
|
-
const
|
|
3841
|
-
if (
|
|
3095
|
+
const confirmed2 = typeof selection === "string" ? entities.filter((e) => e.name === selection) : [];
|
|
3096
|
+
if (confirmed2.length === 0) {
|
|
3842
3097
|
throw new Error("User cancelled entity selection \u2014 analysis halted.");
|
|
3843
3098
|
}
|
|
3844
|
-
ctx.setUserInput("confirmedEntities",
|
|
3099
|
+
ctx.setUserInput("confirmedEntities", confirmed2);
|
|
3845
3100
|
track("AI Wizard Entities Confirmed", {
|
|
3846
|
-
entities: toEntitySummary(
|
|
3101
|
+
entities: toEntitySummary(confirmed2)
|
|
3847
3102
|
});
|
|
3848
|
-
return { ingestionAnalysis: entities, confirmedEntities:
|
|
3103
|
+
return { ingestionAnalysis: entities, confirmedEntities: confirmed2 };
|
|
3849
3104
|
}
|
|
3850
3105
|
|
|
3851
3106
|
// src/actions/review.ts
|
|
@@ -3869,7 +3124,7 @@ ${JSON.stringify(s.output, null, 2)}`
|
|
|
3869
3124
|
}
|
|
3870
3125
|
function formatReviewSummary(result) {
|
|
3871
3126
|
const nextStepLines = result.nextSteps.map((step) => {
|
|
3872
|
-
const isIngestCommand = step.includes("algolia-wizard/
|
|
3127
|
+
const isIngestCommand = step.includes(".algolia-wizard/ingest.sh");
|
|
3873
3128
|
const isWorktreeCommand = step.includes("/worktrees/");
|
|
3874
3129
|
return {
|
|
3875
3130
|
text: `\u2192 ${step}`,
|
|
@@ -3911,14 +3166,13 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
|
|
|
3911
3166
|
import z24 from "zod";
|
|
3912
3167
|
|
|
3913
3168
|
// src/lib/worktree.ts
|
|
3914
|
-
import { execFile } from "node:child_process";
|
|
3915
|
-
import {
|
|
3916
|
-
import { copyFile, mkdir as mkdir6, readdir as readdir4, readFile as readFile9, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
|
|
3169
|
+
import { execFile, spawn as spawn3 } from "node:child_process";
|
|
3170
|
+
import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as readFile8, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
|
|
3917
3171
|
import {
|
|
3918
3172
|
basename as basename2,
|
|
3919
3173
|
dirname as dirname7,
|
|
3920
3174
|
isAbsolute as isAbsolute2,
|
|
3921
|
-
join as
|
|
3175
|
+
join as join10,
|
|
3922
3176
|
relative as relative2,
|
|
3923
3177
|
resolve as resolve3
|
|
3924
3178
|
} from "node:path";
|
|
@@ -3952,8 +3206,8 @@ async function isWorkingTreeDirty(repoRoot) {
|
|
|
3952
3206
|
return out.trim().length > 0;
|
|
3953
3207
|
}
|
|
3954
3208
|
async function pruneOldWorktrees(repoRoot) {
|
|
3955
|
-
const dir =
|
|
3956
|
-
const stale = (await
|
|
3209
|
+
const dir = join10(stateDir(repoRoot), "worktrees");
|
|
3210
|
+
const stale = (await readdir3(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
|
|
3957
3211
|
for (const slug of stale) {
|
|
3958
3212
|
const branch = slug.replace("wizard-implement-", WIZARD_BRANCH_PREFIX);
|
|
3959
3213
|
try {
|
|
@@ -3963,7 +3217,7 @@ async function pruneOldWorktrees(repoRoot) {
|
|
|
3963
3217
|
"worktree",
|
|
3964
3218
|
"remove",
|
|
3965
3219
|
"--force",
|
|
3966
|
-
|
|
3220
|
+
join10(dir, slug)
|
|
3967
3221
|
]);
|
|
3968
3222
|
await git(["-C", repoRoot, "branch", "-D", branch]);
|
|
3969
3223
|
} catch (err) {
|
|
@@ -3977,55 +3231,43 @@ async function pruneOldWorktrees(repoRoot) {
|
|
|
3977
3231
|
async function createWorktree(repoRoot) {
|
|
3978
3232
|
const branch = `${WIZARD_BRANCH_PREFIX}${Date.now()}`;
|
|
3979
3233
|
const dirSlug = branch.replace(/\//g, "-");
|
|
3980
|
-
const path =
|
|
3234
|
+
const path = join10(stateDir(repoRoot), "worktrees", dirSlug);
|
|
3981
3235
|
await git(["-C", repoRoot, "worktree", "prune"]);
|
|
3982
3236
|
await pruneOldWorktrees(repoRoot);
|
|
3983
3237
|
await mkdir6(dirname7(path), { recursive: true });
|
|
3984
3238
|
await git(["-C", repoRoot, "worktree", "add", "-b", branch, path, "HEAD"]);
|
|
3985
3239
|
return { path, branch };
|
|
3986
3240
|
}
|
|
3987
|
-
async function
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
return { ok: code === 0, output: output.trim() };
|
|
3993
|
-
}
|
|
3994
|
-
async function installWorktreeDeps(worktreePath, toolchain) {
|
|
3995
|
-
const { profile, installSteps, packageManager } = toolchain;
|
|
3996
|
-
const declared = packageManager.dependency.mode === "agent-declares" ? packageManager.dependency.file : void 0;
|
|
3997
|
-
const haveSomethingToInstall = await hasProfileManifest(worktreePath, profile) || declared !== void 0 && existsSync4(join12(worktreePath, declared));
|
|
3998
|
-
if (!haveSomethingToInstall) {
|
|
3999
|
-
return {
|
|
4000
|
-
ok: true,
|
|
4001
|
-
output: `no ${profile.displayName} manifest; skipped install`
|
|
4002
|
-
};
|
|
4003
|
-
}
|
|
4004
|
-
if (installSteps.length === 0) {
|
|
4005
|
-
return {
|
|
4006
|
-
ok: true,
|
|
4007
|
-
output: `${profile.displayName} (${toolchain.packageManager.id}) has no wizard-run install step`
|
|
4008
|
-
};
|
|
4009
|
-
}
|
|
4010
|
-
const outputs = [];
|
|
4011
|
-
for (const step of installSteps) {
|
|
4012
|
-
if (step.requiresFile && !existsSync4(join12(worktreePath, step.requiresFile)))
|
|
4013
|
-
continue;
|
|
4014
|
-
const result = await spawnStep(worktreePath, step.argv);
|
|
4015
|
-
if (result.output) outputs.push(result.output);
|
|
4016
|
-
if (result.ok) continue;
|
|
4017
|
-
if (step.optional) {
|
|
4018
|
-
logger.warn(
|
|
4019
|
-
{ step: step.argv.join(" "), output: result.output },
|
|
4020
|
-
"installWorktreeDeps: optional install step failed; continuing"
|
|
4021
|
-
);
|
|
4022
|
-
continue;
|
|
4023
|
-
}
|
|
4024
|
-
return { ok: false, output: outputs.join("\n").trim() };
|
|
3241
|
+
async function installWorktreeDeps(worktreePath) {
|
|
3242
|
+
try {
|
|
3243
|
+
await readPackageJson(worktreePath);
|
|
3244
|
+
} catch {
|
|
3245
|
+
return { ok: true, output: "no package.json; skipped install" };
|
|
4025
3246
|
}
|
|
4026
|
-
|
|
3247
|
+
const pm = await detectPackageManager(worktreePath);
|
|
3248
|
+
return new Promise((resolve4) => {
|
|
3249
|
+
let output = "";
|
|
3250
|
+
const child = spawn3(pm, ["install"], {
|
|
3251
|
+
cwd: worktreePath,
|
|
3252
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
3253
|
+
});
|
|
3254
|
+
child.stdout?.on("data", (d) => output += d);
|
|
3255
|
+
child.stderr?.on("data", (d) => output += d);
|
|
3256
|
+
child.on(
|
|
3257
|
+
"error",
|
|
3258
|
+
(err) => resolve4({
|
|
3259
|
+
ok: false,
|
|
3260
|
+
output: `Failed to run ${pm} install: ${err.message}`
|
|
3261
|
+
})
|
|
3262
|
+
);
|
|
3263
|
+
child.on(
|
|
3264
|
+
"close",
|
|
3265
|
+
(code) => resolve4({ ok: code === 0, output: output.trim() })
|
|
3266
|
+
);
|
|
3267
|
+
});
|
|
4027
3268
|
}
|
|
4028
|
-
|
|
3269
|
+
var INGEST_RUNTIMES = ["node", "python", "python3", "bun"];
|
|
3270
|
+
function validateIngestEntrypoint(worktreePath, entrypoint) {
|
|
4029
3271
|
if (!entrypoint || entrypoint.startsWith("-")) {
|
|
4030
3272
|
return {
|
|
4031
3273
|
ok: false,
|
|
@@ -4040,29 +3282,18 @@ function validateIngestEntrypoint(worktreePath, entrypoint, allowedExtensions) {
|
|
|
4040
3282
|
reason: `entrypoint "${entrypoint}" resolves outside the worktree`
|
|
4041
3283
|
};
|
|
4042
3284
|
}
|
|
4043
|
-
if (allowedExtensions?.length && !allowedExtensions.some((ext) => entrypoint.endsWith(ext))) {
|
|
4044
|
-
return {
|
|
4045
|
-
ok: false,
|
|
4046
|
-
reason: `entrypoint "${entrypoint}" is not one of ${allowedExtensions.join(", ")}`
|
|
4047
|
-
};
|
|
4048
|
-
}
|
|
4049
3285
|
return { ok: true, target };
|
|
4050
3286
|
}
|
|
4051
|
-
async function runIngestScript(worktreePath,
|
|
4052
|
-
|
|
4053
|
-
if (ingest.kind !== "auto") {
|
|
3287
|
+
async function runIngestScript(worktreePath, runtime, entrypoint, env = {}) {
|
|
3288
|
+
if (!INGEST_RUNTIMES.includes(runtime)) {
|
|
4054
3289
|
return {
|
|
4055
3290
|
ran: false,
|
|
4056
3291
|
ok: false,
|
|
4057
3292
|
output: "",
|
|
4058
|
-
reason:
|
|
3293
|
+
reason: `runtime "${runtime}" is not an allowed interpreter (${INGEST_RUNTIMES.join(", ")})`
|
|
4059
3294
|
};
|
|
4060
3295
|
}
|
|
4061
|
-
const validated = validateIngestEntrypoint(
|
|
4062
|
-
worktreePath,
|
|
4063
|
-
entrypoint,
|
|
4064
|
-
ingest.entrypointExtensions
|
|
4065
|
-
);
|
|
3296
|
+
const validated = validateIngestEntrypoint(worktreePath, entrypoint);
|
|
4066
3297
|
if (!validated.ok) {
|
|
4067
3298
|
return { ran: false, ok: false, output: "", reason: validated.reason };
|
|
4068
3299
|
}
|
|
@@ -4083,13 +3314,29 @@ async function runIngestScript(worktreePath, toolchain, entrypoint, env = {}) {
|
|
|
4083
3314
|
reason: `entrypoint "${entrypoint}" does not exist`
|
|
4084
3315
|
};
|
|
4085
3316
|
}
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
3317
|
+
return new Promise((resolveRun) => {
|
|
3318
|
+
let output = "";
|
|
3319
|
+
const child = spawn3(runtime, [entrypoint], {
|
|
3320
|
+
cwd: worktreePath,
|
|
3321
|
+
shell: false,
|
|
3322
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
3323
|
+
env: { ...process.env, ...env }
|
|
3324
|
+
});
|
|
3325
|
+
child.stdout?.on("data", (d) => output += d);
|
|
3326
|
+
child.stderr?.on("data", (d) => output += d);
|
|
3327
|
+
child.on(
|
|
3328
|
+
"error",
|
|
3329
|
+
(err) => resolveRun({
|
|
3330
|
+
ran: true,
|
|
3331
|
+
ok: false,
|
|
3332
|
+
output: `Failed to run ${runtime} ${entrypoint}: ${err.message}`
|
|
3333
|
+
})
|
|
3334
|
+
);
|
|
3335
|
+
child.on(
|
|
3336
|
+
"close",
|
|
3337
|
+
(code) => resolveRun({ ran: true, ok: code === 0, output: output.trim() })
|
|
3338
|
+
);
|
|
4091
3339
|
});
|
|
4092
|
-
return { ran: true, ok: code === 0, output: output.trim() };
|
|
4093
3340
|
}
|
|
4094
3341
|
async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourcePath) {
|
|
4095
3342
|
const trimmed = sourcePath.trim();
|
|
@@ -4104,8 +3351,8 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
|
|
|
4104
3351
|
} catch {
|
|
4105
3352
|
return { ok: false, reason: `"${sourcePath}" does not exist` };
|
|
4106
3353
|
}
|
|
4107
|
-
const relPath =
|
|
4108
|
-
const dest =
|
|
3354
|
+
const relPath = join10(ingestDir, basename2(source));
|
|
3355
|
+
const dest = join10(worktreePath, relPath);
|
|
4109
3356
|
try {
|
|
4110
3357
|
await mkdir6(dirname7(dest), { recursive: true });
|
|
4111
3358
|
await copyFile(source, dest);
|
|
@@ -4121,10 +3368,10 @@ function hasEnvVar(content, name) {
|
|
|
4121
3368
|
return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
|
|
4122
3369
|
}
|
|
4123
3370
|
async function writeSearchEnvValues(worktreePath, vars) {
|
|
4124
|
-
const target =
|
|
3371
|
+
const target = join10(worktreePath, ".env");
|
|
4125
3372
|
let existing = "";
|
|
4126
3373
|
try {
|
|
4127
|
-
existing = await
|
|
3374
|
+
existing = await readFile8(target, "utf8");
|
|
4128
3375
|
} catch (err) {
|
|
4129
3376
|
if (err.code !== "ENOENT") throw err;
|
|
4130
3377
|
}
|
|
@@ -4241,33 +3488,69 @@ async function resolveSearchOnlyKey(index) {
|
|
|
4241
3488
|
}
|
|
4242
3489
|
|
|
4243
3490
|
// src/lib/algoliaDocs.ts
|
|
4244
|
-
import { readFileSync, existsSync as
|
|
4245
|
-
import { dirname as dirname8, join as
|
|
3491
|
+
import { readFileSync, readdirSync, existsSync as existsSync2 } from "node:fs";
|
|
3492
|
+
import { dirname as dirname8, join as join11 } from "node:path";
|
|
4246
3493
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
4247
|
-
var DOCS_SUBPATH =
|
|
3494
|
+
var DOCS_SUBPATH = join11("docs", "algolia-sdk");
|
|
4248
3495
|
function findDocsDir() {
|
|
4249
3496
|
let dir = dirname8(fileURLToPath2(import.meta.url));
|
|
4250
3497
|
for (; ; ) {
|
|
4251
|
-
const candidate =
|
|
4252
|
-
if (
|
|
3498
|
+
const candidate = join11(dir, DOCS_SUBPATH);
|
|
3499
|
+
if (existsSync2(candidate)) return candidate;
|
|
4253
3500
|
const parent = dirname8(dir);
|
|
4254
3501
|
if (parent === dir) return void 0;
|
|
4255
3502
|
dir = parent;
|
|
4256
3503
|
}
|
|
4257
3504
|
}
|
|
4258
|
-
function
|
|
3505
|
+
function loadAlgoliaDoc(language) {
|
|
3506
|
+
const docsDir = findDocsDir();
|
|
3507
|
+
if (!docsDir) {
|
|
3508
|
+
logger.warn(
|
|
3509
|
+
"algoliaDocs: docs/algolia-sdk not found; skipping SDK reference"
|
|
3510
|
+
);
|
|
3511
|
+
return "";
|
|
3512
|
+
}
|
|
3513
|
+
const files = readdirSync(docsDir).filter((f) => f.includes(language));
|
|
3514
|
+
if (files.length === 0) {
|
|
3515
|
+
logger.warn(
|
|
3516
|
+
{ language },
|
|
3517
|
+
"algoliaDocs: no SDK reference found for language; skipping"
|
|
3518
|
+
);
|
|
3519
|
+
return "";
|
|
3520
|
+
}
|
|
3521
|
+
return readFileSync(join11(docsDir, files[0]), "utf8").trim();
|
|
3522
|
+
}
|
|
3523
|
+
function getNamedDoc(name, language) {
|
|
4259
3524
|
const docsDir = findDocsDir();
|
|
4260
3525
|
if (!docsDir) {
|
|
4261
3526
|
logger.warn("docs/algolia-sdk not found");
|
|
4262
3527
|
return "";
|
|
4263
3528
|
}
|
|
4264
|
-
const file =
|
|
4265
|
-
if (!
|
|
4266
|
-
logger.warn({ name,
|
|
3529
|
+
const file = join11(docsDir, `${name}-${language}.md`);
|
|
3530
|
+
if (!existsSync2(file)) {
|
|
3531
|
+
logger.warn({ name, language }, "named SDK reference not found");
|
|
4267
3532
|
return "";
|
|
4268
3533
|
}
|
|
4269
3534
|
return readFileSync(file, "utf8").trim();
|
|
4270
3535
|
}
|
|
3536
|
+
function getFrameworkSpecificDoc(frameworks) {
|
|
3537
|
+
const fw = frameworks.map((f) => f.toLowerCase());
|
|
3538
|
+
if (fw.includes("vue") || fw.includes("nuxt")) {
|
|
3539
|
+
return loadAlgoliaDoc("vue");
|
|
3540
|
+
}
|
|
3541
|
+
if (fw.includes("react") || fw.includes("next.js")) {
|
|
3542
|
+
return loadAlgoliaDoc("react");
|
|
3543
|
+
}
|
|
3544
|
+
if (fw.includes("angular")) {
|
|
3545
|
+
return loadAlgoliaDoc("angular");
|
|
3546
|
+
}
|
|
3547
|
+
return loadAlgoliaDoc("js");
|
|
3548
|
+
}
|
|
3549
|
+
|
|
3550
|
+
// src/lib/shell.ts
|
|
3551
|
+
function shellQuote(value) {
|
|
3552
|
+
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
3553
|
+
}
|
|
4271
3554
|
|
|
4272
3555
|
// src/actions/implement.ts
|
|
4273
3556
|
var implementSchema = z24.object({
|
|
@@ -4302,11 +3585,12 @@ var implementSchema = z24.object({
|
|
|
4302
3585
|
});
|
|
4303
3586
|
var implementationOutputSchema = z24.object({
|
|
4304
3587
|
summary: z24.string(),
|
|
4305
|
-
// Ingestion only:
|
|
4306
|
-
//
|
|
4307
|
-
//
|
|
4308
|
-
//
|
|
4309
|
-
//
|
|
3588
|
+
// Ingestion only: how to run the generated script, as a structured pair the
|
|
3589
|
+
// wizard turns into an argv (`<runtime> <entrypoint>`) — never a free-form
|
|
3590
|
+
// command string. `runtime` is constrained to an allowlisted interpreter and
|
|
3591
|
+
// `entrypoint` is validated to a worktree-relative path before execution, so
|
|
3592
|
+
// the agent cannot inject extra commands or swap the interpreter.
|
|
3593
|
+
runtime: z24.enum(INGEST_RUNTIMES).optional(),
|
|
4310
3594
|
entrypoint: z24.string().optional()
|
|
4311
3595
|
});
|
|
4312
3596
|
var verificationOutputSchema = z24.object({
|
|
@@ -4316,11 +3600,47 @@ var verificationOutputSchema = z24.object({
|
|
|
4316
3600
|
});
|
|
4317
3601
|
var MAX_IMPLEMENT_VERIFICATION_ATTEMPTS = 3;
|
|
4318
3602
|
var DEFAULT_IMPLEMENT_USE_CASES = ["ingestion", "search"];
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
3603
|
+
var INGEST_DIR = ".algolia-wizard";
|
|
3604
|
+
function detectUiFramework(language) {
|
|
3605
|
+
const names = language.frameworks.map((f) => f.name.toLowerCase());
|
|
3606
|
+
if (names.some((n) => n.includes("vue") || n.includes("nuxt"))) return "Vue";
|
|
3607
|
+
if (names.some((n) => n.includes("react") || n.includes("next")))
|
|
3608
|
+
return "React";
|
|
3609
|
+
if (names.some((n) => n.includes("angular"))) return "Angular";
|
|
3610
|
+
return "JavaScript";
|
|
3611
|
+
}
|
|
3612
|
+
function frameworksForDoc(framework) {
|
|
3613
|
+
switch (framework) {
|
|
3614
|
+
case "React":
|
|
3615
|
+
return ["react"];
|
|
3616
|
+
case "Vue":
|
|
3617
|
+
return ["vue"];
|
|
3618
|
+
case "Angular":
|
|
3619
|
+
return ["angular"];
|
|
3620
|
+
case "JavaScript":
|
|
3621
|
+
return [];
|
|
3622
|
+
}
|
|
3623
|
+
}
|
|
3624
|
+
function publicEnvPrefix(language) {
|
|
3625
|
+
const frameworkNames = language.frameworks.map(
|
|
3626
|
+
(framework) => framework.name.toLowerCase()
|
|
4323
3627
|
);
|
|
3628
|
+
if (frameworkNames.some((name) => name.includes("next"))) {
|
|
3629
|
+
return "NEXT_PUBLIC_";
|
|
3630
|
+
}
|
|
3631
|
+
if (frameworkNames.some((name) => name.includes("nuxt"))) {
|
|
3632
|
+
return "NUXT_PUBLIC_";
|
|
3633
|
+
}
|
|
3634
|
+
if (frameworkNames.some((name) => name.includes("astro"))) {
|
|
3635
|
+
return "PUBLIC_";
|
|
3636
|
+
}
|
|
3637
|
+
if (frameworkNames.some((name) => name.includes("vite"))) {
|
|
3638
|
+
return "VITE_";
|
|
3639
|
+
}
|
|
3640
|
+
return "PUBLIC_";
|
|
3641
|
+
}
|
|
3642
|
+
function searchEnvVars(language, appId, searchKey) {
|
|
3643
|
+
const prefix = publicEnvPrefix(language);
|
|
4324
3644
|
return [
|
|
4325
3645
|
{
|
|
4326
3646
|
name: `${prefix}ALGOLIA_APP_ID`,
|
|
@@ -4332,38 +3652,6 @@ function buildSearchEnvVars(language, strategy, appId, searchKey) {
|
|
|
4332
3652
|
}
|
|
4333
3653
|
];
|
|
4334
3654
|
}
|
|
4335
|
-
async function resolveIngestionProfile(ctx, language, repoRoot) {
|
|
4336
|
-
const { candidates, confirmed: confirmed3, onDisk } = await pickIngestionCandidates(
|
|
4337
|
-
repoRoot,
|
|
4338
|
-
language.languages.map((l) => l.name)
|
|
4339
|
-
);
|
|
4340
|
-
if (candidates.length === 0) {
|
|
4341
|
-
const chosen = confirmed3[0] ?? onDisk[0] ?? LANGUAGE_PROFILES[DEFAULT_LANGUAGE_ID];
|
|
4342
|
-
logger.warn(
|
|
4343
|
-
{
|
|
4344
|
-
confirmed: language.languages.map((l) => l.name),
|
|
4345
|
-
onDisk: onDisk.map((p) => p.id),
|
|
4346
|
-
chosen: chosen.id
|
|
4347
|
-
},
|
|
4348
|
-
"implement: no confirmed language matched a manifest on disk; falling back"
|
|
4349
|
-
);
|
|
4350
|
-
return chosen;
|
|
4351
|
-
}
|
|
4352
|
-
if (candidates.length === 1) return candidates[0];
|
|
4353
|
-
const backends = candidates.filter(isBackendLanguage);
|
|
4354
|
-
if (backends.length === 1) return backends[0];
|
|
4355
|
-
if (backends.length === 0) return candidates[0];
|
|
4356
|
-
if (isBackendLanguage(candidates[0])) return candidates[0];
|
|
4357
|
-
const options = backends.map((p) => p.displayName);
|
|
4358
|
-
const selection = await ctx.requestUserInput({
|
|
4359
|
-
prompt: "Which language should the ingestion script use?",
|
|
4360
|
-
promptType: "multipleChoice",
|
|
4361
|
-
options,
|
|
4362
|
-
defaultSelectedIndex: 0
|
|
4363
|
-
});
|
|
4364
|
-
const picked = typeof selection === "string" ? backends.find((p) => p.displayName === selection) : void 0;
|
|
4365
|
-
return picked ?? backends[0];
|
|
4366
|
-
}
|
|
4367
3655
|
function baseInstructions(input) {
|
|
4368
3656
|
return [
|
|
4369
3657
|
`Target Algolia index: ${input.targetIndex}`,
|
|
@@ -4391,48 +3679,37 @@ function sourceSpecificInstructions(input) {
|
|
|
4391
3679
|
generated: [
|
|
4392
3680
|
"No real data source exists; use sample records for each confirmed entity.",
|
|
4393
3681
|
"Call the generateRecord tool once per entity (entityName, attributes, count 20-50); it invents the values and unique objectIDs and writes them to a JSON file in the worktree, returning the file path. Do not write records or objectIDs yourself.",
|
|
4394
|
-
"In the script, read and parse each returned
|
|
3682
|
+
"In the script, read and parse each returned file path at runtime (e.g. JSON.parse(readFileSync(...)) in Node/Bun, json.load(open(...)) in Python) instead of inlining the records as literals.",
|
|
4395
3683
|
"Add a prominent TODO where the developer swaps the generated records (and the JSON file under `.algolia-wizard/data/`) for their real record source."
|
|
4396
3684
|
]
|
|
4397
3685
|
};
|
|
4398
3686
|
return byLine[input.ingestionSource];
|
|
4399
3687
|
}
|
|
4400
3688
|
function ingestionInstructions(input) {
|
|
4401
|
-
const { ingestionProfile: profile, toolchain } = input;
|
|
4402
|
-
const { ingest } = toolchain;
|
|
4403
|
-
const extensions = ingest.entrypointExtensions.join(", ");
|
|
4404
|
-
const runInstruction = ingest.kind === "auto" ? `Return "entrypoint": the script path relative to the worktree root (e.g. "${profile.ingestEntrypointExample}"), ending in one of ${extensions}. The wizard runs it with \`${describeIngestCommand(ingest, profile.ingestEntrypointExample)}\`, so it must be a plain path with no flags or arguments and must run as-is under that command.` : `Return "entrypoint": the script path relative to the worktree root (e.g. "${profile.ingestEntrypointExample}"), ending in one of ${extensions}. The wizard does NOT run ${profile.displayName} ${toolchain.packageManager.id} projects itself \u2014 it tells the developer to run \`${describeIngestCommand(ingest, profile.ingestEntrypointExample)}\`, so also add whatever build configuration that command needs${ingest.kind === "manual" && ingest.requiresBuildTask ? `, including a "${ingest.requiresBuildTask}" task in "${toolchain.packageManager.dependency.mode === "agent-declares" ? toolchain.packageManager.dependency.file : "the build file"}" that runs the script` : ""}.`;
|
|
4405
3689
|
return [
|
|
4406
3690
|
...input.confirmed && input.confirmed.length ? [
|
|
4407
|
-
`
|
|
3691
|
+
`Create an ingestion script under "${input.ingestDir}/" at the repo root.`,
|
|
4408
3692
|
`Ingest only these confirmed entities (name, source paths, attributes): ${JSON.stringify(input.confirmed)}.`,
|
|
4409
|
-
`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.
|
|
4410
|
-
|
|
3693
|
+
`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.`,
|
|
3694
|
+
"Use the appropriate Algolia package in the ingestion script. Do not use the raw HTTP API.",
|
|
4411
3695
|
"After a successful ingest, the script must print exactly one line to stdout in the form `ALGOLIA_WIZARD_RECORD_COUNT=<n>`, where <n> is the total number of records pushed to Algolia. Print it last, on its own line, with no surrounding text.",
|
|
4412
|
-
getNamedDoc("save-records",
|
|
4413
|
-
|
|
3696
|
+
getNamedDoc("save-records", "js"),
|
|
3697
|
+
'Add algoliasearch to package.json "dependencies" with a valid version range; the wizard installs the worktree deps after you finish.',
|
|
4414
3698
|
"The summary should be extremely concise.",
|
|
4415
|
-
|
|
3699
|
+
`Return how to run the script as two fields, not a command string: "runtime" (one of ${INGEST_RUNTIMES.join(", ")}) and "entrypoint" (the script path relative to the worktree root, e.g. "${input.ingestDir}/ingest.mjs"). The wizard runs \`<runtime> <entrypoint>\` directly, so the entrypoint must be a plain path with no flags or arguments. Write a script one of those interpreters can run as-is.`,
|
|
4416
3700
|
...sourceSpecificInstructions(input)
|
|
4417
3701
|
] : []
|
|
4418
3702
|
];
|
|
4419
3703
|
}
|
|
4420
3704
|
function searchInstructions(input) {
|
|
4421
|
-
const doc =
|
|
4422
|
-
"instantsearch-setup",
|
|
4423
|
-
searchDocKey(input.searchStrategy)
|
|
4424
|
-
);
|
|
4425
|
-
const isTemplate = input.searchStrategy === "cdn-template";
|
|
4426
|
-
const placement = isTemplate ? input.searchLocation ? `Add the search UI to the server-rendered template at "${input.searchLocation}" \u2014 ideally a shared layout, so it is reachable across the app.` : `This project has no shared template to host the UI, so create a standalone page at "${input.ingestDir}/search-demo.html" the developer can open directly, and add a TODO explaining how to move the snippet into their own layout.` : `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.`;
|
|
3705
|
+
const doc = getFrameworkSpecificDoc(frameworksForDoc(input.uiFramework));
|
|
4427
3706
|
return [
|
|
4428
3707
|
"Implement an in-app Algolia search experience.",
|
|
4429
|
-
`Build the search UI for ${
|
|
4430
|
-
"Follow the Algolia reference below for client setup and InstantSearch wiring; prefer it over prior knowledge:",
|
|
3708
|
+
`Build the search UI for ${input.uiFramework}.`,
|
|
3709
|
+
"Follow the Algolia JS SDK reference below for client setup and InstantSearch wiring; prefer it over prior knowledge:",
|
|
4431
3710
|
doc,
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
isTemplate ? "Load InstantSearch from a CDN with script tags as shown in the reference. Do not add JavaScript package dependencies, a bundler, or a build step." : '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.',
|
|
4435
|
-
isTemplate ? "Read the App ID and search-only API key from server-side configuration/environment and render them into the page (e.g. as data- attributes the script reads); never hardcode them, and never put a write/admin key in HTML." : "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.",
|
|
3711
|
+
`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.`,
|
|
3712
|
+
"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.",
|
|
4436
3713
|
// appId always resolves (loadActiveProfile throws otherwise); only the
|
|
4437
3714
|
// search-only key is best-effort and can fall back to a placeholder.
|
|
4438
3715
|
`Values: App ID "${input.appId}", search-only key ${input.searchKey ? `"${input.searchKey}"` : "(placeholder for the developer to fill in)"}.`,
|
|
@@ -4440,22 +3717,20 @@ function searchInstructions(input) {
|
|
|
4440
3717
|
// resolved app id / search-only key into ".env" under these exact names
|
|
4441
3718
|
// right after this step, so a renamed prefix here would leave the code
|
|
4442
3719
|
// reading a var the wizard never wrote.
|
|
4443
|
-
`Use exactly these env var names: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
|
|
3720
|
+
`Use exactly these public env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
|
|
3721
|
+
'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.',
|
|
4444
3722
|
"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."
|
|
4445
3723
|
];
|
|
4446
3724
|
}
|
|
4447
3725
|
function verificationInstructions(input) {
|
|
4448
|
-
const protectedDirs = [
|
|
4449
|
-
.../* @__PURE__ */ new Set([input.ingestDir, ingestScriptDir(input.ingestionProfile)])
|
|
4450
|
-
];
|
|
4451
3726
|
return [
|
|
4452
3727
|
"Verify the Algolia implementation changes in the current worktree.",
|
|
4453
3728
|
`Verification tools found in the codebase: ${JSON.stringify(input.findings.verification ?? [])}.`,
|
|
4454
|
-
|
|
3729
|
+
"Call verifyImplementation at least once; it runs every repo-defined lint/typecheck/check script and returns per-check results plus an aggregate ok.",
|
|
4455
3730
|
"For issues caused by the implementation, make minimal fixes with writeFile and re-run verifyImplementation.",
|
|
4456
3731
|
"Do not make speculative fixes when verifyImplementation cannot run, no checks exist, or failures are unrelated to these changes \u2014 note the limitation in your summary.",
|
|
4457
3732
|
"Do not add new Algolia functionality here; only validate and make minimal correctness fixes.",
|
|
4458
|
-
`Do not modify ${
|
|
3733
|
+
`Do not modify "${input.ingestDir}/" unless verifyImplementation reports an actionable issue in its files.`,
|
|
4459
3734
|
"Always call reportStatus with status=success once verification has run, even when sufficient=false.",
|
|
4460
3735
|
"Set sufficient=true only when the implementation is complete and checks pass (or fail for a clearly unrelated reason).",
|
|
4461
3736
|
"Set sufficient=false when the implementation is incomplete or has implementation-caused failures; include concrete additionalInstructions for the next pass."
|
|
@@ -4464,17 +3739,14 @@ function verificationInstructions(input) {
|
|
|
4464
3739
|
var IMPLEMENT_CONFIG = {
|
|
4465
3740
|
ingestion: {
|
|
4466
3741
|
title: "Algolia ingestion",
|
|
4467
|
-
label: "Ingestion",
|
|
4468
3742
|
buildInstructions: ingestionInstructions
|
|
4469
3743
|
},
|
|
4470
3744
|
search: {
|
|
4471
3745
|
title: "Algolia search",
|
|
4472
|
-
label: "Search",
|
|
4473
3746
|
buildInstructions: searchInstructions
|
|
4474
3747
|
},
|
|
4475
3748
|
verification: {
|
|
4476
3749
|
title: "Algolia verification",
|
|
4477
|
-
label: "Verification",
|
|
4478
3750
|
buildInstructions: verificationInstructions
|
|
4479
3751
|
}
|
|
4480
3752
|
};
|
|
@@ -4506,10 +3778,11 @@ function buildAgentInstructions(useCase, input, extraInstructions = []) {
|
|
|
4506
3778
|
];
|
|
4507
3779
|
}
|
|
4508
3780
|
function formatSummary(useCase, summary) {
|
|
4509
|
-
|
|
3781
|
+
const label = useCase === "ingestion" ? "Ingestion" : useCase === "search" ? "Search" : "Verification";
|
|
3782
|
+
return `${label}: ${summary}`;
|
|
4510
3783
|
}
|
|
4511
|
-
function buildIngestCommand(worktree,
|
|
4512
|
-
return `cd ${shellQuote(worktree)} && ${
|
|
3784
|
+
function buildIngestCommand(worktree, runtime, entrypoint) {
|
|
3785
|
+
return `cd ${shellQuote(worktree)} && ${runtime} ${shellQuote(entrypoint)}`;
|
|
4513
3786
|
}
|
|
4514
3787
|
function parseIngestRecordCount(output) {
|
|
4515
3788
|
const match = output.match(/ALGOLIA_WIZARD_RECORD_COUNT=(\d+)/);
|
|
@@ -4591,7 +3864,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4591
3864
|
await confirmDirtyWorkingTree(ctx, repoRoot);
|
|
4592
3865
|
}
|
|
4593
3866
|
const normalized = normalizeFindingPaths(findings);
|
|
4594
|
-
const
|
|
3867
|
+
const confirmed2 = normalized.confirmedEntities;
|
|
4595
3868
|
const searchLocation = normalized.searchImplementationAnalysis;
|
|
4596
3869
|
let appId;
|
|
4597
3870
|
let searchKey;
|
|
@@ -4629,66 +3902,31 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4629
3902
|
);
|
|
4630
3903
|
}
|
|
4631
3904
|
}
|
|
4632
|
-
const ingestionProfile = await resolveIngestionProfile(
|
|
4633
|
-
ctx,
|
|
4634
|
-
language,
|
|
4635
|
-
worktree
|
|
4636
|
-
);
|
|
4637
|
-
const toolchain = await resolveToolchain(worktree, ingestionProfile);
|
|
4638
|
-
const verificationLanguages = [
|
|
4639
|
-
.../* @__PURE__ */ new Set([
|
|
4640
|
-
ingestionProfile.id,
|
|
4641
|
-
...(await detectProfilesFromManifests(worktree)).map((p) => p.id)
|
|
4642
|
-
])
|
|
4643
|
-
];
|
|
4644
|
-
const frameworkName = language.frameworks[0]?.name;
|
|
4645
|
-
const searchStrategy = resolveSearchStrategy(
|
|
4646
|
-
frameworkName,
|
|
4647
|
-
verificationLanguages.includes(JAVASCRIPT)
|
|
4648
|
-
);
|
|
4649
|
-
logger.info(
|
|
4650
|
-
{
|
|
4651
|
-
language: ingestionProfile.id,
|
|
4652
|
-
packageManager: toolchain.packageManager.id,
|
|
4653
|
-
ingest: toolchain.ingest.kind,
|
|
4654
|
-
framework: frameworkName,
|
|
4655
|
-
searchStrategy
|
|
4656
|
-
},
|
|
4657
|
-
"implement: resolved ingestion toolchain and search strategy"
|
|
4658
|
-
);
|
|
4659
3905
|
const input = {
|
|
4660
3906
|
findings: normalized,
|
|
4661
|
-
confirmed:
|
|
3907
|
+
confirmed: confirmed2,
|
|
4662
3908
|
searchLocation,
|
|
4663
3909
|
targetIndex,
|
|
4664
3910
|
language,
|
|
4665
3911
|
appId,
|
|
4666
3912
|
searchKey,
|
|
4667
|
-
searchEnvVars:
|
|
4668
|
-
language,
|
|
4669
|
-
searchStrategy,
|
|
4670
|
-
appId,
|
|
4671
|
-
searchKey
|
|
4672
|
-
),
|
|
3913
|
+
searchEnvVars: searchEnvVars(language, appId, searchKey),
|
|
4673
3914
|
ingestDir: INGEST_DIR,
|
|
4674
3915
|
ingestionSource,
|
|
4675
3916
|
uploadFilePath,
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
toolchain,
|
|
4680
|
-
verificationLanguages
|
|
3917
|
+
// language.frameworks already prefers the confirm-framework step output,
|
|
3918
|
+
// so the user's confirmed stack (not just raw detection) picks the flavor.
|
|
3919
|
+
uiFramework: detectUiFramework(language)
|
|
4681
3920
|
};
|
|
4682
|
-
const searchToolchain = !bundlesJavaScript(searchStrategy) ? void 0 : ingestionProfile.id === JAVASCRIPT ? toolchain : await resolveToolchain(worktree, LANGUAGE_PROFILES[JAVASCRIPT]);
|
|
4683
|
-
const toolchainForUseCase = (useCase) => useCase === "search" ? searchToolchain : toolchain;
|
|
4684
3921
|
const summaries = [];
|
|
4685
3922
|
if (uploadWarning) summaries.push(uploadWarning);
|
|
4686
3923
|
let agentRuns = 0;
|
|
3924
|
+
let ingestRuntime;
|
|
4687
3925
|
let ingestEntrypoint;
|
|
4688
3926
|
let ingestScriptRan = false;
|
|
4689
3927
|
let ingestRecordCount;
|
|
4690
3928
|
let ingestDurationMs;
|
|
4691
|
-
|
|
3929
|
+
let installFailed = false;
|
|
4692
3930
|
let ingestOutcomeMessage;
|
|
4693
3931
|
async function runImplementationUseCase(currentUseCase, extraInstructions = []) {
|
|
4694
3932
|
if (agentRuns > 0) ctx.recordStepExecution();
|
|
@@ -4702,19 +3940,16 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4702
3940
|
tools: toolsForUseCase(currentUseCase, input.ingestionSource),
|
|
4703
3941
|
outputSchema: implementationOutputSchema
|
|
4704
3942
|
});
|
|
4705
|
-
const useCaseToolchain = toolchainForUseCase(currentUseCase);
|
|
4706
|
-
if (!useCaseToolchain) return result;
|
|
4707
3943
|
ctx.notify({
|
|
4708
3944
|
messages: [`Installing dependencies for ${currentUseCase}\u2026`]
|
|
4709
3945
|
});
|
|
4710
3946
|
const installLogId = ctx.logStart("installWorktreeDeps", {
|
|
4711
|
-
useCase: currentUseCase
|
|
4712
|
-
language: useCaseToolchain.profile.id
|
|
3947
|
+
useCase: currentUseCase
|
|
4713
3948
|
});
|
|
4714
|
-
const install = await installWorktreeDeps(worktree
|
|
3949
|
+
const install = await installWorktreeDeps(worktree);
|
|
4715
3950
|
ctx.logEnd(installLogId, install.ok ? "success" : "error");
|
|
4716
3951
|
if (!install.ok) {
|
|
4717
|
-
|
|
3952
|
+
installFailed = true;
|
|
4718
3953
|
logger.warn(
|
|
4719
3954
|
{ useCase: currentUseCase, output: install.output },
|
|
4720
3955
|
"implement: dependency install in worktree failed; generated commands may not run until deps are installed"
|
|
@@ -4728,16 +3963,15 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4728
3963
|
return runAgent({
|
|
4729
3964
|
instructions: buildAgentInstructions("verification", input),
|
|
4730
3965
|
tools: toolsForUseCase("verification"),
|
|
4731
|
-
outputSchema: verificationOutputSchema
|
|
4732
|
-
// So verifyImplementation runs this repo's checks, not just npm scripts.
|
|
4733
|
-
languages: input.verificationLanguages
|
|
3966
|
+
outputSchema: verificationOutputSchema
|
|
4734
3967
|
});
|
|
4735
3968
|
}
|
|
4736
3969
|
if (useCases.includes("ingestion")) {
|
|
4737
|
-
const { summary, entrypoint } = await runImplementationUseCase("ingestion");
|
|
3970
|
+
const { summary, runtime, entrypoint } = await runImplementationUseCase("ingestion");
|
|
4738
3971
|
summaries.push(formatSummary("ingestion", summary));
|
|
3972
|
+
ingestRuntime = runtime;
|
|
4739
3973
|
ingestEntrypoint = entrypoint;
|
|
4740
|
-
if (
|
|
3974
|
+
if (ingestRuntime && ingestEntrypoint && !installFailed) {
|
|
4741
3975
|
ctx.clearNotices();
|
|
4742
3976
|
const runNow = await ctx.requestUserInput({
|
|
4743
3977
|
prompt: `Run the ingestion script now? This writes records to the "${targetIndex}" index.`,
|
|
@@ -4749,13 +3983,13 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4749
3983
|
const profile = await loadActiveProfile();
|
|
4750
3984
|
ctx.notify({ messages: [`Writing records to "${targetIndex}"\u2026`] });
|
|
4751
3985
|
const scriptLogId = ctx.logStart("runIngestScript", {
|
|
4752
|
-
|
|
3986
|
+
runtime: ingestRuntime,
|
|
4753
3987
|
entrypoint: ingestEntrypoint
|
|
4754
3988
|
});
|
|
4755
3989
|
const startedAt = Date.now();
|
|
4756
3990
|
const run2 = await runIngestScript(
|
|
4757
3991
|
worktree,
|
|
4758
|
-
|
|
3992
|
+
ingestRuntime,
|
|
4759
3993
|
ingestEntrypoint,
|
|
4760
3994
|
{
|
|
4761
3995
|
[APP_ID_VAR]: profile.appId,
|
|
@@ -4769,7 +4003,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4769
4003
|
ingestRecordCount = parseIngestRecordCount(run2.output);
|
|
4770
4004
|
if (ingestRecordCount != null) {
|
|
4771
4005
|
track("AI Wizard Ingest Successful", {
|
|
4772
|
-
entity_name:
|
|
4006
|
+
entity_name: confirmed2?.map((e) => e.name).join(", ") || "unknown",
|
|
4773
4007
|
record_count: ingestRecordCount,
|
|
4774
4008
|
duration_ms: ingestDurationMs
|
|
4775
4009
|
});
|
|
@@ -4782,7 +4016,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4782
4016
|
outcomeMessage = `\u26A0\uFE0F The ingestion script did not run: ${run2.reason}`;
|
|
4783
4017
|
logger.warn(
|
|
4784
4018
|
{
|
|
4785
|
-
|
|
4019
|
+
runtime: ingestRuntime,
|
|
4786
4020
|
entrypoint: ingestEntrypoint,
|
|
4787
4021
|
reason: run2.reason
|
|
4788
4022
|
},
|
|
@@ -4805,7 +4039,7 @@ ${run2.output}` : status;
|
|
|
4805
4039
|
outcomeMessage = `\u274C Ingestion failed.${run2.output ? ` ${run2.output}` : ""}`;
|
|
4806
4040
|
logger.warn(
|
|
4807
4041
|
{
|
|
4808
|
-
|
|
4042
|
+
runtime: ingestRuntime,
|
|
4809
4043
|
entrypoint: ingestEntrypoint,
|
|
4810
4044
|
output: run2.output
|
|
4811
4045
|
},
|
|
@@ -4822,28 +4056,10 @@ ${run2.output}` : status;
|
|
|
4822
4056
|
}
|
|
4823
4057
|
}
|
|
4824
4058
|
const commandMessages = [`Open the worktree: cd ${shellQuote(worktree)}`];
|
|
4825
|
-
if (ingestEntrypoint) {
|
|
4059
|
+
if (ingestRuntime && ingestEntrypoint) {
|
|
4826
4060
|
commandMessages.push(
|
|
4827
|
-
`Ingestion command: ${buildIngestCommand(worktree,
|
|
4061
|
+
`Ingestion command: ${buildIngestCommand(worktree, ingestRuntime, ingestEntrypoint)}`
|
|
4828
4062
|
);
|
|
4829
|
-
if (toolchain.ingest.kind === "manual") {
|
|
4830
|
-
commandMessages.push(
|
|
4831
|
-
`The wizard does not run ${ingestionProfile.displayName} ${toolchain.packageManager.id} projects \u2014 run the command above yourself to ingest.`
|
|
4832
|
-
);
|
|
4833
|
-
const missingTask = await missingBuildTask(worktree, toolchain);
|
|
4834
|
-
if (missingTask) {
|
|
4835
|
-
const warning = `\u26A0\uFE0F The command above needs a "${missingTask}" task, which is not in ${toolchain.packageManager.dependency.mode === "agent-declares" ? toolchain.packageManager.dependency.file : "the build file"} \u2014 add it before running, or run the script through your IDE instead.`;
|
|
4836
|
-
commandMessages.push(warning);
|
|
4837
|
-
summaries.push(warning);
|
|
4838
|
-
}
|
|
4839
|
-
}
|
|
4840
|
-
}
|
|
4841
|
-
if (ingestionSource === "local") {
|
|
4842
|
-
const limitation = localSourceLimitation(worktree, ingestionProfile);
|
|
4843
|
-
if (limitation) {
|
|
4844
|
-
commandMessages.push(`\u26A0\uFE0F ${limitation}`);
|
|
4845
|
-
summaries.push(`\u26A0\uFE0F ${limitation}`);
|
|
4846
|
-
}
|
|
4847
4063
|
}
|
|
4848
4064
|
await ctx.requestUserInput({
|
|
4849
4065
|
// No question being asked here, just an acknowledgement — the
|
|
@@ -4854,20 +4070,7 @@ ${run2.output}` : status;
|
|
|
4854
4070
|
messages: ingestOutcomeMessage ? [ingestOutcomeMessage, ...commandMessages] : commandMessages
|
|
4855
4071
|
});
|
|
4856
4072
|
}
|
|
4857
|
-
|
|
4858
|
-
if (skipSearch) {
|
|
4859
|
-
const target = describeSearchTarget(
|
|
4860
|
-
input.searchStrategy,
|
|
4861
|
-
input.frameworkName
|
|
4862
|
-
);
|
|
4863
|
-
summaries.push(
|
|
4864
|
-
`Search UI skipped: the wizard can't scaffold a native search UI for ${target}. Your records are in the "${targetIndex}" index \u2014 build the UI with Algolia's mobile InstantSearch libraries (https://www.algolia.com/doc/guides/building-search-ui/what-is-instantsearch/ios/ for iOS, .../android for Android).`
|
|
4865
|
-
);
|
|
4866
|
-
track("AI Wizard Search UI Skipped", {
|
|
4867
|
-
framework: input.frameworkName ?? "unknown"
|
|
4868
|
-
});
|
|
4869
|
-
}
|
|
4870
|
-
if (useCases.includes("search") && !skipSearch) {
|
|
4073
|
+
if (useCases.includes("search")) {
|
|
4871
4074
|
let extraInstructions = [];
|
|
4872
4075
|
const preSearchFiles = new Set(await listChangedFiles(worktree));
|
|
4873
4076
|
for (let attempt = 1; attempt <= MAX_IMPLEMENT_VERIFICATION_ATTEMPTS; attempt++) {
|
|
@@ -4938,9 +4141,9 @@ ${run2.output}` : status;
|
|
|
4938
4141
|
"implement: agent reported success but no files changed in the worktree"
|
|
4939
4142
|
);
|
|
4940
4143
|
}
|
|
4941
|
-
if (
|
|
4144
|
+
if (installFailed) {
|
|
4942
4145
|
summaries.push(
|
|
4943
|
-
|
|
4146
|
+
'\u26A0\uFE0F Dependency install in the worktree failed. Run your package manager install in the worktree before the command below, or it will fail with "Cannot find module".'
|
|
4944
4147
|
);
|
|
4945
4148
|
}
|
|
4946
4149
|
return {
|
|
@@ -4948,10 +4151,10 @@ ${run2.output}` : status;
|
|
|
4948
4151
|
filesChanged,
|
|
4949
4152
|
summary: summaries.join("\n\n"),
|
|
4950
4153
|
worktreePath: worktree,
|
|
4951
|
-
...useCases.includes("ingestion") && ingestEntrypoint ? {
|
|
4154
|
+
...useCases.includes("ingestion") && ingestRuntime && ingestEntrypoint ? {
|
|
4952
4155
|
ingestCommand: buildIngestCommand(
|
|
4953
4156
|
worktree,
|
|
4954
|
-
|
|
4157
|
+
ingestRuntime,
|
|
4955
4158
|
ingestEntrypoint
|
|
4956
4159
|
),
|
|
4957
4160
|
ingestScriptRan,
|
|
@@ -5280,20 +4483,20 @@ function parseCliArgs(argv) {
|
|
|
5280
4483
|
}
|
|
5281
4484
|
|
|
5282
4485
|
// src/lib/resetState.ts
|
|
5283
|
-
import { readdir as
|
|
5284
|
-
import { join as
|
|
4486
|
+
import { readdir as readdir4, rm as rm2 } from "node:fs/promises";
|
|
4487
|
+
import { join as join12 } from "node:path";
|
|
5285
4488
|
var KEEP = ["wizard.log"];
|
|
5286
4489
|
async function resetProjectState() {
|
|
5287
4490
|
const dir = stateDir();
|
|
5288
4491
|
let entries;
|
|
5289
4492
|
try {
|
|
5290
|
-
entries = await
|
|
4493
|
+
entries = await readdir4(dir);
|
|
5291
4494
|
} catch {
|
|
5292
4495
|
return { dir, removed: [] };
|
|
5293
4496
|
}
|
|
5294
4497
|
const targets = entries.filter((name) => !KEEP.includes(name));
|
|
5295
4498
|
await Promise.all(
|
|
5296
|
-
targets.map((name) => rm2(
|
|
4499
|
+
targets.map((name) => rm2(join12(dir, name), { recursive: true, force: true }))
|
|
5297
4500
|
);
|
|
5298
4501
|
return { dir, removed: targets };
|
|
5299
4502
|
}
|