Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .chronus/changes/autorest-multifile-2025-11-17-18-2-54.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: fix
packages:
- "@azure-tools/typespec-autorest-canonical"
---

Adapt to shared type changes in typespec-autorest
8 changes: 8 additions & 0 deletions .chronus/changes/autorest-multifile-2025-11-5-14-44-29.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
changeKind: feature
packages:
- "@azure-tools/typespec-autorest"
- "@azure-tools/typespec-azure-resource-manager"
---

Add support for multiple output files in typespec-autorest
4 changes: 3 additions & 1 deletion packages/typespec-autorest-canonical/src/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,10 @@ async function emitAllServices(
service,
version: canonicalVersion,
tcgcSdkContext,
multiService: services.length > 1,
};
const result = await getOpenAPIForService(context, options);
const results = await getOpenAPIForService(context, options);
const result = results[0];
const includedVersions = getVersion(program, service.type)
?.getVersions()
?.map((item) => item.value ?? item.name);
Expand Down
6 changes: 6 additions & 0 deletions packages/typespec-autorest/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,12 @@ Determine whether and how to emit schemas for common-types rather than referenci

Strategy for applying XML serialization metadata to schemas.

### `output-splitting`

**Type:** `"legacy-feature-files"`

Determines whether output should be split into multiple files. The only supported option for splitting is "legacy-feature-files", which uses the typespec-azure-resource-manager `@feature` decorators to split into output files based on feature.

## Decorators

### Autorest
Expand Down
151 changes: 109 additions & 42 deletions packages/typespec-autorest/src/emit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { getVersioningMutators } from "@typespec/versioning";
import { AutorestEmitterOptions, getTracer, reportDiagnostic } from "./lib.js";
import {
AutorestDocumentEmitterOptions,
createDocumentProxy,
getOpenAPIForService,
sortOpenAPIDocument,
} from "./openapi.js";
Expand Down Expand Up @@ -113,16 +114,34 @@ export function resolveAutorestOptions(
emitLroOptions: resolvedOptions["emit-lro-options"],
emitCommonTypesSchema: resolvedOptions["emit-common-types-schema"],
xmlStrategy: resolvedOptions["xml-strategy"],
outputSplitting: resolvedOptions["output-splitting"],
};
}

