@cloudcli-ai/cloudcli 1.37.1 → 1.37.2
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/assets/abnfDiagram-N423BO3Z-C4UAoHJb.js +1 -0
- package/dist/assets/arc-B8jF-ja9.js +1 -0
- package/dist/assets/architectureDiagram-T3A2C74G-C_Wur_CU.js +36 -0
- package/dist/assets/blockDiagram-VBNYF7ZC-Cq4P1i8t.js +132 -0
- package/dist/assets/c4Diagram-5PPSVZJV-C1wG4eUj.js +10 -0
- package/dist/assets/channel-_falYbU1.js +1 -0
- package/dist/assets/chunk-2GRJ4B5K-B8fnBG6E.js +1 -0
- package/dist/assets/chunk-2Q5K7J3B-WdfPooPJ.js +1 -0
- package/dist/assets/chunk-5RXB4S5H-DhZJuG2E.js +231 -0
- package/dist/assets/chunk-5VM5RSS4-B0rjslal.js +15 -0
- package/dist/assets/chunk-6Q2QTUOP-CG2Oj4jL.js +88 -0
- package/dist/assets/chunk-GF5L2VYU-rrl2lRxY.js +206 -0
- package/dist/assets/chunk-JWPE2WC7-CjVbrVMA.js +1 -0
- package/dist/assets/chunk-KBJHAD2P-CRzKgU8S.js +1 -0
- package/dist/assets/chunk-RYQCIY6F-DBz4GTZF.js +1 -0
- package/dist/assets/chunk-XXDRQBXY-wHxalsXk.js +1 -0
- package/dist/assets/classDiagram-JCYQIIEL-Dn-3CfpR.js +1 -0
- package/dist/assets/classDiagram-v2-OCEON4UE-Dn-3CfpR.js +1 -0
- package/dist/assets/cose-bilkent-JH36ORCC-s1hygY8e.js +1 -0
- package/dist/assets/cynefin-VYW2F7L2-C4LGV15i.js +178 -0
- package/dist/assets/cynefinDiagram-MW4NZA55-C5uhu7B3.js +62 -0
- package/dist/assets/cytoscape.esm-C10L9Icf.js +331 -0
- package/dist/assets/dagre-VZM6K2ZE-B-OBTewm.js +4 -0
- package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/dist/assets/diagram-7IWD3JNH-BjGKQxih.js +30 -0
- package/dist/assets/diagram-B4RE2ZJO-BMKz1X2L.js +3 -0
- package/dist/assets/diagram-LBJQPF4R-DNh-GhKC.js +24 -0
- package/dist/assets/diagram-Q27KOJAE-BaTG9jPT.js +24 -0
- package/dist/assets/diagram-UB23O5K3-BUwo2OvJ.js +41 -0
- package/dist/assets/ebnfDiagram-BXEA7PRR-DAZgfzX2.js +1 -0
- package/dist/assets/erDiagram-JOGREHBK-1w1CcGEb.js +85 -0
- package/dist/assets/flowDiagram-UKHOOZJN-D4EII8Jj.js +156 -0
- package/dist/assets/ganttDiagram-PKOTCBZU-ChvJZAgB.js +292 -0
- package/dist/assets/gitGraphDiagram-DS77QQ5N-DtOn41S8.js +106 -0
- package/dist/assets/graph-Cx-EqisW.js +1 -0
- package/dist/assets/index-CzPLBvXT.css +32 -0
- package/dist/assets/{index-DTMWjJj8.js → index-DFkBBuSA.js} +303 -304
- package/dist/assets/infoDiagram-6WML65LV-DN_PVEJZ.js +2 -0
- package/dist/assets/init-Gi6I4Gst.js +1 -0
- package/dist/assets/ishikawaDiagram-WSZJBQD7-DbkF1kXb.js +70 -0
- package/dist/assets/journeyDiagram-NVQOT4AX-pJwmhddE.js +139 -0
- package/dist/assets/kanban-definition-27J2QSJJ-PPAPgNp5.js +89 -0
- package/dist/assets/layout-D6BstSzF.js +1 -0
- package/dist/assets/linear-iPR-J9f7.js +1 -0
- package/dist/assets/map-CV9uif0g.js +1 -0
- package/dist/assets/mermaid.core-BEUL86Z6.js +312 -0
- package/dist/assets/mindmap-definition-FAOFIHXS-BvZoBib7.js +96 -0
- package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/dist/assets/pegDiagram-VL7TDLO6-DhYreaUd.js +1 -0
- package/dist/assets/pieDiagram-7S7Q4E2Y-CrOHlyD0.js +39 -0
- package/dist/assets/quadrantDiagram-CIZ2JOQS-D4g5yH3t.js +7 -0
- package/dist/assets/railroadDiagram-AXF67PYL-Dj39lHlf.js +1 -0
- package/dist/assets/requirementDiagram-LRYGKXZP-1AS4Zgm_.js +84 -0
- package/dist/assets/sankeyDiagram-W5VNT64P-CKlMn19P.js +40 -0
- package/dist/assets/sequenceDiagram-SI44F4Z6-B_j3qyvo.js +162 -0
- package/dist/assets/sizeCapture-X5ZJPWSS-Bp2Op16v.js +1 -0
- package/dist/assets/stateDiagram-OKZ733FA-C0cN6DoY.js +1 -0
- package/dist/assets/stateDiagram-v2-UEYNNEHI-BvBxUUrZ.js +1 -0
- package/dist/assets/swimlanes-SLNWSIFB-BEvXOV9b.js +2 -0
- package/dist/assets/swimlanesDiagram-ULZ7WXOC-CsE-x4YK.js +8 -0
- package/dist/assets/timeline-definition-Z64GVDOM-zdJQinvZ.js +120 -0
- package/dist/assets/vennDiagram-T6HMQDX7-DuF1uZMY.js +34 -0
- package/dist/assets/wardleyDiagram-T6FBY63Y-B9AlLQup.js +78 -0
- package/dist/assets/xychartDiagram-ELKLHX3M-DTJX48Qg.js +7 -0
- package/dist/index.html +2 -2
- package/dist-server/server/index.js +1 -2
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/modules/agent/agent.module.js +2 -1
- package/dist-server/server/modules/agent/agent.module.js.map +1 -1
- package/dist-server/server/modules/auth/auth.middleware.js +1 -1
- package/dist-server/server/modules/auth/auth.middleware.js.map +1 -1
- package/dist-server/server/modules/browser-use/browser-use-runtime.js +10 -0
- package/dist-server/server/modules/browser-use/browser-use-runtime.js.map +1 -0
- package/dist-server/server/modules/browser-use/browser-use.service.js +3 -6
- package/dist-server/server/modules/browser-use/browser-use.service.js.map +1 -1
- package/dist-server/server/modules/browser-use/index.js +2 -0
- package/dist-server/server/modules/browser-use/index.js.map +1 -1
- package/dist-server/server/modules/cli/cli.js +9 -3
- package/dist-server/server/modules/cli/cli.js.map +1 -1
- package/dist-server/server/modules/cli/cli.module.js +0 -1
- package/dist-server/server/modules/cli/cli.module.js.map +1 -1
- package/dist-server/server/modules/cli/cli.service.js +0 -18
- package/dist-server/server/modules/cli/cli.service.js.map +1 -1
- package/dist-server/server/modules/cli/tests/cli-environment-bootstrap.test.js +154 -0
- package/dist-server/server/modules/cli/tests/cli-environment-bootstrap.test.js.map +1 -0
- package/dist-server/server/modules/cli/tests/cli.service.test.js +0 -3
- package/dist-server/server/modules/cli/tests/cli.service.test.js.map +1 -1
- package/dist-server/server/modules/file-tree/file-tree.service.js +18 -10
- package/dist-server/server/modules/file-tree/file-tree.service.js.map +1 -1
- package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js +62 -11
- package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js.map +1 -1
- package/dist-server/server/modules/git/git-branch.service.js +15 -0
- package/dist-server/server/modules/git/git-branch.service.js.map +1 -0
- package/dist-server/server/modules/git/git.routes.js +15 -7
- package/dist-server/server/modules/git/git.routes.js.map +1 -1
- package/dist-server/server/modules/git/tests/git-branch.service.test.js +52 -0
- package/dist-server/server/modules/git/tests/git-branch.service.test.js.map +1 -0
- package/dist-server/server/modules/git/tests/git-init.routes.test.js +84 -0
- package/dist-server/server/modules/git/tests/git-init.routes.test.js.map +1 -1
- package/dist-server/server/modules/notifications/index.js +3 -1
- package/dist-server/server/modules/notifications/index.js.map +1 -1
- package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js +22 -1
- package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js +254 -48
- package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js.map +1 -1
- package/dist-server/server/modules/providers/provider.routes.js +5 -0
- package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
- package/dist-server/server/modules/providers/services/session-conversations-search.service.js +63 -7
- package/dist-server/server/modules/providers/services/session-conversations-search.service.js.map +1 -1
- package/dist-server/server/modules/providers/tests/provider.routes.test.js +35 -0
- package/dist-server/server/modules/providers/tests/provider.routes.test.js.map +1 -1
- package/dist-server/server/shared/utils.js +8 -0
- package/dist-server/server/shared/utils.js.map +1 -1
- package/package.json +6 -2
- package/scripts/promote-dist-server.mjs +50 -0
- package/server/index.ts +1 -2
- package/server/modules/agent/agent.module.ts +2 -1
- package/server/modules/auth/auth.middleware.ts +2 -2
- package/server/modules/browser-use/browser-use-runtime.ts +10 -0
- package/server/modules/browser-use/browser-use.service.ts +5 -8
- package/server/modules/browser-use/index.ts +3 -0
- package/server/modules/cli/cli.module.ts +0 -1
- package/server/modules/cli/cli.service.ts +0 -20
- package/server/modules/cli/cli.ts +9 -3
- package/server/modules/cli/tests/cli-environment-bootstrap.test.ts +186 -0
- package/server/modules/cli/tests/cli.service.test.ts +0 -3
- package/server/modules/file-tree/file-tree.service.ts +21 -10
- package/server/modules/file-tree/tests/file-tree.service.test.ts +65 -11
- package/server/modules/git/git-branch.service.ts +43 -0
- package/server/modules/git/git.routes.ts +16 -9
- package/server/modules/git/tests/git-branch.service.test.ts +60 -0
- package/server/modules/git/tests/git-init.routes.test.ts +83 -0
- package/server/modules/notifications/index.ts +2 -0
- package/server/modules/notifications/services/notification-orchestrator.service.js +24 -1
- package/server/modules/providers/list/claude/claude-runtime.provider.js +275 -54
- package/server/modules/providers/provider.routes.ts +5 -0
- package/server/modules/providers/services/session-conversations-search.service.ts +94 -7
- package/server/modules/providers/tests/provider.routes.test.ts +61 -0
- package/server/shared/types.ts +0 -1
- package/server/shared/utils.ts +10 -0
- package/server/tsconfig.json +6 -2
- package/README.de.md +0 -257
- package/README.ja.md +0 -249
- package/README.ko.md +0 -249
- package/README.md +0 -269
- package/README.ru.md +0 -257
- package/README.tr.md +0 -258
- package/README.zh-CN.md +0 -249
- package/README.zh-TW.md +0 -249
- package/dist/assets/index-D3jfzuKN.css +0 -32
- package/dist-server/server/constants/config.js +0 -6
- package/dist-server/server/constants/config.js.map +0 -1
- package/server/constants/config.ts +0 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudcli-ai/cloudcli",
|
|
3
|
-
"version": "1.37.
|
|
3
|
+
"version": "1.37.2",
|
|
4
4
|
"productName": "CloudCLI",
|
|
5
5
|
"description": "A web-based UI for Claude Code CLI",
|
|
6
6
|
"type": "module",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"scripts": {
|
|
30
30
|
"dev": "concurrently --kill-others \"npm run server:dev\" \"npm run client\"",
|
|
31
|
+
"preserver": "node scripts/promote-dist-server.mjs recover",
|
|
31
32
|
"server": "node dist-server/server/index.js",
|
|
32
33
|
"server:dev": "tsx --tsconfig server/tsconfig.json server/index.ts",
|
|
33
34
|
"server:dev-watch": "tsx watch --tsconfig server/tsconfig.json server/index.ts",
|
|
@@ -42,11 +43,13 @@
|
|
|
42
43
|
"desktop:icon:mac": "node electron/scripts/generate-macos-icon.js",
|
|
43
44
|
"build": "npm run build:client && npm run build:server",
|
|
44
45
|
"build:client": "vite build",
|
|
45
|
-
"prebuild:server": "node -e \"require('node:fs').rmSync('dist-server', { recursive: true, force: true })\"",
|
|
46
|
+
"prebuild:server": "node -e \"require('node:fs').rmSync('dist-server.next', { recursive: true, force: true })\"",
|
|
46
47
|
"build:server": "tsc -p server/tsconfig.json && tsc-alias -p server/tsconfig.json",
|
|
48
|
+
"postbuild:server": "node scripts/promote-dist-server.mjs",
|
|
47
49
|
"preview": "vite preview",
|
|
48
50
|
"typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p server/tsconfig.json",
|
|
49
51
|
"test": "tsx --tsconfig server/tsconfig.json --test \"server/**/*.test.ts\" \"server/**/*.test.js\"",
|
|
52
|
+
"test:client": "tsx --test \"src/**/*.test.ts\" \"src/**/*.test.tsx\"",
|
|
50
53
|
"lint": "eslint src/ server/",
|
|
51
54
|
"lint:fix": "eslint src/ server/ --fix",
|
|
52
55
|
"start": "npm run build && npm run server",
|
|
@@ -175,6 +178,7 @@
|
|
|
175
178
|
"jszip": "^3.10.1",
|
|
176
179
|
"katex": "^0.16.25",
|
|
177
180
|
"lucide-react": "^0.515.0",
|
|
181
|
+
"mermaid": "^11.16.1",
|
|
178
182
|
"mime-types": "^3.0.1",
|
|
179
183
|
"multer": "^2.0.1",
|
|
180
184
|
"node-fetch": "^2.7.0",
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// The server build stages into dist-server.next and this script promotes it
|
|
3
|
+
// into place. The live dist-server must never be deleted before the new build
|
|
4
|
+
// is complete: an interrupted build would otherwise leave the installation
|
|
5
|
+
// without a server entrypoint, crash-looping on MODULE_NOT_FOUND at every
|
|
6
|
+
// start until someone rebuilds it by hand.
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
|
|
9
|
+
const NEXT = 'dist-server.next';
|
|
10
|
+
const LIVE = 'dist-server';
|
|
11
|
+
const OLD = 'dist-server.old';
|
|
12
|
+
const ENTRY = 'server/index.js';
|
|
13
|
+
|
|
14
|
+
const mode = process.argv[2] ?? 'promote';
|
|
15
|
+
|
|
16
|
+
if (mode === 'recover') {
|
|
17
|
+
// Ran before every server start: if a promotion was interrupted between the
|
|
18
|
+
// two renames, put the previous build back so the server can boot.
|
|
19
|
+
if (!fs.existsSync(`${LIVE}/${ENTRY}`) && fs.existsSync(`${OLD}/${ENTRY}`)) {
|
|
20
|
+
console.error('promote-dist-server: restoring previous dist-server after an interrupted promotion.');
|
|
21
|
+
fs.rmSync(LIVE, { recursive: true, force: true });
|
|
22
|
+
fs.renameSync(OLD, LIVE);
|
|
23
|
+
}
|
|
24
|
+
process.exit(0);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (mode !== 'promote') {
|
|
28
|
+
console.error(`promote-dist-server: unknown mode "${mode}" (expected "promote" or "recover").`);
|
|
29
|
+
process.exit(64);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!fs.existsSync(`${NEXT}/${ENTRY}`)) {
|
|
33
|
+
console.error(`promote-dist-server: ${NEXT}/${ENTRY} is missing; leaving the current ${LIVE} untouched.`);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
fs.rmSync(OLD, { recursive: true, force: true });
|
|
38
|
+
if (fs.existsSync(LIVE)) {
|
|
39
|
+
fs.renameSync(LIVE, OLD);
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
fs.renameSync(NEXT, LIVE);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
// Put the previous build back before failing so the installation still boots.
|
|
45
|
+
if (fs.existsSync(OLD) && !fs.existsSync(LIVE)) {
|
|
46
|
+
fs.renameSync(OLD, LIVE);
|
|
47
|
+
}
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
fs.rmSync(OLD, { recursive: true, force: true });
|
package/server/index.ts
CHANGED
|
@@ -9,7 +9,7 @@ import http from 'http';
|
|
|
9
9
|
import express, { type NextFunction, type Request, type Response } from 'express';
|
|
10
10
|
import cors from 'cors';
|
|
11
11
|
|
|
12
|
-
import { AppError, findApplicationRoot, getModuleDirectory, terminalTextStyles } from '@/shared/utils.js';
|
|
12
|
+
import { AppError, findApplicationRoot, getModuleDirectory, IS_PLATFORM, terminalTextStyles } from '@/shared/utils.js';
|
|
13
13
|
import {
|
|
14
14
|
closeSessionsWatcher,
|
|
15
15
|
initializeSessionsWatcher,
|
|
@@ -50,7 +50,6 @@ import browserUseMcpRoutes from './modules/browser-use/browser-use-mcp.routes.js
|
|
|
50
50
|
import { browserUseService } from './modules/browser-use/browser-use.service.js';
|
|
51
51
|
import { initializeDatabase, sessionsDb } from './modules/database/index.js';
|
|
52
52
|
import { configureWebPush } from './modules/notifications/index.js';
|
|
53
|
-
import { IS_PLATFORM } from './constants/config.js';
|
|
54
53
|
|
|
55
54
|
const __dirname = getModuleDirectory(import.meta.url);
|
|
56
55
|
// The server source runs from /server, while the compiled output runs from /dist-server/server.
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
userDb,
|
|
13
13
|
} from '@/modules/database/index.js';
|
|
14
14
|
import { providerModelsService } from '@/modules/providers/index.js';
|
|
15
|
+
import { IS_PLATFORM } from '@/shared/utils.js';
|
|
15
16
|
|
|
16
17
|
import { createAgentRouter } from './agent.routes.js';
|
|
17
18
|
|
|
@@ -30,7 +31,7 @@ export function createAgentModule(externalDependencies: AgentExternalDependencie
|
|
|
30
31
|
crypto,
|
|
31
32
|
homeDirectory: os.homedir,
|
|
32
33
|
spawnProcess: spawn,
|
|
33
|
-
platformMode:
|
|
34
|
+
platformMode: IS_PLATFORM,
|
|
34
35
|
users: {
|
|
35
36
|
getFirstUser: () => userDb.getFirstUser(),
|
|
36
37
|
},
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// @ts-nocheck -- JWT request augmentation is narrowed by Auth route contracts.
|
|
2
2
|
import jwt from 'jsonwebtoken';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { IS_PLATFORM } from '@/shared/utils.js';
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
import { userDb, appConfigDb } from '../database/index.js';
|
|
7
7
|
|
|
8
8
|
// Use env var if set, otherwise auto-generate a unique secret per installation
|
|
9
9
|
const JWT_SECRET = process.env.JWT_SECRET || appConfigDb.getOrCreateJwtSecret();
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { IS_PLATFORM } from '@/shared/utils.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Resolves the runtime label exposed by Browser Use status and session APIs.
|
|
5
|
+
* The Browser Use service and CLI environment-mode regression coverage consume
|
|
6
|
+
* this after the server or CLI entrypoint has completed environment bootstrapping.
|
|
7
|
+
*/
|
|
8
|
+
export function getBrowserUseRuntime(): 'cloud' | 'local' {
|
|
9
|
+
return IS_PLATFORM ? 'cloud' : 'local';
|
|
10
|
+
}
|
|
@@ -11,15 +11,16 @@ import { appConfigDb } from '@/modules/database/index.js';
|
|
|
11
11
|
import { providerMcpService } from '@/modules/providers/index.js';
|
|
12
12
|
import { getModuleDirectory } from '@/shared/utils.js';
|
|
13
13
|
|
|
14
|
+
import { getBrowserUseRuntime } from './browser-use-runtime.js';
|
|
15
|
+
|
|
14
16
|
const require = createRequire(import.meta.url);
|
|
15
17
|
const __dirname = getModuleDirectory(import.meta.url);
|
|
16
|
-
const IS_PLATFORM = process.env.VITE_IS_PLATFORM === 'true';
|
|
17
18
|
const MAX_SESSIONS_PER_OWNER = Number.parseInt(process.env.CLOUDCLI_BROWSER_USE_MAX_SESSIONS_PER_OWNER || '3', 10);
|
|
18
19
|
const SESSION_TTL_MS = Number.parseInt(process.env.CLOUDCLI_BROWSER_USE_SESSION_TTL_MS || String(30 * 60 * 1000), 10);
|
|
19
20
|
const BROWSER_USE_SETTINGS_KEY = 'browser_use_settings';
|
|
20
21
|
const BROWSER_USE_MCP_TOKEN_KEY = 'browser_use_mcp_token';
|
|
21
22
|
|
|
22
|
-
type BrowserUseRuntime =
|
|
23
|
+
type BrowserUseRuntime = ReturnType<typeof getBrowserUseRuntime>;
|
|
23
24
|
type BrowserUseSessionStatus = 'ready' | 'stopped' | 'unavailable';
|
|
24
25
|
|
|
25
26
|
type BrowserUseSession = {
|
|
@@ -85,10 +86,6 @@ const MCP_SERVER_NAME = 'cloudcli-browser';
|
|
|
85
86
|
const LEGACY_MCP_SERVER_NAMES = ['cloudcli-browser-use'];
|
|
86
87
|
const RUNTIME_READINESS_CACHE_TTL_MS = 30_000;
|
|
87
88
|
|
|
88
|
-
function getRuntime(): BrowserUseRuntime {
|
|
89
|
-
return IS_PLATFORM ? 'cloud' : 'local';
|
|
90
|
-
}
|
|
91
|
-
|
|
92
89
|
function readSettings(): BrowserUseSettings {
|
|
93
90
|
try {
|
|
94
91
|
const raw = appConfigDb.get(BROWSER_USE_SETTINGS_KEY);
|
|
@@ -446,7 +443,7 @@ export const browserUseService = {
|
|
|
446
443
|
|
|
447
444
|
return {
|
|
448
445
|
enabled: settings.enabled,
|
|
449
|
-
runtime:
|
|
446
|
+
runtime: getBrowserUseRuntime(),
|
|
450
447
|
available,
|
|
451
448
|
playwrightInstalled: readiness.playwrightInstalled,
|
|
452
449
|
chromiumInstalled: readiness.chromiumInstalled,
|
|
@@ -515,7 +512,7 @@ export const browserUseService = {
|
|
|
515
512
|
id: randomUUID(),
|
|
516
513
|
ownerId: AGENT_OWNER_ID,
|
|
517
514
|
createdBy: 'agent',
|
|
518
|
-
runtime:
|
|
515
|
+
runtime: getBrowserUseRuntime(),
|
|
519
516
|
status: 'unavailable',
|
|
520
517
|
url: null,
|
|
521
518
|
title: null,
|
|
@@ -6,3 +6,6 @@
|
|
|
6
6
|
export async function startBrowserUseMcp(): Promise<void> {
|
|
7
7
|
await import('./browser-use-mcp.js');
|
|
8
8
|
}
|
|
9
|
+
|
|
10
|
+
// getBrowserUseRuntime: used by Browser Use services and environment-mode regression coverage.
|
|
11
|
+
export { getBrowserUseRuntime } from './browser-use-runtime.js';
|
|
@@ -28,7 +28,6 @@ export function createCliApplication(): CliApplication {
|
|
|
28
28
|
bugsUrl: packageMetadataJson.bugs?.url,
|
|
29
29
|
};
|
|
30
30
|
const fileSystem = {
|
|
31
|
-
readTextFile: (filePath: string) => fs.readFileSync(filePath, 'utf8'),
|
|
32
31
|
pathExists: (filePath: string) => fs.existsSync(filePath),
|
|
33
32
|
getFileStats: (filePath: string) => {
|
|
34
33
|
const stats = fs.statSync(filePath);
|
|
@@ -77,27 +77,7 @@ function isNewerVersion(candidateVersion: string, currentVersion: string): boole
|
|
|
77
77
|
return false;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
function loadEnvironmentFile(dependencies: CliServiceDependencies): void {
|
|
81
|
-
try {
|
|
82
|
-
const environmentFile = dependencies.fileSystem.readTextFile(
|
|
83
|
-
path.join(dependencies.applicationRoot, '.env'),
|
|
84
|
-
);
|
|
85
|
-
for (const line of environmentFile.split('\n')) {
|
|
86
|
-
const trimmedLine = line.trim();
|
|
87
|
-
if (!trimmedLine || trimmedLine.startsWith('#')) continue;
|
|
88
|
-
|
|
89
|
-
const [key, ...valueParts] = trimmedLine.split('=');
|
|
90
|
-
if (key && valueParts.length > 0 && !dependencies.environment[key]) {
|
|
91
|
-
dependencies.environment[key] = valueParts.join('=').trim();
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
} catch {
|
|
95
|
-
// The repository-level .env file is optional for every CLI command.
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
80
|
function showStatus(dependencies: CliServiceDependencies): void {
|
|
100
|
-
loadEnvironmentFile(dependencies);
|
|
101
81
|
const { environment, fileSystem, output } = dependencies;
|
|
102
82
|
const databasePath = environment.DATABASE_PATH || dependencies.defaultDatabasePath;
|
|
103
83
|
const databaseExists = fileSystem.pathExists(databasePath);
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// The CLI can reach shared environment constants through its composition root,
|
|
4
|
+
// so finish the root environment bootstrap before loading that import graph.
|
|
5
|
+
// eslint-disable-next-line boundaries/no-unknown
|
|
6
|
+
import '../../load-env.js';
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
async function runCli(): Promise<number> {
|
|
9
|
+
const { createCliApplication } = await import('./index.js');
|
|
10
|
+
return createCliApplication().run(process.argv.slice(2));
|
|
11
|
+
}
|
|
6
12
|
|
|
7
|
-
|
|
13
|
+
runCli().then((exitCode) => {
|
|
8
14
|
process.exitCode = exitCode;
|
|
9
15
|
}).catch((error: unknown) => {
|
|
10
16
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
3
|
+
import { copyFile, mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import test from 'node:test';
|
|
7
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
8
|
+
|
|
9
|
+
type BootstrapResult = {
|
|
10
|
+
command: string | null;
|
|
11
|
+
environmentFileReads: number;
|
|
12
|
+
mode: string | null;
|
|
13
|
+
authNextCalled: boolean;
|
|
14
|
+
authStatus: number;
|
|
15
|
+
authenticatedUsername: string | null;
|
|
16
|
+
runtime: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const RESULT_PREFIX = '__CLOUDCLI_BOOTSTRAP_RESULT__';
|
|
20
|
+
const testDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
const applicationRoot = path.resolve(testDirectory, '..', '..', '..', '..');
|
|
22
|
+
|
|
23
|
+
function sourceModuleUrl(...segments: string[]): string {
|
|
24
|
+
return pathToFileURL(path.join(applicationRoot, ...segments)).href;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function runCliBootstrapFixture(isPlatform: boolean): Promise<BootstrapResult> {
|
|
28
|
+
const fixtureRoot = await mkdtemp(path.join(os.tmpdir(), 'cloudcli-cli-bootstrap-'));
|
|
29
|
+
const fixtureServerDirectory = path.join(fixtureRoot, 'server');
|
|
30
|
+
const fixtureCliDirectory = path.join(fixtureServerDirectory, 'modules', 'cli');
|
|
31
|
+
const fixtureDatabasePath = path.join(fixtureRoot, 'auth.db');
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
await mkdir(fixtureCliDirectory, { recursive: true });
|
|
35
|
+
await Promise.all([
|
|
36
|
+
copyFile(
|
|
37
|
+
path.join(applicationRoot, 'server', 'load-env.ts'),
|
|
38
|
+
path.join(fixtureServerDirectory, 'load-env.ts'),
|
|
39
|
+
),
|
|
40
|
+
copyFile(
|
|
41
|
+
path.join(applicationRoot, 'server', 'modules', 'cli', 'cli.ts'),
|
|
42
|
+
path.join(fixtureCliDirectory, 'cli.ts'),
|
|
43
|
+
),
|
|
44
|
+
]);
|
|
45
|
+
await writeFile(
|
|
46
|
+
path.join(fixtureRoot, '.env'),
|
|
47
|
+
[
|
|
48
|
+
`VITE_IS_PLATFORM=${String(isPlatform)}`,
|
|
49
|
+
`DATABASE_PATH=${fixtureDatabasePath.replace(/\\/g, '/')}`,
|
|
50
|
+
].join('\n'),
|
|
51
|
+
'utf8',
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const fixtureIndexSource = `
|
|
55
|
+
import { createCliService } from ${JSON.stringify(sourceModuleUrl('server', 'modules', 'cli', 'cli.service.ts'))};
|
|
56
|
+
import { authenticateToken } from ${JSON.stringify(sourceModuleUrl('server', 'modules', 'auth', 'index.ts'))};
|
|
57
|
+
import { getBrowserUseRuntime } from ${JSON.stringify(sourceModuleUrl('server', 'modules', 'browser-use', 'index.ts'))};
|
|
58
|
+
import { closeConnection, initializeDatabase, userDb } from ${JSON.stringify(sourceModuleUrl('server', 'modules', 'database', 'index.ts'))};
|
|
59
|
+
|
|
60
|
+
let environmentFileReads = 0;
|
|
61
|
+
|
|
62
|
+
export function createCliApplication() {
|
|
63
|
+
const service = createCliService({
|
|
64
|
+
applicationRoot: ${JSON.stringify(fixtureRoot)},
|
|
65
|
+
defaultDatabasePath: ${JSON.stringify(fixtureDatabasePath)},
|
|
66
|
+
homeDirectory: ${JSON.stringify(fixtureRoot)},
|
|
67
|
+
packageMetadata: { version: '1.0.0' },
|
|
68
|
+
environment: process.env,
|
|
69
|
+
fileSystem: {
|
|
70
|
+
readTextFile: () => {
|
|
71
|
+
environmentFileReads += 1;
|
|
72
|
+
return '';
|
|
73
|
+
},
|
|
74
|
+
pathExists: () => false,
|
|
75
|
+
getFileStats: () => ({ size: 0, modifiedAt: new Date(0) }),
|
|
76
|
+
},
|
|
77
|
+
output: { log: () => undefined, error: () => undefined },
|
|
78
|
+
sandboxService: { execute: async () => 0 },
|
|
79
|
+
getLatestPackageVersion: async () => '1.0.0',
|
|
80
|
+
updateGlobalPackage: () => undefined,
|
|
81
|
+
startServer: async () => undefined,
|
|
82
|
+
startBrowserUseMcp: async () => undefined,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
async run(argumentsList: string[]) {
|
|
87
|
+
const exitCode = await service.run(argumentsList);
|
|
88
|
+
await initializeDatabase();
|
|
89
|
+
userDb.createUser('bootstrap-user', 'password-hash');
|
|
90
|
+
|
|
91
|
+
const request: any = { headers: {}, query: {} };
|
|
92
|
+
const response: any = {
|
|
93
|
+
statusCode: 200,
|
|
94
|
+
body: null,
|
|
95
|
+
headers: {},
|
|
96
|
+
setHeader(name: string, value: string) {
|
|
97
|
+
this.headers[name] = value;
|
|
98
|
+
},
|
|
99
|
+
status(statusCode: number) {
|
|
100
|
+
this.statusCode = statusCode;
|
|
101
|
+
return this;
|
|
102
|
+
},
|
|
103
|
+
json(body: unknown) {
|
|
104
|
+
this.body = body;
|
|
105
|
+
return this;
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
let authNextCalled = false;
|
|
109
|
+
await authenticateToken(request, response, () => {
|
|
110
|
+
authNextCalled = true;
|
|
111
|
+
});
|
|
112
|
+
const runtime = getBrowserUseRuntime();
|
|
113
|
+
|
|
114
|
+
console.log(${JSON.stringify(RESULT_PREFIX)} + JSON.stringify({
|
|
115
|
+
command: argumentsList[0] ?? null,
|
|
116
|
+
environmentFileReads,
|
|
117
|
+
mode: process.env.VITE_IS_PLATFORM ?? null,
|
|
118
|
+
authNextCalled,
|
|
119
|
+
authStatus: response.statusCode,
|
|
120
|
+
authenticatedUsername: request.user?.username ?? null,
|
|
121
|
+
runtime,
|
|
122
|
+
}));
|
|
123
|
+
closeConnection();
|
|
124
|
+
return exitCode;
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
`;
|
|
129
|
+
await writeFile(path.join(fixtureCliDirectory, 'index.ts'), fixtureIndexSource, 'utf8');
|
|
130
|
+
|
|
131
|
+
const childEnvironment = { ...process.env };
|
|
132
|
+
delete childEnvironment.VITE_IS_PLATFORM;
|
|
133
|
+
delete childEnvironment.DATABASE_PATH;
|
|
134
|
+
const stdout = execFileSync(
|
|
135
|
+
process.execPath,
|
|
136
|
+
[
|
|
137
|
+
path.join(applicationRoot, 'node_modules', 'tsx', 'dist', 'cli.mjs'),
|
|
138
|
+
'--tsconfig',
|
|
139
|
+
path.join(applicationRoot, 'server', 'tsconfig.json'),
|
|
140
|
+
path.join(fixtureCliDirectory, 'cli.ts'),
|
|
141
|
+
'status',
|
|
142
|
+
],
|
|
143
|
+
{
|
|
144
|
+
cwd: applicationRoot,
|
|
145
|
+
encoding: 'utf8',
|
|
146
|
+
env: childEnvironment,
|
|
147
|
+
timeout: 30_000,
|
|
148
|
+
},
|
|
149
|
+
);
|
|
150
|
+
const resultLine = stdout
|
|
151
|
+
.split(/\r?\n/)
|
|
152
|
+
.find((line) => line.startsWith(RESULT_PREFIX));
|
|
153
|
+
assert.ok(resultLine, `Missing bootstrap result in child output:\n${stdout}`);
|
|
154
|
+
return JSON.parse(resultLine.slice(RESULT_PREFIX.length)) as BootstrapResult;
|
|
155
|
+
} finally {
|
|
156
|
+
await rm(fixtureRoot, { recursive: true, force: true });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
test('CLI bootstrap selects Platform authentication and cloud runtime from .env', async () => {
|
|
161
|
+
const result = await runCliBootstrapFixture(true);
|
|
162
|
+
|
|
163
|
+
assert.deepEqual(result, {
|
|
164
|
+
command: 'status',
|
|
165
|
+
environmentFileReads: 0,
|
|
166
|
+
mode: 'true',
|
|
167
|
+
authNextCalled: true,
|
|
168
|
+
authStatus: 200,
|
|
169
|
+
authenticatedUsername: 'bootstrap-user',
|
|
170
|
+
runtime: 'cloud',
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test('CLI bootstrap selects OSS authentication and local runtime from .env', async () => {
|
|
175
|
+
const result = await runCliBootstrapFixture(false);
|
|
176
|
+
|
|
177
|
+
assert.deepEqual(result, {
|
|
178
|
+
command: 'status',
|
|
179
|
+
environmentFileReads: 0,
|
|
180
|
+
mode: 'false',
|
|
181
|
+
authNextCalled: false,
|
|
182
|
+
authStatus: 401,
|
|
183
|
+
authenticatedUsername: null,
|
|
184
|
+
runtime: 'local',
|
|
185
|
+
});
|
|
186
|
+
});
|
|
@@ -11,9 +11,13 @@ import type {
|
|
|
11
11
|
} from '@/shared/types.js';
|
|
12
12
|
import { AppError, FORBIDDEN_WORKSPACE_PATHS, normalizeProjectPath } from '@/shared/utils.js';
|
|
13
13
|
|
|
14
|
+
const HARD_EXCLUDED_DIRECTORY_NAMES = new Set([
|
|
15
|
+
'node_modules', '.git', '.svn', '.hg',
|
|
16
|
+
]);
|
|
17
|
+
|
|
14
18
|
const IGNORED_DIRECTORY_NAMES = new Set([
|
|
15
|
-
|
|
16
|
-
'.
|
|
19
|
+
...HARD_EXCLUDED_DIRECTORY_NAMES,
|
|
20
|
+
'dist', 'build', '.next', '.nuxt', '.cache', '.parcel-cache',
|
|
17
21
|
'__pycache__', '.pytest_cache', '.mypy_cache', '.tox', 'venv', '.venv',
|
|
18
22
|
'target', 'vendor',
|
|
19
23
|
'.gradle', '.idea', 'coverage', '.nyc_output',
|
|
@@ -36,6 +40,15 @@ const MAXIMUM_FILE_TREE_ENTRIES = 10_000;
|
|
|
36
40
|
|
|
37
41
|
type FileTreeEntryFilter = (entryPath: string, isDirectory: boolean) => boolean;
|
|
38
42
|
|
|
43
|
+
function includeEntryByHardExclusions(entryPath: string, isDirectory: boolean): boolean {
|
|
44
|
+
return !isDirectory || !HARD_EXCLUDED_DIRECTORY_NAMES.has(path.basename(entryPath));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function includeEntryByFallbackDirectoryNames(entryPath: string, isDirectory: boolean): boolean {
|
|
48
|
+
return includeEntryByHardExclusions(entryPath, isDirectory)
|
|
49
|
+
&& (!isDirectory || !IGNORED_DIRECTORY_NAMES.has(path.basename(entryPath)));
|
|
50
|
+
}
|
|
51
|
+
|
|
39
52
|
function createFileTreeError(message: string, statusCode: number, code: string): AppError {
|
|
40
53
|
return new AppError(message, { statusCode, code });
|
|
41
54
|
}
|
|
@@ -146,6 +159,8 @@ function createGitignoreEntryFilter(
|
|
|
146
159
|
const gitignore = ignore().add(gitignoreContent);
|
|
147
160
|
|
|
148
161
|
return (entryPath, isDirectory) => {
|
|
162
|
+
if (!includeEntryByHardExclusions(entryPath, isDirectory)) return false;
|
|
163
|
+
|
|
149
164
|
const relativePath = path.relative(projectRoot, entryPath).split(path.sep).join('/');
|
|
150
165
|
const matchPath = isDirectory ? `${relativePath}/` : relativePath;
|
|
151
166
|
return !gitignore.ignores(matchPath);
|
|
@@ -191,17 +206,13 @@ export function createFileTreeService(dependencies: FileTreeServiceDependencies)
|
|
|
191
206
|
try {
|
|
192
207
|
for await (const entry of fileSystem.openDirectory(directoryPath)) {
|
|
193
208
|
const isDirectory = entry.isDirectory();
|
|
194
|
-
if (isDirectory && IGNORED_DIRECTORY_NAMES.has(entry.name)) {
|
|
195
|
-
continue;
|
|
196
|
-
}
|
|
197
209
|
if (!includeEntry(path.join(directoryPath, entry.name), isDirectory)) {
|
|
198
210
|
continue;
|
|
199
211
|
}
|
|
200
212
|
|
|
201
|
-
// Charged after
|
|
202
|
-
// .
|
|
203
|
-
//
|
|
204
|
-
// rather than per directory.
|
|
213
|
+
// Charged after visibility filtering so ignored entries never consume
|
|
214
|
+
// the budget. Every recursive branch shares one counter, so the cap
|
|
215
|
+
// applies to the whole tree rather than per directory.
|
|
205
216
|
if (remainingEntries.value <= 0) {
|
|
206
217
|
throw createFileTreeError(
|
|
207
218
|
`Project file tree exceeds the ${MAXIMUM_FILE_TREE_ENTRIES.toLocaleString()} entry limit. Choose a narrower project directory or add ignore rules.`,
|
|
@@ -233,7 +244,7 @@ export function createFileTreeService(dependencies: FileTreeServiceDependencies)
|
|
|
233
244
|
directoryPath: string,
|
|
234
245
|
maximumDepth: number,
|
|
235
246
|
currentDepth = 0,
|
|
236
|
-
includeEntry: FileTreeEntryFilter =
|
|
247
|
+
includeEntry: FileTreeEntryFilter = includeEntryByFallbackDirectoryNames,
|
|
237
248
|
remainingEntries = { value: MAXIMUM_FILE_TREE_ENTRIES },
|
|
238
249
|
): Promise<FileTreeNode[]> {
|
|
239
250
|
const visibleEntries = await collectVisibleEntries(directoryPath, includeEntry, remainingEntries);
|
|
@@ -87,37 +87,67 @@ function createDependencies(
|
|
|
87
87
|
};
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
test('listProjectFiles
|
|
90
|
+
test('listProjectFiles applies gitignore alongside hard directory exclusions', async () => {
|
|
91
91
|
const projectRoot = path.resolve('file-tree-test-project');
|
|
92
|
+
const documentationDirectory = path.join(projectRoot, 'docs');
|
|
93
|
+
const buildDocumentationDirectory = path.join(documentationDirectory, 'build');
|
|
94
|
+
const gitDirectory = path.join(projectRoot, '.git');
|
|
95
|
+
const nodeModulesDirectory = path.join(projectRoot, 'node_modules');
|
|
92
96
|
const sourceDirectory = path.join(projectRoot, 'src');
|
|
97
|
+
const readDirectories: string[] = [];
|
|
93
98
|
const fileSystem = createFakeFileSystem({
|
|
94
99
|
access: async () => undefined,
|
|
100
|
+
readTextFile: async (filePath) => {
|
|
101
|
+
assert.equal(filePath, path.join(projectRoot, '.gitignore'));
|
|
102
|
+
return '*.log';
|
|
103
|
+
},
|
|
95
104
|
openDirectory: createDirectoryReader((directoryPath) => {
|
|
105
|
+
readDirectories.push(directoryPath);
|
|
96
106
|
if (directoryPath === projectRoot) {
|
|
97
107
|
return [
|
|
108
|
+
createDirectoryEntry('.git', true),
|
|
98
109
|
createDirectoryEntry('node_modules', true),
|
|
99
110
|
createDirectoryEntry('README.md', false),
|
|
111
|
+
createDirectoryEntry('docs', true),
|
|
100
112
|
createDirectoryEntry('src', true),
|
|
101
113
|
];
|
|
102
114
|
}
|
|
115
|
+
if (directoryPath === documentationDirectory) {
|
|
116
|
+
return [createDirectoryEntry('build', true)];
|
|
117
|
+
}
|
|
118
|
+
if (directoryPath === buildDocumentationDirectory) {
|
|
119
|
+
return [createDirectoryEntry('foo.md', false)];
|
|
120
|
+
}
|
|
103
121
|
if (directoryPath === sourceDirectory) {
|
|
104
122
|
return [createDirectoryEntry('index.ts', false)];
|
|
105
123
|
}
|
|
106
124
|
return [];
|
|
107
125
|
}),
|
|
108
|
-
lstat: async (candidatePath) => createStats(
|
|
126
|
+
lstat: async (candidatePath) => createStats(
|
|
127
|
+
candidatePath === documentationDirectory
|
|
128
|
+
|| candidatePath === buildDocumentationDirectory
|
|
129
|
+
|| candidatePath === sourceDirectory
|
|
130
|
+
|| candidatePath === gitDirectory
|
|
131
|
+
|| candidatePath === nodeModulesDirectory,
|
|
132
|
+
0o754,
|
|
133
|
+
),
|
|
109
134
|
});
|
|
110
135
|
const service = createFileTreeService(createDependencies(fileSystem, projectRoot));
|
|
111
136
|
|
|
112
|
-
const tree = await service.listProjectFiles('project-1');
|
|
137
|
+
const tree = await service.listProjectFiles('project-1', { respectGitignore: true });
|
|
113
138
|
|
|
114
|
-
assert.deepEqual(tree.map((entry) => entry.name), ['src', 'README.md']);
|
|
115
|
-
const
|
|
139
|
+
assert.deepEqual(tree.map((entry) => entry.name), ['docs', 'src', 'README.md']);
|
|
140
|
+
const documentationEntry = tree[0];
|
|
141
|
+
assert.deepEqual(documentationEntry?.children?.map((entry) => entry.name), ['build']);
|
|
142
|
+
assert.deepEqual(documentationEntry?.children?.[0]?.children?.map((entry) => entry.name), ['foo.md']);
|
|
143
|
+
const sourceEntry = tree[1];
|
|
116
144
|
assert.ok(sourceEntry);
|
|
117
145
|
assert.equal(sourceEntry.type, 'directory');
|
|
118
146
|
assert.equal(sourceEntry.permissions, '754');
|
|
119
147
|
assert.equal(sourceEntry.permissionsRwx, 'rwxr-xr--');
|
|
120
148
|
assert.deepEqual(sourceEntry.children?.map((entry) => entry.name), ['index.ts']);
|
|
149
|
+
assert.equal(readDirectories.includes(gitDirectory), false);
|
|
150
|
+
assert.equal(readDirectories.includes(nodeModulesDirectory), false);
|
|
121
151
|
});
|
|
122
152
|
|
|
123
153
|
test('listProjectFiles excludes gitignored entries only when requested', async () => {
|
|
@@ -167,23 +197,47 @@ test('listProjectFiles excludes gitignored entries only when requested', async (
|
|
|
167
197
|
assert.equal(readDirectories.includes(cacheDirectory), false);
|
|
168
198
|
});
|
|
169
199
|
|
|
170
|
-
test('listProjectFiles
|
|
200
|
+
test('listProjectFiles falls back to conventional directory names when no gitignore exists', async () => {
|
|
171
201
|
const projectRoot = path.resolve('file-tree-test-project');
|
|
202
|
+
const documentationDirectory = path.join(projectRoot, 'docs');
|
|
203
|
+
const buildDocumentationDirectory = path.join(documentationDirectory, 'build');
|
|
204
|
+
const nodeModulesDirectory = path.join(projectRoot, 'node_modules');
|
|
205
|
+
const readDirectories: string[] = [];
|
|
172
206
|
const fileSystem = createFakeFileSystem({
|
|
173
207
|
access: async () => undefined,
|
|
174
208
|
readTextFile: async () => {
|
|
175
209
|
throw Object.assign(new Error('missing'), { code: 'ENOENT' });
|
|
176
210
|
},
|
|
177
|
-
openDirectory: createDirectoryReader((directoryPath) =>
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
211
|
+
openDirectory: createDirectoryReader((directoryPath) => {
|
|
212
|
+
readDirectories.push(directoryPath);
|
|
213
|
+
if (directoryPath === projectRoot) {
|
|
214
|
+
return [
|
|
215
|
+
createDirectoryEntry('debug.log', false),
|
|
216
|
+
createDirectoryEntry('docs', true),
|
|
217
|
+
createDirectoryEntry('node_modules', true),
|
|
218
|
+
];
|
|
219
|
+
}
|
|
220
|
+
if (directoryPath === documentationDirectory) {
|
|
221
|
+
return [
|
|
222
|
+
createDirectoryEntry('build', true),
|
|
223
|
+
createDirectoryEntry('guide.md', false),
|
|
224
|
+
];
|
|
225
|
+
}
|
|
226
|
+
if (directoryPath === buildDocumentationDirectory) {
|
|
227
|
+
return [createDirectoryEntry('generated.md', false)];
|
|
228
|
+
}
|
|
229
|
+
return [];
|
|
230
|
+
}),
|
|
231
|
+
lstat: async (candidatePath) => createStats(candidatePath === documentationDirectory, 0o644),
|
|
181
232
|
});
|
|
182
233
|
const service = createFileTreeService(createDependencies(fileSystem, projectRoot));
|
|
183
234
|
|
|
184
235
|
const tree = await service.listProjectFiles('project-1', { respectGitignore: true });
|
|
185
236
|
|
|
186
|
-
assert.deepEqual(tree.map((entry) => entry.name), ['debug.log']);
|
|
237
|
+
assert.deepEqual(tree.map((entry) => entry.name), ['docs', 'debug.log']);
|
|
238
|
+
assert.deepEqual(tree[0]?.children?.map((entry) => entry.name), ['guide.md']);
|
|
239
|
+
assert.equal(readDirectories.includes(nodeModulesDirectory), false);
|
|
240
|
+
assert.equal(readDirectories.includes(buildDocumentationDirectory), false);
|
|
187
241
|
});
|
|
188
242
|
|
|
189
243
|
test('listProjectFiles rejects a tree that exceeds the server entry limit', async () => {
|