@algolia/wizard 0.80.0 → 0.82.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 +579 -151
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -2306,7 +2306,7 @@ async function ensureApplication(resumeAppId) {
|
|
|
2306
2306
|
// package.json
|
|
2307
2307
|
var package_default = {
|
|
2308
2308
|
name: "@algolia/wizard",
|
|
2309
|
-
version: "0.
|
|
2309
|
+
version: "0.82.0",
|
|
2310
2310
|
description: "Magically implement Algolia functionality in your codebase",
|
|
2311
2311
|
type: "module",
|
|
2312
2312
|
engines: {
|
|
@@ -3566,30 +3566,379 @@ async function gitIgnoreOutcome(ctx, target) {
|
|
|
3566
3566
|
}
|
|
3567
3567
|
|
|
3568
3568
|
// src/lib/tools/searchFiles.ts
|
|
3569
|
+
import { execFile as execFile2 } from "node:child_process";
|
|
3569
3570
|
import { tool as tool7 } from "ai";
|
|
3570
3571
|
import z14 from "zod";
|
|
3571
|
-
import { readdir as readdir2, readFile as readFile7 } from "node:fs/promises";
|
|
3572
|
-
import { join as join8 } from "node:path";
|
|
3572
|
+
import { lstat as lstat3, readdir as readdir2, readFile as readFile7, realpath, stat as stat2 } from "node:fs/promises";
|
|
3573
|
+
import { isAbsolute as isAbsolute2, join as join8, relative as relative4, sep as sep2 } from "node:path";
|
|
3574
|
+
import { performance } from "node:perf_hooks";
|
|
3575
|
+
import { Worker } from "node:worker_threads";
|
|
3576
|
+
var MAX_GIT_OUTPUT_BYTES = 32e6;
|
|
3573
3577
|
var MAX_QUERY_LENGTH = 1e3;
|
|
3578
|
+
var MAX_MATCH_TEXT_CHARACTERS = 500;
|
|
3579
|
+
var MAX_RESULT_CHARACTERS = 32e3;
|
|
3580
|
+
var REGEX_TIMEOUT_MS = 100;
|
|
3581
|
+
var REGEX_SEARCH_DEADLINE_MS = 2e3;
|
|
3582
|
+
var LINE_NUMBER_OFFSET = 1;
|
|
3583
|
+
var NEWLINE_CHARACTER_COUNT = 1;
|
|
3574
3584
|
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
3575
3585
|
"node_modules",
|
|
3576
3586
|
"dist",
|
|
3577
3587
|
"build",
|
|
3588
|
+
"coverage",
|
|
3578
3589
|
"vendor",
|
|
3579
3590
|
"venv",
|
|
3580
3591
|
"__pycache__",
|
|
3581
3592
|
"target"
|
|
3582
3593
|
]);
|
|
3583
|
-
|
|
3594
|
+
var SKIP_FILES = /* @__PURE__ */ new Set([
|
|
3595
|
+
"bun.lock",
|
|
3596
|
+
"bun.lockb",
|
|
3597
|
+
"Cargo.lock",
|
|
3598
|
+
"composer.lock",
|
|
3599
|
+
"Gemfile.lock",
|
|
3600
|
+
"package-lock.json",
|
|
3601
|
+
"npm-shrinkwrap.json",
|
|
3602
|
+
"Pipfile.lock",
|
|
3603
|
+
"Podfile.lock",
|
|
3604
|
+
"pnpm-lock.yaml",
|
|
3605
|
+
"poetry.lock",
|
|
3606
|
+
"uv.lock",
|
|
3607
|
+
"yarn.lock"
|
|
3608
|
+
]);
|
|
3609
|
+
var REGEX_WORKER_SOURCE = `
|
|
3610
|
+
const { parentPort, workerData } = require('node:worker_threads')
|
|
3611
|
+
|
|
3612
|
+
function matchingLineSnippet(line, matchIndex) {
|
|
3613
|
+
if (line.length <= workerData.maxMatchTextCharacters) return line
|
|
3614
|
+
|
|
3615
|
+
const contextBefore = Math.floor(workerData.maxMatchTextCharacters / 2)
|
|
3616
|
+
const start = Math.max(0, matchIndex - contextBefore)
|
|
3617
|
+
const end = Math.min(
|
|
3618
|
+
line.length,
|
|
3619
|
+
start + workerData.maxMatchTextCharacters,
|
|
3620
|
+
)
|
|
3621
|
+
const prefix = start > 0 ? '... ' : ''
|
|
3622
|
+
const suffix = end < line.length ? ' ...' : ''
|
|
3623
|
+
return \`\${prefix}\${line.slice(start, end)}\${suffix} (line truncated)\`
|
|
3624
|
+
}
|
|
3625
|
+
|
|
3626
|
+
let expression
|
|
3627
|
+
try {
|
|
3628
|
+
expression = new RegExp(workerData.query)
|
|
3629
|
+
} catch (error) {
|
|
3630
|
+
parentPort.postMessage({
|
|
3631
|
+
type: 'invalid',
|
|
3632
|
+
message: error instanceof Error ? error.message : String(error),
|
|
3633
|
+
})
|
|
3634
|
+
parentPort.close()
|
|
3635
|
+
}
|
|
3636
|
+
|
|
3637
|
+
if (expression !== undefined) {
|
|
3638
|
+
parentPort.postMessage({ type: 'ready' })
|
|
3639
|
+
parentPort.on('message', ({ id, content, limit }) => {
|
|
3640
|
+
const matches = []
|
|
3641
|
+
const lines = content.split('\\n')
|
|
3642
|
+
parentPort.postMessage({ type: 'started', id })
|
|
3643
|
+
for (const [lineIndex, line] of lines.entries()) {
|
|
3644
|
+
const matchIndex = line.search(expression)
|
|
3645
|
+
if (matchIndex >= 0) {
|
|
3646
|
+
matches.push({
|
|
3647
|
+
lineIndex,
|
|
3648
|
+
text: matchingLineSnippet(line, matchIndex),
|
|
3649
|
+
})
|
|
3650
|
+
if (matches.length >= limit) break
|
|
3651
|
+
}
|
|
3652
|
+
}
|
|
3653
|
+
parentPort.postMessage({ type: 'matches', id, matches })
|
|
3654
|
+
})
|
|
3655
|
+
}
|
|
3656
|
+
`;
|
|
3657
|
+
var InvalidRegexError = class extends Error {
|
|
3658
|
+
};
|
|
3659
|
+
var RegexSearchWorker = class _RegexSearchWorker {
|
|
3660
|
+
worker;
|
|
3661
|
+
ready;
|
|
3662
|
+
resolveReady;
|
|
3663
|
+
rejectReady;
|
|
3664
|
+
readySettled = false;
|
|
3665
|
+
stopped = false;
|
|
3666
|
+
nextId = 0;
|
|
3667
|
+
pending;
|
|
3668
|
+
termination;
|
|
3669
|
+
constructor(query) {
|
|
3670
|
+
this.ready = new Promise((resolve6, reject) => {
|
|
3671
|
+
this.resolveReady = resolve6;
|
|
3672
|
+
this.rejectReady = reject;
|
|
3673
|
+
});
|
|
3674
|
+
this.worker = new Worker(REGEX_WORKER_SOURCE, {
|
|
3675
|
+
eval: true,
|
|
3676
|
+
workerData: {
|
|
3677
|
+
query,
|
|
3678
|
+
maxMatchTextCharacters: MAX_MATCH_TEXT_CHARACTERS
|
|
3679
|
+
}
|
|
3680
|
+
});
|
|
3681
|
+
this.worker.on("message", (message) => {
|
|
3682
|
+
this.handleMessage(message);
|
|
3683
|
+
});
|
|
3684
|
+
this.worker.on("error", this.handleWorkerError);
|
|
3685
|
+
this.worker.on("exit", (code) => {
|
|
3686
|
+
if (this.termination === void 0 && !this.stopped) {
|
|
3687
|
+
this.fail(new Error(`Regex worker exited unexpectedly with code ${code}`));
|
|
3688
|
+
}
|
|
3689
|
+
});
|
|
3690
|
+
}
|
|
3691
|
+
static async create(query) {
|
|
3692
|
+
const searchWorker = new _RegexSearchWorker(query);
|
|
3693
|
+
try {
|
|
3694
|
+
await searchWorker.ready;
|
|
3695
|
+
return searchWorker;
|
|
3696
|
+
} catch (error) {
|
|
3697
|
+
await searchWorker.close();
|
|
3698
|
+
throw error;
|
|
3699
|
+
}
|
|
3700
|
+
}
|
|
3701
|
+
match(content, limit, timeoutMs) {
|
|
3702
|
+
if (this.stopped) throw new Error("Regex worker is not available");
|
|
3703
|
+
return new Promise((resolve6, reject) => {
|
|
3704
|
+
const id = ++this.nextId;
|
|
3705
|
+
const pending = { id, timeoutMs, resolve: resolve6, reject };
|
|
3706
|
+
this.pending = pending;
|
|
3707
|
+
try {
|
|
3708
|
+
this.worker.postMessage({ id, content, limit });
|
|
3709
|
+
} catch (error) {
|
|
3710
|
+
this.pending = void 0;
|
|
3711
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
3712
|
+
return;
|
|
3713
|
+
}
|
|
3714
|
+
});
|
|
3715
|
+
}
|
|
3716
|
+
async close() {
|
|
3717
|
+
this.stopped = true;
|
|
3718
|
+
const pending = this.pending;
|
|
3719
|
+
if (pending?.timer !== void 0) clearTimeout(pending.timer);
|
|
3720
|
+
this.termination ??= this.worker.terminate();
|
|
3721
|
+
await this.termination.catch(() => void 0);
|
|
3722
|
+
}
|
|
3723
|
+
handleWorkerError = (error) => {
|
|
3724
|
+
this.fail(error);
|
|
3725
|
+
};
|
|
3726
|
+
handleMessage(message) {
|
|
3727
|
+
if (message.type === "ready") {
|
|
3728
|
+
if (!this.readySettled) {
|
|
3729
|
+
this.readySettled = true;
|
|
3730
|
+
this.resolveReady();
|
|
3731
|
+
}
|
|
3732
|
+
return;
|
|
3733
|
+
}
|
|
3734
|
+
if (message.type === "invalid") {
|
|
3735
|
+
if (!this.readySettled) {
|
|
3736
|
+
this.readySettled = true;
|
|
3737
|
+
this.stopped = true;
|
|
3738
|
+
this.rejectReady(
|
|
3739
|
+
new InvalidRegexError(message.message ?? "Invalid regular expression")
|
|
3740
|
+
);
|
|
3741
|
+
}
|
|
3742
|
+
return;
|
|
3743
|
+
}
|
|
3744
|
+
const pending = this.pending;
|
|
3745
|
+
if (message.type === "started" && message.id !== void 0 && pending !== void 0 && pending.id === message.id) {
|
|
3746
|
+
const startedAt = performance.now();
|
|
3747
|
+
pending.startedAt = startedAt;
|
|
3748
|
+
pending.timer = setTimeout(() => {
|
|
3749
|
+
const current = this.pending;
|
|
3750
|
+
if (current === void 0 || current.id !== message.id) return;
|
|
3751
|
+
this.pending = void 0;
|
|
3752
|
+
this.stopped = true;
|
|
3753
|
+
this.termination = this.worker.terminate();
|
|
3754
|
+
pending.resolve({
|
|
3755
|
+
locations: null,
|
|
3756
|
+
executionMs: performance.now() - startedAt
|
|
3757
|
+
});
|
|
3758
|
+
}, pending.timeoutMs);
|
|
3759
|
+
return;
|
|
3760
|
+
}
|
|
3761
|
+
if (message.type !== "matches" || message.id === void 0 || message.matches === void 0 || pending === void 0 || pending.id !== message.id || pending.startedAt === void 0) {
|
|
3762
|
+
return;
|
|
3763
|
+
}
|
|
3764
|
+
this.pending = void 0;
|
|
3765
|
+
if (pending.timer !== void 0) clearTimeout(pending.timer);
|
|
3766
|
+
pending.resolve({
|
|
3767
|
+
locations: message.matches,
|
|
3768
|
+
executionMs: performance.now() - pending.startedAt
|
|
3769
|
+
});
|
|
3770
|
+
}
|
|
3771
|
+
fail(error) {
|
|
3772
|
+
if (this.stopped) return;
|
|
3773
|
+
this.stopped = true;
|
|
3774
|
+
if (!this.readySettled) {
|
|
3775
|
+
this.readySettled = true;
|
|
3776
|
+
this.rejectReady(error);
|
|
3777
|
+
}
|
|
3778
|
+
const pending = this.pending;
|
|
3779
|
+
this.pending = void 0;
|
|
3780
|
+
if (pending !== void 0) {
|
|
3781
|
+
if (pending.timer !== void 0) clearTimeout(pending.timer);
|
|
3782
|
+
pending.reject(error);
|
|
3783
|
+
}
|
|
3784
|
+
}
|
|
3785
|
+
};
|
|
3786
|
+
function isSearchableGitPath(path, skipGeneratedDirs) {
|
|
3787
|
+
const parts = path.split("/");
|
|
3788
|
+
const fileName = path.slice(path.lastIndexOf("/") + LINE_NUMBER_OFFSET);
|
|
3789
|
+
return !SKIP_FILES.has(fileName) && parts.every(
|
|
3790
|
+
(part) => !part.startsWith(".") && (!skipGeneratedDirs || !SKIP_DIRS.has(part))
|
|
3791
|
+
);
|
|
3792
|
+
}
|
|
3793
|
+
function parseGitFiles(root, output, skipGeneratedDirs) {
|
|
3794
|
+
return output.split("\0").filter(Boolean).filter((path) => isSearchableGitPath(path, skipGeneratedDirs)).map((path) => join8(root, path));
|
|
3795
|
+
}
|
|
3796
|
+
function gitIsUnavailable(error, stderr) {
|
|
3797
|
+
return "code" in error && error.code === "ENOENT" || stderr.includes("not a git repository");
|
|
3798
|
+
}
|
|
3799
|
+
async function listGitFiles(root, pathspec, args, skipGeneratedDirs) {
|
|
3800
|
+
const env = { ...process.env };
|
|
3801
|
+
env.GIT_DIR = void 0;
|
|
3802
|
+
env.GIT_WORK_TREE = void 0;
|
|
3803
|
+
env.GIT_INDEX_FILE = void 0;
|
|
3804
|
+
env.GIT_OBJECT_DIRECTORY = void 0;
|
|
3805
|
+
env.GIT_COMMON_DIR = void 0;
|
|
3806
|
+
return new Promise((resolve6, reject) => {
|
|
3807
|
+
execFile2(
|
|
3808
|
+
"git",
|
|
3809
|
+
["-C", root, "ls-files", ...args, "-z", "--", pathspec],
|
|
3810
|
+
{ env, maxBuffer: MAX_GIT_OUTPUT_BYTES },
|
|
3811
|
+
(error, stdout, stderr) => {
|
|
3812
|
+
if (error === null) {
|
|
3813
|
+
resolve6({
|
|
3814
|
+
available: true,
|
|
3815
|
+
files: parseGitFiles(
|
|
3816
|
+
root,
|
|
3817
|
+
stdout.toString(),
|
|
3818
|
+
skipGeneratedDirs
|
|
3819
|
+
)
|
|
3820
|
+
});
|
|
3821
|
+
return;
|
|
3822
|
+
}
|
|
3823
|
+
if (gitIsUnavailable(error, stderr.toString())) {
|
|
3824
|
+
resolve6({ available: false, files: [] });
|
|
3825
|
+
return;
|
|
3826
|
+
}
|
|
3827
|
+
reject(error);
|
|
3828
|
+
}
|
|
3829
|
+
);
|
|
3830
|
+
});
|
|
3831
|
+
}
|
|
3832
|
+
async function gitVisibleFiles(root, target) {
|
|
3833
|
+
const pathspec = relative4(root, target).split(sep2).join("/") || ".";
|
|
3834
|
+
const [tracked, untracked] = await Promise.all([
|
|
3835
|
+
listGitFiles(root, pathspec, ["--cached"], false),
|
|
3836
|
+
listGitFiles(
|
|
3837
|
+
root,
|
|
3838
|
+
pathspec,
|
|
3839
|
+
["--others", "--exclude-standard"],
|
|
3840
|
+
true
|
|
3841
|
+
)
|
|
3842
|
+
]);
|
|
3843
|
+
if (!tracked.available || !untracked.available) {
|
|
3844
|
+
return { available: false, files: [] };
|
|
3845
|
+
}
|
|
3846
|
+
return {
|
|
3847
|
+
available: true,
|
|
3848
|
+
files: [.../* @__PURE__ */ new Set([...tracked.files, ...untracked.files])]
|
|
3849
|
+
};
|
|
3850
|
+
}
|
|
3851
|
+
function isInsideRoot(root, target) {
|
|
3852
|
+
const fromRoot = relative4(root, target);
|
|
3853
|
+
return fromRoot !== ".." && !fromRoot.startsWith(`..${sep2}`) && !isAbsolute2(fromRoot);
|
|
3854
|
+
}
|
|
3855
|
+
async function walkFiles(root, dir) {
|
|
3856
|
+
const [rootPath, dirPath, dirInfo] = await Promise.all([
|
|
3857
|
+
realpath(root),
|
|
3858
|
+
realpath(dir),
|
|
3859
|
+
lstat3(dir)
|
|
3860
|
+
]);
|
|
3861
|
+
if (dirInfo.isSymbolicLink() || !isInsideRoot(rootPath, dirPath)) {
|
|
3862
|
+
return [];
|
|
3863
|
+
}
|
|
3584
3864
|
const out = [];
|
|
3585
3865
|
for (const e of await readdir2(dir, { withFileTypes: true })) {
|
|
3586
3866
|
if (e.name.startsWith(".") || SKIP_DIRS.has(e.name)) continue;
|
|
3587
3867
|
const full = join8(dir, e.name);
|
|
3588
|
-
if (e.isDirectory()) out.push(...await walkFiles(full));
|
|
3589
|
-
else if (e.isFile()) out.push(full);
|
|
3868
|
+
if (e.isDirectory()) out.push(...await walkFiles(root, full));
|
|
3869
|
+
else if (e.isFile() && !SKIP_FILES.has(e.name)) out.push(full);
|
|
3590
3870
|
}
|
|
3591
3871
|
return out;
|
|
3592
3872
|
}
|
|
3873
|
+
async function expandNestedRepositories(files) {
|
|
3874
|
+
const expanded = [];
|
|
3875
|
+
for (const file of files) {
|
|
3876
|
+
const info = await lstat3(file).catch(() => null);
|
|
3877
|
+
if (info?.isFile() || info?.isSymbolicLink()) {
|
|
3878
|
+
expanded.push(file);
|
|
3879
|
+
continue;
|
|
3880
|
+
}
|
|
3881
|
+
if (!info?.isDirectory()) continue;
|
|
3882
|
+
try {
|
|
3883
|
+
const nested = await gitVisibleFiles(file, file);
|
|
3884
|
+
expanded.push(
|
|
3885
|
+
...nested.available ? nested.files : await walkFiles(file, file)
|
|
3886
|
+
);
|
|
3887
|
+
} catch (error) {
|
|
3888
|
+
logger.warn(
|
|
3889
|
+
{ error, root: file },
|
|
3890
|
+
"searchFiles: nested Git file scan failed, using safe walker"
|
|
3891
|
+
);
|
|
3892
|
+
expanded.push(...await walkFiles(file, file));
|
|
3893
|
+
}
|
|
3894
|
+
}
|
|
3895
|
+
return expanded;
|
|
3896
|
+
}
|
|
3897
|
+
async function gitRootForTarget(root, target) {
|
|
3898
|
+
let repositoryRoot = root;
|
|
3899
|
+
let current = root;
|
|
3900
|
+
const parts = relative4(root, target).split(sep2).filter(Boolean);
|
|
3901
|
+
for (const part of parts) {
|
|
3902
|
+
current = join8(current, part);
|
|
3903
|
+
const info = await lstat3(current).catch(() => null);
|
|
3904
|
+
if (!info?.isDirectory()) return repositoryRoot;
|
|
3905
|
+
const marker = await lstat3(join8(current, ".git")).catch(() => null);
|
|
3906
|
+
if (marker?.isDirectory() || marker?.isFile()) repositoryRoot = current;
|
|
3907
|
+
}
|
|
3908
|
+
return repositoryRoot;
|
|
3909
|
+
}
|
|
3910
|
+
async function safeSearchPath(rootPath, file) {
|
|
3911
|
+
const [info, filePath] = await Promise.all([lstat3(file), realpath(file)]);
|
|
3912
|
+
return info.isFile() && isInsideRoot(rootPath, filePath) ? filePath : null;
|
|
3913
|
+
}
|
|
3914
|
+
async function searchableFiles(root, target) {
|
|
3915
|
+
try {
|
|
3916
|
+
const gitRoot = await gitRootForTarget(root, target);
|
|
3917
|
+
const result = await gitVisibleFiles(gitRoot, target);
|
|
3918
|
+
return result.available ? expandNestedRepositories(result.files) : walkFiles(root, target);
|
|
3919
|
+
} catch (error) {
|
|
3920
|
+
logger.warn(
|
|
3921
|
+
{ error, root, target },
|
|
3922
|
+
"searchFiles: Git file scan failed, using safe walker"
|
|
3923
|
+
);
|
|
3924
|
+
return walkFiles(root, target);
|
|
3925
|
+
}
|
|
3926
|
+
}
|
|
3927
|
+
function formatSearchResult(matches, skippedFiles, deadlineReached, workerFailed, finalMarker) {
|
|
3928
|
+
const lines = [...matches];
|
|
3929
|
+
if (skippedFiles > 0) {
|
|
3930
|
+
const fileLabel = skippedFiles === 1 ? "file" : "files";
|
|
3931
|
+
const deadlineMarker = deadlineReached ? ", regex deadline reached" : "";
|
|
3932
|
+
lines.push(
|
|
3933
|
+
`... (partial results: skipped ${skippedFiles} timed-out ${fileLabel}${deadlineMarker})`
|
|
3934
|
+
);
|
|
3935
|
+
}
|
|
3936
|
+
if (workerFailed) {
|
|
3937
|
+
lines.push("... (partial results: regex worker stopped unexpectedly)");
|
|
3938
|
+
}
|
|
3939
|
+
if (finalMarker !== void 0) lines.push(finalMarker);
|
|
3940
|
+
return lines.length > 0 ? lines.join("\n") : "No matches";
|
|
3941
|
+
}
|
|
3593
3942
|
function searchFilesTool(ctx) {
|
|
3594
3943
|
return tool7({
|
|
3595
3944
|
description: "Search file contents for a JavaScript regular expression (RegExp syntax, not grep/PCRE). Returns matching lines as file:line:text.",
|
|
@@ -3607,32 +3956,124 @@ function searchFilesTool(ctx) {
|
|
|
3607
3956
|
}
|
|
3608
3957
|
const resolved2 = resolveInRoot(ctx, path);
|
|
3609
3958
|
if (!resolved2.ok) return resolved2.error;
|
|
3610
|
-
let re;
|
|
3611
3959
|
try {
|
|
3612
|
-
|
|
3960
|
+
await stat2(resolved2.target);
|
|
3613
3961
|
} catch (err) {
|
|
3614
|
-
return `
|
|
3962
|
+
return `Error searching ${path}: ${err.message}`;
|
|
3963
|
+
}
|
|
3964
|
+
let regexWorker;
|
|
3965
|
+
try {
|
|
3966
|
+
regexWorker = await RegexSearchWorker.create(query);
|
|
3967
|
+
} catch (error) {
|
|
3968
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3969
|
+
if (error instanceof InvalidRegexError) {
|
|
3970
|
+
return `Invalid regex: ${message}`;
|
|
3971
|
+
}
|
|
3972
|
+
logger.warn({ error }, "searchFiles: regex worker failed to start");
|
|
3973
|
+
return "Refused: regex worker could not start. Retry the search.";
|
|
3615
3974
|
}
|
|
3616
3975
|
const matches = [];
|
|
3617
|
-
|
|
3618
|
-
|
|
3976
|
+
let resultCharacters = 0;
|
|
3977
|
+
let skippedFiles = 0;
|
|
3978
|
+
let remainingRegexTime = REGEX_SEARCH_DEADLINE_MS;
|
|
3979
|
+
let deadlineReached = false;
|
|
3980
|
+
let workerFailed = false;
|
|
3981
|
+
try {
|
|
3619
3982
|
try {
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3983
|
+
const files = await searchableFiles(ctx.root, resolved2.target);
|
|
3984
|
+
const rootPath = await realpath(ctx.root);
|
|
3985
|
+
for (const [fileIndex, file] of files.entries()) {
|
|
3986
|
+
const moreFilesRemain = fileIndex < files.length - LINE_NUMBER_OFFSET;
|
|
3987
|
+
if (remainingRegexTime <= 0) {
|
|
3988
|
+
deadlineReached = true;
|
|
3989
|
+
break;
|
|
3990
|
+
}
|
|
3991
|
+
let content;
|
|
3992
|
+
try {
|
|
3993
|
+
const filePath = await safeSearchPath(rootPath, file);
|
|
3994
|
+
if (filePath === null) continue;
|
|
3995
|
+
content = await readFile7(filePath, "utf8");
|
|
3996
|
+
} catch {
|
|
3997
|
+
continue;
|
|
3998
|
+
}
|
|
3999
|
+
let result;
|
|
4000
|
+
try {
|
|
4001
|
+
result = await regexWorker.match(
|
|
4002
|
+
content,
|
|
4003
|
+
ctx.limits.match - matches.length,
|
|
4004
|
+
Math.min(REGEX_TIMEOUT_MS, remainingRegexTime)
|
|
4005
|
+
);
|
|
4006
|
+
} catch (error) {
|
|
4007
|
+
logger.warn(
|
|
4008
|
+
{ error, file },
|
|
4009
|
+
"searchFiles: regex worker stopped during search"
|
|
4010
|
+
);
|
|
4011
|
+
workerFailed = true;
|
|
4012
|
+
break;
|
|
4013
|
+
}
|
|
4014
|
+
const { executionMs, locations } = result;
|
|
4015
|
+
remainingRegexTime -= executionMs;
|
|
4016
|
+
if (locations === null) {
|
|
4017
|
+
skippedFiles += LINE_NUMBER_OFFSET;
|
|
4018
|
+
if (!moreFilesRemain) break;
|
|
4019
|
+
if (remainingRegexTime <= 0) {
|
|
4020
|
+
deadlineReached = true;
|
|
4021
|
+
break;
|
|
4022
|
+
}
|
|
4023
|
+
await regexWorker.close();
|
|
4024
|
+
try {
|
|
4025
|
+
regexWorker = await RegexSearchWorker.create(query);
|
|
4026
|
+
} catch (error) {
|
|
4027
|
+
logger.warn(
|
|
4028
|
+
{ error, file },
|
|
4029
|
+
"searchFiles: regex worker failed to restart"
|
|
4030
|
+
);
|
|
4031
|
+
workerFailed = true;
|
|
4032
|
+
break;
|
|
4033
|
+
}
|
|
4034
|
+
continue;
|
|
4035
|
+
}
|
|
4036
|
+
for (const { lineIndex, text } of locations) {
|
|
4037
|
+
const lineNumber = lineIndex + LINE_NUMBER_OFFSET;
|
|
4038
|
+
const match = `${file}:${lineNumber}:${text}`;
|
|
4039
|
+
const nextResultCharacters = resultCharacters + match.length + NEWLINE_CHARACTER_COUNT;
|
|
4040
|
+
if (nextResultCharacters > MAX_RESULT_CHARACTERS) {
|
|
4041
|
+
matches.push(
|
|
4042
|
+
`... (truncated at ${MAX_RESULT_CHARACTERS} characters)`
|
|
4043
|
+
);
|
|
4044
|
+
return formatSearchResult(
|
|
4045
|
+
matches,
|
|
4046
|
+
skippedFiles,
|
|
4047
|
+
deadlineReached,
|
|
4048
|
+
workerFailed
|
|
4049
|
+
);
|
|
4050
|
+
}
|
|
4051
|
+
matches.push(match);
|
|
4052
|
+
resultCharacters = nextResultCharacters;
|
|
4053
|
+
if (matches.length >= ctx.limits.match) {
|
|
4054
|
+
return formatSearchResult(
|
|
4055
|
+
matches,
|
|
4056
|
+
skippedFiles,
|
|
4057
|
+
deadlineReached,
|
|
4058
|
+
workerFailed,
|
|
4059
|
+
`... (truncated at ${ctx.limits.match} matches)`
|
|
4060
|
+
);
|
|
4061
|
+
}
|
|
3631
4062
|
}
|
|
3632
4063
|
}
|
|
4064
|
+
} catch (error) {
|
|
4065
|
+
logger.warn({ error }, "searchFiles: safe file scan failed");
|
|
4066
|
+
return "Refused: Could not list searchable files safely. Fix file access and retry.";
|
|
3633
4067
|
}
|
|
4068
|
+
} finally {
|
|
4069
|
+
await regexWorker.close();
|
|
3634
4070
|
}
|
|
3635
|
-
return
|
|
4071
|
+
return formatSearchResult(
|
|
4072
|
+
matches,
|
|
4073
|
+
skippedFiles,
|
|
4074
|
+
deadlineReached,
|
|
4075
|
+
workerFailed
|
|
4076
|
+
);
|
|
3636
4077
|
}
|
|
3637
4078
|
});
|
|
3638
4079
|
}
|
|
@@ -3641,14 +4082,14 @@ function searchFilesTool(ctx) {
|
|
|
3641
4082
|
import { tool as tool9, generateText, Output } from "ai";
|
|
3642
4083
|
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
3643
4084
|
import z16 from "zod";
|
|
3644
|
-
import { relative as
|
|
4085
|
+
import { relative as relative6 } from "node:path";
|
|
3645
4086
|
|
|
3646
4087
|
// src/lib/tools/reviewScript.ts
|
|
3647
4088
|
import { tool as tool8 } from "ai";
|
|
3648
4089
|
import z15 from "zod";
|
|
3649
4090
|
import { createHash as createHash2 } from "node:crypto";
|
|
3650
|
-
import { readFile as readFile8, stat as
|
|
3651
|
-
import { basename as basename2, relative as
|
|
4091
|
+
import { readFile as readFile8, stat as stat3 } from "node:fs/promises";
|
|
4092
|
+
import { basename as basename2, relative as relative5, resolve as resolve3 } from "node:path";
|
|
3652
4093
|
|
|
3653
4094
|
// src/lib/tools/utils/prompt.ts
|
|
3654
4095
|
var chain = Promise.resolve();
|
|
@@ -3747,7 +4188,7 @@ function commandDiffersFromReviewedScript(ctx, command, cwd) {
|
|
|
3747
4188
|
if (approval.command === command) return true;
|
|
3748
4189
|
const scriptReferences = [
|
|
3749
4190
|
basename2(approval.filePath),
|
|
3750
|
-
|
|
4191
|
+
relative5(ctx.root, approval.filePath),
|
|
3751
4192
|
".algolia-wizard"
|
|
3752
4193
|
];
|
|
3753
4194
|
return scriptReferences.some(
|
|
@@ -3796,19 +4237,19 @@ function reviewScriptTool(ctx) {
|
|
|
3796
4237
|
if (resolved2.ok === false) return resolved2.error;
|
|
3797
4238
|
const resolvedCwd = resolveInRoot(ctx, cwd);
|
|
3798
4239
|
if (resolvedCwd.ok === false) return resolvedCwd.error;
|
|
3799
|
-
const displayCwd =
|
|
3800
|
-
const displayFilePath =
|
|
4240
|
+
const displayCwd = relative5(ctx.root, resolvedCwd.target) || ".";
|
|
4241
|
+
const displayFilePath = relative5(ctx.root, resolved2.target);
|
|
3801
4242
|
const ingestionSummary = usesGeneratedRecords ? `This will push ${entityName} records from generated sample data to Algolia. You can replace the sample dataset with your real data source in ${displayFilePath}.` : `This will push ${entityName} records from your selected source to Algolia.`;
|
|
3802
4243
|
return serializePrompt(async () => {
|
|
3803
4244
|
revokeCommandApprovals(ctx, command, resolvedCwd.target);
|
|
3804
|
-
const isFile = await
|
|
4245
|
+
const isFile = await stat3(resolved2.target).then(
|
|
3805
4246
|
(s) => s.isFile(),
|
|
3806
4247
|
() => false
|
|
3807
4248
|
);
|
|
3808
4249
|
if (!isFile) {
|
|
3809
4250
|
return `Refused: ${filePath} is not a file. Write the script first, then review the path you wrote.`;
|
|
3810
4251
|
}
|
|
3811
|
-
const relativeScriptPath =
|
|
4252
|
+
const relativeScriptPath = relative5(resolvedCwd.target, resolved2.target);
|
|
3812
4253
|
if (!commandReferencesScript(
|
|
3813
4254
|
command,
|
|
3814
4255
|
resolved2.target,
|
|
@@ -3968,7 +4409,7 @@ function storeApproval(root) {
|
|
|
3968
4409
|
return async (req) => {
|
|
3969
4410
|
const store = useWizard.getState();
|
|
3970
4411
|
if (store.isCommandApproved(req.command, req.cwd)) return "approve";
|
|
3971
|
-
const rel =
|
|
4412
|
+
const rel = relative6(root, req.cwd);
|
|
3972
4413
|
const answer = await store.requestUserInput({
|
|
3973
4414
|
prompt: "Run this command?",
|
|
3974
4415
|
promptType: "commandApproval",
|
|
@@ -4194,8 +4635,8 @@ function defaultCreateModel() {
|
|
|
4194
4635
|
}
|
|
4195
4636
|
function approvedCommandHistory(approvedCommands) {
|
|
4196
4637
|
return Array.from(approvedCommands).map((entry) => {
|
|
4197
|
-
const
|
|
4198
|
-
return { cwd: entry.slice(0,
|
|
4638
|
+
const sep4 = entry.indexOf("\0");
|
|
4639
|
+
return { cwd: entry.slice(0, sep4), command: entry.slice(sep4 + 1) };
|
|
4199
4640
|
});
|
|
4200
4641
|
}
|
|
4201
4642
|
async function classifyCommandSafety(createModel, command, cwd, explanation, approvedHistory) {
|
|
@@ -4312,14 +4753,14 @@ async function runReviewedCommand(ctx, command, cwd) {
|
|
|
4312
4753
|
const approval = getReviewedCommandApproval(ctx, command, cwd);
|
|
4313
4754
|
if (!approval) return void 0;
|
|
4314
4755
|
if (!await reviewedCommandApprovalIsCurrent(approval)) {
|
|
4315
|
-
return `Refused: ${
|
|
4756
|
+
return `Refused: ${relative6(ctx.root, approval.filePath)} changed after review. Call reviewScript again before running it.`;
|
|
4316
4757
|
}
|
|
4317
4758
|
const [env, secrets] = await Promise.all([
|
|
4318
4759
|
resolveShellEnv(ctx, command),
|
|
4319
4760
|
resolveSecretShellEnv(ctx, command)
|
|
4320
4761
|
]);
|
|
4321
4762
|
if (!await reviewedCommandApprovalIsCurrent(approval)) {
|
|
4322
|
-
return `Refused: ${
|
|
4763
|
+
return `Refused: ${relative6(ctx.root, approval.filePath)} changed after review. Call reviewScript again before running it.`;
|
|
4323
4764
|
}
|
|
4324
4765
|
const result = await runAndRecord(
|
|
4325
4766
|
ctx,
|
|
@@ -4923,10 +5364,13 @@ var MODE_CONFIG = {
|
|
|
4923
5364
|
ingestion: {
|
|
4924
5365
|
instructions: [
|
|
4925
5366
|
"Analyze the codebase to find the data entities (models) that should be ingested into Algolia.",
|
|
5367
|
+
"Assume this is a new Algolia customer project. Existing Algolia dependencies, credentials, indices, and integration code are not required.",
|
|
5368
|
+
"This workflow creates the Algolia integration, so its absence is expected and is not a failure.",
|
|
5369
|
+
"Infer entity candidates from domain models, database schemas, data-access code, API handlers, and representative application data.",
|
|
4926
5370
|
"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).",
|
|
4927
5371
|
"Inspect the source of each entity to extract real field names for attributes \u2014 do not guess or leave attributes empty.",
|
|
4928
5372
|
"Prefer domain models (e.g. Document, Product, User) over framework or infrastructure types.",
|
|
4929
|
-
"Use as few tools as possible, but do not guess. If
|
|
5373
|
+
"Use as few tools as possible, but do not guess. If no domain entities exist, report success with an empty array.",
|
|
4930
5374
|
"Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
|
|
4931
5375
|
"When done, call reportStatus"
|
|
4932
5376
|
],
|
|
@@ -5389,16 +5833,16 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
|
|
|
5389
5833
|
// src/actions/implement.ts
|
|
5390
5834
|
import z28 from "zod";
|
|
5391
5835
|
import { access, mkdir as mkdir5, readFile as readFile9 } from "node:fs/promises";
|
|
5392
|
-
import { dirname as dirname5, isAbsolute as
|
|
5836
|
+
import { dirname as dirname5, isAbsolute as isAbsolute4, join as join11, relative as relative7, resolve as resolve5, sep as sep3 } from "node:path";
|
|
5393
5837
|
|
|
5394
5838
|
// src/lib/git.ts
|
|
5395
|
-
import { execFile as
|
|
5396
|
-
import { copyFile, mkdir as mkdir4, stat as
|
|
5397
|
-
import { basename as basename3, dirname as dirname3, isAbsolute as
|
|
5839
|
+
import { execFile as execFile3 } from "node:child_process";
|
|
5840
|
+
import { copyFile, mkdir as mkdir4, stat as stat4 } from "node:fs/promises";
|
|
5841
|
+
import { basename as basename3, dirname as dirname3, isAbsolute as isAbsolute3, join as join9, resolve as resolve4 } from "node:path";
|
|
5398
5842
|
var MAX_BUFFER = 32 * 1024 * 1024;
|
|
5399
5843
|
function git(args) {
|
|
5400
5844
|
return new Promise((resolve6, reject) => {
|
|
5401
|
-
|
|
5845
|
+
execFile3("git", args, { maxBuffer: MAX_BUFFER }, (err, stdout, stderr) => {
|
|
5402
5846
|
if (err)
|
|
5403
5847
|
return reject(
|
|
5404
5848
|
new Error(
|
|
@@ -5423,9 +5867,9 @@ async function copyUploadIntoProject(repoRoot, ingestDir, sourcePath) {
|
|
|
5423
5867
|
if (!trimmed) {
|
|
5424
5868
|
return { ok: false, reason: "no file path was provided" };
|
|
5425
5869
|
}
|
|
5426
|
-
const source =
|
|
5870
|
+
const source = isAbsolute3(trimmed) ? trimmed : resolve4(repoRoot, trimmed);
|
|
5427
5871
|
try {
|
|
5428
|
-
if (!(await
|
|
5872
|
+
if (!(await stat4(source)).isFile()) {
|
|
5429
5873
|
return { ok: false, reason: `"${sourcePath}" is not a file` };
|
|
5430
5874
|
}
|
|
5431
5875
|
} catch {
|
|
@@ -5819,7 +6263,7 @@ function parseIngestRecordCount(output) {
|
|
|
5819
6263
|
return Number.isFinite(count) ? count : void 0;
|
|
5820
6264
|
}
|
|
5821
6265
|
function relativeIngestCwd(execution, repoRoot) {
|
|
5822
|
-
const cwd =
|
|
6266
|
+
const cwd = relative7(repoRoot, execution.cwd);
|
|
5823
6267
|
return cwd && cwd !== "." ? cwd : ".";
|
|
5824
6268
|
}
|
|
5825
6269
|
function reportedCommandWithCwdNote(execution, repoRoot) {
|
|
@@ -5936,8 +6380,8 @@ async function frontendHasPackageJson(repoRoot, searchLocation) {
|
|
|
5936
6380
|
let directory = repoRoot;
|
|
5937
6381
|
if (searchLocation) {
|
|
5938
6382
|
const candidate = dirname5(resolve5(repoRoot, searchLocation));
|
|
5939
|
-
const candidateRelative =
|
|
5940
|
-
const outsideRoot = candidateRelative === ".." || candidateRelative.startsWith(`..${
|
|
6383
|
+
const candidateRelative = relative7(repoRoot, candidate);
|
|
6384
|
+
const outsideRoot = candidateRelative === ".." || candidateRelative.startsWith(`..${sep3}`) || isAbsolute4(candidateRelative);
|
|
5941
6385
|
if (!outsideRoot) directory = candidate;
|
|
5942
6386
|
}
|
|
5943
6387
|
for (; ; ) {
|
|
@@ -6169,7 +6613,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
|
|
|
6169
6613
|
if (agentRuns > 0) ctx.recordStepExecution();
|
|
6170
6614
|
agentRuns += 1;
|
|
6171
6615
|
const writtenFiles = [...new Set(useWizard.getState().writtenFiles)].map(
|
|
6172
|
-
(file) =>
|
|
6616
|
+
(file) => relative7(repoRoot, file)
|
|
6173
6617
|
);
|
|
6174
6618
|
return runAgent({
|
|
6175
6619
|
operation: "search-validation",
|
|
@@ -6309,7 +6753,7 @@ ${detail}` : ""}`
|
|
|
6309
6753
|
const trackComponentGenerated = (mounted_successfully, attempts) => {
|
|
6310
6754
|
const searchFilesChanged = [
|
|
6311
6755
|
...new Set(useWizard.getState().writtenFiles)
|
|
6312
|
-
].map((file) =>
|
|
6756
|
+
].map((file) => relative7(repoRoot, file));
|
|
6313
6757
|
track("AI Wizard Frontend Component Generated", {
|
|
6314
6758
|
file_paths: searchFilesChanged,
|
|
6315
6759
|
framework,
|
|
@@ -6344,7 +6788,7 @@ ${detail}` : ""}`
|
|
|
6344
6788
|
buildCheckPlan ??= await resolveBuildCheckPlan(ctx, repoRoot, input);
|
|
6345
6789
|
const searchFilesChanged = [
|
|
6346
6790
|
...new Set(useWizard.getState().writtenFiles)
|
|
6347
|
-
].map((file) =>
|
|
6791
|
+
].map((file) => relative7(repoRoot, file));
|
|
6348
6792
|
const validation = await runValidationUseCase(
|
|
6349
6793
|
validationRunInstructions(buildCheckPlan, searchFilesChanged, attempt)
|
|
6350
6794
|
);
|
|
@@ -6435,17 +6879,12 @@ var defaultWorkflow = {
|
|
|
6435
6879
|
id: "default",
|
|
6436
6880
|
title: "Default Workflow",
|
|
6437
6881
|
description: "This workflow explores your repo and implements Algolia on your behalf",
|
|
6882
|
+
tips: true,
|
|
6438
6883
|
steps: [
|
|
6439
6884
|
defineStep({
|
|
6440
6885
|
id: "project-scan",
|
|
6441
6886
|
title: "project scan",
|
|
6442
6887
|
outputSchema: projectScanSchema,
|
|
6443
|
-
tips: [
|
|
6444
|
-
"apps-and-indices",
|
|
6445
|
-
"records-are-json",
|
|
6446
|
-
"speed",
|
|
6447
|
-
"searchable-attributes"
|
|
6448
|
-
],
|
|
6449
6888
|
run: (ctx) => {
|
|
6450
6889
|
ctx.notify({
|
|
6451
6890
|
messages: [
|
|
@@ -6481,7 +6920,6 @@ var defaultWorkflow = {
|
|
|
6481
6920
|
id: "ingestion",
|
|
6482
6921
|
title: "ingest records",
|
|
6483
6922
|
outputSchema: implementSchema,
|
|
6484
|
-
tips: ["facets", "test-relevance", "search-analytics", "recommend"],
|
|
6485
6923
|
run: (ctx) => {
|
|
6486
6924
|
ctx.notify({
|
|
6487
6925
|
messages: [
|
|
@@ -6502,7 +6940,6 @@ var defaultWorkflow = {
|
|
|
6502
6940
|
id: "search",
|
|
6503
6941
|
title: "create search ui",
|
|
6504
6942
|
outputSchema: implementSchema,
|
|
6505
|
-
tips: ["typo-tolerance", "synonyms", "multi-index-search", "rules"],
|
|
6506
6943
|
run: (ctx) => {
|
|
6507
6944
|
ctx.notify({
|
|
6508
6945
|
messages: ["Building your Algolia search experience\u2026"]
|
|
@@ -6513,6 +6950,7 @@ var defaultWorkflow = {
|
|
|
6513
6950
|
defineStep({
|
|
6514
6951
|
id: "review",
|
|
6515
6952
|
title: "done",
|
|
6953
|
+
hideTips: true,
|
|
6516
6954
|
outputSchema: reviewSchema,
|
|
6517
6955
|
run: (ctx) => {
|
|
6518
6956
|
ctx.notify({
|
|
@@ -7175,7 +7613,6 @@ var playbookWorkflow = {
|
|
|
7175
7613
|
id: "select-index",
|
|
7176
7614
|
title: "Select index",
|
|
7177
7615
|
outputSchema: selectIndexSchema,
|
|
7178
|
-
tips: ["apps-and-indices", "records-are-json", "speed"],
|
|
7179
7616
|
run: (ctx) => selectIndexStep2(ctx)
|
|
7180
7617
|
}),
|
|
7181
7618
|
defineStep({
|
|
@@ -7183,7 +7620,6 @@ var playbookWorkflow = {
|
|
|
7183
7620
|
title: "Confirm sample index creation",
|
|
7184
7621
|
outputSchema: confirmIndexSchema,
|
|
7185
7622
|
visible: false,
|
|
7186
|
-
tips: ["apps-and-indices", "records-are-json", "speed"],
|
|
7187
7623
|
run: (ctx) => confirmIndexStep(ctx)
|
|
7188
7624
|
}),
|
|
7189
7625
|
defineStep({
|
|
@@ -7788,7 +8224,6 @@ function Code({ children }) {
|
|
|
7788
8224
|
// src/ui/copy/tips.ts
|
|
7789
8225
|
var tips = [
|
|
7790
8226
|
{
|
|
7791
|
-
id: "apps-and-indices",
|
|
7792
8227
|
title: "An Application is like your library. Indices are the books in it.",
|
|
7793
8228
|
chunks: [
|
|
7794
8229
|
{
|
|
@@ -7802,7 +8237,6 @@ var tips = [
|
|
|
7802
8237
|
]
|
|
7803
8238
|
},
|
|
7804
8239
|
{
|
|
7805
|
-
id: "records-are-json",
|
|
7806
8240
|
title: "A Record is a JSON object inside of an index.",
|
|
7807
8241
|
chunks: [
|
|
7808
8242
|
{
|
|
@@ -7836,7 +8270,6 @@ var tips = [
|
|
|
7836
8270
|
]
|
|
7837
8271
|
},
|
|
7838
8272
|
{
|
|
7839
|
-
id: "speed",
|
|
7840
8273
|
title: "Speed isn't a feature, it's our business",
|
|
7841
8274
|
chunks: [
|
|
7842
8275
|
{
|
|
@@ -7850,7 +8283,6 @@ var tips = [
|
|
|
7850
8283
|
]
|
|
7851
8284
|
},
|
|
7852
8285
|
{
|
|
7853
|
-
id: "searchable-attributes",
|
|
7854
8286
|
title: "Searchable attributes are your relevance dial #1",
|
|
7855
8287
|
chunks: [
|
|
7856
8288
|
{
|
|
@@ -7866,7 +8298,6 @@ var tips = [
|
|
|
7866
8298
|
]
|
|
7867
8299
|
},
|
|
7868
8300
|
{
|
|
7869
|
-
id: "facets",
|
|
7870
8301
|
title: "Facets aren't just filters, they help build your UI",
|
|
7871
8302
|
chunks: [
|
|
7872
8303
|
{ type: "codeword", value: "attributesForFaceting" },
|
|
@@ -7877,7 +8308,6 @@ var tips = [
|
|
|
7877
8308
|
]
|
|
7878
8309
|
},
|
|
7879
8310
|
{
|
|
7880
|
-
id: "test-relevance",
|
|
7881
8311
|
title: "Test relevance in the dashboard before you write a line of ranking code",
|
|
7882
8312
|
chunks: [
|
|
7883
8313
|
{
|
|
@@ -7896,7 +8326,6 @@ var tips = [
|
|
|
7896
8326
|
]
|
|
7897
8327
|
},
|
|
7898
8328
|
{
|
|
7899
|
-
id: "search-analytics",
|
|
7900
8329
|
title: "Search Analytics helps you discover opportunities",
|
|
7901
8330
|
chunks: [
|
|
7902
8331
|
{
|
|
@@ -7910,7 +8339,6 @@ var tips = [
|
|
|
7910
8339
|
]
|
|
7911
8340
|
},
|
|
7912
8341
|
{
|
|
7913
|
-
id: "typo-tolerance",
|
|
7914
8342
|
title: "Your users will misspell things. Algolia already handles it.",
|
|
7915
8343
|
chunks: [
|
|
7916
8344
|
{
|
|
@@ -7924,7 +8352,6 @@ var tips = [
|
|
|
7924
8352
|
]
|
|
7925
8353
|
},
|
|
7926
8354
|
{
|
|
7927
|
-
id: "synonyms",
|
|
7928
8355
|
title: "Synonyms teach the engine your words",
|
|
7929
8356
|
chunks: [
|
|
7930
8357
|
{
|
|
@@ -7942,7 +8369,6 @@ var tips = [
|
|
|
7942
8369
|
]
|
|
7943
8370
|
},
|
|
7944
8371
|
{
|
|
7945
|
-
id: "multi-index-search",
|
|
7946
8372
|
title: "One search box, many indices",
|
|
7947
8373
|
chunks: [
|
|
7948
8374
|
{
|
|
@@ -7956,7 +8382,6 @@ var tips = [
|
|
|
7956
8382
|
]
|
|
7957
8383
|
},
|
|
7958
8384
|
{
|
|
7959
|
-
id: "rules",
|
|
7960
8385
|
title: "Rules let you put a thumb on the scale",
|
|
7961
8386
|
chunks: [
|
|
7962
8387
|
{
|
|
@@ -7974,7 +8399,6 @@ var tips = [
|
|
|
7974
8399
|
]
|
|
7975
8400
|
},
|
|
7976
8401
|
{
|
|
7977
|
-
id: "recommend",
|
|
7978
8402
|
title: "Let Algolia act as your recommendation engine",
|
|
7979
8403
|
chunks: [
|
|
7980
8404
|
{
|
|
@@ -7988,18 +8412,12 @@ var tips = [
|
|
|
7988
8412
|
]
|
|
7989
8413
|
}
|
|
7990
8414
|
];
|
|
7991
|
-
var tipsById = Object.fromEntries(
|
|
7992
|
-
tips.map((tip) => [tip.id, tip])
|
|
7993
|
-
);
|
|
7994
|
-
function tipsByIds(ids) {
|
|
7995
|
-
return ids.map((id) => tipsById[id]).filter((tip) => tip !== void 0);
|
|
7996
|
-
}
|
|
7997
8415
|
|
|
7998
8416
|
// src/ui/Tips.tsx
|
|
7999
8417
|
import { jsx as jsx20, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
8000
8418
|
var TICK_MS = 16;
|
|
8001
8419
|
var CHUNK_HOLD_MS = 2e3;
|
|
8002
|
-
var
|
|
8420
|
+
var TIP_DURATION_MS = 25e3;
|
|
8003
8421
|
function TipTitle({ children }) {
|
|
8004
8422
|
return /* @__PURE__ */ jsxs19(Box21, { gap: 1, children: [
|
|
8005
8423
|
/* @__PURE__ */ jsx20(Text22, { color: "cyan", children: "\u2726" }),
|
|
@@ -8056,45 +8474,53 @@ function TipContent({
|
|
|
8056
8474
|
] }, j)) }, i)
|
|
8057
8475
|
) });
|
|
8058
8476
|
}
|
|
8059
|
-
var
|
|
8477
|
+
var noPauseAfterText = [
|
|
8478
|
+
"highlight",
|
|
8479
|
+
"link",
|
|
8480
|
+
"codeword"
|
|
8481
|
+
];
|
|
8482
|
+
function revealSchedule(tip) {
|
|
8483
|
+
const segments = [
|
|
8484
|
+
{ type: "text", value: tip.title },
|
|
8485
|
+
...tip.chunks
|
|
8486
|
+
];
|
|
8487
|
+
const schedule = [];
|
|
8488
|
+
let at = 0;
|
|
8489
|
+
segments.forEach((segment, i) => {
|
|
8490
|
+
for (let c = 0; c < segment.value.length; c++) {
|
|
8491
|
+
const pause = i > 0 && c === 0 && !noPauseAfterText.includes(segment.type);
|
|
8492
|
+
at += pause ? CHUNK_HOLD_MS : TICK_MS;
|
|
8493
|
+
schedule.push(at);
|
|
8494
|
+
}
|
|
8495
|
+
});
|
|
8496
|
+
return schedule;
|
|
8497
|
+
}
|
|
8498
|
+
var clock = { tipIndex: 0, elapsedMs: 0, complete: false };
|
|
8060
8499
|
function Tips({
|
|
8061
|
-
|
|
8062
|
-
|
|
8063
|
-
onRevealStateChange
|
|
8500
|
+
onRevealStateChange,
|
|
8501
|
+
onComplete
|
|
8064
8502
|
}) {
|
|
8065
|
-
const
|
|
8066
|
-
|
|
8067
|
-
|
|
8068
|
-
|
|
8069
|
-
|
|
8070
|
-
|
|
8071
|
-
|
|
8072
|
-
|
|
8073
|
-
|
|
8074
|
-
const tip =
|
|
8075
|
-
const
|
|
8076
|
-
const
|
|
8077
|
-
const
|
|
8078
|
-
|
|
8079
|
-
|
|
8080
|
-
|
|
8081
|
-
const
|
|
8082
|
-
"highlight",
|
|
8083
|
-
"link",
|
|
8084
|
-
"codeword"
|
|
8085
|
-
];
|
|
8086
|
-
const chunkBoundaries = [];
|
|
8087
|
-
let cumulative = 0;
|
|
8088
|
-
for (let i = 0; i < segments.length - 1; i++) {
|
|
8089
|
-
cumulative += segments[i].length;
|
|
8090
|
-
const skipPause = noPauseAfterText.includes(
|
|
8091
|
-
segments[i + 1].type
|
|
8092
|
-
);
|
|
8093
|
-
if (!skipPause) chunkBoundaries.push(cumulative);
|
|
8094
|
-
}
|
|
8095
|
-
const revealState = tips2.length === 0 ? "idle" : revealed < totalLength ? "revealing" : "revealed";
|
|
8503
|
+
const [position, setPosition] = useState11(() => {
|
|
8504
|
+
const now = Date.now();
|
|
8505
|
+
return {
|
|
8506
|
+
tipIndex: clock.tipIndex,
|
|
8507
|
+
startedAt: now - clock.elapsedMs,
|
|
8508
|
+
now,
|
|
8509
|
+
complete: clock.complete
|
|
8510
|
+
};
|
|
8511
|
+
});
|
|
8512
|
+
const tip = tips[position.tipIndex];
|
|
8513
|
+
const elapsedMs = position.now - position.startedAt;
|
|
8514
|
+
const schedule = revealSchedule(tip);
|
|
8515
|
+
const revealed = schedule.filter((at) => at <= elapsedMs).length;
|
|
8516
|
+
const fullyTyped = revealed === schedule.length;
|
|
8517
|
+
const nextAt = fullyTyped ? TIP_DURATION_MS : schedule[revealed];
|
|
8518
|
+
const midSentence = !fullyTyped && revealed > 0 && nextAt - schedule[revealed - 1] === TICK_MS;
|
|
8519
|
+
const revealState = position.complete ? "idle" : midSentence ? "typing" : "resting";
|
|
8096
8520
|
const onRevealStateChangeRef = useRef4(onRevealStateChange);
|
|
8097
8521
|
onRevealStateChangeRef.current = onRevealStateChange;
|
|
8522
|
+
const onCompleteRef = useRef4(onComplete);
|
|
8523
|
+
onCompleteRef.current = onComplete;
|
|
8098
8524
|
const reportedRef = useRef4(null);
|
|
8099
8525
|
useEffect3(() => {
|
|
8100
8526
|
if (reportedRef.current === revealState) return;
|
|
@@ -8103,30 +8529,35 @@ function Tips({
|
|
|
8103
8529
|
}, [revealState]);
|
|
8104
8530
|
useEffect3(() => () => onRevealStateChangeRef.current?.("idle"), []);
|
|
8105
8531
|
useEffect3(() => {
|
|
8106
|
-
if (
|
|
8107
|
-
|
|
8108
|
-
|
|
8109
|
-
const timer2 = setTimeout(() => {
|
|
8110
|
-
setRevealed((r) => {
|
|
8111
|
-
const next = r + 1;
|
|
8112
|
-
progress.revealed = next;
|
|
8113
|
-
return next;
|
|
8114
|
-
});
|
|
8115
|
-
}, delay2);
|
|
8116
|
-
return () => clearTimeout(timer2);
|
|
8532
|
+
if (position.complete) {
|
|
8533
|
+
onCompleteRef.current?.();
|
|
8534
|
+
return;
|
|
8117
8535
|
}
|
|
8118
8536
|
const timer = setTimeout(() => {
|
|
8119
|
-
|
|
8120
|
-
|
|
8121
|
-
|
|
8122
|
-
|
|
8123
|
-
|
|
8124
|
-
|
|
8125
|
-
|
|
8126
|
-
|
|
8127
|
-
|
|
8128
|
-
|
|
8129
|
-
|
|
8537
|
+
const now = Date.now();
|
|
8538
|
+
const tipComplete = now - position.startedAt >= TIP_DURATION_MS;
|
|
8539
|
+
if (tipComplete && position.tipIndex === tips.length - 1) {
|
|
8540
|
+
clock.complete = true;
|
|
8541
|
+
setPosition({ ...position, now, complete: true });
|
|
8542
|
+
return;
|
|
8543
|
+
}
|
|
8544
|
+
setPosition(
|
|
8545
|
+
tipComplete ? {
|
|
8546
|
+
tipIndex: position.tipIndex + 1,
|
|
8547
|
+
startedAt: now,
|
|
8548
|
+
now,
|
|
8549
|
+
complete: false
|
|
8550
|
+
} : { ...position, now }
|
|
8551
|
+
);
|
|
8552
|
+
}, nextAt - elapsedMs);
|
|
8553
|
+
return () => {
|
|
8554
|
+
clearTimeout(timer);
|
|
8555
|
+
if (clock.complete) return;
|
|
8556
|
+
clock.tipIndex = position.tipIndex;
|
|
8557
|
+
clock.elapsedMs = Date.now() - position.startedAt;
|
|
8558
|
+
};
|
|
8559
|
+
}, [position, nextAt, elapsedMs]);
|
|
8560
|
+
if (position.complete) return null;
|
|
8130
8561
|
const titleRevealed = tip.title.slice(0, revealed);
|
|
8131
8562
|
const contentRevealed = Math.max(0, revealed - tip.title.length);
|
|
8132
8563
|
return /* @__PURE__ */ jsxs19(Box21, { flexDirection: "column", marginBottom: 1, gap: 1, children: [
|
|
@@ -8194,17 +8625,16 @@ function App() {
|
|
|
8194
8625
|
const compactRun = isCompactTerminal(columns, rows);
|
|
8195
8626
|
const [showLogs, setShowLogs] = useState12(false);
|
|
8196
8627
|
const [tipState, setTipState] = useState12("idle");
|
|
8628
|
+
const [tipsComplete, setTipsComplete] = useState12(false);
|
|
8197
8629
|
const finished = phase === "done" || phase === "error";
|
|
8198
8630
|
const currentStep = steps[currentStepIndex];
|
|
8199
|
-
const
|
|
8200
|
-
const
|
|
8201
|
-
const
|
|
8631
|
+
const workflowDef = workflow ? getWorkflow(workflow.id) : void 0;
|
|
8632
|
+
const stepHidesTips = workflowDef?.steps.find((s) => s.id === currentStep?.id)?.hideTips === true;
|
|
8633
|
+
const tipsActive = workflowDef?.tips === true && !tipsComplete && !stepHidesTips;
|
|
8202
8634
|
const isAwaitingUserInput = phase === "awaitingInput" && !!inputReq;
|
|
8203
|
-
const
|
|
8204
|
-
const
|
|
8205
|
-
const
|
|
8206
|
-
const showTips = !compactRun && stepHasTips && (phase === "running" && currentStep?.status === "running" && !loadingMessage && !settingUpAppId || isCommandApprovalPrompt);
|
|
8207
|
-
const showNoticesInMain = !stepHasTips;
|
|
8635
|
+
const holdForTip = !compactRun && tipsActive && isAwaitingUserInput && tipState === "typing";
|
|
8636
|
+
const showTips = !compactRun && tipsActive && (phase === "running" && currentStep?.status === "running" && !loadingMessage && !settingUpAppId || holdForTip);
|
|
8637
|
+
const showNoticesInMain = !tipsActive;
|
|
8208
8638
|
const showNotices = !compactRun && (!isAwaitingUserInput || holdForTip) && !review2;
|
|
8209
8639
|
useInput9(
|
|
8210
8640
|
(_input, key) => {
|
|
@@ -8299,12 +8729,11 @@ function App() {
|
|
|
8299
8729
|
loadingMessage
|
|
8300
8730
|
] }) }),
|
|
8301
8731
|
/* @__PURE__ */ jsx21(CliOutput, {}),
|
|
8302
|
-
showTips &&
|
|
8732
|
+
showTips && /* @__PURE__ */ jsx21(
|
|
8303
8733
|
Tips,
|
|
8304
8734
|
{
|
|
8305
|
-
|
|
8306
|
-
|
|
8307
|
-
onRevealStateChange: setTipState
|
|
8735
|
+
onRevealStateChange: setTipState,
|
|
8736
|
+
onComplete: () => setTipsComplete(true)
|
|
8308
8737
|
}
|
|
8309
8738
|
),
|
|
8310
8739
|
showNoticesInMain && showNotices && /* @__PURE__ */ jsx21(Notices, { showAll: true, border: false }),
|
|
@@ -8315,8 +8744,7 @@ function App() {
|
|
|
8315
8744
|
error
|
|
8316
8745
|
] }) })
|
|
8317
8746
|
] }),
|
|
8318
|
-
!showNoticesInMain && showNotices && /* @__PURE__ */ jsx21(Notices, {})
|
|
8319
|
-
showTips && !holdForTip && /* @__PURE__ */ jsx21(PromptInput, {})
|
|
8747
|
+
!showNoticesInMain && showNotices && /* @__PURE__ */ jsx21(Notices, {})
|
|
8320
8748
|
]
|
|
8321
8749
|
}
|
|
8322
8750
|
),
|
|
@@ -8563,7 +8991,7 @@ async function resetProjectState() {
|
|
|
8563
8991
|
// package.json with { type: 'json' }
|
|
8564
8992
|
var package_default2 = {
|
|
8565
8993
|
name: "@algolia/wizard",
|
|
8566
|
-
version: "0.
|
|
8994
|
+
version: "0.82.0",
|
|
8567
8995
|
description: "Magically implement Algolia functionality in your codebase",
|
|
8568
8996
|
type: "module",
|
|
8569
8997
|
engines: {
|