export async function getAllServicesAtAllVersions(
function getEmitterContext(
program: Program,
service: Service,
options: ResolvedAutorestEmitterOptions,
): Promise<AutorestServiceRecord[]> {
multiService: boolean = false,
version?: string,
): AutorestEmitterContext {
const tcgcSdkContext = createTCGCContext(program, "@azure-tools/typespec-autorest");
tcgcSdkContext.enableLegacyHierarchyBuilding = true;
return {
program,
outputFile: resolveOutputFile(program, service, multiService, options, version),
service: service,
tcgcSdkContext,
proxy: createDocumentProxy(program, service, options, version),
version: version,
multiService: multiService,
};
}

export async function getAllServicesAtAllVersions(
program: Program,
options: ResolvedAutorestEmitterOptions,
): Promise<AutorestServiceRecord[]> {
const services = listServices(program);
if (services.length === 0) {
services.push({ type: program.getGlobalNamespaceType() });
Expand All @@ -133,33 +152,60 @@ export async function getAllServicesAtAllVersions(
const versions = getVersioningMutators(program, service.type);

if (versions === undefined) {
const context: AutorestEmitterContext = {
const context: AutorestEmitterContext = getEmitterContext(
program,
outputFile: resolveOutputFile(program, service, services.length > 1, options),
service: service,
tcgcSdkContext,
};

const result = await getOpenAPIForService(context, options);
serviceRecords.push({
service,
versioned: false,
...result,
});
options,
services.length > 1,
);

const results = await getOpenAPIForService(context, options);
for (const result of results) {
const newResult = { ...result };
newResult.outputFile = resolveOutputFile(
program,
service,
services.length > 1,
options,
undefined,
result.feature,
);
serviceRecords.push({
service,
versioned: false,
...newResult,
});
}
} else if (versions.kind === "transient") {
const context: AutorestEmitterContext = {
const context: AutorestEmitterContext = getEmitterContext(
program,
outputFile: resolveOutputFile(program, service, services.length > 1, options),
service: service,
tcgcSdkContext,
};

const result = await getVersionSnapshotDocument(context, versions.mutator, options);
serviceRecords.push({
service,
versioned: false,
...result,
});
options,
services.length > 1,
);

const results = await getVersionSnapshotDocument(
context,
versions.mutator,
options,
services.length > 1,
);
for (const result of results) {
const newResult = { ...result };
newResult.outputFile = resolveOutputFile(
program,
service,
services.length > 1,
options,
undefined,
result.feature,
);
serviceRecords.push({
service,
versioned: false,
...newResult,
});
}
} else {
const filteredVersions = versions.snapshots.filter(
(v) => !options.version || options.version === v.version?.value,
Expand All @@ -176,26 +222,36 @@ export async function getAllServicesAtAllVersions(
serviceRecords.push(serviceRecord);

for (const record of filteredVersions) {
const context: AutorestEmitterContext = {
const context: AutorestEmitterContext = getEmitterContext(
program,
outputFile: resolveOutputFile(
service,
options,
services.length > 1,
record.version?.value,
);

const results = await getVersionSnapshotDocument(
context,
record.mutator,
options,
services.length > 1,
);
for (const result of results) {
const newResult = { ...result };
newResult.outputFile = resolveOutputFile(
program,
service,
services.length > 1,
options,
record.version?.value,
),
service,
version: record.version?.value,
tcgcSdkContext,
};

const result = await getVersionSnapshotDocument(context, record.mutator, options);
serviceRecord.versions.push({
...result,
service,
version: record.version!.value,
});
record.version!.value,
result.feature,
);
serviceRecord.versions.push({
...newResult,
service,
version: record.version!.value,
});
}
}
}
}
Expand All @@ -207,6 +263,7 @@ async function getVersionSnapshotDocument(
context: AutorestEmitterContext,
mutator: unsafe_MutatorWithNamespace,
options: ResolvedAutorestEmitterOptions,
multiService: boolean = false,
) {
const subgraph = unsafe_mutateSubgraphWithNamespace(
context.program,
Expand All @@ -215,10 +272,15 @@ async function getVersionSnapshotDocument(
);

compilerAssert(subgraph.type.kind === "Namespace", "Should not have mutated to another type");
const document = await getOpenAPIForService(
{ ...context, service: getService(context.program, subgraph.type)! },
const service = getService(context.program, subgraph.type)!;
const newContext: AutorestEmitterContext = getEmitterContext(
context.program,
service,
options,
multiService,
context.version,
);
const document = await getOpenAPIForService(newContext, options);

return document;
}
Expand Down Expand Up @@ -247,6 +309,9 @@ async function emitOutput(
result: AutorestEmitterResult,
options: ResolvedAutorestEmitterOptions,
) {
const currentFeature = result.feature;
if (currentFeature !== undefined && result.context.proxy !== undefined)
result.context.proxy.setCurrentFeature(currentFeature);
const sortedDocument = sortOpenAPIDocument(result.document);

// Write out the OpenAPI document to the output path
Expand Down Expand Up @@ -277,12 +342,13 @@ function prettierOutput(output: string) {
return output + "\n";
}

function resolveOutputFile(
export function resolveOutputFile(
program: Program,
service: Service,
multipleServices: boolean,
options: ResolvedAutorestEmitterOptions,
version?: string,
feature?: string,
): string {
const azureResourceProviderFolder = options.azureResourceProviderFolder;
if (azureResourceProviderFolder) {
Expand All @@ -301,6 +367,7 @@ function resolveOutputFile(
: "stable"
: undefined,
version,
feature,
});

return resolvePath(options.outputDir, interpolated);
Expand Down
13 changes: 13 additions & 0 deletions packages/typespec-autorest/src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,12 @@ export interface AutorestEmitterOptions {
* @default "xml-service"
*/
"xml-strategy"?: "xml-service" | "none";

/**
* Determines whether output should be split into multiple files. The only supported option for splitting is "legacy-feature-files",
* which uses the typespec-azure-resource-manager `@feature` decorators to split into output files based on feature.
*/
"output-splitting"?: "legacy-feature-files";
}

const EmitterOptionsSchema: JSONSchemaType<AutorestEmitterOptions> = {
Expand Down Expand Up @@ -238,6 +244,13 @@ const EmitterOptionsSchema: JSONSchemaType<AutorestEmitterOptions> = {
default: "xml-service",
description: "Strategy for applying XML serialization metadata to schemas.",
},
"output-splitting": {
type: "string",
enum: ["legacy-feature-files"],
nullable: true,
description:
'Determines whether output should be split into multiple files. The only supported option for splitting is "legacy-feature-files", which uses the typespec-azure-resource-manager `@feature` decorators to split into output files based on feature.',
},
},
required: [],
};
Expand Down
Loading
Loading