@algolia/wizard 0.81.0 → 0.83.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 +495 -51
- 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.83.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
|
);
|
|
@@ -8547,7 +8991,7 @@ async function resetProjectState() {
|
|
|
8547
8991
|
// package.json with { type: 'json' }
|
|
8548
8992
|
var package_default2 = {
|
|
8549
8993
|
name: "@algolia/wizard",
|
|
8550
|
-
version: "0.
|
|
8994
|
+
version: "0.83.0",
|
|
8551
8995
|
description: "Magically implement Algolia functionality in your codebase",
|
|
8552
8996
|
type: "module",
|
|
8553
8997
|
engines: {
|