Skip to content

Commit fe02475

Browse files
andrewbranchCopilot
andcommitted
Merge upstream/main and adapt API test to project handles
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2 parents 69054b3 + d7d39da commit fe02475

521 files changed

Lines changed: 18142 additions & 151 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/ci.yml‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ name: CI
22

33
on:
44
push:
5-
branches: [main, main-ts7, ts7-release, 'release-*']
5+
branches: [main, main-ts7, ts7-release, 'release-*', 'weswigham/*']
66
pull_request:
7-
branches: [main, main-ts7, ts7-release, 'release-*']
7+
branches: [main, main-ts7, ts7-release, 'release-*', 'weswigham/*']
88
merge_group:
9-
branches: [main, main-ts7, ts7-release, 'release-*']
9+
branches: [main, main-ts7, ts7-release, 'release-*', 'weswigham/*']
1010

1111
permissions:
1212
contents: read

‎.github/workflows/create-cherry-pick-pr.yml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,13 @@ jobs:
6666
run: git switch --detach "origin/$TARGET_BRANCH"
6767
- run: npm ci
6868
- uses: azure/login@a641126d1b8aa4d1fa005f4f92df94a3a4c4c906 # v3.1.0
69+
if: ${{ !cancelled() }}
6970
with:
7071
client-id: ${{ vars.AZURE_CLIENT_ID }}
7172
tenant-id: ${{ vars.AZURE_TENANT_ID }}
7273
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
7374
- name: Create GitHub App token
75+
if: ${{ !cancelled() }}
7476
id: app-token
7577
uses: microsoft/create-github-app-token-via-key-vault@5ba0d436e9c3cac52feff4d1f2f66f9698ce4a2d # v1
7678
with:

‎.github/workflows/new-release-branch.yaml‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,6 @@ jobs:
9595
PACKAGE_VERSION: ${{ inputs.package_version }}
9696
run: |
9797
set -euo pipefail
98-
git diff --check
9998
mapfile -t changedFiles < <(git diff --name-only)
10099
expectedFiles=("Herebyfile.mjs" "tsc/internal/core/version.go")
101100
if [ "${changedFiles[*]}" != "${expectedFiles[*]}" ]; then
@@ -108,11 +107,13 @@ jobs:
108107
git config core.hooksPath /dev/null
109108
git commit -m "Bump version to $PACKAGE_VERSION"
110109
- uses: azure/login@a641126d1b8aa4d1fa005f4f92df94a3a4c4c906 # v3.1.0
110+
if: ${{ !cancelled() }}
111111
with:
112112
client-id: ${{ vars.AZURE_CLIENT_ID }}
113113
tenant-id: ${{ vars.AZURE_TENANT_ID }}
114114
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
115115
- name: Create GitHub App token
116+
if: ${{ !cancelled() }}
116117
id: app-token
117118
uses: microsoft/create-github-app-token-via-key-vault@5ba0d436e9c3cac52feff4d1f2f66f9698ce4a2d # v1
118119
with:
@@ -121,6 +122,7 @@ jobs:
121122
owner: microsoft
122123
repositories: TypeScript
123124
permission-contents: write
125+
permission-issues: write
124126
- name: Configure git for GitHub App token
125127
env:
126128
GITHUB_APP_TOKEN: ${{ steps.app-token.outputs.token }}

‎.github/workflows/set-version.yaml‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,6 @@ jobs:
110110
PACKAGE_VERSION: ${{ inputs.package_version }}
111111
run: |
112112
set -euo pipefail
113-
git diff --check
114113
mapfile -t changedFiles < <(git diff --name-only)
115114
expectedFiles=("Herebyfile.mjs" "tsc/internal/core/version.go")
116115
for changedFile in "${changedFiles[@]}"; do
@@ -125,11 +124,13 @@ jobs:
125124
git config core.hooksPath /dev/null
126125
git commit -m "Bump version to $PACKAGE_VERSION"
127126
- uses: azure/login@a641126d1b8aa4d1fa005f4f92df94a3a4c4c906 # v3.1.0
127+
if: ${{ !cancelled() }}
128128
with:
129129
client-id: ${{ vars.AZURE_CLIENT_ID }}
130130
tenant-id: ${{ vars.AZURE_TENANT_ID }}
131131
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
132132
- name: Create GitHub App token
133+
if: ${{ !cancelled() }}
133134
id: app-token
134135
uses: microsoft/create-github-app-token-via-key-vault@5ba0d436e9c3cac52feff4d1f2f66f9698ce4a2d # v1
135136
with:
@@ -138,6 +139,7 @@ jobs:
138139
owner: microsoft
139140
repositories: TypeScript
140141
permission-contents: write
142+
permission-issues: write
141143
- name: Configure git for GitHub App token
142144
if: steps.configure.outputs.changed == 'true'
143145
env:

