march-cli 0.1.24 → 0.1.25
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/bin/march.mjs +13 -13
- package/package.json +49 -49
- package/src/agent/command-exec-tool.mjs +172 -172
- package/src/agent/context-stats-tool.mjs +57 -57
- package/src/agent/editing/diff-apply.mjs +28 -28
- package/src/agent/editing/diff-format.mjs +57 -57
- package/src/agent/editing/lsp-report.mjs +69 -69
- package/src/agent/file-edit-tool.mjs +250 -250
- package/src/agent/file-tools/read-file-tool.mjs +112 -112
- package/src/agent/file-tools/read-image-tool.mjs +76 -76
- package/src/agent/model-payload-dumper.mjs +208 -208
- package/src/agent/pi-session/pi-session-sidecar-failure.mjs +10 -10
- package/src/agent/provider/payload-messages.mjs +138 -138
- package/src/agent/runner/codex-large-context-guard.mjs +87 -87
- package/src/agent/runner/codex-transport-compression.mjs +180 -180
- package/src/agent/runner/codex-transport-debug.mjs +113 -113
- package/src/agent/runner/codex-websocket-event-debug.mjs +130 -130
- package/src/agent/runner/fast-model.mjs +36 -36
- package/src/agent/runner/runner-cleanup.mjs +12 -12
- package/src/agent/runner/runner-init.mjs +15 -15
- package/src/agent/runner/runner-session-state.mjs +40 -40
- package/src/agent/runner/runner-utils.mjs +24 -24
- package/src/agent/runner.mjs +299 -299
- package/src/agent/runtime/ipc/ipc-peer.mjs +99 -99
- package/src/agent/runtime/ipc/process-ipc-transport.mjs +16 -16
- package/src/agent/runtime/remote-runner-client.mjs +73 -73
- package/src/agent/runtime/remote-ui-client.mjs +20 -20
- package/src/agent/runtime/runner-ipc-target.mjs +125 -125
- package/src/agent/runtime/runner-process-client.mjs +47 -47
- package/src/agent/runtime/runner-process-entry.mjs +11 -11
- package/src/agent/runtime/runner-process-factory.mjs +111 -108
- package/src/agent/runtime/runner-runtime-host.mjs +79 -79
- package/src/agent/runtime/runtime-factory.mjs +42 -42
- package/src/agent/runtime/runtime-host.mjs +34 -34
- package/src/agent/runtime/ui-event-bridge.mjs +95 -95
- package/src/agent/screen-tools/list-windows-tool.mjs +39 -39
- package/src/agent/screen-tools/screen-tool.mjs +49 -49
- package/src/agent/screen-tools/windows-screen.mjs +133 -133
- package/src/agent/session/session-auto-name.mjs +41 -41
- package/src/agent/session/session-binding.mjs +12 -12
- package/src/agent/session/session-options.mjs +47 -47
- package/src/agent/tool-names.mjs +1 -1
- package/src/agent/tool-result.mjs +3 -3
- package/src/agent/tool-summary.mjs +112 -112
- package/src/agent/tools.mjs +58 -58
- package/src/agent/turn/turn-events.mjs +111 -111
- package/src/agent/turn/turn-logging.mjs +30 -30
- package/src/agent/turn/turn-runner.mjs +196 -196
- package/src/agent/vision-capability.mjs +14 -14
- package/src/auth/login-command.mjs +90 -90
- package/src/auth/storage.mjs +34 -34
- package/src/cli/args.mjs +96 -79
- package/src/cli/commands/copy-command.mjs +87 -87
- package/src/cli/commands/export-command.mjs +206 -206
- package/src/cli/commands/extensions-command.mjs +53 -53
- package/src/cli/commands/help-command.mjs +7 -7
- package/src/cli/commands/model-command.mjs +141 -141
- package/src/cli/commands/paste-image-command.mjs +43 -43
- package/src/cli/commands/provider-command.mjs +59 -59
- package/src/cli/commands/status-command.mjs +196 -194
- package/src/cli/commands/thinking-command.mjs +87 -87
- package/src/cli/fallback-ui.mjs +156 -156
- package/src/cli/input/attachment-tokens.mjs +20 -20
- package/src/cli/input/autocomplete.mjs +74 -74
- package/src/cli/input/external-editor.mjs +39 -39
- package/src/cli/input/file-search/index.mjs +160 -160
- package/src/cli/input/history-store.mjs +35 -35
- package/src/cli/input/image-clipboard.mjs +55 -55
- package/src/cli/input/keybinding-dispatch.mjs +76 -76
- package/src/cli/input/keybindings.mjs +96 -96
- package/src/cli/input/mode-state.mjs +43 -43
- package/src/cli/input/prompt-templates.mjs +84 -84
- package/src/cli/input/select-with-keyboard.mjs +86 -86
- package/src/cli/permissions.mjs +103 -103
- package/src/cli/repl-commands.mjs +86 -86
- package/src/cli/repl-loop.mjs +183 -183
- package/src/cli/selector-list.mjs +21 -21
- package/src/cli/session/pi-session-switch-command.mjs +41 -41
- package/src/cli/session/session-command.mjs +23 -23
- package/src/cli/session/session-list-command.mjs +68 -68
- package/src/cli/session/session-name-command.mjs +26 -26
- package/src/cli/session/session-source-command.mjs +89 -89
- package/src/cli/session/session-switch-command.mjs +1 -1
- package/src/cli/shell/shell-command.mjs +55 -55
- package/src/cli/shell/shell-drawer-controls.mjs +33 -33
- package/src/cli/shell/shell-drawer.mjs +192 -192
- package/src/cli/shell/shell-split-layout.mjs +70 -70
- package/src/cli/slash-commands.mjs +192 -192
- package/src/cli/startup/create-runtime-runner.mjs +61 -61
- package/src/cli/startup/runtime-close.mjs +23 -23
- package/src/cli/startup/startup-banner.mjs +71 -71
- package/src/cli/startup/startup-session.mjs +51 -51
- package/src/cli/status-line-updater.mjs +75 -75
- package/src/cli/tool-output.mjs +9 -9
- package/src/cli/tui/editor/external-editor-runner.mjs +24 -24
- package/src/cli/tui/input/mouse-selection-controller.mjs +91 -91
- package/src/cli/tui/input/mouse-tracking.mjs +20 -20
- package/src/cli/tui/layout/main-pane-layout.mjs +47 -47
- package/src/cli/tui/layout/safe-render-boundary.mjs +46 -46
- package/src/cli/tui/markdown-renderer.mjs +285 -285
- package/src/cli/tui/output/scroll-state.mjs +79 -79
- package/src/cli/tui/output/text-line-renderer.mjs +50 -50
- package/src/cli/tui/output/tool-card-renderer.mjs +59 -59
- package/src/cli/tui/output/visible-lines.mjs +8 -8
- package/src/cli/tui/output-buffer.mjs +293 -293
- package/src/cli/tui/permission-request-ui.mjs +18 -18
- package/src/cli/tui/recall-rendering.mjs +28 -25
- package/src/cli/tui/render/render-scheduler.mjs +26 -26
- package/src/cli/tui/render/stream-delta-buffer.mjs +46 -46
- package/src/cli/tui/select/editor-select-list.mjs +111 -111
- package/src/cli/tui/selection-screen.mjs +269 -269
- package/src/cli/tui/status/retry-status.mjs +72 -72
- package/src/cli/tui/status/spinner-status.mjs +42 -42
- package/src/cli/tui/status/status-bar.mjs +225 -225
- package/src/cli/tui/syntax/highlighting.mjs +260 -260
- package/src/cli/tui/syntax/languages.mjs +91 -91
- package/src/cli/tui/syntax/tree-sitter/bash.highlights.scm +261 -261
- package/src/cli/tui/syntax/tree-sitter/c.highlights.scm +341 -341
- package/src/cli/tui/syntax/tree-sitter/cpp.highlights.scm +268 -268
- package/src/cli/tui/syntax/tree-sitter/csharp.highlights.scm +577 -577
- package/src/cli/tui/syntax/tree-sitter/css.highlights.scm +109 -109
- package/src/cli/tui/syntax/tree-sitter/diff.highlights.scm +49 -49
- package/src/cli/tui/syntax/tree-sitter/go.highlights.scm +254 -254
- package/src/cli/tui/syntax/tree-sitter/html.highlights.scm +13 -13
- package/src/cli/tui/syntax/tree-sitter/java.highlights.scm +330 -330
- package/src/cli/tui/syntax/tree-sitter/json.highlights.scm +38 -38
- package/src/cli/tui/syntax/tree-sitter/php.highlights.scm +203 -203
- package/src/cli/tui/syntax/tree-sitter/python.highlights.scm +137 -137
- package/src/cli/tui/syntax/tree-sitter/ruby.highlights.scm +309 -309
- package/src/cli/tui/syntax/tree-sitter/rust.highlights.scm +531 -531
- package/src/cli/tui/syntax/tree-sitter/toml.highlights.scm +39 -39
- package/src/cli/tui/syntax/tree-sitter/tsx.highlights.scm +35 -35
- package/src/cli/tui/syntax/tree-sitter/typescript.highlights.scm +35 -35
- package/src/cli/tui/syntax/tree-sitter/yaml.highlights.scm +99 -99
- package/src/cli/tui/tool-rendering.mjs +87 -87
- package/src/cli/tui/tui-diff-rendering.mjs +157 -157
- package/src/cli/tui/tui-handlers.mjs +111 -111
- package/src/cli/tui/tui-input-controller.mjs +61 -61
- package/src/cli/tui/ui-theme.mjs +157 -157
- package/src/cli/ui.mjs +297 -297
- package/src/config/config-json.mjs +108 -84
- package/src/config/dotenv.mjs +20 -20
- package/src/config/features.mjs +75 -75
- package/src/config/loader.mjs +156 -143
- package/src/config/settings-command.mjs +97 -97
- package/src/context/engine.mjs +199 -198
- package/src/context/injections.mjs +26 -26
- package/src/context/profiles.mjs +39 -39
- package/src/context/project-context.mjs +20 -20
- package/src/context/session-status.mjs +25 -17
- package/src/context/shell-layers.mjs +23 -23
- package/src/context/system-core/base.md +50 -50
- package/src/context/system-core/prompts/deepseek-v4-pro.md +3 -3
- package/src/context/system-core/prompts/default.md +3 -3
- package/src/context/system-core.mjs +35 -35
- package/src/debug/logger.mjs +141 -141
- package/src/debug/model-context-dumper.mjs +52 -52
- package/src/extensions/discovery.mjs +40 -40
- package/src/extensions/lifecycle-adapter.mjs +210 -210
- package/src/extensions/lifecycle-manifest.mjs +69 -69
- package/src/image-gen/index.mjs +7 -7
- package/src/image-gen/provider.mjs +231 -231
- package/src/image-gen/tool.mjs +84 -84
- package/src/lsp/client.mjs +257 -257
- package/src/lsp/diagnostic-store.mjs +42 -42
- package/src/lsp/diagnostics-format.mjs +72 -72
- package/src/lsp/managed-node-server.mjs +99 -99
- package/src/lsp/path-match.mjs +10 -10
- package/src/lsp/server-definitions.mjs +188 -188
- package/src/lsp/servers.mjs +165 -165
- package/src/lsp/service.mjs +110 -110
- package/src/lsp/status-message.mjs +9 -9
- package/src/lsp/typescript-project-resolver.mjs +186 -186
- package/src/main.mjs +294 -299
- package/src/mcp/client.mjs +195 -195
- package/src/mcp/config.mjs +130 -130
- package/src/mcp/index.mjs +48 -48
- package/src/mcp/tools.mjs +98 -98
- package/src/memory/command.mjs +120 -0
- package/src/memory/markdown/markdown-delete.mjs +23 -23
- package/src/memory/markdown/markdown-format.mjs +128 -128
- package/src/memory/markdown/markdown-recall.mjs +28 -28
- package/src/memory/markdown/ripgrep.mjs +16 -16
- package/src/memory/markdown/sqlite-index.mjs +87 -87
- package/src/memory/markdown-store.mjs +272 -286
- package/src/memory/markdown-tools.mjs +174 -103
- package/src/memory/remote/client.mjs +68 -0
- package/src/memory/remote/config.mjs +52 -0
- package/src/memory/remote/server.mjs +99 -0
- package/src/memory/search.mjs +183 -0
- package/src/network/environment.mjs +131 -131
- package/src/notification/desktop-notifier.mjs +262 -262
- package/src/platform/open-file.mjs +28 -28
- package/src/platform/spawn-command.mjs +27 -27
- package/src/provider/accept-command.mjs +89 -89
- package/src/provider/command.mjs +21 -21
- package/src/provider/config-command.mjs +129 -129
- package/src/provider/custom-provider.mjs +113 -113
- package/src/provider/hosted-tools.mjs +111 -111
- package/src/provider/presets.mjs +72 -72
- package/src/provider/share-command.mjs +79 -79
- package/src/provider/share-payload.mjs +52 -52
- package/src/session/attachment-display.mjs +16 -16
- package/src/session/attachment-references.mjs +65 -65
- package/src/session/attachments.mjs +140 -140
- package/src/session/persist.mjs +1 -1
- package/src/session/pi-manager.mjs +34 -34
- package/src/session/session-utils.mjs +16 -16
- package/src/session/sidecar-sync.mjs +19 -19
- package/src/session/sidecar.mjs +69 -69
- package/src/session/transcript.mjs +83 -83
- package/src/session/tree.mjs +42 -42
- package/src/shell/cli-runtime.mjs +11 -11
- package/src/shell/hints.mjs +12 -12
- package/src/shell/node-pty-adapter.mjs +81 -81
- package/src/shell/runtime-state.mjs +126 -126
- package/src/shell/runtime.mjs +252 -252
- package/src/shell/screen-buffer.mjs +136 -136
- package/src/shell/tool-read.mjs +74 -74
- package/src/shell/tools.mjs +299 -299
- package/src/supergrok/actions/image-generate.mjs +60 -60
- package/src/supergrok/actions/search.mjs +78 -78
- package/src/supergrok/auth.mjs +36 -36
- package/src/supergrok/constants.mjs +18 -18
- package/src/supergrok/oauth-provider.mjs +278 -278
- package/src/supergrok/provider.mjs +35 -35
- package/src/supergrok/response.mjs +76 -76
- package/src/supergrok/tool.mjs +61 -61
- package/src/text/ansi.mjs +3 -3
- package/src/web/config-command.mjs +43 -43
- package/src/web/fetch.mjs +78 -78
- package/src/web/presets.mjs +16 -16
- package/src/web/search.mjs +83 -83
- package/src/web/tools.mjs +107 -107
|
@@ -1,186 +1,186 @@
|
|
|
1
|
-
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
2
|
-
import { dirname, join, relative, resolve } from "node:path";
|
|
3
|
-
|
|
4
|
-
const CONFIG_RE = /^(?:tsconfig(?:\..+)?|jsconfig)\.json$/;
|
|
5
|
-
const DEFAULT_EXCLUDES = ["node_modules", "bower_components", "jspm_packages"];
|
|
6
|
-
const MATCH_EXTENSIONS = [".ts", ".tsx", ".d.ts", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"];
|
|
7
|
-
|
|
8
|
-
export function resolveTypeScriptProjectRoot({ filePath, workspaceRoot }) {
|
|
9
|
-
const configs = findTypeScriptConfigs(dirname(filePath), workspaceRoot);
|
|
10
|
-
if (configs.length === 0) return null;
|
|
11
|
-
|
|
12
|
-
const included = configs.find((config) => configIncludesFile(config, filePath));
|
|
13
|
-
return dirname(included ?? configs[0]);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function findTypeScriptConfigs(start, stop) {
|
|
17
|
-
const configs = [];
|
|
18
|
-
let dir = resolve(start);
|
|
19
|
-
const boundary = resolve(stop);
|
|
20
|
-
for (;;) {
|
|
21
|
-
configs.push(...configFilesIn(dir));
|
|
22
|
-
if (dir === boundary || dirname(dir) === dir) return configs;
|
|
23
|
-
dir = dirname(dir);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function configFilesIn(dir) {
|
|
28
|
-
if (!existsSync(dir)) return [];
|
|
29
|
-
return readdirSync(dir, { withFileTypes: true })
|
|
30
|
-
.filter((entry) => entry.isFile() && CONFIG_RE.test(entry.name))
|
|
31
|
-
.map((entry) => entry.name)
|
|
32
|
-
.sort(configSort)
|
|
33
|
-
.map((name) => join(dir, name));
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function configSort(a, b) {
|
|
37
|
-
return configRank(a) - configRank(b) || a.localeCompare(b);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function configRank(name) {
|
|
41
|
-
if (name === "tsconfig.json") return 0;
|
|
42
|
-
if (name === "jsconfig.json") return 1;
|
|
43
|
-
return 2;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function configIncludesFile(configPath, filePath) {
|
|
47
|
-
const config = readConfigChain(configPath);
|
|
48
|
-
if (!config) return false;
|
|
49
|
-
|
|
50
|
-
const exclude = config.exclude ?? DEFAULT_EXCLUDES.map((pattern) => ({ base: dirname(configPath), pattern }));
|
|
51
|
-
if (exclude.some(({ base, pattern }) => matchesPatternFromBase(filePath, base, pattern))) return false;
|
|
52
|
-
|
|
53
|
-
if (config.files) {
|
|
54
|
-
return config.files.some(({ base, pattern }) => normalizePath(relative(base, filePath)) === normalizePath(pattern));
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (config.include) {
|
|
58
|
-
return config.include.some(({ base, pattern }) => matchesPatternFromBase(filePath, base, pattern));
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const file = normalizePath(relative(dirname(configPath), filePath));
|
|
62
|
-
return !isOutside(file) && MATCH_EXTENSIONS.some((ext) => file.endsWith(ext));
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function readConfigChain(path, seen = new Set()) {
|
|
66
|
-
if (seen.has(path)) return null;
|
|
67
|
-
seen.add(path);
|
|
68
|
-
|
|
69
|
-
const raw = readConfig(path);
|
|
70
|
-
if (!raw) return null;
|
|
71
|
-
const base = resolveExtends(path, raw.extends, seen);
|
|
72
|
-
return {
|
|
73
|
-
files: patternList(raw.files, dirname(path)) ?? base?.files ?? null,
|
|
74
|
-
include: patternList(raw.include, dirname(path)) ?? base?.include ?? null,
|
|
75
|
-
exclude: patternList(raw.exclude, dirname(path)) ?? base?.exclude ?? null,
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function resolveExtends(configPath, value, seen) {
|
|
80
|
-
if (typeof value !== "string" || !value.startsWith(".")) return null;
|
|
81
|
-
const resolved = resolve(dirname(configPath), value.endsWith(".json") ? value : `${value}.json`);
|
|
82
|
-
return existsSync(resolved) ? readConfigChain(resolved, seen) : null;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function readConfig(path) {
|
|
86
|
-
try {
|
|
87
|
-
return JSON.parse(stripJsonComments(readFileSync(path, "utf8")));
|
|
88
|
-
} catch {
|
|
89
|
-
return null;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function patternList(value, base) {
|
|
94
|
-
const items = arrayOfStrings(value);
|
|
95
|
-
return items ? items.map((pattern) => ({ base, pattern })) : null;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function arrayOfStrings(value) {
|
|
99
|
-
return Array.isArray(value) && value.every((item) => typeof item === "string") ? value : null;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function isOutside(path) {
|
|
103
|
-
return path === ".." || path.startsWith("../") || path.includes(":");
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function matchesPatternFromBase(filePath, base, pattern) {
|
|
107
|
-
const file = normalizePath(relative(base, filePath));
|
|
108
|
-
return !isOutside(file) && matchesConfigPattern(file, pattern);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function matchesConfigPattern(file, pattern) {
|
|
112
|
-
const normalized = normalizePath(pattern);
|
|
113
|
-
if (!normalized.includes("*") && file === normalized) return true;
|
|
114
|
-
const glob = normalized.includes("*") ? normalized : `${trimSlash(normalized)}/**/*`;
|
|
115
|
-
return globToRegExp(glob).test(file);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function globToRegExp(glob) {
|
|
119
|
-
let source = "^";
|
|
120
|
-
for (let i = 0; i < glob.length; i += 1) {
|
|
121
|
-
const char = glob[i];
|
|
122
|
-
if (char === "*") {
|
|
123
|
-
if (glob[i + 1] === "*") {
|
|
124
|
-
i += 1;
|
|
125
|
-
if (glob[i + 1] === "/") {
|
|
126
|
-
i += 1;
|
|
127
|
-
source += "(?:.*/)?";
|
|
128
|
-
} else {
|
|
129
|
-
source += ".*";
|
|
130
|
-
}
|
|
131
|
-
} else {
|
|
132
|
-
source += "[^/]*";
|
|
133
|
-
}
|
|
134
|
-
} else if (char === "?") {
|
|
135
|
-
source += "[^/]";
|
|
136
|
-
} else {
|
|
137
|
-
source += escapeRegExp(char);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
return new RegExp(`${source}$`);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function trimSlash(path) {
|
|
144
|
-
return path.replace(/\/+$/, "");
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function normalizePath(path) {
|
|
148
|
-
return path.replaceAll("\\", "/");
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function escapeRegExp(value) {
|
|
152
|
-
return value.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&");
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function stripJsonComments(source) {
|
|
156
|
-
let out = "";
|
|
157
|
-
let inString = false;
|
|
158
|
-
let quote = "";
|
|
159
|
-
for (let i = 0; i < source.length; i += 1) {
|
|
160
|
-
const char = source[i];
|
|
161
|
-
const next = source[i + 1];
|
|
162
|
-
if (inString) {
|
|
163
|
-
out += char;
|
|
164
|
-
if (char === "\\") {
|
|
165
|
-
out += next ?? "";
|
|
166
|
-
i += 1;
|
|
167
|
-
} else if (char === quote) {
|
|
168
|
-
inString = false;
|
|
169
|
-
}
|
|
170
|
-
} else if (char === '"' || char === "'") {
|
|
171
|
-
inString = true;
|
|
172
|
-
quote = char;
|
|
173
|
-
out += char;
|
|
174
|
-
} else if (char === "/" && next === "/") {
|
|
175
|
-
while (i < source.length && source[i] !== "\n") i += 1;
|
|
176
|
-
out += "\n";
|
|
177
|
-
} else if (char === "/" && next === "*") {
|
|
178
|
-
i += 2;
|
|
179
|
-
while (i < source.length && !(source[i] === "*" && source[i + 1] === "/")) i += 1;
|
|
180
|
-
i += 1;
|
|
181
|
-
} else {
|
|
182
|
-
out += char;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
return out.replace(/,\s*([}\]])/g, "$1");
|
|
186
|
-
}
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
const CONFIG_RE = /^(?:tsconfig(?:\..+)?|jsconfig)\.json$/;
|
|
5
|
+
const DEFAULT_EXCLUDES = ["node_modules", "bower_components", "jspm_packages"];
|
|
6
|
+
const MATCH_EXTENSIONS = [".ts", ".tsx", ".d.ts", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"];
|
|
7
|
+
|
|
8
|
+
export function resolveTypeScriptProjectRoot({ filePath, workspaceRoot }) {
|
|
9
|
+
const configs = findTypeScriptConfigs(dirname(filePath), workspaceRoot);
|
|
10
|
+
if (configs.length === 0) return null;
|
|
11
|
+
|
|
12
|
+
const included = configs.find((config) => configIncludesFile(config, filePath));
|
|
13
|
+
return dirname(included ?? configs[0]);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function findTypeScriptConfigs(start, stop) {
|
|
17
|
+
const configs = [];
|
|
18
|
+
let dir = resolve(start);
|
|
19
|
+
const boundary = resolve(stop);
|
|
20
|
+
for (;;) {
|
|
21
|
+
configs.push(...configFilesIn(dir));
|
|
22
|
+
if (dir === boundary || dirname(dir) === dir) return configs;
|
|
23
|
+
dir = dirname(dir);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function configFilesIn(dir) {
|
|
28
|
+
if (!existsSync(dir)) return [];
|
|
29
|
+
return readdirSync(dir, { withFileTypes: true })
|
|
30
|
+
.filter((entry) => entry.isFile() && CONFIG_RE.test(entry.name))
|
|
31
|
+
.map((entry) => entry.name)
|
|
32
|
+
.sort(configSort)
|
|
33
|
+
.map((name) => join(dir, name));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function configSort(a, b) {
|
|
37
|
+
return configRank(a) - configRank(b) || a.localeCompare(b);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function configRank(name) {
|
|
41
|
+
if (name === "tsconfig.json") return 0;
|
|
42
|
+
if (name === "jsconfig.json") return 1;
|
|
43
|
+
return 2;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function configIncludesFile(configPath, filePath) {
|
|
47
|
+
const config = readConfigChain(configPath);
|
|
48
|
+
if (!config) return false;
|
|
49
|
+
|
|
50
|
+
const exclude = config.exclude ?? DEFAULT_EXCLUDES.map((pattern) => ({ base: dirname(configPath), pattern }));
|
|
51
|
+
if (exclude.some(({ base, pattern }) => matchesPatternFromBase(filePath, base, pattern))) return false;
|
|
52
|
+
|
|
53
|
+
if (config.files) {
|
|
54
|
+
return config.files.some(({ base, pattern }) => normalizePath(relative(base, filePath)) === normalizePath(pattern));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (config.include) {
|
|
58
|
+
return config.include.some(({ base, pattern }) => matchesPatternFromBase(filePath, base, pattern));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const file = normalizePath(relative(dirname(configPath), filePath));
|
|
62
|
+
return !isOutside(file) && MATCH_EXTENSIONS.some((ext) => file.endsWith(ext));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function readConfigChain(path, seen = new Set()) {
|
|
66
|
+
if (seen.has(path)) return null;
|
|
67
|
+
seen.add(path);
|
|
68
|
+
|
|
69
|
+
const raw = readConfig(path);
|
|
70
|
+
if (!raw) return null;
|
|
71
|
+
const base = resolveExtends(path, raw.extends, seen);
|
|
72
|
+
return {
|
|
73
|
+
files: patternList(raw.files, dirname(path)) ?? base?.files ?? null,
|
|
74
|
+
include: patternList(raw.include, dirname(path)) ?? base?.include ?? null,
|
|
75
|
+
exclude: patternList(raw.exclude, dirname(path)) ?? base?.exclude ?? null,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function resolveExtends(configPath, value, seen) {
|
|
80
|
+
if (typeof value !== "string" || !value.startsWith(".")) return null;
|
|
81
|
+
const resolved = resolve(dirname(configPath), value.endsWith(".json") ? value : `${value}.json`);
|
|
82
|
+
return existsSync(resolved) ? readConfigChain(resolved, seen) : null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function readConfig(path) {
|
|
86
|
+
try {
|
|
87
|
+
return JSON.parse(stripJsonComments(readFileSync(path, "utf8")));
|
|
88
|
+
} catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function patternList(value, base) {
|
|
94
|
+
const items = arrayOfStrings(value);
|
|
95
|
+
return items ? items.map((pattern) => ({ base, pattern })) : null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function arrayOfStrings(value) {
|
|
99
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string") ? value : null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function isOutside(path) {
|
|
103
|
+
return path === ".." || path.startsWith("../") || path.includes(":");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function matchesPatternFromBase(filePath, base, pattern) {
|
|
107
|
+
const file = normalizePath(relative(base, filePath));
|
|
108
|
+
return !isOutside(file) && matchesConfigPattern(file, pattern);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function matchesConfigPattern(file, pattern) {
|
|
112
|
+
const normalized = normalizePath(pattern);
|
|
113
|
+
if (!normalized.includes("*") && file === normalized) return true;
|
|
114
|
+
const glob = normalized.includes("*") ? normalized : `${trimSlash(normalized)}/**/*`;
|
|
115
|
+
return globToRegExp(glob).test(file);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function globToRegExp(glob) {
|
|
119
|
+
let source = "^";
|
|
120
|
+
for (let i = 0; i < glob.length; i += 1) {
|
|
121
|
+
const char = glob[i];
|
|
122
|
+
if (char === "*") {
|
|
123
|
+
if (glob[i + 1] === "*") {
|
|
124
|
+
i += 1;
|
|
125
|
+
if (glob[i + 1] === "/") {
|
|
126
|
+
i += 1;
|
|
127
|
+
source += "(?:.*/)?";
|
|
128
|
+
} else {
|
|
129
|
+
source += ".*";
|
|
130
|
+
}
|
|
131
|
+
} else {
|
|
132
|
+
source += "[^/]*";
|
|
133
|
+
}
|
|
134
|
+
} else if (char === "?") {
|
|
135
|
+
source += "[^/]";
|
|
136
|
+
} else {
|
|
137
|
+
source += escapeRegExp(char);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return new RegExp(`${source}$`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function trimSlash(path) {
|
|
144
|
+
return path.replace(/\/+$/, "");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function normalizePath(path) {
|
|
148
|
+
return path.replaceAll("\\", "/");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function escapeRegExp(value) {
|
|
152
|
+
return value.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function stripJsonComments(source) {
|
|
156
|
+
let out = "";
|
|
157
|
+
let inString = false;
|
|
158
|
+
let quote = "";
|
|
159
|
+
for (let i = 0; i < source.length; i += 1) {
|
|
160
|
+
const char = source[i];
|
|
161
|
+
const next = source[i + 1];
|
|
162
|
+
if (inString) {
|
|
163
|
+
out += char;
|
|
164
|
+
if (char === "\\") {
|
|
165
|
+
out += next ?? "";
|
|
166
|
+
i += 1;
|
|
167
|
+
} else if (char === quote) {
|
|
168
|
+
inString = false;
|
|
169
|
+
}
|
|
170
|
+
} else if (char === '"' || char === "'") {
|
|
171
|
+
inString = true;
|
|
172
|
+
quote = char;
|
|
173
|
+
out += char;
|
|
174
|
+
} else if (char === "/" && next === "/") {
|
|
175
|
+
while (i < source.length && source[i] !== "\n") i += 1;
|
|
176
|
+
out += "\n";
|
|
177
|
+
} else if (char === "/" && next === "*") {
|
|
178
|
+
i += 2;
|
|
179
|
+
while (i < source.length && !(source[i] === "*" && source[i + 1] === "/")) i += 1;
|
|
180
|
+
i += 1;
|
|
181
|
+
} else {
|
|
182
|
+
out += char;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return out.replace(/,\s*([}\]])/g, "$1");
|
|
186
|
+
}
|