Skip to content

Commit 008ad69

Browse files
Copilotkazrael2119JialinHuang803
authored
[typespec-ts] Sync tsconfig.src include with warp exports for multi-client packages (#4872)
Multi-client packages failed to build with `DIST_MISSING`: the generated `config/tsconfig.src.*.json` files hardcoded `include: ["../src/index.ts"]`, while `warp.config.yml` exported every client's entry point as a separate subpath (`./devCenter` → `./src/devCenter/index.ts`). Since the root `src/index.ts` imports the deeper client/api files directly instead of re-exporting the per-client barrels, TypeScript never emitted those barrels to `dist`, and warp's export validation failed. ### Changes - **`build-ts-config.ts`**: New `getSrcIncludePaths(exports)` derives the `include` list from the same `exports` map that drives `warp.config.yml` — mapping `./src/...` → `../src/...`, dropping non-`.ts` values (e.g. `package.json`), de-duplicating, and always keeping `../src/index.ts` as fallback. The four `buildTsSrc{Esm,Browser,ReactNative,Cjs}Config` builders now take `exports`. - **`index.ts`**: Threads `modularPackageInfo.exports` into the `buildTsSrc*Config` builders in both the initial-generation and update/migration paths, keeping the tsconfig inputs and warp exports in sync. - **Tests**: `test-next/unit/metadata/ts-config.test.ts` covering default fallback, multi-client sync, non-`.ts` filtering, de-duplication, and per-target propagation. For the multi-client example in the issue, the emitted configs now include every exported barrel: ```jsonc // config/tsconfig.src.esm.json "include": [ "../src/index.ts", "../src/devCenter/index.ts", "../src/devCenter/api/index.ts", "../src/devBoxes/index.ts", "../src/deploymentEnvironments/index.ts", "../src/models/index.ts" ] ``` --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: kazrael2119 <98569699+kazrael2119@users.noreply.github.com> Co-authored-by: JialinHuang803 <139532647+JialinHuang803@users.noreply.github.com>
1 parent 3fab731 commit 008ad69

4 files changed

Lines changed: 122 additions & 12 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
changeKind: fix
3+
packages:
4+
- "@azure-tools/typespec-ts"
5+
---
6+
7+
Fix multi-client package build failures by syncing the generated `config/tsconfig.src.*.json` `include` lists with the `warp.config.yml` exports, so every client entry point is compiled and emitted to `dist` (previously warp failed with `DIST_MISSING`).

packages/typespec-ts/src/index.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -461,12 +461,12 @@ export async function $onEmit(context: EmitContext) {
461461
// Generate warp.config.yml for Azure monorepo ESM packages
462462
commonBuilders.push((model) => buildWarpConfig(model, modularPackageInfo));
463463
commonBuilders.push(buildTsConfig);
464-
commonBuilders.push(buildTsSrcEsmConfig);
465-
commonBuilders.push(buildTsSrcBrowserConfig);
464+
commonBuilders.push(() => buildTsSrcEsmConfig(modularPackageInfo["exports"]));
465+
commonBuilders.push(() => buildTsSrcBrowserConfig(modularPackageInfo["exports"]));
466466
if (option.generateReactNativeTarget) {
467-
commonBuilders.push(buildTsSrcReactNativeConfig);
467+
commonBuilders.push(() => buildTsSrcReactNativeConfig(modularPackageInfo["exports"]));
468468
}
469-
commonBuilders.push(buildTsSrcCjsConfig);
469+
commonBuilders.push(() => buildTsSrcCjsConfig(modularPackageInfo["exports"]));
470470
if (option.generateSample) {
471471
commonBuilders.push(buildTsSampleConfig);
472472
}
@@ -531,6 +531,14 @@ export async function $onEmit(context: EmitContext) {
531531
// Update warp.config.yml for Azure monorepo packages
532532
updateBuilders.push((model: ClientModel) => buildWarpConfig(model, modularPackageInfo));
533533

534+
// Re-sync tsconfig.src.*.json `include` with the warp exports
535+
updateBuilders.push(() => buildTsSrcEsmConfig(modularPackageInfo["exports"]));
536+
updateBuilders.push(() => buildTsSrcBrowserConfig(modularPackageInfo["exports"]));
537+
if (option.generateReactNativeTarget) {
538+
updateBuilders.push(() => buildTsSrcReactNativeConfig(modularPackageInfo["exports"]));
539+
}
540+
updateBuilders.push(() => buildTsSrcCjsConfig(modularPackageInfo["exports"]));
541+
534542
// If the client name changed, regenerate the README and snippets completely;
535543
// otherwise update only the API reference link in-place.
536544
if (hasReadmeFile) {

packages/typespec-ts/src/metadata/build-ts-config.ts

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,27 @@ export function buildTsConfig(model: ClientModel) {
5252
};
5353
}
5454

55+
/**
56+
* Derives the tsconfig `include` list from the warp `exports` map so every
57+
* exported client entry point is compiled and emitted to `dist`; if they drift
58+
* out of sync, warp fails the build with `DIST_MISSING`. Root index is the fallback.
59+
*/
60+
export function getSrcIncludePaths(exports?: Record<string, string>): string[] {
61+
const includes = new Set<string>(["../src/index.ts"]);
62+
if (exports) {
63+
for (const value of Object.values(exports)) {
64+
if (typeof value === "string" && value.endsWith(".ts")) {
65+
includes.add(value.replace(/^\.\//, "../"));
66+
}
67+
}
68+
}
69+
return Array.from(includes);
70+
}
71+
5572
/**
5673
* Builds config/tsconfig.src.esm.json — extends eng/tsconfigs/src.esm.json
5774
*/
58-
export function buildTsSrcEsmConfig() {
75+
export function buildTsSrcEsmConfig(exports?: Record<string, string>) {
5976
return {
6077
path: "config/tsconfig.src.esm.json",
6178
content: JSON.stringify(
@@ -64,7 +81,7 @@ export function buildTsSrcEsmConfig() {
6481
compilerOptions: {
6582
resolveJsonModule: true,
6683
},
67-
include: ["../src/index.ts"],
84+
include: getSrcIncludePaths(exports),
6885
},
6986
null,
7087
2,
@@ -75,7 +92,7 @@ export function buildTsSrcEsmConfig() {
7592
/**
7693
* Builds config/tsconfig.src.browser.json — extends eng/tsconfigs/src.browser.json
7794
*/
78-
export function buildTsSrcBrowserConfig() {
95+
export function buildTsSrcBrowserConfig(exports?: Record<string, string>) {
7996
return {
8097
path: "config/tsconfig.src.browser.json",
8198
content: JSON.stringify(
@@ -84,7 +101,7 @@ export function buildTsSrcBrowserConfig() {
84101
compilerOptions: {
85102
resolveJsonModule: true,
86103
},
87-
include: ["../src/index.ts"],
104+
include: getSrcIncludePaths(exports),
88105
},
89106
null,
90107
2,
@@ -95,7 +112,7 @@ export function buildTsSrcBrowserConfig() {
95112
/**
96113
* Builds config/tsconfig.src.react-native.json — extends eng/tsconfigs/src.react-native.json
97114
*/
98-
export function buildTsSrcReactNativeConfig() {
115+
export function buildTsSrcReactNativeConfig(exports?: Record<string, string>) {
99116
return {
100117
path: "config/tsconfig.src.react-native.json",
101118
content: JSON.stringify(
@@ -104,7 +121,7 @@ export function buildTsSrcReactNativeConfig() {
104121
compilerOptions: {
105122
resolveJsonModule: true,
106123
},
107-
include: ["../src/index.ts"],
124+
include: getSrcIncludePaths(exports),
108125
},
109126
null,
110127
2,
@@ -115,7 +132,7 @@ export function buildTsSrcReactNativeConfig() {
115132
/**
116133
* Builds config/tsconfig.src.cjs.json — extends eng/tsconfigs/src.cjs.json
117134
*/
118-
export function buildTsSrcCjsConfig() {
135+
export function buildTsSrcCjsConfig(exports?: Record<string, string>) {
119136
return {
120137
path: "config/tsconfig.src.cjs.json",
121138
content: JSON.stringify(
@@ -124,7 +141,7 @@ export function buildTsSrcCjsConfig() {
124141
compilerOptions: {
125142
resolveJsonModule: true,
126143
},
127-
include: ["../src/index.ts"],
144+
include: getSrcIncludePaths(exports),
128145
},
129146
null,
130147
2,
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
import { describe, expect, it } from "vitest";
5+
6+
import {
7+
buildTsSrcBrowserConfig,
8+
buildTsSrcCjsConfig,
9+
buildTsSrcEsmConfig,
10+
buildTsSrcReactNativeConfig,
11+
getSrcIncludePaths,
12+
} from "../../../src/metadata/build-ts-config.js";
13+
14+
describe("tsconfig.src.*.json include generation", () => {
15+
it("defaults to the root entry point when no exports are provided", () => {
16+
expect(getSrcIncludePaths()).toEqual(["../src/index.ts"]);
17+
expect(getSrcIncludePaths({})).toEqual(["../src/index.ts"]);
18+
});
19+
20+
it("keeps the include list in sync with the warp exports for multi-client packages", () => {
21+
// Mirrors the exports map produced for a multi-client package.
22+
const exports = {
23+
".": "./src/index.ts",
24+
"./devCenter": "./src/devCenter/index.ts",
25+
"./devCenter/api": "./src/devCenter/api/index.ts",
26+
"./devBoxes": "./src/devBoxes/index.ts",
27+
"./devBoxes/api": "./src/devBoxes/api/index.ts",
28+
"./deploymentEnvironments": "./src/deploymentEnvironments/index.ts",
29+
"./deploymentEnvironments/api": "./src/deploymentEnvironments/api/index.ts",
30+
"./models": "./src/models/index.ts",
31+
};
32+
33+
const include = getSrcIncludePaths(exports);
34+
35+
// Every per-client barrel must be a compilation input so it is emitted to dist.
36+
expect(include).toContain("../src/index.ts");
37+
expect(include).toContain("../src/devCenter/index.ts");
38+
expect(include).toContain("../src/devBoxes/index.ts");
39+
expect(include).toContain("../src/deploymentEnvironments/index.ts");
40+
// The deeper api/model barrels are exported too, so they are included as well.
41+
expect(include).toContain("../src/devCenter/api/index.ts");
42+
expect(include).toContain("../src/models/index.ts");
43+
});
44+
45+
it("ignores non-TypeScript export values (e.g. package.json)", () => {
46+
const include = getSrcIncludePaths({
47+
"./package.json": "./package.json",
48+
".": "./src/index.ts",
49+
});
50+
expect(include).toEqual(["../src/index.ts"]);
51+
});
52+
53+
it("does not emit duplicate include entries", () => {
54+
const include = getSrcIncludePaths({
55+
".": "./src/index.ts",
56+
"./alias": "./src/index.ts",
57+
});
58+
expect(include).toEqual(["../src/index.ts"]);
59+
});
60+
61+
it("propagates the exports into each per-target tsconfig", () => {
62+
const exports = {
63+
".": "./src/index.ts",
64+
"./devCenter": "./src/devCenter/index.ts",
65+
};
66+
67+
for (const build of [
68+
buildTsSrcEsmConfig,
69+
buildTsSrcBrowserConfig,
70+
buildTsSrcReactNativeConfig,
71+
buildTsSrcCjsConfig,
72+
]) {
73+
const result = build(exports);
74+
const parsed = JSON.parse(result.content);
75+
expect(parsed.include).toEqual(["../src/index.ts", "../src/devCenter/index.ts"]);
76+
}
77+
});
78+
});

0 commit comments

Comments
 (0)