‎.github/workflows/sync-branch.yaml‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,18 +109,19 @@ jobs:
109109
- name: Verify clean merge
110110
run: |
111111
set -euo pipefail
112-
git diff --check
113112
if ! git diff --quiet || ! git diff --cached --quiet; then
114113
echo "Validation modified the worktree; refusing to push the merge." >&2
115114
git status --short
116115
exit 1
117116
fi
118117
- uses: azure/login@a641126d1b8aa4d1fa005f4f92df94a3a4c4c906 # v3.1.0
118+
if: ${{ !cancelled() }}
119119
with:
120120
client-id: ${{ vars.AZURE_CLIENT_ID }}
121121
tenant-id: ${{ vars.AZURE_TENANT_ID }}
122122
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
123123
- name: Create GitHub App token
124+
if: ${{ !cancelled() }}
124125
id: app-token
125126
uses: microsoft/create-github-app-token-via-key-vault@5ba0d436e9c3cac52feff4d1f2f66f9698ce4a2d # v1
126127
with:
@@ -129,6 +130,7 @@ jobs:
129130
owner: microsoft
130131
repositories: TypeScript
131132
permission-contents: write
133+
permission-issues: write
132134
- name: Configure git for GitHub App token
133135
env:
134136
GITHUB_APP_TOKEN: ${{ steps.app-token.outputs.token }}

