From ca3249a4cd1bbb8d8af2e598198a4eb30b5a5837 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Mon, 18 Nov 2024 17:16:01 +0800 Subject: [PATCH 01/42] emit sdk --- packages/typespec-vscode/package.json | 21 +++ .../typespec-vscode/src/emit/emit-code.ts | 24 ++++ packages/typespec-vscode/src/emit/emitters.ts | 39 ++++++ packages/typespec-vscode/src/extension.ts | 126 ++++++++++++++++++ packages/typespec-vscode/src/npm-utils.ts | 11 ++ packages/typespec-vscode/src/utils.ts | 58 ++++++++ 6 files changed, 279 insertions(+) create mode 100644 packages/typespec-vscode/src/emit/emit-code.ts create mode 100644 packages/typespec-vscode/src/emit/emitters.ts create mode 100644 packages/typespec-vscode/src/npm-utils.ts diff --git a/packages/typespec-vscode/package.json b/packages/typespec-vscode/package.json index b1cb2b00a1..9d3cba3e7f 100644 --- a/packages/typespec-vscode/package.json +++ b/packages/typespec-vscode/package.json @@ -108,8 +108,29 @@ "command": "typespec.showOutputChannel", "title": "Show Output Channel", "category": "TypeSpec" + }, + { + "command": "typespec.emit", + "title": "TypeSpec emit", + "category": "TypeSpec" } ], + "menus": { + "explorer/context": [ + { + "command": "typespec.emit", + "when": "explorerResourceIsFolder || resourceLangId == typespec", + "group": "code_generation" + } + ], + "editor/context": [ + { + "command": "typespec.emit", + "when": "resourceLangId == typespec", + "group": "code_generation" + } + ] + }, "semanticTokenScopes": [ { "scopes": { diff --git a/packages/typespec-vscode/src/emit/emit-code.ts b/packages/typespec-vscode/src/emit/emit-code.ts new file mode 100644 index 0000000000..4651152067 --- /dev/null +++ b/packages/typespec-vscode/src/emit/emit-code.ts @@ -0,0 +1,24 @@ +import { dirname } from "path"; +import { executeCommand } from "../utils.js"; + +export async function compile( + command: string, + startFile: string, + emit: string, + outputDir?: string, +) { + const args: string[] = []; + args.push("compile"); + args.push(startFile); + if (emit) { + args.push("--emit", emit); + } + + if (outputDir) { + args.push("--option", `${emit}.emitter-output-dir=${outputDir}`); + } + + await executeCommand(command, args, { + cwd: dirname(startFile), + }); +} diff --git a/packages/typespec-vscode/src/emit/emitters.ts b/packages/typespec-vscode/src/emit/emitters.ts new file mode 100644 index 0000000000..4e36487a6d --- /dev/null +++ b/packages/typespec-vscode/src/emit/emitters.ts @@ -0,0 +1,39 @@ +import vscode from "vscode"; + +export interface EmitPackageQuickPickItem extends vscode.QuickPickItem { + language: string; + package: string; + version?: string; + fromConfig: boolean; +} + +export const recommendedEmitters: ReadonlyArray = [ + { + language: "openapi3", + package: "@typespec/openapi3", + label: "emit OpenAPI 3.0", + description: "from @typespec/openapi3", + fromConfig: false, + }, + { + language: "json-schema", + package: "@typespec/json-schema", + label: "emit JSON Schema", + description: "from @typespec/json-schema", + fromConfig: false, + }, + { + language: "protobuf", + package: "@typespec/protobuf", + label: "emit Protobuf", + description: "from @typespec/protobuf", + fromConfig: false, + }, + { + language: "Net", + package: "@typespec/http-client-csharp", + label: "emit C# code", + description: "from @typespec/http-client-csharp", + fromConfig: false, + }, +]; diff --git a/packages/typespec-vscode/src/extension.ts b/packages/typespec-vscode/src/extension.ts index 3647a203f6..c818dc0bb9 100644 --- a/packages/typespec-vscode/src/extension.ts +++ b/packages/typespec-vscode/src/extension.ts @@ -1,10 +1,15 @@ +import path, { dirname } from "path"; import vscode, { commands, ExtensionContext } from "vscode"; import { SettingName } from "./const.js"; +import { compile } from "./emit/emit-code.js"; +import { EmitPackageQuickPickItem, recommendedEmitters } from "./emit/emitters.js"; import { ExtensionLogListener } from "./log/extension-log-listener.js"; import logger from "./log/logger.js"; import { TypeSpecLogOutputChannel } from "./log/typespec-log-output-channel.js"; +import { npmInstallPackages } from "./npm-utils.js"; import { createTaskProvider } from "./task-provider.js"; import { TspLanguageClient } from "./tsp-language-client.js"; +import { isFile } from "./utils.js"; let client: TspLanguageClient | undefined; /** @@ -31,6 +36,20 @@ export async function activate(context: ExtensionContext) { }), ); + /* code generation command. */ + context.subscriptions.push( + commands.registerCommand("typespec.emit", async (uri: vscode.Uri) => { + await vscode.window.withProgress( + { + location: vscode.ProgressLocation.Window, + title: "TypeSpec Emitting", + cancellable: false, + }, + async (progress) => await doEmit(context, uri, progress), + ); + }), + ); + context.subscriptions.push( vscode.workspace.onDidChangeConfiguration(async (e: vscode.ConfigurationChangeEvent) => { if (e.affectsConfiguration(SettingName.TspServerPath)) { @@ -58,3 +77,110 @@ export async function activate(context: ExtensionContext) { export async function deactivate() { await client?.stop(); } + +async function doEmit( + context: vscode.ExtensionContext, + uri: vscode.Uri, + overallProgress: vscode.Progress<{ message?: string; increment?: number }>, +) { + const baseDir = (await isFile(uri.fsPath)) ? dirname(uri.fsPath) : uri.fsPath; + logger.info("Collecting emitters...", [], { + showOutput: false, + showPopup: false, + progress: overallProgress, + }); + const recommended: EmitPackageQuickPickItem[] = [...recommendedEmitters]; + const toQuickPickItem = ( + language: string, + packageName: string, + picked: boolean, + fromConfig: boolean, + ): EmitPackageQuickPickItem => { + const found = recommended.findIndex((ke) => ke.package === packageName); + if (found >= 0) { + const deleted = recommended.splice(found, 1); + deleted[0].picked = picked; + return { ...deleted[0], ...{ picked, fromConfig } }; + } else { + return { language: language, package: packageName, label: packageName, picked, fromConfig }; + } + }; + /* + const emitOnlyInOptions = Object.keys(config.options ?? {}) + .filter((key) => !config.emit?.includes(key)) + .map((e) => toQuickPickItem(e, false, true)); + const emitInEmit = (config.emit ?? []).map((e: any) => toQuickPickItem(e.toString(), true, true)); + + const all = [...emitInEmit, ...emitOnlyInOptions]; + + if (recommended.length > 0) { + all.push({ + package: "", + label: "Recommended Emitters", + kind: QuickPickItemKind.Separator, + fromConfig: false, + }); + } + recommended.forEach((e) => { + all.push(e); + }); + */ + + const all = [...recommendedEmitters].map((e) => + toQuickPickItem(e.language, e.package, true, false), + ); + let selectedEmitters = await vscode.window.showQuickPick(all, { + canPickMany: true, + placeHolder: "Select emitters to run", + }); + + if (!selectedEmitters || selectedEmitters.length === 0) { + logger.info("No emitters selected. Emit canceled.", [], { + showOutput: false, + showPopup: true, + progress: overallProgress, + }); + return; + } + + /* TODO: verify packages to install. */ + /* TODO: verify the sdk runtime installation. */ + + logger.info("npm install...", [], { + showOutput: false, + showPopup: false, + progress: overallProgress, + }); + + const packagesToInstall: string[] = []; + for (const e of selectedEmitters) { + /* install emitter package. */ + logger.info(`select ${e.package}`); + let packageFullName = e.package; + if (e.version) { + packageFullName = `${e.package}@${e.version}`; + } + packagesToInstall.push(packageFullName); + } + + /* npm install packages. */ + await npmInstallPackages(packagesToInstall, { cwd: baseDir }); + + /* emit */ + logger.info("start to emit code..."); + logger.info("Emit code ...", [], { + showOutput: false, + showPopup: false, + progress: overallProgress, + }); + const startFile = `${baseDir}/main.tsp`; + const compileCommand = "npx tsp"; + for (const e of selectedEmitters) { + /*TODO: add a dialog to config output dir. */ + const outputDir = path.resolve(baseDir, "tsp-output", e.language); + await compile(compileCommand, startFile, e.package, outputDir); + logger.info(`complete generating ${e.language} SDK.`); + + /*TODO: build sdk. */ + } +} diff --git a/packages/typespec-vscode/src/npm-utils.ts b/packages/typespec-vscode/src/npm-utils.ts new file mode 100644 index 0000000000..edf6e23b25 --- /dev/null +++ b/packages/typespec-vscode/src/npm-utils.ts @@ -0,0 +1,11 @@ +import { executeCommand } from "./utils.js"; + +export async function npmInstallPackages(packages: string[] = [], options: any) { + let command; + if (packages.length > 0) { + command = `npm install ${packages.join(" ")}`; + } else { + command = `npm install`; + } + await executeCommand(command, [], options); +} diff --git a/packages/typespec-vscode/src/utils.ts b/packages/typespec-vscode/src/utils.ts index 1bbed0a3ff..a33337e71d 100644 --- a/packages/typespec-vscode/src/utils.ts +++ b/packages/typespec-vscode/src/utils.ts @@ -1,4 +1,5 @@ import type { ModuleResolutionResult, ResolveModuleHost } from "@typespec/compiler"; +import { exec, spawn, SpawnOptions } from "child_process"; import { readFile, realpath, stat } from "fs/promises"; import { dirname, normalize, resolve } from "path"; import { Executable } from "vscode-languageclient/node.js"; @@ -102,3 +103,60 @@ export async function loadModule( return undefined; } } + +export async function executeCommand(command: string, args: string[], options: any): Promise { + if (args.length > 0) { + command = `${command} ${args.join(" ")}`; + } + exec(command, options, (error, stdout, stderr) => { + if (error) { + logger.error(`Error: ${error.message}`); + return; + } + if (stderr) { + logger.error(`Stderr: ${stderr}`); + return; + } + logger.info(`Stdout: ${stdout}`); + }); +} + +export interface ExecOutput { + stdout: string; + stderr: string; + exitCode: number; + error: string; + spawnOptions: SpawnOptions; +} +export async function spawnExecution( + command: string, + args: string[], + options: any, +): Promise { + let stdout = ""; + let stderr = ""; + let retcode = 0; + const child = spawn(command, args, options); + + child.stdout.on("data", (data) => { + // logger.info(`Stdout: ${data}`); + stdout += data.toString(); + }); + + child.stderr.on("data", (data) => { + stderr += data.toString(); + }); + + child.on("close", (code) => { + // logger.info(`Child process exited with code ${code}`); + retcode = code ?? 0; + }); + + return { + stdout: stdout, + stderr: stderr, + exitCode: retcode, + error: stderr, + spawnOptions: options, + }; +} From b8468b69290837067d1fe6c479f8388b8c0b6274 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Wed, 20 Nov 2024 10:53:11 +0800 Subject: [PATCH 02/42] add emitter configuration in extension setting --- packages/typespec-vscode/package.json | 65 +++++++++++++++-- packages/typespec-vscode/src/emit/emitters.ts | 2 + packages/typespec-vscode/src/extension.ts | 70 ++++++++++++++++--- packages/typespec-vscode/src/npm-utils.ts | 49 ++++++++++--- packages/typespec-vscode/src/utils.ts | 1 + 5 files changed, 166 insertions(+), 21 deletions(-) diff --git a/packages/typespec-vscode/package.json b/packages/typespec-vscode/package.json index 9d3cba3e7f..247dc245c1 100644 --- a/packages/typespec-vscode/package.json +++ b/packages/typespec-vscode/package.json @@ -76,6 +76,63 @@ ], "default": "off", "description": "Define whether/how the TypeSpec language server should send traces to client. For the traces to show properly in vscode Output, make sure 'Log Level' is also set to 'Trace' so that they won't be filtered at client side, which can be set through 'Developer: Set Log Level...' command." + }, + "typespec.emitter": { + "scope": "window", + "type": "object", + "properties": { + "Net": { + "type": "string", + "default": "@typespec/http-client-csharp", + "description": "Define the emitter to generate .NET SDK." + }, + "Java": { + "type": "string", + "default": "@typespec/http-client-java", + "description": "Define the emitter to generate Java SDK." + }, + "JavaScript": { + "type": "string", + "default": "@azure-tools/typespec-ts", + "description": "Define the emitter to generate JS SDK." + }, + "Python": { + "type": "string", + "default": "@typespec/http-client-python", + "description": "Define the emitter to generate Python SDK." + } + }, + "default": { + "Net": "@typespec/http-client-csharp", + "Java": "@typespec/http-client-java", + "JavaScript": "@azure-tools/typespec-ts", + "Python": "@typespec/http-client-python" + }, + "description": "Define the emitter for any language sdk generation." + }, + "typespec.emitter.net": { + "scope": "window", + "type": "string", + "default": "@typespec/http-client-csharp", + "description": "Define the emitter to generate .NET SDK." + }, + "typespec.emitter.java": { + "scope": "window", + "type": "string", + "default": "@typespec/http-client-java", + "description": "Define the emitter to generate Java SDK." + }, + "typespec.emitter.python": { + "scope": "window", + "type": "string", + "default": "@typespec/http-client-python", + "description": "Define the emitter to generate Python SDK." + }, + "typespec.emitter.javascript": { + "scope": "window", + "type": "string", + "default": "@azure-tools/typespec-ts", + "description": "Define the emitter to generate Js SDK." } } } @@ -110,22 +167,22 @@ "category": "TypeSpec" }, { - "command": "typespec.emit", - "title": "TypeSpec emit", + "command": "typespec.GenerateSDK", + "title": "TypeSpec Generate SDK", "category": "TypeSpec" } ], "menus": { "explorer/context": [ { - "command": "typespec.emit", + "command": "typespec.GenerateSDK", "when": "explorerResourceIsFolder || resourceLangId == typespec", "group": "code_generation" } ], "editor/context": [ { - "command": "typespec.emit", + "command": "typespec.GenerateSDK", "when": "resourceLangId == typespec", "group": "code_generation" } diff --git a/packages/typespec-vscode/src/emit/emitters.ts b/packages/typespec-vscode/src/emit/emitters.ts index 4e36487a6d..bb4086cbf7 100644 --- a/packages/typespec-vscode/src/emit/emitters.ts +++ b/packages/typespec-vscode/src/emit/emitters.ts @@ -37,3 +37,5 @@ export const recommendedEmitters: ReadonlyArray = [ fromConfig: false, }, ]; + +const config = vscode.workspace.getConfiguration("TypeSpec"); diff --git a/packages/typespec-vscode/src/extension.ts b/packages/typespec-vscode/src/extension.ts index c818dc0bb9..180bd754a5 100644 --- a/packages/typespec-vscode/src/extension.ts +++ b/packages/typespec-vscode/src/extension.ts @@ -6,7 +6,7 @@ import { EmitPackageQuickPickItem, recommendedEmitters } from "./emit/emitters.j import { ExtensionLogListener } from "./log/extension-log-listener.js"; import logger from "./log/logger.js"; import { TypeSpecLogOutputChannel } from "./log/typespec-log-output-channel.js"; -import { npmInstallPackages } from "./npm-utils.js"; +import { InstallationAction, NpmUtil } from "./npm-utils.js"; import { createTaskProvider } from "./task-provider.js"; import { TspLanguageClient } from "./tsp-language-client.js"; import { isFile } from "./utils.js"; @@ -38,11 +38,11 @@ export async function activate(context: ExtensionContext) { /* code generation command. */ context.subscriptions.push( - commands.registerCommand("typespec.emit", async (uri: vscode.Uri) => { + commands.registerCommand("typespec.GenerateSDK", async (uri: vscode.Uri) => { await vscode.window.withProgress( { location: vscode.ProgressLocation.Window, - title: "TypeSpec Emitting", + title: "TypeSpec Gerating SDK...", cancellable: false, }, async (progress) => await doEmit(context, uri, progress), @@ -62,6 +62,15 @@ export async function activate(context: ExtensionContext) { }), ); + vscode.tasks.registerTaskProvider("Typespec.GenerateSdk", { + provideTasks: () => { + return generatSdkTask(); + }, + resolveTask(_task: vscode.Task): vscode.Task | undefined { + return undefined; + }, + }); + return await vscode.window.withProgress( { title: "Launching TypeSpec language service...", @@ -78,7 +87,18 @@ export async function deactivate() { await client?.stop(); } -async function doEmit( +function generatSdkTask(): vscode.Task[] { + let task = new vscode.Task( + { type: "Typespec.GenerateSdk" }, + vscode.TaskScope.Workspace, + "Generate Sdk", + "generate sdk", + new vscode.ShellExecution("echo Hello World"), + ); + return [task]; +} + +export async function doEmit( context: vscode.ExtensionContext, uri: vscode.Uri, overallProgress: vscode.Progress<{ message?: string; increment?: number }>, @@ -144,6 +164,7 @@ async function doEmit( } /* TODO: verify packages to install. */ + /* TODO: verify the sdk runtime installation. */ logger.info("npm install...", [], { @@ -152,19 +173,50 @@ async function doEmit( progress: overallProgress, }); + const npmUtil = new NpmUtil(baseDir); + const packagesToInstall: string[] = []; for (const e of selectedEmitters) { /* install emitter package. */ logger.info(`select ${e.package}`); - let packageFullName = e.package; - if (e.version) { - packageFullName = `${e.package}@${e.version}`; + const { action, version } = npmUtil.ensureNpmPackageInstall(e.package, e.version); + if (action === InstallationAction.Upgrade) { + logger.info(`Upgrading ${e.package} to version ${version}`); + const options = { + ok: `OK (install ${e.package} by 'npm install'`, + recheck: `Check again (install ${e.package} manually)`, + ignore: `Ignore emitter ${e.label}`, + cancel: "Cancel", + }; + const selected = await vscode.window.showQuickPick(Object.values(options), { + canPickMany: false, + ignoreFocusOut: true, + placeHolder: `Package '${e.package}' needs to be installed for emitting`, + title: `TypeSpec Emit...`, + }); + if (selected === options.ok) { + packagesToInstall.push(`${e.package}@${version}`); + } + } else if (action === InstallationAction.Install) { + logger.info(`Installing ${e.package} version ${version}`, [], { + showOutput: true, + showPopup: true, + progress: overallProgress, + }); + logger.info(`Installing ${e.package} version ${version}`); + packagesToInstall.push(`${e.package}@${version}`); } - packagesToInstall.push(packageFullName); + // let packageFullName = e.package; + // if (e.version) { + // packageFullName = `${e.package}@${e.version}`; + // } + + // packagesToInstall.push(packageFullName); } /* npm install packages. */ - await npmInstallPackages(packagesToInstall, { cwd: baseDir }); + await npmUtil.npmInstallPackages(packagesToInstall); + // await npmInstallPackages(packagesToInstall, { cwd: baseDir }); /* emit */ logger.info("start to emit code..."); diff --git a/packages/typespec-vscode/src/npm-utils.ts b/packages/typespec-vscode/src/npm-utils.ts index edf6e23b25..a1abcb824d 100644 --- a/packages/typespec-vscode/src/npm-utils.ts +++ b/packages/typespec-vscode/src/npm-utils.ts @@ -1,11 +1,44 @@ import { executeCommand } from "./utils.js"; -export async function npmInstallPackages(packages: string[] = [], options: any) { - let command; - if (packages.length > 0) { - command = `npm install ${packages.join(" ")}`; - } else { - command = `npm install`; - } - await executeCommand(command, [], options); +export enum InstallationAction { + Install = "Install", + Cancel = "Cancel", + Upgrade = "Upgrade", +} + +export class NpmUtil { + private cwd: string; + + constructor(cwd: string) { + this.cwd = cwd; + } + + public async npmInstallPackages(packages: string[] = [], options: any = {}) { + let command; + if (packages.length > 0) { + command = `npm install ${packages.join(" ")}`; + } else { + command = `npm install`; + } + await executeCommand(command, [], { ...options, cwd: this.cwd }); + } + + public ensureNpmPackageInstall( + packageName: string, + version?: string, + ): { action: InstallationAction; version: string } { + const [isPackageInstalled, installedVersion] = this.isPackageInstalled(packageName, version); + if (isPackageInstalled) { + if (version && installedVersion !== version) { + return { action: InstallationAction.Upgrade, version: version }; + } + return { action: InstallationAction.Cancel, version: installedVersion }; + } else { + return { action: InstallationAction.Install, version: version ?? "latest" }; + } + } + + private isPackageInstalled(packageName: string, version?: string): [boolean, string] { + return [false, "latest"]; + } } diff --git a/packages/typespec-vscode/src/utils.ts b/packages/typespec-vscode/src/utils.ts index a33337e71d..3a4a1df779 100644 --- a/packages/typespec-vscode/src/utils.ts +++ b/packages/typespec-vscode/src/utils.ts @@ -136,6 +136,7 @@ export async function spawnExecution( let stdout = ""; let stderr = ""; let retcode = 0; + const child = spawn(command, args, options); child.stdout.on("data", (data) => { From f0c4b8972da067ccaeec7fc9a7ac5f647b2b70de Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Fri, 22 Nov 2024 18:00:27 +0800 Subject: [PATCH 03/42] add output dir configuration and options configuration --- packages/typespec-vscode/package.json | 5 +- packages/typespec-vscode/src/const.ts | 11 ++ .../src/emit/emit-quick-pick-item.ts | 9 + packages/typespec-vscode/src/emit/emitters.ts | 29 ++- packages/typespec-vscode/src/extension.ts | 171 +++++++++++++++++- packages/typespec-vscode/src/npm-utils.ts | 58 +++++- 6 files changed, 258 insertions(+), 25 deletions(-) create mode 100644 packages/typespec-vscode/src/emit/emit-quick-pick-item.ts diff --git a/packages/typespec-vscode/package.json b/packages/typespec-vscode/package.json index 247dc245c1..f11b0bb4ac 100644 --- a/packages/typespec-vscode/package.json +++ b/packages/typespec-vscode/package.json @@ -168,7 +168,7 @@ }, { "command": "typespec.GenerateSDK", - "title": "TypeSpec Generate SDK", + "title": "TypeSpec: Generate Client SDK", "category": "TypeSpec" } ], @@ -244,6 +244,9 @@ "test:e2e": "pnpm test:web", "test:web": "vscode-test-web --extensionDevelopmentPath=. --headless --extensionTestsPath=dist/test/web/suite.js ./test/web/data" }, + "dependencies": { + "@typespec/compiler": "workspace:~" + }, "devDependencies": { "@rollup/plugin-commonjs": "~28.0.0", "@rollup/plugin-node-resolve": "~15.3.0", diff --git a/packages/typespec-vscode/src/const.ts b/packages/typespec-vscode/src/const.ts index c2d3e7cec6..fa479d79fc 100644 --- a/packages/typespec-vscode/src/const.ts +++ b/packages/typespec-vscode/src/const.ts @@ -1,3 +1,14 @@ export const enum SettingName { TspServerPath = "typespec.tsp-server.path", + NetEmitter = "typespec.emitter.net", + JavaEmitter = "typespec.emitter.java", + PythonEmitter = "typespec.emitter.python", + JsEmitter = "typespec.emitter.javascript", } + +export const languageEmitterSettingNames: Record = { + net: SettingName.NetEmitter, + jave: SettingName.JavaEmitter, + python: SettingName.PythonEmitter, + js: SettingName.JsEmitter, +}; diff --git a/packages/typespec-vscode/src/emit/emit-quick-pick-item.ts b/packages/typespec-vscode/src/emit/emit-quick-pick-item.ts new file mode 100644 index 0000000000..c8299cde84 --- /dev/null +++ b/packages/typespec-vscode/src/emit/emit-quick-pick-item.ts @@ -0,0 +1,9 @@ +import vscode from "vscode"; + +export interface EmitQuickPickItem extends vscode.QuickPickItem { + language: string; + package: string; + version?: string; + fromConfig: boolean; + outputDir?: string; +} diff --git a/packages/typespec-vscode/src/emit/emitters.ts b/packages/typespec-vscode/src/emit/emitters.ts index bb4086cbf7..ce4cce6987 100644 --- a/packages/typespec-vscode/src/emit/emitters.ts +++ b/packages/typespec-vscode/src/emit/emitters.ts @@ -1,12 +1,26 @@ import vscode from "vscode"; +import { languageEmitterSettingNames } from "../const.js"; +import { EmitQuickPickItem } from "./emit-quick-pick-item.js"; -export interface EmitPackageQuickPickItem extends vscode.QuickPickItem { - language: string; - package: string; - version?: string; - fromConfig: boolean; +const extensionConfig = vscode.workspace.getConfiguration(); + +function getEmitterPickItem(language: string): EmitQuickPickItem { + const packageName: string = + extensionConfig.get(languageEmitterSettingNames[language] ?? "") ?? ""; + return { + language: language, + package: packageName, + label: `emit ${language} client sdk`, + description: `from ${packageName}`, + fromConfig: false, + }; } +export const recommendedEmitters: ReadonlyArray = Object.keys( + languageEmitterSettingNames, +).map((lang) => getEmitterPickItem(lang)); + +/* export const recommendedEmitters: ReadonlyArray = [ { language: "openapi3", @@ -31,11 +45,10 @@ export const recommendedEmitters: ReadonlyArray = [ }, { language: "Net", - package: "@typespec/http-client-csharp", + package: config.get("typespec.emitter.net") ?? "@typespec/http-client-csharp", label: "emit C# code", description: "from @typespec/http-client-csharp", fromConfig: false, }, ]; - -const config = vscode.workspace.getConfiguration("TypeSpec"); +*/ diff --git a/packages/typespec-vscode/src/extension.ts b/packages/typespec-vscode/src/extension.ts index 180bd754a5..da22219b6f 100644 --- a/packages/typespec-vscode/src/extension.ts +++ b/packages/typespec-vscode/src/extension.ts @@ -2,7 +2,8 @@ import path, { dirname } from "path"; import vscode, { commands, ExtensionContext } from "vscode"; import { SettingName } from "./const.js"; import { compile } from "./emit/emit-code.js"; -import { EmitPackageQuickPickItem, recommendedEmitters } from "./emit/emitters.js"; +import { EmitQuickPickItem } from "./emit/emit-quick-pick-item.js"; +import { recommendedEmitters } from "./emit/emitters.js"; import { ExtensionLogListener } from "./log/extension-log-listener.js"; import logger from "./log/logger.js"; import { TypeSpecLogOutputChannel } from "./log/typespec-log-output-channel.js"; @@ -42,7 +43,7 @@ export async function activate(context: ExtensionContext) { await vscode.window.withProgress( { location: vscode.ProgressLocation.Window, - title: "TypeSpec Gerating SDK...", + title: "TypeSpec: Gerating Client SDK...", cancellable: false, }, async (progress) => await doEmit(context, uri, progress), @@ -109,13 +110,13 @@ export async function doEmit( showPopup: false, progress: overallProgress, }); - const recommended: EmitPackageQuickPickItem[] = [...recommendedEmitters]; + const recommended: EmitQuickPickItem[] = [...recommendedEmitters]; const toQuickPickItem = ( language: string, packageName: string, picked: boolean, fromConfig: boolean, - ): EmitPackageQuickPickItem => { + ): EmitQuickPickItem => { const found = recommended.findIndex((ke) => ke.package === packageName); if (found >= 0) { const deleted = recommended.splice(found, 1); @@ -125,6 +126,8 @@ export async function doEmit( return { language: language, package: packageName, label: packageName, picked, fromConfig }; } }; + + /* pre-compile. */ /* const emitOnlyInOptions = Object.keys(config.options ?? {}) .filter((key) => !config.emit?.includes(key)) @@ -149,7 +152,7 @@ export async function doEmit( const all = [...recommendedEmitters].map((e) => toQuickPickItem(e.language, e.package, true, false), ); - let selectedEmitters = await vscode.window.showQuickPick(all, { + const selectedEmitters = await vscode.window.showQuickPick(all, { canPickMany: true, placeHolder: "Select emitters to run", }); @@ -163,6 +166,92 @@ export async function doEmit( return; } + /* config the emitter. */ + await vscode.window + .showInformationMessage("configure the emitters in the tspConfig.yaml", "Yes", "No") + .then(async (selection) => { + if (selection === "Yes") { + const document = await vscode.workspace.openTextDocument( + path.resolve(baseDir, "tspconfig.yaml"), + ); + vscode.window.showTextDocument(document, { + preview: false, + viewColumn: vscode.ViewColumn.Two, + }); + await vscode.window + .showInformationMessage("configure emitter.", "Completed") + .then((selection) => { + if (selection === "Completed") { + vscode.commands.executeCommand("workbench.action.closeActiveEditor"); + } + }); + } else if (selection === "No") { + } + }); + + /*config the output dir for each selected sdk. */ + const root = vscode.Uri.joinPath(context.extensionUri, "outputDir_view"); + const panel = vscode.window.createWebviewPanel( + "Configure output directory", // Identifies the type of the webview. Used internally + "Configure output directory", // Title of the panel displayed to the user + vscode.ViewColumn.Beside, // Editor column to show the new webview panel in + { + retainContextWhenHidden: true, + enableScripts: true, + localResourceRoots: [root], + }, // Webview options. More on these later. + ); + + // And set its HTML content + panel.webview.html = getWebviewContent(selectedEmitters); + // Handle messages from the webview + // panel.webview.onDidReceiveMessage( + // async (message) => { + // switch (message.command) { + // case "submitValues": + // vscode.window.showInformationMessage(`${message}}`); + // vscode.commands.executeCommand("workbench.action.closeActiveEditor"); + // panel.dispose(); + // return; + // } + // }, + // undefined, + // context.subscriptions, + // ); + + // Wait for the panel to be disposed + const outputDirs = await new Promise<{}>((resolve) => { + panel.webview.onDidReceiveMessage( + async (message) => { + switch (message.command) { + case "submitValues": + //vscode.window.showInformationMessage(`${message}}`); + //vscode.commands.executeCommand("workbench.action.closeActiveEditor"); + resolve(message); + panel.dispose(); + } + }, + undefined, + context.subscriptions, + ); + }); + + const outputDirsRecord: Record = outputDirs; + selectedEmitters.forEach((e) => (e.outputDir = outputDirsRecord[e.language])); + + /* config the output dir one by one. */ + // for (const e of selectedEmitters) { + // const outputDirInput = await vscode.window.showInputBox({ + // placeHolder: `client/${e.language}`, + // value: `client/${e.language}`, + // prompt: "Please provide the output directory", + // validateInput: (text: string) => { + // return text.trim() === "" ? "Input cannot be empty" : null; + // }, + // }); + // e.outputDir = outputDirInput; + // } + /* TODO: verify packages to install. */ /* TODO: verify the sdk runtime installation. */ @@ -179,7 +268,7 @@ export async function doEmit( for (const e of selectedEmitters) { /* install emitter package. */ logger.info(`select ${e.package}`); - const { action, version } = npmUtil.ensureNpmPackageInstall(e.package, e.version); + const { action, version } = await npmUtil.ensureNpmPackageInstall(e.package, e.version); if (action === InstallationAction.Upgrade) { logger.info(`Upgrading ${e.package} to version ${version}`); const options = { @@ -229,10 +318,78 @@ export async function doEmit( const compileCommand = "npx tsp"; for (const e of selectedEmitters) { /*TODO: add a dialog to config output dir. */ - const outputDir = path.resolve(baseDir, "tsp-output", e.language); + let outputDir = path.resolve(baseDir, "tsp-output", e.language); + // const outputDirInput = await vscode.window.showInputBox({ + // placeHolder: `client/${e.language}`, + // value: `client/${e.language}`, + // prompt: "Please provide the output directory", + // validateInput: (text: string) => { + // return text.trim() === "" ? "Input cannot be empty" : null; + // }, + // }); + + // if (outputDirInput) { + // if (!path.isAbsolute(outputDirInput)) { + // outputDir = path.resolve(baseDir, outputDirInput); + // } else { + // outputDir = outputDirInput; + // } + // } + if (e.outputDir) { + if (!path.isAbsolute(e.outputDir)) { + outputDir = path.resolve(baseDir, e.outputDir); + } else { + outputDir = e.outputDir; + } + } + + //const outputDir = path.resolve(baseDir, "tsp-output", e.language); await compile(compileCommand, startFile, e.package, outputDir); + logger.info(`Generate Client SDK for ${e.language} ...`, [], { + showOutput: false, + showPopup: true, + progress: overallProgress, + }); logger.info(`complete generating ${e.language} SDK.`); /*TODO: build sdk. */ } } + +function getWebviewContent(selectedEmitters: EmitQuickPickItem[]): string { + let body = ""; + let script = ""; + let directories = ""; + for (const e of selectedEmitters) { + body += ` +

`; + script += `const ${e.language} = document.getElementById('${e.language}').value;`; + directories += `${e.language}: ${e.language},`; + } + + script += `vscode.postMessage({ command: "submitValues", ${directories} });`; + + return ` + + + + + + Multi Input + + +

Configure output directory for each SDK

+
+ ${body} + +
+ + + + `; +} diff --git a/packages/typespec-vscode/src/npm-utils.ts b/packages/typespec-vscode/src/npm-utils.ts index a1abcb824d..f84cc1b0aa 100644 --- a/packages/typespec-vscode/src/npm-utils.ts +++ b/packages/typespec-vscode/src/npm-utils.ts @@ -1,9 +1,21 @@ -import { executeCommand } from "./utils.js"; +import fs from "fs"; +import path from "path"; +import logger from "./log/logger.js"; +import { executeCommand, loadModule } from "./utils.js"; export enum InstallationAction { Install = "Install", - Cancel = "Cancel", Upgrade = "Upgrade", + Skip = "Skip", + Cancel = "Cancel", +} + +export interface NpmPackageInfo { + name: string; + version?: string; + resolved?: string; + overridden?: string; + dependencies?: Record; } export class NpmUtil { @@ -23,22 +35,50 @@ export class NpmUtil { await executeCommand(command, [], { ...options, cwd: this.cwd }); } - public ensureNpmPackageInstall( + public async ensureNpmPackageInstall( packageName: string, version?: string, - ): { action: InstallationAction; version: string } { - const [isPackageInstalled, installedVersion] = this.isPackageInstalled(packageName, version); + ): Promise<{ action: InstallationAction; version: string }> { + const { installed: isPackageInstalled, version: installedVersion } = + await this.isPackageInstalled(packageName); if (isPackageInstalled) { if (version && installedVersion !== version) { return { action: InstallationAction.Upgrade, version: version }; } - return { action: InstallationAction.Cancel, version: installedVersion }; + return { action: InstallationAction.Cancel, version: installedVersion ?? "" }; } else { - return { action: InstallationAction.Install, version: version ?? "latest" }; + return { action: InstallationAction.Install, version: version ?? "" }; } } - private isPackageInstalled(packageName: string, version?: string): [boolean, string] { - return [false, "latest"]; + private async isPackageInstalled( + packageName: string, + ): Promise<{ installed: boolean; version: string | undefined }> { + const packageInfo = await this.loadNpmPackage(packageName); + if (packageInfo) return { installed: true, version: packageInfo.version }; + return { installed: false, version: undefined }; + } + + private async loadNpmPackage(packageName: string): Promise { + const executable = await loadModule(this.cwd, packageName); + if (executable) { + const packageJsonPath = path.resolve(executable.path, "package.json"); + + /* get the package version. */ + let version; + try { + const data = fs.readFileSync(packageJsonPath, "utf-8"); + const packageJson = JSON.parse(data); + version = packageJson.version; + } catch (error) { + logger.error(`Error reading package.json: ${error}`); + } + return { + name: packageName, + version: version, + }; + } + + return undefined; } } From 4691431c43ba94d3e9bbcb09c667787aaccfd2dd Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Fri, 22 Nov 2024 18:09:34 +0800 Subject: [PATCH 04/42] remove unused code --- packages/typespec-vscode/src/extension.ts | 43 +---------------------- 1 file changed, 1 insertion(+), 42 deletions(-) diff --git a/packages/typespec-vscode/src/extension.ts b/packages/typespec-vscode/src/extension.ts index da22219b6f..9517114a29 100644 --- a/packages/typespec-vscode/src/extension.ts +++ b/packages/typespec-vscode/src/extension.ts @@ -204,29 +204,13 @@ export async function doEmit( // And set its HTML content panel.webview.html = getWebviewContent(selectedEmitters); + // Handle messages from the webview - // panel.webview.onDidReceiveMessage( - // async (message) => { - // switch (message.command) { - // case "submitValues": - // vscode.window.showInformationMessage(`${message}}`); - // vscode.commands.executeCommand("workbench.action.closeActiveEditor"); - // panel.dispose(); - // return; - // } - // }, - // undefined, - // context.subscriptions, - // ); - - // Wait for the panel to be disposed const outputDirs = await new Promise<{}>((resolve) => { panel.webview.onDidReceiveMessage( async (message) => { switch (message.command) { case "submitValues": - //vscode.window.showInformationMessage(`${message}}`); - //vscode.commands.executeCommand("workbench.action.closeActiveEditor"); resolve(message); panel.dispose(); } @@ -295,17 +279,10 @@ export async function doEmit( logger.info(`Installing ${e.package} version ${version}`); packagesToInstall.push(`${e.package}@${version}`); } - // let packageFullName = e.package; - // if (e.version) { - // packageFullName = `${e.package}@${e.version}`; - // } - - // packagesToInstall.push(packageFullName); } /* npm install packages. */ await npmUtil.npmInstallPackages(packagesToInstall); - // await npmInstallPackages(packagesToInstall, { cwd: baseDir }); /* emit */ logger.info("start to emit code..."); @@ -317,24 +294,7 @@ export async function doEmit( const startFile = `${baseDir}/main.tsp`; const compileCommand = "npx tsp"; for (const e of selectedEmitters) { - /*TODO: add a dialog to config output dir. */ let outputDir = path.resolve(baseDir, "tsp-output", e.language); - // const outputDirInput = await vscode.window.showInputBox({ - // placeHolder: `client/${e.language}`, - // value: `client/${e.language}`, - // prompt: "Please provide the output directory", - // validateInput: (text: string) => { - // return text.trim() === "" ? "Input cannot be empty" : null; - // }, - // }); - - // if (outputDirInput) { - // if (!path.isAbsolute(outputDirInput)) { - // outputDir = path.resolve(baseDir, outputDirInput); - // } else { - // outputDir = outputDirInput; - // } - // } if (e.outputDir) { if (!path.isAbsolute(e.outputDir)) { outputDir = path.resolve(baseDir, e.outputDir); @@ -343,7 +303,6 @@ export async function doEmit( } } - //const outputDir = path.resolve(baseDir, "tsp-output", e.language); await compile(compileCommand, startFile, e.package, outputDir); logger.info(`Generate Client SDK for ${e.language} ...`, [], { showOutput: false, From 539bee9a07546b7391b6289250628782222ed83f Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Sun, 24 Nov 2024 21:21:07 +0800 Subject: [PATCH 05/42] update emitter configuration --- packages/typespec-vscode/package.json | 8 ++++---- packages/typespec-vscode/src/emit/emitters.ts | 4 ++-- packages/typespec-vscode/src/extension.ts | 13 ++++++++----- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/packages/typespec-vscode/package.json b/packages/typespec-vscode/package.json index f11b0bb4ac..4079f55179 100644 --- a/packages/typespec-vscode/package.json +++ b/packages/typespec-vscode/package.json @@ -114,25 +114,25 @@ "scope": "window", "type": "string", "default": "@typespec/http-client-csharp", - "description": "Define the emitter to generate .NET SDK." + "description": "Define the emitter to generate .NET SDK.\n\nExample (with version): @typespec/http-client-csharp@1.0.0\n\nExample (without version): @typespec/http-client-csharp" }, "typespec.emitter.java": { "scope": "window", "type": "string", "default": "@typespec/http-client-java", - "description": "Define the emitter to generate Java SDK." + "description": "Define the emitter to generate Java SDK.\n\nExample (with version): @typespec/http-client-java@1.0.0\n\nExample (without version): @typespec/http-client-java" }, "typespec.emitter.python": { "scope": "window", "type": "string", "default": "@typespec/http-client-python", - "description": "Define the emitter to generate Python SDK." + "description": "Define the emitter to generate Python SDK.\n\nExample (with version): @typespec/http-client-python@1.0.0\n\nExample (without version): @typespec/http-client-python" }, "typespec.emitter.javascript": { "scope": "window", "type": "string", "default": "@azure-tools/typespec-ts", - "description": "Define the emitter to generate Js SDK." + "description": "Define the emitter to generate Js SDK.\n\nExample (with version): @azure-tools/typespec-ts@1.0.0\n\nExample (without version): @azure-tools/typespec-ts" } } } diff --git a/packages/typespec-vscode/src/emit/emitters.ts b/packages/typespec-vscode/src/emit/emitters.ts index ce4cce6987..e516a8b814 100644 --- a/packages/typespec-vscode/src/emit/emitters.ts +++ b/packages/typespec-vscode/src/emit/emitters.ts @@ -10,8 +10,8 @@ function getEmitterPickItem(language: string): EmitQuickPickItem { return { language: language, package: packageName, - label: `emit ${language} client sdk`, - description: `from ${packageName}`, + label: `Generate ${language} client sdk`, + description: `from emitter ${packageName}`, fromConfig: false, }; } diff --git a/packages/typespec-vscode/src/extension.ts b/packages/typespec-vscode/src/extension.ts index 9517114a29..154e63a4b9 100644 --- a/packages/typespec-vscode/src/extension.ts +++ b/packages/typespec-vscode/src/extension.ts @@ -63,7 +63,7 @@ export async function activate(context: ExtensionContext) { }), ); - vscode.tasks.registerTaskProvider("Typespec.GenerateSdk", { + const taskDisposal = vscode.tasks.registerTaskProvider("typespec", { provideTasks: () => { return generatSdkTask(); }, @@ -71,6 +71,7 @@ export async function activate(context: ExtensionContext) { return undefined; }, }); + context.subscriptions.push(taskDisposal); return await vscode.window.withProgress( { @@ -92,9 +93,9 @@ function generatSdkTask(): vscode.Task[] { let task = new vscode.Task( { type: "Typespec.GenerateSdk" }, vscode.TaskScope.Workspace, - "Generate Sdk", - "generate sdk", - new vscode.ShellExecution("echo Hello World"), + "Generate Sdk Task", + "generate sdk Task", + new vscode.ShellExecution("code --command 'typespec.GenerateSDK'"), ); return [task]; } @@ -105,6 +106,7 @@ export async function doEmit( overallProgress: vscode.Progress<{ message?: string; increment?: number }>, ) { const baseDir = (await isFile(uri.fsPath)) ? dirname(uri.fsPath) : uri.fsPath; + /*TODO: check the main.tsp file if it is a project folder. */ logger.info("Collecting emitters...", [], { showOutput: false, showPopup: false, @@ -253,6 +255,7 @@ export async function doEmit( /* install emitter package. */ logger.info(`select ${e.package}`); const { action, version } = await npmUtil.ensureNpmPackageInstall(e.package, e.version); + /* TODO: check the dependent compiler version. */ if (action === InstallationAction.Upgrade) { logger.info(`Upgrading ${e.package} to version ${version}`); const options = { @@ -334,7 +337,7 @@ function getWebviewContent(selectedEmitters: EmitQuickPickItem[]): string { - Multi Input + Configure Output Directory

Configure output directory for each SDK

From 777948138dbd230a6ea1a3ee75e5b154f8390ebb Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Tue, 26 Nov 2024 17:01:38 +0800 Subject: [PATCH 06/42] resolve tsp command --- .../typespec-vscode/src/emit/emit-code.ts | 24 --- packages/typespec-vscode/src/emit/emit.ts | 32 ++++ packages/typespec-vscode/src/extension.ts | 169 +++++++++++++++--- packages/typespec-vscode/src/npm-utils.ts | 12 +- packages/typespec-vscode/src/utils.ts | 77 ++++++-- 5 files changed, 248 insertions(+), 66 deletions(-) delete mode 100644 packages/typespec-vscode/src/emit/emit-code.ts create mode 100644 packages/typespec-vscode/src/emit/emit.ts diff --git a/packages/typespec-vscode/src/emit/emit-code.ts b/packages/typespec-vscode/src/emit/emit-code.ts deleted file mode 100644 index 4651152067..0000000000 --- a/packages/typespec-vscode/src/emit/emit-code.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { dirname } from "path"; -import { executeCommand } from "../utils.js"; - -export async function compile( - command: string, - startFile: string, - emit: string, - outputDir?: string, -) { - const args: string[] = []; - args.push("compile"); - args.push(startFile); - if (emit) { - args.push("--emit", emit); - } - - if (outputDir) { - args.push("--option", `${emit}.emitter-output-dir=${outputDir}`); - } - - await executeCommand(command, args, { - cwd: dirname(startFile), - }); -} diff --git a/packages/typespec-vscode/src/emit/emit.ts b/packages/typespec-vscode/src/emit/emit.ts new file mode 100644 index 0000000000..07ca66ec48 --- /dev/null +++ b/packages/typespec-vscode/src/emit/emit.ts @@ -0,0 +1,32 @@ +import { dirname } from "path"; +import { Executable } from "vscode-languageclient/node.js"; +import { ExecOutput, executeCommand } from "../utils.js"; + +export async function compile( + cli: Executable, + startFile: string, + emitter: string, + options: Record, +): Promise { + const args: string[] = cli.args ?? []; + args.push("compile"); + args.push(startFile); + if (emitter) { + args.push("--emit", emitter); + } + + for (const [key, value] of Object.entries(options)) { + args.push("--option", `${emitter}.${key}=${value}`); + } + + return await executeCommand(cli.command, args, { + cwd: dirname(startFile), + }); +} + +export async function check(): Promise<{ + valid: boolean; + required: { name: string; version: string }[]; +}> { + return { valid: true, required: [] }; +} diff --git a/packages/typespec-vscode/src/extension.ts b/packages/typespec-vscode/src/extension.ts index 154e63a4b9..6f0ee684fc 100644 --- a/packages/typespec-vscode/src/extension.ts +++ b/packages/typespec-vscode/src/extension.ts @@ -1,8 +1,8 @@ import path, { dirname } from "path"; import vscode, { commands, ExtensionContext } from "vscode"; import { SettingName } from "./const.js"; -import { compile } from "./emit/emit-code.js"; import { EmitQuickPickItem } from "./emit/emit-quick-pick-item.js"; +import { check, compile } from "./emit/emit.js"; import { recommendedEmitters } from "./emit/emitters.js"; import { ExtensionLogListener } from "./log/extension-log-listener.js"; import logger from "./log/logger.js"; @@ -10,7 +10,7 @@ import { TypeSpecLogOutputChannel } from "./log/typespec-log-output-channel.js"; import { InstallationAction, NpmUtil } from "./npm-utils.js"; import { createTaskProvider } from "./task-provider.js"; import { TspLanguageClient } from "./tsp-language-client.js"; -import { isFile } from "./utils.js"; +import { isFile, resolveTypeSpecCli } from "./utils.js"; let client: TspLanguageClient | undefined; /** @@ -91,11 +91,15 @@ export async function deactivate() { function generatSdkTask(): vscode.Task[] { let task = new vscode.Task( - { type: "Typespec.GenerateSdk" }, + { + label: "Task: Generate SDK", + type: "typespec", + }, vscode.TaskScope.Workspace, "Generate Sdk Task", - "generate sdk Task", + "tsp", new vscode.ShellExecution("code --command 'typespec.GenerateSDK'"), + // new vscode.ShellExecution("code --command 'typespec.GenerateSDK'"), ); return [task]; } @@ -105,7 +109,51 @@ export async function doEmit( uri: vscode.Uri, overallProgress: vscode.Progress<{ message?: string; increment?: number }>, ) { - const baseDir = (await isFile(uri.fsPath)) ? dirname(uri.fsPath) : uri.fsPath; + let tspProjectFolder: string = ""; + if (!uri) { + // const inputText = await vscode.window + // .showInputBox({ prompt: "Choose the tsp project folder or tsp file." }) + // .then(async (inputText) => { + // if (inputText !== undefined) { + // const options = { + // canSelectMany: false, + // openLabel: "Select Folder", + // canSelectFolders: true, + // canSelectFiles: false, + // }; + + // await vscode.window.showOpenDialog(options).then((folderUris) => { + // inputText = folderUris ? folderUris[0].fsPath : ""; + // }); + + // // vscode.window.showOpenDialog(options).then(folderUri => { + // // if (folderUri && folderUri) { + // // vscode.window.showInformationMessage(`You entered: ${inputText} and selected folder: ${folderUri.fsPath}`); + // // } + // // }); + // } + // }); + // tspProjectFolder = inputText ?? ""; + // } + const options = { + canSelectMany: false, + openLabel: "Choose tsp project Directory", + canSelectFolders: true, + canSelectFiles: false, + }; + await await vscode.window.showOpenDialog(options).then((uris) => { + tspProjectFolder = uris ? uris[0].fsPath : ""; + }); + } else { + tspProjectFolder = uri.fsPath; + } + + logger.info(`Select folder ${tspProjectFolder} ...`, [], { + showOutput: true, + showPopup: false, + progress: overallProgress, + }); + const baseDir = (await isFile(tspProjectFolder)) ? dirname(tspProjectFolder) : tspProjectFolder; /*TODO: check the main.tsp file if it is a project folder. */ logger.info("Collecting emitters...", [], { showOutput: false, @@ -168,6 +216,28 @@ export async function doEmit( return; } + /* TODO: verify the sdk runtime installation. */ + /* inform to install needed runtime. */ + const { valid, required } = await check(); + if (!valid) { + const toInstall = required.map((e) => e.name).join(", "); + await vscode.window + .showInformationMessage( + `Please install the required runtime for the selected emitters\n\n.net (>= 0.8.0) ${toInstall}`, + "OK", + ) + .then((selection) => { + if (selection === "OK") { + logger.info("Emit canceled.", [], { + showOutput: false, + showPopup: true, + progress: overallProgress, + }); + } + }); + return; + } + /* config the emitter. */ await vscode.window .showInformationMessage("configure the emitters in the tspConfig.yaml", "Yes", "No") @@ -230,7 +300,7 @@ export async function doEmit( // const outputDirInput = await vscode.window.showInputBox({ // placeHolder: `client/${e.language}`, // value: `client/${e.language}`, - // prompt: "Please provide the output directory", + // prompt: `Please provide the output directory for ${e.language} SDK`, // validateInput: (text: string) => { // return text.trim() === "" ? "Input cannot be empty" : null; // }, @@ -240,8 +310,6 @@ export async function doEmit( /* TODO: verify packages to install. */ - /* TODO: verify the sdk runtime installation. */ - logger.info("npm install...", [], { showOutput: false, showPopup: false, @@ -259,7 +327,7 @@ export async function doEmit( if (action === InstallationAction.Upgrade) { logger.info(`Upgrading ${e.package} to version ${version}`); const options = { - ok: `OK (install ${e.package} by 'npm install'`, + ok: `OK (install ${e.package}@${version} by 'npm install'`, recheck: `Check again (install ${e.package} manually)`, ignore: `Ignore emitter ${e.label}`, cancel: "Cancel", @@ -274,28 +342,74 @@ export async function doEmit( packagesToInstall.push(`${e.package}@${version}`); } } else if (action === InstallationAction.Install) { - logger.info(`Installing ${e.package} version ${version}`, [], { + // logger.info(`Installing ${e.package} version ${version}`, [], { + // showOutput: true, + // showPopup: true, + // progress: overallProgress, + // }); + let packageFullName = e.package; + if (e.version) { + packageFullName = `${e.package}@${e.version}`; + } + logger.info(`Installing ${packageFullName}`); + packagesToInstall.push(`${packageFullName}`); + } + } + + /* npm install packages. */ + if (packagesToInstall.length > 0) { + logger.info(`Installing ${packagesToInstall.join("\n\n")}`, [], { + showOutput: true, + showPopup: true, + progress: overallProgress, + }); + try { + const npmInstallResult = await npmUtil.npmInstallPackages(packagesToInstall); + logger.info("completed install..."); + if (npmInstallResult.exitCode !== 0) { + logger.error(`Error occurred when installing packages: ${npmInstallResult.stderr}`, [], { + showOutput: true, + showPopup: true, + progress: overallProgress, + }); + return; + } + } catch (err) { + logger.error(`Error occurred when installing packages: ${err}`, [], { showOutput: true, showPopup: true, progress: overallProgress, }); - logger.info(`Installing ${e.package} version ${version}`); - packagesToInstall.push(`${e.package}@${version}`); + return; } } - - /* npm install packages. */ - await npmUtil.npmInstallPackages(packagesToInstall); + // const npmInstallResult = await npmUtil.npmInstallPackages(packagesToInstall); + // if (npmInstallResult.exitCode !== 0) { + // logger.error(`Error occurred when installing packages: ${npmInstallResult.stderr}`, [], { + // showOutput: true, + // showPopup: true, + // progress: overallProgress, + // }); + // return; + // } /* emit */ - logger.info("start to emit code..."); logger.info("Emit code ...", [], { showOutput: false, showPopup: false, progress: overallProgress, }); + /*TODO: resolve the start file. */ const startFile = `${baseDir}/main.tsp`; - const compileCommand = "npx tsp"; + const cli = await resolveTypeSpecCli(baseDir); + if (!cli) { + logger.error("Cannot find TypeSpec CLI. Please install @typespec/compiler. Cancel emit.", [], { + showOutput: true, + showPopup: true, + progress: overallProgress, + }); + return; + } for (const e of selectedEmitters) { let outputDir = path.resolve(baseDir, "tsp-output", e.language); if (e.outputDir) { @@ -306,12 +420,25 @@ export async function doEmit( } } - await compile(compileCommand, startFile, e.package, outputDir); + const options: Record = {}; + options["emitter-output-dir"] = outputDir; logger.info(`Generate Client SDK for ${e.language} ...`, [], { showOutput: false, showPopup: true, progress: overallProgress, }); + const compileResult = await compile(cli, startFile, e.package, options); + if (compileResult.exitCode !== 0) { + logger.info( + `Failed to generate Client SDK for ${e.language}. error: ${compileResult.error}`, + [], + { + showOutput: false, + showPopup: true, + progress: overallProgress, + }, + ); + } logger.info(`complete generating ${e.language} SDK.`); /*TODO: build sdk. */ @@ -323,7 +450,7 @@ function getWebviewContent(selectedEmitters: EmitQuickPickItem[]): string { let script = ""; let directories = ""; for (const e of selectedEmitters) { - body += ` + body += `

`; script += `const ${e.language} = document.getElementById('${e.language}').value;`; directories += `${e.language}: ${e.language},`; @@ -339,8 +466,8 @@ function getWebviewContent(selectedEmitters: EmitQuickPickItem[]): string { Configure Output Directory - -

Configure output directory for each SDK

+ +

Configure output directory for each language SDK

${body} diff --git a/packages/typespec-vscode/src/npm-utils.ts b/packages/typespec-vscode/src/npm-utils.ts index f84cc1b0aa..d2d29376a8 100644 --- a/packages/typespec-vscode/src/npm-utils.ts +++ b/packages/typespec-vscode/src/npm-utils.ts @@ -1,7 +1,7 @@ import fs from "fs"; import path from "path"; import logger from "./log/logger.js"; -import { executeCommand, loadModule } from "./utils.js"; +import { ExecOutput, executeCommand, loadModule } from "./utils.js"; export enum InstallationAction { Install = "Install", @@ -25,14 +25,20 @@ export class NpmUtil { this.cwd = cwd; } - public async npmInstallPackages(packages: string[] = [], options: any = {}) { + public async npmInstallPackages(packages: string[] = [], options: any = {}): Promise { let command; if (packages.length > 0) { command = `npm install ${packages.join(" ")}`; } else { command = `npm install`; } - await executeCommand(command, [], { ...options, cwd: this.cwd }); + // const output = await new Promise((resolve) => { + // const execResult = executeCommand(command, [], { ...options, cwd: this.cwd }); + // resolve(execResult); + // }); + // return output; + return await executeCommand(command, [], { ...options, cwd: this.cwd }); + //return spawnExecution("npm", ["install", ...packages], { ...options, cwd: this.cwd }); } public async ensureNpmPackageInstall( diff --git a/packages/typespec-vscode/src/utils.ts b/packages/typespec-vscode/src/utils.ts index 3a4a1df779..80aa5d2d66 100644 --- a/packages/typespec-vscode/src/utils.ts +++ b/packages/typespec-vscode/src/utils.ts @@ -1,7 +1,8 @@ import type { ModuleResolutionResult, ResolveModuleHost } from "@typespec/compiler"; import { exec, spawn, SpawnOptions } from "child_process"; import { readFile, realpath, stat } from "fs/promises"; -import { dirname, normalize, resolve } from "path"; +import path, { dirname, normalize, resolve } from "path"; +import { promisify } from "util"; import { Executable } from "vscode-languageclient/node.js"; import logger from "./log/logger.js"; @@ -104,23 +105,6 @@ export async function loadModule( } } -export async function executeCommand(command: string, args: string[], options: any): Promise { - if (args.length > 0) { - command = `${command} ${args.join(" ")}`; - } - exec(command, options, (error, stdout, stderr) => { - if (error) { - logger.error(`Error: ${error.message}`); - return; - } - if (stderr) { - logger.error(`Stderr: ${stderr}`); - return; - } - logger.info(`Stdout: ${stdout}`); - }); -} - export interface ExecOutput { stdout: string; stderr: string; @@ -128,6 +112,47 @@ export interface ExecOutput { error: string; spawnOptions: SpawnOptions; } + +export async function executeCommand( + command: string, + args: string[], + options: any, +): Promise { + let stdoutstr: string = ""; + let errMessage: string = ""; + let retcode = 0; + if (args.length > 0) { + command = `${command} ${args.join(" ")}`; + } + // exec(command, options, (error, stdout, stderr) => { + // if (error) { + // logger.error(`Error: ${error.message}`); + // errMessage += error.message; + // retcode = error.code ?? 0; + // return; + // } + // if (stderr) { + // logger.error(`Stderr: ${stderr}`); + // errMessage += stderr; + // return; + // } + // stdoutstr += stdout; + // logger.info(`Stdout: ${stdout}`); + // }); + const execPromise = promisify(exec); + + const { stdout, stderr } = await execPromise(command, options); + if (stdout) stdoutstr += stdout; + if (stderr) errMessage += stderr; + return { + stdout: stdoutstr, + stderr: errMessage, + exitCode: retcode, + error: errMessage, + spawnOptions: options, + }; +} + export async function spawnExecution( command: string, args: string[], @@ -161,3 +186,19 @@ export async function spawnExecution( spawnOptions: options, }; } + +export async function resolveTypeSpecCli(absolutePath: string): Promise { + if (!path.isAbsolute(absolutePath) || (await isFile(absolutePath))) { + return undefined; + } + const modelInfo = await loadModule(absolutePath, "@typespec/compiler"); + if (modelInfo) { + //const cli = modelInfo.executables.find((exe) => exe.name === "tsp"); + const cmdPath = path.resolve(modelInfo.path, "cmd/tsp.js"); + return { + command: "node", + args: [cmdPath], + }; + } + return undefined; +} From 05bbef75772a2824353a33e3919f3d969f34d0c0 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Tue, 26 Nov 2024 17:23:35 +0800 Subject: [PATCH 07/42] define promised exec --- packages/typespec-vscode/src/extension.ts | 2 +- packages/typespec-vscode/src/npm-utils.ts | 10 ++-- packages/typespec-vscode/src/utils.ts | 58 ++++++++++++++++------- 3 files changed, 46 insertions(+), 24 deletions(-) diff --git a/packages/typespec-vscode/src/extension.ts b/packages/typespec-vscode/src/extension.ts index 6f0ee684fc..fbf60a2247 100644 --- a/packages/typespec-vscode/src/extension.ts +++ b/packages/typespec-vscode/src/extension.ts @@ -429,7 +429,7 @@ export async function doEmit( }); const compileResult = await compile(cli, startFile, e.package, options); if (compileResult.exitCode !== 0) { - logger.info( + logger.error( `Failed to generate Client SDK for ${e.language}. error: ${compileResult.error}`, [], { diff --git a/packages/typespec-vscode/src/npm-utils.ts b/packages/typespec-vscode/src/npm-utils.ts index d2d29376a8..7e1cb34b80 100644 --- a/packages/typespec-vscode/src/npm-utils.ts +++ b/packages/typespec-vscode/src/npm-utils.ts @@ -1,7 +1,7 @@ import fs from "fs"; import path from "path"; import logger from "./log/logger.js"; -import { ExecOutput, executeCommand, loadModule } from "./utils.js"; +import { ExecOutput, loadModule, promisifyExec } from "./utils.js"; export enum InstallationAction { Install = "Install", @@ -32,12 +32,8 @@ export class NpmUtil { } else { command = `npm install`; } - // const output = await new Promise((resolve) => { - // const execResult = executeCommand(command, [], { ...options, cwd: this.cwd }); - // resolve(execResult); - // }); - // return output; - return await executeCommand(command, [], { ...options, cwd: this.cwd }); + + return await promisifyExec(command, [], { ...options, cwd: this.cwd }); //return spawnExecution("npm", ["install", ...packages], { ...options, cwd: this.cwd }); } diff --git a/packages/typespec-vscode/src/utils.ts b/packages/typespec-vscode/src/utils.ts index 80aa5d2d66..77de531843 100644 --- a/packages/typespec-vscode/src/utils.ts +++ b/packages/typespec-vscode/src/utils.ts @@ -124,26 +124,52 @@ export async function executeCommand( if (args.length > 0) { command = `${command} ${args.join(" ")}`; } - // exec(command, options, (error, stdout, stderr) => { - // if (error) { - // logger.error(`Error: ${error.message}`); - // errMessage += error.message; - // retcode = error.code ?? 0; - // return; - // } - // if (stderr) { - // logger.error(`Stderr: ${stderr}`); - // errMessage += stderr; - // return; - // } - // stdoutstr += stdout; - // logger.info(`Stdout: ${stdout}`); - // }); + exec(command, options, (error, stdout, stderr) => { + if (error) { + logger.error(`Error: ${error.message}`); + errMessage += error.message; + retcode = error.code ?? 0; + return; + } + if (stderr) { + logger.error(`Stderr: ${stderr}`); + errMessage += stderr; + return; + } + stdoutstr += stdout; + logger.info(`Stdout: ${stdout}`); + }); + + return { + stdout: stdoutstr, + stderr: errMessage, + exitCode: retcode, + error: errMessage, + spawnOptions: options, + }; +} + +export async function promisifyExec( + command: string, + args: string[], + options: any, +): Promise { + let stdoutstr: string = ""; + let errMessage: string = ""; + let retcode = 0; + if (args.length > 0) { + command = `${command} ${args.join(" ")}`; + } + const execPromise = promisify(exec); const { stdout, stderr } = await execPromise(command, options); if (stdout) stdoutstr += stdout; - if (stderr) errMessage += stderr; + if (stderr) { + errMessage += stderr; + retcode = 1; + } + return { stdout: stdoutstr, stderr: errMessage, From 41a8796a67f7673cccdccb4abd8b8cae05043e6a Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Mon, 2 Dec 2024 15:21:35 +0800 Subject: [PATCH 08/42] remove doEmit to emit.ts --- packages/typespec-vscode/src/emit/emit.ts | 388 +++++++++++++++++++++- packages/typespec-vscode/src/extension.ts | 386 +-------------------- 2 files changed, 387 insertions(+), 387 deletions(-) diff --git a/packages/typespec-vscode/src/emit/emit.ts b/packages/typespec-vscode/src/emit/emit.ts index 07ca66ec48..6eb14c4553 100644 --- a/packages/typespec-vscode/src/emit/emit.ts +++ b/packages/typespec-vscode/src/emit/emit.ts @@ -1,6 +1,390 @@ -import { dirname } from "path"; +import path, { dirname } from "path"; +import vscode from "vscode"; import { Executable } from "vscode-languageclient/node.js"; -import { ExecOutput, executeCommand } from "../utils.js"; +import logger from "../log/logger.js"; +import { InstallationAction, NpmUtil } from "../npm-utils.js"; +import { ExecOutput, executeCommand, isFile, resolveTypeSpecCli } from "../utils.js"; +import { EmitQuickPickItem } from "./emit-quick-pick-item.js"; +import { recommendedEmitters } from "./emitters.js"; + +export async function doEmit( + context: vscode.ExtensionContext, + uri: vscode.Uri, + overallProgress: vscode.Progress<{ message?: string; increment?: number }>, +) { + let tspProjectFolder: string = ""; + if (!uri) { + // const inputText = await vscode.window + // .showInputBox({ prompt: "Choose the tsp project folder or tsp file." }) + // .then(async (inputText) => { + // if (inputText !== undefined) { + // const options = { + // canSelectMany: false, + // openLabel: "Select Folder", + // canSelectFolders: true, + // canSelectFiles: false, + // }; + + // await vscode.window.showOpenDialog(options).then((folderUris) => { + // inputText = folderUris ? folderUris[0].fsPath : ""; + // }); + + // // vscode.window.showOpenDialog(options).then(folderUri => { + // // if (folderUri && folderUri) { + // // vscode.window.showInformationMessage(`You entered: ${inputText} and selected folder: ${folderUri.fsPath}`); + // // } + // // }); + // } + // }); + // tspProjectFolder = inputText ?? ""; + // } + const options = { + canSelectMany: false, + openLabel: "Choose tsp project Directory", + canSelectFolders: true, + canSelectFiles: false, + }; + await await vscode.window.showOpenDialog(options).then((uris) => { + tspProjectFolder = uris ? uris[0].fsPath : ""; + }); + } else { + tspProjectFolder = uri.fsPath; + } + + logger.info(`Select folder ${tspProjectFolder} ...`, [], { + showOutput: true, + showPopup: false, + progress: overallProgress, + }); + const baseDir = (await isFile(tspProjectFolder)) ? dirname(tspProjectFolder) : tspProjectFolder; + /*TODO: check the main.tsp file if it is a project folder. */ + logger.info("Collecting emitters...", [], { + showOutput: false, + showPopup: false, + progress: overallProgress, + }); + const recommended: EmitQuickPickItem[] = [...recommendedEmitters]; + const toQuickPickItem = ( + language: string, + packageName: string, + picked: boolean, + fromConfig: boolean, + ): EmitQuickPickItem => { + const found = recommended.findIndex((ke) => ke.package === packageName); + if (found >= 0) { + const deleted = recommended.splice(found, 1); + deleted[0].picked = picked; + return { ...deleted[0], ...{ picked, fromConfig } }; + } else { + return { language: language, package: packageName, label: packageName, picked, fromConfig }; + } + }; + + /* pre-compile. */ + /* + const emitOnlyInOptions = Object.keys(config.options ?? {}) + .filter((key) => !config.emit?.includes(key)) + .map((e) => toQuickPickItem(e, false, true)); + const emitInEmit = (config.emit ?? []).map((e: any) => toQuickPickItem(e.toString(), true, true)); + + const all = [...emitInEmit, ...emitOnlyInOptions]; + + if (recommended.length > 0) { + all.push({ + package: "", + label: "Recommended Emitters", + kind: QuickPickItemKind.Separator, + fromConfig: false, + }); + } + recommended.forEach((e) => { + all.push(e); + }); + */ + + const all = [...recommendedEmitters].map((e) => + toQuickPickItem(e.language, e.package, true, false), + ); + const selectedEmitters = await vscode.window.showQuickPick(all, { + canPickMany: true, + placeHolder: "Select emitters to run", + }); + + if (!selectedEmitters || selectedEmitters.length === 0) { + logger.info("No emitters selected. Emit canceled.", [], { + showOutput: false, + showPopup: true, + progress: overallProgress, + }); + return; + } + + /* TODO: verify the sdk runtime installation. */ + /* inform to install needed runtime. */ + const { valid, required } = await check(); + if (!valid) { + const toInstall = required.map((e) => e.name).join(", "); + await vscode.window + .showInformationMessage( + `Please install the required runtime for the selected emitters\n\n.net (>= 0.8.0) ${toInstall}`, + "OK", + ) + .then((selection) => { + if (selection === "OK") { + logger.info("Emit canceled.", [], { + showOutput: false, + showPopup: true, + progress: overallProgress, + }); + } + }); + return; + } + + /* config the emitter. */ + await vscode.window + .showInformationMessage("configure the emitters in the tspConfig.yaml", "Yes", "No") + .then(async (selection) => { + if (selection === "Yes") { + const document = await vscode.workspace.openTextDocument( + path.resolve(baseDir, "tspconfig.yaml"), + ); + vscode.window.showTextDocument(document, { + preview: false, + viewColumn: vscode.ViewColumn.Two, + }); + await vscode.window + .showInformationMessage("configure emitter.", "Completed") + .then((selection) => { + if (selection === "Completed") { + vscode.commands.executeCommand("workbench.action.closeActiveEditor"); + } + }); + } else if (selection === "No") { + } + }); + + /*config the output dir for each selected sdk. */ + const root = vscode.Uri.joinPath(context.extensionUri, "outputDir_view"); + const panel = vscode.window.createWebviewPanel( + "Configure output directory", // Identifies the type of the webview. Used internally + "Configure output directory", // Title of the panel displayed to the user + vscode.ViewColumn.Beside, // Editor column to show the new webview panel in + { + retainContextWhenHidden: true, + enableScripts: true, + localResourceRoots: [root], + }, // Webview options. More on these later. + ); + + // And set its HTML content + panel.webview.html = getWebviewContent(selectedEmitters); + + // Handle messages from the webview + const outputDirs = await new Promise<{}>((resolve) => { + panel.webview.onDidReceiveMessage( + async (message) => { + switch (message.command) { + case "submitValues": + resolve(message); + panel.dispose(); + } + }, + undefined, + context.subscriptions, + ); + }); + + const outputDirsRecord: Record = outputDirs; + selectedEmitters.forEach((e) => (e.outputDir = outputDirsRecord[e.language])); + + /* config the output dir one by one. */ + // for (const e of selectedEmitters) { + // const outputDirInput = await vscode.window.showInputBox({ + // placeHolder: `client/${e.language}`, + // value: `client/${e.language}`, + // prompt: `Please provide the output directory for ${e.language} SDK`, + // validateInput: (text: string) => { + // return text.trim() === "" ? "Input cannot be empty" : null; + // }, + // }); + // e.outputDir = outputDirInput; + // } + + /* TODO: verify packages to install. */ + + logger.info("npm install...", [], { + showOutput: false, + showPopup: false, + progress: overallProgress, + }); + + const npmUtil = new NpmUtil(baseDir); + + const packagesToInstall: string[] = []; + for (const e of selectedEmitters) { + /* install emitter package. */ + logger.info(`select ${e.package}`); + const { action, version } = await npmUtil.ensureNpmPackageInstall(e.package, e.version); + /* TODO: check the dependent compiler version. */ + if (action === InstallationAction.Upgrade) { + logger.info(`Upgrading ${e.package} to version ${version}`); + const options = { + ok: `OK (install ${e.package}@${version} by 'npm install'`, + recheck: `Check again (install ${e.package} manually)`, + ignore: `Ignore emitter ${e.label}`, + cancel: "Cancel", + }; + const selected = await vscode.window.showQuickPick(Object.values(options), { + canPickMany: false, + ignoreFocusOut: true, + placeHolder: `Package '${e.package}' needs to be installed for emitting`, + title: `TypeSpec Emit...`, + }); + if (selected === options.ok) { + packagesToInstall.push(`${e.package}@${version}`); + } + } else if (action === InstallationAction.Install) { + // logger.info(`Installing ${e.package} version ${version}`, [], { + // showOutput: true, + // showPopup: true, + // progress: overallProgress, + // }); + let packageFullName = e.package; + if (e.version) { + packageFullName = `${e.package}@${e.version}`; + } + logger.info(`Installing ${packageFullName}`); + packagesToInstall.push(`${packageFullName}`); + } + } + + /* npm install packages. */ + if (packagesToInstall.length > 0) { + logger.info(`Installing ${packagesToInstall.join("\n\n")}`, [], { + showOutput: true, + showPopup: true, + progress: overallProgress, + }); + try { + const npmInstallResult = await npmUtil.npmInstallPackages(packagesToInstall); + logger.info("completed install..."); + if (npmInstallResult.exitCode !== 0) { + logger.error(`Error occurred when installing packages: ${npmInstallResult.stderr}`, [], { + showOutput: true, + showPopup: true, + progress: overallProgress, + }); + return; + } + } catch (err) { + logger.error(`Error occurred when installing packages: ${err}`, [], { + showOutput: true, + showPopup: true, + progress: overallProgress, + }); + return; + } + } + // const npmInstallResult = await npmUtil.npmInstallPackages(packagesToInstall); + // if (npmInstallResult.exitCode !== 0) { + // logger.error(`Error occurred when installing packages: ${npmInstallResult.stderr}`, [], { + // showOutput: true, + // showPopup: true, + // progress: overallProgress, + // }); + // return; + // } + + /* emit */ + logger.info("Emit code ...", [], { + showOutput: false, + showPopup: false, + progress: overallProgress, + }); + /*TODO: resolve the start file. */ + const startFile = `${baseDir}/main.tsp`; + const cli = await resolveTypeSpecCli(baseDir); + if (!cli) { + logger.error("Cannot find TypeSpec CLI. Please install @typespec/compiler. Cancel emit.", [], { + showOutput: true, + showPopup: true, + progress: overallProgress, + }); + return; + } + for (const e of selectedEmitters) { + let outputDir = path.resolve(baseDir, "tsp-output", e.language); + if (e.outputDir) { + if (!path.isAbsolute(e.outputDir)) { + outputDir = path.resolve(baseDir, e.outputDir); + } else { + outputDir = e.outputDir; + } + } + + const options: Record = {}; + options["emitter-output-dir"] = outputDir; + logger.info(`Generate Client SDK for ${e.language} ...`, [], { + showOutput: false, + showPopup: true, + progress: overallProgress, + }); + const compileResult = await compile(cli, startFile, e.package, options); + if (compileResult.exitCode !== 0) { + logger.error( + `Failed to generate Client SDK for ${e.language}. error: ${compileResult.error}`, + [], + { + showOutput: false, + showPopup: true, + progress: overallProgress, + }, + ); + } + logger.info(`complete generating ${e.language} SDK.`); + + /*TODO: build sdk. */ + } +} + +function getWebviewContent(selectedEmitters: EmitQuickPickItem[]): string { + let body = ""; + let script = ""; + let directories = ""; + for (const e of selectedEmitters) { + body += ` +

`; + script += `const ${e.language} = document.getElementById('${e.language}').value;`; + directories += `${e.language}: ${e.language},`; + } + + script += `vscode.postMessage({ command: "submitValues", ${directories} });`; + + return ` + + + + + + Configure Output Directory + + +

Configure output directory for each language SDK

+ + ${body} + + + + + + `; +} export async function compile( cli: Executable, diff --git a/packages/typespec-vscode/src/extension.ts b/packages/typespec-vscode/src/extension.ts index fbf60a2247..bd5b441e21 100644 --- a/packages/typespec-vscode/src/extension.ts +++ b/packages/typespec-vscode/src/extension.ts @@ -1,16 +1,11 @@ -import path, { dirname } from "path"; import vscode, { commands, ExtensionContext } from "vscode"; import { SettingName } from "./const.js"; -import { EmitQuickPickItem } from "./emit/emit-quick-pick-item.js"; -import { check, compile } from "./emit/emit.js"; -import { recommendedEmitters } from "./emit/emitters.js"; +import { doEmit } from "./emit/emit.js"; import { ExtensionLogListener } from "./log/extension-log-listener.js"; import logger from "./log/logger.js"; import { TypeSpecLogOutputChannel } from "./log/typespec-log-output-channel.js"; -import { InstallationAction, NpmUtil } from "./npm-utils.js"; import { createTaskProvider } from "./task-provider.js"; import { TspLanguageClient } from "./tsp-language-client.js"; -import { isFile, resolveTypeSpecCli } from "./utils.js"; let client: TspLanguageClient | undefined; /** @@ -103,382 +98,3 @@ function generatSdkTask(): vscode.Task[] { ); return [task]; } - -export async function doEmit( - context: vscode.ExtensionContext, - uri: vscode.Uri, - overallProgress: vscode.Progress<{ message?: string; increment?: number }>, -) { - let tspProjectFolder: string = ""; - if (!uri) { - // const inputText = await vscode.window - // .showInputBox({ prompt: "Choose the tsp project folder or tsp file." }) - // .then(async (inputText) => { - // if (inputText !== undefined) { - // const options = { - // canSelectMany: false, - // openLabel: "Select Folder", - // canSelectFolders: true, - // canSelectFiles: false, - // }; - - // await vscode.window.showOpenDialog(options).then((folderUris) => { - // inputText = folderUris ? folderUris[0].fsPath : ""; - // }); - - // // vscode.window.showOpenDialog(options).then(folderUri => { - // // if (folderUri && folderUri) { - // // vscode.window.showInformationMessage(`You entered: ${inputText} and selected folder: ${folderUri.fsPath}`); - // // } - // // }); - // } - // }); - // tspProjectFolder = inputText ?? ""; - // } - const options = { - canSelectMany: false, - openLabel: "Choose tsp project Directory", - canSelectFolders: true, - canSelectFiles: false, - }; - await await vscode.window.showOpenDialog(options).then((uris) => { - tspProjectFolder = uris ? uris[0].fsPath : ""; - }); - } else { - tspProjectFolder = uri.fsPath; - } - - logger.info(`Select folder ${tspProjectFolder} ...`, [], { - showOutput: true, - showPopup: false, - progress: overallProgress, - }); - const baseDir = (await isFile(tspProjectFolder)) ? dirname(tspProjectFolder) : tspProjectFolder; - /*TODO: check the main.tsp file if it is a project folder. */ - logger.info("Collecting emitters...", [], { - showOutput: false, - showPopup: false, - progress: overallProgress, - }); - const recommended: EmitQuickPickItem[] = [...recommendedEmitters]; - const toQuickPickItem = ( - language: string, - packageName: string, - picked: boolean, - fromConfig: boolean, - ): EmitQuickPickItem => { - const found = recommended.findIndex((ke) => ke.package === packageName); - if (found >= 0) { - const deleted = recommended.splice(found, 1); - deleted[0].picked = picked; - return { ...deleted[0], ...{ picked, fromConfig } }; - } else { - return { language: language, package: packageName, label: packageName, picked, fromConfig }; - } - }; - - /* pre-compile. */ - /* - const emitOnlyInOptions = Object.keys(config.options ?? {}) - .filter((key) => !config.emit?.includes(key)) - .map((e) => toQuickPickItem(e, false, true)); - const emitInEmit = (config.emit ?? []).map((e: any) => toQuickPickItem(e.toString(), true, true)); - - const all = [...emitInEmit, ...emitOnlyInOptions]; - - if (recommended.length > 0) { - all.push({ - package: "", - label: "Recommended Emitters", - kind: QuickPickItemKind.Separator, - fromConfig: false, - }); - } - recommended.forEach((e) => { - all.push(e); - }); - */ - - const all = [...recommendedEmitters].map((e) => - toQuickPickItem(e.language, e.package, true, false), - ); - const selectedEmitters = await vscode.window.showQuickPick(all, { - canPickMany: true, - placeHolder: "Select emitters to run", - }); - - if (!selectedEmitters || selectedEmitters.length === 0) { - logger.info("No emitters selected. Emit canceled.", [], { - showOutput: false, - showPopup: true, - progress: overallProgress, - }); - return; - } - - /* TODO: verify the sdk runtime installation. */ - /* inform to install needed runtime. */ - const { valid, required } = await check(); - if (!valid) { - const toInstall = required.map((e) => e.name).join(", "); - await vscode.window - .showInformationMessage( - `Please install the required runtime for the selected emitters\n\n.net (>= 0.8.0) ${toInstall}`, - "OK", - ) - .then((selection) => { - if (selection === "OK") { - logger.info("Emit canceled.", [], { - showOutput: false, - showPopup: true, - progress: overallProgress, - }); - } - }); - return; - } - - /* config the emitter. */ - await vscode.window - .showInformationMessage("configure the emitters in the tspConfig.yaml", "Yes", "No") - .then(async (selection) => { - if (selection === "Yes") { - const document = await vscode.workspace.openTextDocument( - path.resolve(baseDir, "tspconfig.yaml"), - ); - vscode.window.showTextDocument(document, { - preview: false, - viewColumn: vscode.ViewColumn.Two, - }); - await vscode.window - .showInformationMessage("configure emitter.", "Completed") - .then((selection) => { - if (selection === "Completed") { - vscode.commands.executeCommand("workbench.action.closeActiveEditor"); - } - }); - } else if (selection === "No") { - } - }); - - /*config the output dir for each selected sdk. */ - const root = vscode.Uri.joinPath(context.extensionUri, "outputDir_view"); - const panel = vscode.window.createWebviewPanel( - "Configure output directory", // Identifies the type of the webview. Used internally - "Configure output directory", // Title of the panel displayed to the user - vscode.ViewColumn.Beside, // Editor column to show the new webview panel in - { - retainContextWhenHidden: true, - enableScripts: true, - localResourceRoots: [root], - }, // Webview options. More on these later. - ); - - // And set its HTML content - panel.webview.html = getWebviewContent(selectedEmitters); - - // Handle messages from the webview - const outputDirs = await new Promise<{}>((resolve) => { - panel.webview.onDidReceiveMessage( - async (message) => { - switch (message.command) { - case "submitValues": - resolve(message); - panel.dispose(); - } - }, - undefined, - context.subscriptions, - ); - }); - - const outputDirsRecord: Record = outputDirs; - selectedEmitters.forEach((e) => (e.outputDir = outputDirsRecord[e.language])); - - /* config the output dir one by one. */ - // for (const e of selectedEmitters) { - // const outputDirInput = await vscode.window.showInputBox({ - // placeHolder: `client/${e.language}`, - // value: `client/${e.language}`, - // prompt: `Please provide the output directory for ${e.language} SDK`, - // validateInput: (text: string) => { - // return text.trim() === "" ? "Input cannot be empty" : null; - // }, - // }); - // e.outputDir = outputDirInput; - // } - - /* TODO: verify packages to install. */ - - logger.info("npm install...", [], { - showOutput: false, - showPopup: false, - progress: overallProgress, - }); - - const npmUtil = new NpmUtil(baseDir); - - const packagesToInstall: string[] = []; - for (const e of selectedEmitters) { - /* install emitter package. */ - logger.info(`select ${e.package}`); - const { action, version } = await npmUtil.ensureNpmPackageInstall(e.package, e.version); - /* TODO: check the dependent compiler version. */ - if (action === InstallationAction.Upgrade) { - logger.info(`Upgrading ${e.package} to version ${version}`); - const options = { - ok: `OK (install ${e.package}@${version} by 'npm install'`, - recheck: `Check again (install ${e.package} manually)`, - ignore: `Ignore emitter ${e.label}`, - cancel: "Cancel", - }; - const selected = await vscode.window.showQuickPick(Object.values(options), { - canPickMany: false, - ignoreFocusOut: true, - placeHolder: `Package '${e.package}' needs to be installed for emitting`, - title: `TypeSpec Emit...`, - }); - if (selected === options.ok) { - packagesToInstall.push(`${e.package}@${version}`); - } - } else if (action === InstallationAction.Install) { - // logger.info(`Installing ${e.package} version ${version}`, [], { - // showOutput: true, - // showPopup: true, - // progress: overallProgress, - // }); - let packageFullName = e.package; - if (e.version) { - packageFullName = `${e.package}@${e.version}`; - } - logger.info(`Installing ${packageFullName}`); - packagesToInstall.push(`${packageFullName}`); - } - } - - /* npm install packages. */ - if (packagesToInstall.length > 0) { - logger.info(`Installing ${packagesToInstall.join("\n\n")}`, [], { - showOutput: true, - showPopup: true, - progress: overallProgress, - }); - try { - const npmInstallResult = await npmUtil.npmInstallPackages(packagesToInstall); - logger.info("completed install..."); - if (npmInstallResult.exitCode !== 0) { - logger.error(`Error occurred when installing packages: ${npmInstallResult.stderr}`, [], { - showOutput: true, - showPopup: true, - progress: overallProgress, - }); - return; - } - } catch (err) { - logger.error(`Error occurred when installing packages: ${err}`, [], { - showOutput: true, - showPopup: true, - progress: overallProgress, - }); - return; - } - } - // const npmInstallResult = await npmUtil.npmInstallPackages(packagesToInstall); - // if (npmInstallResult.exitCode !== 0) { - // logger.error(`Error occurred when installing packages: ${npmInstallResult.stderr}`, [], { - // showOutput: true, - // showPopup: true, - // progress: overallProgress, - // }); - // return; - // } - - /* emit */ - logger.info("Emit code ...", [], { - showOutput: false, - showPopup: false, - progress: overallProgress, - }); - /*TODO: resolve the start file. */ - const startFile = `${baseDir}/main.tsp`; - const cli = await resolveTypeSpecCli(baseDir); - if (!cli) { - logger.error("Cannot find TypeSpec CLI. Please install @typespec/compiler. Cancel emit.", [], { - showOutput: true, - showPopup: true, - progress: overallProgress, - }); - return; - } - for (const e of selectedEmitters) { - let outputDir = path.resolve(baseDir, "tsp-output", e.language); - if (e.outputDir) { - if (!path.isAbsolute(e.outputDir)) { - outputDir = path.resolve(baseDir, e.outputDir); - } else { - outputDir = e.outputDir; - } - } - - const options: Record = {}; - options["emitter-output-dir"] = outputDir; - logger.info(`Generate Client SDK for ${e.language} ...`, [], { - showOutput: false, - showPopup: true, - progress: overallProgress, - }); - const compileResult = await compile(cli, startFile, e.package, options); - if (compileResult.exitCode !== 0) { - logger.error( - `Failed to generate Client SDK for ${e.language}. error: ${compileResult.error}`, - [], - { - showOutput: false, - showPopup: true, - progress: overallProgress, - }, - ); - } - logger.info(`complete generating ${e.language} SDK.`); - - /*TODO: build sdk. */ - } -} - -function getWebviewContent(selectedEmitters: EmitQuickPickItem[]): string { - let body = ""; - let script = ""; - let directories = ""; - for (const e of selectedEmitters) { - body += ` -

`; - script += `const ${e.language} = document.getElementById('${e.language}').value;`; - directories += `${e.language}: ${e.language},`; - } - - script += `vscode.postMessage({ command: "submitValues", ${directories} });`; - - return ` - - - - - - Configure Output Directory - - -

Configure output directory for each language SDK

-
- ${body} - -
- - - - `; -} From 31ee9e244feb3772e3b01998d1ef3839afb84844 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Tue, 3 Dec 2024 13:28:36 +0800 Subject: [PATCH 09/42] change to single language generation each run --- packages/typespec-vscode/src/emit/emit.ts | 225 ++++++++-------------- 1 file changed, 78 insertions(+), 147 deletions(-) diff --git a/packages/typespec-vscode/src/emit/emit.ts b/packages/typespec-vscode/src/emit/emit.ts index 6eb14c4553..008debebb3 100644 --- a/packages/typespec-vscode/src/emit/emit.ts +++ b/packages/typespec-vscode/src/emit/emit.ts @@ -105,12 +105,14 @@ export async function doEmit( const all = [...recommendedEmitters].map((e) => toQuickPickItem(e.language, e.package, true, false), ); - const selectedEmitters = await vscode.window.showQuickPick(all, { - canPickMany: true, - placeHolder: "Select emitters to run", + + const selectedEmitter = await vscode.window.showQuickPick(all, { + title: "Select a Language", + canPickMany: false, + placeHolder: "Pick a Language", }); - if (!selectedEmitters || selectedEmitters.length === 0) { + if (!selectedEmitter) { logger.info("No emitters selected. Emit canceled.", [], { showOutput: false, showPopup: true, @@ -141,6 +143,17 @@ export async function doEmit( return; } + /* config the output dir. */ + const outputDirInput = await vscode.window.showInputBox({ + placeHolder: `client/${selectedEmitter.language}`, + value: `client/${selectedEmitter.language}`, + prompt: `Please provide the output directory for ${selectedEmitter.language} SDK`, + validateInput: (text: string) => { + return text.trim() === "" ? "Input cannot be empty" : null; + }, + }); + selectedEmitter.outputDir = outputDirInput; + /* config the emitter. */ await vscode.window .showInformationMessage("configure the emitters in the tspConfig.yaml", "Yes", "No") @@ -164,40 +177,6 @@ export async function doEmit( } }); - /*config the output dir for each selected sdk. */ - const root = vscode.Uri.joinPath(context.extensionUri, "outputDir_view"); - const panel = vscode.window.createWebviewPanel( - "Configure output directory", // Identifies the type of the webview. Used internally - "Configure output directory", // Title of the panel displayed to the user - vscode.ViewColumn.Beside, // Editor column to show the new webview panel in - { - retainContextWhenHidden: true, - enableScripts: true, - localResourceRoots: [root], - }, // Webview options. More on these later. - ); - - // And set its HTML content - panel.webview.html = getWebviewContent(selectedEmitters); - - // Handle messages from the webview - const outputDirs = await new Promise<{}>((resolve) => { - panel.webview.onDidReceiveMessage( - async (message) => { - switch (message.command) { - case "submitValues": - resolve(message); - panel.dispose(); - } - }, - undefined, - context.subscriptions, - ); - }); - - const outputDirsRecord: Record = outputDirs; - selectedEmitters.forEach((e) => (e.outputDir = outputDirsRecord[e.language])); - /* config the output dir one by one. */ // for (const e of selectedEmitters) { // const outputDirInput = await vscode.window.showInputBox({ @@ -222,41 +201,42 @@ export async function doEmit( const npmUtil = new NpmUtil(baseDir); const packagesToInstall: string[] = []; - for (const e of selectedEmitters) { - /* install emitter package. */ - logger.info(`select ${e.package}`); - const { action, version } = await npmUtil.ensureNpmPackageInstall(e.package, e.version); - /* TODO: check the dependent compiler version. */ - if (action === InstallationAction.Upgrade) { - logger.info(`Upgrading ${e.package} to version ${version}`); - const options = { - ok: `OK (install ${e.package}@${version} by 'npm install'`, - recheck: `Check again (install ${e.package} manually)`, - ignore: `Ignore emitter ${e.label}`, - cancel: "Cancel", - }; - const selected = await vscode.window.showQuickPick(Object.values(options), { - canPickMany: false, - ignoreFocusOut: true, - placeHolder: `Package '${e.package}' needs to be installed for emitting`, - title: `TypeSpec Emit...`, - }); - if (selected === options.ok) { - packagesToInstall.push(`${e.package}@${version}`); - } - } else if (action === InstallationAction.Install) { - // logger.info(`Installing ${e.package} version ${version}`, [], { - // showOutput: true, - // showPopup: true, - // progress: overallProgress, - // }); - let packageFullName = e.package; - if (e.version) { - packageFullName = `${e.package}@${e.version}`; - } - logger.info(`Installing ${packageFullName}`); - packagesToInstall.push(`${packageFullName}`); + /* install emitter package. */ + logger.info(`select ${selectedEmitter.package}`); + const { action, version } = await npmUtil.ensureNpmPackageInstall( + selectedEmitter.package, + selectedEmitter.version, + ); + /* TODO: check the dependent compiler version. */ + if (action === InstallationAction.Upgrade) { + logger.info(`Upgrading ${selectedEmitter.package} to version ${version}`); + const options = { + ok: `OK (install ${selectedEmitter.package}@${version} by 'npm install'`, + recheck: `Check again (install ${selectedEmitter.package} manually)`, + ignore: `Ignore emitter ${selectedEmitter.label}`, + cancel: "Cancel", + }; + const selected = await vscode.window.showQuickPick(Object.values(options), { + canPickMany: false, + ignoreFocusOut: true, + placeHolder: `Package '${selectedEmitter.package}' needs to be installed for emitting`, + title: `TypeSpec Emit...`, + }); + if (selected === options.ok) { + packagesToInstall.push(`${selectedEmitter.package}@${version}`); } + } else if (action === InstallationAction.Install) { + // logger.info(`Installing ${e.package} version ${version}`, [], { + // showOutput: true, + // showPopup: true, + // progress: overallProgress, + // }); + let packageFullName = selectedEmitter.package; + if (selectedEmitter.version) { + packageFullName = `${selectedEmitter.package}@${selectedEmitter.version}`; + } + logger.info(`Installing ${packageFullName}`); + packagesToInstall.push(`${packageFullName}`); } /* npm install packages. */ @@ -286,15 +266,6 @@ export async function doEmit( return; } } - // const npmInstallResult = await npmUtil.npmInstallPackages(packagesToInstall); - // if (npmInstallResult.exitCode !== 0) { - // logger.error(`Error occurred when installing packages: ${npmInstallResult.stderr}`, [], { - // showOutput: true, - // showPopup: true, - // progress: overallProgress, - // }); - // return; - // } /* emit */ logger.info("Emit code ...", [], { @@ -313,77 +284,37 @@ export async function doEmit( }); return; } - for (const e of selectedEmitters) { - let outputDir = path.resolve(baseDir, "tsp-output", e.language); - if (e.outputDir) { - if (!path.isAbsolute(e.outputDir)) { - outputDir = path.resolve(baseDir, e.outputDir); - } else { - outputDir = e.outputDir; - } - } - - const options: Record = {}; - options["emitter-output-dir"] = outputDir; - logger.info(`Generate Client SDK for ${e.language} ...`, [], { - showOutput: false, - showPopup: true, - progress: overallProgress, - }); - const compileResult = await compile(cli, startFile, e.package, options); - if (compileResult.exitCode !== 0) { - logger.error( - `Failed to generate Client SDK for ${e.language}. error: ${compileResult.error}`, - [], - { - showOutput: false, - showPopup: true, - progress: overallProgress, - }, - ); + let outputDir = path.resolve(baseDir, "tsp-output", selectedEmitter.language); + if (selectedEmitter.outputDir) { + if (!path.isAbsolute(selectedEmitter.outputDir)) { + outputDir = path.resolve(baseDir, selectedEmitter.outputDir); + } else { + outputDir = selectedEmitter.outputDir; } - logger.info(`complete generating ${e.language} SDK.`); - - /*TODO: build sdk. */ } -} -function getWebviewContent(selectedEmitters: EmitQuickPickItem[]): string { - let body = ""; - let script = ""; - let directories = ""; - for (const e of selectedEmitters) { - body += ` -

`; - script += `const ${e.language} = document.getElementById('${e.language}').value;`; - directories += `${e.language}: ${e.language},`; + const options: Record = {}; + options["emitter-output-dir"] = outputDir; + logger.info(`Generate Client SDK for ${selectedEmitter.language} ...`, [], { + showOutput: false, + showPopup: true, + progress: overallProgress, + }); + const compileResult = await compile(cli, startFile, selectedEmitter.package, options); + if (compileResult.exitCode !== 0) { + logger.error( + `Failed to generate Client SDK for ${selectedEmitter.language}. error: ${compileResult.error}`, + [], + { + showOutput: false, + showPopup: true, + progress: overallProgress, + }, + ); } + logger.info(`complete generating ${selectedEmitter.language} SDK.`); - script += `vscode.postMessage({ command: "submitValues", ${directories} });`; - - return ` - - - - - - Configure Output Directory - - -

Configure output directory for each language SDK

-
- ${body} - -
- - - - `; + /*TODO: build sdk. */ } export async function compile( From 6d604d8148030a37f91b11251649d02560ed670c Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Fri, 6 Dec 2024 13:13:22 +0800 Subject: [PATCH 10/42] select language box --- packages/typespec-vscode/icons/dotnet.svg | 3 + packages/typespec-vscode/icons/java.svg | 3 + packages/typespec-vscode/icons/javascript.svg | 3 + packages/typespec-vscode/icons/openfolder.svg | 7 + packages/typespec-vscode/icons/python.svg | 3 + packages/typespec-vscode/src/const.ts | 8 +- packages/typespec-vscode/src/emit/emit.ts | 295 +++++++++++++----- packages/typespec-vscode/src/emit/emitter.ts | 40 +++ packages/typespec-vscode/src/emit/emitters.ts | 4 +- 9 files changed, 288 insertions(+), 78 deletions(-) create mode 100644 packages/typespec-vscode/icons/dotnet.svg create mode 100644 packages/typespec-vscode/icons/java.svg create mode 100644 packages/typespec-vscode/icons/javascript.svg create mode 100644 packages/typespec-vscode/icons/openfolder.svg create mode 100644 packages/typespec-vscode/icons/python.svg create mode 100644 packages/typespec-vscode/src/emit/emitter.ts diff --git a/packages/typespec-vscode/icons/dotnet.svg b/packages/typespec-vscode/icons/dotnet.svg new file mode 100644 index 0000000000..9c614b11d5 --- /dev/null +++ b/packages/typespec-vscode/icons/dotnet.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/typespec-vscode/icons/java.svg b/packages/typespec-vscode/icons/java.svg new file mode 100644 index 0000000000..649dcb26c8 --- /dev/null +++ b/packages/typespec-vscode/icons/java.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/typespec-vscode/icons/javascript.svg b/packages/typespec-vscode/icons/javascript.svg new file mode 100644 index 0000000000..a400bccfd0 --- /dev/null +++ b/packages/typespec-vscode/icons/javascript.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/typespec-vscode/icons/openfolder.svg b/packages/typespec-vscode/icons/openfolder.svg new file mode 100644 index 0000000000..2e220729d4 --- /dev/null +++ b/packages/typespec-vscode/icons/openfolder.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/packages/typespec-vscode/icons/python.svg b/packages/typespec-vscode/icons/python.svg new file mode 100644 index 0000000000..3490261bed --- /dev/null +++ b/packages/typespec-vscode/icons/python.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/typespec-vscode/src/const.ts b/packages/typespec-vscode/src/const.ts index fa479d79fc..1da0f81724 100644 --- a/packages/typespec-vscode/src/const.ts +++ b/packages/typespec-vscode/src/const.ts @@ -7,8 +7,8 @@ export const enum SettingName { } export const languageEmitterSettingNames: Record = { - net: SettingName.NetEmitter, - jave: SettingName.JavaEmitter, - python: SettingName.PythonEmitter, - js: SettingName.JsEmitter, + DotNet: SettingName.NetEmitter, + Java: SettingName.JavaEmitter, + Python: SettingName.PythonEmitter, + JavaScript: SettingName.JsEmitter, }; diff --git a/packages/typespec-vscode/src/emit/emit.ts b/packages/typespec-vscode/src/emit/emit.ts index 008debebb3..0fd8754ff2 100644 --- a/packages/typespec-vscode/src/emit/emit.ts +++ b/packages/typespec-vscode/src/emit/emit.ts @@ -1,12 +1,11 @@ import path, { dirname } from "path"; -import vscode from "vscode"; +import vscode, { QuickInputButton, Uri } from "vscode"; import { Executable } from "vscode-languageclient/node.js"; import logger from "../log/logger.js"; import { InstallationAction, NpmUtil } from "../npm-utils.js"; import { ExecOutput, executeCommand, isFile, resolveTypeSpecCli } from "../utils.js"; import { EmitQuickPickItem } from "./emit-quick-pick-item.js"; -import { recommendedEmitters } from "./emitters.js"; - +import { clientEmitters, Emitter } from "./emitter.js"; export async function doEmit( context: vscode.ExtensionContext, uri: vscode.Uri, @@ -15,7 +14,7 @@ export async function doEmit( let tspProjectFolder: string = ""; if (!uri) { // const inputText = await vscode.window - // .showInputBox({ prompt: "Choose the tsp project folder or tsp file." }) + // .showInputBox({ prompt: "Choose the TypeSpec project folder or TypeSpec enterpointer file(e.g. main.tsp)." , ignoreFocusOut: true, buttons: { iconPath: Uri.file(context.asAbsolutePath("resources/dark/add.svg")), tooltip: "Select Folder" } }) // .then(async (inputText) => { // if (inputText !== undefined) { // const options = { @@ -38,15 +37,60 @@ export async function doEmit( // }); // tspProjectFolder = inputText ?? ""; // } - const options = { - canSelectMany: false, - openLabel: "Choose tsp project Directory", - canSelectFolders: true, - canSelectFiles: false, - }; - await await vscode.window.showOpenDialog(options).then((uris) => { - tspProjectFolder = uris ? uris[0].fsPath : ""; + class MyButton implements QuickInputButton { + constructor( + public iconPath: { light: Uri; dark: Uri }, + public tooltip: string, + ) {} + } + const openDiaglogButton = new MyButton( + { + dark: Uri.file(context.asAbsolutePath("./icons/openfolder.svg")), + light: Uri.file(context.asAbsolutePath("./icons/openfolder.svg")), + }, + "Browse...", + ); + await new Promise((resolve) => { + const inputBox = vscode.window.createInputBox(); + inputBox.title = "Choose TypeSpec Project Directory"; + inputBox.prompt = "Choose the TypeSpec project."; + inputBox.placeholder = vscode.workspace.workspaceFolders + ? vscode.workspace.workspaceFolders[0].uri.fsPath + : "TypeSpec project folder or TypeSpec enterpointer file(e.g. main.tsp)."; + inputBox.buttons = [openDiaglogButton]; + + inputBox.onDidTriggerButton(async () => { + const options = { + canSelectMany: false, + openLabel: "Choose TypeSpec Project Directory", + canSelectFolders: true, + canSelectFiles: false, + }; + await vscode.window.showOpenDialog(options).then((uris) => { + tspProjectFolder = uris ? uris[0].fsPath : ""; + inputBox.value = tspProjectFolder; + }); + }); + + inputBox.onDidAccept(() => { + const userInput = inputBox.value; + vscode.window.showInformationMessage(`You entered: ${userInput}`); + inputBox.hide(); + resolve(userInput); + }); + inputBox.ignoreFocusOut = true; + inputBox.show(); }); + + // const options = { + // canSelectMany: false, + // openLabel: "Choose TypeSpec Project Directory", + // canSelectFolders: true, + // canSelectFiles: false, + // }; + // await vscode.window.showOpenDialog(options).then((uris) => { + // tspProjectFolder = uris ? uris[0].fsPath : ""; + // }); } else { tspProjectFolder = uri.fsPath; } @@ -58,58 +102,31 @@ export async function doEmit( }); const baseDir = (await isFile(tspProjectFolder)) ? dirname(tspProjectFolder) : tspProjectFolder; /*TODO: check the main.tsp file if it is a project folder. */ - logger.info("Collecting emitters...", [], { + logger.info("select language...", [], { showOutput: false, showPopup: false, progress: overallProgress, }); - const recommended: EmitQuickPickItem[] = [...recommendedEmitters]; - const toQuickPickItem = ( - language: string, - packageName: string, - picked: boolean, - fromConfig: boolean, - ): EmitQuickPickItem => { - const found = recommended.findIndex((ke) => ke.package === packageName); - if (found >= 0) { - const deleted = recommended.splice(found, 1); - deleted[0].picked = picked; - return { ...deleted[0], ...{ picked, fromConfig } }; - } else { - return { language: language, package: packageName, label: packageName, picked, fromConfig }; - } - }; - - /* pre-compile. */ - /* - const emitOnlyInOptions = Object.keys(config.options ?? {}) - .filter((key) => !config.emit?.includes(key)) - .map((e) => toQuickPickItem(e, false, true)); - const emitInEmit = (config.emit ?? []).map((e: any) => toQuickPickItem(e.toString(), true, true)); - const all = [...emitInEmit, ...emitOnlyInOptions]; - - if (recommended.length > 0) { - all.push({ - package: "", - label: "Recommended Emitters", - kind: QuickPickItemKind.Separator, + const toQuickPickItem = (e: Emitter): EmitQuickPickItem => { + return { + language: e.language, + package: e.package, + label: e.language, + detail: `Create ${e.language} sdk from ${e.package}`, + picked: false, fromConfig: false, - }); - } - recommended.forEach((e) => { - all.push(e); - }); - */ + iconPath: Uri.file(context.asAbsolutePath(`./icons/${e.language.toLowerCase()}.svg`)), + }; + }; - const all = [...recommendedEmitters].map((e) => - toQuickPickItem(e.language, e.package, true, false), - ); + const all = [...clientEmitters].map((e) => toQuickPickItem(e)); const selectedEmitter = await vscode.window.showQuickPick(all, { - title: "Select a Language", + title: "Select the Language of the SDK", canPickMany: false, placeHolder: "Pick a Language", + ignoreFocusOut: true, }); if (!selectedEmitter) { @@ -123,7 +140,7 @@ export async function doEmit( /* TODO: verify the sdk runtime installation. */ /* inform to install needed runtime. */ - const { valid, required } = await check(); + const { valid, required } = await check(`${baseDir}/main.tsp`); if (!valid) { const toInstall = required.map((e) => e.name).join(", "); await vscode.window @@ -143,20 +160,116 @@ export async function doEmit( return; } + const configFile = path.resolve(baseDir, "tspconfig.yaml"); + if (!(await isFile(configFile))) { + await vscode.window + .showQuickPick(["Yes", "No"], { + title: "No tspconfig.yaml found in the project directory. Do you want to create one?", + canPickMany: false, + placeHolder: "Pick a option", + ignoreFocusOut: true, + }) + .then(async (selection) => { + if (selection === "Yes") { + /* create tspconfig.yaml */ + const yaml = `emitters:\n - language: ${selectedEmitter.language}\n package: ${selectedEmitter.package}\n outputDir: client/${selectedEmitter.language}`; + await vscode.workspace.fs.writeFile( + vscode.Uri.file(path.resolve(baseDir, "tspconfig.yaml")), + Buffer.from(yaml), + ); + const document = await vscode.workspace.openTextDocument( + vscode.Uri.file(path.resolve(baseDir, "tspconfig.yaml")), + ); + vscode.window.showTextDocument(document, { + preview: false, + viewColumn: vscode.ViewColumn.Two, + }); + } + }); + // await vscode.window + // .showInformationMessage( + // `No tspconfig.yaml found in the project directory. Do you want to create one?`, + // "Yes", + // "No", + // ) + // .then(async (selection) => { + // if (selection === "Yes") { + // /* create tspconfig.yaml */ + // const yaml = `emitters:\n - language: ${selectedEmitter.language}\n package: ${selectedEmitter.package}\n outputDir: client/${selectedEmitter.language}`; + // await vscode.workspace.fs.writeFile( + // vscode.Uri.file(path.resolve(baseDir, "tspconfig.yaml")), + // Buffer.from(yaml), + // ); + // const document = await vscode.workspace.openTextDocument( + // vscode.Uri.file(path.resolve(baseDir, "tspconfig.yaml")), + // ); + // vscode.window.showTextDocument(document, { + // preview: false, + // viewColumn: vscode.ViewColumn.Two, + // }); + // } + // }); + } else { + /* check the emitter in the tspConfig.yaml */ + const document = await vscode.workspace.openTextDocument(configFile); + document.getText(); + } + /* config the output dir. */ const outputDirInput = await vscode.window.showInputBox({ + title: `Configure output directory for ${selectedEmitter.language}`, placeHolder: `client/${selectedEmitter.language}`, value: `client/${selectedEmitter.language}`, prompt: `Please provide the output directory for ${selectedEmitter.language} SDK`, validateInput: (text: string) => { return text.trim() === "" ? "Input cannot be empty" : null; }, + ignoreFocusOut: true, }); selectedEmitter.outputDir = outputDirInput; - /* config the emitter. */ + // /* config the emitter. */ + // class MyButton implements QuickInputButton { + // constructor( + // public iconPath: { light: Uri; dark: Uri }, + // public tooltip: string, + // ) {} + // } + // const createResourceGroupButton = new MyButton( + // { + // dark: Uri.file(context.asAbsolutePath("resources/dark/add.svg")), + // light: Uri.file(context.asAbsolutePath("resources/light/add.svg")), + // }, + // "Create Resource Group", + // ); + + // await new Promise((resolve) => { + // const inputBox = vscode.window.createInputBox(); + // inputBox.prompt = "Enter your input"; + // inputBox.buttons = [vscode.QuickInputButtons.Back, createResourceGroupButton]; + + // inputBox.onDidTriggerButton(() => { + // vscode.window.showInformationMessage("Button clicked!"); + // inputBox.hide(); + // }); + + // inputBox.onDidAccept(() => { + // const userInput = inputBox.value; + // vscode.window.showInformationMessage(`You entered: ${userInput}`); + // inputBox.hide(); + // resolve(userInput); + // }); + // inputBox.ignoreFocusOut = true; + // inputBox.show(); + // }); + await vscode.window - .showInformationMessage("configure the emitters in the tspConfig.yaml", "Yes", "No") + .showQuickPick(["Yes", "No"], { + title: "configure the emitters in the tspConfig.yaml?", + canPickMany: false, + placeHolder: "Pick a option", + ignoreFocusOut: true, + }) .then(async (selection) => { if (selection === "Yes") { const document = await vscode.workspace.openTextDocument( @@ -167,28 +280,46 @@ export async function doEmit( viewColumn: vscode.ViewColumn.Two, }); await vscode.window - .showInformationMessage("configure emitter.", "Completed") + .showQuickPick(["Completed"], { + title: "Is emitter configuration completed?", + canPickMany: false, + placeHolder: "Pick a option", + ignoreFocusOut: true, + }) .then((selection) => { if (selection === "Completed") { vscode.commands.executeCommand("workbench.action.closeActiveEditor"); } }); - } else if (selection === "No") { + // await vscode.window.showInformationMessage("configure emitter.", "Completed").then((selection) => { + // if (selection === "Completed") { + // vscode.commands.executeCommand("workbench.action.closeActiveEditor"); + // } + // }); } }); - /* config the output dir one by one. */ - // for (const e of selectedEmitters) { - // const outputDirInput = await vscode.window.showInputBox({ - // placeHolder: `client/${e.language}`, - // value: `client/${e.language}`, - // prompt: `Please provide the output directory for ${e.language} SDK`, - // validateInput: (text: string) => { - // return text.trim() === "" ? "Input cannot be empty" : null; - // }, + // await vscode.window + // .showInformationMessage("configure the emitters in the tspConfig.yaml", "Yes", "No") + // .then(async (selection) => { + // if (selection === "Yes") { + // const document = await vscode.workspace.openTextDocument( + // path.resolve(baseDir, "tspconfig.yaml"), + // ); + // vscode.window.showTextDocument(document, { + // preview: false, + // viewColumn: vscode.ViewColumn.Two, + // }); + // await vscode.window + // .showInformationMessage("configure emitter.", "Completed") + // .then((selection) => { + // if (selection === "Completed") { + // vscode.commands.executeCommand("workbench.action.closeActiveEditor"); + // } + // }); + // } else if (selection === "No") { + // } // }); - // e.outputDir = outputDirInput; - // } /* TODO: verify packages to install. */ @@ -306,13 +437,17 @@ export async function doEmit( `Failed to generate Client SDK for ${selectedEmitter.language}. error: ${compileResult.error}`, [], { - showOutput: false, + showOutput: true, showPopup: true, progress: overallProgress, }, ); } - logger.info(`complete generating ${selectedEmitter.language} SDK.`); + logger.info(`complete generating ${selectedEmitter.language} SDK.`, [], { + showOutput: true, + showPopup: true, + progress: overallProgress, + }); /*TODO: build sdk. */ } @@ -339,9 +474,25 @@ export async function compile( }); } -export async function check(): Promise<{ +export async function check(startFile: string): Promise<{ valid: boolean; required: { name: string; version: string }[]; }> { + // await new Promise((resolve) => { + // setTimeout(resolve, 180000); + // logger.info(`complete runtime check.`); + // }); + const cli = await resolveTypeSpecCli(dirname(startFile)); + if (!cli) { + return { valid: true, required: [] }; + } + const args: string[] = cli.args ?? []; + args.push("compile"); + args.push(startFile); + args.push("--no-emit"); + await executeCommand(cli.command, args, { + cwd: dirname(startFile), + }); + logger.info(`complete runtime check.`); return { valid: true, required: [] }; } diff --git a/packages/typespec-vscode/src/emit/emitter.ts b/packages/typespec-vscode/src/emit/emitter.ts new file mode 100644 index 0000000000..bf755034d6 --- /dev/null +++ b/packages/typespec-vscode/src/emit/emitter.ts @@ -0,0 +1,40 @@ +import vscode from "vscode"; +import { languageEmitterSettingNames } from "../const.js"; + +export enum EmitterKind { + Schema = "schema", + Client = "client", + Server = "server", +} + +export interface Emitter { + language: string; + package: string; + version?: string; + kind: EmitterKind; +} + +const extensionConfig = vscode.workspace.getConfiguration(); + +function getEmitter(language: string): Emitter { + const packageFullName: string = + extensionConfig.get(languageEmitterSettingNames[language] ?? "") ?? ""; + const index = packageFullName.lastIndexOf("@"); + let version = undefined; + let packageName = packageFullName; + if (index !== -1 && index !== 0) { + version = packageFullName.substring(index + 1); + packageName = packageFullName.substring(0, index); + } + + return { + language: language, + package: packageName, + version: version, + kind: EmitterKind.Client, + }; +} + +export const clientEmitters: ReadonlyArray = Object.keys(languageEmitterSettingNames).map( + (lang) => getEmitter(lang), +); diff --git a/packages/typespec-vscode/src/emit/emitters.ts b/packages/typespec-vscode/src/emit/emitters.ts index e516a8b814..2faffb2dfa 100644 --- a/packages/typespec-vscode/src/emit/emitters.ts +++ b/packages/typespec-vscode/src/emit/emitters.ts @@ -10,8 +10,8 @@ function getEmitterPickItem(language: string): EmitQuickPickItem { return { language: language, package: packageName, - label: `Generate ${language} client sdk`, - description: `from emitter ${packageName}`, + label: `${language}`, + detail: `Create ${language} sdk from ${packageName}`, fromConfig: false, }; } From 406e7f9d598539e766b758bda94b2e53387e5f0b Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Fri, 6 Dec 2024 15:01:18 +0800 Subject: [PATCH 11/42] remove unused code --- packages/typespec-vscode/src/emit/emit.ts | 125 +--------------------- 1 file changed, 1 insertion(+), 124 deletions(-) diff --git a/packages/typespec-vscode/src/emit/emit.ts b/packages/typespec-vscode/src/emit/emit.ts index 0fd8754ff2..c77b0dbaee 100644 --- a/packages/typespec-vscode/src/emit/emit.ts +++ b/packages/typespec-vscode/src/emit/emit.ts @@ -13,30 +13,6 @@ export async function doEmit( ) { let tspProjectFolder: string = ""; if (!uri) { - // const inputText = await vscode.window - // .showInputBox({ prompt: "Choose the TypeSpec project folder or TypeSpec enterpointer file(e.g. main.tsp)." , ignoreFocusOut: true, buttons: { iconPath: Uri.file(context.asAbsolutePath("resources/dark/add.svg")), tooltip: "Select Folder" } }) - // .then(async (inputText) => { - // if (inputText !== undefined) { - // const options = { - // canSelectMany: false, - // openLabel: "Select Folder", - // canSelectFolders: true, - // canSelectFiles: false, - // }; - - // await vscode.window.showOpenDialog(options).then((folderUris) => { - // inputText = folderUris ? folderUris[0].fsPath : ""; - // }); - - // // vscode.window.showOpenDialog(options).then(folderUri => { - // // if (folderUri && folderUri) { - // // vscode.window.showInformationMessage(`You entered: ${inputText} and selected folder: ${folderUri.fsPath}`); - // // } - // // }); - // } - // }); - // tspProjectFolder = inputText ?? ""; - // } class MyButton implements QuickInputButton { constructor( public iconPath: { light: Uri; dark: Uri }, @@ -81,16 +57,6 @@ export async function doEmit( inputBox.ignoreFocusOut = true; inputBox.show(); }); - - // const options = { - // canSelectMany: false, - // openLabel: "Choose TypeSpec Project Directory", - // canSelectFolders: true, - // canSelectFiles: false, - // }; - // await vscode.window.showOpenDialog(options).then((uris) => { - // tspProjectFolder = uris ? uris[0].fsPath : ""; - // }); } else { tspProjectFolder = uri.fsPath; } @@ -186,29 +152,6 @@ export async function doEmit( }); } }); - // await vscode.window - // .showInformationMessage( - // `No tspconfig.yaml found in the project directory. Do you want to create one?`, - // "Yes", - // "No", - // ) - // .then(async (selection) => { - // if (selection === "Yes") { - // /* create tspconfig.yaml */ - // const yaml = `emitters:\n - language: ${selectedEmitter.language}\n package: ${selectedEmitter.package}\n outputDir: client/${selectedEmitter.language}`; - // await vscode.workspace.fs.writeFile( - // vscode.Uri.file(path.resolve(baseDir, "tspconfig.yaml")), - // Buffer.from(yaml), - // ); - // const document = await vscode.workspace.openTextDocument( - // vscode.Uri.file(path.resolve(baseDir, "tspconfig.yaml")), - // ); - // vscode.window.showTextDocument(document, { - // preview: false, - // viewColumn: vscode.ViewColumn.Two, - // }); - // } - // }); } else { /* check the emitter in the tspConfig.yaml */ const document = await vscode.workspace.openTextDocument(configFile); @@ -228,41 +171,7 @@ export async function doEmit( }); selectedEmitter.outputDir = outputDirInput; - // /* config the emitter. */ - // class MyButton implements QuickInputButton { - // constructor( - // public iconPath: { light: Uri; dark: Uri }, - // public tooltip: string, - // ) {} - // } - // const createResourceGroupButton = new MyButton( - // { - // dark: Uri.file(context.asAbsolutePath("resources/dark/add.svg")), - // light: Uri.file(context.asAbsolutePath("resources/light/add.svg")), - // }, - // "Create Resource Group", - // ); - - // await new Promise((resolve) => { - // const inputBox = vscode.window.createInputBox(); - // inputBox.prompt = "Enter your input"; - // inputBox.buttons = [vscode.QuickInputButtons.Back, createResourceGroupButton]; - - // inputBox.onDidTriggerButton(() => { - // vscode.window.showInformationMessage("Button clicked!"); - // inputBox.hide(); - // }); - - // inputBox.onDidAccept(() => { - // const userInput = inputBox.value; - // vscode.window.showInformationMessage(`You entered: ${userInput}`); - // inputBox.hide(); - // resolve(userInput); - // }); - // inputBox.ignoreFocusOut = true; - // inputBox.show(); - // }); - + /* config the emitter in the tspConfig.yaml */ await vscode.window .showQuickPick(["Yes", "No"], { title: "configure the emitters in the tspConfig.yaml?", @@ -291,36 +200,9 @@ export async function doEmit( vscode.commands.executeCommand("workbench.action.closeActiveEditor"); } }); - // await vscode.window.showInformationMessage("configure emitter.", "Completed").then((selection) => { - // if (selection === "Completed") { - // vscode.commands.executeCommand("workbench.action.closeActiveEditor"); - // } - // }); } }); - // await vscode.window - // .showInformationMessage("configure the emitters in the tspConfig.yaml", "Yes", "No") - // .then(async (selection) => { - // if (selection === "Yes") { - // const document = await vscode.workspace.openTextDocument( - // path.resolve(baseDir, "tspconfig.yaml"), - // ); - // vscode.window.showTextDocument(document, { - // preview: false, - // viewColumn: vscode.ViewColumn.Two, - // }); - // await vscode.window - // .showInformationMessage("configure emitter.", "Completed") - // .then((selection) => { - // if (selection === "Completed") { - // vscode.commands.executeCommand("workbench.action.closeActiveEditor"); - // } - // }); - // } else if (selection === "No") { - // } - // }); - /* TODO: verify packages to install. */ logger.info("npm install...", [], { @@ -357,11 +239,6 @@ export async function doEmit( packagesToInstall.push(`${selectedEmitter.package}@${version}`); } } else if (action === InstallationAction.Install) { - // logger.info(`Installing ${e.package} version ${version}`, [], { - // showOutput: true, - // showPopup: true, - // progress: overallProgress, - // }); let packageFullName = selectedEmitter.package; if (selectedEmitter.version) { packageFullName = `${selectedEmitter.package}@${selectedEmitter.version}`; From a93c8b879356903a3ae9c903da0257f30c40be94 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Sun, 8 Dec 2024 19:20:55 +0800 Subject: [PATCH 12/42] add validation of typespec project selection input box --- packages/typespec-vscode/src/emit/emit.ts | 65 +++++++++++++++++++---- 1 file changed, 54 insertions(+), 11 deletions(-) diff --git a/packages/typespec-vscode/src/emit/emit.ts b/packages/typespec-vscode/src/emit/emit.ts index c77b0dbaee..ac2f730742 100644 --- a/packages/typespec-vscode/src/emit/emit.ts +++ b/packages/typespec-vscode/src/emit/emit.ts @@ -1,3 +1,4 @@ +// import { TypeSpecConfig } from "@typespec/compiler"; import path, { dirname } from "path"; import vscode, { QuickInputButton, Uri } from "vscode"; import { Executable } from "vscode-languageclient/node.js"; @@ -26,15 +27,20 @@ export async function doEmit( }, "Browse...", ); - await new Promise((resolve) => { + tspProjectFolder = await new Promise((resolve) => { const inputBox = vscode.window.createInputBox(); inputBox.title = "Choose TypeSpec Project Directory"; inputBox.prompt = "Choose the TypeSpec project."; + inputBox.value = vscode.workspace.workspaceFolders + ? vscode.workspace.workspaceFolders[0].uri.fsPath + : ""; inputBox.placeholder = vscode.workspace.workspaceFolders ? vscode.workspace.workspaceFolders[0].uri.fsPath : "TypeSpec project folder or TypeSpec enterpointer file(e.g. main.tsp)."; inputBox.buttons = [openDiaglogButton]; - + const validateInput = (text: string) => { + return text.trim() === "" ? "Please choose Typespec folder" : null; + }; inputBox.onDidTriggerButton(async () => { const options = { canSelectMany: false, @@ -43,16 +49,34 @@ export async function doEmit( canSelectFiles: false, }; await vscode.window.showOpenDialog(options).then((uris) => { - tspProjectFolder = uris ? uris[0].fsPath : ""; - inputBox.value = tspProjectFolder; + // tspProjectFolder = uris ? uris[0].fsPath : ""; + // inputBox.value = tspProjectFolder; + inputBox.value = uris ? uris[0].fsPath : ""; + inputBox.validationMessage = undefined; }); }); + inputBox.onDidChangeValue(async (text) => { + const validate = validateInput(text); + if (validate !== null) { + inputBox.validationMessage = validate; + } else { + inputBox.validationMessage = undefined; + } + }); + inputBox.onDidAccept(() => { const userInput = inputBox.value; - vscode.window.showInformationMessage(`You entered: ${userInput}`); - inputBox.hide(); - resolve(userInput); + const validate = validateInput(userInput); + if (validate === null) { + resolve(userInput); + vscode.window.showInformationMessage(`You entered: ${userInput}`); + } else { + inputBox.validationMessage = validate; + } + // vscode.window.showInformationMessage(`You entered: ${userInput}`); + // inputBox.hide(); + // resolve(userInput); }); inputBox.ignoreFocusOut = true; inputBox.show(); @@ -127,6 +151,7 @@ export async function doEmit( } const configFile = path.resolve(baseDir, "tspconfig.yaml"); + // let typespecConfig = undefined; if (!(await isFile(configFile))) { await vscode.window .showQuickPick(["Yes", "No"], { @@ -138,7 +163,7 @@ export async function doEmit( .then(async (selection) => { if (selection === "Yes") { /* create tspconfig.yaml */ - const yaml = `emitters:\n - language: ${selectedEmitter.language}\n package: ${selectedEmitter.package}\n outputDir: client/${selectedEmitter.language}`; + const yaml = `options:\n "${selectedEmitter.package}":\n emitter-output-dir: client/${selectedEmitter.language}`; await vscode.workspace.fs.writeFile( vscode.Uri.file(path.resolve(baseDir, "tspconfig.yaml")), Buffer.from(yaml), @@ -154,15 +179,23 @@ export async function doEmit( }); } else { /* check the emitter in the tspConfig.yaml */ - const document = await vscode.workspace.openTextDocument(configFile); - document.getText(); + // const document = await vscode.workspace.openTextDocument(configFile); + // const doc = parseDocument(document.getText(), { + // prettyErrors: false, // We are handling the error display ourself to be consistent in the style. + // }); + // typespecConfig = doc.toJSON(); } /* config the output dir. */ + // const optionsInConfig = typespecConfig?.options + // ? typespecConfig?.options[`${selectedEmitter.package}`] + // : undefined; + // const outputDirInConfig = optionsInConfig ? optionsInConfig["emitter-output-dir"] : undefined; + const outputDirInConfig = undefined; const outputDirInput = await vscode.window.showInputBox({ title: `Configure output directory for ${selectedEmitter.language}`, placeHolder: `client/${selectedEmitter.language}`, - value: `client/${selectedEmitter.language}`, + value: outputDirInConfig ?? `client/${selectedEmitter.language}`, prompt: `Please provide the output directory for ${selectedEmitter.language} SDK`, validateInput: (text: string) => { return text.trim() === "" ? "Input cannot be empty" : null; @@ -170,6 +203,16 @@ export async function doEmit( ignoreFocusOut: true, }); selectedEmitter.outputDir = outputDirInput; + // if (optionsInConfig) { + // optionsInConfig["emitter-output-dir"] = outputDirInput; + // typespecConfig!.options![`${selectedEmitter.package}`] = optionsInConfig; + // } + + /* save emitter config */ + // await vscode.workspace.fs.writeFile( + // vscode.Uri.file(path.resolve(baseDir, "tspconfig.yaml")), + // Buffer.from(stringify(typespecConfig)), + // ); /* config the emitter in the tspConfig.yaml */ await vscode.window From 4ba9a3a475a9e7e70d58256ee92add3988887fd5 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Sun, 8 Dec 2024 19:39:11 +0800 Subject: [PATCH 13/42] transfer logs from spawn --- packages/typespec-vscode/src/utils.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/typespec-vscode/src/utils.ts b/packages/typespec-vscode/src/utils.ts index 77de531843..f3da5686bc 100644 --- a/packages/typespec-vscode/src/utils.ts +++ b/packages/typespec-vscode/src/utils.ts @@ -129,12 +129,10 @@ export async function executeCommand( logger.error(`Error: ${error.message}`); errMessage += error.message; retcode = error.code ?? 0; - return; } if (stderr) { logger.error(`Stderr: ${stderr}`); errMessage += stderr; - return; } stdoutstr += stdout; logger.info(`Stdout: ${stdout}`); From ca87267a0a790dd6ab85b52aacd51dd967079254 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Mon, 9 Dec 2024 13:46:26 +0800 Subject: [PATCH 14/42] transport log out when execute command --- packages/typespec-vscode/src/emit/emit.ts | 15 +- packages/typespec-vscode/src/npm-utils.ts | 10 +- .../typespec-vscode/src/typespec-utils.ts | 19 ++ packages/typespec-vscode/src/utils.ts | 177 ++++++++++++++---- 4 files changed, 184 insertions(+), 37 deletions(-) create mode 100644 packages/typespec-vscode/src/typespec-utils.ts diff --git a/packages/typespec-vscode/src/emit/emit.ts b/packages/typespec-vscode/src/emit/emit.ts index ac2f730742..37b62f5e2a 100644 --- a/packages/typespec-vscode/src/emit/emit.ts +++ b/packages/typespec-vscode/src/emit/emit.ts @@ -4,6 +4,7 @@ import vscode, { QuickInputButton, Uri } from "vscode"; import { Executable } from "vscode-languageclient/node.js"; import logger from "../log/logger.js"; import { InstallationAction, NpmUtil } from "../npm-utils.js"; +import { toError, toOutput } from "../typespec-utils.js"; import { ExecOutput, executeCommand, isFile, resolveTypeSpecCli } from "../utils.js"; import { EmitQuickPickItem } from "./emit-quick-pick-item.js"; import { clientEmitters, Emitter } from "./emitter.js"; @@ -298,7 +299,10 @@ export async function doEmit( progress: overallProgress, }); try { - const npmInstallResult = await npmUtil.npmInstallPackages(packagesToInstall); + const npmInstallResult = await npmUtil.npmInstallPackages(packagesToInstall, undefined, { + onStdioOut: toOutput, + onStdioError: toError, + }); logger.info("completed install..."); if (npmInstallResult.exitCode !== 0) { logger.error(`Error occurred when installing packages: ${npmInstallResult.stderr}`, [], { @@ -309,7 +313,7 @@ export async function doEmit( return; } } catch (err) { - logger.error(`Error occurred when installing packages: ${err}`, [], { + logger.error(`Exception occurred when installing packages: ${err}`, [], { showOutput: true, showPopup: true, progress: overallProgress, @@ -363,6 +367,13 @@ export async function doEmit( }, ); } + + logger.info(`${compileResult.stdout}.`, [], { + showOutput: true, + showPopup: false, + progress: overallProgress, + }); + logger.info(`complete generating ${selectedEmitter.language} SDK.`, [], { showOutput: true, showPopup: true, diff --git a/packages/typespec-vscode/src/npm-utils.ts b/packages/typespec-vscode/src/npm-utils.ts index 7e1cb34b80..9d8d63bfed 100644 --- a/packages/typespec-vscode/src/npm-utils.ts +++ b/packages/typespec-vscode/src/npm-utils.ts @@ -1,7 +1,7 @@ import fs from "fs"; import path from "path"; import logger from "./log/logger.js"; -import { ExecOutput, loadModule, promisifyExec } from "./utils.js"; +import { ExecOutput, executionEvents, loadModule, promisifyExec } from "./utils.js"; export enum InstallationAction { Install = "Install", @@ -25,7 +25,11 @@ export class NpmUtil { this.cwd = cwd; } - public async npmInstallPackages(packages: string[] = [], options: any = {}): Promise { + public async npmInstallPackages( + packages: string[] = [], + options: any = {}, + on?: executionEvents, + ): Promise { let command; if (packages.length > 0) { command = `npm install ${packages.join(" ")}`; @@ -33,7 +37,7 @@ export class NpmUtil { command = `npm install`; } - return await promisifyExec(command, [], { ...options, cwd: this.cwd }); + return await promisifyExec(command, [], { ...options, cwd: this.cwd }, on); //return spawnExecution("npm", ["install", ...packages], { ...options, cwd: this.cwd }); } diff --git a/packages/typespec-vscode/src/typespec-utils.ts b/packages/typespec-vscode/src/typespec-utils.ts new file mode 100644 index 0000000000..17361a61a0 --- /dev/null +++ b/packages/typespec-vscode/src/typespec-utils.ts @@ -0,0 +1,19 @@ +import logger from "./log/logger.js"; + +export const toOutput = (str: string) => { + str + .trim() + .split("\n") + .forEach((line) => logger.info(line)); +}; +export const toError = (str: string) => { + str + .trim() + .split("\n") + .forEach((line) => + logger.error(line, [], { + showOutput: true, + showPopup: false, + }), + ); +}; diff --git a/packages/typespec-vscode/src/utils.ts b/packages/typespec-vscode/src/utils.ts index f3da5686bc..9952d35692 100644 --- a/packages/typespec-vscode/src/utils.ts +++ b/packages/typespec-vscode/src/utils.ts @@ -2,7 +2,6 @@ import type { ModuleResolutionResult, ResolveModuleHost } from "@typespec/compil import { exec, spawn, SpawnOptions } from "child_process"; import { readFile, realpath, stat } from "fs/promises"; import path, { dirname, normalize, resolve } from "path"; -import { promisify } from "util"; import { Executable } from "vscode-languageclient/node.js"; import logger from "./log/logger.js"; @@ -113,10 +112,18 @@ export interface ExecOutput { spawnOptions: SpawnOptions; } +export interface executionEvents { + onStdioOut?: (data: string) => void; + onStdioError?: (error: string) => void; + onError?: (error: any, stdout: string, stderr: string) => void; + onExit?: (code: number | null, stdout: string, stderror: string) => void; +} + export async function executeCommand( command: string, args: string[], options: any, + on?: executionEvents, ): Promise { let stdoutstr: string = ""; let errMessage: string = ""; @@ -124,18 +131,58 @@ export async function executeCommand( if (args.length > 0) { command = `${command} ${args.join(" ")}`; } - exec(command, options, (error, stdout, stderr) => { - if (error) { - logger.error(`Error: ${error.message}`); - errMessage += error.message; - retcode = error.code ?? 0; - } - if (stderr) { - logger.error(`Stderr: ${stderr}`); - errMessage += stderr; - } - stdoutstr += stdout; - logger.info(`Stdout: ${stdout}`); + // exec(command, options, (error, stdout, stderr) => { + // if (error) { + // // logger.error(`Error: ${error.message}`); + // errMessage += error.message; + // retcode = error.code ?? 0; + // if (on && on.onError) { + // on.onError(error, stdout.toString(), stderr.toString()); + // } + // } + // if (stderr) { + // // logger.error(`Stderr: ${stderr}`); + // errMessage += stderr; + // if (on && on.onStdioError) { + // on.onStdioError(stderr.toString()); + // } + // } + // stdoutstr += stdout; + // on?.onStdioOut?.(stdout.toString()); + // // logger.info(`Stdout: ${stdout}`); + // }); + + const child = exec(command, options); + child.stdout?.on("data", (data) => { + // logger.info(`Stdout: ${data}`); + stdoutstr += data.toString(); + on?.onStdioOut?.(data.toString()); + }); + + child.stderr?.on("data", (data) => { + errMessage += data.toString(); + on?.onStdioError?.(data.toString()); + }); + + if (on && on.onError) { + child.on("error", (error: any) => { + on.onError!(error, stdoutstr, errMessage); + }); + } + if (on && on.onExit) { + child.on("exit", (code) => { + on.onExit!(code, stdoutstr, errMessage); + }); + } + + child.on("close", (code) => { + // logger.info(`Child process exited with code ${code}`); + retcode = code ?? 0; + }); + + child.on("exit", (code) => { + // logger.info(`Child process exited with code ${code}`); + retcode = code ?? 0; }); return { @@ -151,36 +198,84 @@ export async function promisifyExec( command: string, args: string[], options: any, + on?: executionEvents, ): Promise { - let stdoutstr: string = ""; - let errMessage: string = ""; + // let stdoutstr: string = ""; + // let errMessage: string = ""; + // let retcode = 0; + // if (args.length > 0) { + // command = `${command} ${args.join(" ")}`; + // } + + // const execPromise = promisify(exec); + + // const { stdout, stderr } = await execPromise(command, options); + // if (stdout) { + // stdoutstr += stdout; + // on?.onStdioOut?.(stdout.toString()); + // } + // if (stderr) { + // errMessage += stderr; + // // retcode = 1; + // on?.onStdioError?.(stderr.toString()); + // } + // logger.info(`Stdout: ${stdout}`); + // return { + // stdout: stdoutstr, + // stderr: errMessage, + // exitCode: retcode, + // error: errMessage, + // spawnOptions: options, + // }; + + let stdout: string = ""; + let stderr: string = ""; let retcode = 0; if (args.length > 0) { command = `${command} ${args.join(" ")}`; } - - const execPromise = promisify(exec); - - const { stdout, stderr } = await execPromise(command, options); - if (stdout) stdoutstr += stdout; - if (stderr) { - errMessage += stderr; - retcode = 1; - } - - return { - stdout: stdoutstr, - stderr: errMessage, - exitCode: retcode, - error: errMessage, - spawnOptions: options, - }; + return await new Promise((resolve, reject) => { + const child = exec(command, options); + child.stdout?.on("data", (data) => { + // logger.info(`Stdout: ${data}`); + stdout += data.toString(); + on?.onStdioOut?.(data.toString()); + }); + child.on("error", (error) => { + stderr += error.message; + }); + child.stderr?.on("data", (data) => { + stderr += data.toString(); + on?.onStdioError?.(data.toString()); + }); + child.on("close", (code) => { + retcode = code ?? 0; + resolve({ + stdout: stdout, + stderr: stderr, + exitCode: retcode, + error: stderr, + spawnOptions: options, + }); + }); + child.on("exit", (code) => { + retcode = code ?? 0; + resolve({ + stdout: stdout, + stderr: stderr, + exitCode: retcode, + error: stderr, + spawnOptions: options, + }); + }); + }); } export async function spawnExecution( command: string, args: string[], options: any, + on?: executionEvents, ): Promise { let stdout = ""; let stderr = ""; @@ -191,17 +286,35 @@ export async function spawnExecution( child.stdout.on("data", (data) => { // logger.info(`Stdout: ${data}`); stdout += data.toString(); + on?.onStdioOut?.(data.toString()); }); child.stderr.on("data", (data) => { stderr += data.toString(); + on?.onStdioError?.(data.toString()); }); + if (on && on.onError) { + child.on("error", (error: any) => { + on.onError!(error, stdout, stderr); + }); + } + if (on && on.onExit) { + child.on("exit", (code) => { + on.onExit!(code, stdout, stderr); + }); + } + child.on("close", (code) => { // logger.info(`Child process exited with code ${code}`); retcode = code ?? 0; }); + child.on("exit", (code) => { + // logger.info(`Child process exited with code ${code}`); + retcode = code ?? 0; + }); + return { stdout: stdout, stderr: stderr, From a14b47e9cb0cc2e87852a7d6a6b7e6b92c3ddd1e Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Mon, 9 Dec 2024 15:10:48 +0800 Subject: [PATCH 15/42] validate the typespec project --- packages/typespec-vscode/src/emit/emit.ts | 70 ++++++----- .../typespec-vscode/src/typespec-utils.ts | 52 +++++++++ packages/typespec-vscode/src/utils.ts | 109 +++--------------- 3 files changed, 110 insertions(+), 121 deletions(-) diff --git a/packages/typespec-vscode/src/emit/emit.ts b/packages/typespec-vscode/src/emit/emit.ts index 37b62f5e2a..b93257ac47 100644 --- a/packages/typespec-vscode/src/emit/emit.ts +++ b/packages/typespec-vscode/src/emit/emit.ts @@ -4,8 +4,8 @@ import vscode, { QuickInputButton, Uri } from "vscode"; import { Executable } from "vscode-languageclient/node.js"; import logger from "../log/logger.js"; import { InstallationAction, NpmUtil } from "../npm-utils.js"; -import { toError, toOutput } from "../typespec-utils.js"; -import { ExecOutput, executeCommand, isFile, resolveTypeSpecCli } from "../utils.js"; +import { getMainTspFile, resolveTypeSpecCli, toError, toOutput } from "../typespec-utils.js"; +import { ExecOutput, executeCommand, isFile, promisifyExec } from "../utils.js"; import { EmitQuickPickItem } from "./emit-quick-pick-item.js"; import { clientEmitters, Emitter } from "./emitter.js"; export async function doEmit( @@ -39,8 +39,15 @@ export async function doEmit( ? vscode.workspace.workspaceFolders[0].uri.fsPath : "TypeSpec project folder or TypeSpec enterpointer file(e.g. main.tsp)."; inputBox.buttons = [openDiaglogButton]; - const validateInput = (text: string) => { - return text.trim() === "" ? "Please choose Typespec folder" : null; + const validateInput = async (text: string) => { + if (text.trim() === "") { + return "Please choose Typespec folder"; + } + const start = await getMainTspFile(text); + if (!start) { + return "Cannot find main tsp file in the project. Please select a valid TypeSpec project folder."; + } + return null; }; inputBox.onDidTriggerButton(async () => { const options = { @@ -58,7 +65,7 @@ export async function doEmit( }); inputBox.onDidChangeValue(async (text) => { - const validate = validateInput(text); + const validate = await validateInput(text); if (validate !== null) { inputBox.validationMessage = validate; } else { @@ -66,18 +73,14 @@ export async function doEmit( } }); - inputBox.onDidAccept(() => { + inputBox.onDidAccept(async () => { const userInput = inputBox.value; - const validate = validateInput(userInput); + const validate = await validateInput(userInput); if (validate === null) { resolve(userInput); - vscode.window.showInformationMessage(`You entered: ${userInput}`); } else { inputBox.validationMessage = validate; } - // vscode.window.showInformationMessage(`You entered: ${userInput}`); - // inputBox.hide(); - // resolve(userInput); }); inputBox.ignoreFocusOut = true; inputBox.show(); @@ -99,6 +102,17 @@ export async function doEmit( progress: overallProgress, }); + const startFile = await getMainTspFile(tspProjectFolder); + + if (!startFile) { + logger.info( + "Invalid typespec project. There is no main tsp file in the project. Emit canceled.", + [], + { showOutput: false, showPopup: true, progress: overallProgress }, + ); + return; + } + const toQuickPickItem = (e: Emitter): EmitQuickPickItem => { return { language: e.language, @@ -328,8 +342,7 @@ export async function doEmit( showPopup: false, progress: overallProgress, }); - /*TODO: resolve the start file. */ - const startFile = `${baseDir}/main.tsp`; + const cli = await resolveTypeSpecCli(baseDir); if (!cli) { logger.error("Cannot find TypeSpec CLI. Please install @typespec/compiler. Cancel emit.", [], { @@ -357,23 +370,13 @@ export async function doEmit( }); const compileResult = await compile(cli, startFile, selectedEmitter.package, options); if (compileResult.exitCode !== 0) { - logger.error( - `Failed to generate Client SDK for ${selectedEmitter.language}. error: ${compileResult.error}`, - [], - { - showOutput: true, - showPopup: true, - progress: overallProgress, - }, - ); + logger.error(`Failed to generate Client SDK for ${selectedEmitter.language}.`, [], { + showOutput: true, + showPopup: true, + progress: overallProgress, + }); } - logger.info(`${compileResult.stdout}.`, [], { - showOutput: true, - showPopup: false, - progress: overallProgress, - }); - logger.info(`complete generating ${selectedEmitter.language} SDK.`, [], { showOutput: true, showPopup: true, @@ -400,9 +403,14 @@ export async function compile( args.push("--option", `${emitter}.${key}=${value}`); } - return await executeCommand(cli.command, args, { - cwd: dirname(startFile), - }); + return await promisifyExec( + cli.command, + args, + { + cwd: dirname(startFile), + }, + { onStdioOut: toOutput, onStdioError: toError }, + ); } export async function check(startFile: string): Promise<{ diff --git a/packages/typespec-vscode/src/typespec-utils.ts b/packages/typespec-vscode/src/typespec-utils.ts index 17361a61a0..7d3fc517da 100644 --- a/packages/typespec-vscode/src/typespec-utils.ts +++ b/packages/typespec-vscode/src/typespec-utils.ts @@ -1,4 +1,8 @@ +import { readdir } from "fs"; +import path, { dirname } from "path"; +import { Executable } from "vscode-languageclient/node.js"; import logger from "./log/logger.js"; +import { isFile, loadModule } from "./utils.js"; export const toOutput = (str: string) => { str @@ -17,3 +21,51 @@ export const toError = (str: string) => { }), ); }; + +export async function resolveTypeSpecCli(absolutePath: string): Promise { + if (!path.isAbsolute(absolutePath) || (await isFile(absolutePath))) { + return undefined; + } + const modelInfo = await loadModule(absolutePath, "@typespec/compiler"); + if (modelInfo) { + //const cli = modelInfo.executables.find((exe) => exe.name === "tsp"); + const cmdPath = path.resolve(modelInfo.path, "cmd/tsp.js"); + return { + command: "node", + args: [cmdPath], + }; + } + return undefined; +} + +export async function getMainTspFile(tspPath: string): Promise { + const isFilePath = await isFile(tspPath); + const baseDir = isFilePath ? dirname(tspPath) : tspPath; + const mainTspFile = path.resolve(baseDir, "main.tsp"); + if (await isFile(mainTspFile)) { + return mainTspFile; + } + + if (isFilePath && tspPath.endsWith(".tsp")) { + return tspPath; + } + + try { + const files = await new Promise((resolve, reject) => { + readdir(baseDir, (err, files) => { + if (err) { + reject(err); + } else { + resolve(files); + } + }); + }); + if (files.length === 1 && files[0].endsWith(".tsp")) { + return path.resolve(baseDir, files[0]); + } + } catch (error) { + return undefined; + } + + return undefined; +} diff --git a/packages/typespec-vscode/src/utils.ts b/packages/typespec-vscode/src/utils.ts index 9952d35692..c27fb36026 100644 --- a/packages/typespec-vscode/src/utils.ts +++ b/packages/typespec-vscode/src/utils.ts @@ -1,7 +1,7 @@ import type { ModuleResolutionResult, ResolveModuleHost } from "@typespec/compiler"; import { exec, spawn, SpawnOptions } from "child_process"; import { readFile, realpath, stat } from "fs/promises"; -import path, { dirname, normalize, resolve } from "path"; +import { dirname, normalize, resolve } from "path"; import { Executable } from "vscode-languageclient/node.js"; import logger from "./log/logger.js"; @@ -125,37 +125,16 @@ export async function executeCommand( options: any, on?: executionEvents, ): Promise { - let stdoutstr: string = ""; + let stdout: string = ""; let errMessage: string = ""; - let retcode = 0; + let retCode = 0; if (args.length > 0) { command = `${command} ${args.join(" ")}`; } - // exec(command, options, (error, stdout, stderr) => { - // if (error) { - // // logger.error(`Error: ${error.message}`); - // errMessage += error.message; - // retcode = error.code ?? 0; - // if (on && on.onError) { - // on.onError(error, stdout.toString(), stderr.toString()); - // } - // } - // if (stderr) { - // // logger.error(`Stderr: ${stderr}`); - // errMessage += stderr; - // if (on && on.onStdioError) { - // on.onStdioError(stderr.toString()); - // } - // } - // stdoutstr += stdout; - // on?.onStdioOut?.(stdout.toString()); - // // logger.info(`Stdout: ${stdout}`); - // }); const child = exec(command, options); child.stdout?.on("data", (data) => { - // logger.info(`Stdout: ${data}`); - stdoutstr += data.toString(); + stdout += data.toString(); on?.onStdioOut?.(data.toString()); }); @@ -166,29 +145,27 @@ export async function executeCommand( if (on && on.onError) { child.on("error", (error: any) => { - on.onError!(error, stdoutstr, errMessage); + on.onError!(error, stdout, errMessage); }); } if (on && on.onExit) { child.on("exit", (code) => { - on.onExit!(code, stdoutstr, errMessage); + on.onExit!(code, stdout, errMessage); }); } child.on("close", (code) => { - // logger.info(`Child process exited with code ${code}`); - retcode = code ?? 0; + retCode = code ?? 0; }); child.on("exit", (code) => { - // logger.info(`Child process exited with code ${code}`); - retcode = code ?? 0; + retCode = code ?? 0; }); return { - stdout: stdoutstr, + stdout: stdout, stderr: errMessage, - exitCode: retcode, + exitCode: retCode, error: errMessage, spawnOptions: options, }; @@ -200,44 +177,15 @@ export async function promisifyExec( options: any, on?: executionEvents, ): Promise { - // let stdoutstr: string = ""; - // let errMessage: string = ""; - // let retcode = 0; - // if (args.length > 0) { - // command = `${command} ${args.join(" ")}`; - // } - - // const execPromise = promisify(exec); - - // const { stdout, stderr } = await execPromise(command, options); - // if (stdout) { - // stdoutstr += stdout; - // on?.onStdioOut?.(stdout.toString()); - // } - // if (stderr) { - // errMessage += stderr; - // // retcode = 1; - // on?.onStdioError?.(stderr.toString()); - // } - // logger.info(`Stdout: ${stdout}`); - // return { - // stdout: stdoutstr, - // stderr: errMessage, - // exitCode: retcode, - // error: errMessage, - // spawnOptions: options, - // }; - let stdout: string = ""; let stderr: string = ""; - let retcode = 0; + let retCode = 0; if (args.length > 0) { command = `${command} ${args.join(" ")}`; } return await new Promise((resolve, reject) => { const child = exec(command, options); child.stdout?.on("data", (data) => { - // logger.info(`Stdout: ${data}`); stdout += data.toString(); on?.onStdioOut?.(data.toString()); }); @@ -249,21 +197,21 @@ export async function promisifyExec( on?.onStdioError?.(data.toString()); }); child.on("close", (code) => { - retcode = code ?? 0; + retCode = code ?? 0; resolve({ stdout: stdout, stderr: stderr, - exitCode: retcode, + exitCode: retCode, error: stderr, spawnOptions: options, }); }); child.on("exit", (code) => { - retcode = code ?? 0; + retCode = code ?? 0; resolve({ stdout: stdout, stderr: stderr, - exitCode: retcode, + exitCode: retCode, error: stderr, spawnOptions: options, }); @@ -279,12 +227,11 @@ export async function spawnExecution( ): Promise { let stdout = ""; let stderr = ""; - let retcode = 0; + let retCode = 0; const child = spawn(command, args, options); child.stdout.on("data", (data) => { - // logger.info(`Stdout: ${data}`); stdout += data.toString(); on?.onStdioOut?.(data.toString()); }); @@ -306,36 +253,18 @@ export async function spawnExecution( } child.on("close", (code) => { - // logger.info(`Child process exited with code ${code}`); - retcode = code ?? 0; + retCode = code ?? 0; }); child.on("exit", (code) => { - // logger.info(`Child process exited with code ${code}`); - retcode = code ?? 0; + retCode = code ?? 0; }); return { stdout: stdout, stderr: stderr, - exitCode: retcode, + exitCode: retCode, error: stderr, spawnOptions: options, }; } - -export async function resolveTypeSpecCli(absolutePath: string): Promise { - if (!path.isAbsolute(absolutePath) || (await isFile(absolutePath))) { - return undefined; - } - const modelInfo = await loadModule(absolutePath, "@typespec/compiler"); - if (modelInfo) { - //const cli = modelInfo.executables.find((exe) => exe.name === "tsp"); - const cmdPath = path.resolve(modelInfo.path, "cmd/tsp.js"); - return { - command: "node", - args: [cmdPath], - }; - } - return undefined; -} From 553446606bbf13ffdb637ccde33e3c958f634f13 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Mon, 9 Dec 2024 22:42:16 +0800 Subject: [PATCH 16/42] remove executeCommand --- packages/typespec-vscode/src/emit/emit.ts | 6 +- packages/typespec-vscode/src/emit/emitters.ts | 54 ------- packages/typespec-vscode/src/npm-utils.ts | 12 +- packages/typespec-vscode/src/utils.ts | 147 ++++++++---------- 4 files changed, 68 insertions(+), 151 deletions(-) delete mode 100644 packages/typespec-vscode/src/emit/emitters.ts diff --git a/packages/typespec-vscode/src/emit/emit.ts b/packages/typespec-vscode/src/emit/emit.ts index b93257ac47..23bc1d1145 100644 --- a/packages/typespec-vscode/src/emit/emit.ts +++ b/packages/typespec-vscode/src/emit/emit.ts @@ -5,7 +5,7 @@ import { Executable } from "vscode-languageclient/node.js"; import logger from "../log/logger.js"; import { InstallationAction, NpmUtil } from "../npm-utils.js"; import { getMainTspFile, resolveTypeSpecCli, toError, toOutput } from "../typespec-utils.js"; -import { ExecOutput, executeCommand, isFile, promisifyExec } from "../utils.js"; +import { ExecOutput, isFile, promisifySpawn } from "../utils.js"; import { EmitQuickPickItem } from "./emit-quick-pick-item.js"; import { clientEmitters, Emitter } from "./emitter.js"; export async function doEmit( @@ -403,7 +403,7 @@ export async function compile( args.push("--option", `${emitter}.${key}=${value}`); } - return await promisifyExec( + return await promisifySpawn( cli.command, args, { @@ -429,7 +429,7 @@ export async function check(startFile: string): Promise<{ args.push("compile"); args.push(startFile); args.push("--no-emit"); - await executeCommand(cli.command, args, { + await promisifySpawn(cli.command, args, { cwd: dirname(startFile), }); logger.info(`complete runtime check.`); diff --git a/packages/typespec-vscode/src/emit/emitters.ts b/packages/typespec-vscode/src/emit/emitters.ts deleted file mode 100644 index 2faffb2dfa..0000000000 --- a/packages/typespec-vscode/src/emit/emitters.ts +++ /dev/null @@ -1,54 +0,0 @@ -import vscode from "vscode"; -import { languageEmitterSettingNames } from "../const.js"; -import { EmitQuickPickItem } from "./emit-quick-pick-item.js"; - -const extensionConfig = vscode.workspace.getConfiguration(); - -function getEmitterPickItem(language: string): EmitQuickPickItem { - const packageName: string = - extensionConfig.get(languageEmitterSettingNames[language] ?? "") ?? ""; - return { - language: language, - package: packageName, - label: `${language}`, - detail: `Create ${language} sdk from ${packageName}`, - fromConfig: false, - }; -} - -export const recommendedEmitters: ReadonlyArray = Object.keys( - languageEmitterSettingNames, -).map((lang) => getEmitterPickItem(lang)); - -/* -export const recommendedEmitters: ReadonlyArray = [ - { - language: "openapi3", - package: "@typespec/openapi3", - label: "emit OpenAPI 3.0", - description: "from @typespec/openapi3", - fromConfig: false, - }, - { - language: "json-schema", - package: "@typespec/json-schema", - label: "emit JSON Schema", - description: "from @typespec/json-schema", - fromConfig: false, - }, - { - language: "protobuf", - package: "@typespec/protobuf", - label: "emit Protobuf", - description: "from @typespec/protobuf", - fromConfig: false, - }, - { - language: "Net", - package: config.get("typespec.emitter.net") ?? "@typespec/http-client-csharp", - label: "emit C# code", - description: "from @typespec/http-client-csharp", - fromConfig: false, - }, -]; -*/ diff --git a/packages/typespec-vscode/src/npm-utils.ts b/packages/typespec-vscode/src/npm-utils.ts index 9d8d63bfed..34dc50573a 100644 --- a/packages/typespec-vscode/src/npm-utils.ts +++ b/packages/typespec-vscode/src/npm-utils.ts @@ -1,7 +1,7 @@ import fs from "fs"; import path from "path"; import logger from "./log/logger.js"; -import { ExecOutput, executionEvents, loadModule, promisifyExec } from "./utils.js"; +import { ExecOutput, executionEvents, loadModule, promisifySpawn } from "./utils.js"; export enum InstallationAction { Install = "Install", @@ -30,15 +30,7 @@ export class NpmUtil { options: any = {}, on?: executionEvents, ): Promise { - let command; - if (packages.length > 0) { - command = `npm install ${packages.join(" ")}`; - } else { - command = `npm install`; - } - - return await promisifyExec(command, [], { ...options, cwd: this.cwd }, on); - //return spawnExecution("npm", ["install", ...packages], { ...options, cwd: this.cwd }); + return promisifySpawn("npm", ["install", ...packages], { cwd: this.cwd }, on); } public async ensureNpmPackageInstall( diff --git a/packages/typespec-vscode/src/utils.ts b/packages/typespec-vscode/src/utils.ts index c27fb36026..e3dec6579e 100644 --- a/packages/typespec-vscode/src/utils.ts +++ b/packages/typespec-vscode/src/utils.ts @@ -1,5 +1,5 @@ import type { ModuleResolutionResult, ResolveModuleHost } from "@typespec/compiler"; -import { exec, spawn, SpawnOptions } from "child_process"; +import { spawn, SpawnOptions } from "child_process"; import { readFile, realpath, stat } from "fs/promises"; import { dirname, normalize, resolve } from "path"; import { Executable } from "vscode-languageclient/node.js"; @@ -119,38 +119,36 @@ export interface executionEvents { onExit?: (code: number | null, stdout: string, stderror: string) => void; } -export async function executeCommand( +export async function spawnExecution( command: string, args: string[], options: any, on?: executionEvents, ): Promise { - let stdout: string = ""; - let errMessage: string = ""; + let stdout = ""; + let stderr = ""; let retCode = 0; - if (args.length > 0) { - command = `${command} ${args.join(" ")}`; - } - const child = exec(command, options); - child.stdout?.on("data", (data) => { + const child = spawn(command, args, options); + + child.stdout.on("data", (data) => { stdout += data.toString(); on?.onStdioOut?.(data.toString()); }); - child.stderr?.on("data", (data) => { - errMessage += data.toString(); + child.stderr.on("data", (data) => { + stderr += data.toString(); on?.onStdioError?.(data.toString()); }); if (on && on.onError) { child.on("error", (error: any) => { - on.onError!(error, stdout, errMessage); + on.onError!(error, stdout, stderr); }); } if (on && on.onExit) { child.on("exit", (code) => { - on.onExit!(code, stdout, errMessage); + on.onExit!(code, stdout, stderr); }); } @@ -164,37 +162,64 @@ export async function executeCommand( return { stdout: stdout, - stderr: errMessage, + stderr: stderr, exitCode: retCode, - error: errMessage, + error: stderr, spawnOptions: options, }; } -export async function promisifyExec( +export async function promisifySpawn( command: string, args: string[], - options: any, + options: SpawnOptions, on?: executionEvents, ): Promise { - let stdout: string = ""; - let stderr: string = ""; + const shell = process.platform === "win32"; + const cmd = shell && command.includes(" ") ? `"${command}"` : command; + let stdout = ""; + let stderr = ""; let retCode = 0; - if (args.length > 0) { - command = `${command} ${args.join(" ")}`; - } - return await new Promise((resolve, reject) => { - const child = exec(command, options); - child.stdout?.on("data", (data) => { - stdout += data.toString(); - on?.onStdioOut?.(data.toString()); - }); + + const spawnOptions: SpawnOptions = { + shell, + stdio: "pipe", + windowsHide: true, + ...options, + }; + const child = spawn(cmd, args, spawnOptions); + + child.stdout?.on("data", (data) => { + stdout += data.toString(); + if (on && on.onStdioOut) { + on.onStdioOut(data.toString()); + } + }); + + child.stderr?.on("data", (data) => { + stderr += data.toString(); + if (on && on.onStdioError) { + on.onStdioError(data.toString()); + } + }); + + child.on("error", (error) => { + if (on && on.onError) { + on.onError(error, stdout, stderr); + } + stderr += error.message; + }); + + return new Promise((resolve, reject) => { child.on("error", (error) => { stderr += error.message; - }); - child.stderr?.on("data", (data) => { - stderr += data.toString(); - on?.onStdioError?.(data.toString()); + resolve({ + stdout: stdout, + stderr: stderr, + exitCode: 0x1212, + error: stderr, + spawnOptions: spawnOptions, + }); }); child.on("close", (code) => { retCode = code ?? 0; @@ -203,68 +228,22 @@ export async function promisifyExec( stderr: stderr, exitCode: retCode, error: stderr, - spawnOptions: options, + spawnOptions: spawnOptions, }); }); + child.on("exit", (code) => { retCode = code ?? 0; + if (on && on.onExit) { + on.onExit(code, stdout, stderr); + } resolve({ stdout: stdout, stderr: stderr, exitCode: retCode, error: stderr, - spawnOptions: options, + spawnOptions: spawnOptions, }); }); }); } - -export async function spawnExecution( - command: string, - args: string[], - options: any, - on?: executionEvents, -): Promise { - let stdout = ""; - let stderr = ""; - let retCode = 0; - - const child = spawn(command, args, options); - - child.stdout.on("data", (data) => { - stdout += data.toString(); - on?.onStdioOut?.(data.toString()); - }); - - child.stderr.on("data", (data) => { - stderr += data.toString(); - on?.onStdioError?.(data.toString()); - }); - - if (on && on.onError) { - child.on("error", (error: any) => { - on.onError!(error, stdout, stderr); - }); - } - if (on && on.onExit) { - child.on("exit", (code) => { - on.onExit!(code, stdout, stderr); - }); - } - - child.on("close", (code) => { - retCode = code ?? 0; - }); - - child.on("exit", (code) => { - retCode = code ?? 0; - }); - - return { - stdout: stdout, - stderr: stderr, - exitCode: retCode, - error: stderr, - spawnOptions: options, - }; -} From 3a9886c6378836af404b355ed6879341ec076261 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Tue, 10 Dec 2024 10:31:20 +0800 Subject: [PATCH 17/42] update pnpm-lock --- pnpm-lock.yaml | 69 +++++++++++++++++++++++++------------------------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 07ece0d799..b2dbcf56d8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -43,7 +43,7 @@ importers: version: 22.7.9 '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5) + version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) c8: specifier: ^10.1.2 version: 10.1.2 @@ -67,7 +67,7 @@ importers: version: 56.0.1(eslint@9.15.0(jiti@1.21.6)) eslint-plugin-vitest: specifier: ^0.5.4 - version: 0.5.4(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)(vitest@2.1.5) + version: 0.5.4(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) micromatch: specifier: ^4.0.8 version: 4.0.8 @@ -151,7 +151,7 @@ importers: version: link:../compiler '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5) + version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -200,7 +200,7 @@ importers: version: 7.5.8 '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5) + version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -258,7 +258,7 @@ importers: version: 17.0.33 '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5) + version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -346,7 +346,7 @@ importers: version: link:../internal-build-utils '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5) + version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -398,7 +398,7 @@ importers: version: 8.15.0 '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5) + version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -434,7 +434,7 @@ importers: version: link:../tspd '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5) + version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -504,7 +504,7 @@ importers: version: 4.3.3(vite@5.4.11(@types/node@22.7.9)) '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5) + version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -549,7 +549,7 @@ importers: version: link:../tspd '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5) + version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -598,7 +598,7 @@ importers: version: link:../tspd '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5) + version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -712,7 +712,7 @@ importers: version: 17.0.33 '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5) + version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -755,7 +755,7 @@ importers: version: link:../tspd '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5) + version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -788,7 +788,7 @@ importers: version: link:../compiler '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5) + version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -816,7 +816,7 @@ importers: version: 22.7.9 '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5) + version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -858,7 +858,7 @@ importers: version: link:../tspd '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5) + version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -919,7 +919,7 @@ importers: version: link:../xml '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5) + version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -1161,7 +1161,7 @@ importers: version: 4.3.3(vite@5.4.11(@types/node@22.7.9)) '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5) + version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -1237,7 +1237,7 @@ importers: version: link:../tspd '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5) + version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -1298,7 +1298,7 @@ importers: version: 4.3.3(vite@5.4.11(@types/node@22.7.9)) '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5) + version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -1343,7 +1343,7 @@ importers: version: link:../tspd '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5) + version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -1413,7 +1413,7 @@ importers: version: link:../internal-build-utils '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5) + version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -1507,7 +1507,7 @@ importers: version: 17.0.33 '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5) + version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -1729,7 +1729,7 @@ importers: version: link:../tspd '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5) + version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -1762,7 +1762,7 @@ importers: version: link:../tspd '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5) + version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -1830,7 +1830,7 @@ importers: version: link:../prettier-plugin-typespec '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5) + version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -1866,6 +1866,10 @@ importers: version: link:../typespec-vscode packages/typespec-vscode: + dependencies: + '@typespec/compiler': + specifier: workspace:~ + version: link:../compiler devDependencies: '@rollup/plugin-commonjs': specifier: ~28.0.0 @@ -1885,15 +1889,12 @@ importers: '@types/vscode': specifier: ~1.94.0 version: 1.94.0 - '@typespec/compiler': - specifier: workspace:~ - version: link:../compiler '@typespec/internal-build-utils': specifier: workspace:~ version: link:../internal-build-utils '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5) + version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -1941,7 +1942,7 @@ importers: version: link:../tspd '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5) + version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -1974,7 +1975,7 @@ importers: version: link:../tspd '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5) + version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -16371,7 +16372,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitest/coverage-v8@2.1.5(vitest@2.1.5)': + '@vitest/coverage-v8@2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 0.2.3 @@ -18550,7 +18551,7 @@ snapshots: semver: 7.6.3 strip-indent: 3.0.0 - eslint-plugin-vitest@0.5.4(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)(vitest@2.1.5): + eslint-plugin-vitest@0.5.4(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)): dependencies: '@typescript-eslint/utils': 7.18.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3) eslint: 9.15.0(jiti@1.21.6) From 7fec5be57265fbfba2c1e7676b8fa229f1ffe612 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Tue, 10 Dec 2024 10:46:05 +0800 Subject: [PATCH 18/42] add change log --- .chronus/changes/generateSDK-2024-11-10-10-45-35.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .chronus/changes/generateSDK-2024-11-10-10-45-35.md diff --git a/.chronus/changes/generateSDK-2024-11-10-10-45-35.md b/.chronus/changes/generateSDK-2024-11-10-10-45-35.md new file mode 100644 index 0000000000..6c70a9ff96 --- /dev/null +++ b/.chronus/changes/generateSDK-2024-11-10-10-45-35.md @@ -0,0 +1,7 @@ +--- +changeKind: feature +packages: + - typespec-vscode +--- + +integrate client SDK generation \ No newline at end of file From bddc07b137ec1f06b963c3bf892e8b114e9f8bd4 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Tue, 10 Dec 2024 10:57:21 +0800 Subject: [PATCH 19/42] resolve lint --- packages/typespec-vscode/src/extension.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/typespec-vscode/src/extension.ts b/packages/typespec-vscode/src/extension.ts index bd5b441e21..edc0d9f7de 100644 --- a/packages/typespec-vscode/src/extension.ts +++ b/packages/typespec-vscode/src/extension.ts @@ -85,7 +85,7 @@ export async function deactivate() { } function generatSdkTask(): vscode.Task[] { - let task = new vscode.Task( + const task = new vscode.Task( { label: "Task: Generate SDK", type: "typespec", From 8c364638a33929aeefcff34fdf2b1cd6acb5a63d Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Tue, 10 Dec 2024 17:17:00 +0800 Subject: [PATCH 20/42] set package name instead of output dir --- packages/typespec-vscode/src/emit/emit.ts | 101 ++++++++++++++-------- 1 file changed, 63 insertions(+), 38 deletions(-) diff --git a/packages/typespec-vscode/src/emit/emit.ts b/packages/typespec-vscode/src/emit/emit.ts index 23bc1d1145..c4c062a820 100644 --- a/packages/typespec-vscode/src/emit/emit.ts +++ b/packages/typespec-vscode/src/emit/emit.ts @@ -32,12 +32,14 @@ export async function doEmit( const inputBox = vscode.window.createInputBox(); inputBox.title = "Choose TypeSpec Project Directory"; inputBox.prompt = "Choose the TypeSpec project."; - inputBox.value = vscode.workspace.workspaceFolders - ? vscode.workspace.workspaceFolders[0].uri.fsPath - : ""; - inputBox.placeholder = vscode.workspace.workspaceFolders - ? vscode.workspace.workspaceFolders[0].uri.fsPath - : "TypeSpec project folder or TypeSpec enterpointer file(e.g. main.tsp)."; + // inputBox.value = vscode.workspace.workspaceFolders + // ? vscode.workspace.workspaceFolders[0].uri.fsPath + // : ""; + // inputBox.placeholder = vscode.workspace.workspaceFolders + // ? vscode.workspace.workspaceFolders[0].uri.fsPath + // : "TypeSpec project folder or TypeSpec enterpointer file(e.g. main.tsp)."; + inputBox.placeholder = + "Path of TypeSpec project folder or TypeSpec enterpointer file(e.g. main.tsp)."; inputBox.buttons = [openDiaglogButton]; const validateInput = async (text: string) => { if (text.trim() === "") { @@ -166,8 +168,9 @@ export async function doEmit( } const configFile = path.resolve(baseDir, "tspconfig.yaml"); + let hasConfig = await isFile(configFile); // let typespecConfig = undefined; - if (!(await isFile(configFile))) { + if (!hasConfig) { await vscode.window .showQuickPick(["Yes", "No"], { title: "No tspconfig.yaml found in the project directory. Do you want to create one?", @@ -178,7 +181,7 @@ export async function doEmit( .then(async (selection) => { if (selection === "Yes") { /* create tspconfig.yaml */ - const yaml = `options:\n "${selectedEmitter.package}":\n emitter-output-dir: client/${selectedEmitter.language}`; + const yaml = `options:\n "${selectedEmitter.package}":\n clear-output-folder: true`; await vscode.workspace.fs.writeFile( vscode.Uri.file(path.resolve(baseDir, "tspconfig.yaml")), Buffer.from(yaml), @@ -190,6 +193,7 @@ export async function doEmit( preview: false, viewColumn: vscode.ViewColumn.Two, }); + hasConfig = true; } }); } else { @@ -207,17 +211,17 @@ export async function doEmit( // : undefined; // const outputDirInConfig = optionsInConfig ? optionsInConfig["emitter-output-dir"] : undefined; const outputDirInConfig = undefined; - const outputDirInput = await vscode.window.showInputBox({ - title: `Configure output directory for ${selectedEmitter.language}`, - placeHolder: `client/${selectedEmitter.language}`, - value: outputDirInConfig ?? `client/${selectedEmitter.language}`, - prompt: `Please provide the output directory for ${selectedEmitter.language} SDK`, + const packageName = await vscode.window.showInputBox({ + title: `Configure Package Name for ${selectedEmitter.language}`, + placeHolder: `${selectedEmitter.language}`, + value: outputDirInConfig ?? `${selectedEmitter.language}`, + prompt: `Please provide the package name for ${selectedEmitter.language} SDK`, validateInput: (text: string) => { return text.trim() === "" ? "Input cannot be empty" : null; }, ignoreFocusOut: true, }); - selectedEmitter.outputDir = outputDirInput; + selectedEmitter.outputDir = packageName; // if (optionsInConfig) { // optionsInConfig["emitter-output-dir"] = outputDirInput; // typespecConfig!.options![`${selectedEmitter.package}`] = optionsInConfig; @@ -230,15 +234,28 @@ export async function doEmit( // ); /* config the emitter in the tspConfig.yaml */ + const yes = { + label: "Yes", + detail: "Configure the emitter in the tspConfig.yaml", + }; + const no = { label: "No", description: "" }; await vscode.window - .showQuickPick(["Yes", "No"], { - title: "configure the emitters in the tspConfig.yaml?", + .showQuickPick([yes, no], { + title: "configure emitter", canPickMany: false, - placeHolder: "Pick a option", + placeHolder: "Configure the emitter in the tspConfig.yaml", ignoreFocusOut: true, }) .then(async (selection) => { - if (selection === "Yes") { + if (selection === yes) { + if (!hasConfig) { + /* create tspconfig.yaml */ + const yaml = `options:\n "${selectedEmitter.package}":\n package-dir: ${packageName}`; + await vscode.workspace.fs.writeFile( + vscode.Uri.file(path.resolve(baseDir, "tspconfig.yaml")), + Buffer.from(yaml), + ); + } const document = await vscode.workspace.openTextDocument( path.resolve(baseDir, "tspconfig.yaml"), ); @@ -247,14 +264,22 @@ export async function doEmit( viewColumn: vscode.ViewColumn.Two, }); await vscode.window - .showQuickPick(["Completed"], { - title: "Is emitter configuration completed?", - canPickMany: false, - placeHolder: "Pick a option", - ignoreFocusOut: true, - }) + .showQuickPick( + [ + { + label: "Completed", + detail: "Emitter configuration is completed.", + }, + ], + { + title: "Is emitter configuration completed?", + canPickMany: false, + placeHolder: "Pick a option", + ignoreFocusOut: true, + }, + ) .then((selection) => { - if (selection === "Completed") { + if (selection?.label === "Completed") { vscode.commands.executeCommand("workbench.action.closeActiveEditor"); } }); @@ -355,7 +380,7 @@ export async function doEmit( let outputDir = path.resolve(baseDir, "tsp-output", selectedEmitter.language); if (selectedEmitter.outputDir) { if (!path.isAbsolute(selectedEmitter.outputDir)) { - outputDir = path.resolve(baseDir, selectedEmitter.outputDir); + outputDir = path.resolve(baseDir, "client", selectedEmitter.outputDir); } else { outputDir = selectedEmitter.outputDir; } @@ -421,17 +446,17 @@ export async function check(startFile: string): Promise<{ // setTimeout(resolve, 180000); // logger.info(`complete runtime check.`); // }); - const cli = await resolveTypeSpecCli(dirname(startFile)); - if (!cli) { - return { valid: true, required: [] }; - } - const args: string[] = cli.args ?? []; - args.push("compile"); - args.push(startFile); - args.push("--no-emit"); - await promisifySpawn(cli.command, args, { - cwd: dirname(startFile), - }); - logger.info(`complete runtime check.`); + // const cli = await resolveTypeSpecCli(dirname(startFile)); + // if (!cli) { + // return { valid: true, required: [] }; + // } + // const args: string[] = cli.args ?? []; + // args.push("compile"); + // args.push(startFile); + // args.push("--no-emit"); + // await promisifySpawn(cli.command, args, { + // cwd: dirname(startFile), + // }); + // logger.info(`complete runtime check.`); return { valid: true, required: [] }; } From 3b820b8a5b5b5a8157b6a20f22c1357c2604fafc Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Wed, 11 Dec 2024 21:14:08 +0800 Subject: [PATCH 21/42] add emit code command --- packages/typespec-vscode/icons/schema.svg | 29 ++++++ packages/typespec-vscode/icons/sdk.svg | 8 ++ packages/typespec-vscode/icons/serverstub.svg | 9 ++ packages/typespec-vscode/package.json | 56 +++++++++++- packages/typespec-vscode/src/const.ts | 2 + .../src/emit/emit-quick-pick-item.ts | 4 + packages/typespec-vscode/src/emit/emit.ts | 91 ++++++++++++++++++- packages/typespec-vscode/src/extension.ts | 45 ++++----- .../typespec-vscode/src/typespec-utils.ts | 14 ++- 9 files changed, 220 insertions(+), 38 deletions(-) create mode 100644 packages/typespec-vscode/icons/schema.svg create mode 100644 packages/typespec-vscode/icons/sdk.svg create mode 100644 packages/typespec-vscode/icons/serverstub.svg diff --git a/packages/typespec-vscode/icons/schema.svg b/packages/typespec-vscode/icons/schema.svg new file mode 100644 index 0000000000..a871efb4bf --- /dev/null +++ b/packages/typespec-vscode/icons/schema.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/typespec-vscode/icons/sdk.svg b/packages/typespec-vscode/icons/sdk.svg new file mode 100644 index 0000000000..b493a458d1 --- /dev/null +++ b/packages/typespec-vscode/icons/sdk.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/packages/typespec-vscode/icons/serverstub.svg b/packages/typespec-vscode/icons/serverstub.svg new file mode 100644 index 0000000000..828b762e17 --- /dev/null +++ b/packages/typespec-vscode/icons/serverstub.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/packages/typespec-vscode/package.json b/packages/typespec-vscode/package.json index f61b63f79c..b68c34dbbe 100644 --- a/packages/typespec-vscode/package.json +++ b/packages/typespec-vscode/package.json @@ -77,7 +77,50 @@ "default": "off", "description": "Define whether/how the TypeSpec language server should send traces to client. For the traces to show properly in vscode Output, make sure 'Log Level' is also set to 'Trace' so that they won't be filtered at client side, which can be set through 'Developer: Set Log Level...' command." }, - "typespec.emitter": { + "typespec.client.emitter": { + "scope": "window", + "type": "array", + "items": { + "type": "object", + "properties": { + "language": { + "type": "string", + "enum": [ + "DotNet", + "Java", + "TypeScript", + "Python", + "Go" + ], + "description": "Define the language for the emitter." + }, + "package": { + "type": "string", + "description": "Define the emitter package.\n\nExample (with version): @typespec/http-client-csharp@1.0.0\n\nExample (without version): @typespec/http-client-csharp" + } + } + }, + "default": [ + { + "language": "DotNet", + "package": "@typespec/http-client-csharp" + }, + { + "language": "Java", + "package": "@typespec/http-client-java" + }, + { + "language": "TypeScript", + "package": "@azure-tools/typespec-ts" + }, + { + "language": "Python", + "package": "@typespec/http-client-python" + } + ], + "description": "Define the emitter for any language client sdk generation." + }, + "typespec.server.emitter": { "scope": "window", "type": "object", "properties": { @@ -167,22 +210,27 @@ "category": "TypeSpec" }, { - "command": "typespec.GenerateSDK", + "command": "typespec.generateSDK", "title": "TypeSpec: Generate Client SDK", "category": "TypeSpec" + }, + { + "command": "typespec.emit", + "title": "TypeSpec: Emit Code", + "category": "TypeSpec" } ], "menus": { "explorer/context": [ { - "command": "typespec.GenerateSDK", + "command": "typespec.generateSDK", "when": "explorerResourceIsFolder || resourceLangId == typespec", "group": "code_generation" } ], "editor/context": [ { - "command": "typespec.GenerateSDK", + "command": "typespec.generateSDK", "when": "resourceLangId == typespec", "group": "code_generation" } diff --git a/packages/typespec-vscode/src/const.ts b/packages/typespec-vscode/src/const.ts index 1da0f81724..814ea79927 100644 --- a/packages/typespec-vscode/src/const.ts +++ b/packages/typespec-vscode/src/const.ts @@ -12,3 +12,5 @@ export const languageEmitterSettingNames: Record = { Python: SettingName.PythonEmitter, JavaScript: SettingName.JsEmitter, }; + +export const StartFileName = "main.tsp"; diff --git a/packages/typespec-vscode/src/emit/emit-quick-pick-item.ts b/packages/typespec-vscode/src/emit/emit-quick-pick-item.ts index c8299cde84..b0b4f7f004 100644 --- a/packages/typespec-vscode/src/emit/emit-quick-pick-item.ts +++ b/packages/typespec-vscode/src/emit/emit-quick-pick-item.ts @@ -7,3 +7,7 @@ export interface EmitQuickPickItem extends vscode.QuickPickItem { fromConfig: boolean; outputDir?: string; } + +export interface TypeSpecProjectPickItem extends vscode.QuickPickItem { + path: string; +} diff --git a/packages/typespec-vscode/src/emit/emit.ts b/packages/typespec-vscode/src/emit/emit.ts index c4c062a820..8c962c53c0 100644 --- a/packages/typespec-vscode/src/emit/emit.ts +++ b/packages/typespec-vscode/src/emit/emit.ts @@ -4,9 +4,15 @@ import vscode, { QuickInputButton, Uri } from "vscode"; import { Executable } from "vscode-languageclient/node.js"; import logger from "../log/logger.js"; import { InstallationAction, NpmUtil } from "../npm-utils.js"; -import { getMainTspFile, resolveTypeSpecCli, toError, toOutput } from "../typespec-utils.js"; +import { + getMainTspFile, + resolveTypeSpecCli, + toError, + toOutput, + TraverseMainTspFileInWorkspace, +} from "../typespec-utils.js"; import { ExecOutput, isFile, promisifySpawn } from "../utils.js"; -import { EmitQuickPickItem } from "./emit-quick-pick-item.js"; +import { EmitQuickPickItem, TypeSpecProjectPickItem } from "./emit-quick-pick-item.js"; import { clientEmitters, Emitter } from "./emitter.js"; export async function doEmit( context: vscode.ExtensionContext, @@ -28,13 +34,14 @@ export async function doEmit( }, "Browse...", ); + tspProjectFolder = await new Promise((resolve) => { const inputBox = vscode.window.createInputBox(); inputBox.title = "Choose TypeSpec Project Directory"; inputBox.prompt = "Choose the TypeSpec project."; - // inputBox.value = vscode.workspace.workspaceFolders - // ? vscode.workspace.workspaceFolders[0].uri.fsPath - // : ""; + inputBox.value = vscode.workspace.workspaceFolders + ? vscode.workspace.workspaceFolders[0].uri.fsPath + : ""; // inputBox.placeholder = vscode.workspace.workspaceFolders // ? vscode.workspace.workspaceFolders[0].uri.fsPath // : "TypeSpec project folder or TypeSpec enterpointer file(e.g. main.tsp)."; @@ -411,6 +418,80 @@ export async function doEmit( /*TODO: build sdk. */ } +export async function emitCode( + context: vscode.ExtensionContext, + uri: vscode.Uri, + overallProgress: vscode.Progress<{ message?: string; increment?: number }>, +) { + const targetPathes = await TraverseMainTspFileInWorkspace(); + logger.info(`Found ${targetPathes.length} main.tsp files`); + if (targetPathes.length === 0) { + logger.info("No main.tsp file found. Emit canceled.", [], { + showOutput: false, + showPopup: true, + progress: overallProgress, + }); + return; + } + const toProjectPickItem = (filePath: string): TypeSpecProjectPickItem => { + return { + label: filePath, + path: filePath, + }; + }; + const typespecProjectQuickPickItems: TypeSpecProjectPickItem[] = targetPathes.map((filePath) => + toProjectPickItem(filePath), + ); + const selectedProject = await vscode.window.showQuickPick(typespecProjectQuickPickItems, { + title: "Select TypeSpec Project", + canPickMany: false, + placeHolder: "Pick a project", + ignoreFocusOut: true, + }); + if (!selectedProject) { + logger.info("No project selected. Emit canceled.", [], { + showOutput: false, + showPopup: true, + progress: overallProgress, + }); + return; + } + const codesToEmit = [ + { + label: "Client SDK", + detail: "Generate client SDK library from typespec.", + iconPath: Uri.file(context.asAbsolutePath(`./icons/sdk.svg`)), + }, + { + label: "Server Stub", + detail: "Generate server codes from typespec", + iconPath: Uri.file(context.asAbsolutePath(`./icons/serverstub.svg`)), + }, + { + label: "Protocol Schema", + detail: "Generate protocol schema (e.g. OpenAPI, Protobuf) from typespec", + iconPath: Uri.file(context.asAbsolutePath(`./icons/schema.svg`)), + }, + ]; + const codeType = await vscode.window.showQuickPick(codesToEmit, { + title: "Emit Code", + canPickMany: false, + placeHolder: "Select an option", + ignoreFocusOut: true, + }); + if (!codeType) { + logger.info("No emitters selected. Emit canceled.", [], { + showOutput: false, + showPopup: true, + progress: overallProgress, + }); + return; + } + if (codeType.label === "Client SDK") { + await doEmit(context, uri, overallProgress); + } +} + export async function compile( cli: Executable, startFile: string, diff --git a/packages/typespec-vscode/src/extension.ts b/packages/typespec-vscode/src/extension.ts index edc0d9f7de..0b61ec379d 100644 --- a/packages/typespec-vscode/src/extension.ts +++ b/packages/typespec-vscode/src/extension.ts @@ -1,6 +1,6 @@ import vscode, { commands, ExtensionContext } from "vscode"; import { SettingName } from "./const.js"; -import { doEmit } from "./emit/emit.js"; +import { doEmit, emitCode } from "./emit/emit.js"; import { ExtensionLogListener } from "./log/extension-log-listener.js"; import logger from "./log/logger.js"; import { TypeSpecLogOutputChannel } from "./log/typespec-log-output-channel.js"; @@ -34,11 +34,11 @@ export async function activate(context: ExtensionContext) { /* code generation command. */ context.subscriptions.push( - commands.registerCommand("typespec.GenerateSDK", async (uri: vscode.Uri) => { + commands.registerCommand("typespec.generateSDK", async (uri: vscode.Uri) => { await vscode.window.withProgress( { location: vscode.ProgressLocation.Window, - title: "TypeSpec: Gerating Client SDK...", + title: "Generate Client SDK...", cancellable: false, }, async (progress) => await doEmit(context, uri, progress), @@ -46,6 +46,20 @@ export async function activate(context: ExtensionContext) { }), ); + /* emit command. */ + context.subscriptions.push( + commands.registerCommand("typespec.emit", async (uri: vscode.Uri) => { + await vscode.window.withProgress( + { + location: vscode.ProgressLocation.Window, + title: "Emit Code...", + cancellable: false, + }, + async (progress) => await emitCode(context, uri, progress), + ); + }), + ); + context.subscriptions.push( vscode.workspace.onDidChangeConfiguration(async (e: vscode.ConfigurationChangeEvent) => { if (e.affectsConfiguration(SettingName.TspServerPath)) { @@ -58,16 +72,6 @@ export async function activate(context: ExtensionContext) { }), ); - const taskDisposal = vscode.tasks.registerTaskProvider("typespec", { - provideTasks: () => { - return generatSdkTask(); - }, - resolveTask(_task: vscode.Task): vscode.Task | undefined { - return undefined; - }, - }); - context.subscriptions.push(taskDisposal); - return await vscode.window.withProgress( { title: "Launching TypeSpec language service...", @@ -83,18 +87,3 @@ export async function activate(context: ExtensionContext) { export async function deactivate() { await client?.stop(); } - -function generatSdkTask(): vscode.Task[] { - const task = new vscode.Task( - { - label: "Task: Generate SDK", - type: "typespec", - }, - vscode.TaskScope.Workspace, - "Generate Sdk Task", - "tsp", - new vscode.ShellExecution("code --command 'typespec.GenerateSDK'"), - // new vscode.ShellExecution("code --command 'typespec.GenerateSDK'"), - ); - return [task]; -} diff --git a/packages/typespec-vscode/src/typespec-utils.ts b/packages/typespec-vscode/src/typespec-utils.ts index 7d3fc517da..144b2c1d76 100644 --- a/packages/typespec-vscode/src/typespec-utils.ts +++ b/packages/typespec-vscode/src/typespec-utils.ts @@ -1,8 +1,10 @@ import { readdir } from "fs"; import path, { dirname } from "path"; +import vscode from "vscode"; import { Executable } from "vscode-languageclient/node.js"; +import { StartFileName } from "./const.js"; import logger from "./log/logger.js"; -import { isFile, loadModule } from "./utils.js"; +import { isFile, loadModule, normalizeSlash } from "./utils.js"; export const toOutput = (str: string) => { str @@ -69,3 +71,13 @@ export async function getMainTspFile(tspPath: string): Promise + uris + .filter((uri) => uri.scheme === "file" && !uri.fsPath.includes("node_modules")) + .map((uri) => normalizeSlash(uri.fsPath)), + ); +} From b3d794b39436a586bbd8edffd9d9a2896dd4ff45 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Thu, 12 Dec 2024 15:32:33 +0800 Subject: [PATCH 22/42] integrate server code and openapi generation --- packages/typespec-vscode/package.json | 122 +++--- packages/typespec-vscode/src/const.ts | 9 + .../src/emit/emit-quick-pick-item.ts | 2 + packages/typespec-vscode/src/emit/emit.ts | 362 ++++-------------- packages/typespec-vscode/src/emit/emitter.ts | 20 +- packages/typespec-vscode/src/extension.ts | 16 +- 6 files changed, 162 insertions(+), 369 deletions(-) diff --git a/packages/typespec-vscode/package.json b/packages/typespec-vscode/package.json index b68c34dbbe..85d54d657f 100644 --- a/packages/typespec-vscode/package.json +++ b/packages/typespec-vscode/package.json @@ -88,11 +88,11 @@ "enum": [ "DotNet", "Java", - "TypeScript", + "JavaScript", "Python", "Go" ], - "description": "Define the language for the emitter." + "description": "Define the language the emitter will emit." }, "package": { "type": "string", @@ -118,64 +118,71 @@ "package": "@typespec/http-client-python" } ], - "description": "Define the emitter for any language client sdk generation." + "description": "Define the emitter for a language client sdk generation." }, "typespec.server.emitter": { "scope": "window", - "type": "object", - "properties": { - "Net": { - "type": "string", - "default": "@typespec/http-client-csharp", - "description": "Define the emitter to generate .NET SDK." - }, - "Java": { - "type": "string", - "default": "@typespec/http-client-java", - "description": "Define the emitter to generate Java SDK." - }, - "JavaScript": { - "type": "string", - "default": "@azure-tools/typespec-ts", - "description": "Define the emitter to generate JS SDK." - }, - "Python": { - "type": "string", - "default": "@typespec/http-client-python", - "description": "Define the emitter to generate Python SDK." + "type": "array", + "items": { + "type": "object", + "properties": { + "language": { + "type": "string", + "enum": [ + "DotNet", + "Java", + "JavaScript", + "Python", + "Go" + ], + "description": "Define the language for the emitter." + }, + "package": { + "type": "string", + "description": "Define the emitter package.\n\nExample (with version): @typespec/http-server-csharp@1.0.0\n\nExample (without version): @typespec/http-server-csharp" + } } }, - "default": { - "Net": "@typespec/http-client-csharp", - "Java": "@typespec/http-client-java", - "JavaScript": "@azure-tools/typespec-ts", - "Python": "@typespec/http-client-python" - }, - "description": "Define the emitter for any language sdk generation." - }, - "typespec.emitter.net": { - "scope": "window", - "type": "string", - "default": "@typespec/http-client-csharp", - "description": "Define the emitter to generate .NET SDK.\n\nExample (with version): @typespec/http-client-csharp@1.0.0\n\nExample (without version): @typespec/http-client-csharp" - }, - "typespec.emitter.java": { - "scope": "window", - "type": "string", - "default": "@typespec/http-client-java", - "description": "Define the emitter to generate Java SDK.\n\nExample (with version): @typespec/http-client-java@1.0.0\n\nExample (without version): @typespec/http-client-java" - }, - "typespec.emitter.python": { - "scope": "window", - "type": "string", - "default": "@typespec/http-client-python", - "description": "Define the emitter to generate Python SDK.\n\nExample (with version): @typespec/http-client-python@1.0.0\n\nExample (without version): @typespec/http-client-python" + "default": [ + { + "language": "DotNet", + "package": "@typespec/http-server-csharp" + }, + { + "language": "JavaScript", + "package": "@typespec/http-server-javascript" + } + ], + "description": "Define the emitter for server code generation." }, - "typespec.emitter.javascript": { + "typespec.schema.emitter": { "scope": "window", - "type": "string", - "default": "@azure-tools/typespec-ts", - "description": "Define the emitter to generate Js SDK.\n\nExample (with version): @azure-tools/typespec-ts@1.0.0\n\nExample (without version): @azure-tools/typespec-ts" + "type": "array", + "items": { + "type": "object", + "properties": { + "language": { + "type": "string", + "enum": [ + "OpenAPI3", + "ProtoBuf", + "JsonSchema" + ], + "description": "Define the language for the emitter." + }, + "package": { + "type": "string", + "description": "Define the emitter package.\n\nExample (with version): @typespec/openapi3@1.0.0\n\nExample (without version): @typespec/openapi3" + } + } + }, + "default": [ + { + "language": "OpenAPI3", + "package": "@typespec/openapi3" + } + ], + "description": "Define the emitter for a schema." } } } @@ -209,11 +216,6 @@ "title": "Show Output Channel", "category": "TypeSpec" }, - { - "command": "typespec.generateSDK", - "title": "TypeSpec: Generate Client SDK", - "category": "TypeSpec" - }, { "command": "typespec.emit", "title": "TypeSpec: Emit Code", @@ -223,14 +225,14 @@ "menus": { "explorer/context": [ { - "command": "typespec.generateSDK", + "command": "typespec.emit", "when": "explorerResourceIsFolder || resourceLangId == typespec", "group": "code_generation" } ], "editor/context": [ { - "command": "typespec.generateSDK", + "command": "typespec.emit", "when": "resourceLangId == typespec", "group": "code_generation" } diff --git a/packages/typespec-vscode/src/const.ts b/packages/typespec-vscode/src/const.ts index 814ea79927..8dfb828340 100644 --- a/packages/typespec-vscode/src/const.ts +++ b/packages/typespec-vscode/src/const.ts @@ -4,6 +4,9 @@ export const enum SettingName { JavaEmitter = "typespec.emitter.java", PythonEmitter = "typespec.emitter.python", JsEmitter = "typespec.emitter.javascript", + ClientEmitter = "typespec.client.emitter", + ServerEmitter = "typespec.server.emitter", + SchemaEmitter = "typespec.schema.emitter", } export const languageEmitterSettingNames: Record = { @@ -13,4 +16,10 @@ export const languageEmitterSettingNames: Record = { JavaScript: SettingName.JsEmitter, }; +export const EmitterSettingName: Record = { + client: SettingName.ClientEmitter, + server: SettingName.ServerEmitter, + schema: SettingName.SchemaEmitter, +}; + export const StartFileName = "main.tsp"; diff --git a/packages/typespec-vscode/src/emit/emit-quick-pick-item.ts b/packages/typespec-vscode/src/emit/emit-quick-pick-item.ts index b0b4f7f004..86391b1dc9 100644 --- a/packages/typespec-vscode/src/emit/emit-quick-pick-item.ts +++ b/packages/typespec-vscode/src/emit/emit-quick-pick-item.ts @@ -1,4 +1,5 @@ import vscode from "vscode"; +import { EmitterKind } from "./emitter.js"; export interface EmitQuickPickItem extends vscode.QuickPickItem { language: string; @@ -6,6 +7,7 @@ export interface EmitQuickPickItem extends vscode.QuickPickItem { version?: string; fromConfig: boolean; outputDir?: string; + emitterKind: EmitterKind; } export interface TypeSpecProjectPickItem extends vscode.QuickPickItem { diff --git a/packages/typespec-vscode/src/emit/emit.ts b/packages/typespec-vscode/src/emit/emit.ts index 8c962c53c0..f3f4bafab4 100644 --- a/packages/typespec-vscode/src/emit/emit.ts +++ b/packages/typespec-vscode/src/emit/emit.ts @@ -1,6 +1,6 @@ // import { TypeSpecConfig } from "@typespec/compiler"; import path, { dirname } from "path"; -import vscode, { QuickInputButton, Uri } from "vscode"; +import vscode, { Uri } from "vscode"; import { Executable } from "vscode-languageclient/node.js"; import logger from "../log/logger.js"; import { InstallationAction, NpmUtil } from "../npm-utils.js"; @@ -13,107 +13,15 @@ import { } from "../typespec-utils.js"; import { ExecOutput, isFile, promisifySpawn } from "../utils.js"; import { EmitQuickPickItem, TypeSpecProjectPickItem } from "./emit-quick-pick-item.js"; -import { clientEmitters, Emitter } from "./emitter.js"; +import { Emitter, EmitterKind, getRegisterEmitters } from "./emitter.js"; + export async function doEmit( context: vscode.ExtensionContext, - uri: vscode.Uri, + mainTspFile: string, + kind: EmitterKind, overallProgress: vscode.Progress<{ message?: string; increment?: number }>, ) { - let tspProjectFolder: string = ""; - if (!uri) { - class MyButton implements QuickInputButton { - constructor( - public iconPath: { light: Uri; dark: Uri }, - public tooltip: string, - ) {} - } - const openDiaglogButton = new MyButton( - { - dark: Uri.file(context.asAbsolutePath("./icons/openfolder.svg")), - light: Uri.file(context.asAbsolutePath("./icons/openfolder.svg")), - }, - "Browse...", - ); - - tspProjectFolder = await new Promise((resolve) => { - const inputBox = vscode.window.createInputBox(); - inputBox.title = "Choose TypeSpec Project Directory"; - inputBox.prompt = "Choose the TypeSpec project."; - inputBox.value = vscode.workspace.workspaceFolders - ? vscode.workspace.workspaceFolders[0].uri.fsPath - : ""; - // inputBox.placeholder = vscode.workspace.workspaceFolders - // ? vscode.workspace.workspaceFolders[0].uri.fsPath - // : "TypeSpec project folder or TypeSpec enterpointer file(e.g. main.tsp)."; - inputBox.placeholder = - "Path of TypeSpec project folder or TypeSpec enterpointer file(e.g. main.tsp)."; - inputBox.buttons = [openDiaglogButton]; - const validateInput = async (text: string) => { - if (text.trim() === "") { - return "Please choose Typespec folder"; - } - const start = await getMainTspFile(text); - if (!start) { - return "Cannot find main tsp file in the project. Please select a valid TypeSpec project folder."; - } - return null; - }; - inputBox.onDidTriggerButton(async () => { - const options = { - canSelectMany: false, - openLabel: "Choose TypeSpec Project Directory", - canSelectFolders: true, - canSelectFiles: false, - }; - await vscode.window.showOpenDialog(options).then((uris) => { - // tspProjectFolder = uris ? uris[0].fsPath : ""; - // inputBox.value = tspProjectFolder; - inputBox.value = uris ? uris[0].fsPath : ""; - inputBox.validationMessage = undefined; - }); - }); - - inputBox.onDidChangeValue(async (text) => { - const validate = await validateInput(text); - if (validate !== null) { - inputBox.validationMessage = validate; - } else { - inputBox.validationMessage = undefined; - } - }); - - inputBox.onDidAccept(async () => { - const userInput = inputBox.value; - const validate = await validateInput(userInput); - if (validate === null) { - resolve(userInput); - } else { - inputBox.validationMessage = validate; - } - }); - inputBox.ignoreFocusOut = true; - inputBox.show(); - }); - } else { - tspProjectFolder = uri.fsPath; - } - - logger.info(`Select folder ${tspProjectFolder} ...`, [], { - showOutput: true, - showPopup: false, - progress: overallProgress, - }); - const baseDir = (await isFile(tspProjectFolder)) ? dirname(tspProjectFolder) : tspProjectFolder; - /*TODO: check the main.tsp file if it is a project folder. */ - logger.info("select language...", [], { - showOutput: false, - showPopup: false, - progress: overallProgress, - }); - - const startFile = await getMainTspFile(tspProjectFolder); - - if (!startFile) { + if (!mainTspFile || !(await isFile(mainTspFile))) { logger.info( "Invalid typespec project. There is no main tsp file in the project. Emit canceled.", [], @@ -122,10 +30,13 @@ export async function doEmit( return; } + const baseDir = dirname(mainTspFile); + const toQuickPickItem = (e: Emitter): EmitQuickPickItem => { return { language: e.language, package: e.package, + emitterKind: e.kind, label: e.language, detail: `Create ${e.language} sdk from ${e.package}`, picked: false, @@ -134,7 +45,8 @@ export async function doEmit( }; }; - const all = [...clientEmitters].map((e) => toQuickPickItem(e)); + const registerEmitters = getRegisterEmitters(kind); + const all = [...registerEmitters].map((e) => toQuickPickItem(e)); const selectedEmitter = await vscode.window.showQuickPick(all, { title: "Select the Language of the SDK", @@ -174,125 +86,6 @@ export async function doEmit( return; } - const configFile = path.resolve(baseDir, "tspconfig.yaml"); - let hasConfig = await isFile(configFile); - // let typespecConfig = undefined; - if (!hasConfig) { - await vscode.window - .showQuickPick(["Yes", "No"], { - title: "No tspconfig.yaml found in the project directory. Do you want to create one?", - canPickMany: false, - placeHolder: "Pick a option", - ignoreFocusOut: true, - }) - .then(async (selection) => { - if (selection === "Yes") { - /* create tspconfig.yaml */ - const yaml = `options:\n "${selectedEmitter.package}":\n clear-output-folder: true`; - await vscode.workspace.fs.writeFile( - vscode.Uri.file(path.resolve(baseDir, "tspconfig.yaml")), - Buffer.from(yaml), - ); - const document = await vscode.workspace.openTextDocument( - vscode.Uri.file(path.resolve(baseDir, "tspconfig.yaml")), - ); - vscode.window.showTextDocument(document, { - preview: false, - viewColumn: vscode.ViewColumn.Two, - }); - hasConfig = true; - } - }); - } else { - /* check the emitter in the tspConfig.yaml */ - // const document = await vscode.workspace.openTextDocument(configFile); - // const doc = parseDocument(document.getText(), { - // prettyErrors: false, // We are handling the error display ourself to be consistent in the style. - // }); - // typespecConfig = doc.toJSON(); - } - - /* config the output dir. */ - // const optionsInConfig = typespecConfig?.options - // ? typespecConfig?.options[`${selectedEmitter.package}`] - // : undefined; - // const outputDirInConfig = optionsInConfig ? optionsInConfig["emitter-output-dir"] : undefined; - const outputDirInConfig = undefined; - const packageName = await vscode.window.showInputBox({ - title: `Configure Package Name for ${selectedEmitter.language}`, - placeHolder: `${selectedEmitter.language}`, - value: outputDirInConfig ?? `${selectedEmitter.language}`, - prompt: `Please provide the package name for ${selectedEmitter.language} SDK`, - validateInput: (text: string) => { - return text.trim() === "" ? "Input cannot be empty" : null; - }, - ignoreFocusOut: true, - }); - selectedEmitter.outputDir = packageName; - // if (optionsInConfig) { - // optionsInConfig["emitter-output-dir"] = outputDirInput; - // typespecConfig!.options![`${selectedEmitter.package}`] = optionsInConfig; - // } - - /* save emitter config */ - // await vscode.workspace.fs.writeFile( - // vscode.Uri.file(path.resolve(baseDir, "tspconfig.yaml")), - // Buffer.from(stringify(typespecConfig)), - // ); - - /* config the emitter in the tspConfig.yaml */ - const yes = { - label: "Yes", - detail: "Configure the emitter in the tspConfig.yaml", - }; - const no = { label: "No", description: "" }; - await vscode.window - .showQuickPick([yes, no], { - title: "configure emitter", - canPickMany: false, - placeHolder: "Configure the emitter in the tspConfig.yaml", - ignoreFocusOut: true, - }) - .then(async (selection) => { - if (selection === yes) { - if (!hasConfig) { - /* create tspconfig.yaml */ - const yaml = `options:\n "${selectedEmitter.package}":\n package-dir: ${packageName}`; - await vscode.workspace.fs.writeFile( - vscode.Uri.file(path.resolve(baseDir, "tspconfig.yaml")), - Buffer.from(yaml), - ); - } - const document = await vscode.workspace.openTextDocument( - path.resolve(baseDir, "tspconfig.yaml"), - ); - vscode.window.showTextDocument(document, { - preview: false, - viewColumn: vscode.ViewColumn.Two, - }); - await vscode.window - .showQuickPick( - [ - { - label: "Completed", - detail: "Emitter configuration is completed.", - }, - ], - { - title: "Is emitter configuration completed?", - canPickMany: false, - placeHolder: "Pick a option", - ignoreFocusOut: true, - }, - ) - .then((selection) => { - if (selection?.label === "Completed") { - vscode.commands.executeCommand("workbench.action.closeActiveEditor"); - } - }); - } - }); - /* TODO: verify packages to install. */ logger.info("npm install...", [], { @@ -384,37 +177,30 @@ export async function doEmit( }); return; } - let outputDir = path.resolve(baseDir, "tsp-output", selectedEmitter.language); - if (selectedEmitter.outputDir) { - if (!path.isAbsolute(selectedEmitter.outputDir)) { - outputDir = path.resolve(baseDir, "client", selectedEmitter.outputDir); - } else { - outputDir = selectedEmitter.outputDir; - } - } + const outputDir = path.resolve(baseDir, selectedEmitter.emitterKind, selectedEmitter.language); const options: Record = {}; options["emitter-output-dir"] = outputDir; - logger.info(`Generate Client SDK for ${selectedEmitter.language} ...`, [], { + logger.info(`Generate ${selectedEmitter.language} Client SDK under ${outputDir}...`, [], { showOutput: false, showPopup: true, progress: overallProgress, }); - const compileResult = await compile(cli, startFile, selectedEmitter.package, options); + const compileResult = await compile(cli, mainTspFile, selectedEmitter.package, options); if (compileResult.exitCode !== 0) { - logger.error(`Failed to generate Client SDK for ${selectedEmitter.language}.`, [], { + logger.error(`Failed to generate ${selectedEmitter.language} Client SDK.`, [], { + showOutput: true, + showPopup: true, + progress: overallProgress, + }); + } else { + logger.info(`complete generating ${selectedEmitter.language} Client SDK.`, [], { showOutput: true, showPopup: true, progress: overallProgress, }); } - logger.info(`complete generating ${selectedEmitter.language} SDK.`, [], { - showOutput: true, - showPopup: true, - progress: overallProgress, - }); - /*TODO: build sdk. */ } @@ -423,57 +209,80 @@ export async function emitCode( uri: vscode.Uri, overallProgress: vscode.Progress<{ message?: string; increment?: number }>, ) { - const targetPathes = await TraverseMainTspFileInWorkspace(); - logger.info(`Found ${targetPathes.length} main.tsp files`); - if (targetPathes.length === 0) { - logger.info("No main.tsp file found. Emit canceled.", [], { - showOutput: false, - showPopup: true, - progress: overallProgress, - }); - return; - } - const toProjectPickItem = (filePath: string): TypeSpecProjectPickItem => { - return { - label: filePath, - path: filePath, + let tspProjectFile: string = ""; + if (!uri) { + const targetPathes = await TraverseMainTspFileInWorkspace(); + logger.info(`Found ${targetPathes.length} main.tsp files`); + if (targetPathes.length === 0) { + logger.info("No main.tsp file found. Emit canceled.", [], { + showOutput: false, + showPopup: true, + progress: overallProgress, + }); + return; + } + const toProjectPickItem = (filePath: string): TypeSpecProjectPickItem => { + return { + label: filePath, + path: filePath, + }; }; - }; - const typespecProjectQuickPickItems: TypeSpecProjectPickItem[] = targetPathes.map((filePath) => - toProjectPickItem(filePath), - ); - const selectedProject = await vscode.window.showQuickPick(typespecProjectQuickPickItems, { - title: "Select TypeSpec Project", - canPickMany: false, - placeHolder: "Pick a project", - ignoreFocusOut: true, - }); - if (!selectedProject) { - logger.info("No project selected. Emit canceled.", [], { - showOutput: false, - showPopup: true, - progress: overallProgress, + const typespecProjectQuickPickItems: TypeSpecProjectPickItem[] = targetPathes.map((filePath) => + toProjectPickItem(filePath), + ); + const selectedProjectFile = await vscode.window.showQuickPick(typespecProjectQuickPickItems, { + title: "Select TypeSpec Project", + canPickMany: false, + placeHolder: "Pick a project", + ignoreFocusOut: true, }); - return; + if (!selectedProjectFile) { + logger.info("No project selected. Emit canceled.", [], { + showOutput: false, + showPopup: true, + progress: overallProgress, + }); + return; + } + tspProjectFile = selectedProjectFile.path; + } else { + const tspStartFile = await getMainTspFile(uri.fsPath); + if (!tspStartFile) { + logger.info("No main file. Invalid typespec project.", [], { + showOutput: false, + showPopup: true, + progress: overallProgress, + }); + return; + } + tspProjectFile = tspStartFile; + } + + interface EmitTypeQuickPickItem extends vscode.QuickPickItem { + emitterKind: EmitterKind; } + const codesToEmit = [ { label: "Client SDK", detail: "Generate client SDK library from typespec.", iconPath: Uri.file(context.asAbsolutePath(`./icons/sdk.svg`)), + emitterKind: EmitterKind.Client, }, { label: "Server Stub", detail: "Generate server codes from typespec", iconPath: Uri.file(context.asAbsolutePath(`./icons/serverstub.svg`)), + emitterKind: EmitterKind.Server, }, { label: "Protocol Schema", detail: "Generate protocol schema (e.g. OpenAPI, Protobuf) from typespec", iconPath: Uri.file(context.asAbsolutePath(`./icons/schema.svg`)), + emitterKind: EmitterKind.Schema, }, ]; - const codeType = await vscode.window.showQuickPick(codesToEmit, { + const codeType = await vscode.window.showQuickPick(codesToEmit, { title: "Emit Code", canPickMany: false, placeHolder: "Select an option", @@ -487,9 +296,7 @@ export async function emitCode( }); return; } - if (codeType.label === "Client SDK") { - await doEmit(context, uri, overallProgress); - } + await doEmit(context, tspProjectFile, codeType.emitterKind, overallProgress); } export async function compile( @@ -523,21 +330,6 @@ export async function check(startFile: string): Promise<{ valid: boolean; required: { name: string; version: string }[]; }> { - // await new Promise((resolve) => { - // setTimeout(resolve, 180000); - // logger.info(`complete runtime check.`); - // }); - // const cli = await resolveTypeSpecCli(dirname(startFile)); - // if (!cli) { - // return { valid: true, required: [] }; - // } - // const args: string[] = cli.args ?? []; - // args.push("compile"); - // args.push(startFile); - // args.push("--no-emit"); - // await promisifySpawn(cli.command, args, { - // cwd: dirname(startFile), - // }); - // logger.info(`complete runtime check.`); + /* TODO: check the runtime. */ return { valid: true, required: [] }; } diff --git a/packages/typespec-vscode/src/emit/emitter.ts b/packages/typespec-vscode/src/emit/emitter.ts index bf755034d6..555bd03157 100644 --- a/packages/typespec-vscode/src/emit/emitter.ts +++ b/packages/typespec-vscode/src/emit/emitter.ts @@ -1,5 +1,5 @@ import vscode from "vscode"; -import { languageEmitterSettingNames } from "../const.js"; +import { EmitterSettingName } from "../const.js"; export enum EmitterKind { Schema = "schema", @@ -16,9 +16,8 @@ export interface Emitter { const extensionConfig = vscode.workspace.getConfiguration(); -function getEmitter(language: string): Emitter { - const packageFullName: string = - extensionConfig.get(languageEmitterSettingNames[language] ?? "") ?? ""; +function getEmitter(kind: EmitterKind, emitter: Emitter): Emitter { + const packageFullName: string = emitter.package ?? ""; const index = packageFullName.lastIndexOf("@"); let version = undefined; let packageName = packageFullName; @@ -28,13 +27,16 @@ function getEmitter(language: string): Emitter { } return { - language: language, + language: emitter.language, package: packageName, version: version, - kind: EmitterKind.Client, + kind: kind, }; } -export const clientEmitters: ReadonlyArray = Object.keys(languageEmitterSettingNames).map( - (lang) => getEmitter(lang), -); +export function getRegisterEmitters(kind: EmitterKind): ReadonlyArray { + const emitters: ReadonlyArray = + extensionConfig.get(EmitterSettingName[kind] ?? "") ?? []; + + return emitters.map((emitter) => getEmitter(kind, emitter)); +} diff --git a/packages/typespec-vscode/src/extension.ts b/packages/typespec-vscode/src/extension.ts index 0b61ec379d..2cc690f552 100644 --- a/packages/typespec-vscode/src/extension.ts +++ b/packages/typespec-vscode/src/extension.ts @@ -1,6 +1,6 @@ import vscode, { commands, ExtensionContext } from "vscode"; import { SettingName } from "./const.js"; -import { doEmit, emitCode } from "./emit/emit.js"; +import { emitCode } from "./emit/emit.js"; import { ExtensionLogListener } from "./log/extension-log-listener.js"; import logger from "./log/logger.js"; import { TypeSpecLogOutputChannel } from "./log/typespec-log-output-channel.js"; @@ -32,20 +32,6 @@ export async function activate(context: ExtensionContext) { }), ); - /* code generation command. */ - context.subscriptions.push( - commands.registerCommand("typespec.generateSDK", async (uri: vscode.Uri) => { - await vscode.window.withProgress( - { - location: vscode.ProgressLocation.Window, - title: "Generate Client SDK...", - cancellable: false, - }, - async (progress) => await doEmit(context, uri, progress), - ); - }), - ); - /* emit command. */ context.subscriptions.push( commands.registerCommand("typespec.emit", async (uri: vscode.Uri) => { From c5e28e1db3e487f8226dce68fab0c0ca353619c7 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Thu, 12 Dec 2024 15:56:23 +0800 Subject: [PATCH 23/42] remove unused interface --- packages/typespec-vscode/src/const.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/typespec-vscode/src/const.ts b/packages/typespec-vscode/src/const.ts index 8dfb828340..40e860c63b 100644 --- a/packages/typespec-vscode/src/const.ts +++ b/packages/typespec-vscode/src/const.ts @@ -9,13 +9,6 @@ export const enum SettingName { SchemaEmitter = "typespec.schema.emitter", } -export const languageEmitterSettingNames: Record = { - DotNet: SettingName.NetEmitter, - Java: SettingName.JavaEmitter, - Python: SettingName.PythonEmitter, - JavaScript: SettingName.JsEmitter, -}; - export const EmitterSettingName: Record = { client: SettingName.ClientEmitter, server: SettingName.ServerEmitter, From 8b5ec56fcbf12bc2d030c4f1dd0eb12b428ee0de Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Thu, 12 Dec 2024 16:03:04 +0800 Subject: [PATCH 24/42] refine the label text --- packages/typespec-vscode/src/emit/emit.ts | 46 ++++++++++++++--------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/packages/typespec-vscode/src/emit/emit.ts b/packages/typespec-vscode/src/emit/emit.ts index f3f4bafab4..68dfffbc06 100644 --- a/packages/typespec-vscode/src/emit/emit.ts +++ b/packages/typespec-vscode/src/emit/emit.ts @@ -38,7 +38,7 @@ export async function doEmit( package: e.package, emitterKind: e.kind, label: e.language, - detail: `Create ${e.language} sdk from ${e.package}`, + detail: `Generate ${e.language} ${e.kind} code from ${e.package}`, picked: false, fromConfig: false, iconPath: Uri.file(context.asAbsolutePath(`./icons/${e.language.toLowerCase()}.svg`)), @@ -49,7 +49,7 @@ export async function doEmit( const all = [...registerEmitters].map((e) => toQuickPickItem(e)); const selectedEmitter = await vscode.window.showQuickPick(all, { - title: "Select the Language of the SDK", + title: "Select the Language", canPickMany: false, placeHolder: "Pick a Language", ignoreFocusOut: true, @@ -181,24 +181,36 @@ export async function doEmit( const options: Record = {}; options["emitter-output-dir"] = outputDir; - logger.info(`Generate ${selectedEmitter.language} Client SDK under ${outputDir}...`, [], { - showOutput: false, - showPopup: true, - progress: overallProgress, - }); - const compileResult = await compile(cli, mainTspFile, selectedEmitter.package, options); - if (compileResult.exitCode !== 0) { - logger.error(`Failed to generate ${selectedEmitter.language} Client SDK.`, [], { - showOutput: true, + logger.info( + `Generate ${selectedEmitter.language} ${selectedEmitter.kind} code under ${outputDir}...`, + [], + { + showOutput: false, showPopup: true, progress: overallProgress, - }); + }, + ); + const compileResult = await compile(cli, mainTspFile, selectedEmitter.package, options); + if (compileResult.exitCode !== 0) { + logger.error( + `Failed to generate ${selectedEmitter.language} ${selectedEmitter.kind} code.`, + [], + { + showOutput: true, + showPopup: true, + progress: overallProgress, + }, + ); } else { - logger.info(`complete generating ${selectedEmitter.language} Client SDK.`, [], { - showOutput: true, - showPopup: true, - progress: overallProgress, - }); + logger.info( + `complete generating ${selectedEmitter.language} ${selectedEmitter.kind} code.`, + [], + { + showOutput: true, + showPopup: true, + progress: overallProgress, + }, + ); } /*TODO: build sdk. */ From 3c333d9e3acf0187fbb57ea8353c8d029f2f800c Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Thu, 12 Dec 2024 17:03:59 +0800 Subject: [PATCH 25/42] add icon for start file --- packages/typespec-vscode/src/const.ts | 4 ---- packages/typespec-vscode/src/emit/emit.ts | 4 ++++ packages/typespec-vscode/src/typespec-utils.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/typespec-vscode/src/const.ts b/packages/typespec-vscode/src/const.ts index 40e860c63b..e79c28b5fe 100644 --- a/packages/typespec-vscode/src/const.ts +++ b/packages/typespec-vscode/src/const.ts @@ -1,9 +1,5 @@ export const enum SettingName { TspServerPath = "typespec.tsp-server.path", - NetEmitter = "typespec.emitter.net", - JavaEmitter = "typespec.emitter.java", - PythonEmitter = "typespec.emitter.python", - JsEmitter = "typespec.emitter.javascript", ClientEmitter = "typespec.client.emitter", ServerEmitter = "typespec.server.emitter", SchemaEmitter = "typespec.schema.emitter", diff --git a/packages/typespec-vscode/src/emit/emit.ts b/packages/typespec-vscode/src/emit/emit.ts index 68dfffbc06..faaf23abf2 100644 --- a/packages/typespec-vscode/src/emit/emit.ts +++ b/packages/typespec-vscode/src/emit/emit.ts @@ -237,6 +237,10 @@ export async function emitCode( return { label: filePath, path: filePath, + iconPath: { + light: Uri.file(context.asAbsolutePath(`./icons/tsp-file.light.svg`)), + dark: Uri.file(context.asAbsolutePath(`./icons/tsp-file.dark.svg`)), + }, }; }; const typespecProjectQuickPickItems: TypeSpecProjectPickItem[] = targetPathes.map((filePath) => diff --git a/packages/typespec-vscode/src/typespec-utils.ts b/packages/typespec-vscode/src/typespec-utils.ts index 144b2c1d76..e40834e5f4 100644 --- a/packages/typespec-vscode/src/typespec-utils.ts +++ b/packages/typespec-vscode/src/typespec-utils.ts @@ -43,7 +43,7 @@ export async function resolveTypeSpecCli(absolutePath: string): Promise { const isFilePath = await isFile(tspPath); const baseDir = isFilePath ? dirname(tspPath) : tspPath; - const mainTspFile = path.resolve(baseDir, "main.tsp"); + const mainTspFile = path.resolve(baseDir, StartFileName); if (await isFile(mainTspFile)) { return mainTspFile; } From 64af9ad539b88b347bc723bb758bf29d71dfdbde Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Tue, 17 Dec 2024 11:54:21 +0800 Subject: [PATCH 26/42] check dependency and resolve dependency conflict --- packages/typespec-vscode/package.json | 6 +- packages/typespec-vscode/src/emit/emit.ts | 95 ++++++++++++++--------- packages/typespec-vscode/src/npm-utils.ts | 65 +++++++++++++++- pnpm-lock.yaml | 6 ++ 4 files changed, 132 insertions(+), 40 deletions(-) diff --git a/packages/typespec-vscode/package.json b/packages/typespec-vscode/package.json index 2cec214585..59580d5481 100644 --- a/packages/typespec-vscode/package.json +++ b/packages/typespec-vscode/package.json @@ -110,7 +110,7 @@ "package": "@typespec/http-client-java" }, { - "language": "TypeScript", + "language": "JavaScript", "package": "@azure-tools/typespec-ts" }, { @@ -306,6 +306,7 @@ "@types/vscode": "~1.94.0", "@typespec/compiler": "workspace:~", "@typespec/internal-build-utils": "workspace:~", + "@types/semver": "^7.5.8", "@vitest/coverage-v8": "^2.1.5", "@vitest/ui": "^2.1.2", "@vscode/test-web": "^0.0.62", @@ -316,6 +317,7 @@ "rollup": "~4.24.0", "typescript": "~5.6.3", "vitest": "^2.1.5", - "vscode-languageclient": "~9.0.1" + "vscode-languageclient": "~9.0.1", + "semver": "^7.6.3" } } diff --git a/packages/typespec-vscode/src/emit/emit.ts b/packages/typespec-vscode/src/emit/emit.ts index faaf23abf2..8d1bd6e228 100644 --- a/packages/typespec-vscode/src/emit/emit.ts +++ b/packages/typespec-vscode/src/emit/emit.ts @@ -3,7 +3,7 @@ import path, { dirname } from "path"; import vscode, { Uri } from "vscode"; import { Executable } from "vscode-languageclient/node.js"; import logger from "../log/logger.js"; -import { InstallationAction, NpmUtil } from "../npm-utils.js"; +import { InstallationAction, npmDependencyType, NpmUtil } from "../npm-utils.js"; import { getMainTspFile, resolveTypeSpecCli, @@ -95,44 +95,69 @@ export async function doEmit( }); const npmUtil = new NpmUtil(baseDir); - const packagesToInstall: string[] = []; + const packagesToVerify: { package: string; version?: string }[] = []; + // packagesToVerify.push({ package: "@typespec/compiler" }); /* install emitter package. */ logger.info(`select ${selectedEmitter.package}`); - const { action, version } = await npmUtil.ensureNpmPackageInstall( - selectedEmitter.package, - selectedEmitter.version, - ); - /* TODO: check the dependent compiler version. */ - if (action === InstallationAction.Upgrade) { - logger.info(`Upgrading ${selectedEmitter.package} to version ${version}`); - const options = { - ok: `OK (install ${selectedEmitter.package}@${version} by 'npm install'`, - recheck: `Check again (install ${selectedEmitter.package} manually)`, - ignore: `Ignore emitter ${selectedEmitter.label}`, - cancel: "Cancel", - }; - const selected = await vscode.window.showQuickPick(Object.values(options), { - canPickMany: false, - ignoreFocusOut: true, - placeHolder: `Package '${selectedEmitter.package}' needs to be installed for emitting`, - title: `TypeSpec Emit...`, - }); - if (selected === options.ok) { - packagesToInstall.push(`${selectedEmitter.package}@${version}`); - } - } else if (action === InstallationAction.Install) { - let packageFullName = selectedEmitter.package; - if (selectedEmitter.version) { - packageFullName = `${selectedEmitter.package}@${selectedEmitter.version}`; + packagesToVerify.push({ package: selectedEmitter.package, version: selectedEmitter.version }); + for (const p of packagesToVerify) { + const { action, version } = await npmUtil.ensureNpmPackageInstall(p.package, p.version); + /* TODO: check the dependent compiler version. */ + if (action === InstallationAction.Upgrade) { + logger.info(`Upgrading ${p.package} to version ${version}`); + const options = { + ok: `OK (install ${p.package}@${version} by 'npm install'`, + recheck: `Check again (install ${p.package} manually)`, + ignore: `Ignore emitter ${p.package}`, + cancel: "Cancel", + }; + const selected = await vscode.window.showQuickPick(Object.values(options), { + canPickMany: false, + ignoreFocusOut: true, + placeHolder: `Package '${p.package}' needs to be installed for emitting`, + title: `TypeSpec Emit...`, + }); + if (selected === options.ok) { + packagesToInstall.push(`${p.package}@${version}`); + } + } else if (action === InstallationAction.Install) { + let packageFullName = p.package; + if (version) { + packageFullName = `${p.package}@${version}`; + } + logger.info(`Installing ${packageFullName}`); + /* verify dependency packages. */ + const dependenciesToInstall = await npmUtil.ensureNpmPackageDependencyInstall( + p.package, + version, + npmDependencyType.peerDependencies, + ); + logger.info(`${dependenciesToInstall}`); + for (const dependency of dependenciesToInstall) { + const options = { + ok: `OK (Upgrade ${dependency} by 'npm install'`, + recheck: `Check again (install ${p.package} manually)`, + ignore: `Ignore emitter ${p.package}`, + cancel: "Cancel", + }; + const selected = await vscode.window.showQuickPick(Object.values(options), { + canPickMany: false, + ignoreFocusOut: true, + placeHolder: `Package '${dependency}' needs to be upgraded for emitting`, + title: `TypeSpec Emit...`, + }); + if (selected === options.ok) { + packagesToInstall.push(dependency); + } + } + packagesToInstall.push(`${packageFullName}`); } - logger.info(`Installing ${packageFullName}`); - packagesToInstall.push(`${packageFullName}`); } /* npm install packages. */ if (packagesToInstall.length > 0) { - logger.info(`Installing ${packagesToInstall.join("\n\n")}`, [], { + logger.info(`Installing ${packagesToInstall.join("\n\n")}, baseDir: ${baseDir}`, [], { showOutput: true, showPopup: true, progress: overallProgress, @@ -182,10 +207,10 @@ export async function doEmit( const options: Record = {}; options["emitter-output-dir"] = outputDir; logger.info( - `Generate ${selectedEmitter.language} ${selectedEmitter.kind} code under ${outputDir}...`, + `Generate ${selectedEmitter.language} ${selectedEmitter.emitterKind} code under ${outputDir}...`, [], { - showOutput: false, + showOutput: true, showPopup: true, progress: overallProgress, }, @@ -193,7 +218,7 @@ export async function doEmit( const compileResult = await compile(cli, mainTspFile, selectedEmitter.package, options); if (compileResult.exitCode !== 0) { logger.error( - `Failed to generate ${selectedEmitter.language} ${selectedEmitter.kind} code.`, + `Failed to generate ${selectedEmitter.language} ${selectedEmitter.emitterKind} code.`, [], { showOutput: true, @@ -203,7 +228,7 @@ export async function doEmit( ); } else { logger.info( - `complete generating ${selectedEmitter.language} ${selectedEmitter.kind} code.`, + `complete generating ${selectedEmitter.language} ${selectedEmitter.emitterKind} code.`, [], { showOutput: true, diff --git a/packages/typespec-vscode/src/npm-utils.ts b/packages/typespec-vscode/src/npm-utils.ts index 34dc50573a..da8b1583c4 100644 --- a/packages/typespec-vscode/src/npm-utils.ts +++ b/packages/typespec-vscode/src/npm-utils.ts @@ -1,5 +1,6 @@ import fs from "fs"; import path from "path"; +import semver from "semver"; import logger from "./log/logger.js"; import { ExecOutput, executionEvents, loadModule, promisifySpawn } from "./utils.js"; @@ -10,6 +11,12 @@ export enum InstallationAction { Cancel = "Cancel", } +export enum npmDependencyType { + dependencies = "dependencies", + peerDependencies = "peerDependencies", + devDependencies = "devDependencies", +} + export interface NpmPackageInfo { name: string; version?: string; @@ -36,17 +43,69 @@ export class NpmUtil { public async ensureNpmPackageInstall( packageName: string, version?: string, - ): Promise<{ action: InstallationAction; version: string }> { + ): Promise<{ action: InstallationAction; version?: string }> { const { installed: isPackageInstalled, version: installedVersion } = await this.isPackageInstalled(packageName); if (isPackageInstalled) { if (version && installedVersion !== version) { return { action: InstallationAction.Upgrade, version: version }; } - return { action: InstallationAction.Cancel, version: installedVersion ?? "" }; + return { action: InstallationAction.Cancel, version: installedVersion }; } else { - return { action: InstallationAction.Install, version: version ?? "" }; + return { action: InstallationAction.Install, version: version }; + } + } + + public async ensureNpmPackageDependencyInstall( + packageName: string, + version?: string, + dependencyType: npmDependencyType = npmDependencyType.dependencies, + options: any = {}, + on?: executionEvents, + ): Promise { + const dependenciesToInstall: string[] = []; + let packageFullName = packageName; + if (version) { + packageFullName = `${packageName}@${version}`; } + + /* get dependencies. */ + const dependenciesResult = await promisifySpawn( + "npm", + ["view", packageFullName, dependencyType], + { cwd: this.cwd }, + on, + ); + + if (dependenciesResult.exitCode === 0) { + try { + // Remove all newline characters + let dependenciesJsonStr = dependenciesResult.stdout.trim(); + dependenciesJsonStr = dependenciesJsonStr.replace(/\n/g, ""); + + // Change single quotes to double quotes + dependenciesJsonStr = dependenciesJsonStr.replace(/'/g, '"'); + const json = JSON.parse(dependenciesJsonStr); + for (const [key, value] of Object.entries(json)) { + const { installed, version: installedVersion } = await this.isPackageInstalled(key); + if (installed && installedVersion) { + if (!this.isValidVersion(installedVersion, value as string)) { + dependenciesToInstall.push(`${key}@latest`); + } + } + } + } catch (err) { + if (on && on.onError) { + on.onError(err, "", ""); + } + } + } + + return dependenciesToInstall; + } + + private isValidVersion(version: string, range: string): boolean { + return semver.satisfies(version, range); } private async isPackageInstalled( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b2dbcf56d8..47d1467efb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1886,6 +1886,9 @@ importers: '@types/node': specifier: ~22.7.9 version: 22.7.9 + '@types/semver': + specifier: ^7.5.8 + version: 7.5.8 '@types/vscode': specifier: ~1.94.0 version: 1.94.0 @@ -1916,6 +1919,9 @@ importers: rollup: specifier: ~4.24.0 version: 4.24.4 + semver: + specifier: ^7.6.3 + version: 7.6.3 typescript: specifier: ~5.6.3 version: 5.6.3 From 21536fe130f5a166c3c7ee29fdc6bdb106a2f57f Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Tue, 17 Dec 2024 20:55:05 +0800 Subject: [PATCH 27/42] format --- packages/typespec-vscode/src/emit/emit.ts | 38 +++++++++++++++-------- packages/typespec-vscode/src/npm-utils.ts | 2 +- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/packages/typespec-vscode/src/emit/emit.ts b/packages/typespec-vscode/src/emit/emit.ts index 0f88688fcc..11e45c57c2 100644 --- a/packages/typespec-vscode/src/emit/emit.ts +++ b/packages/typespec-vscode/src/emit/emit.ts @@ -215,20 +215,32 @@ export async function doEmit( progress: overallProgress, }, ); - const compileResult = await compile(cli, mainTspFile, selectedEmitter.package, options); - if (compileResult.exitCode !== 0) { - logger.error( - `Failed to generate ${selectedEmitter.language} ${selectedEmitter.emitterKind} code.`, - [], - { - showOutput: true, - showPopup: true, - progress: overallProgress, - }, - ); - } else { + try { + const compileResult = await compile(cli, mainTspFile, selectedEmitter.package, options); + if (compileResult.exitCode !== 0) { + logger.error( + `Failed to generate ${selectedEmitter.language} ${selectedEmitter.emitterKind} code.`, + [], + { + showOutput: true, + showPopup: true, + progress: overallProgress, + }, + ); + } else { + logger.info( + `complete generating ${selectedEmitter.language} ${selectedEmitter.emitterKind} code.`, + [], + { + showOutput: true, + showPopup: true, + progress: overallProgress, + }, + ); + } + } catch (err) { logger.info( - `complete generating ${selectedEmitter.language} ${selectedEmitter.emitterKind} code.`, + `Exception occurred when generating ${selectedEmitter.language} ${selectedEmitter.emitterKind} code. ${err}`, [], { showOutput: true, diff --git a/packages/typespec-vscode/src/npm-utils.ts b/packages/typespec-vscode/src/npm-utils.ts index f7e6d3b18c..d558beb232 100644 --- a/packages/typespec-vscode/src/npm-utils.ts +++ b/packages/typespec-vscode/src/npm-utils.ts @@ -73,7 +73,7 @@ export class NpmUtil { const dependenciesResult = await spawnExecution( "npm", ["view", packageFullName, dependencyType], - this.cwd , + this.cwd, on, ); From c88335364dd26cdb097b55cc60b534e34392c262 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Tue, 17 Dec 2024 20:59:59 +0800 Subject: [PATCH 28/42] remove unused code --- packages/typespec-vscode/src/emit/emit.ts | 2 +- packages/typespec-vscode/src/typespec-utils.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/typespec-vscode/src/emit/emit.ts b/packages/typespec-vscode/src/emit/emit.ts index 11e45c57c2..3d3b8ac74d 100644 --- a/packages/typespec-vscode/src/emit/emit.ts +++ b/packages/typespec-vscode/src/emit/emit.ts @@ -97,7 +97,7 @@ export async function doEmit( const npmUtil = new NpmUtil(baseDir); const packagesToInstall: string[] = []; const packagesToVerify: { package: string; version?: string }[] = []; - // packagesToVerify.push({ package: "@typespec/compiler" }); + /* install emitter package. */ logger.info(`select ${selectedEmitter.package}`); packagesToVerify.push({ package: selectedEmitter.package, version: selectedEmitter.version }); diff --git a/packages/typespec-vscode/src/typespec-utils.ts b/packages/typespec-vscode/src/typespec-utils.ts index e40834e5f4..f111f8b912 100644 --- a/packages/typespec-vscode/src/typespec-utils.ts +++ b/packages/typespec-vscode/src/typespec-utils.ts @@ -30,7 +30,6 @@ export async function resolveTypeSpecCli(absolutePath: string): Promise exe.name === "tsp"); const cmdPath = path.resolve(modelInfo.path, "cmd/tsp.js"); return { command: "node", From 69319805c9594ad4e7681c5115802a3e52321409 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Wed, 18 Dec 2024 09:40:32 +0800 Subject: [PATCH 29/42] refine text --- packages/typespec-vscode/src/emit/emit.ts | 141 +++++++++++++--------- packages/typespec-vscode/src/extension.ts | 2 +- packages/typespec-vscode/src/types.ts | 1 + 3 files changed, 84 insertions(+), 60 deletions(-) diff --git a/packages/typespec-vscode/src/emit/emit.ts b/packages/typespec-vscode/src/emit/emit.ts index 3d3b8ac74d..2c0df3bee0 100644 --- a/packages/typespec-vscode/src/emit/emit.ts +++ b/packages/typespec-vscode/src/emit/emit.ts @@ -2,6 +2,7 @@ import path, { dirname } from "path"; import vscode, { Uri } from "vscode"; import { Executable } from "vscode-languageclient/node.js"; +import { StartFileName } from "../const.js"; import logger from "../log/logger.js"; import { InstallationAction, npmDependencyType, NpmUtil } from "../npm-utils.js"; import { @@ -66,12 +67,12 @@ export async function doEmit( /* TODO: verify the sdk runtime installation. */ /* inform to install needed runtime. */ - const { valid, required } = await check(`${baseDir}/main.tsp`); + const { valid, required } = await check(`${baseDir}/${StartFileName}`, selectedEmitter.package); if (!valid) { const toInstall = required.map((e) => e.name).join(", "); await vscode.window .showInformationMessage( - `Please install the required runtime for the selected emitters\n\n.net (>= 0.8.0) ${toInstall}`, + `Please install the required runtime for the selected emitters\n\n. ${toInstall}`, "OK", ) .then((selection) => { @@ -86,8 +87,6 @@ export async function doEmit( return; } - /* TODO: verify packages to install. */ - logger.info("npm install...", [], { showOutput: false, showPopup: false, @@ -96,68 +95,89 @@ export async function doEmit( const npmUtil = new NpmUtil(baseDir); const packagesToInstall: string[] = []; - const packagesToVerify: { package: string; version?: string }[] = []; /* install emitter package. */ logger.info(`select ${selectedEmitter.package}`); - packagesToVerify.push({ package: selectedEmitter.package, version: selectedEmitter.version }); - for (const p of packagesToVerify) { - const { action, version } = await npmUtil.ensureNpmPackageInstall(p.package, p.version); - /* TODO: check the dependent compiler version. */ - if (action === InstallationAction.Upgrade) { - logger.info(`Upgrading ${p.package} to version ${version}`); + const { action, version } = await npmUtil.ensureNpmPackageInstall( + selectedEmitter.package, + selectedEmitter.version, + ); + + if (action === InstallationAction.Upgrade) { + logger.info(`Upgrading ${selectedEmitter.package} to version ${version}`); + const options = { + ok: `OK (install ${selectedEmitter.package}@${version} by 'npm install'`, + recheck: `Check again (install ${selectedEmitter.package} manually)`, + ignore: `Ignore emitter ${selectedEmitter.package}`, + cancel: "Cancel", + }; + const selected = await vscode.window.showQuickPick(Object.values(options), { + canPickMany: false, + ignoreFocusOut: true, + placeHolder: `Package '${selectedEmitter.package}' needs to be installed for emitting`, + title: `TypeSpec Emit...`, + }); + if (selected === options.ok) { + packagesToInstall.push(`${selectedEmitter.package}@${version}`); + } else { + logger.info( + `Need to manually install the package ${selectedEmitter.package}@${version}. Emit canceled.`, + [], + { + showOutput: false, + showPopup: true, + progress: overallProgress, + }, + ); + return; + } + } else if (action === InstallationAction.Install) { + let packageFullName = selectedEmitter.package; + if (version) { + packageFullName = `${selectedEmitter.package}@${version}`; + } + logger.info(`Installing ${packageFullName}`); + /* verify dependency packages. */ + const dependenciesToInstall = await npmUtil.ensureNpmPackageDependencyInstall( + selectedEmitter.package, + version, + npmDependencyType.peerDependencies, + ); + logger.info(`${dependenciesToInstall}`); + for (const dependency of dependenciesToInstall) { const options = { - ok: `OK (install ${p.package}@${version} by 'npm install'`, - recheck: `Check again (install ${p.package} manually)`, - ignore: `Ignore emitter ${p.package}`, + ok: `OK (Upgrade ${dependency} by 'npm install'`, + recheck: `Check again (install ${dependency} manually)`, + ignore: `Ignore ${dependency}`, cancel: "Cancel", }; const selected = await vscode.window.showQuickPick(Object.values(options), { canPickMany: false, ignoreFocusOut: true, - placeHolder: `Package '${p.package}' needs to be installed for emitting`, + placeHolder: `Package '${dependency}' needs to be upgraded for emitting`, title: `TypeSpec Emit...`, }); if (selected === options.ok) { - packagesToInstall.push(`${p.package}@${version}`); - } - } else if (action === InstallationAction.Install) { - let packageFullName = p.package; - if (version) { - packageFullName = `${p.package}@${version}`; - } - logger.info(`Installing ${packageFullName}`); - /* verify dependency packages. */ - const dependenciesToInstall = await npmUtil.ensureNpmPackageDependencyInstall( - p.package, - version, - npmDependencyType.peerDependencies, - ); - logger.info(`${dependenciesToInstall}`); - for (const dependency of dependenciesToInstall) { - const options = { - ok: `OK (Upgrade ${dependency} by 'npm install'`, - recheck: `Check again (install ${p.package} manually)`, - ignore: `Ignore emitter ${p.package}`, - cancel: "Cancel", - }; - const selected = await vscode.window.showQuickPick(Object.values(options), { - canPickMany: false, - ignoreFocusOut: true, - placeHolder: `Package '${dependency}' needs to be upgraded for emitting`, - title: `TypeSpec Emit...`, - }); - if (selected === options.ok) { - packagesToInstall.push(dependency); - } + packagesToInstall.push(dependency); + } else { + logger.info( + `Need to manually install the dependency package ${dependency}@latest. Emit canceled.`, + [], + { + showOutput: false, + showPopup: true, + progress: overallProgress, + }, + ); + return; } - packagesToInstall.push(`${packageFullName}`); } + packagesToInstall.push(`${packageFullName}`); } /* npm install packages. */ if (packagesToInstall.length > 0) { - logger.info(`Installing ${packagesToInstall.join("\n\n")}, baseDir: ${baseDir}`, [], { + logger.info(`Installing ${packagesToInstall.join("\n\n")} under ${baseDir}`, [], { showOutput: true, showPopup: true, progress: overallProgress, @@ -167,17 +187,17 @@ export async function doEmit( onStdioOut: toOutput, onStdioError: toError, }); - logger.info("completed install..."); if (npmInstallResult.exitCode !== 0) { - logger.error(`Error occurred when installing packages: ${npmInstallResult.stderr}`, [], { + logger.error(`Error occurred when installing packages.`, [`${npmInstallResult.stderr}`], { showOutput: true, showPopup: true, progress: overallProgress, }); return; } + logger.info("completed install..."); } catch (err) { - logger.error(`Exception occurred when installing packages: ${err}`, [], { + logger.error(`Exception occurred when installing packages.`, [err], { showOutput: true, showPopup: true, progress: overallProgress, @@ -207,11 +227,11 @@ export async function doEmit( const options: Record = {}; options["emitter-output-dir"] = outputDir; logger.info( - `Generate ${selectedEmitter.language} ${selectedEmitter.emitterKind} code under ${outputDir}...`, + `Start to generate ${selectedEmitter.language} ${selectedEmitter.emitterKind} code under ${outputDir}...`, [], { showOutput: true, - showPopup: true, + showPopup: false, progress: overallProgress, }, ); @@ -239,9 +259,9 @@ export async function doEmit( ); } } catch (err) { - logger.info( - `Exception occurred when generating ${selectedEmitter.language} ${selectedEmitter.emitterKind} code. ${err}`, - [], + logger.error( + `Exception occurred when generating ${selectedEmitter.language} ${selectedEmitter.emitterKind} code.`, + [err], { showOutput: true, showPopup: true, @@ -261,9 +281,9 @@ export async function emitCode( let tspProjectFile: string = ""; if (!uri) { const targetPathes = await TraverseMainTspFileInWorkspace(); - logger.info(`Found ${targetPathes.length} main.tsp files`); + logger.info(`Found ${targetPathes.length} ${StartFileName} files`); if (targetPathes.length === 0) { - logger.info("No main.tsp file found. Emit canceled.", [], { + logger.info("No main tsp file found. Emit canceled.", [], { showOutput: false, showPopup: true, progress: overallProgress, @@ -375,7 +395,10 @@ export async function compile( }); } -export async function check(startFile: string): Promise<{ +export async function check( + startFile: string, + emitter: string, +): Promise<{ valid: boolean; required: { name: string; version: string }[]; }> { diff --git a/packages/typespec-vscode/src/extension.ts b/packages/typespec-vscode/src/extension.ts index da8edc5860..d14b3a02fe 100644 --- a/packages/typespec-vscode/src/extension.ts +++ b/packages/typespec-vscode/src/extension.ts @@ -47,7 +47,7 @@ export async function activate(context: ExtensionContext) { /* emit command. */ context.subscriptions.push( - commands.registerCommand("typespec.emit", async (uri: vscode.Uri) => { + commands.registerCommand(CommandName.EmitCode, async (uri: vscode.Uri) => { await vscode.window.withProgress( { location: vscode.ProgressLocation.Window, diff --git a/packages/typespec-vscode/src/types.ts b/packages/typespec-vscode/src/types.ts index c13f5ce9c1..3300121ff0 100644 --- a/packages/typespec-vscode/src/types.ts +++ b/packages/typespec-vscode/src/types.ts @@ -12,6 +12,7 @@ export const enum CommandName { InstallGlobalCompilerCli = "typespec.installGlobalCompilerCli", CreateProject = "typespec.createProject", OpenUrl = "typespec.openUrl", + EmitCode = "typespec.emit", } export interface InstallGlobalCliCommandArgs { From 86372ee7cfc0c4989fc94da4685f7c996376e1ad Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Wed, 18 Dec 2024 11:49:17 +0800 Subject: [PATCH 30/42] change the package install options --- packages/typespec-vscode/src/emit/emit.ts | 15 +++++++++------ packages/typespec-vscode/src/npm-utils.ts | 7 +++++-- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/packages/typespec-vscode/src/emit/emit.ts b/packages/typespec-vscode/src/emit/emit.ts index 2c0df3bee0..f55776b28e 100644 --- a/packages/typespec-vscode/src/emit/emit.ts +++ b/packages/typespec-vscode/src/emit/emit.ts @@ -108,17 +108,22 @@ export async function doEmit( const options = { ok: `OK (install ${selectedEmitter.package}@${version} by 'npm install'`, recheck: `Check again (install ${selectedEmitter.package} manually)`, - ignore: `Ignore emitter ${selectedEmitter.package}`, - cancel: "Cancel", + ignore: `Ignore (don't upgrade emitter ${selectedEmitter.package})`, }; const selected = await vscode.window.showQuickPick(Object.values(options), { canPickMany: false, ignoreFocusOut: true, - placeHolder: `Package '${selectedEmitter.package}' needs to be installed for emitting`, + placeHolder: `Package '${selectedEmitter.package}' needs to be upgraded for emitting`, title: `TypeSpec Emit...`, }); if (selected === options.ok) { packagesToInstall.push(`${selectedEmitter.package}@${version}`); + } else if (selected === options.ignore) { + logger.info(`Ignore upgrading emitter ${selectedEmitter.package} for emitting`, [], { + showOutput: false, + showPopup: false, + progress: overallProgress, + }); } else { logger.info( `Need to manually install the package ${selectedEmitter.package}@${version}. Emit canceled.`, @@ -146,10 +151,8 @@ export async function doEmit( logger.info(`${dependenciesToInstall}`); for (const dependency of dependenciesToInstall) { const options = { - ok: `OK (Upgrade ${dependency} by 'npm install'`, + ok: `OK (Upgrade ${dependency} by 'npm install)'`, recheck: `Check again (install ${dependency} manually)`, - ignore: `Ignore ${dependency}`, - cancel: "Cancel", }; const selected = await vscode.window.showQuickPick(Object.values(options), { canPickMany: false, diff --git a/packages/typespec-vscode/src/npm-utils.ts b/packages/typespec-vscode/src/npm-utils.ts index d558beb232..e5d7196487 100644 --- a/packages/typespec-vscode/src/npm-utils.ts +++ b/packages/typespec-vscode/src/npm-utils.ts @@ -48,9 +48,12 @@ export class NpmUtil { await this.isPackageInstalled(packageName); if (isPackageInstalled) { if (version && installedVersion !== version) { - return { action: InstallationAction.Upgrade, version: version }; + return { + action: InstallationAction.Upgrade, + version: version, + }; } - return { action: InstallationAction.Cancel, version: installedVersion }; + return { action: InstallationAction.Skip, version: installedVersion }; } else { return { action: InstallationAction.Install, version: version }; } From b121717b47d69192c5986927b69c80070c3fdf6d Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Wed, 18 Dec 2024 13:34:27 +0800 Subject: [PATCH 31/42] resolve comments --- packages/typespec-vscode/package.json | 3 - packages/typespec-vscode/src/task-provider.ts | 5 +- pnpm-lock.yaml | 69 +++++++++---------- 3 files changed, 37 insertions(+), 40 deletions(-) diff --git a/packages/typespec-vscode/package.json b/packages/typespec-vscode/package.json index 623efce53e..d1c8f91131 100644 --- a/packages/typespec-vscode/package.json +++ b/packages/typespec-vscode/package.json @@ -336,9 +336,6 @@ "test:e2e": "pnpm test:web", "test:web": "vscode-test-web --extensionDevelopmentPath=. --headless --extensionTestsPath=dist/test/web/suite.js ./test/web/data" }, - "dependencies": { - "@typespec/compiler": "workspace:~" - }, "devDependencies": { "@rollup/plugin-commonjs": "~28.0.0", "@rollup/plugin-node-resolve": "~15.3.0", diff --git a/packages/typespec-vscode/src/task-provider.ts b/packages/typespec-vscode/src/task-provider.ts index 39d8da558e..c0f0c16ee5 100644 --- a/packages/typespec-vscode/src/task-provider.ts +++ b/packages/typespec-vscode/src/task-provider.ts @@ -1,6 +1,7 @@ import { resolve } from "path"; import vscode, { workspace } from "vscode"; import { Executable } from "vscode-languageclient/node.js"; +import { StartFileName } from "./const.js"; import logger from "./log/logger.js"; import { normalizeSlashes } from "./path-utils.js"; import { resolveTypeSpecCli } from "./tsp-executable-resolver.js"; @@ -11,13 +12,13 @@ export function createTaskProvider() { provideTasks: async () => { logger.info("Providing tsp tasks"); const targetPathes = await vscode.workspace - .findFiles("**/main.tsp", "**/node_modules/**") + .findFiles(`**/${StartFileName}`, "**/node_modules/**") .then((uris) => uris .filter((uri) => uri.scheme === "file" && !uri.fsPath.includes("node_modules")) .map((uri) => normalizeSlashes(uri.fsPath)), ); - logger.info(`Found ${targetPathes.length} main.tsp files`); + logger.info(`Found ${targetPathes.length} ${StartFileName} files`); const tasks: vscode.Task[] = []; for (const targetPath of targetPathes) { tasks.push(...(await createBuiltInTasks(targetPath))); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 47d1467efb..4d84583b8d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -43,7 +43,7 @@ importers: version: 22.7.9 '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) + version: 2.1.5(vitest@2.1.5) c8: specifier: ^10.1.2 version: 10.1.2 @@ -67,7 +67,7 @@ importers: version: 56.0.1(eslint@9.15.0(jiti@1.21.6)) eslint-plugin-vitest: specifier: ^0.5.4 - version: 0.5.4(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) + version: 0.5.4(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)(vitest@2.1.5) micromatch: specifier: ^4.0.8 version: 4.0.8 @@ -151,7 +151,7 @@ importers: version: link:../compiler '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) + version: 2.1.5(vitest@2.1.5) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -200,7 +200,7 @@ importers: version: 7.5.8 '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) + version: 2.1.5(vitest@2.1.5) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -258,7 +258,7 @@ importers: version: 17.0.33 '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) + version: 2.1.5(vitest@2.1.5) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -346,7 +346,7 @@ importers: version: link:../internal-build-utils '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) + version: 2.1.5(vitest@2.1.5) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -398,7 +398,7 @@ importers: version: 8.15.0 '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) + version: 2.1.5(vitest@2.1.5) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -434,7 +434,7 @@ importers: version: link:../tspd '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) + version: 2.1.5(vitest@2.1.5) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -504,7 +504,7 @@ importers: version: 4.3.3(vite@5.4.11(@types/node@22.7.9)) '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) + version: 2.1.5(vitest@2.1.5) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -549,7 +549,7 @@ importers: version: link:../tspd '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) + version: 2.1.5(vitest@2.1.5) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -598,7 +598,7 @@ importers: version: link:../tspd '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) + version: 2.1.5(vitest@2.1.5) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -712,7 +712,7 @@ importers: version: 17.0.33 '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) + version: 2.1.5(vitest@2.1.5) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -755,7 +755,7 @@ importers: version: link:../tspd '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) + version: 2.1.5(vitest@2.1.5) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -788,7 +788,7 @@ importers: version: link:../compiler '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) + version: 2.1.5(vitest@2.1.5) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -816,7 +816,7 @@ importers: version: 22.7.9 '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) + version: 2.1.5(vitest@2.1.5) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -858,7 +858,7 @@ importers: version: link:../tspd '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) + version: 2.1.5(vitest@2.1.5) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -919,7 +919,7 @@ importers: version: link:../xml '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) + version: 2.1.5(vitest@2.1.5) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -1161,7 +1161,7 @@ importers: version: 4.3.3(vite@5.4.11(@types/node@22.7.9)) '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) + version: 2.1.5(vitest@2.1.5) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -1237,7 +1237,7 @@ importers: version: link:../tspd '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) + version: 2.1.5(vitest@2.1.5) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -1298,7 +1298,7 @@ importers: version: 4.3.3(vite@5.4.11(@types/node@22.7.9)) '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) + version: 2.1.5(vitest@2.1.5) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -1343,7 +1343,7 @@ importers: version: link:../tspd '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) + version: 2.1.5(vitest@2.1.5) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -1413,7 +1413,7 @@ importers: version: link:../internal-build-utils '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) + version: 2.1.5(vitest@2.1.5) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -1507,7 +1507,7 @@ importers: version: 17.0.33 '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) + version: 2.1.5(vitest@2.1.5) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -1729,7 +1729,7 @@ importers: version: link:../tspd '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) + version: 2.1.5(vitest@2.1.5) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -1762,7 +1762,7 @@ importers: version: link:../tspd '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) + version: 2.1.5(vitest@2.1.5) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -1830,7 +1830,7 @@ importers: version: link:../prettier-plugin-typespec '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) + version: 2.1.5(vitest@2.1.5) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -1866,10 +1866,6 @@ importers: version: link:../typespec-vscode packages/typespec-vscode: - dependencies: - '@typespec/compiler': - specifier: workspace:~ - version: link:../compiler devDependencies: '@rollup/plugin-commonjs': specifier: ~28.0.0 @@ -1892,12 +1888,15 @@ importers: '@types/vscode': specifier: ~1.94.0 version: 1.94.0 + '@typespec/compiler': + specifier: workspace:~ + version: link:../compiler '@typespec/internal-build-utils': specifier: workspace:~ version: link:../internal-build-utils '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) + version: 2.1.5(vitest@2.1.5) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -1948,7 +1947,7 @@ importers: version: link:../tspd '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) + version: 2.1.5(vitest@2.1.5) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -1981,7 +1980,7 @@ importers: version: link:../tspd '@vitest/coverage-v8': specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)) + version: 2.1.5(vitest@2.1.5) '@vitest/ui': specifier: ^2.1.2 version: 2.1.5(vitest@2.1.5) @@ -16378,7 +16377,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitest/coverage-v8@2.1.5(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0))': + '@vitest/coverage-v8@2.1.5(vitest@2.1.5)': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 0.2.3 @@ -18557,7 +18556,7 @@ snapshots: semver: 7.6.3 strip-indent: 3.0.0 - eslint-plugin-vitest@0.5.4(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)(vitest@2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0)): + eslint-plugin-vitest@0.5.4(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)(vitest@2.1.5): dependencies: '@typescript-eslint/utils': 7.18.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3) eslint: 9.15.0(jiti@1.21.6) From 71295271e96811605e09536bfd330666eee994aa Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Wed, 18 Dec 2024 15:35:44 +0800 Subject: [PATCH 32/42] remove options parameter --- packages/typespec-vscode/src/npm-utils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/typespec-vscode/src/npm-utils.ts b/packages/typespec-vscode/src/npm-utils.ts index e5d7196487..542f270e2d 100644 --- a/packages/typespec-vscode/src/npm-utils.ts +++ b/packages/typespec-vscode/src/npm-utils.ts @@ -63,7 +63,6 @@ export class NpmUtil { packageName: string, version?: string, dependencyType: npmDependencyType = npmDependencyType.dependencies, - options: any = {}, on?: spawnExecutionEvents, ): Promise { const dependenciesToInstall: string[] = []; From e96e073c4dac80d1b3e0b0436a8af88bf4b9bcda Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Wed, 18 Dec 2024 16:19:48 +0800 Subject: [PATCH 33/42] refine text --- packages/typespec-vscode/package.json | 104 +++++++------------ packages/typespec-vscode/src/const.ts | 8 -- packages/typespec-vscode/src/emit/emit.ts | 80 +++++++------- packages/typespec-vscode/src/emit/emitter.ts | 10 +- packages/typespec-vscode/src/extension.ts | 4 +- packages/typespec-vscode/src/types.ts | 6 +- 6 files changed, 86 insertions(+), 126 deletions(-) diff --git a/packages/typespec-vscode/package.json b/packages/typespec-vscode/package.json index d1c8f91131..d789c2cca2 100644 --- a/packages/typespec-vscode/package.json +++ b/packages/typespec-vscode/package.json @@ -109,7 +109,7 @@ "default": "off", "description": "Define whether/how the TypeSpec language server should send traces to client. For the traces to show properly in vscode Output, make sure 'Log Level' is also set to 'Trace' so that they won't be filtered at client side, which can be set through 'Developer: Set Log Level...' command." }, - "typespec.client.emitter": { + "typespec.emitters": { "scope": "window", "type": "array", "items": { @@ -122,99 +122,65 @@ "Java", "JavaScript", "Python", - "Go" + "Go", + "OpenAPI3", + "ProtoBuf", + "JsonSchema" ], "description": "Define the language the emitter will emit." }, "package": { "type": "string", "description": "Define the emitter package.\n\nExample (with version): @typespec/http-client-csharp@1.0.0\n\nExample (without version): @typespec/http-client-csharp" + }, + "kind": { + "type": "string", + "enum": [ + "client", + "server", + "schema" + ], + "description": "Define the emitter kind." } } }, "default": [ { "language": "DotNet", - "package": "@typespec/http-client-csharp" + "package": "@typespec/http-client-csharp", + "kind": "client" }, { "language": "Java", - "package": "@typespec/http-client-java" + "package": "@typespec/http-client-java", + "kind": "client" }, { "language": "JavaScript", - "package": "@azure-tools/typespec-ts" + "package": "@azure-tools/typespec-ts", + "kind": "client" }, { "language": "Python", - "package": "@typespec/http-client-python" - } - ], - "description": "Define the emitter for a language client sdk generation." - }, - "typespec.server.emitter": { - "scope": "window", - "type": "array", - "items": { - "type": "object", - "properties": { - "language": { - "type": "string", - "enum": [ - "DotNet", - "Java", - "JavaScript", - "Python", - "Go" - ], - "description": "Define the language for the emitter." - }, - "package": { - "type": "string", - "description": "Define the emitter package.\n\nExample (with version): @typespec/http-server-csharp@1.0.0\n\nExample (without version): @typespec/http-server-csharp" - } - } - }, - "default": [ + "package": "@typespec/http-client-python", + "kind": "client" + }, { "language": "DotNet", - "package": "@typespec/http-server-csharp" + "package": "@typespec/http-server-csharp", + "kind": "server" }, { "language": "JavaScript", - "package": "@typespec/http-server-javascript" - } - ], - "description": "Define the emitter for server code generation." - }, - "typespec.schema.emitter": { - "scope": "window", - "type": "array", - "items": { - "type": "object", - "properties": { - "language": { - "type": "string", - "enum": [ - "OpenAPI3", - "ProtoBuf", - "JsonSchema" - ], - "description": "Define the language for the emitter." - }, - "package": { - "type": "string", - "description": "Define the emitter package.\n\nExample (with version): @typespec/openapi3@1.0.0\n\nExample (without version): @typespec/openapi3" - } - } - }, - "default": [ + "package": "@typespec/http-server-javascript", + "kind": "server" + }, { "language": "OpenAPI3", - "package": "@typespec/openapi3" + "package": "@typespec/openapi3", + "kind": "schema" } - ], - "description": "Define the emitter for a schema." + ] } } } @@ -249,8 +215,8 @@ "category": "TypeSpec" }, { - "command": "typespec.emit", - "title": "TypeSpec: Emit Code", + "command": "typespec.generate", + "title": "Generate from TypeSpec", "category": "TypeSpec" }, { @@ -267,14 +233,14 @@ "menus": { "explorer/context": [ { - "command": "typespec.emit", + "command": "typespec.generate", "when": "explorerResourceIsFolder || resourceLangId == typespec", "group": "code_generation" } ], "editor/context": [ { - "command": "typespec.emit", + "command": "typespec.generate", "when": "resourceLangId == typespec", "group": "code_generation" } diff --git a/packages/typespec-vscode/src/const.ts b/packages/typespec-vscode/src/const.ts index 9216ab4e34..e7b898f945 100644 --- a/packages/typespec-vscode/src/const.ts +++ b/packages/typespec-vscode/src/const.ts @@ -1,9 +1 @@ -import { SettingName } from "./types.js"; - -export const EmitterSettingName: Record = { - client: SettingName.ClientEmitter, - server: SettingName.ServerEmitter, - schema: SettingName.SchemaEmitter, -}; - export const StartFileName = "main.tsp"; diff --git a/packages/typespec-vscode/src/emit/emit.ts b/packages/typespec-vscode/src/emit/emit.ts index f55776b28e..c6fe8c3358 100644 --- a/packages/typespec-vscode/src/emit/emit.ts +++ b/packages/typespec-vscode/src/emit/emit.ts @@ -24,7 +24,7 @@ export async function doEmit( ) { if (!mainTspFile || !(await isFile(mainTspFile))) { logger.info( - "Invalid typespec project. There is no main tsp file in the project. Emit canceled.", + "Invalid typespec project. There is no main tsp file in the project. Generating Cancelled.", [], { showOutput: false, showPopup: true, progress: overallProgress }, ); @@ -50,16 +50,16 @@ export async function doEmit( const all = [...registerEmitters].map((e) => toQuickPickItem(e)); const selectedEmitter = await vscode.window.showQuickPick(all, { - title: "Select the Language", + title: "Select a Language", canPickMany: false, placeHolder: "Pick a Language", ignoreFocusOut: true, }); if (!selectedEmitter) { - logger.info("No emitters selected. Emit canceled.", [], { + logger.info("No emitter selected. Generating Cancelled.", [], { showOutput: false, - showPopup: true, + showPopup: false, progress: overallProgress, }); return; @@ -77,7 +77,7 @@ export async function doEmit( ) .then((selection) => { if (selection === "OK") { - logger.info("Emit canceled.", [], { + logger.info("Generating Cancelled.", [], { showOutput: false, showPopup: true, progress: overallProgress, @@ -113,20 +113,20 @@ export async function doEmit( const selected = await vscode.window.showQuickPick(Object.values(options), { canPickMany: false, ignoreFocusOut: true, - placeHolder: `Package '${selectedEmitter.package}' needs to be upgraded for emitting`, - title: `TypeSpec Emit...`, + placeHolder: `Package '${selectedEmitter.package}' needs to be upgraded for generating`, + title: `TypeSpec Generating...`, }); if (selected === options.ok) { packagesToInstall.push(`${selectedEmitter.package}@${version}`); } else if (selected === options.ignore) { - logger.info(`Ignore upgrading emitter ${selectedEmitter.package} for emitting`, [], { + logger.info(`Ignore upgrading emitter ${selectedEmitter.package} for generating`, [], { showOutput: false, showPopup: false, progress: overallProgress, }); } else { logger.info( - `Need to manually install the package ${selectedEmitter.package}@${version}. Emit canceled.`, + `Need to manually install the package ${selectedEmitter.package}@${version}. Generating Cancelled.`, [], { showOutput: false, @@ -157,14 +157,14 @@ export async function doEmit( const selected = await vscode.window.showQuickPick(Object.values(options), { canPickMany: false, ignoreFocusOut: true, - placeHolder: `Package '${dependency}' needs to be upgraded for emitting`, - title: `TypeSpec Emit...`, + placeHolder: `Package '${dependency}' needs to be upgraded for generating`, + title: `TypeSpec Generate...`, }); if (selected === options.ok) { packagesToInstall.push(dependency); } else { logger.info( - `Need to manually install the dependency package ${dependency}@latest. Emit canceled.`, + `Need to manually install the dependency package ${dependency}@latest. Generating Cancelled.`, [], { showOutput: false, @@ -210,7 +210,7 @@ export async function doEmit( } /* emit */ - logger.info("Emit code ...", [], { + logger.info("Generating ...", [], { showOutput: false, showPopup: false, progress: overallProgress, @@ -218,11 +218,15 @@ export async function doEmit( const cli = await resolveTypeSpecCli(baseDir); if (!cli) { - logger.error("Cannot find TypeSpec CLI. Please install @typespec/compiler. Cancel emit.", [], { - showOutput: true, - showPopup: true, - progress: overallProgress, - }); + logger.error( + "Cannot find TypeSpec CLI. Please install @typespec/compiler. Generating Cancelled.", + [], + { + showOutput: true, + showPopup: true, + progress: overallProgress, + }, + ); return; } const outputDir = path.resolve(baseDir, selectedEmitter.emitterKind, selectedEmitter.language); @@ -242,7 +246,7 @@ export async function doEmit( const compileResult = await compile(cli, mainTspFile, selectedEmitter.package, options); if (compileResult.exitCode !== 0) { logger.error( - `Failed to generate ${selectedEmitter.language} ${selectedEmitter.emitterKind} code.`, + `Failed to generate ${selectedEmitter.emitterKind} code for ${selectedEmitter.language}.`, [], { showOutput: true, @@ -252,7 +256,7 @@ export async function doEmit( ); } else { logger.info( - `complete generating ${selectedEmitter.language} ${selectedEmitter.emitterKind} code.`, + `Generating ${selectedEmitter.emitterKind} code for ${selectedEmitter.language}...Succeeded`, [], { showOutput: true, @@ -263,7 +267,7 @@ export async function doEmit( } } catch (err) { logger.error( - `Exception occurred when generating ${selectedEmitter.language} ${selectedEmitter.emitterKind} code.`, + `Exception occurred when generating ${selectedEmitter.emitterKind} code for ${selectedEmitter.language}.`, [err], { showOutput: true, @@ -286,7 +290,7 @@ export async function emitCode( const targetPathes = await TraverseMainTspFileInWorkspace(); logger.info(`Found ${targetPathes.length} ${StartFileName} files`); if (targetPathes.length === 0) { - logger.info("No main tsp file found. Emit canceled.", [], { + logger.info("No main tsp file found. Generating Cancelled.", [], { showOutput: false, showPopup: true, progress: overallProgress, @@ -307,13 +311,13 @@ export async function emitCode( toProjectPickItem(filePath), ); const selectedProjectFile = await vscode.window.showQuickPick(typespecProjectQuickPickItems, { - title: "Select TypeSpec Project", + title: "Select a TypeSpec Project", canPickMany: false, placeHolder: "Pick a project", ignoreFocusOut: true, }); if (!selectedProjectFile) { - logger.info("No project selected. Emit canceled.", [], { + logger.info("No project selected. Generating Cancelled.", [], { showOutput: false, showPopup: true, progress: overallProgress, @@ -340,34 +344,34 @@ export async function emitCode( const codesToEmit = [ { - label: "Client SDK", - detail: "Generate client SDK library from typespec.", + label: "Protocol Schema", + detail: "Generating Protocol schema (OpenAPI for example) from TypeSpec", + iconPath: Uri.file(context.asAbsolutePath(`./icons/schema.svg`)), + emitterKind: EmitterKind.Schema, + }, + { + label: "Client Code", + detail: "Generating Client Code from TypeSpec.", iconPath: Uri.file(context.asAbsolutePath(`./icons/sdk.svg`)), emitterKind: EmitterKind.Client, }, { - label: "Server Stub", - detail: "Generate server codes from typespec", + label: " Server Stub", + detail: "Generating Server Stub from TypeSpec", iconPath: Uri.file(context.asAbsolutePath(`./icons/serverstub.svg`)), emitterKind: EmitterKind.Server, }, - { - label: "Protocol Schema", - detail: "Generate protocol schema (e.g. OpenAPI, Protobuf) from typespec", - iconPath: Uri.file(context.asAbsolutePath(`./icons/schema.svg`)), - emitterKind: EmitterKind.Schema, - }, ]; const codeType = await vscode.window.showQuickPick(codesToEmit, { - title: "Emit Code", + title: "Select an Emitter Type", canPickMany: false, - placeHolder: "Select an option", + placeHolder: "Select an emitter type", ignoreFocusOut: true, }); if (!codeType) { - logger.info("No emitters selected. Emit canceled.", [], { + logger.info("No emitter Type selected. Generating Cancelled.", [], { showOutput: false, - showPopup: true, + showPopup: false, progress: overallProgress, }); return; diff --git a/packages/typespec-vscode/src/emit/emitter.ts b/packages/typespec-vscode/src/emit/emitter.ts index 555bd03157..bb6a760327 100644 --- a/packages/typespec-vscode/src/emit/emitter.ts +++ b/packages/typespec-vscode/src/emit/emitter.ts @@ -1,5 +1,5 @@ import vscode from "vscode"; -import { EmitterSettingName } from "../const.js"; +import { SettingName } from "../types.js"; export enum EmitterKind { Schema = "schema", @@ -35,8 +35,8 @@ function getEmitter(kind: EmitterKind, emitter: Emitter): Emitter { } export function getRegisterEmitters(kind: EmitterKind): ReadonlyArray { - const emitters: ReadonlyArray = - extensionConfig.get(EmitterSettingName[kind] ?? "") ?? []; - - return emitters.map((emitter) => getEmitter(kind, emitter)); + const emitters: ReadonlyArray = extensionConfig.get(SettingName.Emitters) ?? []; + return emitters + .filter((emitter) => emitter.kind === kind) + .map((emitter) => getEmitter(kind, emitter)); } diff --git a/packages/typespec-vscode/src/extension.ts b/packages/typespec-vscode/src/extension.ts index d14b3a02fe..f5882c3324 100644 --- a/packages/typespec-vscode/src/extension.ts +++ b/packages/typespec-vscode/src/extension.ts @@ -47,11 +47,11 @@ export async function activate(context: ExtensionContext) { /* emit command. */ context.subscriptions.push( - commands.registerCommand(CommandName.EmitCode, async (uri: vscode.Uri) => { + commands.registerCommand(CommandName.Generate, async (uri: vscode.Uri) => { await vscode.window.withProgress( { location: vscode.ProgressLocation.Window, - title: "Emit Code...", + title: "Generate from TypeSpec...", cancellable: false, }, async (progress) => await emitCode(context, uri, progress), diff --git a/packages/typespec-vscode/src/types.ts b/packages/typespec-vscode/src/types.ts index 3300121ff0..6c748d6be8 100644 --- a/packages/typespec-vscode/src/types.ts +++ b/packages/typespec-vscode/src/types.ts @@ -1,9 +1,7 @@ export const enum SettingName { TspServerPath = "typespec.tsp-server.path", InitTemplatesUrls = "typespec.initTemplatesUrls", - ClientEmitter = "typespec.client.emitter", - ServerEmitter = "typespec.server.emitter", - SchemaEmitter = "typespec.schema.emitter", + Emitters = "typespec.emitters", } export const enum CommandName { @@ -12,7 +10,7 @@ export const enum CommandName { InstallGlobalCompilerCli = "typespec.installGlobalCompilerCli", CreateProject = "typespec.createProject", OpenUrl = "typespec.openUrl", - EmitCode = "typespec.emit", + Generate = "typespec.generate", } export interface InstallGlobalCliCommandArgs { From 9105874ee11c3e00662ee158f159966d66c6c5f5 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Wed, 18 Dec 2024 17:31:17 +0800 Subject: [PATCH 34/42] remove dependency auto-install --- packages/typespec-vscode/src/emit/emit.ts | 31 +++++------------------ 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/packages/typespec-vscode/src/emit/emit.ts b/packages/typespec-vscode/src/emit/emit.ts index c6fe8c3358..a01f7aba89 100644 --- a/packages/typespec-vscode/src/emit/emit.ts +++ b/packages/typespec-vscode/src/emit/emit.ts @@ -149,31 +149,12 @@ export async function doEmit( npmDependencyType.peerDependencies, ); logger.info(`${dependenciesToInstall}`); - for (const dependency of dependenciesToInstall) { - const options = { - ok: `OK (Upgrade ${dependency} by 'npm install)'`, - recheck: `Check again (install ${dependency} manually)`, - }; - const selected = await vscode.window.showQuickPick(Object.values(options), { - canPickMany: false, - ignoreFocusOut: true, - placeHolder: `Package '${dependency}' needs to be upgraded for generating`, - title: `TypeSpec Generate...`, - }); - if (selected === options.ok) { - packagesToInstall.push(dependency); - } else { - logger.info( - `Need to manually install the dependency package ${dependency}@latest. Generating Cancelled.`, - [], - { - showOutput: false, - showPopup: true, - progress: overallProgress, - }, - ); - return; - } + if (dependenciesToInstall.length > 0) { + vscode.window.showInformationMessage( + `Need to manually upgrade following dependency packages: ${dependenciesToInstall.join("\\n")}. \nGenerating Cancelled`, + "OK", + ); + return; } packagesToInstall.push(`${packageFullName}`); } From f70e9d0324f1da4f5e97dc5716f0752627970a8c Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Thu, 19 Dec 2024 13:19:25 +0800 Subject: [PATCH 35/42] resolve comments --- .../icons/{sdk.svg => client.svg} | 0 packages/typespec-vscode/icons/openapi3.svg | 7 + packages/typespec-vscode/icons/openfolder.svg | 7 - .../icons/{serverstub.svg => server.svg} | 0 packages/typespec-vscode/package.json | 8 +- .../src/emit/emit-quick-pick-item.ts | 4 - packages/typespec-vscode/src/emit/emit.ts | 146 ++++++------------ packages/typespec-vscode/src/emit/emitter.ts | 39 ++++- packages/typespec-vscode/src/extension.ts | 4 +- packages/typespec-vscode/src/npm-utils.ts | 51 +++--- packages/typespec-vscode/src/types.ts | 4 +- .../typespec-vscode/src/typespec-utils.ts | 50 ++---- packages/typespec-vscode/src/utils.ts | 5 - 13 files changed, 135 insertions(+), 190 deletions(-) rename packages/typespec-vscode/icons/{sdk.svg => client.svg} (100%) create mode 100644 packages/typespec-vscode/icons/openapi3.svg delete mode 100644 packages/typespec-vscode/icons/openfolder.svg rename packages/typespec-vscode/icons/{serverstub.svg => server.svg} (100%) diff --git a/packages/typespec-vscode/icons/sdk.svg b/packages/typespec-vscode/icons/client.svg similarity index 100% rename from packages/typespec-vscode/icons/sdk.svg rename to packages/typespec-vscode/icons/client.svg diff --git a/packages/typespec-vscode/icons/openapi3.svg b/packages/typespec-vscode/icons/openapi3.svg new file mode 100644 index 0000000000..22b781fe93 --- /dev/null +++ b/packages/typespec-vscode/icons/openapi3.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/packages/typespec-vscode/icons/openfolder.svg b/packages/typespec-vscode/icons/openfolder.svg deleted file mode 100644 index 2e220729d4..0000000000 --- a/packages/typespec-vscode/icons/openfolder.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/packages/typespec-vscode/icons/serverstub.svg b/packages/typespec-vscode/icons/server.svg similarity index 100% rename from packages/typespec-vscode/icons/serverstub.svg rename to packages/typespec-vscode/icons/server.svg diff --git a/packages/typespec-vscode/package.json b/packages/typespec-vscode/package.json index d789c2cca2..096a1235ed 100644 --- a/packages/typespec-vscode/package.json +++ b/packages/typespec-vscode/package.json @@ -109,7 +109,7 @@ "default": "off", "description": "Define whether/how the TypeSpec language server should send traces to client. For the traces to show properly in vscode Output, make sure 'Log Level' is also set to 'Trace' so that they won't be filtered at client side, which can be set through 'Developer: Set Log Level...' command." }, - "typespec.emitters": { + "typespec.generateCode.emitters": { "scope": "window", "type": "array", "items": { @@ -215,7 +215,7 @@ "category": "TypeSpec" }, { - "command": "typespec.generate", + "command": "typespec.generateCode", "title": "Generate from TypeSpec", "category": "TypeSpec" }, @@ -233,14 +233,14 @@ "menus": { "explorer/context": [ { - "command": "typespec.generate", + "command": "typespec.generateCode", "when": "explorerResourceIsFolder || resourceLangId == typespec", "group": "code_generation" } ], "editor/context": [ { - "command": "typespec.generate", + "command": "typespec.generateCode", "when": "resourceLangId == typespec", "group": "code_generation" } diff --git a/packages/typespec-vscode/src/emit/emit-quick-pick-item.ts b/packages/typespec-vscode/src/emit/emit-quick-pick-item.ts index 86391b1dc9..9be498b995 100644 --- a/packages/typespec-vscode/src/emit/emit-quick-pick-item.ts +++ b/packages/typespec-vscode/src/emit/emit-quick-pick-item.ts @@ -9,7 +9,3 @@ export interface EmitQuickPickItem extends vscode.QuickPickItem { outputDir?: string; emitterKind: EmitterKind; } - -export interface TypeSpecProjectPickItem extends vscode.QuickPickItem { - path: string; -} diff --git a/packages/typespec-vscode/src/emit/emit.ts b/packages/typespec-vscode/src/emit/emit.ts index a01f7aba89..d6031e357a 100644 --- a/packages/typespec-vscode/src/emit/emit.ts +++ b/packages/typespec-vscode/src/emit/emit.ts @@ -1,37 +1,42 @@ -// import { TypeSpecConfig } from "@typespec/compiler"; -import path, { dirname } from "path"; +import path from "path"; import vscode, { Uri } from "vscode"; import { Executable } from "vscode-languageclient/node.js"; import { StartFileName } from "../const.js"; import logger from "../log/logger.js"; import { InstallationAction, npmDependencyType, NpmUtil } from "../npm-utils.js"; +import { getDirectoryPath } from "../path-utils.js"; +import { resolveTypeSpecCli } from "../tsp-executable-resolver.js"; import { - getMainTspFile, - resolveTypeSpecCli, - toError, - toOutput, + getEntrypointTspFile, + onStdioError, + onStdioOut, TraverseMainTspFileInWorkspace, } from "../typespec-utils.js"; import { ExecOutput, isFile, spawnExecution } from "../utils.js"; -import { EmitQuickPickItem, TypeSpecProjectPickItem } from "./emit-quick-pick-item.js"; -import { Emitter, EmitterKind, getRegisterEmitters } from "./emitter.js"; +import { EmitQuickPickItem } from "./emit-quick-pick-item.js"; +import { + Emitter, + EmitterKind, + getRegisterEmitters, + getRegisterEmitterTypes, + PreDefinedEmitterPickItems, +} from "./emitter.js"; export async function doEmit( context: vscode.ExtensionContext, mainTspFile: string, kind: EmitterKind, - overallProgress: vscode.Progress<{ message?: string; increment?: number }>, ) { if (!mainTspFile || !(await isFile(mainTspFile))) { logger.info( "Invalid typespec project. There is no main tsp file in the project. Generating Cancelled.", [], - { showOutput: false, showPopup: true, progress: overallProgress }, + { showOutput: false, showPopup: true }, ); return; } - const baseDir = dirname(mainTspFile); + const baseDir = getDirectoryPath(mainTspFile); const toQuickPickItem = (e: Emitter): EmitQuickPickItem => { return { @@ -60,37 +65,13 @@ export async function doEmit( logger.info("No emitter selected. Generating Cancelled.", [], { showOutput: false, showPopup: false, - progress: overallProgress, }); return; } - /* TODO: verify the sdk runtime installation. */ - /* inform to install needed runtime. */ - const { valid, required } = await check(`${baseDir}/${StartFileName}`, selectedEmitter.package); - if (!valid) { - const toInstall = required.map((e) => e.name).join(", "); - await vscode.window - .showInformationMessage( - `Please install the required runtime for the selected emitters\n\n. ${toInstall}`, - "OK", - ) - .then((selection) => { - if (selection === "OK") { - logger.info("Generating Cancelled.", [], { - showOutput: false, - showPopup: true, - progress: overallProgress, - }); - } - }); - return; - } - logger.info("npm install...", [], { showOutput: false, showPopup: false, - progress: overallProgress, }); const npmUtil = new NpmUtil(baseDir); @@ -98,7 +79,7 @@ export async function doEmit( /* install emitter package. */ logger.info(`select ${selectedEmitter.package}`); - const { action, version } = await npmUtil.ensureNpmPackageInstall( + const { action, version } = await npmUtil.ensureNpmPackageInstallAction( selectedEmitter.package, selectedEmitter.version, ); @@ -122,7 +103,6 @@ export async function doEmit( logger.info(`Ignore upgrading emitter ${selectedEmitter.package} for generating`, [], { showOutput: false, showPopup: false, - progress: overallProgress, }); } else { logger.info( @@ -131,7 +111,6 @@ export async function doEmit( { showOutput: false, showPopup: true, - progress: overallProgress, }, ); return; @@ -143,7 +122,7 @@ export async function doEmit( } logger.info(`Installing ${packageFullName}`); /* verify dependency packages. */ - const dependenciesToInstall = await npmUtil.ensureNpmPackageDependencyInstall( + const dependenciesToInstall = await npmUtil.ensureNpmPackageDependencyToUpgrade( selectedEmitter.package, version, npmDependencyType.peerDependencies, @@ -164,18 +143,16 @@ export async function doEmit( logger.info(`Installing ${packagesToInstall.join("\n\n")} under ${baseDir}`, [], { showOutput: true, showPopup: true, - progress: overallProgress, }); try { const npmInstallResult = await npmUtil.npmInstallPackages(packagesToInstall, undefined, { - onStdioOut: toOutput, - onStdioError: toError, + onStdioOut: onStdioOut, + onStdioError: onStdioError, }); if (npmInstallResult.exitCode !== 0) { logger.error(`Error occurred when installing packages.`, [`${npmInstallResult.stderr}`], { showOutput: true, showPopup: true, - progress: overallProgress, }); return; } @@ -184,7 +161,6 @@ export async function doEmit( logger.error(`Exception occurred when installing packages.`, [err], { showOutput: true, showPopup: true, - progress: overallProgress, }); return; } @@ -194,7 +170,6 @@ export async function doEmit( logger.info("Generating ...", [], { showOutput: false, showPopup: false, - progress: overallProgress, }); const cli = await resolveTypeSpecCli(baseDir); @@ -205,7 +180,6 @@ export async function doEmit( { showOutput: true, showPopup: true, - progress: overallProgress, }, ); return; @@ -220,7 +194,6 @@ export async function doEmit( { showOutput: true, showPopup: false, - progress: overallProgress, }, ); try { @@ -232,7 +205,6 @@ export async function doEmit( { showOutput: true, showPopup: true, - progress: overallProgress, }, ); } else { @@ -242,7 +214,6 @@ export async function doEmit( { showOutput: true, showPopup: true, - progress: overallProgress, }, ); } @@ -253,19 +224,12 @@ export async function doEmit( { showOutput: true, showPopup: true, - progress: overallProgress, }, ); } - - /*TODO: build sdk. */ } -export async function emitCode( - context: vscode.ExtensionContext, - uri: vscode.Uri, - overallProgress: vscode.Progress<{ message?: string; increment?: number }>, -) { +export async function emitCode(context: vscode.ExtensionContext, uri: vscode.Uri) { let tspProjectFile: string = ""; if (!uri) { const targetPathes = await TraverseMainTspFileInWorkspace(); @@ -274,11 +238,10 @@ export async function emitCode( logger.info("No main tsp file found. Generating Cancelled.", [], { showOutput: false, showPopup: true, - progress: overallProgress, }); return; } - const toProjectPickItem = (filePath: string): TypeSpecProjectPickItem => { + const toProjectPickItem = (filePath: string): any => { return { label: filePath, path: filePath, @@ -288,7 +251,7 @@ export async function emitCode( }, }; }; - const typespecProjectQuickPickItems: TypeSpecProjectPickItem[] = targetPathes.map((filePath) => + const typespecProjectQuickPickItems: any[] = targetPathes.map((filePath) => toProjectPickItem(filePath), ); const selectedProjectFile = await vscode.window.showQuickPick(typespecProjectQuickPickItems, { @@ -301,49 +264,38 @@ export async function emitCode( logger.info("No project selected. Generating Cancelled.", [], { showOutput: false, showPopup: true, - progress: overallProgress, }); return; } tspProjectFile = selectedProjectFile.path; } else { - const tspStartFile = await getMainTspFile(uri.fsPath); + const tspStartFile = await getEntrypointTspFile(uri.fsPath); if (!tspStartFile) { logger.info("No main file. Invalid typespec project.", [], { showOutput: false, showPopup: true, - progress: overallProgress, }); return; } tspProjectFile = tspStartFile; } - interface EmitTypeQuickPickItem extends vscode.QuickPickItem { - emitterKind: EmitterKind; - } + logger.info(`Generate from entrypoint file: ${tspProjectFile}`, [], { + showOutput: false, + showPopup: false, + }); - const codesToEmit = [ - { - label: "Protocol Schema", - detail: "Generating Protocol schema (OpenAPI for example) from TypeSpec", - iconPath: Uri.file(context.asAbsolutePath(`./icons/schema.svg`)), - emitterKind: EmitterKind.Schema, - }, - { - label: "Client Code", - detail: "Generating Client Code from TypeSpec.", - iconPath: Uri.file(context.asAbsolutePath(`./icons/sdk.svg`)), - emitterKind: EmitterKind.Client, - }, - { - label: " Server Stub", - detail: "Generating Server Stub from TypeSpec", - iconPath: Uri.file(context.asAbsolutePath(`./icons/serverstub.svg`)), - emitterKind: EmitterKind.Server, - }, - ]; - const codeType = await vscode.window.showQuickPick(codesToEmit, { + const emitterKinds = getRegisterEmitterTypes(); + const toEitTypeQuickPickItem = (kind: EmitterKind): any => { + return { + label: PreDefinedEmitterPickItems[kind]?.label ?? kind, + detail: PreDefinedEmitterPickItems[kind]?.detail ?? `Generate ${kind} code from TypeSpec`, + emitterKind: kind, + iconPath: Uri.file(context.asAbsolutePath(`./icons/${kind.toLowerCase()}.svg`)), + }; + }; + const codesToEmit = emitterKinds.map((kind) => toEitTypeQuickPickItem(kind)); + const codeType = await vscode.window.showQuickPick(codesToEmit, { title: "Select an Emitter Type", canPickMany: false, placeHolder: "Select an emitter type", @@ -353,11 +305,10 @@ export async function emitCode( logger.info("No emitter Type selected. Generating Cancelled.", [], { showOutput: false, showPopup: false, - progress: overallProgress, }); return; } - await doEmit(context, tspProjectFile, codeType.emitterKind, overallProgress); + await doEmit(context, tspProjectFile, codeType.emitterKind); } export async function compile( @@ -377,19 +328,8 @@ export async function compile( args.push("--option", `${emitter}.${key}=${value}`); } - return await spawnExecution(cli.command, args, dirname(startFile), { - onStdioOut: toOutput, - onStdioError: toError, + return await spawnExecution(cli.command, args, getDirectoryPath(startFile), { + onStdioOut: onStdioOut, + onStdioError: onStdioError, }); } - -export async function check( - startFile: string, - emitter: string, -): Promise<{ - valid: boolean; - required: { name: string; version: string }[]; -}> { - /* TODO: check the runtime. */ - return { valid: true, required: [] }; -} diff --git a/packages/typespec-vscode/src/emit/emitter.ts b/packages/typespec-vscode/src/emit/emitter.ts index bb6a760327..e3b1281a1d 100644 --- a/packages/typespec-vscode/src/emit/emitter.ts +++ b/packages/typespec-vscode/src/emit/emitter.ts @@ -1,4 +1,5 @@ import vscode from "vscode"; +import logger from "../log/logger.js"; import { SettingName } from "../types.js"; export enum EmitterKind { @@ -14,10 +15,28 @@ export interface Emitter { kind: EmitterKind; } -const extensionConfig = vscode.workspace.getConfiguration(); +export const PreDefinedEmitterPickItems: Record = { + schema: { + label: "Protocol Schema", + detail: "Generating Protocol schema (OpenAPI for example) from TypeSpec", + }, + client: { + label: "Client Code", + detail: "Generating Client Code from TypeSpec.", + }, + server: { + label: " Server Stub", + detail: "Generating Server Stub from TypeSpec", + }, +}; -function getEmitter(kind: EmitterKind, emitter: Emitter): Emitter { - const packageFullName: string = emitter.package ?? ""; +function getEmitter(kind: EmitterKind, emitter: Emitter): Emitter | undefined { + let packageFullName: string = emitter.package; + if (!packageFullName) { + logger.error("Emitter package name is required."); + return undefined; + } + packageFullName = packageFullName.trim(); const index = packageFullName.lastIndexOf("@"); let version = undefined; let packageName = packageFullName; @@ -35,8 +54,18 @@ function getEmitter(kind: EmitterKind, emitter: Emitter): Emitter { } export function getRegisterEmitters(kind: EmitterKind): ReadonlyArray { - const emitters: ReadonlyArray = extensionConfig.get(SettingName.Emitters) ?? []; + const extensionConfig = vscode.workspace.getConfiguration(); + const emitters: ReadonlyArray = + extensionConfig.get(SettingName.GenerateCodeEmitters) ?? []; return emitters .filter((emitter) => emitter.kind === kind) - .map((emitter) => getEmitter(kind, emitter)); + .map((emitter) => getEmitter(kind, emitter)) + .filter((emitter) => emitter !== undefined) as Emitter[]; +} + +export function getRegisterEmitterTypes(): ReadonlyArray { + const extensionConfig = vscode.workspace.getConfiguration(); + const emitters: ReadonlyArray = + extensionConfig.get(SettingName.GenerateCodeEmitters) ?? []; + return Array.from(new Set(emitters.map((emitter) => emitter.kind))); } diff --git a/packages/typespec-vscode/src/extension.ts b/packages/typespec-vscode/src/extension.ts index f5882c3324..5c3c43ff3f 100644 --- a/packages/typespec-vscode/src/extension.ts +++ b/packages/typespec-vscode/src/extension.ts @@ -47,14 +47,14 @@ export async function activate(context: ExtensionContext) { /* emit command. */ context.subscriptions.push( - commands.registerCommand(CommandName.Generate, async (uri: vscode.Uri) => { + commands.registerCommand(CommandName.GenerateCode, async (uri: vscode.Uri) => { await vscode.window.withProgress( { location: vscode.ProgressLocation.Window, title: "Generate from TypeSpec...", cancellable: false, }, - async (progress) => await emitCode(context, uri, progress), + async () => await emitCode(context, uri), ); }), ); diff --git a/packages/typespec-vscode/src/npm-utils.ts b/packages/typespec-vscode/src/npm-utils.ts index 542f270e2d..e952ed86e5 100644 --- a/packages/typespec-vscode/src/npm-utils.ts +++ b/packages/typespec-vscode/src/npm-utils.ts @@ -1,4 +1,4 @@ -import fs from "fs"; +import { readFile } from "fs/promises"; import path from "path"; import semver from "semver"; import logger from "./log/logger.js"; @@ -40,7 +40,8 @@ export class NpmUtil { return spawnExecution("npm", ["install", ...packages], this.cwd, on); } - public async ensureNpmPackageInstall( + /* identify the action to take for a package. install or skip or cancel or upgrade */ + public async ensureNpmPackageInstallAction( packageName: string, version?: string, ): Promise<{ action: InstallationAction; version?: string }> { @@ -48,10 +49,14 @@ export class NpmUtil { await this.isPackageInstalled(packageName); if (isPackageInstalled) { if (version && installedVersion !== version) { - return { - action: InstallationAction.Upgrade, - version: version, - }; + if (semver.gt(version, installedVersion!)) { + return { action: InstallationAction.Upgrade, version: version }; + } else { + logger.info( + "The version to intall is less than the installed version. Skip installation.", + ); + return { action: InstallationAction.Skip, version: installedVersion }; + } } return { action: InstallationAction.Skip, version: installedVersion }; } else { @@ -59,7 +64,8 @@ export class NpmUtil { } } - public async ensureNpmPackageDependencyInstall( + /* identify the dependency packages need to be upgraded */ + public async ensureNpmPackageDependencyToUpgrade( packageName: string, version?: string, dependencyType: npmDependencyType = npmDependencyType.dependencies, @@ -72,15 +78,15 @@ export class NpmUtil { } /* get dependencies. */ - const dependenciesResult = await spawnExecution( - "npm", - ["view", packageFullName, dependencyType], - this.cwd, - on, - ); - - if (dependenciesResult.exitCode === 0) { - try { + try { + const dependenciesResult = await spawnExecution( + "npm", + ["view", packageFullName, dependencyType], + this.cwd, + on, + ); + + if (dependenciesResult.exitCode === 0) { // Remove all newline characters let dependenciesJsonStr = dependenciesResult.stdout.trim(); dependenciesJsonStr = dependenciesJsonStr.replace(/\n/g, ""); @@ -96,11 +102,12 @@ export class NpmUtil { } } } - } catch (err) { - if (on && on.onError) { - on.onError(err, "", ""); - } } + } catch (err) { + if (on && on.onError) { + on.onError(err, "", ""); + } + logger.error("Error getting dependencies.", [err]); } return dependenciesToInstall; @@ -126,11 +133,11 @@ export class NpmUtil { /* get the package version. */ let version; try { - const data = fs.readFileSync(packageJsonPath, "utf-8"); + const data = await readFile(packageJsonPath, { encoding: "utf-8" }); const packageJson = JSON.parse(data); version = packageJson.version; } catch (error) { - logger.error(`Error reading package.json: ${error}`); + logger.error("Error reading package.json.", [error]); } return { name: packageName, diff --git a/packages/typespec-vscode/src/types.ts b/packages/typespec-vscode/src/types.ts index 6c748d6be8..899f88df1a 100644 --- a/packages/typespec-vscode/src/types.ts +++ b/packages/typespec-vscode/src/types.ts @@ -1,7 +1,7 @@ export const enum SettingName { TspServerPath = "typespec.tsp-server.path", InitTemplatesUrls = "typespec.initTemplatesUrls", - Emitters = "typespec.emitters", + GenerateCodeEmitters = "typespec.generateCode.emitters", } export const enum CommandName { @@ -10,7 +10,7 @@ export const enum CommandName { InstallGlobalCompilerCli = "typespec.installGlobalCompilerCli", CreateProject = "typespec.createProject", OpenUrl = "typespec.openUrl", - Generate = "typespec.generate", + GenerateCode = "typespec.generateCode", } export interface InstallGlobalCliCommandArgs { diff --git a/packages/typespec-vscode/src/typespec-utils.ts b/packages/typespec-vscode/src/typespec-utils.ts index f111f8b912..edf3ac05da 100644 --- a/packages/typespec-vscode/src/typespec-utils.ts +++ b/packages/typespec-vscode/src/typespec-utils.ts @@ -1,18 +1,18 @@ -import { readdir } from "fs"; -import path, { dirname } from "path"; +import { readdir } from "node:fs/promises"; +import path from "path"; import vscode from "vscode"; -import { Executable } from "vscode-languageclient/node.js"; import { StartFileName } from "./const.js"; import logger from "./log/logger.js"; -import { isFile, loadModule, normalizeSlash } from "./utils.js"; +import { getDirectoryPath, normalizeSlashes } from "./path-utils.js"; +import { isFile } from "./utils.js"; -export const toOutput = (str: string) => { +export const onStdioOut = (str: string) => { str .trim() .split("\n") .forEach((line) => logger.info(line)); }; -export const toError = (str: string) => { +export const onStdioError = (str: string) => { str .trim() .split("\n") @@ -24,24 +24,9 @@ export const toError = (str: string) => { ); }; -export async function resolveTypeSpecCli(absolutePath: string): Promise { - if (!path.isAbsolute(absolutePath) || (await isFile(absolutePath))) { - return undefined; - } - const modelInfo = await loadModule(absolutePath, "@typespec/compiler"); - if (modelInfo) { - const cmdPath = path.resolve(modelInfo.path, "cmd/tsp.js"); - return { - command: "node", - args: [cmdPath], - }; - } - return undefined; -} - -export async function getMainTspFile(tspPath: string): Promise { +export async function getEntrypointTspFile(tspPath: string): Promise { const isFilePath = await isFile(tspPath); - const baseDir = isFilePath ? dirname(tspPath) : tspPath; + const baseDir = isFilePath ? getDirectoryPath(tspPath) : tspPath; const mainTspFile = path.resolve(baseDir, StartFileName); if (await isFile(mainTspFile)) { return mainTspFile; @@ -52,19 +37,12 @@ export async function getMainTspFile(tspPath: string): Promise((resolve, reject) => { - readdir(baseDir, (err, files) => { - if (err) { - reject(err); - } else { - resolve(files); - } - }); - }); - if (files.length === 1 && files[0].endsWith(".tsp")) { + const files = await readdir(baseDir); + if (files && files.length === 1 && files[0].endsWith(".tsp")) { return path.resolve(baseDir, files[0]); } - } catch (error) { + } catch (err) { + logger.error("Error reading directory", [err]); return undefined; } @@ -72,11 +50,11 @@ export async function getMainTspFile(tspPath: string): Promise uris .filter((uri) => uri.scheme === "file" && !uri.fsPath.includes("node_modules")) - .map((uri) => normalizeSlash(uri.fsPath)), + .map((uri) => normalizeSlashes(uri.fsPath)), ); } diff --git a/packages/typespec-vscode/src/utils.ts b/packages/typespec-vscode/src/utils.ts index ea70042f7d..f5b72bae7d 100644 --- a/packages/typespec-vscode/src/utils.ts +++ b/packages/typespec-vscode/src/utils.ts @@ -7,11 +7,6 @@ import { Executable } from "vscode-languageclient/node.js"; import logger from "./log/logger.js"; import { isUrl } from "./path-utils.js"; -/** normalize / and \\ to / */ -export function normalizeSlash(str: string): string { - return str.replaceAll(/\\/g, "/"); -} - export async function isFile(path: string) { try { const stats = await stat(path); From 8dc67ec6b4edccc13bc329d6690b0f1cb35d6a94 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Tue, 24 Dec 2024 11:44:57 +0800 Subject: [PATCH 36/42] resolve comments --- packages/typespec-vscode/src/emit/emit.ts | 31 +++++--------------- packages/typespec-vscode/src/emit/emitter.ts | 6 ++-- packages/typespec-vscode/src/npm-utils.ts | 12 +++----- 3 files changed, 15 insertions(+), 34 deletions(-) diff --git a/packages/typespec-vscode/src/emit/emit.ts b/packages/typespec-vscode/src/emit/emit.ts index d6031e357a..34dfa42391 100644 --- a/packages/typespec-vscode/src/emit/emit.ts +++ b/packages/typespec-vscode/src/emit/emit.ts @@ -22,13 +22,9 @@ import { PreDefinedEmitterPickItems, } from "./emitter.js"; -export async function doEmit( - context: vscode.ExtensionContext, - mainTspFile: string, - kind: EmitterKind, -) { +async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kind: EmitterKind) { if (!mainTspFile || !(await isFile(mainTspFile))) { - logger.info( + logger.error( "Invalid typespec project. There is no main tsp file in the project. Generating Cancelled.", [], { showOutput: false, showPopup: true }, @@ -62,18 +58,10 @@ export async function doEmit( }); if (!selectedEmitter) { - logger.info("No emitter selected. Generating Cancelled.", [], { - showOutput: false, - showPopup: false, - }); + logger.info("No emitter selected. Generating Cancelled."); return; } - logger.info("npm install...", [], { - showOutput: false, - showPopup: false, - }); - const npmUtil = new NpmUtil(baseDir); const packagesToInstall: string[] = []; @@ -167,11 +155,6 @@ export async function doEmit( } /* emit */ - logger.info("Generating ...", [], { - showOutput: false, - showPopup: false, - }); - const cli = await resolveTypeSpecCli(baseDir); if (!cli) { logger.error( @@ -200,7 +183,7 @@ export async function doEmit( const compileResult = await compile(cli, mainTspFile, selectedEmitter.package, options); if (compileResult.exitCode !== 0) { logger.error( - `Failed to generate ${selectedEmitter.emitterKind} code for ${selectedEmitter.language}.`, + `Generating ${selectedEmitter.emitterKind} code for ${selectedEmitter.language}...Failed`, [], { showOutput: true, @@ -286,7 +269,7 @@ export async function emitCode(context: vscode.ExtensionContext, uri: vscode.Uri }); const emitterKinds = getRegisterEmitterTypes(); - const toEitTypeQuickPickItem = (kind: EmitterKind): any => { + const toEmitterTypeQuickPickItem = (kind: EmitterKind): any => { return { label: PreDefinedEmitterPickItems[kind]?.label ?? kind, detail: PreDefinedEmitterPickItems[kind]?.detail ?? `Generate ${kind} code from TypeSpec`, @@ -294,7 +277,7 @@ export async function emitCode(context: vscode.ExtensionContext, uri: vscode.Uri iconPath: Uri.file(context.asAbsolutePath(`./icons/${kind.toLowerCase()}.svg`)), }; }; - const codesToEmit = emitterKinds.map((kind) => toEitTypeQuickPickItem(kind)); + const codesToEmit = emitterKinds.map((kind) => toEmitterTypeQuickPickItem(kind)); const codeType = await vscode.window.showQuickPick(codesToEmit, { title: "Select an Emitter Type", canPickMany: false, @@ -311,7 +294,7 @@ export async function emitCode(context: vscode.ExtensionContext, uri: vscode.Uri await doEmit(context, tspProjectFile, codeType.emitterKind); } -export async function compile( +async function compile( cli: Executable, startFile: string, emitter: string, diff --git a/packages/typespec-vscode/src/emit/emitter.ts b/packages/typespec-vscode/src/emit/emitter.ts index e3b1281a1d..04c11fb6e9 100644 --- a/packages/typespec-vscode/src/emit/emitter.ts +++ b/packages/typespec-vscode/src/emit/emitter.ts @@ -8,6 +8,8 @@ export enum EmitterKind { Server = "server", } +export type InstallActions = "schema" | "client" | "server"; + export interface Emitter { language: string; package: string; @@ -17,8 +19,8 @@ export interface Emitter { export const PreDefinedEmitterPickItems: Record = { schema: { - label: "Protocol Schema", - detail: "Generating Protocol schema (OpenAPI for example) from TypeSpec", + label: "OpenAPI", + detail: "Generating OpenAPI from TypeSpec", }, client: { label: "Client Code", diff --git a/packages/typespec-vscode/src/npm-utils.ts b/packages/typespec-vscode/src/npm-utils.ts index e952ed86e5..eaf321090c 100644 --- a/packages/typespec-vscode/src/npm-utils.ts +++ b/packages/typespec-vscode/src/npm-utils.ts @@ -81,19 +81,13 @@ export class NpmUtil { try { const dependenciesResult = await spawnExecution( "npm", - ["view", packageFullName, dependencyType], + ["view", packageFullName, dependencyType, "--json"], this.cwd, on, ); if (dependenciesResult.exitCode === 0) { - // Remove all newline characters - let dependenciesJsonStr = dependenciesResult.stdout.trim(); - dependenciesJsonStr = dependenciesJsonStr.replace(/\n/g, ""); - - // Change single quotes to double quotes - dependenciesJsonStr = dependenciesJsonStr.replace(/'/g, '"'); - const json = JSON.parse(dependenciesJsonStr); + const json = JSON.parse(dependenciesResult.stdout); for (const [key, value] of Object.entries(json)) { const { installed, version: installedVersion } = await this.isPackageInstalled(key); if (installed && installedVersion) { @@ -102,6 +96,8 @@ export class NpmUtil { } } } + } else { + logger.error("Error getting dependencies.", [dependenciesResult.stderr]); } } catch (err) { if (on && on.onError) { From ee763abc5ade9b8d6cd8c5160c6b2647f22d6494 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Tue, 24 Dec 2024 16:09:23 +0800 Subject: [PATCH 37/42] add progress for long running operations --- packages/typespec-vscode/src/emit/emit.ts | 130 ++++++++++++---------- 1 file changed, 72 insertions(+), 58 deletions(-) diff --git a/packages/typespec-vscode/src/emit/emit.ts b/packages/typespec-vscode/src/emit/emit.ts index 34dfa42391..832af23c3a 100644 --- a/packages/typespec-vscode/src/emit/emit.ts +++ b/packages/typespec-vscode/src/emit/emit.ts @@ -128,30 +128,40 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin /* npm install packages. */ if (packagesToInstall.length > 0) { - logger.info(`Installing ${packagesToInstall.join("\n\n")} under ${baseDir}`, [], { - showOutput: true, - showPopup: true, - }); - try { - const npmInstallResult = await npmUtil.npmInstallPackages(packagesToInstall, undefined, { - onStdioOut: onStdioOut, - onStdioError: onStdioError, - }); - if (npmInstallResult.exitCode !== 0) { - logger.error(`Error occurred when installing packages.`, [`${npmInstallResult.stderr}`], { - showOutput: true, - showPopup: true, - }); - return; - } - logger.info("completed install..."); - } catch (err) { - logger.error(`Exception occurred when installing packages.`, [err], { - showOutput: true, - showPopup: true, - }); - return; - } + logger.info(`Installing ${packagesToInstall.join("\n\n")} under ${baseDir}`); + await vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: "Installing packages...", + cancellable: false, + }, + async () => { + try { + const npmInstallResult = await npmUtil.npmInstallPackages(packagesToInstall, undefined, { + onStdioOut: onStdioOut, + onStdioError: onStdioError, + }); + if (npmInstallResult.exitCode !== 0) { + logger.error( + `Error occurred when installing packages.`, + [`${npmInstallResult.stderr}`], + { + showOutput: true, + showPopup: true, + }, + ); + return; + } + logger.info("completed install..."); + } catch (err) { + logger.error(`Exception occurred when installing packages.`, [err], { + showOutput: true, + showPopup: true, + }); + return; + } + }, + ); } /* emit */ @@ -173,43 +183,47 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin options["emitter-output-dir"] = outputDir; logger.info( `Start to generate ${selectedEmitter.language} ${selectedEmitter.emitterKind} code under ${outputDir}...`, - [], + ); + await vscode.window.withProgress( { - showOutput: true, - showPopup: false, + location: vscode.ProgressLocation.Notification, + title: `Generating ${selectedEmitter.emitterKind} code for ${selectedEmitter.language}...`, + cancellable: false, + }, + async () => { + try { + const compileResult = await compile(cli, mainTspFile, selectedEmitter.package, options); + if (compileResult.exitCode !== 0) { + logger.error( + `Generating ${selectedEmitter.emitterKind} code for ${selectedEmitter.language}...Failed`, + [], + { + showOutput: true, + showPopup: true, + }, + ); + } else { + logger.info( + `Generating ${selectedEmitter.emitterKind} code for ${selectedEmitter.language}...Succeeded`, + [], + { + showOutput: true, + showPopup: true, + }, + ); + } + } catch (err) { + logger.error( + `Exception occurred when generating ${selectedEmitter.emitterKind} code for ${selectedEmitter.language}.`, + [err], + { + showOutput: true, + showPopup: true, + }, + ); + } }, ); - try { - const compileResult = await compile(cli, mainTspFile, selectedEmitter.package, options); - if (compileResult.exitCode !== 0) { - logger.error( - `Generating ${selectedEmitter.emitterKind} code for ${selectedEmitter.language}...Failed`, - [], - { - showOutput: true, - showPopup: true, - }, - ); - } else { - logger.info( - `Generating ${selectedEmitter.emitterKind} code for ${selectedEmitter.language}...Succeeded`, - [], - { - showOutput: true, - showPopup: true, - }, - ); - } - } catch (err) { - logger.error( - `Exception occurred when generating ${selectedEmitter.emitterKind} code for ${selectedEmitter.language}.`, - [err], - { - showOutput: true, - showPopup: true, - }, - ); - } } export async function emitCode(context: vscode.ExtensionContext, uri: vscode.Uri) { From 792ec1c79bc7b86078e248a7749249ae8d01f1af Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Tue, 24 Dec 2024 16:18:04 +0800 Subject: [PATCH 38/42] remove emit to vscode-cmd folder --- packages/typespec-vscode/src/extension.ts | 2 +- .../emit-code/emit-code.ts} | 16 ++++++++-------- .../emit-code}/emit-quick-pick-item.ts | 0 .../{emit => vscode-cmd/emit-code}/emitter.ts | 6 ++---- 4 files changed, 11 insertions(+), 13 deletions(-) rename packages/typespec-vscode/src/{emit/emit.ts => vscode-cmd/emit-code/emit-code.ts} (95%) rename packages/typespec-vscode/src/{emit => vscode-cmd/emit-code}/emit-quick-pick-item.ts (100%) rename packages/typespec-vscode/src/{emit => vscode-cmd/emit-code}/emitter.ts (93%) diff --git a/packages/typespec-vscode/src/extension.ts b/packages/typespec-vscode/src/extension.ts index 5c3c43ff3f..efbd44898e 100644 --- a/packages/typespec-vscode/src/extension.ts +++ b/packages/typespec-vscode/src/extension.ts @@ -1,7 +1,6 @@ import vscode, { commands, ExtensionContext } from "vscode"; import { State } from "vscode-languageclient"; import { createCodeActionProvider } from "./code-action-provider.js"; -import { emitCode } from "./emit/emit.js"; import { ExtensionLogListener } from "./log/extension-log-listener.js"; import logger from "./log/logger.js"; import { TypeSpecLogOutputChannel } from "./log/typespec-log-output-channel.js"; @@ -14,6 +13,7 @@ import { SettingName, } from "./types.js"; import { createTypeSpecProject } from "./vscode-cmd/create-tsp-project.js"; +import { emitCode } from "./vscode-cmd/emit-code/emit-code.js"; import { installCompilerGlobally } from "./vscode-cmd/install-tsp-compiler.js"; let client: TspLanguageClient | undefined; diff --git a/packages/typespec-vscode/src/emit/emit.ts b/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts similarity index 95% rename from packages/typespec-vscode/src/emit/emit.ts rename to packages/typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts index 832af23c3a..eb26068db0 100644 --- a/packages/typespec-vscode/src/emit/emit.ts +++ b/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts @@ -1,18 +1,18 @@ import path from "path"; import vscode, { Uri } from "vscode"; import { Executable } from "vscode-languageclient/node.js"; -import { StartFileName } from "../const.js"; -import logger from "../log/logger.js"; -import { InstallationAction, npmDependencyType, NpmUtil } from "../npm-utils.js"; -import { getDirectoryPath } from "../path-utils.js"; -import { resolveTypeSpecCli } from "../tsp-executable-resolver.js"; +import { StartFileName } from "../../const.js"; +import logger from "../../log/logger.js"; +import { InstallationAction, npmDependencyType, NpmUtil } from "../../npm-utils.js"; +import { getDirectoryPath } from "../../path-utils.js"; +import { resolveTypeSpecCli } from "../../tsp-executable-resolver.js"; import { getEntrypointTspFile, onStdioError, onStdioOut, TraverseMainTspFileInWorkspace, -} from "../typespec-utils.js"; -import { ExecOutput, isFile, spawnExecution } from "../utils.js"; +} from "../../typespec-utils.js"; +import { ExecOutput, isFile, spawnExecution } from "../../utils.js"; import { EmitQuickPickItem } from "./emit-quick-pick-item.js"; import { Emitter, @@ -177,7 +177,7 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin ); return; } - const outputDir = path.resolve(baseDir, selectedEmitter.emitterKind, selectedEmitter.language); + const outputDir = path.join(baseDir, selectedEmitter.emitterKind, selectedEmitter.language); const options: Record = {}; options["emitter-output-dir"] = outputDir; diff --git a/packages/typespec-vscode/src/emit/emit-quick-pick-item.ts b/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-quick-pick-item.ts similarity index 100% rename from packages/typespec-vscode/src/emit/emit-quick-pick-item.ts rename to packages/typespec-vscode/src/vscode-cmd/emit-code/emit-quick-pick-item.ts diff --git a/packages/typespec-vscode/src/emit/emitter.ts b/packages/typespec-vscode/src/vscode-cmd/emit-code/emitter.ts similarity index 93% rename from packages/typespec-vscode/src/emit/emitter.ts rename to packages/typespec-vscode/src/vscode-cmd/emit-code/emitter.ts index 04c11fb6e9..6ef43e0c87 100644 --- a/packages/typespec-vscode/src/emit/emitter.ts +++ b/packages/typespec-vscode/src/vscode-cmd/emit-code/emitter.ts @@ -1,6 +1,6 @@ import vscode from "vscode"; -import logger from "../log/logger.js"; -import { SettingName } from "../types.js"; +import logger from "../../log/logger.js"; +import { SettingName } from "../../types.js"; export enum EmitterKind { Schema = "schema", @@ -8,8 +8,6 @@ export enum EmitterKind { Server = "server", } -export type InstallActions = "schema" | "client" | "server"; - export interface Emitter { language: string; package: string; From 99c9ad07c5a2d1901927d555b4ca08c7b2890767 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Tue, 24 Dec 2024 16:53:44 +0800 Subject: [PATCH 39/42] refine the logger call --- .../typespec-vscode/src/typespec-utils.ts | 11 ++---- .../src/vscode-cmd/emit-code/emit-code.ts | 34 +++++++------------ 2 files changed, 15 insertions(+), 30 deletions(-) diff --git a/packages/typespec-vscode/src/typespec-utils.ts b/packages/typespec-vscode/src/typespec-utils.ts index edf3ac05da..9614b0cf41 100644 --- a/packages/typespec-vscode/src/typespec-utils.ts +++ b/packages/typespec-vscode/src/typespec-utils.ts @@ -6,22 +6,17 @@ import logger from "./log/logger.js"; import { getDirectoryPath, normalizeSlashes } from "./path-utils.js"; import { isFile } from "./utils.js"; -export const onStdioOut = (str: string) => { +export const logStdoutLineByLineCallBack = (str: string) => { str .trim() .split("\n") .forEach((line) => logger.info(line)); }; -export const onStdioError = (str: string) => { +export const logStderrorLineByLineCallBack = (str: string) => { str .trim() .split("\n") - .forEach((line) => - logger.error(line, [], { - showOutput: true, - showPopup: false, - }), - ); + .forEach((line) => logger.error(line)); }; export async function getEntrypointTspFile(tspPath: string): Promise { diff --git a/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts b/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts index eb26068db0..94377940e4 100644 --- a/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts +++ b/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts @@ -8,8 +8,8 @@ import { getDirectoryPath } from "../../path-utils.js"; import { resolveTypeSpecCli } from "../../tsp-executable-resolver.js"; import { getEntrypointTspFile, - onStdioError, - onStdioOut, + logStderrorLineByLineCallBack, + logStdoutLineByLineCallBack, TraverseMainTspFileInWorkspace, } from "../../typespec-utils.js"; import { ExecOutput, isFile, spawnExecution } from "../../utils.js"; @@ -88,10 +88,7 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin if (selected === options.ok) { packagesToInstall.push(`${selectedEmitter.package}@${version}`); } else if (selected === options.ignore) { - logger.info(`Ignore upgrading emitter ${selectedEmitter.package} for generating`, [], { - showOutput: false, - showPopup: false, - }); + logger.info(`Ignore upgrading emitter ${selectedEmitter.package} for generating`); } else { logger.info( `Need to manually install the package ${selectedEmitter.package}@${version}. Generating Cancelled.`, @@ -138,8 +135,8 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin async () => { try { const npmInstallResult = await npmUtil.npmInstallPackages(packagesToInstall, undefined, { - onStdioOut: onStdioOut, - onStdioError: onStdioError, + onStdioOut: logStdoutLineByLineCallBack, + onStdioError: logStderrorLineByLineCallBack, }); if (npmInstallResult.exitCode !== 0) { logger.error( @@ -152,7 +149,6 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin ); return; } - logger.info("completed install..."); } catch (err) { logger.error(`Exception occurred when installing packages.`, [err], { showOutput: true, @@ -233,7 +229,7 @@ export async function emitCode(context: vscode.ExtensionContext, uri: vscode.Uri logger.info(`Found ${targetPathes.length} ${StartFileName} files`); if (targetPathes.length === 0) { logger.info("No main tsp file found. Generating Cancelled.", [], { - showOutput: false, + showOutput: true, showPopup: true, }); return; @@ -259,7 +255,7 @@ export async function emitCode(context: vscode.ExtensionContext, uri: vscode.Uri }); if (!selectedProjectFile) { logger.info("No project selected. Generating Cancelled.", [], { - showOutput: false, + showOutput: true, showPopup: true, }); return; @@ -269,7 +265,7 @@ export async function emitCode(context: vscode.ExtensionContext, uri: vscode.Uri const tspStartFile = await getEntrypointTspFile(uri.fsPath); if (!tspStartFile) { logger.info("No main file. Invalid typespec project.", [], { - showOutput: false, + showOutput: true, showPopup: true, }); return; @@ -277,10 +273,7 @@ export async function emitCode(context: vscode.ExtensionContext, uri: vscode.Uri tspProjectFile = tspStartFile; } - logger.info(`Generate from entrypoint file: ${tspProjectFile}`, [], { - showOutput: false, - showPopup: false, - }); + logger.info(`Generate from entrypoint file: ${tspProjectFile}`); const emitterKinds = getRegisterEmitterTypes(); const toEmitterTypeQuickPickItem = (kind: EmitterKind): any => { @@ -299,10 +292,7 @@ export async function emitCode(context: vscode.ExtensionContext, uri: vscode.Uri ignoreFocusOut: true, }); if (!codeType) { - logger.info("No emitter Type selected. Generating Cancelled.", [], { - showOutput: false, - showPopup: false, - }); + logger.info("No emitter Type selected. Generating Cancelled."); return; } await doEmit(context, tspProjectFile, codeType.emitterKind); @@ -326,7 +316,7 @@ async function compile( } return await spawnExecution(cli.command, args, getDirectoryPath(startFile), { - onStdioOut: onStdioOut, - onStdioError: onStdioError, + onStdioOut: logStdoutLineByLineCallBack, + onStdioError: logStderrorLineByLineCallBack, }); } From b2e27cae7ac84662ac65aaee58d6e2c088b4fe54 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Tue, 24 Dec 2024 17:35:43 +0800 Subject: [PATCH 40/42] submit comment every time --- eng/tsp-core/scripts/create-tryit-comment.ts | 21 ++++++++++++++++++- .../src/vscode-cmd/emit-code/emitter.ts | 2 +- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/eng/tsp-core/scripts/create-tryit-comment.ts b/eng/tsp-core/scripts/create-tryit-comment.ts index 62ce170e05..55931a50eb 100644 --- a/eng/tsp-core/scripts/create-tryit-comment.ts +++ b/eng/tsp-core/scripts/create-tryit-comment.ts @@ -30,11 +30,12 @@ async function main() { const tryItComments = data.filter((x) => x.body.includes(TRY_ID_COMMENT_IDENTIFIER)); console.log(`Found ${azoComments.length} Cadl Try It comment(s)`); + const comment = makeComment(folderName, prNumber, vscodeDownloadUrl); if (tryItComments.length > 0) { + await updateComment(repo, tryItComments[0].id, comment, ghAuth); return; } - const comment = makeComment(folderName, prNumber, vscodeDownloadUrl); await writeComment(repo, prNumber, comment, ghAuth); } @@ -85,6 +86,24 @@ async function writeComment(repo: string, prNumber: string, comment: string, ghA console.log("Comment created", response); } +async function updateComment(repo: string, commentId: string, comment: string, ghAuth: string) { + const url = `https://api.github.com/repos/${repo}/issues/comments/${commentId}`; + const body = { + body: comment, + }; + const headers = { + "Content-Type": "application/json", + }; + + const response = await request("PATCH", url, { + headers, + body: JSON.stringify(body), + ghAuth, + }); + + console.log("Comment updated", response); +} + async function request(method: string, url: string, data: any): Promise { const response = await fetch(url, { method, diff --git a/packages/typespec-vscode/src/vscode-cmd/emit-code/emitter.ts b/packages/typespec-vscode/src/vscode-cmd/emit-code/emitter.ts index 6ef43e0c87..2883a48bf1 100644 --- a/packages/typespec-vscode/src/vscode-cmd/emit-code/emitter.ts +++ b/packages/typespec-vscode/src/vscode-cmd/emit-code/emitter.ts @@ -17,7 +17,7 @@ export interface Emitter { export const PreDefinedEmitterPickItems: Record = { schema: { - label: "OpenAPI", + label: "OpenAPI/protoBuf", detail: "Generating OpenAPI from TypeSpec", }, client: { From 3b52923176e7660985bdbe1e20ae0795afbd474e Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Tue, 24 Dec 2024 20:36:26 +0800 Subject: [PATCH 41/42] revert try-it comment update --- eng/tsp-core/scripts/create-tryit-comment.ts | 21 +------------------ .../src/vscode-cmd/emit-code/emitter.ts | 2 +- 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/eng/tsp-core/scripts/create-tryit-comment.ts b/eng/tsp-core/scripts/create-tryit-comment.ts index 55931a50eb..62ce170e05 100644 --- a/eng/tsp-core/scripts/create-tryit-comment.ts +++ b/eng/tsp-core/scripts/create-tryit-comment.ts @@ -30,12 +30,11 @@ async function main() { const tryItComments = data.filter((x) => x.body.includes(TRY_ID_COMMENT_IDENTIFIER)); console.log(`Found ${azoComments.length} Cadl Try It comment(s)`); - const comment = makeComment(folderName, prNumber, vscodeDownloadUrl); if (tryItComments.length > 0) { - await updateComment(repo, tryItComments[0].id, comment, ghAuth); return; } + const comment = makeComment(folderName, prNumber, vscodeDownloadUrl); await writeComment(repo, prNumber, comment, ghAuth); } @@ -86,24 +85,6 @@ async function writeComment(repo: string, prNumber: string, comment: string, ghA console.log("Comment created", response); } -async function updateComment(repo: string, commentId: string, comment: string, ghAuth: string) { - const url = `https://api.github.com/repos/${repo}/issues/comments/${commentId}`; - const body = { - body: comment, - }; - const headers = { - "Content-Type": "application/json", - }; - - const response = await request("PATCH", url, { - headers, - body: JSON.stringify(body), - ghAuth, - }); - - console.log("Comment updated", response); -} - async function request(method: string, url: string, data: any): Promise { const response = await fetch(url, { method, diff --git a/packages/typespec-vscode/src/vscode-cmd/emit-code/emitter.ts b/packages/typespec-vscode/src/vscode-cmd/emit-code/emitter.ts index 2883a48bf1..6ef43e0c87 100644 --- a/packages/typespec-vscode/src/vscode-cmd/emit-code/emitter.ts +++ b/packages/typespec-vscode/src/vscode-cmd/emit-code/emitter.ts @@ -17,7 +17,7 @@ export interface Emitter { export const PreDefinedEmitterPickItems: Record = { schema: { - label: "OpenAPI/protoBuf", + label: "OpenAPI", detail: "Generating OpenAPI from TypeSpec", }, client: { From 82b1a42476c4c70d171d92bc0a772d00061dd5d5 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Wed, 25 Dec 2024 13:48:47 +0800 Subject: [PATCH 42/42] only pop-up project selection window when there are more than one tsp projects --- packages/typespec-vscode/src/npm-utils.ts | 4 +- .../typespec-vscode/src/typespec-utils.ts | 15 ----- .../src/vscode-cmd/emit-code/emit-code.ts | 63 ++++++++++--------- 3 files changed, 35 insertions(+), 47 deletions(-) diff --git a/packages/typespec-vscode/src/npm-utils.ts b/packages/typespec-vscode/src/npm-utils.ts index eaf321090c..33550721db 100644 --- a/packages/typespec-vscode/src/npm-utils.ts +++ b/packages/typespec-vscode/src/npm-utils.ts @@ -41,7 +41,7 @@ export class NpmUtil { } /* identify the action to take for a package. install or skip or cancel or upgrade */ - public async ensureNpmPackageInstallAction( + public async calculateNpmPackageInstallAction( packageName: string, version?: string, ): Promise<{ action: InstallationAction; version?: string }> { @@ -65,7 +65,7 @@ export class NpmUtil { } /* identify the dependency packages need to be upgraded */ - public async ensureNpmPackageDependencyToUpgrade( + public async calculateNpmPackageDependencyToUpgrade( packageName: string, version?: string, dependencyType: npmDependencyType = npmDependencyType.dependencies, diff --git a/packages/typespec-vscode/src/typespec-utils.ts b/packages/typespec-vscode/src/typespec-utils.ts index 9614b0cf41..ad23718e6c 100644 --- a/packages/typespec-vscode/src/typespec-utils.ts +++ b/packages/typespec-vscode/src/typespec-utils.ts @@ -1,4 +1,3 @@ -import { readdir } from "node:fs/promises"; import path from "path"; import vscode from "vscode"; import { StartFileName } from "./const.js"; @@ -27,20 +26,6 @@ export async function getEntrypointTspFile(tspPath: string): Promise { - return { - label: filePath, - path: filePath, - iconPath: { - light: Uri.file(context.asAbsolutePath(`./icons/tsp-file.light.svg`)), - dark: Uri.file(context.asAbsolutePath(`./icons/tsp-file.dark.svg`)), - }, + } else if (targetPathes.length === 1) { + tspProjectFile = targetPathes[0]; + } else { + const toProjectPickItem = (filePath: string): any => { + return { + label: filePath, + path: filePath, + iconPath: { + light: Uri.file(context.asAbsolutePath(`./icons/tsp-file.light.svg`)), + dark: Uri.file(context.asAbsolutePath(`./icons/tsp-file.dark.svg`)), + }, + }; }; - }; - const typespecProjectQuickPickItems: any[] = targetPathes.map((filePath) => - toProjectPickItem(filePath), - ); - const selectedProjectFile = await vscode.window.showQuickPick(typespecProjectQuickPickItems, { - title: "Select a TypeSpec Project", - canPickMany: false, - placeHolder: "Pick a project", - ignoreFocusOut: true, - }); - if (!selectedProjectFile) { - logger.info("No project selected. Generating Cancelled.", [], { - showOutput: true, - showPopup: true, + const typespecProjectQuickPickItems: any[] = targetPathes.map((filePath) => + toProjectPickItem(filePath), + ); + const selectedProjectFile = await vscode.window.showQuickPick(typespecProjectQuickPickItems, { + title: "Select a TypeSpec Project", + canPickMany: false, + placeHolder: "Pick a project", + ignoreFocusOut: true, }); - return; + if (!selectedProjectFile) { + logger.info("No project selected. Generating Cancelled.", [], { + showOutput: true, + showPopup: true, + }); + return; + } + tspProjectFile = selectedProjectFile.path; } - tspProjectFile = selectedProjectFile.path; } else { const tspStartFile = await getEntrypointTspFile(uri.fsPath); if (!tspStartFile) { - logger.info("No main file. Invalid typespec project.", [], { + logger.info(`No entrypoint file (${StartFileName}). Invalid typespec project.`, [], { showOutput: true, showPopup: true, });