Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
3f730a3
feat(typespec-autorest): add skip-example-copying emitter option
Copilot Apr 3, 2026
9e3df9b
add changeset and versioned spec test for skip-example-copying
Copilot Apr 3, 2026
5e0f100
Merge branch 'main' into copilot/add-emitter-option-skip-example-copying
markcowl Apr 3, 2026
b520b3f
fix: apply formatting to test file
Copilot Apr 3, 2026
48251bb
Merge branch 'main' into copilot/add-emitter-option-skip-example-copying
markcowl Apr 14, 2026
62c9e93
update init feeds and x-ms-examples docs to use skip-example-copying
Copilot Apr 14, 2026
7fc6ab4
fix: use consistent examples-dir naming in doc
Copilot Apr 14, 2026
d32fb08
add examples-dir setting to tspconfig.yaml files in feeds and rebuild…
Copilot Apr 14, 2026
3893783
Merge remote-tracking branch 'origin/main' into copilot/add-emitter-o…
Copilot Apr 20, 2026
b8664fb
fix: remove skip-example-copying from standalone tspconfig files (not…
Copilot Apr 20, 2026
e81b280
Merge remote-tracking branch 'origin/main' into copilot/add-emitter-o…
Copilot May 11, 2026
7e3f198
feat: support {version-status} and {version} interpolation in example…
Copilot May 11, 2026
ff2350d
test: add interpolated examples-dir versioned tests and sub-directory…
Copilot May 12, 2026
826c7a9
Merge remote-tracking branch 'origin/main' into copilot/add-emitter-o…
Copilot May 29, 2026
e706716
test: enhance sub-directory examples test with multiple resource sub-…
Copilot May 29, 2026
2efa88d
Merge remote-tracking branch 'origin/main' into copilot/add-emitter-o…
Copilot May 29, 2026
b16c231
revert: remove unnecessary package.json version pinning in standalone…
Copilot May 29, 2026
37519c9
Merge remote-tracking branch 'origin/main' into copilot/add-emitter-o…
Copilot May 30, 2026
8c84164
docs: apply suggested x-ms-examples doc changes (skip-example-copying…
Copilot May 30, 2026
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
Comment thread
markcowl marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: feature
packages:
- "@azure-tools/typespec-autorest"
---

Added `skip-example-copying` emitter option. When enabled, example files are not copied to the output directory and `x-ms-examples` `$ref` values point directly to the source example files via relative paths.
2 changes: 2 additions & 0 deletions eng/feeds/__snapshots__/azure-arm/tspconfig.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ options:
emitter-output-dir: "{project-root}"
output-file: "{emitter-output-dir}/{version-status}/{version}/widget.json"
arm-types-dir: "{project-root}/../../../../common-types/resource-management"
examples-dir: "{emitter-output-dir}/{version-status}/{version}/examples"
skip-example-copying: true
"@azure-tools/typespec-csharp":
emitter-output-dir: "{output-dir}/{service-dir}/Azure.ResourceManager.Contoso"
clear-output-folder: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ options:
"@azure-tools/typespec-autorest":
use-read-only-status-schema: true
output-file: "resource-manager/{service-name}/{version-status}/{version}/openapi.json"
examples-dir: "{project-root}/examples"
linter:
extends:
- "@azure-tools/typespec-azure-rulesets/resource-manager"
6 changes: 4 additions & 2 deletions eng/feeds/__snapshots__/azure-core/tspconfig.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ emit:
- "@azure-tools/typespec-autorest"
options:
"@azure-tools/typespec-autorest":
emitter-output-dir: "{project-root}/.."
output-file: "data-plane/{service-name}/{version-status}/{version}/openapi.json"
emitter-output-dir: "{project-root}"
output-file: "{emitter-output-dir}/{version-status}/{version}/openapi.json"
examples-dir: "{emitter-output-dir}/{version-status}/{version}/examples"
skip-example-copying: true
"@azure-tools/typespec-python":
emitter-output-dir: "{output-dir}/{service-dir}/azure-contoso"
namespace: "azure.contoso"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ emit:
options:
"@azure-tools/typespec-autorest":
output-file: "data-plane/{service-name}/{version-status}/{version}/openapi.json"
examples-dir: "{project-root}/examples"
Comment thread
markcowl marked this conversation as resolved.
"@azure-tools/typespec-python":
emitter-output-dir: "{output-dir}/{service-dir}/azure-contoso"
namespace: azure.contoso
Expand Down
2 changes: 2 additions & 0 deletions eng/feeds/arm-canonical/tspconfig.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ options:
emitter-output-dir: "{project-root}"
output-file: "{emitter-output-dir}/{version-status}/{version}/widget.json"
arm-types-dir: "{project-root}/../../../../common-types/resource-management"
examples-dir: "{emitter-output-dir}/{version-status}/{version}/examples"
skip-example-copying: true
"@azure-tools/typespec-autorest-canonical":
emitter-output-dir: "{project-root}/.."
output-file: "{emitter-output-dir}/canonical/openapi.json"
Expand Down
2 changes: 2 additions & 0 deletions eng/feeds/arm/tspconfig.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ options:
emitter-output-dir: "{project-root}"
output-file: "{emitter-output-dir}/{version-status}/{version}/widget.json"
arm-types-dir: "{project-root}/../../../../common-types/resource-management"
examples-dir: "{emitter-output-dir}/{version-status}/{version}/examples"
skip-example-copying: true
"@azure-tools/typespec-csharp":
emitter-output-dir: "{output-dir}/{service-dir}/Azure.ResourceManager.{{parameters.ServiceNamespace}}"
clear-output-folder: true
Expand Down
1 change: 1 addition & 0 deletions eng/feeds/arm/tspconfig_stand_alone.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ options:
"@azure-tools/typespec-autorest":
use-read-only-status-schema: true
output-file: "resource-manager/{service-name}/{version-status}/{version}/openapi.json"
examples-dir: "{project-root}/examples"
linter:
extends:
- "@azure-tools/typespec-azure-rulesets/resource-manager"
2 changes: 2 additions & 0 deletions eng/feeds/data-plane/tspconfig.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ options:
"@azure-tools/typespec-autorest":
emitter-output-dir: "{project-root}"
output-file: "{emitter-output-dir}/{version-status}/{version}/openapi.json"
examples-dir: "{emitter-output-dir}/{version-status}/{version}/examples"
skip-example-copying: true
"@azure-tools/typespec-python":
emitter-output-dir: "{output-dir}/{service-dir}/{{#normalizePackageName}}{{parameters.ServiceNamespace}}{{/normalizePackageName}}"
namespace: "{{#toLowerCase}}{{parameters.ServiceNamespace}}{{/toLowerCase}}"
Expand Down
1 change: 1 addition & 0 deletions eng/feeds/data-plane/tspconfig_stand_alone.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ emit:
options:
"@azure-tools/typespec-autorest":
output-file: "data-plane/{service-name}/{version-status}/{version}/openapi.json"
examples-dir: "{project-root}/examples"
"@azure-tools/typespec-python":
emitter-output-dir: "{output-dir}/{service-dir}/{{#normalizePackageName}}{{parameters.ServiceNamespace}}{{/normalizePackageName}}"
namespace: {{#toLowerCase}}{{parameters.ServiceNamespace}}{{/toLowerCase}}
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 @@ -170,6 +170,12 @@ Strategy for applying XML serialization metadata to schemas.

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.

### `skip-example-copying`

**Type:** `boolean`

When enabled, the emitter will not copy example files to the output directory. Instead, it will reference the source example files using relative file paths.

## Decorators

### Autorest
Expand Down
3 changes: 2 additions & 1 deletion packages/typespec-autorest/src/emit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ export function resolveAutorestOptions(
emitCommonTypesSchema: resolvedOptions["emit-common-types-schema"],
xmlStrategy: resolvedOptions["xml-strategy"],
outputSplitting: resolvedOptions["output-splitting"],
skipExampleCopying: resolvedOptions["skip-example-copying"],
};
}

Expand Down Expand Up @@ -322,7 +323,7 @@ async function emitOutput(
});

// Copy examples to the output directory
if (result.operationExamples.length > 0) {
if (result.operationExamples.length > 0 && !options.skipExampleCopying) {
const examplesPath = resolvePath(getDirectoryPath(result.outputFile), "examples");
await program.host.mkdirp(examplesPath);
for (const { examples } of result.operationExamples) {
Expand Down
14 changes: 14 additions & 0 deletions packages/typespec-autorest/src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,13 @@ export interface AutorestEmitterOptions {
* which uses the typespec-azure-resource-manager `@feature` decorators to split into output files based on feature.
*/
"output-splitting"?: "legacy-feature-files";

/**
* When enabled, the emitter will not copy example files to the output directory.
* Instead, it will reference the source example files using relative file paths.
* @default false
*/
"skip-example-copying"?: boolean;
}

const EmitterOptionsSchema: JSONSchemaType<AutorestEmitterOptions> = {
Expand Down Expand Up @@ -254,6 +261,13 @@ const EmitterOptionsSchema: JSONSchemaType<AutorestEmitterOptions> = {
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.',
},
"skip-example-copying": {
type: "boolean",
nullable: true,
default: false,
description:
"When enabled, the emitter will not copy example files to the output directory. Instead, it will reference the source example files using relative file paths.",
},
},
required: [],
};
Expand Down
55 changes: 52 additions & 3 deletions packages/typespec-autorest/src/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,13 @@ export interface AutorestDocumentEmitterOptions {
* Determines whether output should be split into multiple files. The only supported option for splitting is "legacy-feature-files",
*/
readonly outputSplitting?: "legacy-feature-files";

/**
* When enabled, example files will not be copied to the output directory.
* Instead, the source example files will be referenced using relative file paths.
* @default false
*/
readonly skipExampleCopying?: boolean;
}

type HttpParameterProperties = Extract<
Expand Down Expand Up @@ -317,6 +324,13 @@ export async function getOpenAPIForService(

const operationIdsWithExample = new Set<string>();

// Compute the example directory for resolving source example paths
const exampleDir = resolveExampleDir(
options.examplesDirectory,
program.projectRoot,
context.version,
);

const [exampleMap, diagnostics] = await loadExamples(program, options, context.version);
program.reportDiagnostics(diagnostics);

Expand Down Expand Up @@ -610,7 +624,15 @@ export async function getOpenAPIForService(
operationIdsWithExample.add(currentEndpoint.operationId);
currentEndpoint["x-ms-examples"] = currentEndpoint["x-ms-examples"] || {};
for (const [title, example] of Object.entries(autoExamples)) {
currentEndpoint["x-ms-examples"][title] = { $ref: `./examples/${example.relativePath}` };
let ref: string;
if (options.skipExampleCopying) {
const sourceExamplePath = resolvePath(exampleDir, example.relativePath);
const outputDir = getDirectoryPath(context.outputFile);
ref = getRelativePathFromDirectory(outputDir, sourceExamplePath, false);
} else {
ref = `./examples/${example.relativePath}`;
}
currentEndpoint["x-ms-examples"][title] = { $ref: ref };
}
}

Expand Down Expand Up @@ -2799,6 +2821,34 @@ export function sortOpenAPIDocument(doc: OpenAPI2Document): OpenAPI2Document {
return sorted;
}

/**
* Resolves the example directory path, supporting `{version}` and `{version-status}` interpolation
* variables in the `examples-dir` option.
*
* When the examples-dir contains `{version}` or `{version-status}`, these are interpolated
* with the actual version values and the resulting path is used directly.
* Otherwise, the version is appended as a subdirectory (legacy behavior).
*/
function resolveExampleDir(
examplesDirectory: string | undefined,
projectRoot: string,
version: string | undefined,
): string {
const rawDir = examplesDirectory ?? resolvePath(projectRoot, "examples");
const hasVersionInterpolation =
rawDir.includes("{version}") || rawDir.includes("{version-status}");

if (hasVersionInterpolation) {
const versionStatus = version && (version.includes("preview") ? "preview" : "stable");
return interpolatePath(rawDir, {
"version-status": versionStatus,
version: version,
});
}

return version ? resolvePath(rawDir, version) : rawDir;
}

async function checkExamplesDirExists(host: CompilerHost, dir: string) {
try {
return (await host.stat(dir)).isDirectory();
Expand Down Expand Up @@ -2841,8 +2891,7 @@ async function loadExamples(
): Promise<[Map<string, Record<string, LoadedExample>>, readonly Diagnostic[]]> {
const host = program.host;
const diagnostics = createDiagnosticCollector();
const examplesBaseDir = options.examplesDirectory ?? resolvePath(program.projectRoot, "examples");
const exampleDir = version ? resolvePath(examplesBaseDir, version) : resolvePath(examplesBaseDir);
const exampleDir = resolveExampleDir(options.examplesDirectory, program.projectRoot, version);

if (!(await checkExamplesDirExists(host, exampleDir))) {
if (options.examplesDirectory) {
Expand Down
Loading