‎packages/typescript/src/api/async/api.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -839,8 +839,8 @@ export class API<FromLSP extends boolean = false> implements FormatDiagnosticsHo
839839
*
840840
* Fetching the snapshot issues a lightweight request to the server to
841841
* retrieve its timing collection. Collection must be enabled via the
842-
* `collectTiming` option; when it is not, the returned snapshot has
843-
* `enabled: false` and zeroed totals.
842+
* `collectTiming` option or `TS_API_TIMING_DIR` environment variable; when
843+
* it is not, the returned snapshot has `enabled: false` and zeroed totals.
844844
*/
845845
getTimingInfo(): Promise<TimingInfo> {
846846
return this.client.getTimingInfo();

‎packages/typescript/src/api/async/client.ts‎

Lines changed: 53 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import {
4343
TimingCollector,
4444
type TimingInfo,
4545
} from "../timing.ts";
46+
import { TimingDump } from "../timingDump.ts";
4647

4748
export type { ClientOptions, ClientSocketOptions, ClientSpawnOptions };
4849

@@ -59,16 +60,22 @@ export class Client {
5960
private closed = false;
6061
private connecting: Promise<void> | undefined;
6162
private timing: TimingCollector | undefined;
63+
private timingDump: TimingDump | undefined;
64+
private closing: Promise<void> | undefined;
6265
private batchedRequests: { method: APIRequest["method"]; params: APIRequest["params"]; resolve: (value: unknown) => void; reject: (reason?: any) => void; }[] = [];
6366
private nextBatch: NodeJS.Immediate | "manual" | undefined;
6467

6568
constructor(options: ClientOptions) {
69+
this.timingDump = TimingDump.fromEnvironment("async", isSpawnOptions(options) ? "spawn" : "socket");
70+
if (this.timingDump || isSpawnOptions(options) && options.collectTiming) {
71+
this.timing = new TimingCollector();
72+
}
73+
if (this.timingDump && isSpawnOptions(options)) {
74+
options = { ...options, collectTiming: true };
75+
}
6676
this.options = options;
6777
if (isSpawnOptions(options)) {
6878
configureFileSystemCallbacks(options.fs);
69-
if (options.collectTiming) {
70-
this.timing = new TimingCollector();
71-
}
7279
}
7380
}
7481

@@ -401,26 +408,52 @@ export class Client {
401408
return this.connection.sendRequest(requestType, undefined);
402409
}
403410

404-
async close(): Promise<void> {
411+
close(): Promise<void> {
412+
return this.closing ??= this.closeWorker();
413+
}
414+
415+
private async closeWorker(): Promise<void> {
405416
await this.connecting?.catch(() => {}); // if connection is still in-progress, wait for it to finish before closing the connection
406417
this.closed = true;
407-
if (this.connection) {
408-
this.connection.dispose();
409-
this.connection = undefined;
410-
}
411-
if (this.socket) {
412-
this.socket.destroy();
413-
this.socket = undefined;
418+
try {
419+
if (this.timingDump && this.timing) {
420+
const local = this.timing.getInfo();
421+
let server: ServerTimingInfo | undefined;
422+
let serverTimingError: string | undefined;
423+
const connection = this.connection;
424+
// Closing alone does not reject pending JSON-RPC requests.
425+
const onClose = connection?.onClose(() => connection.dispose());
426+
try {
427+
if (this.connected) server = await this.fetchServerTiming();
428+
}
429+
catch (error) {
430+
serverTimingError = String(error);
431+
}
432+
finally {
433+
onClose?.dispose();
434+
}
435+
this.timingDump.write(local, server, serverTimingError);
436+
}
414437
}
415-
if (this.process) {
416-
// Close stdin to unblock the server's read loop, allowing it to exit cleanly.
417-
// The server is blocked on stdin.Read(), so just sending SIGTERM would deadlock:
418-
// - Node won't exit while child is alive
419-
// - Child can't process SIGTERM while blocked on read
420-
// - Read won't error until stdin is closed
421-
this.process.stdin?.end();
422-
this.process = undefined;
438+
finally {
439+
if (this.connection) {
440+
this.connection.dispose();
441+
this.connection = undefined;
442+
}
443+
if (this.socket) {
444+
this.socket.destroy();
445+
this.socket = undefined;
446+
}
447+
if (this.process) {
448+
// Close stdin to unblock the server's read loop, allowing it to exit cleanly.
449+
// The server is blocked on stdin.Read(), so just sending SIGTERM would deadlock:
450+
// - Node won't exit while child is alive
451+
// - Child can't process SIGTERM while blocked on read
452+
// - Read won't error until stdin is closed
453+
this.process.stdin?.end();
454+
this.process = undefined;
455+
}
456+
this.connected = false;
423457
}
424-
this.connected = false;
425458
}
426459
}

‎packages/typescript/src/api/options.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ export interface ClientSpawnOptions {
3333
* measures its own per-request processing time; both are combined (along
3434
* with an estimated transport overhead) in the snapshot returned by
3535
* {@link API.getTimingInfo}.
36+
* Setting `TS_API_TIMING_DIR` to a nonempty output directory also enables
37+
* collection, even when this option is false, and writes a JSON snapshot
38+
* when the API is closed.
3639
*/
3740
collectTiming?: boolean | undefined;
3841
}

‎packages/typescript/src/api/sync/api.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1344,8 +1344,8 @@ export class API<FromLSP extends boolean = false> implements FormatDiagnosticsHo
13441344
*
13451345
* Fetching the snapshot issues a lightweight request to the server to
13461346
* retrieve its timing collection. Collection must be enabled via the
1347-
* `collectTiming` option; when it is not, the returned snapshot has
1348-
* `enabled: false` and zeroed totals.
1347+
* `collectTiming` option or `TS_API_TIMING_DIR` environment variable; when
1348+
* it is not, the returned snapshot has `enabled: false` and zeroed totals.
13491349
*/
13501350
get getTimingInfo(): {
13511351
(): TimingInfo;

‎packages/typescript/src/api/sync/client.ts‎

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,27 @@ import {
2525
TimingCollector,
2626
type TimingInfo,
2727
} from "../timing.ts";
28+
import { TimingDump } from "../timingDump.ts";
2829

2930
export type { ClientOptions, ClientSocketOptions, ClientSpawnOptions };
3031

3132
export class Client {
3233
private channel: SyncRpcChannel;
3334
private encoder = new TextEncoder();
3435
private timing: TimingCollector | undefined;
36+
private timingDump: TimingDump | undefined;
37+
private closed = false;
3538
private maxResponseBytesPerPage: number | undefined;
3639

3740
constructor(options: ClientOptions) {
3841
if (!isSpawnOptions(options)) {
3942
throw new Error("Socket connections are not yet supported in the sync client");
4043
}
4144

45+
this.timingDump = TimingDump.fromEnvironment("sync", "spawn");
46+
if (this.timingDump) {
47+
options = { ...options, collectTiming: true };
48+
}
4249
const args = getAPIProcessArgs(options, false);
4350
this.maxResponseBytesPerPage = options.maxResponseBytesPerPage;
4451

@@ -184,6 +191,24 @@ export class Client {
184191
}
185192

186193
close(): void {
187-
this.channel.close();
194+
if (this.closed) return;
195+
this.closed = true;
196+
try {
197+
if (this.timingDump && this.timing) {
198+
const local = this.timing.getInfo();
199+
let server: ServerTimingInfo | undefined;
200+
let serverTimingError: string | undefined;
201+
try {
202+
server = JSON.parse(this.channel.requestSync("getServerTiming", "")) as ServerTimingInfo;
203+
}
204+
catch (error) {
205+
serverTimingError = String(error);
206+
}
207+
this.timingDump.write(local, server, serverTimingError);
208+
}
209+
}
210+
finally {
211+
this.channel.close();
212+
}
188213
}
189214
}

0 commit comments

Comments
 (0)