From 03bdd2e9a89a530b55abfdc23476d444bfb1ef6e Mon Sep 17 00:00:00 2001 From: Deyaaeldeen Almahallawi Date: Fri, 6 Dec 2024 19:22:24 -0800 Subject: [PATCH] Centralized browser tsconfig (#32087) --- common/config/rush/pnpm-lock.yaml | 67 +++++++++- common/tools/dev-tool/package.json | 1 + .../dev-tool/src/commands/run/build-test.ts | 24 ++-- .../dev-tool/src/util/resolveTsConfig.ts | 60 +++++++++ .../dev-tool/test/resolveTsConfig.spec.ts | 117 ++++++++++++++++++ .../event-hubs/tsconfig.browser.config.json | 8 +- .../tsconfig.browser.config.json | 7 +- .../identity/tsconfig.browser.config.json | 8 +- .../openai/tsconfig.browser.config.json | 7 +- tsconfig.browser.base.json | 7 ++ tsconfig.lib.json | 2 +- tsconfig.test.base.json | 2 +- 12 files changed, 269 insertions(+), 41 deletions(-) create mode 100644 common/tools/dev-tool/src/util/resolveTsConfig.ts create mode 100644 common/tools/dev-tool/test/resolveTsConfig.spec.ts create mode 100644 tsconfig.browser.base.json diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index beae7aa5a70c..a584d53c2bf8 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -2719,6 +2719,37 @@ packages: lodash: 4.17.21 dev: false + /@jsonjoy.com/base64@1.1.2(tslib@2.8.1): + resolution: {integrity: sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + dependencies: + tslib: 2.8.1 + dev: false + + /@jsonjoy.com/json-pack@1.1.0(tslib@2.8.1): + resolution: {integrity: sha512-zlQONA+msXPPwHWZMKFVS78ewFczIll5lXiVPwFPCZUsrOKdxc2AvxU1HoNBmMRhqDZUR9HkC3UOm+6pME6Xsg==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + dependencies: + '@jsonjoy.com/base64': 1.1.2(tslib@2.8.1) + '@jsonjoy.com/util': 1.5.0(tslib@2.8.1) + hyperdyperid: 1.2.0 + thingies: 1.21.0(tslib@2.8.1) + tslib: 2.8.1 + dev: false + + /@jsonjoy.com/util@1.5.0(tslib@2.8.1): + resolution: {integrity: sha512-ojoNsrIuPI9g6o8UxhraZQSyF2ByJanAY4cTFbc8Mf2AXEF4aQRGY1dJxyJpuyav8r9FGflEt/Ff3u5Nt6YMPA==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + dependencies: + tslib: 2.8.1 + dev: false + /@microsoft/api-extractor-model@7.30.0(@types/node@18.19.67): resolution: {integrity: sha512-26/LJZBrsWDKAkOWRiQbdVgcfd1F3nyJnAiJzsAgpouPk7LtOIj7PK9aJtBaw/pUXrkotEg27RrT+Jm/q0bbug==} dependencies: @@ -7089,6 +7120,11 @@ packages: ms: 2.1.3 dev: false + /hyperdyperid@1.2.0: + resolution: {integrity: sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==} + engines: {node: '>=10.18'} + dev: false + /iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} @@ -8161,6 +8197,16 @@ packages: engines: {node: '>= 0.6'} dev: false + /memfs@4.14.1: + resolution: {integrity: sha512-Fq5CMEth+2iprLJ5mNizRcWuiwRZYjNkUD0zKk224jZunE9CRacTRDK8QLALbMBlNX2y3nY6lKZbesCwDwacig==} + engines: {node: '>= 4.0.0'} + dependencies: + '@jsonjoy.com/json-pack': 1.1.0(tslib@2.8.1) + '@jsonjoy.com/util': 1.5.0(tslib@2.8.1) + tree-dump: 1.0.2(tslib@2.8.1) + tslib: 2.8.1 + dev: false + /meow@10.1.5: resolution: {integrity: sha512-/d+PQ4GKmGvM9Bee/DPa8z3mXs/pkvJE2KEThngVNOqtmljC6K7NMPxtc2JeZYTmpWb9k/TmxjeL18ez3h7vCw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -10370,6 +10416,15 @@ packages: any-promise: 1.3.0 dev: false + /thingies@1.21.0(tslib@2.8.1): + resolution: {integrity: sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==} + engines: {node: '>=10.18'} + peerDependencies: + tslib: ^2 + dependencies: + tslib: 2.8.1 + dev: false + /through2@2.0.5: resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} dependencies: @@ -10469,6 +10524,15 @@ packages: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} dev: false + /tree-dump@1.0.2(tslib@2.8.1): + resolution: {integrity: sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + dependencies: + tslib: 2.8.1 + dev: false + /tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true @@ -19965,7 +20029,7 @@ packages: dev: false file:projects/dev-tool.tgz: - resolution: {integrity: sha512-o8HIfA9RDgJlu3W5gJoMWW0yJ+dbl3+K3nsCA6mVMIXsCakEfZNSss6z9EUBKA1mlrZeFnhyHtIMQU1LRdUwpQ==, tarball: file:projects/dev-tool.tgz} + resolution: {integrity: sha512-FPrbQSShxuTjy1c/SWvDZN0ywtIGBH/2JJPhihg8E6fJhIOj/RfmTBj2sm8Ft9Eh19Zbgw+kXRy4LEjpeFih3A==, tarball: file:projects/dev-tool.tgz} name: '@rush-temp/dev-tool' version: 0.0.0 dependencies: @@ -20001,6 +20065,7 @@ packages: eslint: 9.16.0 express: 4.21.1 fs-extra: 11.2.0 + memfs: 4.14.1 minimist: 1.2.8 mkdirp: 3.0.1 prettier: 3.4.1 diff --git a/common/tools/dev-tool/package.json b/common/tools/dev-tool/package.json index 674e08758050..131f60ba23e4 100644 --- a/common/tools/dev-tool/package.json +++ b/common/tools/dev-tool/package.json @@ -59,6 +59,7 @@ "env-paths": "^2.2.1", "express": "^4.19.2", "fs-extra": "^11.2.0", + "memfs": "^4.14.1", "minimist": "^1.2.8", "prettier": "^3.3.3", "rollup": "^4.22.0", diff --git a/common/tools/dev-tool/src/commands/run/build-test.ts b/common/tools/dev-tool/src/commands/run/build-test.ts index 824a2c899ac2..3bf9cb415aef 100644 --- a/common/tools/dev-tool/src/commands/run/build-test.ts +++ b/common/tools/dev-tool/src/commands/run/build-test.ts @@ -2,18 +2,11 @@ // Licensed under the MIT License. import path from "node:path"; -import { - cpSync, - existsSync, - mkdirSync, - readFileSync, - readdirSync, - statSync, - writeFileSync, -} from "node:fs"; +import { cpSync, existsSync, mkdirSync, readdirSync, statSync, writeFileSync } from "node:fs"; import { leafCommand, makeCommandInfo } from "../../framework/command"; import { createPrinter } from "../../util/printer"; import { resolveProject } from "../../util/resolveProject"; +import { resolveConfig } from "../../util/resolveTsConfig"; import { spawnSync } from "node:child_process"; const log = createPrinter("build-test"); @@ -107,15 +100,18 @@ function compileForEnvironment( overrideMap: Map, ): boolean { const tsconfigPath = path.join(process.cwd(), tsConfig); - const tsConfigFile = readFileSync(tsconfigPath, "utf8"); - const tsConfigJSON = JSON.parse(tsConfigFile); + const tsConfigJSON = resolveConfig(tsconfigPath); const outputPath = tsConfigJSON.compilerOptions.outDir; if (!existsSync(tsconfigPath)) { log.error(`TypeScript config ${tsConfig} does not exist`); return false; } - const browserTestPath = path.join(process.cwd(), outputPath); + const browserTestPath = outputPath; + if (!browserTestPath) { + log.error(`Output path not defined in ${tsConfig}`); + return false; + } if (!existsSync(browserTestPath)) { mkdirSync(browserTestPath, { recursive: true }); } @@ -191,8 +187,8 @@ function overrideFile( sourceFile: string, destinationFile: string, ): void { - const sourceFileType = path.join(process.cwd(), rootDir, relativeDir, sourceFile); - const destFileType = path.join(process.cwd(), rootDir, relativeDir, destinationFile); + const sourceFileType = path.join(rootDir, relativeDir, sourceFile); + const destFileType = path.join(rootDir, relativeDir, destinationFile); cpSync(sourceFileType, destFileType, { force: true }); } diff --git a/common/tools/dev-tool/src/util/resolveTsConfig.ts b/common/tools/dev-tool/src/util/resolveTsConfig.ts new file mode 100644 index 000000000000..c237f53e2e94 --- /dev/null +++ b/common/tools/dev-tool/src/util/resolveTsConfig.ts @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import * as path from "path"; +import * as fs from "fs"; +import ts from "typescript"; + +type Config = { compilerOptions: ts.CompilerOptions }; + +function resolveConfigDir(configPath: string, unresolvedPath: string) { + return unresolvedPath.replace(/\$\{configDir\}/g, path.dirname(configPath)); +} + +function mergeConfig(config1: Config, config2: Config) { + return { + ...config1, + ...config2, + compilerOptions: { + ...config1.compilerOptions, + ...config2.compilerOptions, + }, + }; +} + +/** + * Generates a complete TypeScript configuration by reading and parsing a given tsconfig file. + * + * @param configPath - The path to the tsconfig file. + * @returns A record containing the raw parsed configuration. + */ +export function resolveConfig(configPath: string) { + function helper(configPath: string) { + const absolutePath = path.resolve(configPath); + const configDir = path.dirname(absolutePath); + const rawConfig = JSON.parse( + fs.readFileSync( + !absolutePath.toLowerCase().endsWith(".json") ? `${absolutePath}.json` : absolutePath, + "utf8", + ), + ); + + let resolvedConfig = {} as Config; + const { extends: parents, ...rest } = rawConfig; + if (parents) { + const baseConfigs = Array.isArray(parents) ? parents : [parents]; + for (const baseConfigPath of baseConfigs) { + const baseConfigAbsolutePath = path.resolve(configDir, baseConfigPath); + const baseConfig = helper(baseConfigAbsolutePath); + resolvedConfig = mergeConfig(resolvedConfig, baseConfig); + } + } + return mergeConfig(resolvedConfig, rest); + } + const resolvedConfig = helper(configPath); + const outDir = resolvedConfig.compilerOptions.outDir; + if (outDir) { + resolvedConfig.compilerOptions.outDir = resolveConfigDir(configPath, outDir); + } + return resolvedConfig; +} diff --git a/common/tools/dev-tool/test/resolveTsConfig.spec.ts b/common/tools/dev-tool/test/resolveTsConfig.spec.ts new file mode 100644 index 000000000000..0f16f95a54a0 --- /dev/null +++ b/common/tools/dev-tool/test/resolveTsConfig.spec.ts @@ -0,0 +1,117 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License + +import { describe, it, beforeEach, expect, vi } from "vitest"; +import { vol } from "memfs"; +import { resolveConfig } from "../src/util/resolveTsConfig"; + +vi.mock("fs", async () => { + const memfs = await import("memfs"); + return { + ...memfs.fs, + }; +}); + +describe("resolveConfig", () => { + beforeEach(() => { + vol.reset(); + }); + + it("should resolve a simple tsconfig.json", () => { + const def = { + compilerOptions: { + target: "ES6", + }, + }; + vol.fromJSON({ + "/project/tsconfig.json": JSON.stringify(def), + }); + + const result = resolveConfig("/project/tsconfig.json"); + expect(result).toEqual(def); + }); + + it("should resolve tsconfig.json with single extend", () => { + vol.fromJSON({ + "/project/tsconfig.base.json": JSON.stringify({ + compilerOptions: { + target: "ES5", + module: "CommonJS", + }, + }), + "/project/tsconfig.json": JSON.stringify({ + extends: "./tsconfig.base.json", + compilerOptions: { + strict: true, + }, + }), + }); + + const result = resolveConfig("/project/tsconfig.json"); + expect(result).toEqual({ + compilerOptions: { + target: "ES5", + module: "CommonJS", + strict: true, + }, + }); + }); + + it("should resolve tsconfig.json with single extend and conflicting option", () => { + vol.fromJSON({ + "/project/tsconfig.base.json": JSON.stringify({ + compilerOptions: { + target: "ES5", + outDir: "path1", + }, + }), + "/project/tsconfig.json": JSON.stringify({ + extends: "./tsconfig.base.json", + compilerOptions: { + strict: true, + outDir: "path2", + }, + }), + }); + + const result = resolveConfig("/project/tsconfig.json"); + expect(result).toEqual({ + compilerOptions: { + target: "ES5", + strict: true, + outDir: "path2", + }, + }); + }); + + it("should resolve tsconfig.json with multiple extends with conflicting options in parents", () => { + vol.fromJSON({ + "/project/tsconfig.base1.json": JSON.stringify({ + compilerOptions: { + target: "ES5", + outDir: "path1", + }, + }), + "/project/tsconfig.base2.json": JSON.stringify({ + compilerOptions: { + outDir: "path2", + }, + }), + "/project/tsconfig.json": JSON.stringify({ + extends: ["./tsconfig.base1.json", "./tsconfig.base2.json"], + compilerOptions: { + strict: true, + }, + }), + }); + + const result = resolveConfig("/project/tsconfig.json"); + expect(result).toEqual({ + compilerOptions: { + target: "ES5", + strict: true, + outDir: "path2", + }, + }); + }); +}); diff --git a/sdk/eventhub/event-hubs/tsconfig.browser.config.json b/sdk/eventhub/event-hubs/tsconfig.browser.config.json index 6080861d7f08..197671191442 100644 --- a/sdk/eventhub/event-hubs/tsconfig.browser.config.json +++ b/sdk/eventhub/event-hubs/tsconfig.browser.config.json @@ -1,8 +1,4 @@ { - "extends": "./tsconfig.test.json", - "exclude": ["./test/**/node", "./test/stress"], - "compilerOptions": { - "outDir": "./dist-test/browser", - "noEmit": false - } + "extends": ["./tsconfig.test.json", "../../../tsconfig.browser.base.json"], + "exclude": ["./test/**/node", "./test/stress"] } diff --git a/sdk/eventhub/eventhubs-checkpointstore-blob/tsconfig.browser.config.json b/sdk/eventhub/eventhubs-checkpointstore-blob/tsconfig.browser.config.json index b268832306ae..75871518e3a0 100644 --- a/sdk/eventhub/eventhubs-checkpointstore-blob/tsconfig.browser.config.json +++ b/sdk/eventhub/eventhubs-checkpointstore-blob/tsconfig.browser.config.json @@ -1,8 +1,3 @@ { - "extends": "./tsconfig.test.json", - "exclude": ["./test/**/node"], - "compilerOptions": { - "outDir": "./dist-test/browser", - "noEmit": false - } + "extends": ["./tsconfig.test.json", "../../../tsconfig.browser.base.json"] } diff --git a/sdk/identity/identity/tsconfig.browser.config.json b/sdk/identity/identity/tsconfig.browser.config.json index 158ecb97e7a3..4bf1b67b1854 100644 --- a/sdk/identity/identity/tsconfig.browser.config.json +++ b/sdk/identity/identity/tsconfig.browser.config.json @@ -1,8 +1,4 @@ { - "extends": "./tsconfig.test.json", - "exclude": ["./test/**/node", "./test/manual*"], - "compilerOptions": { - "outDir": "./dist-test/browser", - "noEmit": false - } + "extends": ["./tsconfig.test.json", "../../../tsconfig.browser.base.json"], + "exclude": ["./test/**/node", "./test/manual*"] } diff --git a/sdk/openai/openai/tsconfig.browser.config.json b/sdk/openai/openai/tsconfig.browser.config.json index b268832306ae..75871518e3a0 100644 --- a/sdk/openai/openai/tsconfig.browser.config.json +++ b/sdk/openai/openai/tsconfig.browser.config.json @@ -1,8 +1,3 @@ { - "extends": "./tsconfig.test.json", - "exclude": ["./test/**/node"], - "compilerOptions": { - "outDir": "./dist-test/browser", - "noEmit": false - } + "extends": ["./tsconfig.test.json", "../../../tsconfig.browser.base.json"] } diff --git a/tsconfig.browser.base.json b/tsconfig.browser.base.json new file mode 100644 index 000000000000..044bef518fc9 --- /dev/null +++ b/tsconfig.browser.base.json @@ -0,0 +1,7 @@ +{ + "exclude": ["${configDir}/test/**/node"], + "compilerOptions": { + "outDir": "${configDir}/dist-test/browser", + "noEmit": false + } +} diff --git a/tsconfig.lib.json b/tsconfig.lib.json index da4e03117385..3276fd337a6a 100644 --- a/tsconfig.lib.json +++ b/tsconfig.lib.json @@ -1,5 +1,5 @@ { - "extends": "./tsconfig.json", + "extends": "./tsconfig", "compilerOptions": { "module": "NodeNext", "moduleResolution": "NodeNext", diff --git a/tsconfig.test.base.json b/tsconfig.test.base.json index a25b45d816e1..8c460d29f7e1 100644 --- a/tsconfig.test.base.json +++ b/tsconfig.test.base.json @@ -1,5 +1,5 @@ { - "extends": "./tsconfig.nonlib.json", + "extends": "./tsconfig.nonlib", "compilerOptions": { "skipLibCheck": true },