typegraph-mcp 0.9.44 → 0.9.45
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/benchmark.js +24 -0
- package/dist/cli.js +34 -3
- package/dist/server.js +34 -3
- package/dist/smoke-test.js +24 -0
- package/dist/tsserver-client.js +24 -0
- package/engine-sync-test.ts +26 -0
- package/package.json +1 -1
- package/server.ts +12 -3
- package/tsserver-client.ts +29 -0
package/dist/benchmark.js
CHANGED
|
@@ -25,6 +25,7 @@ var TsServerClient = class {
|
|
|
25
25
|
shuttingDown = false;
|
|
26
26
|
restartCount = 0;
|
|
27
27
|
maxRestarts = 3;
|
|
28
|
+
projectsDirty = false;
|
|
28
29
|
// ─── Path Resolution ────────────────────────────────────────────────────
|
|
29
30
|
resolvePath(file) {
|
|
30
31
|
return path.isAbsolute(file) ? file : path.resolve(this.projectRoot, file);
|
|
@@ -201,6 +202,24 @@ var TsServerClient = class {
|
|
|
201
202
|
this.sendNotification("open", { file: absPath });
|
|
202
203
|
await new Promise((r) => setTimeout(r, 50));
|
|
203
204
|
}
|
|
205
|
+
/**
|
|
206
|
+
* Mark tsserver's view of the disk as stale (a file changed that is NOT open
|
|
207
|
+
* in tsserver). tsserver's own disk watching silently decays in long-lived
|
|
208
|
+
* instances at monorepo scale, leaving closed-file contents frozen at project
|
|
209
|
+
* load time and new files unassigned (they land in single-file inferred
|
|
210
|
+
* projects, producing definition-only reference results). The next query
|
|
211
|
+
* forces a `reloadProjects`, which re-globs configs and re-reads closed files
|
|
212
|
+
* from disk. Open files are unaffected by design — their content is
|
|
213
|
+
* protocol-owned and refreshed via reloadOpenFile().
|
|
214
|
+
*/
|
|
215
|
+
markProjectsDirty() {
|
|
216
|
+
this.projectsDirty = true;
|
|
217
|
+
}
|
|
218
|
+
refreshIfDirty() {
|
|
219
|
+
if (!this.projectsDirty) return;
|
|
220
|
+
this.projectsDirty = false;
|
|
221
|
+
this.sendNotification("reloadProjects");
|
|
222
|
+
}
|
|
204
223
|
async reloadOpenFile(file) {
|
|
205
224
|
const absPath = this.resolvePath(file);
|
|
206
225
|
if (!this.openFiles.has(absPath)) return false;
|
|
@@ -218,6 +237,7 @@ var TsServerClient = class {
|
|
|
218
237
|
}
|
|
219
238
|
// ─── Public API ────────────────────────────────────────────────────────
|
|
220
239
|
async definition(file, line, offset) {
|
|
240
|
+
this.refreshIfDirty();
|
|
221
241
|
const absPath = this.resolvePath(file);
|
|
222
242
|
await this.ensureOpen(absPath);
|
|
223
243
|
const body = await this.sendRequest("definition", {
|
|
@@ -232,6 +252,7 @@ var TsServerClient = class {
|
|
|
232
252
|
}));
|
|
233
253
|
}
|
|
234
254
|
async references(file, line, offset) {
|
|
255
|
+
this.refreshIfDirty();
|
|
235
256
|
const absPath = this.resolvePath(file);
|
|
236
257
|
await this.ensureOpen(absPath);
|
|
237
258
|
const body = await this.sendRequest("references", {
|
|
@@ -246,6 +267,7 @@ var TsServerClient = class {
|
|
|
246
267
|
}));
|
|
247
268
|
}
|
|
248
269
|
async quickinfo(file, line, offset) {
|
|
270
|
+
this.refreshIfDirty();
|
|
249
271
|
const absPath = this.resolvePath(file);
|
|
250
272
|
await this.ensureOpen(absPath);
|
|
251
273
|
try {
|
|
@@ -260,6 +282,7 @@ var TsServerClient = class {
|
|
|
260
282
|
}
|
|
261
283
|
}
|
|
262
284
|
async navto(searchValue, maxResults = 10, file) {
|
|
285
|
+
this.refreshIfDirty();
|
|
263
286
|
if (file) await this.ensureOpen(file);
|
|
264
287
|
const args = {
|
|
265
288
|
searchValue,
|
|
@@ -274,6 +297,7 @@ var TsServerClient = class {
|
|
|
274
297
|
}));
|
|
275
298
|
}
|
|
276
299
|
async navbar(file) {
|
|
300
|
+
this.refreshIfDirty();
|
|
277
301
|
const absPath = this.resolvePath(file);
|
|
278
302
|
await this.ensureOpen(absPath);
|
|
279
303
|
const body = await this.sendRequest("navbar", {
|
package/dist/cli.js
CHANGED
|
@@ -954,6 +954,7 @@ var init_tsserver_client = __esm({
|
|
|
954
954
|
shuttingDown = false;
|
|
955
955
|
restartCount = 0;
|
|
956
956
|
maxRestarts = 3;
|
|
957
|
+
projectsDirty = false;
|
|
957
958
|
// ─── Path Resolution ────────────────────────────────────────────────────
|
|
958
959
|
resolvePath(file) {
|
|
959
960
|
return path4.isAbsolute(file) ? file : path4.resolve(this.projectRoot, file);
|
|
@@ -1130,6 +1131,24 @@ var init_tsserver_client = __esm({
|
|
|
1130
1131
|
this.sendNotification("open", { file: absPath2 });
|
|
1131
1132
|
await new Promise((r) => setTimeout(r, 50));
|
|
1132
1133
|
}
|
|
1134
|
+
/**
|
|
1135
|
+
* Mark tsserver's view of the disk as stale (a file changed that is NOT open
|
|
1136
|
+
* in tsserver). tsserver's own disk watching silently decays in long-lived
|
|
1137
|
+
* instances at monorepo scale, leaving closed-file contents frozen at project
|
|
1138
|
+
* load time and new files unassigned (they land in single-file inferred
|
|
1139
|
+
* projects, producing definition-only reference results). The next query
|
|
1140
|
+
* forces a `reloadProjects`, which re-globs configs and re-reads closed files
|
|
1141
|
+
* from disk. Open files are unaffected by design — their content is
|
|
1142
|
+
* protocol-owned and refreshed via reloadOpenFile().
|
|
1143
|
+
*/
|
|
1144
|
+
markProjectsDirty() {
|
|
1145
|
+
this.projectsDirty = true;
|
|
1146
|
+
}
|
|
1147
|
+
refreshIfDirty() {
|
|
1148
|
+
if (!this.projectsDirty) return;
|
|
1149
|
+
this.projectsDirty = false;
|
|
1150
|
+
this.sendNotification("reloadProjects");
|
|
1151
|
+
}
|
|
1133
1152
|
async reloadOpenFile(file) {
|
|
1134
1153
|
const absPath2 = this.resolvePath(file);
|
|
1135
1154
|
if (!this.openFiles.has(absPath2)) return false;
|
|
@@ -1147,6 +1166,7 @@ var init_tsserver_client = __esm({
|
|
|
1147
1166
|
}
|
|
1148
1167
|
// ─── Public API ────────────────────────────────────────────────────────
|
|
1149
1168
|
async definition(file, line, offset) {
|
|
1169
|
+
this.refreshIfDirty();
|
|
1150
1170
|
const absPath2 = this.resolvePath(file);
|
|
1151
1171
|
await this.ensureOpen(absPath2);
|
|
1152
1172
|
const body = await this.sendRequest("definition", {
|
|
@@ -1161,6 +1181,7 @@ var init_tsserver_client = __esm({
|
|
|
1161
1181
|
}));
|
|
1162
1182
|
}
|
|
1163
1183
|
async references(file, line, offset) {
|
|
1184
|
+
this.refreshIfDirty();
|
|
1164
1185
|
const absPath2 = this.resolvePath(file);
|
|
1165
1186
|
await this.ensureOpen(absPath2);
|
|
1166
1187
|
const body = await this.sendRequest("references", {
|
|
@@ -1175,6 +1196,7 @@ var init_tsserver_client = __esm({
|
|
|
1175
1196
|
}));
|
|
1176
1197
|
}
|
|
1177
1198
|
async quickinfo(file, line, offset) {
|
|
1199
|
+
this.refreshIfDirty();
|
|
1178
1200
|
const absPath2 = this.resolvePath(file);
|
|
1179
1201
|
await this.ensureOpen(absPath2);
|
|
1180
1202
|
try {
|
|
@@ -1189,6 +1211,7 @@ var init_tsserver_client = __esm({
|
|
|
1189
1211
|
}
|
|
1190
1212
|
}
|
|
1191
1213
|
async navto(searchValue, maxResults = 10, file) {
|
|
1214
|
+
this.refreshIfDirty();
|
|
1192
1215
|
if (file) await this.ensureOpen(file);
|
|
1193
1216
|
const args2 = {
|
|
1194
1217
|
searchValue,
|
|
@@ -1203,6 +1226,7 @@ var init_tsserver_client = __esm({
|
|
|
1203
1226
|
}));
|
|
1204
1227
|
}
|
|
1205
1228
|
async navbar(file) {
|
|
1229
|
+
this.refreshIfDirty();
|
|
1206
1230
|
const absPath2 = this.resolvePath(file);
|
|
1207
1231
|
await this.ensureOpen(absPath2);
|
|
1208
1232
|
const body = await this.sendRequest("navbar", {
|
|
@@ -2697,11 +2721,18 @@ async function main4() {
|
|
|
2697
2721
|
moduleGraph = graphResult.graph;
|
|
2698
2722
|
moduleResolver = graphResult.resolver;
|
|
2699
2723
|
startWatcher(projectRoot2, moduleGraph, graphResult.resolver, {
|
|
2700
|
-
onFileUpdated: (filePath) => client.reloadOpenFile(filePath).
|
|
2701
|
-
|
|
2702
|
-
|
|
2724
|
+
onFileUpdated: (filePath) => client.reloadOpenFile(filePath).then(
|
|
2725
|
+
(wasOpen) => {
|
|
2726
|
+
if (!wasOpen) client.markProjectsDirty();
|
|
2727
|
+
},
|
|
2728
|
+
(err) => {
|
|
2729
|
+
client.markProjectsDirty();
|
|
2730
|
+
log3(`Failed to reload open file ${relPath2(filePath)}:`, err);
|
|
2731
|
+
}
|
|
2732
|
+
),
|
|
2703
2733
|
onFileDeleted: (filePath) => {
|
|
2704
2734
|
client.closeFile(filePath);
|
|
2735
|
+
client.markProjectsDirty();
|
|
2705
2736
|
}
|
|
2706
2737
|
});
|
|
2707
2738
|
const transport = new StdioServerTransport();
|
package/dist/server.js
CHANGED
|
@@ -26,6 +26,7 @@ var TsServerClient = class {
|
|
|
26
26
|
shuttingDown = false;
|
|
27
27
|
restartCount = 0;
|
|
28
28
|
maxRestarts = 3;
|
|
29
|
+
projectsDirty = false;
|
|
29
30
|
// ─── Path Resolution ────────────────────────────────────────────────────
|
|
30
31
|
resolvePath(file) {
|
|
31
32
|
return path.isAbsolute(file) ? file : path.resolve(this.projectRoot, file);
|
|
@@ -202,6 +203,24 @@ var TsServerClient = class {
|
|
|
202
203
|
this.sendNotification("open", { file: absPath2 });
|
|
203
204
|
await new Promise((r) => setTimeout(r, 50));
|
|
204
205
|
}
|
|
206
|
+
/**
|
|
207
|
+
* Mark tsserver's view of the disk as stale (a file changed that is NOT open
|
|
208
|
+
* in tsserver). tsserver's own disk watching silently decays in long-lived
|
|
209
|
+
* instances at monorepo scale, leaving closed-file contents frozen at project
|
|
210
|
+
* load time and new files unassigned (they land in single-file inferred
|
|
211
|
+
* projects, producing definition-only reference results). The next query
|
|
212
|
+
* forces a `reloadProjects`, which re-globs configs and re-reads closed files
|
|
213
|
+
* from disk. Open files are unaffected by design — their content is
|
|
214
|
+
* protocol-owned and refreshed via reloadOpenFile().
|
|
215
|
+
*/
|
|
216
|
+
markProjectsDirty() {
|
|
217
|
+
this.projectsDirty = true;
|
|
218
|
+
}
|
|
219
|
+
refreshIfDirty() {
|
|
220
|
+
if (!this.projectsDirty) return;
|
|
221
|
+
this.projectsDirty = false;
|
|
222
|
+
this.sendNotification("reloadProjects");
|
|
223
|
+
}
|
|
205
224
|
async reloadOpenFile(file) {
|
|
206
225
|
const absPath2 = this.resolvePath(file);
|
|
207
226
|
if (!this.openFiles.has(absPath2)) return false;
|
|
@@ -219,6 +238,7 @@ var TsServerClient = class {
|
|
|
219
238
|
}
|
|
220
239
|
// ─── Public API ────────────────────────────────────────────────────────
|
|
221
240
|
async definition(file, line, offset) {
|
|
241
|
+
this.refreshIfDirty();
|
|
222
242
|
const absPath2 = this.resolvePath(file);
|
|
223
243
|
await this.ensureOpen(absPath2);
|
|
224
244
|
const body = await this.sendRequest("definition", {
|
|
@@ -233,6 +253,7 @@ var TsServerClient = class {
|
|
|
233
253
|
}));
|
|
234
254
|
}
|
|
235
255
|
async references(file, line, offset) {
|
|
256
|
+
this.refreshIfDirty();
|
|
236
257
|
const absPath2 = this.resolvePath(file);
|
|
237
258
|
await this.ensureOpen(absPath2);
|
|
238
259
|
const body = await this.sendRequest("references", {
|
|
@@ -247,6 +268,7 @@ var TsServerClient = class {
|
|
|
247
268
|
}));
|
|
248
269
|
}
|
|
249
270
|
async quickinfo(file, line, offset) {
|
|
271
|
+
this.refreshIfDirty();
|
|
250
272
|
const absPath2 = this.resolvePath(file);
|
|
251
273
|
await this.ensureOpen(absPath2);
|
|
252
274
|
try {
|
|
@@ -261,6 +283,7 @@ var TsServerClient = class {
|
|
|
261
283
|
}
|
|
262
284
|
}
|
|
263
285
|
async navto(searchValue, maxResults = 10, file) {
|
|
286
|
+
this.refreshIfDirty();
|
|
264
287
|
if (file) await this.ensureOpen(file);
|
|
265
288
|
const args = {
|
|
266
289
|
searchValue,
|
|
@@ -275,6 +298,7 @@ var TsServerClient = class {
|
|
|
275
298
|
}));
|
|
276
299
|
}
|
|
277
300
|
async navbar(file) {
|
|
301
|
+
this.refreshIfDirty();
|
|
278
302
|
const absPath2 = this.resolvePath(file);
|
|
279
303
|
await this.ensureOpen(absPath2);
|
|
280
304
|
const body = await this.sendRequest("navbar", {
|
|
@@ -1695,11 +1719,18 @@ async function main() {
|
|
|
1695
1719
|
moduleGraph = graphResult.graph;
|
|
1696
1720
|
moduleResolver = graphResult.resolver;
|
|
1697
1721
|
startWatcher(projectRoot, moduleGraph, graphResult.resolver, {
|
|
1698
|
-
onFileUpdated: (filePath) => client.reloadOpenFile(filePath).
|
|
1699
|
-
|
|
1700
|
-
|
|
1722
|
+
onFileUpdated: (filePath) => client.reloadOpenFile(filePath).then(
|
|
1723
|
+
(wasOpen) => {
|
|
1724
|
+
if (!wasOpen) client.markProjectsDirty();
|
|
1725
|
+
},
|
|
1726
|
+
(err) => {
|
|
1727
|
+
client.markProjectsDirty();
|
|
1728
|
+
log3(`Failed to reload open file ${relPath(filePath)}:`, err);
|
|
1729
|
+
}
|
|
1730
|
+
),
|
|
1701
1731
|
onFileDeleted: (filePath) => {
|
|
1702
1732
|
client.closeFile(filePath);
|
|
1733
|
+
client.markProjectsDirty();
|
|
1703
1734
|
}
|
|
1704
1735
|
});
|
|
1705
1736
|
const transport = new StdioServerTransport();
|
package/dist/smoke-test.js
CHANGED
|
@@ -24,6 +24,7 @@ var TsServerClient = class {
|
|
|
24
24
|
shuttingDown = false;
|
|
25
25
|
restartCount = 0;
|
|
26
26
|
maxRestarts = 3;
|
|
27
|
+
projectsDirty = false;
|
|
27
28
|
// ─── Path Resolution ────────────────────────────────────────────────────
|
|
28
29
|
resolvePath(file) {
|
|
29
30
|
return path.isAbsolute(file) ? file : path.resolve(this.projectRoot, file);
|
|
@@ -200,6 +201,24 @@ var TsServerClient = class {
|
|
|
200
201
|
this.sendNotification("open", { file: absPath });
|
|
201
202
|
await new Promise((r) => setTimeout(r, 50));
|
|
202
203
|
}
|
|
204
|
+
/**
|
|
205
|
+
* Mark tsserver's view of the disk as stale (a file changed that is NOT open
|
|
206
|
+
* in tsserver). tsserver's own disk watching silently decays in long-lived
|
|
207
|
+
* instances at monorepo scale, leaving closed-file contents frozen at project
|
|
208
|
+
* load time and new files unassigned (they land in single-file inferred
|
|
209
|
+
* projects, producing definition-only reference results). The next query
|
|
210
|
+
* forces a `reloadProjects`, which re-globs configs and re-reads closed files
|
|
211
|
+
* from disk. Open files are unaffected by design — their content is
|
|
212
|
+
* protocol-owned and refreshed via reloadOpenFile().
|
|
213
|
+
*/
|
|
214
|
+
markProjectsDirty() {
|
|
215
|
+
this.projectsDirty = true;
|
|
216
|
+
}
|
|
217
|
+
refreshIfDirty() {
|
|
218
|
+
if (!this.projectsDirty) return;
|
|
219
|
+
this.projectsDirty = false;
|
|
220
|
+
this.sendNotification("reloadProjects");
|
|
221
|
+
}
|
|
203
222
|
async reloadOpenFile(file) {
|
|
204
223
|
const absPath = this.resolvePath(file);
|
|
205
224
|
if (!this.openFiles.has(absPath)) return false;
|
|
@@ -217,6 +236,7 @@ var TsServerClient = class {
|
|
|
217
236
|
}
|
|
218
237
|
// ─── Public API ────────────────────────────────────────────────────────
|
|
219
238
|
async definition(file, line, offset) {
|
|
239
|
+
this.refreshIfDirty();
|
|
220
240
|
const absPath = this.resolvePath(file);
|
|
221
241
|
await this.ensureOpen(absPath);
|
|
222
242
|
const body = await this.sendRequest("definition", {
|
|
@@ -231,6 +251,7 @@ var TsServerClient = class {
|
|
|
231
251
|
}));
|
|
232
252
|
}
|
|
233
253
|
async references(file, line, offset) {
|
|
254
|
+
this.refreshIfDirty();
|
|
234
255
|
const absPath = this.resolvePath(file);
|
|
235
256
|
await this.ensureOpen(absPath);
|
|
236
257
|
const body = await this.sendRequest("references", {
|
|
@@ -245,6 +266,7 @@ var TsServerClient = class {
|
|
|
245
266
|
}));
|
|
246
267
|
}
|
|
247
268
|
async quickinfo(file, line, offset) {
|
|
269
|
+
this.refreshIfDirty();
|
|
248
270
|
const absPath = this.resolvePath(file);
|
|
249
271
|
await this.ensureOpen(absPath);
|
|
250
272
|
try {
|
|
@@ -259,6 +281,7 @@ var TsServerClient = class {
|
|
|
259
281
|
}
|
|
260
282
|
}
|
|
261
283
|
async navto(searchValue, maxResults = 10, file) {
|
|
284
|
+
this.refreshIfDirty();
|
|
262
285
|
if (file) await this.ensureOpen(file);
|
|
263
286
|
const args = {
|
|
264
287
|
searchValue,
|
|
@@ -273,6 +296,7 @@ var TsServerClient = class {
|
|
|
273
296
|
}));
|
|
274
297
|
}
|
|
275
298
|
async navbar(file) {
|
|
299
|
+
this.refreshIfDirty();
|
|
276
300
|
const absPath = this.resolvePath(file);
|
|
277
301
|
await this.ensureOpen(absPath);
|
|
278
302
|
const body = await this.sendRequest("navbar", {
|
package/dist/tsserver-client.js
CHANGED
|
@@ -19,6 +19,7 @@ var TsServerClient = class {
|
|
|
19
19
|
shuttingDown = false;
|
|
20
20
|
restartCount = 0;
|
|
21
21
|
maxRestarts = 3;
|
|
22
|
+
projectsDirty = false;
|
|
22
23
|
// ─── Path Resolution ────────────────────────────────────────────────────
|
|
23
24
|
resolvePath(file) {
|
|
24
25
|
return path.isAbsolute(file) ? file : path.resolve(this.projectRoot, file);
|
|
@@ -195,6 +196,24 @@ var TsServerClient = class {
|
|
|
195
196
|
this.sendNotification("open", { file: absPath });
|
|
196
197
|
await new Promise((r) => setTimeout(r, 50));
|
|
197
198
|
}
|
|
199
|
+
/**
|
|
200
|
+
* Mark tsserver's view of the disk as stale (a file changed that is NOT open
|
|
201
|
+
* in tsserver). tsserver's own disk watching silently decays in long-lived
|
|
202
|
+
* instances at monorepo scale, leaving closed-file contents frozen at project
|
|
203
|
+
* load time and new files unassigned (they land in single-file inferred
|
|
204
|
+
* projects, producing definition-only reference results). The next query
|
|
205
|
+
* forces a `reloadProjects`, which re-globs configs and re-reads closed files
|
|
206
|
+
* from disk. Open files are unaffected by design — their content is
|
|
207
|
+
* protocol-owned and refreshed via reloadOpenFile().
|
|
208
|
+
*/
|
|
209
|
+
markProjectsDirty() {
|
|
210
|
+
this.projectsDirty = true;
|
|
211
|
+
}
|
|
212
|
+
refreshIfDirty() {
|
|
213
|
+
if (!this.projectsDirty) return;
|
|
214
|
+
this.projectsDirty = false;
|
|
215
|
+
this.sendNotification("reloadProjects");
|
|
216
|
+
}
|
|
198
217
|
async reloadOpenFile(file) {
|
|
199
218
|
const absPath = this.resolvePath(file);
|
|
200
219
|
if (!this.openFiles.has(absPath)) return false;
|
|
@@ -212,6 +231,7 @@ var TsServerClient = class {
|
|
|
212
231
|
}
|
|
213
232
|
// ─── Public API ────────────────────────────────────────────────────────
|
|
214
233
|
async definition(file, line, offset) {
|
|
234
|
+
this.refreshIfDirty();
|
|
215
235
|
const absPath = this.resolvePath(file);
|
|
216
236
|
await this.ensureOpen(absPath);
|
|
217
237
|
const body = await this.sendRequest("definition", {
|
|
@@ -226,6 +246,7 @@ var TsServerClient = class {
|
|
|
226
246
|
}));
|
|
227
247
|
}
|
|
228
248
|
async references(file, line, offset) {
|
|
249
|
+
this.refreshIfDirty();
|
|
229
250
|
const absPath = this.resolvePath(file);
|
|
230
251
|
await this.ensureOpen(absPath);
|
|
231
252
|
const body = await this.sendRequest("references", {
|
|
@@ -240,6 +261,7 @@ var TsServerClient = class {
|
|
|
240
261
|
}));
|
|
241
262
|
}
|
|
242
263
|
async quickinfo(file, line, offset) {
|
|
264
|
+
this.refreshIfDirty();
|
|
243
265
|
const absPath = this.resolvePath(file);
|
|
244
266
|
await this.ensureOpen(absPath);
|
|
245
267
|
try {
|
|
@@ -254,6 +276,7 @@ var TsServerClient = class {
|
|
|
254
276
|
}
|
|
255
277
|
}
|
|
256
278
|
async navto(searchValue, maxResults = 10, file) {
|
|
279
|
+
this.refreshIfDirty();
|
|
257
280
|
if (file) await this.ensureOpen(file);
|
|
258
281
|
const args = {
|
|
259
282
|
searchValue,
|
|
@@ -268,6 +291,7 @@ var TsServerClient = class {
|
|
|
268
291
|
}));
|
|
269
292
|
}
|
|
270
293
|
async navbar(file) {
|
|
294
|
+
this.refreshIfDirty();
|
|
271
295
|
const absPath = this.resolvePath(file);
|
|
272
296
|
await this.ensureOpen(absPath);
|
|
273
297
|
const body = await this.sendRequest("navbar", {
|
package/engine-sync-test.ts
CHANGED
|
@@ -114,6 +114,12 @@ async function main(): Promise<void> {
|
|
|
114
114
|
);
|
|
115
115
|
writeFile(projectRoot, "src/test.ts", "export const oldName = 1 as const;\n");
|
|
116
116
|
writeFile(projectRoot, "src/util.ts", "export const helper = 1 as const;\n");
|
|
117
|
+
writeFile(projectRoot, "src/closed.ts", "export const closedValue = 1 as const;\n");
|
|
118
|
+
writeFile(
|
|
119
|
+
projectRoot,
|
|
120
|
+
"src/closed-consumer.ts",
|
|
121
|
+
'import { closedValue } from "./closed";\nexport const observed = closedValue;\n'
|
|
122
|
+
);
|
|
117
123
|
|
|
118
124
|
fs.mkdirSync(path.join(projectRoot, "node_modules"), { recursive: true });
|
|
119
125
|
fs.symlinkSync(
|
|
@@ -196,6 +202,25 @@ async function main(): Promise<void> {
|
|
|
196
202
|
assert.ok(!exportsResult.exports.some((item) => item.symbol === "oldName"));
|
|
197
203
|
});
|
|
198
204
|
|
|
205
|
+
// Change a file that tsserver has not opened. The watcher should mark
|
|
206
|
+
// projects dirty so the next semantic query reloads closed-file contents.
|
|
207
|
+
writeFile(projectRoot, "src/closed.ts", "export const closedValue = 2 as const;\n");
|
|
208
|
+
|
|
209
|
+
await waitFor("closed file update to refresh semantic project state", async () => {
|
|
210
|
+
const deps = await callTool<DependencyTreeResult>("ts_dependency_tree", {
|
|
211
|
+
file: "src/closed-consumer.ts",
|
|
212
|
+
});
|
|
213
|
+
const normalizedDeps = deps.files.map(normalize);
|
|
214
|
+
assert.ok(normalizedDeps.includes("src/closed.ts"), `Expected src/closed.ts in ${normalizedDeps}`);
|
|
215
|
+
|
|
216
|
+
const typeInfo = await callTool<TypeInfoResult>("ts_type_info", {
|
|
217
|
+
file: "src/closed-consumer.ts",
|
|
218
|
+
line: 2,
|
|
219
|
+
column: 14,
|
|
220
|
+
});
|
|
221
|
+
assert.match(typeInfo.type ?? "", /\b2\b/);
|
|
222
|
+
});
|
|
223
|
+
|
|
199
224
|
// Open util.ts directly so tsserver tracks it, then delete it from disk.
|
|
200
225
|
const utilInfo = await callTool<TypeInfoResult>("ts_type_info", {
|
|
201
226
|
file: "src/util.ts",
|
|
@@ -223,6 +248,7 @@ async function main(): Promise<void> {
|
|
|
223
248
|
console.log("==============================");
|
|
224
249
|
console.log(" ✓ import swaps keep dependency_tree and type_info aligned");
|
|
225
250
|
console.log(" ✓ export renames refresh ts_module_exports semantic metadata");
|
|
251
|
+
console.log(" ✓ closed-file edits refresh tsserver project state");
|
|
226
252
|
console.log(" ✓ deleted open files do not survive as tsserver ghost snapshots");
|
|
227
253
|
} finally {
|
|
228
254
|
await transport.close().catch(() => {});
|
package/package.json
CHANGED
package/server.ts
CHANGED
|
@@ -1134,11 +1134,20 @@ async function main() {
|
|
|
1134
1134
|
moduleResolver = graphResult.resolver;
|
|
1135
1135
|
startWatcher(projectRoot, moduleGraph, graphResult.resolver, {
|
|
1136
1136
|
onFileUpdated: (filePath) =>
|
|
1137
|
-
client.reloadOpenFile(filePath).
|
|
1138
|
-
|
|
1139
|
-
|
|
1137
|
+
client.reloadOpenFile(filePath).then(
|
|
1138
|
+
(wasOpen) => {
|
|
1139
|
+
// Closed files: tsserver's own disk watching decays in long-lived
|
|
1140
|
+
// instances; force a projects reload before the next query.
|
|
1141
|
+
if (!wasOpen) client.markProjectsDirty();
|
|
1142
|
+
},
|
|
1143
|
+
(err) => {
|
|
1144
|
+
client.markProjectsDirty();
|
|
1145
|
+
log(`Failed to reload open file ${relPath(filePath)}:`, err);
|
|
1146
|
+
}
|
|
1147
|
+
),
|
|
1140
1148
|
onFileDeleted: (filePath) => {
|
|
1141
1149
|
client.closeFile(filePath);
|
|
1150
|
+
client.markProjectsDirty();
|
|
1142
1151
|
},
|
|
1143
1152
|
});
|
|
1144
1153
|
|
package/tsserver-client.ts
CHANGED
|
@@ -90,6 +90,7 @@ export class TsServerClient {
|
|
|
90
90
|
private shuttingDown = false;
|
|
91
91
|
private restartCount = 0;
|
|
92
92
|
private readonly maxRestarts = 3;
|
|
93
|
+
private projectsDirty = false;
|
|
93
94
|
|
|
94
95
|
constructor(
|
|
95
96
|
private readonly projectRoot: string,
|
|
@@ -323,6 +324,29 @@ export class TsServerClient {
|
|
|
323
324
|
await new Promise((r) => setTimeout(r, 50));
|
|
324
325
|
}
|
|
325
326
|
|
|
327
|
+
/**
|
|
328
|
+
* Mark tsserver's view of the disk as stale (a file changed that is NOT open
|
|
329
|
+
* in tsserver). tsserver's own disk watching silently decays in long-lived
|
|
330
|
+
* instances at monorepo scale, leaving closed-file contents frozen at project
|
|
331
|
+
* load time and new files unassigned (they land in single-file inferred
|
|
332
|
+
* projects, producing definition-only reference results). The next query
|
|
333
|
+
* forces a `reloadProjects`, which re-globs configs and re-reads closed files
|
|
334
|
+
* from disk. Open files are unaffected by design — their content is
|
|
335
|
+
* protocol-owned and refreshed via reloadOpenFile().
|
|
336
|
+
*/
|
|
337
|
+
markProjectsDirty(): void {
|
|
338
|
+
this.projectsDirty = true;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
private refreshIfDirty(): void {
|
|
342
|
+
if (!this.projectsDirty) return;
|
|
343
|
+
this.projectsDirty = false;
|
|
344
|
+
// reloadProjects sends no response (notRequired), so fire it as a
|
|
345
|
+
// notification; the following request queues behind the reload on
|
|
346
|
+
// tsserver's single-threaded loop.
|
|
347
|
+
this.sendNotification("reloadProjects");
|
|
348
|
+
}
|
|
349
|
+
|
|
326
350
|
async reloadOpenFile(file: string): Promise<boolean> {
|
|
327
351
|
const absPath = this.resolvePath(file);
|
|
328
352
|
if (!this.openFiles.has(absPath)) return false;
|
|
@@ -343,6 +367,7 @@ export class TsServerClient {
|
|
|
343
367
|
// ─── Public API ────────────────────────────────────────────────────────
|
|
344
368
|
|
|
345
369
|
async definition(file: string, line: number, offset: number): Promise<DefinitionResult[]> {
|
|
370
|
+
this.refreshIfDirty();
|
|
346
371
|
const absPath = this.resolvePath(file);
|
|
347
372
|
await this.ensureOpen(absPath);
|
|
348
373
|
|
|
@@ -361,6 +386,7 @@ export class TsServerClient {
|
|
|
361
386
|
}
|
|
362
387
|
|
|
363
388
|
async references(file: string, line: number, offset: number): Promise<ReferenceEntry[]> {
|
|
389
|
+
this.refreshIfDirty();
|
|
364
390
|
const absPath = this.resolvePath(file);
|
|
365
391
|
await this.ensureOpen(absPath);
|
|
366
392
|
|
|
@@ -379,6 +405,7 @@ export class TsServerClient {
|
|
|
379
405
|
}
|
|
380
406
|
|
|
381
407
|
async quickinfo(file: string, line: number, offset: number): Promise<QuickInfoResult | null> {
|
|
408
|
+
this.refreshIfDirty();
|
|
382
409
|
const absPath = this.resolvePath(file);
|
|
383
410
|
await this.ensureOpen(absPath);
|
|
384
411
|
|
|
@@ -398,6 +425,7 @@ export class TsServerClient {
|
|
|
398
425
|
}
|
|
399
426
|
|
|
400
427
|
async navto(searchValue: string, maxResults = 10, file?: string): Promise<NavToItem[]> {
|
|
428
|
+
this.refreshIfDirty();
|
|
401
429
|
// If a file is specified, open it first so tsserver knows about it
|
|
402
430
|
if (file) await this.ensureOpen(file);
|
|
403
431
|
|
|
@@ -418,6 +446,7 @@ export class TsServerClient {
|
|
|
418
446
|
}
|
|
419
447
|
|
|
420
448
|
async navbar(file: string): Promise<NavBarItem[]> {
|
|
449
|
+
this.refreshIfDirty();
|
|
421
450
|
const absPath = this.resolvePath(file);
|
|
422
451
|
await this.ensureOpen(absPath);
|
|
423
452
|
|