atoo-studio 0.0.2 → 0.0.4
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/README.github.md +8 -2
- package/README.md +7 -1
- package/README.npm.md +7 -1
- package/dist/src/config.d.ts +1 -2
- package/dist/src/config.js +1 -2
- package/dist/src/handlers/projects.js +61 -2
- package/dist/src/services/file-search.d.ts +59 -0
- package/dist/src/services/file-search.js +269 -0
- package/dist/src/services/git-ops.d.ts +8 -0
- package/dist/src/services/git-ops.js +33 -4
- package/dist/src/services/remote-git-ops.d.ts +4 -0
- package/dist/src/services/remote-git-ops.js +7 -4
- package/dist/src/services/ssh-manager.js +1 -2
- package/frontend/dist/assets/{_basePickBy-B-LibQ4-.js → _basePickBy-DVcFyvUy.js} +1 -1
- package/frontend/dist/assets/{_baseUniq-CprifHap.js → _baseUniq-BlaoUomR.js} +1 -1
- package/frontend/dist/assets/architecture-PBZL5I3N-BExHjBcm.js +1 -0
- package/frontend/dist/assets/{architectureDiagram-2XIMDMQ5-DiHPxX4j.js → architectureDiagram-2XIMDMQ5-BNx6-oUQ.js} +1 -1
- package/frontend/dist/assets/{blockDiagram-WCTKOSBZ-C40u_hLo.js → blockDiagram-WCTKOSBZ-B4T4UcRa.js} +1 -1
- package/frontend/dist/assets/{chunk-7E7YKBS2-7zRaOLjj.js → chunk-7E7YKBS2-NeXbzmbu.js} +1 -1
- package/frontend/dist/assets/{chunk-C72U2L5F-_JbQPbLN.js → chunk-C72U2L5F-B8CSWPSJ.js} +1 -1
- package/frontend/dist/assets/{chunk-EGIJ26TM-B--aFyPw.js → chunk-EGIJ26TM-CIih76L7.js} +1 -1
- package/frontend/dist/assets/{chunk-L3YUKLVL-C-DkZTMr.js → chunk-L3YUKLVL-ki-YiEHS.js} +1 -1
- package/frontend/dist/assets/{chunk-NQ4KR5QH-Bpu9FsM7.js → chunk-NQ4KR5QH-CotGwnjL.js} +1 -1
- package/frontend/dist/assets/{chunk-OZEHJAEY-CNNiJtG0.js → chunk-OZEHJAEY-BpNr8Rjz.js} +1 -1
- package/frontend/dist/assets/{chunk-R5LLSJPH-CHQzVVOV.js → chunk-R5LLSJPH-BijepqoK.js} +1 -1
- package/frontend/dist/assets/{chunk-WL4C6EOR-BNFU6IIi.js → chunk-WL4C6EOR-pSm7LSvh.js} +1 -1
- package/frontend/dist/assets/{chunk-XIRO2GV7-98T93G85.js → chunk-XIRO2GV7-DwBvqLWa.js} +1 -1
- package/frontend/dist/assets/{chunk-XZSTWKYB-BcW3cyNp.js → chunk-XZSTWKYB-CBqjoIm6.js} +1 -1
- package/frontend/dist/assets/{classDiagram-VBA2DB6C-DikXzgcD.js → classDiagram-VBA2DB6C-BFFlfFWc.js} +1 -1
- package/frontend/dist/assets/{classDiagram-v2-RAHNMMFH-D7E3tQUK.js → classDiagram-v2-RAHNMMFH-Zm4JHiZG.js} +1 -1
- package/frontend/dist/assets/clone-DTaWrNz3.js +1 -0
- package/frontend/dist/assets/{dagre-DH4bgZO7.js → dagre-DwFVI7Jd.js} +1 -1
- package/frontend/dist/assets/{dagre-KLK3FWXG-DNSqDkwT.js → dagre-KLK3FWXG-HvRFWYud.js} +1 -1
- package/frontend/dist/assets/{diagram-E7M64L7V-RqPNT5Vs.js → diagram-E7M64L7V-CQyHprzu.js} +1 -1
- package/frontend/dist/assets/{diagram-IFDJBPK2-B-5NRyaE.js → diagram-IFDJBPK2-CqkOPpOt.js} +1 -1
- package/frontend/dist/assets/{diagram-P4PSJMXO-BrP69Hk0.js → diagram-P4PSJMXO-OpXCvbKg.js} +1 -1
- package/frontend/dist/assets/{erDiagram-INFDFZHY-BYiB9NYg.js → erDiagram-INFDFZHY-eQioynMC.js} +1 -1
- package/frontend/dist/assets/{flowDiagram-PKNHOUZH-Cwq47rsR.js → flowDiagram-PKNHOUZH-hx7shOit.js} +1 -1
- package/frontend/dist/assets/gitGraph-HDMCJU4V-CcSU6vzN.js +1 -0
- package/frontend/dist/assets/{gitGraphDiagram-K3NZZRJ6-BaUxboNc.js → gitGraphDiagram-K3NZZRJ6-BUC-llGK.js} +1 -1
- package/frontend/dist/assets/{graphlib-kEFlkt3U.js → graphlib-ChWzv2kY.js} +1 -1
- package/frontend/dist/assets/index-B71Ng0qa.css +1 -0
- package/frontend/dist/assets/index-B8EBg0hm.js +157 -0
- package/frontend/dist/assets/info-3K5VOQVL-B3MsAb_d.js +1 -0
- package/frontend/dist/assets/infoDiagram-LFFYTUFH-BMFFvbZ3.js +2 -0
- package/frontend/dist/assets/{ishikawaDiagram-PHBUUO56-Bld4two_.js → ishikawaDiagram-PHBUUO56-B80zFWUg.js} +1 -1
- package/frontend/dist/assets/{kanban-definition-K7BYSVSG-DpgsZmpG.js → kanban-definition-K7BYSVSG-wvVdN2XJ.js} +1 -1
- package/frontend/dist/assets/{mermaid-parser.core-DAeTodBQ.js → mermaid-parser.core-fDWeedvo.js} +2 -2
- package/frontend/dist/assets/{mindmap-definition-YRQLILUH-CoNlFyVl.js → mindmap-definition-YRQLILUH-COI6teLb.js} +1 -1
- package/frontend/dist/assets/packet-RMMSAZCW-AtnYULi6.js +1 -0
- package/frontend/dist/assets/pie-UPGHQEXC-Cm4Nnlh1.js +1 -0
- package/frontend/dist/assets/{pieDiagram-SKSYHLDU-CM_hbCcn.js → pieDiagram-SKSYHLDU-bjwLIsJh.js} +1 -1
- package/frontend/dist/assets/radar-KQ55EAFF-D5ioamgd.js +1 -0
- package/frontend/dist/assets/{requirementDiagram-Z7DCOOCP-CorP7L7F.js → requirementDiagram-Z7DCOOCP-3UYkNoFC.js} +1 -1
- package/frontend/dist/assets/{sequenceDiagram-2WXFIKYE-JMqJSFq6.js → sequenceDiagram-2WXFIKYE-DCXcXQoE.js} +1 -1
- package/frontend/dist/assets/{stateDiagram-RAJIS63D-CgFfENdy.js → stateDiagram-RAJIS63D-DnpcJhHB.js} +1 -1
- package/frontend/dist/assets/{stateDiagram-v2-FVOUBMTO-C4Hh2P-U.js → stateDiagram-v2-FVOUBMTO-Db77rn81.js} +1 -1
- package/frontend/dist/assets/treemap-KZPCXAKY-BJS2z0TG.js +1 -0
- package/frontend/dist/assets/{vennDiagram-LZ73GAT5-DygS4Zzd.js → vennDiagram-LZ73GAT5-DpZs8KCD.js} +1 -1
- package/frontend/dist/assets/{xychartDiagram-JWTSCODW-D6wY1Jwd.js → xychartDiagram-JWTSCODW-CFVB_zoy.js} +1 -1
- package/frontend/dist/index.html +2 -2
- package/package.json +1 -1
- package/frontend/dist/assets/architecture-PBZL5I3N-hvVXGhqd.js +0 -1
- package/frontend/dist/assets/clone-mOXuZa7C.js +0 -1
- package/frontend/dist/assets/gitGraph-HDMCJU4V-COlTQ7bA.js +0 -1
- package/frontend/dist/assets/index-DMLxes_u.js +0 -157
- package/frontend/dist/assets/index-DmzeqkB1.css +0 -1
- package/frontend/dist/assets/info-3K5VOQVL-DBtHyA4C.js +0 -1
- package/frontend/dist/assets/infoDiagram-LFFYTUFH-yBXLgMPI.js +0 -2
- package/frontend/dist/assets/packet-RMMSAZCW-FF6-Tmai.js +0 -1
- package/frontend/dist/assets/pie-UPGHQEXC-CFvXY2o-.js +0 -1
- package/frontend/dist/assets/radar-KQ55EAFF-MPZu7SdX.js +0 -1
- package/frontend/dist/assets/treemap-KZPCXAKY-qb1Pl9la.js +0 -1
package/README.github.md
CHANGED
|
@@ -165,6 +165,12 @@ npx atoo-studio
|
|
|
165
165
|
|
|
166
166
|
Then open `https://localhost:3010` in your browser.
|
|
167
167
|
|
|
168
|
+
To use a different port, set the `ATOO_PORT` environment variable:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
ATOO_PORT=4000 npx atoo-studio
|
|
172
|
+
```
|
|
173
|
+
|
|
168
174
|
### Docker
|
|
169
175
|
|
|
170
176
|
```bash
|
|
@@ -193,13 +199,13 @@ Run one of the setup scripts on your Proxmox host:
|
|
|
193
199
|
**LXC container** — lightweight, 2 CPU / 2 GB RAM / 20 GB disk
|
|
194
200
|
|
|
195
201
|
```bash
|
|
196
|
-
bash -c "$(curl -fsSL https://raw.githubusercontent.com/atooai/atoo-studio/
|
|
202
|
+
bash -c "$(curl -fsSL https://raw.githubusercontent.com/atooai/atoo-studio/main/proxmox/lxc.sh)"
|
|
197
203
|
```
|
|
198
204
|
|
|
199
205
|
**VM** — stronger isolation, CUSE/serial support, 4 CPU / 4 GB RAM / 50 GB disk
|
|
200
206
|
|
|
201
207
|
```bash
|
|
202
|
-
bash -c "$(curl -fsSL https://raw.githubusercontent.com/atooai/atoo-studio/
|
|
208
|
+
bash -c "$(curl -fsSL https://raw.githubusercontent.com/atooai/atoo-studio/main/proxmox/vm.sh)"
|
|
203
209
|
```
|
|
204
210
|
|
|
205
211
|
Both scripts prompt for container or VM ID, hostname, storage, and resources with sensible defaults.
|
package/README.md
CHANGED
|
@@ -14,6 +14,12 @@ npx atoo-studio
|
|
|
14
14
|
|
|
15
15
|
Then open `https://localhost:3010` in your browser.
|
|
16
16
|
|
|
17
|
+
To use a different port:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
ATOO_PORT=4000 npx atoo-studio
|
|
21
|
+
```
|
|
22
|
+
|
|
17
23
|
## Install Globally
|
|
18
24
|
|
|
19
25
|
```bash
|
|
@@ -100,7 +106,7 @@ Atoo Studio exposes MCP tools for agent integration:
|
|
|
100
106
|
|
|
101
107
|
- **Docker**: `ghcr.io/atooai/atoo-studio`
|
|
102
108
|
- **LXC/LXD**: Download from [GitHub Releases](https://github.com/atooai/atoo-studio/releases)
|
|
103
|
-
- **Proxmox**: One-command [LXC](https://github.com/atooai/atoo-studio/tree/
|
|
109
|
+
- **Proxmox**: One-command [LXC](https://github.com/atooai/atoo-studio/tree/main/proxmox) and [VM](https://github.com/atooai/atoo-studio/tree/main/proxmox) scripts
|
|
104
110
|
|
|
105
111
|
## Links
|
|
106
112
|
|
package/README.npm.md
CHANGED
|
@@ -14,6 +14,12 @@ npx atoo-studio
|
|
|
14
14
|
|
|
15
15
|
Then open `https://localhost:3010` in your browser.
|
|
16
16
|
|
|
17
|
+
To use a different port:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
ATOO_PORT=4000 npx atoo-studio
|
|
21
|
+
```
|
|
22
|
+
|
|
17
23
|
## Install Globally
|
|
18
24
|
|
|
19
25
|
```bash
|
|
@@ -100,7 +106,7 @@ Atoo Studio exposes MCP tools for agent integration:
|
|
|
100
106
|
|
|
101
107
|
- **Docker**: `ghcr.io/atooai/atoo-studio`
|
|
102
108
|
- **LXC/LXD**: Download from [GitHub Releases](https://github.com/atooai/atoo-studio/releases)
|
|
103
|
-
- **Proxmox**: One-command [LXC](https://github.com/atooai/atoo-studio/tree/
|
|
109
|
+
- **Proxmox**: One-command [LXC](https://github.com/atooai/atoo-studio/tree/main/proxmox) and [VM](https://github.com/atooai/atoo-studio/tree/main/proxmox) scripts
|
|
104
110
|
|
|
105
111
|
## Links
|
|
106
112
|
|
package/dist/src/config.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export declare const PROJECT_ROOT: string;
|
|
2
|
-
export declare const
|
|
3
|
-
export declare const WEB_PORT = 3010;
|
|
2
|
+
export declare const WEB_PORT: number;
|
|
4
3
|
export declare const CERTS_DIR: string;
|
|
5
4
|
export declare const CA_CERT_PATH: string;
|
|
6
5
|
export declare const CA_KEY_PATH: string;
|
package/dist/src/config.js
CHANGED
|
@@ -5,8 +5,7 @@ const __config_dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
5
5
|
export const PROJECT_ROOT = __config_dirname.includes('/dist/src')
|
|
6
6
|
? path.resolve(__config_dirname, '../..') // dist/src/ → project root
|
|
7
7
|
: path.resolve(__config_dirname, '..'); // src/ → project root
|
|
8
|
-
export const
|
|
9
|
-
export const WEB_PORT = 3010;
|
|
8
|
+
export const WEB_PORT = parseInt(process.env.ATOO_PORT || '3010', 10);
|
|
10
9
|
export const CERTS_DIR = path.join(PROJECT_ROOT, 'certs') + '/';
|
|
11
10
|
export const CA_CERT_PATH = `${CERTS_DIR}ca.pem`;
|
|
12
11
|
export const CA_KEY_PATH = `${CERTS_DIR}ca-key.pem`;
|
|
@@ -9,6 +9,7 @@ import { getRemoteFileTree, readRemoteFileContent } from '../services/remote-fs-
|
|
|
9
9
|
import * as gitOps from '../services/git-ops.js';
|
|
10
10
|
import * as remoteGitOps from '../services/remote-git-ops.js';
|
|
11
11
|
import { sshManager } from '../services/ssh-manager.js';
|
|
12
|
+
import { searchFiles, replaceInFiles, replaceInSingleFile } from '../services/file-search.js';
|
|
12
13
|
import { watchProject, unwatchProject, reconcileWorktrees } from '../services/project-watcher.js';
|
|
13
14
|
export const projectsRouter = Router();
|
|
14
15
|
// Helper: get project context (local or remote)
|
|
@@ -67,7 +68,10 @@ projectsRouter.get('/api/projects/:id/files', async (req, res) => {
|
|
|
67
68
|
if (!ctx)
|
|
68
69
|
return res.status(404).json({ error: 'Project not found' });
|
|
69
70
|
try {
|
|
70
|
-
const
|
|
71
|
+
const requestedRootPath = req.query.rootPath;
|
|
72
|
+
const rootPath = requestedRootPath
|
|
73
|
+
? (path.isAbsolute(requestedRootPath) ? requestedRootPath : path.join(ctx.cwd, requestedRootPath))
|
|
74
|
+
: ctx.cwd;
|
|
71
75
|
const showHidden = req.query.showHidden === 'true';
|
|
72
76
|
const maxDepth = req.query.maxDepth != null ? parseInt(req.query.maxDepth, 10) : undefined;
|
|
73
77
|
if (ctx.connectionId) {
|
|
@@ -78,7 +82,7 @@ projectsRouter.get('/api/projects/:id/files', async (req, res) => {
|
|
|
78
82
|
const tree = await getFileTree(rootPath, 0, showHidden, maxDepth);
|
|
79
83
|
res.json(tree);
|
|
80
84
|
// Start watching when project root is first accessed (lazy — avoids watching all projects on startup)
|
|
81
|
-
if (!
|
|
85
|
+
if (!requestedRootPath && !ctx.connectionId) {
|
|
82
86
|
setImmediate(() => watchProject(req.params.id, ctx.cwd));
|
|
83
87
|
}
|
|
84
88
|
}
|
|
@@ -230,6 +234,61 @@ projectsRouter.post('/api/files/raw/search', (req, res) => {
|
|
|
230
234
|
res.status(500).json({ error: err.message });
|
|
231
235
|
}
|
|
232
236
|
});
|
|
237
|
+
// Search in project files (content search)
|
|
238
|
+
projectsRouter.post('/api/projects/:id/search', async (req, res) => {
|
|
239
|
+
const ctx = getProjectContext(req.params.id);
|
|
240
|
+
if (!ctx)
|
|
241
|
+
return res.status(404).json({ error: 'Project not found' });
|
|
242
|
+
if (!req.body.query)
|
|
243
|
+
return res.status(400).json({ error: 'query is required' });
|
|
244
|
+
try {
|
|
245
|
+
const result = await searchFiles(ctx.cwd, { ...req.body, showHidden: req.body.showHidden });
|
|
246
|
+
res.json(result);
|
|
247
|
+
}
|
|
248
|
+
catch (err) {
|
|
249
|
+
if (err instanceof SyntaxError || err.message?.includes('Invalid regular expression')) {
|
|
250
|
+
return res.status(400).json({ error: `Invalid regex: ${err.message}` });
|
|
251
|
+
}
|
|
252
|
+
res.status(500).json({ error: err.message });
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
// Replace in project files
|
|
256
|
+
projectsRouter.post('/api/projects/:id/replace-all', async (req, res) => {
|
|
257
|
+
const ctx = getProjectContext(req.params.id);
|
|
258
|
+
if (!ctx)
|
|
259
|
+
return res.status(404).json({ error: 'Project not found' });
|
|
260
|
+
if (!req.body.query || req.body.replacement == null)
|
|
261
|
+
return res.status(400).json({ error: 'query and replacement are required' });
|
|
262
|
+
try {
|
|
263
|
+
const result = await replaceInFiles(ctx.cwd, { ...req.body, showHidden: req.body.showHidden });
|
|
264
|
+
res.json(result);
|
|
265
|
+
}
|
|
266
|
+
catch (err) {
|
|
267
|
+
if (err instanceof SyntaxError || err.message?.includes('Invalid regular expression')) {
|
|
268
|
+
return res.status(400).json({ error: `Invalid regex: ${err.message}` });
|
|
269
|
+
}
|
|
270
|
+
res.status(500).json({ error: err.message });
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
// Replace in a single file (optionally specific lines)
|
|
274
|
+
projectsRouter.post('/api/projects/:id/replace-in-file', async (req, res) => {
|
|
275
|
+
const ctx = getProjectContext(req.params.id);
|
|
276
|
+
if (!ctx)
|
|
277
|
+
return res.status(404).json({ error: 'Project not found' });
|
|
278
|
+
const { file, query, replacement, lines, ...opts } = req.body;
|
|
279
|
+
if (!file || !query || replacement == null)
|
|
280
|
+
return res.status(400).json({ error: 'file, query and replacement are required' });
|
|
281
|
+
try {
|
|
282
|
+
const result = await replaceInSingleFile(ctx.cwd, file, { query, replacement, lines, ...opts });
|
|
283
|
+
res.json(result);
|
|
284
|
+
}
|
|
285
|
+
catch (err) {
|
|
286
|
+
if (err.message?.includes('Invalid regular expression')) {
|
|
287
|
+
return res.status(400).json({ error: `Invalid regex: ${err.message}` });
|
|
288
|
+
}
|
|
289
|
+
res.status(500).json({ error: err.message });
|
|
290
|
+
}
|
|
291
|
+
});
|
|
233
292
|
projectsRouter.put('/api/files', async (req, res) => {
|
|
234
293
|
const { path: filePath, content, ssh_connection_id } = req.body;
|
|
235
294
|
if (!filePath || typeof content !== 'string') {
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export interface SearchMatch {
|
|
2
|
+
line: number;
|
|
3
|
+
column: number;
|
|
4
|
+
length: number;
|
|
5
|
+
lineContent: string;
|
|
6
|
+
}
|
|
7
|
+
export interface SearchFileResult {
|
|
8
|
+
file: string;
|
|
9
|
+
matches: SearchMatch[];
|
|
10
|
+
filenameMatch?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface SearchRequest {
|
|
13
|
+
query: string;
|
|
14
|
+
isRegex?: boolean;
|
|
15
|
+
matchCase?: boolean;
|
|
16
|
+
matchWholeWord?: boolean;
|
|
17
|
+
includeFilenames?: boolean;
|
|
18
|
+
includeFilter?: string;
|
|
19
|
+
excludeFilter?: string;
|
|
20
|
+
includeFilterIsRegex?: boolean;
|
|
21
|
+
excludeFilterIsRegex?: boolean;
|
|
22
|
+
openFilesOnly?: string[];
|
|
23
|
+
showHidden?: boolean;
|
|
24
|
+
maxResults?: number;
|
|
25
|
+
}
|
|
26
|
+
export interface SearchResponse {
|
|
27
|
+
results: SearchFileResult[];
|
|
28
|
+
truncated: boolean;
|
|
29
|
+
totalFiles: number;
|
|
30
|
+
totalMatches: number;
|
|
31
|
+
}
|
|
32
|
+
export interface ReplaceRequest extends SearchRequest {
|
|
33
|
+
replacement: string;
|
|
34
|
+
preserveCase?: boolean;
|
|
35
|
+
renameFiles?: boolean;
|
|
36
|
+
}
|
|
37
|
+
export interface ReplaceResponse {
|
|
38
|
+
filesModified: number;
|
|
39
|
+
totalReplacements: number;
|
|
40
|
+
filesRenamed: number;
|
|
41
|
+
errors: Array<{
|
|
42
|
+
file: string;
|
|
43
|
+
error: string;
|
|
44
|
+
}>;
|
|
45
|
+
}
|
|
46
|
+
export declare function searchFiles(rootPath: string, req: SearchRequest): Promise<SearchResponse>;
|
|
47
|
+
export declare function replaceInFiles(rootPath: string, req: ReplaceRequest): Promise<ReplaceResponse>;
|
|
48
|
+
export declare function replaceInSingleFile(rootPath: string, relFile: string, req: {
|
|
49
|
+
query: string;
|
|
50
|
+
replacement: string;
|
|
51
|
+
isRegex?: boolean;
|
|
52
|
+
matchCase?: boolean;
|
|
53
|
+
matchWholeWord?: boolean;
|
|
54
|
+
preserveCase?: boolean;
|
|
55
|
+
lines?: number[];
|
|
56
|
+
}): Promise<{
|
|
57
|
+
replacements: number;
|
|
58
|
+
error?: string;
|
|
59
|
+
}>;
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import fsp from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { isBinaryFile } from './fs-browser.js';
|
|
4
|
+
const SKIP_NAMES = new Set(['.git', '.atoo-studio', 'node_modules', '.next', '.nuxt', 'dist', 'build', '__pycache__', '.venv', 'venv', '.cache', '.parcel-cache', 'coverage', '.svn', '.hg']);
|
|
5
|
+
const MAX_FILE_SIZE = 2 * 1024 * 1024; // 2MB
|
|
6
|
+
function buildSearchRegex(query, opts) {
|
|
7
|
+
let pattern = opts.isRegex ? query : query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
8
|
+
if (opts.matchWholeWord)
|
|
9
|
+
pattern = `\\b${pattern}\\b`;
|
|
10
|
+
const flags = opts.matchCase ? 'g' : 'gi';
|
|
11
|
+
return new RegExp(pattern, flags);
|
|
12
|
+
}
|
|
13
|
+
function globToRegex(glob) {
|
|
14
|
+
// Simple glob-to-regex: * → [^/]*, ** → .*, ? → .
|
|
15
|
+
return glob
|
|
16
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
17
|
+
.replace(/\*\*/g, '{{DOUBLESTAR}}')
|
|
18
|
+
.replace(/\*/g, '[^/]*')
|
|
19
|
+
.replace(/\?/g, '.')
|
|
20
|
+
.replace(/\{\{DOUBLESTAR\}\}/g, '.*');
|
|
21
|
+
}
|
|
22
|
+
function buildFilterRegex(filter, isRegex) {
|
|
23
|
+
if (!filter.trim())
|
|
24
|
+
return null;
|
|
25
|
+
if (isRegex) {
|
|
26
|
+
return new RegExp(filter, 'i');
|
|
27
|
+
}
|
|
28
|
+
// Comma-separated globs
|
|
29
|
+
const parts = filter.split(',').map(s => s.trim()).filter(Boolean);
|
|
30
|
+
if (parts.length === 0)
|
|
31
|
+
return null;
|
|
32
|
+
const regexParts = parts.map(globToRegex);
|
|
33
|
+
return new RegExp(`^(${regexParts.join('|')})$`, 'i');
|
|
34
|
+
}
|
|
35
|
+
function matchesFilter(relPath, filter) {
|
|
36
|
+
const fileName = path.basename(relPath);
|
|
37
|
+
// Match against both the full relative path and just the filename
|
|
38
|
+
return filter.test(relPath) || filter.test(fileName);
|
|
39
|
+
}
|
|
40
|
+
function preserveCaseReplace(match, replacement) {
|
|
41
|
+
if (match === match.toUpperCase())
|
|
42
|
+
return replacement.toUpperCase();
|
|
43
|
+
if (match === match.toLowerCase())
|
|
44
|
+
return replacement.toLowerCase();
|
|
45
|
+
if (match[0] === match[0].toUpperCase() && match.slice(1) === match.slice(1).toLowerCase()) {
|
|
46
|
+
return replacement[0].toUpperCase() + replacement.slice(1).toLowerCase();
|
|
47
|
+
}
|
|
48
|
+
return replacement;
|
|
49
|
+
}
|
|
50
|
+
async function walkFiles(dirPath, rootPath, showHidden, includeRe, excludeRe, openFilesSet, callback) {
|
|
51
|
+
let entries;
|
|
52
|
+
try {
|
|
53
|
+
entries = await fsp.readdir(dirPath, { withFileTypes: true });
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
let fileCount = 0;
|
|
59
|
+
for (const entry of entries) {
|
|
60
|
+
if (!showHidden && SKIP_NAMES.has(entry.name))
|
|
61
|
+
continue;
|
|
62
|
+
const absPath = path.join(dirPath, entry.name);
|
|
63
|
+
const relPath = path.relative(rootPath, absPath);
|
|
64
|
+
if (entry.isDirectory()) {
|
|
65
|
+
await walkFiles(absPath, rootPath, showHidden, includeRe, excludeRe, openFilesSet, callback);
|
|
66
|
+
}
|
|
67
|
+
else if (entry.isFile() || entry.isSymbolicLink()) {
|
|
68
|
+
// Filter by open files
|
|
69
|
+
if (openFilesSet && !openFilesSet.has(absPath))
|
|
70
|
+
continue;
|
|
71
|
+
// Apply include/exclude filters
|
|
72
|
+
if (includeRe && !matchesFilter(relPath, includeRe))
|
|
73
|
+
continue;
|
|
74
|
+
if (excludeRe && matchesFilter(relPath, excludeRe))
|
|
75
|
+
continue;
|
|
76
|
+
// Skip binary files
|
|
77
|
+
if (isBinaryFile(absPath))
|
|
78
|
+
continue;
|
|
79
|
+
// Skip large files
|
|
80
|
+
try {
|
|
81
|
+
const stat = await fsp.stat(absPath);
|
|
82
|
+
if (stat.size > MAX_FILE_SIZE)
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const shouldContinue = await callback(absPath, relPath);
|
|
89
|
+
if (!shouldContinue)
|
|
90
|
+
return;
|
|
91
|
+
// Yield every 50 files
|
|
92
|
+
fileCount++;
|
|
93
|
+
if (fileCount % 50 === 0) {
|
|
94
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
export async function searchFiles(rootPath, req) {
|
|
100
|
+
const maxResults = req.maxResults || 500;
|
|
101
|
+
const regex = buildSearchRegex(req.query, req);
|
|
102
|
+
const includeRe = buildFilterRegex(req.includeFilter || '', req.includeFilterIsRegex || false);
|
|
103
|
+
const excludeRe = buildFilterRegex(req.excludeFilter || '', req.excludeFilterIsRegex || false);
|
|
104
|
+
const openFilesSet = req.openFilesOnly ? new Set(req.openFilesOnly.map(f => path.resolve(f))) : null;
|
|
105
|
+
const results = [];
|
|
106
|
+
let totalMatches = 0;
|
|
107
|
+
let totalFiles = 0;
|
|
108
|
+
let truncated = false;
|
|
109
|
+
await walkFiles(rootPath, rootPath, req.showHidden || false, includeRe, excludeRe, openFilesSet, async (absPath, relPath) => {
|
|
110
|
+
totalFiles++;
|
|
111
|
+
const fileResult = { file: relPath, matches: [] };
|
|
112
|
+
// Check filename match
|
|
113
|
+
if (req.includeFilenames) {
|
|
114
|
+
const fnRegex = buildSearchRegex(req.query, req);
|
|
115
|
+
if (fnRegex.test(relPath)) {
|
|
116
|
+
fileResult.filenameMatch = true;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Search file contents
|
|
120
|
+
let content;
|
|
121
|
+
try {
|
|
122
|
+
content = await fsp.readFile(absPath, 'utf-8');
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
const lines = content.split('\n');
|
|
128
|
+
for (let i = 0; i < lines.length; i++) {
|
|
129
|
+
const line = lines[i];
|
|
130
|
+
// Reset regex lastIndex for each line
|
|
131
|
+
const lineRegex = buildSearchRegex(req.query, req);
|
|
132
|
+
let m;
|
|
133
|
+
while ((m = lineRegex.exec(line)) !== null) {
|
|
134
|
+
fileResult.matches.push({
|
|
135
|
+
line: i + 1,
|
|
136
|
+
column: m.index,
|
|
137
|
+
length: m[0].length,
|
|
138
|
+
lineContent: line.length > 500 ? line.substring(0, 500) : line,
|
|
139
|
+
});
|
|
140
|
+
totalMatches++;
|
|
141
|
+
if (totalMatches >= maxResults) {
|
|
142
|
+
truncated = true;
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
// Prevent infinite loop on zero-length matches
|
|
146
|
+
if (m[0].length === 0)
|
|
147
|
+
lineRegex.lastIndex++;
|
|
148
|
+
}
|
|
149
|
+
if (truncated)
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
if (fileResult.matches.length > 0 || fileResult.filenameMatch) {
|
|
153
|
+
results.push(fileResult);
|
|
154
|
+
}
|
|
155
|
+
return !truncated;
|
|
156
|
+
});
|
|
157
|
+
return { results, truncated, totalFiles, totalMatches };
|
|
158
|
+
}
|
|
159
|
+
export async function replaceInFiles(rootPath, req) {
|
|
160
|
+
const regex = buildSearchRegex(req.query, req);
|
|
161
|
+
const includeRe = buildFilterRegex(req.includeFilter || '', req.includeFilterIsRegex || false);
|
|
162
|
+
const excludeRe = buildFilterRegex(req.excludeFilter || '', req.excludeFilterIsRegex || false);
|
|
163
|
+
const openFilesSet = req.openFilesOnly ? new Set(req.openFilesOnly.map(f => path.resolve(f))) : null;
|
|
164
|
+
let filesModified = 0;
|
|
165
|
+
let totalReplacements = 0;
|
|
166
|
+
let filesRenamed = 0;
|
|
167
|
+
const errors = [];
|
|
168
|
+
// Collect files to process (for rename, we need all paths first)
|
|
169
|
+
const filesToProcess = [];
|
|
170
|
+
await walkFiles(rootPath, rootPath, req.showHidden || false, includeRe, excludeRe, openFilesSet, async (absPath, relPath) => {
|
|
171
|
+
filesToProcess.push({ absPath, relPath });
|
|
172
|
+
return true;
|
|
173
|
+
});
|
|
174
|
+
// Replace contents
|
|
175
|
+
for (const { absPath, relPath } of filesToProcess) {
|
|
176
|
+
try {
|
|
177
|
+
const content = await fsp.readFile(absPath, 'utf-8');
|
|
178
|
+
const replaceRegex = buildSearchRegex(req.query, req);
|
|
179
|
+
let newContent;
|
|
180
|
+
let count = 0;
|
|
181
|
+
if (req.preserveCase) {
|
|
182
|
+
newContent = content.replace(replaceRegex, (match) => {
|
|
183
|
+
count++;
|
|
184
|
+
return preserveCaseReplace(match, req.replacement);
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
newContent = content.replace(replaceRegex, () => {
|
|
189
|
+
count++;
|
|
190
|
+
return req.replacement;
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
if (count > 0) {
|
|
194
|
+
await fsp.writeFile(absPath, newContent, 'utf-8');
|
|
195
|
+
filesModified++;
|
|
196
|
+
totalReplacements += count;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
catch (err) {
|
|
200
|
+
errors.push({ file: relPath, error: err.message });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// Rename files if requested
|
|
204
|
+
if (req.renameFiles && req.includeFilenames) {
|
|
205
|
+
for (const { absPath, relPath } of filesToProcess) {
|
|
206
|
+
const fileName = path.basename(absPath);
|
|
207
|
+
const renameRegex = buildSearchRegex(req.query, req);
|
|
208
|
+
let newName;
|
|
209
|
+
if (req.preserveCase) {
|
|
210
|
+
newName = fileName.replace(renameRegex, (match) => preserveCaseReplace(match, req.replacement));
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
newName = fileName.replace(renameRegex, req.replacement);
|
|
214
|
+
}
|
|
215
|
+
if (newName !== fileName) {
|
|
216
|
+
const newPath = path.join(path.dirname(absPath), newName);
|
|
217
|
+
try {
|
|
218
|
+
await fsp.rename(absPath, newPath);
|
|
219
|
+
filesRenamed++;
|
|
220
|
+
}
|
|
221
|
+
catch (err) {
|
|
222
|
+
errors.push({ file: relPath, error: `Rename failed: ${err.message}` });
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return { filesModified, totalReplacements, filesRenamed, errors };
|
|
228
|
+
}
|
|
229
|
+
// Replace in a single file, optionally restricted to specific lines
|
|
230
|
+
export async function replaceInSingleFile(rootPath, relFile, req) {
|
|
231
|
+
const absPath = path.join(rootPath, relFile);
|
|
232
|
+
try {
|
|
233
|
+
const content = await fsp.readFile(absPath, 'utf-8');
|
|
234
|
+
const lineSet = req.lines ? new Set(req.lines) : null;
|
|
235
|
+
if (lineSet) {
|
|
236
|
+
// Replace only on specific lines
|
|
237
|
+
const lines = content.split('\n');
|
|
238
|
+
let count = 0;
|
|
239
|
+
for (let i = 0; i < lines.length; i++) {
|
|
240
|
+
if (!lineSet.has(i + 1))
|
|
241
|
+
continue; // lines are 1-based
|
|
242
|
+
const lineRegex = buildSearchRegex(req.query, req);
|
|
243
|
+
const newLine = req.preserveCase
|
|
244
|
+
? lines[i].replace(lineRegex, (match) => { count++; return preserveCaseReplace(match, req.replacement); })
|
|
245
|
+
: lines[i].replace(lineRegex, () => { count++; return req.replacement; });
|
|
246
|
+
lines[i] = newLine;
|
|
247
|
+
}
|
|
248
|
+
if (count > 0) {
|
|
249
|
+
await fsp.writeFile(absPath, lines.join('\n'), 'utf-8');
|
|
250
|
+
}
|
|
251
|
+
return { replacements: count };
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
// Replace all in file
|
|
255
|
+
const replaceRegex = buildSearchRegex(req.query, req);
|
|
256
|
+
let count = 0;
|
|
257
|
+
const newContent = req.preserveCase
|
|
258
|
+
? content.replace(replaceRegex, (match) => { count++; return preserveCaseReplace(match, req.replacement); })
|
|
259
|
+
: content.replace(replaceRegex, () => { count++; return req.replacement; });
|
|
260
|
+
if (count > 0) {
|
|
261
|
+
await fsp.writeFile(absPath, newContent, 'utf-8');
|
|
262
|
+
}
|
|
263
|
+
return { replacements: count };
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
catch (err) {
|
|
267
|
+
return { replacements: 0, error: err.message };
|
|
268
|
+
}
|
|
269
|
+
}
|
|
@@ -8,6 +8,10 @@ export declare function gitStatus(cwd: string): Promise<{
|
|
|
8
8
|
workTreeStatus: string;
|
|
9
9
|
oldPath: string | undefined;
|
|
10
10
|
}[]>;
|
|
11
|
+
export declare function parseRefs(decorate: string): {
|
|
12
|
+
type: "head" | "branch" | "tag" | "remote";
|
|
13
|
+
label: string;
|
|
14
|
+
}[];
|
|
11
15
|
export declare function gitLog(cwd: string, branch?: string, count?: number): Promise<{
|
|
12
16
|
hash: string;
|
|
13
17
|
fullHash: string;
|
|
@@ -17,6 +21,10 @@ export declare function gitLog(cwd: string, branch?: string, count?: number): Pr
|
|
|
17
21
|
date: string;
|
|
18
22
|
files: never[];
|
|
19
23
|
merge: boolean;
|
|
24
|
+
refs: {
|
|
25
|
+
type: "head" | "branch" | "tag" | "remote";
|
|
26
|
+
label: string;
|
|
27
|
+
}[];
|
|
20
28
|
}[]>;
|
|
21
29
|
export declare function gitCommitFiles(cwd: string, hash: string): Promise<({
|
|
22
30
|
path: string;
|
|
@@ -90,9 +90,36 @@ export async function gitStatus(cwd) {
|
|
|
90
90
|
}
|
|
91
91
|
return entries;
|
|
92
92
|
}
|
|
93
|
+
export function parseRefs(decorate) {
|
|
94
|
+
if (!decorate.trim())
|
|
95
|
+
return [];
|
|
96
|
+
const refs = [];
|
|
97
|
+
for (const part of decorate.split(',')) {
|
|
98
|
+
const token = part.trim();
|
|
99
|
+
if (!token)
|
|
100
|
+
continue;
|
|
101
|
+
if (token === 'HEAD') {
|
|
102
|
+
refs.push({ type: 'head', label: 'HEAD' });
|
|
103
|
+
}
|
|
104
|
+
else if (token.startsWith('HEAD -> ')) {
|
|
105
|
+
refs.push({ type: 'head', label: 'HEAD' });
|
|
106
|
+
refs.push({ type: 'branch', label: token.slice(8) });
|
|
107
|
+
}
|
|
108
|
+
else if (token.startsWith('tag: ')) {
|
|
109
|
+
refs.push({ type: 'tag', label: token.slice(5) });
|
|
110
|
+
}
|
|
111
|
+
else if (token.includes('/')) {
|
|
112
|
+
refs.push({ type: 'remote', label: token });
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
refs.push({ type: 'branch', label: token });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return refs;
|
|
119
|
+
}
|
|
93
120
|
export async function gitLog(cwd, branch, count = 30) {
|
|
94
121
|
const SEP = '---GIT-LOG-SEP---';
|
|
95
|
-
const args = ['log', `--format=${SEP}%n%H%n%h%n%an%n%ar%n%s%n%B`, '-n', String(count)];
|
|
122
|
+
const args = ['log', `--format=${SEP}%n%H%n%h%n%an%n%ar%n%D%n%s%n%B`, '-n', String(count)];
|
|
96
123
|
if (branch)
|
|
97
124
|
args.push(branch);
|
|
98
125
|
const output = await git(args, cwd);
|
|
@@ -103,10 +130,12 @@ export async function gitLog(cwd, branch, count = 30) {
|
|
|
103
130
|
const hash = lines[1] || '';
|
|
104
131
|
const author = lines[2] || '';
|
|
105
132
|
const date = lines[3] || '';
|
|
106
|
-
const
|
|
107
|
-
const
|
|
133
|
+
const decorate = lines[4] || '';
|
|
134
|
+
const msg = lines[5] || '';
|
|
135
|
+
const fullMessage = lines.slice(6).join('\n').trim() || msg;
|
|
108
136
|
const isMerge = msg.toLowerCase().startsWith('merge');
|
|
109
|
-
|
|
137
|
+
const refs = parseRefs(decorate);
|
|
138
|
+
return { hash, fullHash, msg, fullMessage, author, date, files: [], merge: isMerge, refs };
|
|
110
139
|
});
|
|
111
140
|
}
|
|
112
141
|
export async function gitCommitFiles(cwd, hash) {
|
|
@@ -17,6 +17,10 @@ export declare function gitLog(connId: string, cwd: string, branch?: string, cou
|
|
|
17
17
|
date: string;
|
|
18
18
|
files: never[];
|
|
19
19
|
merge: boolean;
|
|
20
|
+
refs: {
|
|
21
|
+
type: "head" | "branch" | "tag" | "remote";
|
|
22
|
+
label: string;
|
|
23
|
+
}[];
|
|
20
24
|
}[]>;
|
|
21
25
|
export declare function gitCommitFiles(connId: string, cwd: string, hash: string): Promise<{
|
|
22
26
|
path: string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { sshManager } from './ssh-manager.js';
|
|
2
|
+
import { parseRefs } from './git-ops.js';
|
|
2
3
|
function shellEscape(s) {
|
|
3
4
|
return "'" + s.replace(/'/g, "'\\''") + "'";
|
|
4
5
|
}
|
|
@@ -34,7 +35,7 @@ export async function gitStatus(connId, cwd) {
|
|
|
34
35
|
}
|
|
35
36
|
export async function gitLog(connId, cwd, branch, count = 30) {
|
|
36
37
|
const SEP = '---GIT-LOG-SEP---';
|
|
37
|
-
const args = ['log', `--format=${SEP}%n%H%n%h%n%an%n%ar%n%s%n%B`, '-n', String(count)];
|
|
38
|
+
const args = ['log', `--format=${SEP}%n%H%n%h%n%an%n%ar%n%D%n%s%n%B`, '-n', String(count)];
|
|
38
39
|
if (branch)
|
|
39
40
|
args.push(branch);
|
|
40
41
|
const output = await git(connId, args, cwd);
|
|
@@ -45,10 +46,12 @@ export async function gitLog(connId, cwd, branch, count = 30) {
|
|
|
45
46
|
const hash = lines[1] || '';
|
|
46
47
|
const author = lines[2] || '';
|
|
47
48
|
const date = lines[3] || '';
|
|
48
|
-
const
|
|
49
|
-
const
|
|
49
|
+
const decorate = lines[4] || '';
|
|
50
|
+
const msg = lines[5] || '';
|
|
51
|
+
const fullMessage = lines.slice(6).join('\n').trim() || msg;
|
|
50
52
|
const isMerge = msg.toLowerCase().startsWith('merge');
|
|
51
|
-
|
|
53
|
+
const refs = parseRefs(decorate);
|
|
54
|
+
return { hash, fullHash, msg, fullMessage, author, date, files: [], merge: isMerge, refs };
|
|
52
55
|
});
|
|
53
56
|
}
|
|
54
57
|
export async function gitCommitFiles(connId, cwd, hash) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Client } from 'ssh2';
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import net from 'net';
|
|
4
|
-
import { CA_CERT_PATH
|
|
4
|
+
import { CA_CERT_PATH } from '../config.js';
|
|
5
5
|
import { deobfuscate } from './obfuscation.js';
|
|
6
6
|
class SshManager {
|
|
7
7
|
connections = new Map();
|
|
@@ -46,7 +46,6 @@ class SshManager {
|
|
|
46
46
|
try {
|
|
47
47
|
// Setup reverse tunnels so remote claude can reach our proxy
|
|
48
48
|
await this.setupReverseTunnel(config.id, 3000, 3010);
|
|
49
|
-
await this.setupReverseTunnel(config.id, PROXY_PORT, PROXY_PORT);
|
|
50
49
|
// Upload CA cert to remote
|
|
51
50
|
await this.uploadCaCert(config.id);
|
|
52
51
|
resolve();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{H as e,R as t,S as n,U as r,b as i,c as a,f as o,n as s,r as c,s as l,u,v as d}from"./_createAssigner-ByDUqGii.js";import{A as f,C as p,D as m,M as h,N as g,S as _,T as v,f as y,h as b,u as x,w as S}from"./_baseUniq-
|
|
1
|
+
import{H as e,R as t,S as n,U as r,b as i,c as a,f as o,n as s,r as c,s as l,u,v as d}from"./_createAssigner-ByDUqGii.js";import{A as f,C as p,D as m,M as h,N as g,S as _,T as v,f as y,h as b,u as x,w as S}from"./_baseUniq-BlaoUomR.js";var C=/\s/;function w(e){for(var t=e.length;t--&&C.test(e.charAt(t)););return t}var ee=/^\s+/;function T(e){return e&&e.slice(0,w(e)+1).replace(ee,``)}var E=NaN,D=/^[-+]0x[0-9a-f]+$/i,O=/^0b[01]+$/i,k=/^0o[0-7]+$/i,A=parseInt;function j(t){if(typeof t==`number`)return t;if(g(t))return E;if(e(t)){var n=typeof t.valueOf==`function`?t.valueOf():t;t=e(n)?n+``:n}if(typeof t!=`string`)return t===0?t:+t;t=T(t);var r=O.test(t);return r||k.test(t)?A(t.slice(2),r?2:8):D.test(t)?E:+t}var M=1/0,N=17976931348623157e292;function P(e){return e?(e=j(e),e===M||e===-M?(e<0?-1:1)*N:e===e?e:0):e===0?e:0}function F(e){var t=P(e),n=t%1;return t===t?n?t-n:t:0}function I(e){return e!=null&&e.length?_(e,1):[]}var L=Object.prototype,R=L.hasOwnProperty,z=c(function(e,n){e=Object(e);var r=-1,i=n.length,o=i>2?n[2]:void 0;for(o&&s(n[0],n[1],o)&&(i=1);++r<i;)for(var c=n[r],l=a(c),u=-1,d=l.length;++u<d;){var f=l[u],p=e[f];(p===void 0||t(p,L[f])&&!R.call(e,f))&&(e[f]=c[f])}return e});function B(e){var t=e==null?0:e.length;return t?e[t-1]:void 0}function V(e){return function(t,n,r){var i=Object(t);if(!d(t)){var a=y(n,3);t=m(t),n=function(e){return a(i[e],e,i)}}var o=e(t,n,r);return o>-1?i[a?t[o]:o]:void 0}}var H=Math.max;function U(e,t,n){var r=e==null?0:e.length;if(!r)return-1;var i=n==null?0:F(n);return i<0&&(i=H(r+i,0)),f(e,y(t,3),i)}var W=V(U);function G(e,t){var n=-1,r=d(e)?Array(e.length):[];return x(e,function(e,i,a){r[++n]=t(e,i,a)}),r}function K(e,t){return(i(e)?h:G)(e,y(t,3))}var q=Object.prototype.hasOwnProperty;function J(e,t){return e!=null&&q.call(e,t)}function Y(e,t){return e!=null&&b(e,t,J)}var X=`[object String]`;function Z(e){return typeof e==`string`||!i(e)&&n(e)&&r(e)==X}function Q(e,t){return e<t}function $(e,t,n){for(var r=-1,i=e.length;++r<i;){var a=e[r],o=t(a);if(o!=null&&(s===void 0?o===o&&!g(o):n(o,s)))var s=o,c=a}return c}function te(e){return e&&e.length?$(e,l,Q):void 0}function ne(t,n,r,i){if(!e(t))return t;n=v(n,t);for(var a=-1,s=n.length,c=s-1,l=t;l!=null&&++a<s;){var d=S(n[a]),f=r;if(d===`__proto__`||d===`constructor`||d===`prototype`)return t;if(a!=c){var p=l[d];f=i?i(p,d,l):void 0,f===void 0&&(f=e(p)?p:u(n[a+1])?[]:{})}o(l,d,f),l=l[d]}return t}function re(e,t,n){for(var r=-1,i=t.length,a={};++r<i;){var o=t[r],s=p(e,o);n(s,o)&&ne(a,v(o,e),s)}return a}export{Z as a,G as c,z as d,I as f,Q as i,W as l,P as m,te as n,Y as o,F as p,$ as r,K as s,re as t,B as u};
|