Skip to content

Commit

Permalink
[tsp-client] Support swagger to typespec conversion (#7538)
Browse files Browse the repository at this point in the history
* add support for swagger->typespec conversion

* add usage information

* update readme

* update options wording

* update conversion command

* remove comment

* use npx

* update wording

* changelog + package.json
  • Loading branch information
catalinaperalta authored Jan 23, 2024
1 parent 7787c1b commit 20c54ae
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 8 deletions.
3 changes: 2 additions & 1 deletion tools/tsp-client/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# Release

## Unreleased - 0.4.0
## 2024-01-23 - 0.4.0

- Added support for initializing a project from a private repository specification.
- Added `convert` command to support swagger to TypeSpec project conversion.
- Changed `doesFileExist()` function to check local file system.
- Removed `fetch()` function.

Expand Down
4 changes: 4 additions & 0 deletions tools/tsp-client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ By default the `sync` command will look for a tsp-location.yaml to get the proje
### generate
Generate a client library from a TypeSpec project. The `generate` command should be run after the `sync` command. `generate` relies on the existence of the `TempTypeSpecFiles` directory created by the `sync` command and on an `emitter-package.json` file checked into your repository at the following path: `<repo root>/eng/emitter-package.json`. The `emitter-package.json` file is used to install project dependencies and get the appropriate emitter package.

### convert
Convert an existing swagger specification to a TypeSpec project. This command should only be run once to get started working on a TypeSpec project. TypeSpec projects will need to be optimized manually and fully reviewed after conversion. When using this command a path or url to a swagger README file is required through the `--swagger-readme` flag.

## Options
```
-c, --tsp-config The tspconfig.yaml file to use [string]
Expand All @@ -50,6 +53,7 @@ Generate a client library from a TypeSpec project. The `generate` command should
--local-spec-repo Path to local repository with the TypeSpec project [string]
--save-inputs Don't clean up the temp directory after generation [boolean]
--skip-sync-and-generate Skip sync and generate during project init [boolean]
--swagger-readme Path or url to swagger readme file [string]
-o, --output-dir Specify an alternate output directory for the
generated files. Default is your local directory. [string]
--repo Repository where the project is defined for init
Expand Down
2 changes: 1 addition & 1 deletion tools/tsp-client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@azure-tools/typespec-client-generator-cli",
"version": "0.3.0",
"version": "0.4.0",
"description": "A tool to generate Azure SDKs from TypeSpec",
"main": "dist/index.js",
"homepage": "https://github.com/Azure/azure-sdk-tools/tree/main/tools/tsp-client#readme",
Expand Down
35 changes: 34 additions & 1 deletion tools/tsp-client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ import { TspLocation, compileTsp, discoverMainFile, resolveTspConfigUrl } from "
import { getOptions } from "./options.js";
import { mkdir, writeFile, cp, readFile } from "node:fs/promises";
import { addSpecFiles, checkoutCommit, cloneRepo, getRepoRoot, sparseCheckout } from "./git.js";
import { doesFileExist } from "./network.js";
import { parse as parseYaml } from "yaml";
import { joinPaths, resolvePath } from "@typespec/compiler";
import { joinPaths, normalizePath, resolvePath } from "@typespec/compiler";
import { formatAdditionalDirectories, getAdditionalDirectoryName } from "./utils.js";
import { resolve } from "node:path";
import { spawn } from "node:child_process";


async function sdkInit(
Expand Down Expand Up @@ -191,6 +194,28 @@ async function generate({
}
}


async function convert(readme: string, outputDir: string): Promise<void> {
return new Promise((resolve, reject) => {
const autorest = spawn("npx", ["autorest", readme, "--openapi-to-typespec", "--use=@autorest/openapi-to-typespec", `--output-folder=${outputDir}`], {
cwd: outputDir,
stdio: "inherit",
shell: true,
});
autorest.once("exit", (code) => {
if (code === 0) {
resolve();
} else {
reject(new Error(`openapi to typespec conversion failed exited with code ${code}`));
}
});
autorest.once("error", (err) => {
reject(new Error(`openapi to typespec conversion failed with error: ${err}`));
});
});
}


async function main() {
const options = await getOptions();
if (options.debug) {
Expand Down Expand Up @@ -242,6 +267,14 @@ async function main() {
await syncTspFiles(rootUrl, options.localSpecRepo);
await generate({ rootUrl, noCleanup: options.noCleanup, additionalEmitterOptions: options.emitterOptions});
break;
case "convert":
Logger.info("Converting swagger to typespec...");
let readme = options.swaggerReadme!;
if (await doesFileExist(readme)) {
readme = normalizePath(resolve(readme));
}
await convert(readme, rootUrl);
break;
default:
throw new Error(`Unknown command: ${options.command}`);
}
Expand Down
5 changes: 4 additions & 1 deletion tools/tsp-client/src/log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ Commands:
sync Sync TypeSpec project specified in tsp-location.yaml [string]
generate Generate from a TypeSpec project [string]
update Sync and generate from a TypeSpec project [string]
convert Convert a swagger specification to TypeSpec [string]
Options:
-c, --tsp-config The tspconfig.yaml file to use [string]
Expand All @@ -77,7 +78,9 @@ Options:
--local-spec-repo Path to local repository with the TypeSpec project [string]
--save-inputs Don't clean up the temp directory after generation [boolean]
--skip-sync-and-generate Skip sync and generate during project init [boolean]
-o, --output-dir The output directory for the generated files [string]
--swagger-readme Path or url to swagger readme file [string]
-o, --output-dir Specify an alternate output directory for the
generated files. Default is your current directory [string]
--repo Repository where the project is defined for init
or update [string]
-v, --version Show version number [boolean]
Expand Down
30 changes: 26 additions & 4 deletions tools/tsp-client/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface Options {
isUrl: boolean;
localSpecRepo?: string;
emitterOptions?: string;
swaggerReadme?: string;
}

export async function getOptions(): Promise<Options> {
Expand Down Expand Up @@ -60,6 +61,9 @@ export async function getOptions(): Promise<Options> {
},
["skip-sync-and-generate"]: {
type: "boolean",
},
["swagger-readme"]: {
type: "string",
}
},
});
Expand All @@ -78,15 +82,23 @@ export async function getOptions(): Promise<Options> {
printUsage();
process.exit(1);
}
const supportedCommands = ["sync", "generate", "update", "init", "convert"];

const command = positionals[0];
if (!command) {
Logger.error("Command is required");
printUsage();
process.exit(1);
}

if (positionals[0] !== "sync" && positionals[0] !== "generate" && positionals[0] !== "update" && positionals[0] !== "init") {
Logger.error(`Unknown command ${positionals[0]}`);
if (!supportedCommands.includes(command)) {
Logger.error(`Unknown command ${command}`);
printUsage();
process.exit(1);
}

let isUrl = true;
if (positionals[0] === "init") {
if (command === "init") {
if (!values["tsp-config"]) {
Logger.error("tspConfig is required");
printUsage();
Expand All @@ -103,6 +115,15 @@ export async function getOptions(): Promise<Options> {
}
}
}

if (command === "convert") {
if (!values["swagger-readme"]) {
Logger.error("Must specify a swagger readme with the `--swagger-readme` flag");
printUsage();
process.exit(1);
}
}

// By default, assume that the command is run from the output directory
let outputDir = ".";
if (values["output-dir"]) {
Expand Down Expand Up @@ -133,7 +154,7 @@ export async function getOptions(): Promise<Options> {

return {
debug: values.debug ?? false,
command: positionals[0],
command: command,
tspConfig: values["tsp-config"],
noCleanup: values["save-inputs"] ?? false,
skipSyncAndGenerate: values["skip-sync-and-generate"] ?? false,
Expand All @@ -143,5 +164,6 @@ export async function getOptions(): Promise<Options> {
isUrl: isUrl,
localSpecRepo: values["local-spec-repo"],
emitterOptions: values["emitter-options"],
swaggerReadme: values["swagger-readme"],
};
}

0 comments on commit 20c54ae

Please sign in to comment.