From 168c199e795f3062e06147533deec5abe8ef9fa6 Mon Sep 17 00:00:00 2001 From: kazrael2119 <98569699+kazrael2119@users.noreply.github.com> Date: Tue, 9 Jun 2026 15:18:27 +0800 Subject: [PATCH 1/8] [typespec-ts] fix platform import issue for customization --- .../typespec-ts/src/framework/hooks/binder.ts | 31 ++++++++--- .../src/framework/load-static-helpers.ts | 55 +++++++++++++++++-- 2 files changed, 72 insertions(+), 14 deletions(-) diff --git a/packages/typespec-ts/src/framework/hooks/binder.ts b/packages/typespec-ts/src/framework/hooks/binder.ts index 241829b5e6..4820ffcf47 100644 --- a/packages/typespec-ts/src/framework/hooks/binder.ts +++ b/packages/typespec-ts/src/framework/hooks/binder.ts @@ -10,7 +10,11 @@ import { import { provideContext, useContext } from "../../context-manager.js"; import { generateLocallyUniqueName } from "../../modular/helpers/naming-helpers.js"; import { ReferenceableSymbol } from "../dependency.js"; -import { SourceFileSymbol, StaticHelperMetadata } from "../load-static-helpers.js"; +import { + SourceFileSymbol, + StaticHelperMetadata, + getPlatformSubpathSpecifier, +} from "../load-static-helpers.js"; import { refkey } from "../refkey.js"; import { provideDependencies, useDependencies } from "./use-dependencies.js"; @@ -190,21 +194,25 @@ class BinderImp implements Binder { * Returns the #platform/ subpath import specifier for a static helper file * that has a polyfill variant (-browser.mts or -react-native.mts sibling), * or undefined if subpath imports are disabled or no variant exists. - * e.g. "src/static-helpers/serialization/get-binary-response.ts" - * -> "#platform/static-helpers/serialization/get-binary-response" + * The relative path is computed against the package `src` directory (since + * `#platform/*` maps to `./src/*`), derived from the actual `sourceRoot` + * (which can be e.g. `.../src`, `.../src/generated` or `.../generated`) + * rather than a hardcoded "/src/" segment. + * e.g. sourceRoot ".../src/generated", file ".../src/generated/static-helpers/serialization/get-binary-response.ts" + * -> "#platform/generated/static-helpers/serialization/get-binary-response" */ - private getPlatformImportSpecifier(declarationSourceFile: SourceFile): string | undefined { + private getPlatformImportSpecifier( + declarationSourceFile: SourceFile, + sourceRoot: string, + ): string | undefined { if (!this.useSubpathImports) return undefined; const filePath = declarationSourceFile.getFilePath(); - const srcIndex = filePath.indexOf("/src/"); - if (srcIndex === -1) return undefined; // Check if a -browser.mts or -react-native.mts sibling exists const basePath = filePath.replace(/\.ts$/, ""); const hasBrowserVariant = this.project.getSourceFile(basePath + "-browser.mts"); const hasReactNativeVariant = this.project.getSourceFile(basePath + "-react-native.mts"); if (!hasBrowserVariant && !hasReactNativeVariant) return undefined; - const relativePath = filePath.substring(srcIndex + "/src/".length); - return "#platform/" + relativePath.replace(/\.ts$/, ""); + return getPlatformSubpathSpecifier(filePath, sourceRoot); } /** @@ -239,6 +247,7 @@ class BinderImp implements Binder { declarationByPlaceholder, presentPlaceholders, replacementMap, + sourceRoot, ); this.resolveDependencyReferences( file, @@ -300,6 +309,7 @@ class BinderImp implements Binder { declarationByPlaceholder: Map, presentPlaceholders: Set, replacementMap: Map, + sourceRoot: string, ) { for (const [placeholderKey, [declarationKey, declaration]] of declarationByPlaceholder) { if (!presentPlaceholders.has(placeholderKey)) { @@ -318,7 +328,10 @@ class BinderImp implements Binder { if (file !== declarationSourceFile) { this.trackReference(declarationKey, file); // Use #platform/ subpath import specifier for static helpers in warp packages - const platformSpecifier = this.getPlatformImportSpecifier(declarationSourceFile); + const platformSpecifier = this.getPlatformImportSpecifier( + declarationSourceFile, + sourceRoot, + ); const importTarget = platformSpecifier ?? declarationSourceFile; const importDec = this.addImport(file, importTarget, name); name = importDec.alias ?? name; diff --git a/packages/typespec-ts/src/framework/load-static-helpers.ts b/packages/typespec-ts/src/framework/load-static-helpers.ts index e181edf9ea..4bec02375b 100644 --- a/packages/typespec-ts/src/framework/load-static-helpers.ts +++ b/packages/typespec-ts/src/framework/load-static-helpers.ts @@ -1,4 +1,4 @@ -import { NoTarget, Program } from "@typespec/compiler"; +import { normalizePath, NoTarget, Program } from "@typespec/compiler"; import { readdir, readFile, stat } from "fs/promises"; import * as path from "path"; import { @@ -16,6 +16,37 @@ import { isAzurePackage } from "../rlc-common/index.js"; import { resolveProjectRoot } from "../utils/resolve-project-root.js"; import { refkey } from "./refkey.js"; export const SourceFileSymbol = Symbol("SourceFile"); + +/** + * Computes the `#platform/` subpath import specifier for a file, where the + * path is relative to the package `src` directory (since `#platform/*` maps to + * `./src/*` in the generated package.json). + * + * The package `src` directory is derived from the actual `sourceRoot` (which may + * be e.g. `.../src`, `.../src/generated` or `.../generated`) by locating its + * trailing `src` path segment, instead of assuming a hardcoded `/src/` segment. + * Returns `undefined` when no `src` segment can be found or the file is not + * located under it. + */ +export function getPlatformSubpathSpecifier( + filePath: string, + sourceRoot: string, +): string | undefined { + const normalizedFile = normalizePath(filePath); + const normalizedRoot = normalizePath(sourceRoot).replace(/\/+$/, ""); + const rootSegments = normalizedRoot.split("/"); + const srcSegmentIndex = rootSegments.lastIndexOf("src"); + if (srcSegmentIndex === -1) { + return undefined; + } + const srcDir = rootSegments.slice(0, srcSegmentIndex + 1).join("/"); + if (!normalizedFile.startsWith(`${srcDir}/`)) { + return undefined; + } + const relativePath = normalizedFile.substring(srcDir.length + 1); + return `#platform/${relativePath.replace(/\.[cm]?[jt]s$/, "")}`; +} + export interface StaticHelperMetadata { name: string; kind: "function" | "interface" | "typeAlias" | "class" | "enum"; @@ -85,12 +116,20 @@ export async function loadStaticHelpers( return assertAllHelpersLoadedPresent(helpersMap); async function loadFiles(files: FileMetadata[], generateDir: string) { + const sourcePaths = new Set(files.map((f) => normalizePath(f.source))); for (const file of files) { const targetPath = path.join(generateDir, file.target); const contents = await readFile(file.source, "utf-8"); const addedFile = project.createSourceFile(targetPath, contents, { overwrite: true, }); + // A file with its own platform variant (a sibling -browser.mts / + // -react-native.mts) is resolved as a whole via #platform, so its + // internal relative platform-types imports must be kept relative. + const sourceBase = normalizePath(file.source).replace(/\.[cm]?ts$/, ""); + const hasPlatformVariant = + sourcePaths.has(`${sourceBase}-browser.mts`) || + sourcePaths.has(`${sourceBase}-react-native.mts`); addedFile.getImportDeclarations().map((i) => { if (!isAzurePackage({ options: options.options })) { if (i.getModuleSpecifier().getFullText().includes("@azure/core-rest-pipeline")) { @@ -103,8 +142,9 @@ export async function loadStaticHelpers( // Rewrite relative platform-types imports to #platform/ specifiers // so that browser/react-native variants are resolved via subpath imports. // Only rewrite imports to the default variant (not -browser/-react-native variants - // which are already platform-specific direct imports). - if (options.options?.azureSdkForJs) { + // which are already platform-specific direct imports), and only when the + // current file does not itself have a platform variant. + if (options.options?.azureSdkForJs && !hasPlatformVariant) { const specifier = i.getModuleSpecifierValue(); if ( specifier.startsWith(".") && @@ -112,11 +152,16 @@ export async function loadStaticHelpers( !specifier.includes("-browser") && !specifier.includes("-react-native") ) { - i.setModuleSpecifier("#platform/static-helpers/platform-types"); + const platformTypesPath = path.posix.normalize( + path.posix.join(path.posix.dirname(normalizePath(targetPath)), specifier), + ); + const platformSpecifier = getPlatformSubpathSpecifier(platformTypesPath, generateDir); + if (platformSpecifier) { + i.setModuleSpecifier(platformSpecifier); + } } } }); - for (const entry of Object.values(helpers)) { if (!addedFile.getFilePath().endsWith(entry.location)) { continue; From c670826b17924b873684d1d91466c04bf4a13522 Mon Sep 17 00:00:00 2001 From: kazrael2119 <98569699+kazrael2119@users.noreply.github.com> Date: Mon, 22 Jun 2026 13:53:49 +0800 Subject: [PATCH 2/8] update --- .../src/framework/load-static-helpers.ts | 26 ++++--------------- packages/typespec-ts/src/index.ts | 6 ++--- .../src/modular/external-dependencies.ts | 10 +++++++ .../src/modular/static-helpers-metadata.ts | 14 +++------- .../src/rlc-common/build-parameter-types.ts | 2 +- .../src/rlc-common/build-schema-type.ts | 2 +- .../rlc-common/metadata/build-package-file.ts | 9 ++++--- .../package-json/azure-package-common.ts | 4 +-- .../build-azure-monorepo-package.ts | 4 +-- .../static-helpers/platform-types-browser.mts | 5 ---- .../platform-types-react-native.mts | 5 ---- .../get-binary-stream-response-browser.mts | 22 ---------------- ...et-binary-stream-response-react-native.mts | 1 - .../get-binary-stream-response.ts | 22 ---------------- .../integration/load-static-files.test.ts | 4 +-- pnpm-workspace.yaml | 4 +-- 16 files changed, 37 insertions(+), 103 deletions(-) delete mode 100644 packages/typespec-ts/static/static-helpers/platform-types-browser.mts delete mode 100644 packages/typespec-ts/static/static-helpers/platform-types-react-native.mts delete mode 100644 packages/typespec-ts/static/static-helpers/serialization/get-binary-stream-response-browser.mts delete mode 100644 packages/typespec-ts/static/static-helpers/serialization/get-binary-stream-response-react-native.mts delete mode 100644 packages/typespec-ts/static/static-helpers/serialization/get-binary-stream-response.ts diff --git a/packages/typespec-ts/src/framework/load-static-helpers.ts b/packages/typespec-ts/src/framework/load-static-helpers.ts index 4bec02375b..318030173c 100644 --- a/packages/typespec-ts/src/framework/load-static-helpers.ts +++ b/packages/typespec-ts/src/framework/load-static-helpers.ts @@ -116,20 +116,12 @@ export async function loadStaticHelpers( return assertAllHelpersLoadedPresent(helpersMap); async function loadFiles(files: FileMetadata[], generateDir: string) { - const sourcePaths = new Set(files.map((f) => normalizePath(f.source))); for (const file of files) { const targetPath = path.join(generateDir, file.target); const contents = await readFile(file.source, "utf-8"); const addedFile = project.createSourceFile(targetPath, contents, { overwrite: true, }); - // A file with its own platform variant (a sibling -browser.mts / - // -react-native.mts) is resolved as a whole via #platform, so its - // internal relative platform-types imports must be kept relative. - const sourceBase = normalizePath(file.source).replace(/\.[cm]?ts$/, ""); - const hasPlatformVariant = - sourcePaths.has(`${sourceBase}-browser.mts`) || - sourcePaths.has(`${sourceBase}-react-native.mts`); addedFile.getImportDeclarations().map((i) => { if (!isAzurePackage({ options: options.options })) { if (i.getModuleSpecifier().getFullText().includes("@azure/core-rest-pipeline")) { @@ -139,12 +131,10 @@ export async function loadStaticHelpers( i.setModuleSpecifier("@typespec/ts-http-runtime"); } } - // Rewrite relative platform-types imports to #platform/ specifiers - // so that browser/react-native variants are resolved via subpath imports. - // Only rewrite imports to the default variant (not -browser/-react-native variants - // which are already platform-specific direct imports), and only when the - // current file does not itself have a platform variant. - if (options.options?.azureSdkForJs && !hasPlatformVariant) { + // Rewrite relative platform-types imports to @azure/core-rest-pipeline for azure packages + // (NodeReadableStream is now exported directly from @azure/core-rest-pipeline). + // Non-azure packages keep the relative import to the local platform-types.ts. + if (options.options?.azureSdkForJs) { const specifier = i.getModuleSpecifierValue(); if ( specifier.startsWith(".") && @@ -152,13 +142,7 @@ export async function loadStaticHelpers( !specifier.includes("-browser") && !specifier.includes("-react-native") ) { - const platformTypesPath = path.posix.normalize( - path.posix.join(path.posix.dirname(normalizePath(targetPath)), specifier), - ); - const platformSpecifier = getPlatformSubpathSpecifier(platformTypesPath, generateDir); - if (platformSpecifier) { - i.setModuleSpecifier(platformSpecifier); - } + i.setModuleSpecifier("@azure/core-rest-pipeline"); } } }); diff --git a/packages/typespec-ts/src/index.ts b/packages/typespec-ts/src/index.ts index b98a1c07b9..470c3b02e6 100644 --- a/packages/typespec-ts/src/index.ts +++ b/packages/typespec-ts/src/index.ts @@ -16,7 +16,6 @@ import { CreateRecorderHelpers, MultipartHelpers, PagingHelpers, - PlatformTypeHelpers, PollingHelpers, SerializationHelpers, SimplePollerHelpers, @@ -84,7 +83,7 @@ import { basename, join } from "path"; import { Project } from "ts-morph"; import { provideBinder } from "./framework/hooks/binder.js"; import { provideSdkTypes } from "./framework/hooks/sdk-types.js"; -import { loadStaticHelpers } from "./framework/load-static-helpers.js"; +import { loadStaticHelpers, type StaticHelpers } from "./framework/load-static-helpers.js"; import { EmitterOptions } from "./lib.js"; import { buildClassicalClient } from "./modular/build-classical-client.js"; import { buildClassicOperationFiles } from "./modular/build-classical-operation-groups.js"; @@ -150,12 +149,11 @@ export async function $onEmit(context: EmitContext) { ...SimplePollerHelpers, ...UrlTemplateHelpers, ...MultipartHelpers, - ...PlatformTypeHelpers, ...CloudSettingHelpers, ...XmlHelpers, ...(rlcOptions.generateTest ? CreateRecorderHelpers : {}), ...(rlcOptions.enableStorageCompat ? StorageCompatHelpers : {}), - }, + } as unknown as StaticHelpers, { sourcesDir: dpgContext.generationPathDetail?.modularSourcesDir, rootDir: dpgContext.generationPathDetail?.rootDir, diff --git a/packages/typespec-ts/src/modular/external-dependencies.ts b/packages/typespec-ts/src/modular/external-dependencies.ts index da36ad7c12..d7492dc7fd 100644 --- a/packages/typespec-ts/src/modular/external-dependencies.ts +++ b/packages/typespec-ts/src/modular/external-dependencies.ts @@ -207,6 +207,16 @@ export const AzureCoreDependencies: CoreDependencies = { name: "ErrorResponse", module: "@azure-rest/core-client", }, + getBinaryStreamResponse: { + kind: "externalDependency", + module: "@azure-rest/core-client", + name: "getBinaryStreamResponse", + }, + NodeReadableStream: { + kind: "externalDependency", + module: "@azure/core-rest-pipeline", + name: "NodeReadableStream", + }, }; export const AzureIdentityDependencies = { diff --git a/packages/typespec-ts/src/modular/static-helpers-metadata.ts b/packages/typespec-ts/src/modular/static-helpers-metadata.ts index fdb7821057..3226ee41dc 100644 --- a/packages/typespec-ts/src/modular/static-helpers-metadata.ts +++ b/packages/typespec-ts/src/modular/static-helpers-metadata.ts @@ -1,3 +1,5 @@ +import { AzureCoreDependencies } from "./external-dependencies.js"; + export const SerializationHelpers = { buildMultiCollection: { kind: "function", @@ -59,11 +61,7 @@ export const SerializationHelpers = { name: "getBinaryResponse", location: "serialization/get-binary-response.ts", }, - getBinaryStreamResponse: { - kind: "function", - name: "getBinaryStreamResponse", - location: "serialization/get-binary-stream-response.ts", - }, + getBinaryStreamResponse: AzureCoreDependencies["getBinaryStreamResponse"], areAllPropsUndefined: { kind: "function", name: "areAllPropsUndefined", @@ -151,11 +149,7 @@ export const MultipartHelpers = { } as const; export const PlatformTypeHelpers = { - NodeReadableStream: { - kind: "typeAlias", - name: "NodeReadableStream", - location: "platform-types.ts", - }, + NodeReadableStream: AzureCoreDependencies["NodeReadableStream"], } as const; export const CloudSettingHelpers = { diff --git a/packages/typespec-ts/src/rlc-common/build-parameter-types.ts b/packages/typespec-ts/src/rlc-common/build-parameter-types.ts index 17ff54e57d..3d9175b84e 100644 --- a/packages/typespec-ts/src/rlc-common/build-parameter-types.ts +++ b/packages/typespec-ts/src/rlc-common/build-parameter-types.ts @@ -177,7 +177,7 @@ export function buildParameterTypes(model: RLCModel) { // union arm drops out naturally in non-Node builds. if (parametersFile.getFullText().includes("NodeReadableStream")) { const platformTypesModuleSpecifier = model.options?.azureSdkForJs - ? "#platform/platform-types" + ? "@azure/core-rest-pipeline" : getImportModuleName( { cjsName: `./platform-types`, diff --git a/packages/typespec-ts/src/rlc-common/build-schema-type.ts b/packages/typespec-ts/src/rlc-common/build-schema-type.ts index 040c465a4e..414c220363 100644 --- a/packages/typespec-ts/src/rlc-common/build-schema-type.ts +++ b/packages/typespec-ts/src/rlc-common/build-schema-type.ts @@ -67,7 +67,7 @@ export function generateModelFiles( // union arm drops out naturally in non-Node builds. if (modelsFile.getFullText().includes("NodeReadableStream")) { const platformTypesModuleSpecifier = model.options?.azureSdkForJs - ? "#platform/platform-types" + ? "@azure/core-rest-pipeline" : getImportModuleName( { cjsName: `./platform-types`, diff --git a/packages/typespec-ts/src/rlc-common/metadata/build-package-file.ts b/packages/typespec-ts/src/rlc-common/metadata/build-package-file.ts index 5f25c27843..251a2f993c 100644 --- a/packages/typespec-ts/src/rlc-common/metadata/build-package-file.ts +++ b/packages/typespec-ts/src/rlc-common/metadata/build-package-file.ts @@ -184,9 +184,6 @@ export function updatePackageFile( // Update Core Client dependency if (needsCoreClientUpdate) { delete deps["@azure/core-client"]; - if (!("@azure-rest/core-client" in deps)) { - deps["@azure-rest/core-client"] = "^2.3.1"; - } packageInfo.dependencies = deps; } @@ -199,6 +196,12 @@ export function updatePackageFile( }; } + packageInfo.dependencies = { + ...packageInfo.dependencies, + "@azure/core-rest-pipeline": "^1.24.0", + "@azure-rest/core-client": "^2.7.0", + }; + // Update constantPaths metadata for Azure packages if (needsConstantPathsUpdate && isAzure && packageInfo["//metadata"]) { const metadata = packageInfo["//metadata"]; diff --git a/packages/typespec-ts/src/rlc-common/metadata/package-json/azure-package-common.ts b/packages/typespec-ts/src/rlc-common/metadata/package-json/azure-package-common.ts index 84bd20b3b7..0cf69478f6 100644 --- a/packages/typespec-ts/src/rlc-common/metadata/package-json/azure-package-common.ts +++ b/packages/typespec-ts/src/rlc-common/metadata/package-json/azure-package-common.ts @@ -31,9 +31,9 @@ export function getAzurePackageDependencies({ }: AzurePackageInfoConfig) { let azureDependencies: Record = { ...dependencies, - "@azure-rest/core-client": specSource === "Swagger" ? "^1.4.0" : "^2.3.1", + "@azure-rest/core-client": specSource === "Swagger" ? "^1.4.0" : "^2.7.0", "@azure/core-auth": "^1.6.0", - "@azure/core-rest-pipeline": "^1.5.0", + "@azure/core-rest-pipeline": "^1.24.0", "@azure/logger": "^1.0.0", tslib: "^2.6.2", }; diff --git a/packages/typespec-ts/src/rlc-common/metadata/package-json/build-azure-monorepo-package.ts b/packages/typespec-ts/src/rlc-common/metadata/package-json/build-azure-monorepo-package.ts index e1a6640a76..00a9bd56d8 100644 --- a/packages/typespec-ts/src/rlc-common/metadata/package-json/build-azure-monorepo-package.ts +++ b/packages/typespec-ts/src/rlc-common/metadata/package-json/build-azure-monorepo-package.ts @@ -33,7 +33,7 @@ export function getAzureMonorepoDependencies(config: AzureMonorepoInfoConfig) { // revert this change after sdk repo update. const runtimeDeps = { ...dependencies, - "@azure-rest/core-client": "^2.3.1", + "@azure-rest/core-client": "^2.7.0", ...(hasLro && { "@azure/abort-controller": "^2.1.2", }), @@ -41,7 +41,7 @@ export function getAzureMonorepoDependencies(config: AzureMonorepoInfoConfig) { ...(hasLro && { "@azure/core-lro": "^3.1.0", }), - "@azure/core-rest-pipeline": "^1.20.0", + "@azure/core-rest-pipeline": "^1.24.0", "@azure/core-util": "^1.12.0", "@azure/logger": "^1.2.0", tslib: "^2.8.1", diff --git a/packages/typespec-ts/static/static-helpers/platform-types-browser.mts b/packages/typespec-ts/static/static-helpers/platform-types-browser.mts deleted file mode 100644 index 6b6410ced0..0000000000 --- a/packages/typespec-ts/static/static-helpers/platform-types-browser.mts +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Browser platform variant — NodeReadableStream resolves to `never` - * so it drops out of union types and optional properties become effectively absent. - */ -export type NodeReadableStream = never; diff --git a/packages/typespec-ts/static/static-helpers/platform-types-react-native.mts b/packages/typespec-ts/static/static-helpers/platform-types-react-native.mts deleted file mode 100644 index e13cb8e69b..0000000000 --- a/packages/typespec-ts/static/static-helpers/platform-types-react-native.mts +++ /dev/null @@ -1,5 +0,0 @@ -/** - * React Native platform variant — NodeReadableStream resolves to `never` - * so it drops out of union types and optional properties become effectively absent. - */ -export type NodeReadableStream = never; diff --git a/packages/typespec-ts/static/static-helpers/serialization/get-binary-stream-response-browser.mts b/packages/typespec-ts/static/static-helpers/serialization/get-binary-stream-response-browser.mts deleted file mode 100644 index dbdf3faf77..0000000000 --- a/packages/typespec-ts/static/static-helpers/serialization/get-binary-stream-response-browser.mts +++ /dev/null @@ -1,22 +0,0 @@ -import { HttpResponse, StreamableMethod } from "@azure-rest/core-client"; -import { NodeReadableStream } from "../platform-types-browser.mjs"; - -/** - * Resolves a StreamableMethod into a binary stream response using browser streaming. - * Returns both the raw HttpResponse (for status/header inspection) and a blobBody Promise. - * Error handling is left to the caller so that generated deserializers can apply - * operation-specific error deserialization (per-status-code details, exception headers, etc.). - */ -export async function getBinaryStreamResponse(streamableMethod: StreamableMethod): Promise< - HttpResponse & { - blobBody?: Promise; - readableStreamBody?: NodeReadableStream; - } -> { - const response = await streamableMethod.asBrowserStream(); - return { - ...response, - blobBody: new Response(response.body).blob(), - readableStreamBody: undefined, - }; -} diff --git a/packages/typespec-ts/static/static-helpers/serialization/get-binary-stream-response-react-native.mts b/packages/typespec-ts/static/static-helpers/serialization/get-binary-stream-response-react-native.mts deleted file mode 100644 index d30a405be6..0000000000 --- a/packages/typespec-ts/static/static-helpers/serialization/get-binary-stream-response-react-native.mts +++ /dev/null @@ -1 +0,0 @@ -export { getBinaryStreamResponse } from "./get-binary-stream-response-browser.mjs"; diff --git a/packages/typespec-ts/static/static-helpers/serialization/get-binary-stream-response.ts b/packages/typespec-ts/static/static-helpers/serialization/get-binary-stream-response.ts deleted file mode 100644 index ff37e55031..0000000000 --- a/packages/typespec-ts/static/static-helpers/serialization/get-binary-stream-response.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { HttpResponse, StreamableMethod } from "@azure-rest/core-client"; -import { NodeReadableStream } from "../platform-types.js"; - -/** - * Resolves a StreamableMethod into a binary stream response using Node.js streaming. - * Returns both the raw HttpResponse (for status/header inspection) and the readable stream body. - * Error handling is left to the caller so that generated deserializers can apply - * operation-specific error deserialization (per-status-code details, exception headers, etc.). - */ -export async function getBinaryStreamResponse(streamableMethod: StreamableMethod): Promise< - HttpResponse & { - blobBody?: Promise; - readableStreamBody?: NodeReadableStream; - } -> { - const response = await streamableMethod.asNodeStream(); - return { - ...response, - blobBody: undefined, - readableStreamBody: response.body, - }; -} diff --git a/packages/typespec-ts/test-next/integration/load-static-files.test.ts b/packages/typespec-ts/test-next/integration/load-static-files.test.ts index 3efaeba0f1..ba6794b23b 100644 --- a/packages/typespec-ts/test-next/integration/load-static-files.test.ts +++ b/packages/typespec-ts/test-next/integration/load-static-files.test.ts @@ -69,7 +69,7 @@ describe("loadStaticHelpers", () => { ).rejects.toThrowError(/invalid helper kind/); }); - it("should rewrite platform-types imports to #platform subpath without extension for azure monorepo", async () => { + it("should rewrite platform-types imports to @azure/core-rest-pipeline for azure monorepo", async () => { const helpers = { usesPlatformImport: { kind: "function", @@ -90,7 +90,7 @@ describe("loadStaticHelpers", () => { .getSourceFiles() .find((file) => file.getFilePath().endsWith("/static-helpers/platform-import.ts")); assert(sourceFile); - const importDecl = sourceFile.getImportDeclaration("#platform/static-helpers/platform-types"); + const importDecl = sourceFile.getImportDeclaration("@azure/core-rest-pipeline"); expect(importDecl).toBeDefined(); }); }); diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 868cc9d84d..90905fd88f 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -31,12 +31,12 @@ catalog: "@astrojs/check": ^0.9.8 "@astrojs/react": ^5.0.3 "@astrojs/starlight": ^0.39.2 - "@azure-rest/core-client": ^2.3.1 + "@azure-rest/core-client": ^2.7.0 "@azure/abort-controller": ^2.1.2 "@azure/core-auth": ^1.6.0 "@azure/core-lro": ^3.1.0 "@azure/core-paging": ^1.5.0 - "@azure/core-rest-pipeline": ^1.14.0 + "@azure/core-rest-pipeline": ^1.24.0 "@azure/core-util": ^1.4.0 "@azure/identity": ^4.13.1 "@azure/logger": ^1.0.4 From 525cbbe884045287515f1d0392859c4435e65b48 Mon Sep 17 00:00:00 2001 From: ZiWei Chen Date: Thu, 25 Jun 2026 15:18:48 +0800 Subject: [PATCH 3/8] Update pnpm-lock.yaml --- pnpm-lock.yaml | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 16a050eb9a..924257fdfc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -40,8 +40,8 @@ catalogs: specifier: ^0.39.2 version: 0.39.2 '@azure-rest/core-client': - specifier: ^2.3.1 - version: 2.6.0 + specifier: ^2.7.0 + version: 2.7.0 '@azure/abort-controller': specifier: ^2.1.2 version: 2.1.2 @@ -55,8 +55,8 @@ catalogs: specifier: ^1.5.0 version: 1.6.2 '@azure/core-rest-pipeline': - specifier: ^1.14.0 - version: 1.23.0 + specifier: ^1.24.0 + version: 1.24.0 '@azure/core-util': specifier: ^1.4.0 version: 1.13.1 @@ -3895,7 +3895,7 @@ importers: devDependencies: '@azure-rest/core-client': specifier: 'catalog:' - version: 2.6.0 + version: 2.7.0 '@azure-tools/azure-http-specs': specifier: workspace:^ version: link:../azure-http-specs @@ -3925,7 +3925,7 @@ importers: version: 1.6.2 '@azure/core-rest-pipeline': specifier: 'catalog:' - version: 1.23.0 + version: 1.24.0 '@azure/core-util': specifier: 'catalog:' version: 1.13.1 @@ -4280,8 +4280,8 @@ packages: '@azu/style-format@1.0.1': resolution: {integrity: sha512-AHcTojlNBdD/3/KxIKlg8sxIWHfOtQszLvOpagLTO+bjC3u7SAszu1lf//u7JJC50aUSH+BVWDD/KvaA6Gfn5g==} - '@azure-rest/core-client@2.6.0': - resolution: {integrity: sha512-iuFKDm8XPzNxPfRjhyU5/xKZmcRDzSuEghXDHHk4MjBV/wFL34GmYVBZnn9wmuoLBeS1qAw9ceMdaeJBPcB1QQ==} + '@azure-rest/core-client@2.7.0': + resolution: {integrity: sha512-rL0lJqh1E8HLXNgjIw8cRyGAV/v+m6p1xRu/8OhsnmN8XHhwkyYJkAoGM+zrew96v7jZYPmVfy7pv7v4Iccfsg==} engines: {node: '>=20.0.0'} '@azure/abort-controller@2.1.2': @@ -4319,6 +4319,10 @@ packages: resolution: {integrity: sha512-Evs1INHo+jUjwHi1T6SG6Ua/LHOQBCLuKEEE6efIpt4ZOoNonaT1kP32GoOcdNDbfqsD2445CPri3MubBy5DEQ==} engines: {node: '>=20.0.0'} + '@azure/core-rest-pipeline@1.24.0': + resolution: {integrity: sha512-PpLsoDQ3AMmKZ0VU+0GrmqMxgp/sExjlVm4R+nLWngeoEGAzOIPVifaxKGU5gMv+nWELUoHfvrolWD+ZS/nFJg==} + engines: {node: '>=20.0.0'} + '@azure/core-tracing@1.3.1': resolution: {integrity: sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ==} engines: {node: '>=20.0.0'} @@ -15336,11 +15340,11 @@ snapshots: dependencies: '@azu/format-text': 1.0.2 - '@azure-rest/core-client@2.6.0': + '@azure-rest/core-client@2.7.0': dependencies: '@azure/abort-controller': 2.1.2 '@azure/core-auth': 1.10.1 - '@azure/core-rest-pipeline': 1.23.0 + '@azure/core-rest-pipeline': 1.24.0 '@azure/core-tracing': 1.3.1 '@typespec/ts-http-runtime': 0.3.5 tslib: 2.8.1 @@ -15411,6 +15415,18 @@ snapshots: transitivePeerDependencies: - supports-color + '@azure/core-rest-pipeline@1.24.0': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-auth': 1.10.1 + '@azure/core-tracing': 1.3.1 + '@azure/core-util': 1.13.1 + '@azure/logger': 1.3.0 + '@typespec/ts-http-runtime': 0.3.5 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + '@azure/core-tracing@1.3.1': dependencies: tslib: 2.8.1 From 5d8e67b0e5d4f93f56963fc6977a92f0704d4614 Mon Sep 17 00:00:00 2001 From: ZiWei Chen Date: Fri, 26 Jun 2026 11:00:56 +0800 Subject: [PATCH 4/8] update --- .../typespec-ts/src/framework/hooks/binder.ts | 2 - .../src/rlc-common/build-parameter-types.ts | 554 ------------------ .../src/rlc-common/build-schema-type.ts | 89 --- 3 files changed, 645 deletions(-) delete mode 100644 packages/typespec-ts/src/rlc-common/build-parameter-types.ts delete mode 100644 packages/typespec-ts/src/rlc-common/build-schema-type.ts diff --git a/packages/typespec-ts/src/framework/hooks/binder.ts b/packages/typespec-ts/src/framework/hooks/binder.ts index a2adb40d7e..db3385c190 100644 --- a/packages/typespec-ts/src/framework/hooks/binder.ts +++ b/packages/typespec-ts/src/framework/hooks/binder.ts @@ -220,7 +220,6 @@ class BinderImp implements Binder { declarationByPlaceholder, presentPlaceholders, replacementMap, - sourceRoot, ); this.resolveDependencyReferences( file, @@ -282,7 +281,6 @@ class BinderImp implements Binder { declarationByPlaceholder: Map, presentPlaceholders: Set, replacementMap: Map, - sourceRoot: string, ) { for (const [placeholderKey, [declarationKey, declaration]] of declarationByPlaceholder) { if (!presentPlaceholders.has(placeholderKey)) { diff --git a/packages/typespec-ts/src/rlc-common/build-parameter-types.ts b/packages/typespec-ts/src/rlc-common/build-parameter-types.ts deleted file mode 100644 index 3d9175b84e..0000000000 --- a/packages/typespec-ts/src/rlc-common/build-parameter-types.ts +++ /dev/null @@ -1,554 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -import * as path from "path"; -import { - InterfaceDeclarationStructure, - Project, - PropertySignatureStructure, - SourceFile, - StructureKind, -} from "ts-morph"; -import { getObjectInterfaceDeclaration } from "./build-object-types.js"; -import { getImportSpecifier } from "./helpers/imports-util.js"; -import { - getImportModuleName, - getParameterBaseName, - getParameterTypeName, -} from "./helpers/name-constructors.js"; -import { getGeneratedWrapperTypes } from "./helpers/operation-helpers.js"; -import { - ObjectSchema, - ParameterMetadata, - ParameterMetadatas, - RLCModel, - Schema, - SchemaContext, -} from "./interfaces.js"; - -export function buildParameterTypes(model: RLCModel) { - const project = new Project(); - const srcPath = model.srcPath; - const filePath = path.join(srcPath, `parameters.ts`); - const partialBodyTypeNames = new Set(); - const parametersFile = project.createSourceFile(filePath, undefined, { - overwrite: true, - }); - let hasHeaders = false; - - if (!model.parameters) { - return; - } - for (const requestParameter of model.parameters) { - const baseParameterName = getParameterBaseName( - requestParameter.operationGroup, - requestParameter.operationName, - ); - const requestCount = requestParameter?.parameters?.length ?? 0; - const topParamName = getParameterTypeName(baseParameterName); - const subParamNames: string[] = []; - - // We need to loop the requests. An operation with multiple requests means that - // the operation can get different values for content-type and each value may - // have a different type associated to it. - for (let i = 0; i < requestCount; i++) { - const parameter = requestParameter.parameters[i]; - if (!parameter) { - continue; - } - const internalReferences = new Set(); - // In case we have more than one request to model we need to add a suffix to differentiate - const nameSuffix = i > 0 ? `${i}` : ""; - const parameterInterfaceName = - requestCount > 1 ? `${baseParameterName}RequestParameters${nameSuffix}` : topParamName; - const queryParameterDefinitions = buildQueryParameterDefinition( - model, - parameter, - baseParameterName, - internalReferences, - i, - ); - const pathParameterDefinitions = buildPathParameterDefinitions( - model, - parameter, - baseParameterName, - parametersFile, - internalReferences, - i, - ); - - const headerParameterDefinitions = buildHeaderParameterDefinitions( - parameter, - baseParameterName, - parametersFile, - internalReferences, - i, - ); - - const contentTypeParameterDefinition = buildContentTypeParametersDefinition( - parameter, - baseParameterName, - internalReferences, - i, - ); - - const bodyParameterDefinition = buildBodyParametersDefinition( - parameter, - baseParameterName, - internalReferences, - i, - ); - - const bodyTypeAlias = buildBodyTypeAlias(parameter, partialBodyTypeNames); - if (bodyTypeAlias) { - parametersFile.addTypeAlias(bodyTypeAlias); - } - - // Add interfaces for body and query parameters - parametersFile.addInterfaces([ - ...(bodyParameterDefinition ?? []), - ...(queryParameterDefinitions ?? []), - ...(pathParameterDefinitions ?? []), - ...(headerParameterDefinitions ? [headerParameterDefinitions] : []), - ...(contentTypeParameterDefinition ? [contentTypeParameterDefinition] : []), - ]); - - // Add Operation parameters type alias which is composed of the types we generated above - // plus the common type RequestParameters - parametersFile.addTypeAlias({ - name: parameterInterfaceName, - isExported: true, - type: [...internalReferences, "RequestParameters"].join(" & "), - }); - - subParamNames.push(parameterInterfaceName); - - if (headerParameterDefinitions !== undefined) { - hasHeaders = true; - } - } - // Add Operation parameters type alias which is composed of the types we generated above - // plus the common type RequestParameters - if (requestCount > 1) { - parametersFile.addTypeAlias({ - name: topParamName, - isExported: true, - type: [...subParamNames].join(" | "), - }); - } - } - - if (hasHeaders) { - parametersFile.addImportDeclarations([ - { - isTypeOnly: true, - namedImports: ["RawHttpHeadersInput"], - moduleSpecifier: getImportSpecifier("restPipeline", model.importInfo.runtimeImports), - }, - ]); - } - parametersFile.addImportDeclarations([ - { - isTypeOnly: true, - namedImports: ["RequestParameters"], - moduleSpecifier: getImportSpecifier("restClient", model.importInfo.runtimeImports), - }, - ]); - if ((model.importInfo.internalImports?.parameter?.importsSet?.size ?? 0) > 0) { - parametersFile.addImportDeclarations([ - { - isTypeOnly: true, - namedImports: Array.from(model.importInfo.internalImports.parameter.importsSet!), - moduleSpecifier: getImportModuleName( - { - cjsName: `./models`, - esModulesName: `./models.js`, - }, - model, - ), - }, - ]); - } - // Add NodeReadableStream import if binary types are used in parameters. - // platform-types.ts is generated directly under src/ (no static-helpers/ - // subdirectory) to match the RLC design where all output lives in src/. - // The platform-types static helper resolves NodeReadableStream to - // NodeJS.ReadableStream on Node and `never` on browser/react-native, so the - // union arm drops out naturally in non-Node builds. - if (parametersFile.getFullText().includes("NodeReadableStream")) { - const platformTypesModuleSpecifier = model.options?.azureSdkForJs - ? "@azure/core-rest-pipeline" - : getImportModuleName( - { - cjsName: `./platform-types`, - esModulesName: `./platform-types.js`, - }, - model, - ); - parametersFile.addImportDeclarations([ - { - isTypeOnly: true, - namedImports: ["NodeReadableStream"], - moduleSpecifier: platformTypesModuleSpecifier, - }, - ]); - } - return { path: filePath, content: parametersFile.getFullText() }; -} - -function buildQueryParameterDefinition( - model: RLCModel, - parameters: ParameterMetadatas, - baseName: string, - internalReferences: Set, - requestIndex: number, -): InterfaceDeclarationStructure[] | undefined { - const queryParameters = (parameters?.parameters || []).filter((p) => p.type === "query"); - - if (!queryParameters.length) { - return undefined; - } - - const nameSuffix = requestIndex > 0 ? `${requestIndex}` : ""; - const queryParameterInterfaceName = `${baseName}QueryParam${nameSuffix}`; - const queryParameterPropertiesName = `${baseName}QueryParamProperties`; - - // Get the property signature for each query parameter - const propertiesDefinition = queryParameters.map((qp) => getPropertyFromSchema(qp.param)); - // Get wrapper types for query parameters - const wrapperTypesDefinition = getGeneratedWrapperTypes(queryParameters).map((wrapObj) => { - return getObjectInterfaceDeclaration( - model, - wrapObj.name, - wrapObj, - [SchemaContext.Input], - new Set(), - ); - }); - - const hasRequiredParameters = propertiesDefinition.some((p) => !p.hasQuestionToken); - - const propertiesInterface: InterfaceDeclarationStructure = { - kind: StructureKind.Interface, - isExported: true, - name: queryParameterPropertiesName, - properties: propertiesDefinition, - }; - - const parameterInterface: InterfaceDeclarationStructure = { - kind: StructureKind.Interface, - isExported: true, - name: queryParameterInterfaceName, - properties: [ - { - name: "queryParameters", - type: queryParameterPropertiesName, - // Mark as optional if there are no required parameters - hasQuestionToken: !hasRequiredParameters, - }, - ], - }; - - // Mark the queryParameter interface for importing - internalReferences.add(queryParameterInterfaceName); - - return [...wrapperTypesDefinition, propertiesInterface, parameterInterface]; -} - -function getPropertyFromSchema(schema: Schema): PropertySignatureStructure { - const description = schema.description; - return { - name: schema.name, - ...(description && { docs: [{ description }] }), - type: schema.type, - hasQuestionToken: !schema.required, - kind: StructureKind.PropertySignature, - }; -} - -function buildPathParameterDefinitions( - model: RLCModel, - parameters: ParameterMetadatas, - baseName: string, - parametersFile: SourceFile, - internalReferences: Set, - requestIndex: number, -): InterfaceDeclarationStructure[] | undefined { - const pathParameters = (parameters.parameters || []).filter((p) => p.type === "path"); - if (!pathParameters.length) { - return undefined; - } - const allDefinitions: InterfaceDeclarationStructure[] = []; - - buildClientPathParameters(); - buildMethodWrapParameters(); - return allDefinitions; - function buildClientPathParameters() { - // we only have client-level path parameters if the source is from swagger - if (model.options?.sourceFrom === "TypeSpec") { - return; - } - const clientPathParams = pathParameters.length > 0 ? pathParameters : []; - const nameSuffix = requestIndex > 0 ? `${requestIndex}` : ""; - const pathParameterInterfaceName = `${baseName}PathParam${nameSuffix}`; - - const pathInterface = getPathInterfaceDefinition(clientPathParams, baseName); - - if (pathInterface) { - parametersFile.addInterface(pathInterface); - } - - internalReferences.add(pathParameterInterfaceName); - - allDefinitions.push({ - isExported: true, - kind: StructureKind.Interface, - name: pathParameterInterfaceName, - properties: [ - { - name: "pathParameters", - type: `${baseName}PathParameters`, - kind: StructureKind.PropertySignature, - }, - ], - }); - } - - function buildMethodWrapParameters() { - if (model.options?.sourceFrom === "Swagger") { - return; - } - // we only have method-level path parameters if the source is from typespec - const methodPathParams = pathParameters.length > 0 ? pathParameters : []; - - // we only need to build the wrapper types if the path parameters are objects - const wrapperTypesDefinition = getGeneratedWrapperTypes(methodPathParams).map((wrap) => { - return getObjectInterfaceDeclaration( - model, - wrap.name, - wrap, - [SchemaContext.Input], - new Set(), - ); - }); - allDefinitions.push(...wrapperTypesDefinition); - } -} - -function getPathInterfaceDefinition( - pathParameters: ParameterMetadata[], - baseName: string, -): undefined | InterfaceDeclarationStructure { - const pathInterfaceName = `${baseName}PathParameters`; - return { - kind: StructureKind.Interface, - isExported: true, - name: pathInterfaceName, - properties: pathParameters.map((p: ParameterMetadata) => getPropertyFromSchema(p.param)), - }; -} - -function buildHeaderParameterDefinitions( - parameters: ParameterMetadatas, - baseName: string, - parametersFile: SourceFile, - internalReferences: Set, - requestIndex: number, -): InterfaceDeclarationStructure | undefined { - const headerParameters = (parameters.parameters || []).filter( - (p) => p.type === "header" && p.name !== "contentType", - ); - if (!headerParameters.length) { - return undefined; - } - - const nameSuffix = requestIndex > 0 ? `${requestIndex}` : ""; - const headerParameterInterfaceName = `${baseName}HeaderParam${nameSuffix}`; - - const headersInterface = getRequestHeaderInterfaceDefinition(headerParameters, baseName); - - let isOptional = true; - if (headersInterface) { - parametersFile.addInterface(headersInterface); - isOptional = !(headersInterface.properties || []).some( - (prop) => prop.hasQuestionToken === false, - ); - } - - internalReferences.add(headerParameterInterfaceName); - - return { - isExported: true, - kind: StructureKind.Interface, - name: headerParameterInterfaceName, - properties: [ - { - name: "headers", - type: `RawHttpHeadersInput & ${baseName}Headers`, - kind: StructureKind.PropertySignature, - hasQuestionToken: isOptional, - }, - ], - }; -} - -function getRequestHeaderInterfaceDefinition( - headerParameters: ParameterMetadata[], - baseName: string, -): undefined | InterfaceDeclarationStructure { - const headersInterfaceName = `${baseName}Headers`; - return { - kind: StructureKind.Interface, - isExported: true, - name: headersInterfaceName, - properties: headerParameters.map((h: ParameterMetadata) => getPropertyFromSchema(h.param)), - }; -} - -function buildContentTypeParametersDefinition( - parameters: ParameterMetadatas, - baseName: string, - internalReferences: Set, - requestIndex: number, -): InterfaceDeclarationStructure | undefined { - const mediaTypeParameters = (parameters.parameters || []).filter( - (p) => p.type === "header" && p.name === "contentType", - ); - if (!mediaTypeParameters.length) { - return undefined; - } - - const nameSuffix = requestIndex > 0 ? `${requestIndex}` : ""; - const mediaTypesParameterInterfaceName = `${baseName}MediaTypesParam${nameSuffix}`; - - // Mark the queryParameter interface for importing - internalReferences.add(mediaTypesParameterInterfaceName); - const firstMediaType = mediaTypeParameters[0]; - if (!firstMediaType) { - return undefined; - } - const mediaParam = firstMediaType.param; - - return { - isExported: true, - kind: StructureKind.Interface, - name: mediaTypesParameterInterfaceName, - properties: [getPropertyFromSchema(mediaParam)], - }; -} - -function buildBodyParametersDefinition( - parameters: ParameterMetadatas, - baseName: string, - internalReferences: Set, - requestIndex: number, -): InterfaceDeclarationStructure[] { - const bodyParameters = parameters.body; - if (!bodyParameters || !bodyParameters?.body || !bodyParameters?.body.length) { - return []; - } - - const nameSuffix = requestIndex > 0 ? `${requestIndex}` : ""; - const bodyParameterInterfaceName = `${baseName}BodyParam${nameSuffix}`; - internalReferences.add(bodyParameterInterfaceName); - - // In case of formData we'd get multiple properties in body marked as partialBody - if (bodyParameters.isPartialBody) { - let allOptionalParts = true; - const propertiesDefinitions: PropertySignatureStructure[] = []; - for (const param of bodyParameters.body) { - if (param.required) { - allOptionalParts = false; - } - - propertiesDefinitions.push(getPropertyFromSchema(param)); - } - - const formBodyName = `${baseName}FormBody`; - const formBodyInterface: InterfaceDeclarationStructure = { - isExported: true, - kind: StructureKind.Interface, - name: formBodyName, - properties: propertiesDefinitions, - }; - - return [ - { - isExported: true, - kind: StructureKind.Interface, - name: bodyParameterInterfaceName, - properties: [ - { - name: "body", - type: formBodyName, - hasQuestionToken: allOptionalParts, - }, - ], - }, - formBodyInterface, - ]; - } else { - const firstBody = bodyParameters.body[0]; - if (!firstBody) { - return []; - } - const bodySignature = getPropertyFromSchema(firstBody); - - return [ - { - isExported: true, - kind: StructureKind.Interface, - name: bodyParameterInterfaceName, - properties: [ - { - docs: bodySignature.docs, - name: "body", - type: bodySignature.type, - hasQuestionToken: bodySignature.hasQuestionToken, - }, - ], - }, - ]; - } -} - -export function buildBodyTypeAlias( - parameters: ParameterMetadatas, - partialBodyTypeNames: Set, -) { - const bodyParameters = parameters.body; - if (!bodyParameters || !bodyParameters?.body || !bodyParameters?.body.length) { - return undefined; - } - const schema = bodyParameters.body[0] as ObjectSchema; - const headerParameters = (parameters.parameters || []).filter( - (p) => p.type === "header" && p.name === "contentType", - ); - if (!headerParameters.length || headerParameters.length > 1) { - return undefined; - } - - const firstHeader = headerParameters[0]; - if (!firstHeader) { - return undefined; - } - const contentType = firstHeader.param.type; - const description = `${schema.description}`; - const typeName = `${schema.typeName}ResourceMergeAndPatch`; - if (partialBodyTypeNames.has(typeName)) { - return null; - } else { - partialBodyTypeNames.add(typeName); - } - if (contentType.includes("application/merge-patch+json")) { - const type = `Partial<${schema.typeName}>`; - return { - // kind: StructureKind.TypeAlias, - ...(description && { docs: [{ description }] }), - name: `${typeName}`, - type, - isExported: true, - }; - } - return undefined; -} diff --git a/packages/typespec-ts/src/rlc-common/build-schema-type.ts b/packages/typespec-ts/src/rlc-common/build-schema-type.ts deleted file mode 100644 index 414c220363..0000000000 --- a/packages/typespec-ts/src/rlc-common/build-schema-type.ts +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -import * as path from "path"; -import { Project } from "ts-morph"; -import { - buildObjectAliases, - buildObjectInterfaces, - buildPolymorphicAliases, -} from "./build-object-types.js"; -import { getImportSpecifier } from "./helpers/imports-util.js"; -import { getImportModuleName } from "./helpers/name-constructors.js"; -import { RLCModel, SchemaContext } from "./interfaces.js"; - -/** - * Generates types to represent schema definitions in the swagger - */ -export function buildSchemaTypes(model: RLCModel) { - const { srcPath } = model; - const project = new Project(); - let filePath = path.join(srcPath, `models.ts`); - const inputModelFile = generateModelFiles(model, project, filePath, [SchemaContext.Input]); - filePath = path.join(srcPath, `outputModels.ts`); - const outputModelFile = generateModelFiles(model, project, filePath, [ - SchemaContext.Output, - SchemaContext.Exception, - ]); - return { inputModelFile, outputModelFile }; -} - -export function generateModelFiles( - model: RLCModel, - project: Project, - filePath: string, - schemaContext: SchemaContext[], -) { - // Track models that need to be imported - const importedModels = new Set(); - const objectsDefinitions = buildObjectInterfaces(model, importedModels, schemaContext); - - const objectTypeAliases = buildPolymorphicAliases(model, schemaContext); - - const objectAliases = buildObjectAliases(model, importedModels, schemaContext); - - if (objectTypeAliases.length || objectsDefinitions.length || objectAliases.length) { - const modelsFile = project.createSourceFile(filePath, undefined, { - overwrite: true, - }); - - modelsFile.addInterfaces(objectsDefinitions); - modelsFile.addTypeAliases(objectTypeAliases); - modelsFile.addTypeAliases(objectAliases); - if (importedModels.size > 0) { - modelsFile.addImportDeclarations([ - { - isTypeOnly: true, - namedImports: [...Array.from(importedModels || [])], - moduleSpecifier: getImportSpecifier("restClient", model.importInfo.runtimeImports), - }, - ]); - } - // Add NodeReadableStream import if binary types are used in models. - // platform-types.ts is generated directly under src/ (no static-helpers/ - // subdirectory) to match the RLC design where all output lives in src/. - // The platform-types static helper resolves NodeReadableStream to - // NodeJS.ReadableStream on Node and `never` on browser/react-native, so the - // union arm drops out naturally in non-Node builds. - if (modelsFile.getFullText().includes("NodeReadableStream")) { - const platformTypesModuleSpecifier = model.options?.azureSdkForJs - ? "@azure/core-rest-pipeline" - : getImportModuleName( - { - cjsName: `./platform-types`, - esModulesName: `./platform-types.js`, - }, - model, - ); - modelsFile.addImportDeclarations([ - { - isTypeOnly: true, - namedImports: ["NodeReadableStream"], - moduleSpecifier: platformTypesModuleSpecifier, - }, - ]); - } - return { path: filePath, content: modelsFile.getFullText() }; - } - return undefined; -} From a0e2ef329621eb1bddab3176366df79eb9bf0308 Mon Sep 17 00:00:00 2001 From: ZiWei Chen Date: Fri, 26 Jun 2026 11:35:36 +0800 Subject: [PATCH 5/8] update --- packages/typespec-ts/src/framework/hooks/binder.ts | 2 -- packages/typespec-ts/src/index.ts | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/typespec-ts/src/framework/hooks/binder.ts b/packages/typespec-ts/src/framework/hooks/binder.ts index db3385c190..15efdb027f 100644 --- a/packages/typespec-ts/src/framework/hooks/binder.ts +++ b/packages/typespec-ts/src/framework/hooks/binder.ts @@ -53,7 +53,6 @@ class BinderImp implements Binder { private project: Project; private dependencies: Record; private staticHelpers: Map; - private useSubpathImports: boolean; constructor(project: Project, options: BinderOptions = {}) { this.project = project; @@ -61,7 +60,6 @@ class BinderImp implements Binder { provideDependencies(options.dependencies); this.staticHelpers = options.staticHelpers ?? new Map(); this.dependencies = useDependencies(); - this.useSubpathImports = options.useSubpathImports ?? false; } trackDeclaration(refkey: unknown, name: string, sourceFile: SourceFile): string { diff --git a/packages/typespec-ts/src/index.ts b/packages/typespec-ts/src/index.ts index afdaa70886..fce130bc47 100644 --- a/packages/typespec-ts/src/index.ts +++ b/packages/typespec-ts/src/index.ts @@ -171,8 +171,7 @@ export async function $onEmit(context: EmitContext) { staticHelpers, dependencies: { ...extraDependencies, - }, - useSubpathImports: true, + } }); provideSdkTypes(dpgContext); From 56bf49170528aefd29c9fc697402ea535b75904a Mon Sep 17 00:00:00 2001 From: ZiWei Chen Date: Fri, 26 Jun 2026 11:36:01 +0800 Subject: [PATCH 6/8] merge main --- core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core b/core index 1e863964fe..fb8afa8713 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 1e863964fec8dbfb443ff6a3789c2f221b662843 +Subproject commit fb8afa87138d07684ad9e86ef927f26196eb1a09 From aa89875b2916f492582fdbbaad1812a43d0d739f Mon Sep 17 00:00:00 2001 From: ZiWei Chen Date: Fri, 26 Jun 2026 16:40:36 +0800 Subject: [PATCH 7/8] Update load-static-helpers.ts --- .../typespec-ts/src/framework/load-static-helpers.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/packages/typespec-ts/src/framework/load-static-helpers.ts b/packages/typespec-ts/src/framework/load-static-helpers.ts index b4a91591f8..6efd3b87a8 100644 --- a/packages/typespec-ts/src/framework/load-static-helpers.ts +++ b/packages/typespec-ts/src/framework/load-static-helpers.ts @@ -95,15 +95,7 @@ export async function loadStaticHelpers( const addedFile = project.createSourceFile(targetPath, contents, { overwrite: true, }); - addedFile.getImportDeclarations().map((i) => { - - if (i.getModuleSpecifier().getFullText().includes("@azure/core-rest-pipeline")) { - i.setModuleSpecifier("@typespec/ts-http-runtime"); - } - if (i.getModuleSpecifier().getFullText().includes("@azure-rest/core-client")) { - i.setModuleSpecifier("@typespec/ts-http-runtime"); - } - + addedFile.getImportDeclarations().map((i) => { // Rewrite relative platform-types imports to @azure/core-rest-pipeline for azure packages // (NodeReadableStream is now exported directly from @azure/core-rest-pipeline). // Non-azure packages keep the relative import to the local platform-types.ts. From 9a1800ef1d3e21102b155625f93581da90a16313 Mon Sep 17 00:00:00 2001 From: ZiWei Chen Date: Mon, 29 Jun 2026 16:41:52 +0800 Subject: [PATCH 8/8] update --- .../typespec-ts/src/framework/hooks/binder.ts | 5 +--- .../src/framework/load-static-helpers.ts | 23 +++++++++---------- packages/typespec-ts/src/index.ts | 2 +- .../src/modular/build-root-index.ts | 10 +++----- .../src/modular/helpers/operation-helpers.ts | 7 +++--- .../src/modular/static-helpers-metadata.ts | 7 ------ .../rlc-common/metadata/build-package-file.ts | 8 +++---- pnpm-lock.yaml | 15 +++++++----- 8 files changed, 32 insertions(+), 45 deletions(-) diff --git a/packages/typespec-ts/src/framework/hooks/binder.ts b/packages/typespec-ts/src/framework/hooks/binder.ts index 15efdb027f..075f0544a5 100644 --- a/packages/typespec-ts/src/framework/hooks/binder.ts +++ b/packages/typespec-ts/src/framework/hooks/binder.ts @@ -9,10 +9,7 @@ import { import { provideContext, useContext } from "../../context-manager.js"; import { generateLocallyUniqueName } from "../../modular/helpers/naming-helpers.js"; import { ReferenceableSymbol } from "../dependency.js"; -import { - SourceFileSymbol, - StaticHelperMetadata, -} from "../load-static-helpers.js"; +import { SourceFileSymbol, StaticHelperMetadata } from "../load-static-helpers.js"; import { refkey } from "../refkey.js"; import { provideDependencies, useDependencies } from "./use-dependencies.js"; diff --git a/packages/typespec-ts/src/framework/load-static-helpers.ts b/packages/typespec-ts/src/framework/load-static-helpers.ts index 6efd3b87a8..2351f58959 100644 --- a/packages/typespec-ts/src/framework/load-static-helpers.ts +++ b/packages/typespec-ts/src/framework/load-static-helpers.ts @@ -95,21 +95,20 @@ export async function loadStaticHelpers( const addedFile = project.createSourceFile(targetPath, contents, { overwrite: true, }); - addedFile.getImportDeclarations().map((i) => { + addedFile.getImportDeclarations().map((i) => { // Rewrite relative platform-types imports to @azure/core-rest-pipeline for azure packages // (NodeReadableStream is now exported directly from @azure/core-rest-pipeline). // Non-azure packages keep the relative import to the local platform-types.ts. - - const specifier = i.getModuleSpecifierValue(); - if ( - specifier.startsWith(".") && - specifier.includes("platform-types") && - !specifier.includes("-browser") && - !specifier.includes("-react-native") - ) { - i.setModuleSpecifier("@azure/core-rest-pipeline"); - } - + + const specifier = i.getModuleSpecifierValue(); + if ( + specifier.startsWith(".") && + specifier.includes("platform-types") && + !specifier.includes("-browser") && + !specifier.includes("-react-native") + ) { + i.setModuleSpecifier("@azure/core-rest-pipeline"); + } }); for (const entry of Object.values(helpers)) { if (!addedFile.getFilePath().endsWith(entry.location)) { diff --git a/packages/typespec-ts/src/index.ts b/packages/typespec-ts/src/index.ts index fce130bc47..af4c494f5c 100644 --- a/packages/typespec-ts/src/index.ts +++ b/packages/typespec-ts/src/index.ts @@ -171,7 +171,7 @@ export async function $onEmit(context: EmitContext) { staticHelpers, dependencies: { ...extraDependencies, - } + }, }); provideSdkTypes(dpgContext); diff --git a/packages/typespec-ts/src/modular/build-root-index.ts b/packages/typespec-ts/src/modular/build-root-index.ts index 07fcb5482e..5942171d01 100644 --- a/packages/typespec-ts/src/modular/build-root-index.ts +++ b/packages/typespec-ts/src/modular/build-root-index.ts @@ -9,15 +9,11 @@ import { getModularClientOptions } from "../utils/client-utils.js"; import { SdkContext } from "../utils/interfaces.js"; import { getMethodHierarchiesMap } from "../utils/operation-util.js"; import { partitionAndEmitExports } from "./build-subpath-index.js"; +import { AzureCoreDependencies } from "./external-dependencies.js"; import { getClassicalClientName } from "./helpers/naming-helpers.js"; import { isLroOnlyOperation } from "./helpers/operation-helpers.js"; import { ModularEmitterOptions } from "./interfaces.js"; -import { - CloudSettingHelpers, - MultipartHelpers, - PagingHelpers, - PlatformTypeHelpers, -} from "./static-helpers-metadata.js"; +import { CloudSettingHelpers, MultipartHelpers, PagingHelpers } from "./static-helpers-metadata.js"; export function buildRootIndex( context: SdkContext, @@ -157,7 +153,7 @@ function exportFileContentsType(context: SdkContext, rootIndexFile: SourceFile) rootIndexFile, [ resolveReference(MultipartHelpers.FileContents), - resolveReference(PlatformTypeHelpers.NodeReadableStream), + resolveReference(AzureCoreDependencies["NodeReadableStream"]), ], true, ); diff --git a/packages/typespec-ts/src/modular/helpers/operation-helpers.ts b/packages/typespec-ts/src/modular/helpers/operation-helpers.ts index 0f474c2f65..15c17d8d77 100644 --- a/packages/typespec-ts/src/modular/helpers/operation-helpers.ts +++ b/packages/typespec-ts/src/modular/helpers/operation-helpers.ts @@ -48,7 +48,7 @@ import { KnownCollectionFormat, ServiceOperation, } from "../../utils/operation-util.js"; -import { AzurePollingDependencies } from "../external-dependencies.js"; +import { AzureCoreDependencies, AzurePollingDependencies } from "../external-dependencies.js"; import { buildModelDeserializer, buildPropertyDeserializer, @@ -70,7 +70,6 @@ import { } from "../serialization/serialize-utils.js"; import { PagingHelpers, - PlatformTypeHelpers, PollingHelpers, SerializationHelpers, StorageCompatHelpers, @@ -1043,7 +1042,7 @@ export function getOperationFunction( statements.push(`const ${streamableMethodVarName} = _${name}Send(${sendParameterList});`); const binaryHelper = wrapReturn && wrapReturnIsBinary - ? SerializationHelpers.getBinaryStreamResponse + ? AzureCoreDependencies["getBinaryStreamResponse"] : SerializationHelpers.getBinaryResponse; statements.push( `const ${resultVarName} = await ${resolveReference(binaryHelper)}(${streamableMethodVarName});`, @@ -3003,7 +3002,7 @@ export function buildNonModelResponseTypeDeclaration( let typeBody: string; if (isBinary) { - const nodeReadableStreamRef = resolveReference(PlatformTypeHelpers.NodeReadableStream); + const nodeReadableStreamRef = resolveReference(AzureCoreDependencies["NodeReadableStream"]); typeBody = `{ /** * BROWSER ONLY diff --git a/packages/typespec-ts/src/modular/static-helpers-metadata.ts b/packages/typespec-ts/src/modular/static-helpers-metadata.ts index 3226ee41dc..1736683424 100644 --- a/packages/typespec-ts/src/modular/static-helpers-metadata.ts +++ b/packages/typespec-ts/src/modular/static-helpers-metadata.ts @@ -1,5 +1,3 @@ -import { AzureCoreDependencies } from "./external-dependencies.js"; - export const SerializationHelpers = { buildMultiCollection: { kind: "function", @@ -61,7 +59,6 @@ export const SerializationHelpers = { name: "getBinaryResponse", location: "serialization/get-binary-response.ts", }, - getBinaryStreamResponse: AzureCoreDependencies["getBinaryStreamResponse"], areAllPropsUndefined: { kind: "function", name: "areAllPropsUndefined", @@ -148,10 +145,6 @@ export const MultipartHelpers = { }, } as const; -export const PlatformTypeHelpers = { - NodeReadableStream: AzureCoreDependencies["NodeReadableStream"], -} as const; - export const CloudSettingHelpers = { AzureClouds: { kind: "enum", diff --git a/packages/typespec-ts/src/rlc-common/metadata/build-package-file.ts b/packages/typespec-ts/src/rlc-common/metadata/build-package-file.ts index 740892f3af..61e38786d8 100644 --- a/packages/typespec-ts/src/rlc-common/metadata/build-package-file.ts +++ b/packages/typespec-ts/src/rlc-common/metadata/build-package-file.ts @@ -144,10 +144,10 @@ export function updatePackageFile( } packageInfo.dependencies = { - ...packageInfo.dependencies, - "@azure/core-rest-pipeline": "^1.24.0", - "@azure-rest/core-client": "^2.7.0", - }; + ...packageInfo.dependencies, + "@azure/core-rest-pipeline": "^1.24.0", + "@azure-rest/core-client": "^2.7.0", + }; // Update constantPaths metadata for Azure packages if (needsConstantPathsUpdate && packageInfo["//metadata"]) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0360345574..ca73224c56 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1054,6 +1054,9 @@ importers: '@alloy-js/typescript': specifier: 'catalog:' version: 0.24.0 + '@types/node': + specifier: 'catalog:' + version: 26.0.0 '@typespec/compiler': specifier: workspace:^ version: link:../compiler @@ -2934,8 +2937,8 @@ importers: specifier: 'catalog:' version: 7.7.1 '@types/vscode': - specifier: ~1.120.0 - version: 1.120.0 + specifier: ~1.125.0 + version: 1.125.0 '@types/which': specifier: 'catalog:' version: 3.0.4 @@ -8279,8 +8282,8 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - '@types/vscode@1.120.0': - resolution: {integrity: sha512-feaT4Rst+FkTch5zz/ZbNCxoIvo55YU80Be2kiL7OJcod4+CUYf2lUBPdIJzozNnSEMq1VRTGrWEcCGFB3fBmA==} + '@types/vscode@1.125.0': + resolution: {integrity: sha512-0icm/ZQAaism87P0ekHqi4/Ju9du+Tm0RUW+y7vqRsxY2cY0FNRX1nAnaW7nT6npPt2tfHiheZ55Zm9UhqonFA==} '@types/whatwg-mimetype@3.0.2': resolution: {integrity: sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==} @@ -20610,7 +20613,7 @@ snapshots: '@types/sax@1.2.7': dependencies: - '@types/node': 24.13.2 + '@types/node': 26.0.0 '@types/semver@7.7.1': {} @@ -20645,7 +20648,7 @@ snapshots: '@types/unist@3.0.3': {} - '@types/vscode@1.120.0': {} + '@types/vscode@1.125.0': {} '@types/whatwg-mimetype@3.0.2': {}