Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VSCode extension: add openapi3 preview #6015

Open
wants to merge 61 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 55 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
ac13a8e
init check-in
Jan 22, 2025
8e9c36b
do not use tmp folder
Jan 22, 2025
5f4bba3
open preview panel only after swagger file is generated
Feb 6, 2025
64e14d8
Merge branch 'main' into vscode/openapi-preview
Feb 8, 2025
63492f3
improve multiple swagger file selection ui
Feb 11, 2025
556e9db
use temp folder to host preview output
Feb 13, 2025
9166069
Merge branch 'main' into vscode/openapi-preview
Feb 13, 2025
f057599
fix pnpm not supprted in vsce package
Feb 13, 2025
c202688
fix command title
Feb 14, 2025
84dd3f8
Check minimal version of compiler
Feb 14, 2025
baae442
Merge branch 'main' into vscode/openapi-preview
Feb 14, 2025
e87fc7c
improve file selection error
Feb 14, 2025
b5a1f69
improve error handling
Feb 14, 2025
e224bde
add chronus change log
Feb 14, 2025
9185597
Merge branch 'main' into vscode/openapi-preview
archerzz Feb 14, 2025
ae60f8c
fix lint and format errors
Feb 14, 2025
fcd4a5c
roll back changes in license notice
Feb 14, 2025
68727e3
update change log to remove [vscode]
Feb 18, 2025
f639b31
add `swagger-ui-dist` as dependency and update build scripts accordingly
Feb 18, 2025
29b71e5
replace showErrorMessage with logger.error
Feb 18, 2025
0388c2b
catch async io operation exceptions
Feb 19, 2025
a3ffb90
roll back the change of throwing exception from `runCliCommand`
Feb 19, 2025
8014ae4
move `throttle()` from command implementation into `utils.ts`
Feb 19, 2025
c7b3238
move `parseOpenApi3File()` into `utils.ts`
Feb 19, 2025
3c87bd9
replace methods of `path` module with `path-utils` from compiler
Feb 19, 2025
3c5e28c
move `openaapi3-preview.ts` into `vscode-cmd` folder
Feb 19, 2025
71fdb8f
add openapi3 preview into menu items
Feb 19, 2025
ea6f6f7
update `showQuickPick` options to follow the convention
Feb 19, 2025
5d23d15
prompt uses to select when multiple output files are available
Feb 19, 2025
4a11104
Merge branch 'main' into vscode/openapi-preview
Feb 19, 2025
872f302
update pnpm-lock
Feb 19, 2025
ff30c0e
restore license notice
Feb 19, 2025
bcdcb52
Merge branch 'main' into vscode/openapi-preview
archerzz Feb 19, 2025
76d9c8f
http-client-java, when codegen error, trace the stderr too (#6054)
weidongxu-microsoft Feb 19, 2025
ac0b22d
Add details for C# emitter (#6052)
JoshLove-msft Feb 19, 2025
2ec8fae
[@typespec/http-specs] Fixed paths in specs/routes (#6013)
v-jiaodi Feb 19, 2025
2571810
Respect fully qualified namespace when matching (#6051)
JoshLove-msft Feb 19, 2025
d3d8482
refactor: rename ClientModelPlugin to ScmCodeModelPlugin (#6055)
jorgerangel-msft Feb 19, 2025
53cc8d8
Add additional options to readme (#6057)
JoshLove-msft Feb 19, 2025
266342e
Fix: StringTemplate type not supported in typespecValueToJson (#5937)
timotheeguerin Feb 19, 2025
8b397be
Add tests for includeTemplateDeclaration (#5894)
timotheeguerin Feb 19, 2025
df4f95b
Add fully qualified names support for AddTypesToKeep (#6061)
JoshLove-msft Feb 19, 2025
59ff8bc
Delete out-dated doc (#6063)
JoshLove-msft Feb 19, 2025
72492cd
Standalone ability to start local server (#6047)
timotheeguerin Feb 19, 2025
d67eb8c
remove typespec extension out of preview model (#5900)
chunyu3 Feb 20, 2025
1030b4a
[python] remove confusing words from docstring of models (#6070)
msyyc Feb 20, 2025
56316b9
Avoid duplicate null check and fix null serialization property name (…
JoshLove-msft Feb 20, 2025
7174976
Fixes to enums, operation signatures (#5952)
markcowl Feb 20, 2025
10883fc
Build emitter tests (#6089)
JoshLove-msft Feb 21, 2025
69bf3ce
update ordering of commands and menu items
Feb 21, 2025
ab1889e
rename `parseOpenApi3File` to `parseJsonFromFile` and change error ha…
Feb 21, 2025
ef3981c
add selected file in the error log if it's available
Feb 21, 2025
ed00127
Merge branch 'main' into vscode/openapi-preview
archerzz Feb 21, 2025
3bce4db
Merge branch 'main' into vscode/openapi-preview
archerzz Feb 24, 2025
8f74429
Merge branch 'main' into vscode/openapi-preview
archerzz Feb 25, 2025
f403f70
Merge branch 'main' into vscode/openapi-preview
archerzz Feb 26, 2025
0c85f2f
re-use `TraverseMainTspFileInWorkspace()` in `typespec-utils.ts`
Feb 26, 2025
c3e39de
do not show pop-up message when user cancels file selection
Feb 26, 2025
5d520df
move `swagger-ui-dist` from `dependencies` to `devDependencies`
Feb 26, 2025
48dbbe1
use rollup to copy swaigger-ui-dist files
Feb 26, 2025
ee3a135
Merge branch 'main' into vscode/openapi-preview
archerzz Feb 27, 2025
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/vscode-openapi-preview-2025-1-14-15-22-4.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: feature
packages:
- typespec-vscode
---

add openapi3 preview
3 changes: 3 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,8 @@ packages/http-client-csharp/generator/TestProjects/**/tspCodeModel.json
packages/http-client-java/generator/http-client-generator-test/src/main/**/*.json
packages/http-client-java/generator/http-client-generator-clientcore-test/src/main/**/*.json

# swagger-ui-dist lib files
packages/typespec-vscode/swagger-ui/swagger-ui*

.gitattributes
CODEOWNERS
1 change: 1 addition & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ export default tsEslint.config(
"packages/compiler/templates/**/*", // Ignore the templates which might have invalid code and not follow exactly our rules.
"**/venv/**/*", // Ignore python virtual env
"**/.vscode-test-web/**/*", // Ignore VSCode test web project
"packages/typespec-vscode/swagger-ui/swagger-ui*", // Ignore swagger-ui-dist files
// TODO: enable
"**/.scripts/**/*",
"eng/tsp-core/scripts/**/*",
Expand Down
2 changes: 2 additions & 0 deletions packages/typespec-vscode/.vscodeignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
!dist/**/*.js
!dist/**/*.cjs
!dist/**/*.js.map
!dist/**/*.css
!dist/**/language-configuration.json
dist/test/

Expand All @@ -18,3 +19,4 @@ dist/test/
!LICENSE
!icons/
!snippets.json
!swagger-ui/
27 changes: 23 additions & 4 deletions packages/typespec-vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,19 +116,29 @@
"explorer/context": [
{
"command": "typespec.importFromOpenApi3",
"group": "typespec@2"
"group": "typespec@3"
},
{
"command": "typespec.generateCode",
"when": "explorerResourceIsFolder || resourceLangId == typespec",
"group": "typespec@1"
},
{
"command": "typespec.showOpenApi3",
"when": "resourceLangId == typespec",
"group": "typespec@2"
}
],
"editor/context": [
{
"command": "typespec.generateCode",
"when": "resourceLangId == typespec",
"group": "typespec@1"
},
{
"command": "typespec.showOpenApi3",
"when": "resourceLangId == typespec",
"group": "typespec@2"
}
]
},
Expand Down Expand Up @@ -180,6 +190,11 @@
"command": "typespec.importFromOpenApi3",
"title": "Import TypeSpec from OpenApi3",
"category": "TypeSpec"
},
{
"command": "typespec.showOpenApi3",
"title": "Show OpenAPI3 Documentation",
"category": "TypeSpec"
}
],
"semanticTokenScopes": [
Expand Down Expand Up @@ -225,14 +240,15 @@
},
"scripts": {
"clean": "rimraf ./dist ./temp",
"build": "npm run compile && npm run copy-tmlanguage && npm run generate-language-configuration && npm run generate-third-party-notices && npm run package-vsix",
"build": "npm run compile && npm run copy-tmlanguage && npm run copy-swagger-ui-dist && npm run generate-language-configuration && npm run generate-third-party-notices && npm run package-vsix",
"compile": "rollup --config rollup.config.ts --configPlugin typescript --failAfterWarnings 2>&1",
"watch": "rollup --config rollup.config.ts --configPlugin typescript --watch",
"dogfood": "node scripts/dogfood.js",
"copy-tmlanguage": "node scripts/copy-tmlanguage.js",
"copy-swagger-ui-dist": "node scripts/copy-swagger-ui-dist.js",
"generate-language-configuration": "node scripts/generate-language-configuration.js",
"generate-third-party-notices": "typespec-build-tool generate-third-party-notices",
"package-vsix": "vsce package",
"package-vsix": "vsce package --no-dependencies",
"deploy": "vsce publish",
"open-in-browser": "vscode-test-web --extensionDevelopmentPath=. .",
"test:e2e": "pnpm test:web",
Expand All @@ -244,8 +260,8 @@
"@rollup/plugin-typescript": "~12.1.0",
"@types/mocha": "^10.0.9",
"@types/node": "~22.10.10",
"@types/vscode": "~1.96.0",
"@types/semver": "^7.5.8",
"@types/vscode": "~1.96.0",
"@typespec/compiler": "workspace:~",
"@typespec/internal-build-utils": "workspace:~",
"@vitest/coverage-v8": "^3.0.4",
Expand All @@ -261,5 +277,8 @@
"vitest": "^3.0.5",
"vscode-languageclient": "~9.0.1",
"yaml": "~2.7.0"
},
"dependencies": {
"swagger-ui-dist": "^5.18.2"
}
}
14 changes: 14 additions & 0 deletions packages/typespec-vscode/scripts/copy-swagger-ui-dist.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { copyFile } from "fs/promises";

await copyFile(
"node_modules/swagger-ui-dist/swagger-ui.css",
"../typespec-vscode/dist/swagger-ui.css",
);
await copyFile(
"node_modules/swagger-ui-dist/swagger-ui-bundle.js",
"../typespec-vscode/dist/swagger-ui-bundle.js",
);
await copyFile(
"node_modules/swagger-ui-dist/swagger-ui-standalone-preset.js",
"../typespec-vscode/dist/swagger-ui-standalone-preset.js",
);
8 changes: 8 additions & 0 deletions packages/typespec-vscode/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { createTypeSpecProject } from "./vscode-cmd/create-tsp-project.js";
import { emitCode } from "./vscode-cmd/emit-code/emit-code.js";
import { importFromOpenApi3 } from "./vscode-cmd/import-from-openapi3.js";
import { installCompilerGlobally } from "./vscode-cmd/install-tsp-compiler.js";
import { clearOpenApi3PreviewTempFolders, showOpenApi3 } from "./vscode-cmd/openapi3-preview.js";

let client: TspLanguageClient | undefined;
/**
Expand Down Expand Up @@ -114,6 +115,12 @@ export async function activate(context: ExtensionContext) {
}),
);

context.subscriptions.push(
commands.registerCommand(CommandName.ShowOpenApi3, async (uri: vscode.Uri) => {
await showOpenApi3(uri, context, client!);
}),
);

context.subscriptions.push(
vscode.workspace.onDidChangeConfiguration(async (e: vscode.ConfigurationChangeEvent) => {
if (e.affectsConfiguration(SettingName.TspServerPath)) {
Expand Down Expand Up @@ -171,6 +178,7 @@ export async function activate(context: ExtensionContext) {

export async function deactivate() {
await client?.stop();
await clearOpenApi3PreviewTempFolders();
}

async function recreateLSPClient(context: ExtensionContext) {
Expand Down
20 changes: 20 additions & 0 deletions packages/typespec-vscode/src/tsp-language-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,4 +224,24 @@ export class TspLanguageClient {
const lc = new LanguageClient(id, name, { run: exe, debug: exe }, options);
return new TspLanguageClient(lc, exe);
}

async compileOpenApi3(
mainTspFile: string,
srcFolder: string,
outputFolder: string,
): Promise<ExecOutput | undefined> {
const result = await this.runCliCommand(
[
"compile",
mainTspFile,
"--emit=@typespec/openapi3",
"--option",
"@typespec/openapi3.file-type=json",
"--option",
`@typespec/openapi3.emitter-output-dir=${outputFolder}`,
],
srcFolder,
);
return result;
}
}
1 change: 1 addition & 0 deletions packages/typespec-vscode/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const enum CommandName {
OpenUrl = "typespec.openUrl",
GenerateCode = "typespec.generateCode",
ImportFromOpenApi3 = "typespec.importFromOpenApi3",
ShowOpenApi3 = "typespec.showOpenApi3",
}

export interface InstallGlobalCliCommandArgs {
Expand Down
51 changes: 50 additions & 1 deletion packages/typespec-vscode/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { ModuleResolutionResult, NodePackage, ResolveModuleHost } from "@typespec/compiler";
import { spawn, SpawnOptions } from "child_process";
import { readFile, realpath, stat } from "fs/promises";
import { mkdir, readFile, realpath, stat } from "fs/promises";
import { tmpdir } from "os";
import { dirname } from "path";
import { CancellationToken } from "vscode";
import { Executable } from "vscode-languageclient/node.js";
Expand All @@ -27,6 +28,24 @@ export async function isDirectory(path: string) {
}
}

export async function createTempDir(): Promise<string | undefined> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

node has mkdtemp which does that too

try {
const tempDir = tmpdir();
const realTempDir = await realpath(tempDir);
const uid = createGuid();
const subDir = joinPaths(realTempDir, uid);
await mkdir(subDir);
return subDir;
} catch (e) {
logger.error("Failed to create temp folder", [e]);
return undefined;
}
}

function createGuid() {
return crypto.randomUUID();
}

export function isWhitespaceStringOrUndefined(str: string | undefined): boolean {
return str === undefined || str.trim() === "";
}
Expand Down Expand Up @@ -389,3 +408,33 @@ export async function loadPackageJsonFile(
if (!packageJson) return undefined;
return packageJson as NodePackage;
}

export async function parseJsonFromFile(filePath: string): Promise<string | undefined> {
try {
const fileContent = await readFile(filePath, "utf-8");
const content = JSON.parse(fileContent);
return content;
} catch (e) {
logger.error(`Failed to load JSON file: ${filePath}`, [e]);
return;
}
}

/**
* Throttle the function to be called at most once in every blockInMs milliseconds. This utility
* is useful when your event handler will trigger the same event multiple times in a short period.
*
* @param fn Underlying function to be throttled
* @param blockInMs Block time in milliseconds
* @returns a throttled function
*/
export function throttle<T extends (...args: any[]) => any>(fn: T, blockInMs: number): T {
let time: number | undefined;
return function (this: any, ...args: Parameters<T>) {
const now = Date.now();
if (time === undefined || now - time >= blockInMs) {
time = now;
fn.apply(this, args);
}
} as T;
}
Loading
Loading