From ca3249a4cd1bbb8d8af2e598198a4eb30b5a5837 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Mon, 18 Nov 2024 17:16:01 +0800 Subject: [PATCH 01/66] 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 b1cb2b00a19..9d3cba3e7fc 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 00000000000..46511520679 --- /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 00000000000..4e36487a6d2 --- /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 3647a203f6e..c818dc0bb96 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 00000000000..edf6e23b25c --- /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 1bbed0a3ff1..a33337e71df 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/66] 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 9d3cba3e7fc..247dc245c19 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 4e36487a6d2..bb4086cbf76 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 c818dc0bb96..180bd754a5b 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 edf6e23b25c..a1abcb824d2 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 a33337e71df..3a4a1df779d 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/66] 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 247dc245c19..f11b0bb4ac3 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 c2d3e7cec6b..fa479d79fce 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 00000000000..c8299cde847 --- /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 bb4086cbf76..ce4cce6987e 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 180bd754a5b..da22219b6f1 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 a1abcb824d2..f84cc1b0aaf 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/66] 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 da22219b6f1..9517114a294 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/66] 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 f11b0bb4ac3..4079f551791 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 ce4cce6987e..e516a8b814a 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 9517114a294..154e63a4b9f 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/66] 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 46511520679..00000000000 --- 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 00000000000..07ca66ec482 --- /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 154e63a4b9f..6f0ee684fcd 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 f84cc1b0aaf..d2d29376a80 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 3a4a1df779d..80aa5d2d665 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/66] 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 6f0ee684fcd..fbf60a22475 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 d2d29376a80..7e1cb34b803 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 80aa5d2d665..77de5318434 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/66] 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 07ca66ec482..6eb14c45531 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 fbf60a22475..bd5b441e21b 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/66] 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 6eb14c45531..008debebb3b 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/66] 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 00000000000..9c614b11d5c --- /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 00000000000..649dcb26c8f --- /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 00000000000..a400bccfd0a --- /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 00000000000..2e220729d45 --- /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 00000000000..3490261bede --- /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 fa479d79fce..1da0f81724c 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 008debebb3b..0fd8754ff2b 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 00000000000..bf755034d62 --- /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 e516a8b814a..2faffb2dfae 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/66] 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 0fd8754ff2b..c77b0dbaeec 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/66] 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 c77b0dbaeec..ac2f730742e 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/66] 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 77de5318434..f3da5686bc2 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/66] 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 ac2f730742e..37b62f5e2af 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 7e1cb34b803..9d8d63bfedb 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 00000000000..17361a61a05 --- /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 f3da5686bc2..9952d356924 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/66] 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 37b62f5e2af..b93257ac474 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 17361a61a05..7d3fc517daf 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 9952d356924..c27fb36026b 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/66] 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 b93257ac474..23bc1d1145e 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 2faffb2dfae..00000000000 --- 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 9d8d63bfedb..34dc50573af 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 c27fb36026b..e3dec6579eb 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/66] 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 07ece0d799d..b2dbcf56d8e 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/66] 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 00000000000..6c70a9ff96c --- /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/66] 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 bd5b441e21b..edc0d9f7de6 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/66] 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 23bc1d1145e..c4c062a8209 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/66] 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 00000000000..a871efb4bf0 --- /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 00000000000..b493a458d1e --- /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 00000000000..828b762e17b --- /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 f61b63f79ce..b68c34dbbe8 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 1da0f81724c..814ea799270 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 c8299cde847..b0b4f7f004a 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 c4c062a8209..8c962c53c05 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 edc0d9f7de6..0b61ec379d2 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 7d3fc517daf..144b2c1d760 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/66] 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 b68c34dbbe8..85d54d657f4 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 814ea799270..8dfb828340e 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 b0b4f7f004a..86391b1dc9a 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 8c962c53c05..f3f4bafab45 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 bf755034d62..555bd031577 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 0b61ec379d2..2cc690f552b 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/66] 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 8dfb828340e..40e860c63bd 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/66] 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 f3f4bafab45..68dfffbc06f 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/66] 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 40e860c63bd..e79c28b5fe7 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 68dfffbc06f..faaf23abf2c 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 144b2c1d760..e40834e5f49 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/66] 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 2cec2145852..59580d54819 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 faaf23abf2c..8d1bd6e2289 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 34dc50573af..da8b1583c48 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 b2dbcf56d8e..47d1467efba 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/66] 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 0f88688fccd..11e45c57c2e 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 f7e6d3b18cf..d558beb2327 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/66] 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 11e45c57c2e..3d3b8ac74db 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 e40834e5f49..f111f8b9125 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/66] 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 3d3b8ac74db..2c0df3bee00 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 da8edc58602..d14b3a02fe4 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 c13f5ce9c17..3300121ff06 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/66] 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 2c0df3bee00..f55776b28ef 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 d558beb2327..e5d7196487c 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/66] 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 623efce53e5..d1c8f911314 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 39d8da558e4..c0f0c16ee55 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 47d1467efba..4d84583b8d0 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/66] 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 e5d7196487c..542f270e2d0 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/66] 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 d1c8f911314..d789c2cca23 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 9216ab4e34c..e7b898f945c 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 f55776b28ef..c6fe8c33586 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 555bd031577..bb6a7603273 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 d14b3a02fe4..f5882c33240 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 3300121ff06..6c748d6be83 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/66] 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 c6fe8c33586..a01f7aba893 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/66] 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 00000000000..22b781fe93c --- /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 2e220729d45..00000000000 --- 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 d789c2cca23..096a1235edb 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 86391b1dc9a..9be498b9959 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 a01f7aba893..d6031e357a3 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 bb6a7603273..e3b1281a1d1 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 f5882c33240..5c3c43ff3fd 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 542f270e2d0..e952ed86e5d 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 6c748d6be83..899f88df1a8 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 f111f8b9125..edf3ac05dad 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 ea70042f7dd..f5b72bae7d4 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/66] 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 d6031e357a3..34dfa42391d 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 e3b1281a1d1..04c11fb6e91 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 e952ed86e5d..eaf321090c7 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/66] 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 34dfa42391d..832af23c3a2 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/66] 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 5c3c43ff3fd..efbd44898eb 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 832af23c3a2..eb26068db00 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 04c11fb6e91..6ef43e0c876 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/66] 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 edf3ac05dad..9614b0cf418 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 eb26068db00..94377940e44 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/66] 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 62ce170e05c..55931a50eb8 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 6ef43e0c876..2883a48bf1d 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/66] 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 55931a50eb8..62ce170e05c 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 2883a48bf1d..6ef43e0c876 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/66] 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 eaf321090c7..33550721dbe 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 9614b0cf418..ad23718e6c7 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, }); From 77452a010f6abcf446bd93b0517562955a6522e1 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Fri, 27 Dec 2024 11:31:46 +0800 Subject: [PATCH 43/66] refine text --- packages/typespec-vscode/icons/openapi.svg | 7 +++++ packages/typespec-vscode/icons/schema.svg | 29 ------------------ packages/typespec-vscode/package.json | 21 +++++++++---- .../src/vscode-cmd/emit-code/emit-code.ts | 30 +++++++++++++------ .../src/vscode-cmd/emit-code/emitter.ts | 24 +++++++++++---- 5 files changed, 62 insertions(+), 49 deletions(-) create mode 100644 packages/typespec-vscode/icons/openapi.svg delete mode 100644 packages/typespec-vscode/icons/schema.svg diff --git a/packages/typespec-vscode/icons/openapi.svg b/packages/typespec-vscode/icons/openapi.svg new file mode 100644 index 00000000000..22b781fe93c --- /dev/null +++ b/packages/typespec-vscode/icons/openapi.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/packages/typespec-vscode/icons/schema.svg b/packages/typespec-vscode/icons/schema.svg deleted file mode 100644 index a871efb4bf0..00000000000 --- a/packages/typespec-vscode/icons/schema.svg +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/typespec-vscode/package.json b/packages/typespec-vscode/package.json index 096a1235edb..9aef805f178 100644 --- a/packages/typespec-vscode/package.json +++ b/packages/typespec-vscode/package.json @@ -118,7 +118,7 @@ "language": { "type": "string", "enum": [ - "DotNet", + ".NET", "Java", "JavaScript", "Python", @@ -133,12 +133,16 @@ "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" }, + "sourceRepo": { + "type": "string", + "description": "Define the source repository of the emitter package." + }, "kind": { "type": "string", "enum": [ "client", "server", - "schema" + "openapi" ], "description": "Define the emitter kind." } @@ -146,39 +150,46 @@ }, "default": [ { - "language": "DotNet", + "language": ".NET", "package": "@typespec/http-client-csharp", + "sourceRepo": "https://github.com/microsoft/typespec/tree/main/packages/http-client-csharp", "kind": "client" }, { "language": "Java", "package": "@typespec/http-client-java", + "sourceRepo": "https://github.com/microsoft/typespec/tree/main/packages/http-client-java ", "kind": "client" }, { "language": "JavaScript", "package": "@azure-tools/typespec-ts", + "sourceRepo": "https://github.com/Azure/autorest.typescript/tree/main/packages/typespec-ts", "kind": "client" }, { "language": "Python", "package": "@typespec/http-client-python", + "sourceRepo": "https://github.com/microsoft/typespec/tree/main/packages/http-client-python", "kind": "client" }, { - "language": "DotNet", + "language": ".NET", "package": "@typespec/http-server-csharp", + "sourceRepo": "https://github.com/microsoft/typespec/tree/main/packages/http-server-csharp", "kind": "server" }, { "language": "JavaScript", "package": "@typespec/http-server-javascript", + "sourceRepo": "https://github.com/microsoft/typespec/tree/main/packages/http-server-javascript", "kind": "server" }, { "language": "OpenAPI3", "package": "@typespec/openapi3", - "kind": "schema" + "sourceRepo": "https://github.com/microsoft/typespec/tree/main/packages/openapi3", + "kind": "openapi" } ] } 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 6ac8bd23579..b223d2b6c88 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 @@ -17,6 +17,7 @@ import { EmitQuickPickItem } from "./emit-quick-pick-item.js"; import { Emitter, EmitterKind, + getLanguageAlias, getRegisterEmitters, getRegisterEmitterTypes, PreDefinedEmitterPickItems, @@ -35,15 +36,18 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin const baseDir = getDirectoryPath(mainTspFile); const toQuickPickItem = (e: Emitter): EmitQuickPickItem => { + const moreDetail = e.sourceRepo ? `[**More Detail**](${e.sourceRepo})` : ""; return { language: e.language, package: e.package, emitterKind: e.kind, label: e.language, - detail: `Generate ${e.language} ${e.kind} code from ${e.package}`, + detail: `Generate ${e.kind} code for ${e.language} by TypeSpec library ${e.package}.${moreDetail}`, picked: false, fromConfig: false, - iconPath: Uri.file(context.asAbsolutePath(`./icons/${e.language.toLowerCase()}.svg`)), + iconPath: Uri.file( + context.asAbsolutePath(`./icons/${getLanguageAlias(e.language).toLowerCase()}.svg`), + ), }; }; @@ -51,14 +55,14 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin const all = [...registerEmitters].map((e) => toQuickPickItem(e)); const selectedEmitter = await vscode.window.showQuickPick(all, { - title: "Select a Language", + title: `Select a Language for ${kind} code generation`, canPickMany: false, - placeHolder: "Pick a Language", + placeHolder: `Select a Language for ${kind} code generation`, ignoreFocusOut: true, }); if (!selectedEmitter) { - logger.info("No emitter selected. Generating Cancelled."); + logger.info("No language selected. Generating Cancelled."); return; } @@ -173,7 +177,11 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin ); return; } - const outputDir = path.join(baseDir, selectedEmitter.emitterKind, selectedEmitter.language); + const outputDir = path.join( + baseDir, + selectedEmitter.emitterKind, + getLanguageAlias(selectedEmitter.language), + ); const options: Record = {}; options["emitter-output-dir"] = outputDir; @@ -238,7 +246,7 @@ export async function emitCode(context: vscode.ExtensionContext, uri: vscode.Uri } else { const toProjectPickItem = (filePath: string): any => { return { - label: filePath, + label: `Project: ${filePath}`, path: filePath, iconPath: { light: Uri.file(context.asAbsolutePath(`./icons/tsp-file.light.svg`)), @@ -250,7 +258,7 @@ export async function emitCode(context: vscode.ExtensionContext, uri: vscode.Uri toProjectPickItem(filePath), ); const selectedProjectFile = await vscode.window.showQuickPick(typespecProjectQuickPickItems, { - title: "Select a TypeSpec Project", + title: "Select a Project", canPickMany: false, placeHolder: "Pick a project", ignoreFocusOut: true, @@ -280,9 +288,13 @@ export async function emitCode(context: vscode.ExtensionContext, uri: vscode.Uri const emitterKinds = getRegisterEmitterTypes(); const toEmitterTypeQuickPickItem = (kind: EmitterKind): any => { + const registerEmitters = getRegisterEmitters(kind); + const supportedLanguages = registerEmitters.map((e) => e.language).join(", "); return { label: PreDefinedEmitterPickItems[kind]?.label ?? kind, - detail: PreDefinedEmitterPickItems[kind]?.detail ?? `Generate ${kind} code from TypeSpec`, + detail: + PreDefinedEmitterPickItems[kind]?.detail ?? + `Generate ${kind} code from TypeSpec files. Supported languages are ${supportedLanguages}.`, emitterKind: kind, iconPath: Uri.file(context.asAbsolutePath(`./icons/${kind.toLowerCase()}.svg`)), }; 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 6ef43e0c876..29a160b0624 100644 --- a/packages/typespec-vscode/src/vscode-cmd/emit-code/emitter.ts +++ b/packages/typespec-vscode/src/vscode-cmd/emit-code/emitter.ts @@ -3,7 +3,7 @@ import logger from "../../log/logger.js"; import { SettingName } from "../../types.js"; export enum EmitterKind { - Schema = "schema", + Schema = "openapi", Client = "client", Server = "server", } @@ -12,21 +12,23 @@ export interface Emitter { language: string; package: string; version?: string; + sourceRepo?: string; kind: EmitterKind; } export const PreDefinedEmitterPickItems: Record = { - schema: { - label: "OpenAPI", - detail: "Generating OpenAPI from TypeSpec", + openapi: { + label: "OpenAPI Document", + detail: "Generating OpenAPI3 Document from TypeSpec files.", }, client: { label: "Client Code", - detail: "Generating Client Code from TypeSpec.", + detail: + "Generating Client Code from TypeSpec files. Supported languages are .NET, Python, Java, JavaScript.", }, server: { label: " Server Stub", - detail: "Generating Server Stub from TypeSpec", + detail: "Generating Server Stub from TypeSpec files. Supported languages are .NET, JavaScript.", }, }; @@ -49,6 +51,7 @@ function getEmitter(kind: EmitterKind, emitter: Emitter): Emitter | undefined { language: emitter.language, package: packageName, version: version, + sourceRepo: emitter.sourceRepo, kind: kind, }; } @@ -69,3 +72,12 @@ export function getRegisterEmitterTypes(): ReadonlyArray { extensionConfig.get(SettingName.GenerateCodeEmitters) ?? []; return Array.from(new Set(emitters.map((emitter) => emitter.kind))); } + +const languageAlias: Record = { + ".net": "dotnet", +}; + +/*return the alias of the language if it exists, otherwise return the original language. */ +export function getLanguageAlias(language: string): string { + return languageAlias[language] ?? language; +} From 0e2ab0fd2205b259e2956052fff38c6c9ce43987 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Sun, 29 Dec 2024 10:48:16 +0800 Subject: [PATCH 44/66] support check multiple dependency type --- packages/typespec-vscode/src/npm-utils.ts | 36 ++++++++++++++++--- .../src/vscode-cmd/emit-code/emit-code.ts | 2 +- .../src/vscode-cmd/emit-code/emitter.ts | 2 +- 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/packages/typespec-vscode/src/npm-utils.ts b/packages/typespec-vscode/src/npm-utils.ts index 33550721dbe..16b49bcba4c 100644 --- a/packages/typespec-vscode/src/npm-utils.ts +++ b/packages/typespec-vscode/src/npm-utils.ts @@ -68,7 +68,7 @@ export class NpmUtil { public async calculateNpmPackageDependencyToUpgrade( packageName: string, version?: string, - dependencyType: npmDependencyType = npmDependencyType.dependencies, + dependencyTypes: npmDependencyType[] = [npmDependencyType.dependencies], on?: spawnExecutionEvents, ): Promise { const dependenciesToInstall: string[] = []; @@ -78,20 +78,32 @@ export class NpmUtil { } /* get dependencies. */ + if (dependencyTypes.length === 0) { + logger.info("No dependency to check."); + return dependenciesToInstall; + } + try { const dependenciesResult = await spawnExecution( "npm", - ["view", packageFullName, dependencyType, "--json"], + ["view", packageFullName, ...dependencyTypes, "--json"], this.cwd, on, ); if (dependenciesResult.exitCode === 0) { const json = JSON.parse(dependenciesResult.stdout); - for (const [key, value] of Object.entries(json)) { + const jsonDependencies: any[] = []; + if (dependencyTypes.length > 1) { + jsonDependencies.push(...Object.values(json)); + } else { + jsonDependencies.push(json); + } + const dependencies = parseDependency(jsonDependencies); + for (const [key, value] of Object.entries(dependencies)) { const { installed, version: installedVersion } = await this.isPackageInstalled(key); if (installed && installedVersion) { - if (!this.isValidVersion(installedVersion, value as string)) { + if (!this.isValidVersion(installedVersion, value.join("||"))) { dependenciesToInstall.push(`${key}@latest`); } } @@ -106,6 +118,22 @@ export class NpmUtil { logger.error("Error getting dependencies.", [err]); } + function parseDependency(jsonDependencies: any[]): Record { + const dependencies: Record = {}; + for (const dependency of jsonDependencies) { + for (const [key, value] of Object.entries(dependency)) { + if (dependencies[key]) { + dependencies[key].push(value as string); + } else { + dependencies[key] = [value as string]; + } + } + } + return dependencies; + } + + logger.info("Finishing upgrade check."); + return dependenciesToInstall; } 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 b223d2b6c88..98bed66c68a 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 @@ -114,7 +114,7 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin const dependenciesToInstall = await npmUtil.calculateNpmPackageDependencyToUpgrade( selectedEmitter.package, version, - npmDependencyType.peerDependencies, + [npmDependencyType.dependencies, npmDependencyType.peerDependencies], ); logger.info(`${dependenciesToInstall}`); if (dependenciesToInstall.length > 0) { 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 29a160b0624..55c9d42dbbf 100644 --- a/packages/typespec-vscode/src/vscode-cmd/emit-code/emitter.ts +++ b/packages/typespec-vscode/src/vscode-cmd/emit-code/emitter.ts @@ -74,7 +74,7 @@ export function getRegisterEmitterTypes(): ReadonlyArray { } const languageAlias: Record = { - ".net": "dotnet", + ".NET": "dotnet", }; /*return the alias of the language if it exists, otherwise return the original language. */ From d353b46ddf52b1780f75d4689809b147d5d158d8 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Sun, 29 Dec 2024 11:25:34 +0800 Subject: [PATCH 45/66] change to use spawnExecutionAndLogToOutput --- packages/typespec-vscode/src/npm-utils.ts | 19 ++++--------------- .../typespec-vscode/src/typespec-utils.ts | 14 -------------- .../src/vscode-cmd/emit-code/emit-code.ts | 19 ++++--------------- 3 files changed, 8 insertions(+), 44 deletions(-) diff --git a/packages/typespec-vscode/src/npm-utils.ts b/packages/typespec-vscode/src/npm-utils.ts index 16b49bcba4c..05a17527130 100644 --- a/packages/typespec-vscode/src/npm-utils.ts +++ b/packages/typespec-vscode/src/npm-utils.ts @@ -2,7 +2,7 @@ import { readFile } from "fs/promises"; import path from "path"; import semver from "semver"; import logger from "./log/logger.js"; -import { ExecOutput, loadModule, spawnExecution, spawnExecutionEvents } from "./utils.js"; +import { ExecOutput, loadModule, spawnExecutionAndLogToOutput } from "./utils.js"; export enum InstallationAction { Install = "Install", @@ -32,12 +32,8 @@ export class NpmUtil { this.cwd = cwd; } - public async npmInstallPackages( - packages: string[] = [], - options: any = {}, - on?: spawnExecutionEvents, - ): Promise { - return spawnExecution("npm", ["install", ...packages], this.cwd, on); + public async npmInstallPackages(packages: string[] = [], options: any = {}): Promise { + return spawnExecutionAndLogToOutput("npm", ["install", ...packages], this.cwd); } /* identify the action to take for a package. install or skip or cancel or upgrade */ @@ -69,7 +65,6 @@ export class NpmUtil { packageName: string, version?: string, dependencyTypes: npmDependencyType[] = [npmDependencyType.dependencies], - on?: spawnExecutionEvents, ): Promise { const dependenciesToInstall: string[] = []; let packageFullName = packageName; @@ -84,11 +79,10 @@ export class NpmUtil { } try { - const dependenciesResult = await spawnExecution( + const dependenciesResult = await spawnExecutionAndLogToOutput( "npm", ["view", packageFullName, ...dependencyTypes, "--json"], this.cwd, - on, ); if (dependenciesResult.exitCode === 0) { @@ -112,9 +106,6 @@ export class NpmUtil { logger.error("Error getting dependencies.", [dependenciesResult.stderr]); } } catch (err) { - if (on && on.onError) { - on.onError(err, "", ""); - } logger.error("Error getting dependencies.", [err]); } @@ -132,8 +123,6 @@ export class NpmUtil { return dependencies; } - logger.info("Finishing upgrade check."); - return dependenciesToInstall; } diff --git a/packages/typespec-vscode/src/typespec-utils.ts b/packages/typespec-vscode/src/typespec-utils.ts index ad23718e6c7..ebe1620f766 100644 --- a/packages/typespec-vscode/src/typespec-utils.ts +++ b/packages/typespec-vscode/src/typespec-utils.ts @@ -1,23 +1,9 @@ import path from "path"; import vscode from "vscode"; import { StartFileName } from "./const.js"; -import logger from "./log/logger.js"; import { getDirectoryPath, normalizeSlashes } from "./path-utils.js"; import { isFile } from "./utils.js"; -export const logStdoutLineByLineCallBack = (str: string) => { - str - .trim() - .split("\n") - .forEach((line) => logger.info(line)); -}; -export const logStderrorLineByLineCallBack = (str: string) => { - str - .trim() - .split("\n") - .forEach((line) => logger.error(line)); -}; - export async function getEntrypointTspFile(tspPath: string): Promise { const isFilePath = await isFile(tspPath); const baseDir = isFilePath ? getDirectoryPath(tspPath) : tspPath; 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 98bed66c68a..a9ec4db5484 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 @@ -6,13 +6,8 @@ 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, - logStderrorLineByLineCallBack, - logStdoutLineByLineCallBack, - TraverseMainTspFileInWorkspace, -} from "../../typespec-utils.js"; -import { ExecOutput, isFile, spawnExecution } from "../../utils.js"; +import { getEntrypointTspFile, TraverseMainTspFileInWorkspace } from "../../typespec-utils.js"; +import { ExecOutput, isFile, spawnExecutionAndLogToOutput } from "../../utils.js"; import { EmitQuickPickItem } from "./emit-quick-pick-item.js"; import { Emitter, @@ -138,10 +133,7 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin }, async () => { try { - const npmInstallResult = await npmUtil.npmInstallPackages(packagesToInstall, undefined, { - onStdioOut: logStdoutLineByLineCallBack, - onStdioError: logStderrorLineByLineCallBack, - }); + const npmInstallResult = await npmUtil.npmInstallPackages(packagesToInstall, undefined); if (npmInstallResult.exitCode !== 0) { logger.error( `Error occurred when installing packages.`, @@ -330,8 +322,5 @@ async function compile( args.push("--option", `${emitter}.${key}=${value}`); } - return await spawnExecution(cli.command, args, getDirectoryPath(startFile), { - onStdioOut: logStdoutLineByLineCallBack, - onStdioError: logStderrorLineByLineCallBack, - }); + return await spawnExecutionAndLogToOutput(cli.command, args, getDirectoryPath(startFile)); } From 0bf3f40bb46bb4474f820333aae00405f34b7133 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Sun, 29 Dec 2024 12:15:00 +0800 Subject: [PATCH 46/66] retrive entrypoint file from package.json and parent directory --- .../typespec-vscode/src/typespec-utils.ts | 37 +++++++++++++++++-- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/packages/typespec-vscode/src/typespec-utils.ts b/packages/typespec-vscode/src/typespec-utils.ts index ebe1620f766..fc112a6cc5e 100644 --- a/packages/typespec-vscode/src/typespec-utils.ts +++ b/packages/typespec-vscode/src/typespec-utils.ts @@ -1,15 +1,44 @@ +import { readFile } from "fs/promises"; import path from "path"; import vscode from "vscode"; import { StartFileName } from "./const.js"; +import logger from "./log/logger.js"; import { getDirectoryPath, normalizeSlashes } from "./path-utils.js"; import { isFile } from "./utils.js"; export async function getEntrypointTspFile(tspPath: string): Promise { const isFilePath = await isFile(tspPath); - const baseDir = isFilePath ? getDirectoryPath(tspPath) : tspPath; - const mainTspFile = path.resolve(baseDir, StartFileName); - if (await isFile(mainTspFile)) { - return mainTspFile; + let baseDir = isFilePath ? getDirectoryPath(tspPath) : tspPath; + + while (true) { + const pkgPath = path.resolve(baseDir, "package.json"); + if (await isFile(pkgPath)) { + /* get the tspMain from package.json. */ + try { + const data = await readFile(pkgPath, { encoding: "utf-8" }); + const packageJson = JSON.parse(data); + const tspMain = packageJson.tspMain; + if (typeof tspMain === "string") { + const tspMainFile = path.resolve(baseDir, tspMain); + if (await isFile(tspMainFile)) { + logger.debug(`tspMain file ${tspMainFile} selected as entrypoint file.`); + return tspMainFile; + } + } + } catch (error) { + logger.error(`An error occurred while reading the package.json file ${pkgPath}`, [error]); + } + } + + const mainTspFile = path.resolve(baseDir, StartFileName); + if (await isFile(mainTspFile)) { + return mainTspFile; + } + const parentDir = getDirectoryPath(baseDir); + if (parentDir === baseDir) { + break; + } + baseDir = parentDir; } return undefined; From 9d7c0b131fab86252e34fa046e9cad8e70a96e59 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Mon, 30 Dec 2024 13:53:35 +0800 Subject: [PATCH 47/66] implement package install or upgrade page --- packages/typespec-vscode/package.json | 14 ++ packages/typespec-vscode/src/npm-utils.ts | 6 +- .../src/vscode-cmd/emit-code/emit-code.ts | 172 ++++++++++-------- .../emit-code/emit-quick-pick-item.ts | 2 + .../src/vscode-cmd/emit-code/emitter.ts | 2 + 5 files changed, 120 insertions(+), 76 deletions(-) diff --git a/packages/typespec-vscode/package.json b/packages/typespec-vscode/package.json index 9aef805f178..7074bddd114 100644 --- a/packages/typespec-vscode/package.json +++ b/packages/typespec-vscode/package.json @@ -137,6 +137,13 @@ "type": "string", "description": "Define the source repository of the emitter package." }, + "requisites": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Define the requisites of the emitter package." + }, "kind": { "type": "string", "enum": [ @@ -153,12 +160,19 @@ "language": ".NET", "package": "@typespec/http-client-csharp", "sourceRepo": "https://github.com/microsoft/typespec/tree/main/packages/http-client-csharp", + "requisites": [ + ".NET 8.0 SDK" + ], "kind": "client" }, { "language": "Java", "package": "@typespec/http-client-java", "sourceRepo": "https://github.com/microsoft/typespec/tree/main/packages/http-client-java ", + "requisites": [ + "Java 17 or above", + "Maven" + ], "kind": "client" }, { diff --git a/packages/typespec-vscode/src/npm-utils.ts b/packages/typespec-vscode/src/npm-utils.ts index 05a17527130..1ce5edc8bf1 100644 --- a/packages/typespec-vscode/src/npm-utils.ts +++ b/packages/typespec-vscode/src/npm-utils.ts @@ -65,8 +65,8 @@ export class NpmUtil { packageName: string, version?: string, dependencyTypes: npmDependencyType[] = [npmDependencyType.dependencies], - ): Promise { - const dependenciesToInstall: string[] = []; + ): Promise<{ name: string; version: string }[]> { + const dependenciesToInstall: { name: string; version: string }[] = []; let packageFullName = packageName; if (version) { packageFullName = `${packageName}@${version}`; @@ -98,7 +98,7 @@ export class NpmUtil { const { installed, version: installedVersion } = await this.isPackageInstalled(key); if (installed && installedVersion) { if (!this.isValidVersion(installedVersion, value.join("||"))) { - dependenciesToInstall.push(`${key}@latest`); + dependenciesToInstall.push({ name: key, version: "latest" }); } } } 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 a9ec4db5484..5d373c2ed1c 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 @@ -35,6 +35,9 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin return { language: e.language, package: e.package, + version: e.version, + requisites: e.requisites, + sourceRepo: e.sourceRepo, emitterKind: e.kind, label: e.language, detail: `Generate ${e.kind} code for ${e.language} by TypeSpec library ${e.package}.${moreDetail}`, @@ -71,89 +74,112 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin 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 (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 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 generating`); - } else { - logger.info( - `Need to manually install the package ${selectedEmitter.package}@${version}. Generating Cancelled.`, - [], - { - showOutput: false, - showPopup: true, - }, - ); - return; - } - } else if (action === InstallationAction.Install) { + const typespecProjectQuickPickItems = []; + if (action === InstallationAction.Upgrade || action === InstallationAction.Install) { + const minimumRequisites = selectedEmitter.requisites + ? ` Minimum requisites: install ${selectedEmitter.requisites?.join(", ")}` + : ""; + const moreDetail = selectedEmitter.sourceRepo + ? `[**More Detail**](${selectedEmitter.sourceRepo})` + : ""; let packageFullName = selectedEmitter.package; if (version) { packageFullName = `${selectedEmitter.package}@${version}`; } - logger.info(`To install ${packageFullName}`); + typespecProjectQuickPickItems.push({ + label: `${selectedEmitter.package}`, + detail: `TypeSpec library for emitting ${selectedEmitter.language} from TypeSpec files.${minimumRequisites} ${moreDetail}`, + packageFullName: packageFullName, + picked: true, + }); + packagesToInstall.push(packageFullName); + } + + for (const p of packagesToInstall) { /* verify dependency packages. */ - const dependenciesToInstall = await npmUtil.calculateNpmPackageDependencyToUpgrade( - selectedEmitter.package, - version, - [npmDependencyType.dependencies, npmDependencyType.peerDependencies], - ); - logger.info(`${dependenciesToInstall}`); - if (dependenciesToInstall.length > 0) { - vscode.window.showInformationMessage( - `Need to manually upgrade following dependency packages: ${dependenciesToInstall.join("\\n")}. \nGenerating Cancelled`, - "OK", + try { + const dependenciesToInstall = await npmUtil.calculateNpmPackageDependencyToUpgrade( + p, + version, + [npmDependencyType.dependencies, npmDependencyType.peerDependencies], ); - return; + if (dependenciesToInstall.length > 0) { + packagesToInstall.push(...dependenciesToInstall.map((d) => `${d.name}@${d.version}`)); + for (const dep of dependenciesToInstall) { + const packageFullName = `${dep.name}@${version ?? "latest"}`; + typespecProjectQuickPickItems.push({ + label: `${dep.name}`, + detail: `Upgrade Dependency ${dep.name}.`, + packageFullName: packageFullName, + picked: true, + }); + } + } + } catch (err) { + logger.error(`Exception occurred when check dependency packages for ${p}.`); } - packagesToInstall.push(`${packageFullName}`); } - /* npm install packages. */ - if (packagesToInstall.length > 0) { - 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); - if (npmInstallResult.exitCode !== 0) { - logger.error( - `Error occurred when installing packages.`, - [`${npmInstallResult.stderr}`], - { - showOutput: true, - showPopup: true, - }, - ); - return; + if (typespecProjectQuickPickItems.length > 0) { + const selectedPackages = await vscode.window.showQuickPick(typespecProjectQuickPickItems, { + title: "Install or update libraries", + canPickMany: true, + placeHolder: "Here are libraries to install or update.", + ignoreFocusOut: true, + }); + if (!selectedPackages || selectedPackages.length === 0) { + logger.info("No package selected. Generating Cancelled.", [], { + showOutput: true, + showPopup: true, + }); + return; + } + /* npm install packages. */ + if (selectedPackages.length > 0) { + const installPackages = selectedPackages.map((p) => p.packageFullName); + logger.info(`Install ${installPackages.join("\n\n")} under ${baseDir}`, [], { + showOutput: true, + showPopup: true, + }); + const installResult = await vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: "Installing packages...", + cancellable: false, + }, + async () => { + try { + const npmInstallResult = await npmUtil.npmInstallPackages(installPackages, undefined); + if (npmInstallResult.exitCode !== 0) { + logger.error( + `Error occurred when installing packages.`, + [`${npmInstallResult.stderr}`], + { + showOutput: true, + showPopup: true, + }, + ); + return false; + } else { + return true; + } + } catch (err) { + logger.error(`Exception occurred when installing packages.`, [err], { + showOutput: true, + showPopup: true, + }); + return false; } - } catch (err) { - logger.error(`Exception occurred when installing packages.`, [err], { - showOutput: true, - showPopup: true, - }); - return; - } - }, - ); + }, + ); + if (!installResult) { + logger.error(`Error occurred when installing packages. Generating Cancelled.`, [], { + showOutput: false, + showPopup: true, + }); + return; + } + } } /* emit */ diff --git a/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-quick-pick-item.ts b/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-quick-pick-item.ts index 9be498b9959..e98fc48a9ac 100644 --- a/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-quick-pick-item.ts +++ b/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-quick-pick-item.ts @@ -5,6 +5,8 @@ export interface EmitQuickPickItem extends vscode.QuickPickItem { language: string; package: string; version?: string; + sourceRepo?: string; + requisites?: string[]; fromConfig: boolean; outputDir?: string; emitterKind: EmitterKind; 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 55c9d42dbbf..aa68e513f86 100644 --- a/packages/typespec-vscode/src/vscode-cmd/emit-code/emitter.ts +++ b/packages/typespec-vscode/src/vscode-cmd/emit-code/emitter.ts @@ -13,6 +13,7 @@ export interface Emitter { package: string; version?: string; sourceRepo?: string; + requisites?: string[]; kind: EmitterKind; } @@ -52,6 +53,7 @@ function getEmitter(kind: EmitterKind, emitter: Emitter): Emitter | undefined { package: packageName, version: version, sourceRepo: emitter.sourceRepo, + requisites: emitter.requisites, kind: kind, }; } From 88eb4a4aaa3f5cf31e4c7bd376e0ab4ae17319df Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Mon, 30 Dec 2024 15:53:29 +0800 Subject: [PATCH 48/66] log refine --- .../typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 5d373c2ed1c..9ffca7e18be 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 @@ -137,7 +137,7 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin /* npm install packages. */ if (selectedPackages.length > 0) { const installPackages = selectedPackages.map((p) => p.packageFullName); - logger.info(`Install ${installPackages.join("\n\n")} under ${baseDir}`, [], { + logger.info(`Install ${installPackages.join("\n\n")} under directory ${baseDir}`, [], { showOutput: true, showPopup: true, }); @@ -204,7 +204,7 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin const options: Record = {}; options["emitter-output-dir"] = outputDir; logger.info( - `Start to generate ${selectedEmitter.language} ${selectedEmitter.emitterKind} code under ${outputDir}...`, + `Start to generate ${selectedEmitter.language} ${selectedEmitter.emitterKind} code under directory ${outputDir}`, ); await vscode.window.withProgress( { From 7cec0fbeab546fe272800d36275df77a78f551e0 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Mon, 30 Dec 2024 16:27:17 +0800 Subject: [PATCH 49/66] disable log pretty --- .../src/vscode-cmd/emit-code/emit-code.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) 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 9ffca7e18be..7d387927f03 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 @@ -214,7 +214,13 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin }, async () => { try { - const compileResult = await compile(cli, mainTspFile, selectedEmitter.package, options); + const compileResult = await compile( + cli, + mainTspFile, + selectedEmitter.package, + options, + false, + ); if (compileResult.exitCode !== 0) { logger.error( `Generating ${selectedEmitter.emitterKind} code for ${selectedEmitter.language}...Failed`, @@ -336,6 +342,7 @@ async function compile( startFile: string, emitter: string, options: Record, + logPretty?: boolean, ): Promise { const args: string[] = cli.args ?? []; args.push("compile"); @@ -343,6 +350,10 @@ async function compile( if (emitter) { args.push("--emit", emitter); } + if (logPretty !== undefined) { + args.push("--pretty"); + args.push(logPretty ? "true" : "false"); + } for (const [key, value] of Object.entries(options)) { args.push("--option", `${emitter}.${key}=${value}`); From 8af049cb940cd88539bf56967c79cadd5ad3d5b1 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Mon, 30 Dec 2024 17:06:18 +0800 Subject: [PATCH 50/66] add progress bar for calculating packages to install --- .../src/vscode-cmd/emit-code/emit-code.ts | 98 ++++++++++--------- 1 file changed, 54 insertions(+), 44 deletions(-) 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 7d387927f03..5b812f96e9c 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 @@ -74,54 +74,64 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin selectedEmitter.version, ); - const typespecProjectQuickPickItems = []; - if (action === InstallationAction.Upgrade || action === InstallationAction.Install) { - const minimumRequisites = selectedEmitter.requisites - ? ` Minimum requisites: install ${selectedEmitter.requisites?.join(", ")}` - : ""; - const moreDetail = selectedEmitter.sourceRepo - ? `[**More Detail**](${selectedEmitter.sourceRepo})` - : ""; - let packageFullName = selectedEmitter.package; - if (version) { - packageFullName = `${selectedEmitter.package}@${version}`; - } - typespecProjectQuickPickItems.push({ - label: `${selectedEmitter.package}`, - detail: `TypeSpec library for emitting ${selectedEmitter.language} from TypeSpec files.${minimumRequisites} ${moreDetail}`, - packageFullName: packageFullName, - picked: true, - }); - packagesToInstall.push(packageFullName); - } + const installPackageQuickPickItems = await vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: "Calculating packages to install or upgrade ...", + cancellable: false, + }, + async () => { + const packageQuickPickItems = []; + if (action === InstallationAction.Upgrade || action === InstallationAction.Install) { + const minimumRequisites = selectedEmitter.requisites + ? ` Minimum requisites: install ${selectedEmitter.requisites?.join(", ")}` + : ""; + const moreDetail = selectedEmitter.sourceRepo + ? `[**More Detail**](${selectedEmitter.sourceRepo})` + : ""; + let packageFullName = selectedEmitter.package; + if (version) { + packageFullName = `${selectedEmitter.package}@${version}`; + } + packageQuickPickItems.push({ + label: `${selectedEmitter.package}`, + detail: `TypeSpec library for emitting ${selectedEmitter.language} from TypeSpec files.${minimumRequisites} ${moreDetail}`, + packageFullName: packageFullName, + picked: true, + }); + packagesToInstall.push(packageFullName); + } - for (const p of packagesToInstall) { - /* verify dependency packages. */ - try { - const dependenciesToInstall = await npmUtil.calculateNpmPackageDependencyToUpgrade( - p, - version, - [npmDependencyType.dependencies, npmDependencyType.peerDependencies], - ); - if (dependenciesToInstall.length > 0) { - packagesToInstall.push(...dependenciesToInstall.map((d) => `${d.name}@${d.version}`)); - for (const dep of dependenciesToInstall) { - const packageFullName = `${dep.name}@${version ?? "latest"}`; - typespecProjectQuickPickItems.push({ - label: `${dep.name}`, - detail: `Upgrade Dependency ${dep.name}.`, - packageFullName: packageFullName, - picked: true, - }); + for (const p of packagesToInstall) { + /* verify dependency packages. */ + try { + const dependenciesToInstall = await npmUtil.calculateNpmPackageDependencyToUpgrade( + p, + version, + [npmDependencyType.dependencies, npmDependencyType.peerDependencies], + ); + if (dependenciesToInstall.length > 0) { + packagesToInstall.push(...dependenciesToInstall.map((d) => `${d.name}@${d.version}`)); + for (const dep of dependenciesToInstall) { + const packageFullName = `${dep.name}@${version ?? "latest"}`; + packageQuickPickItems.push({ + label: `${dep.name}`, + detail: `Upgrade Dependency ${dep.name}.`, + packageFullName: packageFullName, + picked: true, + }); + } + } + } catch (err) { + logger.error(`Exception occurred when check dependency packages for ${p}.`); } } - } catch (err) { - logger.error(`Exception occurred when check dependency packages for ${p}.`); - } - } + return packageQuickPickItems; + }, + ); - if (typespecProjectQuickPickItems.length > 0) { - const selectedPackages = await vscode.window.showQuickPick(typespecProjectQuickPickItems, { + if (installPackageQuickPickItems.length > 0) { + const selectedPackages = await vscode.window.showQuickPick(installPackageQuickPickItems, { title: "Install or update libraries", canPickMany: true, placeHolder: "Here are libraries to install or update.", From 7a96a1c2970523cadf9e6df0cc4edec661c9b567 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Thu, 2 Jan 2025 08:40:50 +0800 Subject: [PATCH 51/66] parse the spawn output and display in correct way --- .../src/vscode-cmd/emit-code/emit-code.ts | 34 ++++++++++++++----- 1 file changed, 25 insertions(+), 9 deletions(-) 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 5b812f96e9c..cc8acdf0543 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 @@ -250,15 +250,31 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin }, ); } - } catch (err) { - logger.error( - `Exception occurred when generating ${selectedEmitter.emitterKind} code for ${selectedEmitter.language}.`, - [err], - { - showOutput: true, - showPopup: true, - }, - ); + } catch (err: any) { + if (typeof err === "object" && "stdout" in err && "stderr" in err && `error` in err) { + const execOutput = err as ExecOutput; + const details = []; + if (execOutput.stdout !== "") details.push(execOutput.stdout); + if (execOutput.stderr !== "") details.push(execOutput.stderr); + if (execOutput.error) details.push(execOutput.error); + logger.error( + `Exception occurred when generating ${selectedEmitter.emitterKind} code for ${selectedEmitter.language}.`, + details, + { + showOutput: true, + showPopup: true, + }, + ); + } else { + logger.error( + `Exception occurred when generating ${selectedEmitter.emitterKind} code for ${selectedEmitter.language}.`, + [err], + { + showOutput: true, + showPopup: true, + }, + ); + } } }, ); From b0fb151b1fb2452bd8f507a244ef97fd491feb60 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Thu, 2 Jan 2025 10:35:29 +0800 Subject: [PATCH 52/66] typo --- packages/typespec-vscode/src/npm-utils.ts | 12 ++++++------ .../src/vscode-cmd/emit-code/emit-code.ts | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/typespec-vscode/src/npm-utils.ts b/packages/typespec-vscode/src/npm-utils.ts index 1ce5edc8bf1..d3b5f618674 100644 --- a/packages/typespec-vscode/src/npm-utils.ts +++ b/packages/typespec-vscode/src/npm-utils.ts @@ -4,7 +4,7 @@ import semver from "semver"; import logger from "./log/logger.js"; import { ExecOutput, loadModule, spawnExecutionAndLogToOutput } from "./utils.js"; -export enum InstallationAction { +export enum InstallAction { Install = "Install", Upgrade = "Upgrade", Skip = "Skip", @@ -40,23 +40,23 @@ export class NpmUtil { public async calculateNpmPackageInstallAction( packageName: string, version?: string, - ): Promise<{ action: InstallationAction; version?: string }> { + ): Promise<{ action: InstallAction; version?: string }> { const { installed: isPackageInstalled, version: installedVersion } = await this.isPackageInstalled(packageName); if (isPackageInstalled) { if (version && installedVersion !== version) { if (semver.gt(version, installedVersion!)) { - return { action: InstallationAction.Upgrade, version: version }; + return { action: InstallAction.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: InstallAction.Skip, version: installedVersion }; } } - return { action: InstallationAction.Skip, version: installedVersion }; + return { action: InstallAction.Skip, version: installedVersion }; } else { - return { action: InstallationAction.Install, version: version }; + return { action: InstallAction.Install, version: version }; } } 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 cc8acdf0543..75c4f0acb52 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 @@ -3,7 +3,7 @@ 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 { InstallAction, npmDependencyType, NpmUtil } from "../../npm-utils.js"; import { getDirectoryPath } from "../../path-utils.js"; import { resolveTypeSpecCli } from "../../tsp-executable-resolver.js"; import { getEntrypointTspFile, TraverseMainTspFileInWorkspace } from "../../typespec-utils.js"; @@ -82,7 +82,7 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin }, async () => { const packageQuickPickItems = []; - if (action === InstallationAction.Upgrade || action === InstallationAction.Install) { + if (action === InstallAction.Upgrade || action === InstallAction.Install) { const minimumRequisites = selectedEmitter.requisites ? ` Minimum requisites: install ${selectedEmitter.requisites?.join(", ")}` : ""; From d5285757c43fbcc2f827a751a8f9a3731cb29132 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Thu, 2 Jan 2025 15:37:50 +0800 Subject: [PATCH 53/66] refine te --- packages/typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 75c4f0acb52..ab97ec8f85e 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 @@ -116,7 +116,7 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin const packageFullName = `${dep.name}@${version ?? "latest"}`; packageQuickPickItems.push({ label: `${dep.name}`, - detail: `Upgrade Dependency ${dep.name}.`, + detail: "Required for enabling the emitter library.", packageFullName: packageFullName, picked: true, }); From cdbcc8cfc997d75cc1082595ac193641f5b5307c Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Fri, 3 Jan 2025 15:45:00 +0800 Subject: [PATCH 54/66] add icon for dark themes --- .../typespec-vscode/icons/client.dark.svg | 12 +++++++++ .../icons/{client.svg => client.light.svg} | 0 .../typespec-vscode/icons/dotnet.dark.svg | 8 ++++++ .../icons/{dotnet.svg => dotnet.light.svg} | 0 .../icons/{java.svg => java.dark.svg} | 0 packages/typespec-vscode/icons/java.light.svg | 3 +++ .../typespec-vscode/icons/javascript.dark.svg | 8 ++++++ .../{javascript.svg => javascript.light.svg} | 0 .../typespec-vscode/icons/openapi.dark.svg | 16 +++++++++++ .../typespec-vscode/icons/openapi.light.svg | 16 +++++++++++ .../icons/{openapi.svg => openapi3.dark.svg} | 0 .../{openapi3.svg => openapi3.light.svg} | 0 .../typespec-vscode/icons/python.dark.svg | 7 +++++ .../icons/{python.svg => python.light.svg} | 0 .../typespec-vscode/icons/server.dark.svg | 27 +++++++++++++++++++ .../icons/{server.svg => server.light.svg} | 0 .../src/vscode-cmd/emit-code/emit-code.ts | 21 +++++++++------ 17 files changed, 110 insertions(+), 8 deletions(-) create mode 100644 packages/typespec-vscode/icons/client.dark.svg rename packages/typespec-vscode/icons/{client.svg => client.light.svg} (100%) create mode 100644 packages/typespec-vscode/icons/dotnet.dark.svg rename packages/typespec-vscode/icons/{dotnet.svg => dotnet.light.svg} (100%) rename packages/typespec-vscode/icons/{java.svg => java.dark.svg} (100%) create mode 100644 packages/typespec-vscode/icons/java.light.svg create mode 100644 packages/typespec-vscode/icons/javascript.dark.svg rename packages/typespec-vscode/icons/{javascript.svg => javascript.light.svg} (100%) create mode 100644 packages/typespec-vscode/icons/openapi.dark.svg create mode 100644 packages/typespec-vscode/icons/openapi.light.svg rename packages/typespec-vscode/icons/{openapi.svg => openapi3.dark.svg} (100%) rename packages/typespec-vscode/icons/{openapi3.svg => openapi3.light.svg} (100%) create mode 100644 packages/typespec-vscode/icons/python.dark.svg rename packages/typespec-vscode/icons/{python.svg => python.light.svg} (100%) create mode 100644 packages/typespec-vscode/icons/server.dark.svg rename packages/typespec-vscode/icons/{server.svg => server.light.svg} (100%) diff --git a/packages/typespec-vscode/icons/client.dark.svg b/packages/typespec-vscode/icons/client.dark.svg new file mode 100644 index 00000000000..18aa6ebd5d4 --- /dev/null +++ b/packages/typespec-vscode/icons/client.dark.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/typespec-vscode/icons/client.svg b/packages/typespec-vscode/icons/client.light.svg similarity index 100% rename from packages/typespec-vscode/icons/client.svg rename to packages/typespec-vscode/icons/client.light.svg diff --git a/packages/typespec-vscode/icons/dotnet.dark.svg b/packages/typespec-vscode/icons/dotnet.dark.svg new file mode 100644 index 00000000000..39be6a20c61 --- /dev/null +++ b/packages/typespec-vscode/icons/dotnet.dark.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/packages/typespec-vscode/icons/dotnet.svg b/packages/typespec-vscode/icons/dotnet.light.svg similarity index 100% rename from packages/typespec-vscode/icons/dotnet.svg rename to packages/typespec-vscode/icons/dotnet.light.svg diff --git a/packages/typespec-vscode/icons/java.svg b/packages/typespec-vscode/icons/java.dark.svg similarity index 100% rename from packages/typespec-vscode/icons/java.svg rename to packages/typespec-vscode/icons/java.dark.svg diff --git a/packages/typespec-vscode/icons/java.light.svg b/packages/typespec-vscode/icons/java.light.svg new file mode 100644 index 00000000000..649dcb26c8f --- /dev/null +++ b/packages/typespec-vscode/icons/java.light.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/typespec-vscode/icons/javascript.dark.svg b/packages/typespec-vscode/icons/javascript.dark.svg new file mode 100644 index 00000000000..978d95f5cd8 --- /dev/null +++ b/packages/typespec-vscode/icons/javascript.dark.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/packages/typespec-vscode/icons/javascript.svg b/packages/typespec-vscode/icons/javascript.light.svg similarity index 100% rename from packages/typespec-vscode/icons/javascript.svg rename to packages/typespec-vscode/icons/javascript.light.svg diff --git a/packages/typespec-vscode/icons/openapi.dark.svg b/packages/typespec-vscode/icons/openapi.dark.svg new file mode 100644 index 00000000000..3f05bbddf68 --- /dev/null +++ b/packages/typespec-vscode/icons/openapi.dark.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/typespec-vscode/icons/openapi.light.svg b/packages/typespec-vscode/icons/openapi.light.svg new file mode 100644 index 00000000000..3f05bbddf68 --- /dev/null +++ b/packages/typespec-vscode/icons/openapi.light.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/typespec-vscode/icons/openapi.svg b/packages/typespec-vscode/icons/openapi3.dark.svg similarity index 100% rename from packages/typespec-vscode/icons/openapi.svg rename to packages/typespec-vscode/icons/openapi3.dark.svg diff --git a/packages/typespec-vscode/icons/openapi3.svg b/packages/typespec-vscode/icons/openapi3.light.svg similarity index 100% rename from packages/typespec-vscode/icons/openapi3.svg rename to packages/typespec-vscode/icons/openapi3.light.svg diff --git a/packages/typespec-vscode/icons/python.dark.svg b/packages/typespec-vscode/icons/python.dark.svg new file mode 100644 index 00000000000..5e792117a73 --- /dev/null +++ b/packages/typespec-vscode/icons/python.dark.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.light.svg similarity index 100% rename from packages/typespec-vscode/icons/python.svg rename to packages/typespec-vscode/icons/python.light.svg diff --git a/packages/typespec-vscode/icons/server.dark.svg b/packages/typespec-vscode/icons/server.dark.svg new file mode 100644 index 00000000000..8289d77d3a3 --- /dev/null +++ b/packages/typespec-vscode/icons/server.dark.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/typespec-vscode/icons/server.svg b/packages/typespec-vscode/icons/server.light.svg similarity index 100% rename from packages/typespec-vscode/icons/server.svg rename to packages/typespec-vscode/icons/server.light.svg 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 ab97ec8f85e..2a277cd1aa3 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 @@ -43,9 +43,14 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin detail: `Generate ${e.kind} code for ${e.language} by TypeSpec library ${e.package}.${moreDetail}`, picked: false, fromConfig: false, - iconPath: Uri.file( - context.asAbsolutePath(`./icons/${getLanguageAlias(e.language).toLowerCase()}.svg`), - ), + iconPath: { + light: Uri.file( + context.asAbsolutePath(`./icons/${getLanguageAlias(e.language).toLowerCase()}.light.svg`), + ), + dark: Uri.file( + context.asAbsolutePath(`./icons/${getLanguageAlias(e.language).toLowerCase()}.dark.svg`), + ), + }, }; }; @@ -147,10 +152,7 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin /* npm install packages. */ if (selectedPackages.length > 0) { const installPackages = selectedPackages.map((p) => p.packageFullName); - logger.info(`Install ${installPackages.join("\n\n")} under directory ${baseDir}`, [], { - showOutput: true, - showPopup: true, - }); + logger.info(`Install ${installPackages.join("\n\n")} under directory ${baseDir}`); const installResult = await vscode.window.withProgress( { location: vscode.ProgressLocation.Notification, @@ -346,7 +348,10 @@ export async function emitCode(context: vscode.ExtensionContext, uri: vscode.Uri PreDefinedEmitterPickItems[kind]?.detail ?? `Generate ${kind} code from TypeSpec files. Supported languages are ${supportedLanguages}.`, emitterKind: kind, - iconPath: Uri.file(context.asAbsolutePath(`./icons/${kind.toLowerCase()}.svg`)), + iconPath: { + light: Uri.file(context.asAbsolutePath(`./icons/${kind.toLowerCase()}.light.svg`)), + dark: Uri.file(context.asAbsolutePath(`./icons/${kind.toLowerCase()}.dark.svg`)), + }, }; }; const codesToEmit = emitterKinds.map((kind) => toEmitterTypeQuickPickItem(kind)); From 3973ad3468b8d65614ecfaf788a10abbdbab57dc Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Tue, 7 Jan 2025 15:22:07 +0800 Subject: [PATCH 55/66] update the title of page --- .../src/vscode-cmd/emit-code/emit-code.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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 2a277cd1aa3..67da3fd1721 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 @@ -58,7 +58,7 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin const all = [...registerEmitters].map((e) => toQuickPickItem(e)); const selectedEmitter = await vscode.window.showQuickPick(all, { - title: `Select a Language for ${kind} code generation`, + title: `Generate from TypeSpec`, canPickMany: false, placeHolder: `Select a Language for ${kind} code generation`, ignoreFocusOut: true, @@ -137,7 +137,7 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin if (installPackageQuickPickItems.length > 0) { const selectedPackages = await vscode.window.showQuickPick(installPackageQuickPickItems, { - title: "Install or update libraries", + title: "Generate from TypeSpec", canPickMany: true, placeHolder: "Here are libraries to install or update.", ignoreFocusOut: true, @@ -310,9 +310,9 @@ export async function emitCode(context: vscode.ExtensionContext, uri: vscode.Uri toProjectPickItem(filePath), ); const selectedProjectFile = await vscode.window.showQuickPick(typespecProjectQuickPickItems, { - title: "Select a Project", + title: "Generate from TypeSpec", canPickMany: false, - placeHolder: "Pick a project", + placeHolder: "Select a project", ignoreFocusOut: true, }); if (!selectedProjectFile) { @@ -356,7 +356,7 @@ export async function emitCode(context: vscode.ExtensionContext, uri: vscode.Uri }; const codesToEmit = emitterKinds.map((kind) => toEmitterTypeQuickPickItem(kind)); const codeType = await vscode.window.showQuickPick(codesToEmit, { - title: "Select an Emitter Type", + title: "Generate from TypeSpec", canPickMany: false, placeHolder: "Select an emitter type", ignoreFocusOut: true, From e066e81bf592d4a291eafe86681bdc613035f1fa Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Wed, 8 Jan 2025 13:30:02 +0800 Subject: [PATCH 56/66] log nice error message --- packages/typespec-vscode/package.json | 2 +- .../src/vscode-cmd/emit-code/emit-code.ts | 56 +++++++++++++------ 2 files changed, 39 insertions(+), 19 deletions(-) diff --git a/packages/typespec-vscode/package.json b/packages/typespec-vscode/package.json index 7074bddd114..2601aa13a03 100644 --- a/packages/typespec-vscode/package.json +++ b/packages/typespec-vscode/package.json @@ -168,7 +168,7 @@ { "language": "Java", "package": "@typespec/http-client-java", - "sourceRepo": "https://github.com/microsoft/typespec/tree/main/packages/http-client-java ", + "sourceRepo": "https://github.com/microsoft/typespec/tree/main/packages/http-client-java", "requisites": [ "Java 17 or above", "Maven" 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 67da3fd1721..76f3d0add1d 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 @@ -152,7 +152,7 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin /* npm install packages. */ if (selectedPackages.length > 0) { const installPackages = selectedPackages.map((p) => p.packageFullName); - logger.info(`Install ${installPackages.join("\n\n")} under directory ${baseDir}`); + logger.info(`Install ${installPackages.join(",")} under directory ${baseDir}`); const installResult = await vscode.window.withProgress( { location: vscode.ProgressLocation.Notification, @@ -163,23 +163,35 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin try { const npmInstallResult = await npmUtil.npmInstallPackages(installPackages, undefined); if (npmInstallResult.exitCode !== 0) { - logger.error( - `Error occurred when installing packages.`, - [`${npmInstallResult.stderr}`], - { - showOutput: true, - showPopup: true, - }, - ); + const details = []; + if (npmInstallResult.stdout !== "") details.push(npmInstallResult.stdout); + if (npmInstallResult.stderr !== "") details.push(npmInstallResult.stderr); + if (npmInstallResult.error) details.push(npmInstallResult.error); + logger.error(`Error occurred when installing packages.`, [details], { + showOutput: true, + showPopup: true, + }); return false; } else { return true; } - } catch (err) { - logger.error(`Exception occurred when installing packages.`, [err], { - showOutput: true, - showPopup: true, - }); + } catch (err: any) { + if (typeof err === "object" && "stdout" in err && "stderr" in err && `error` in err) { + const execOutput = err as ExecOutput; + const details = []; + if (execOutput.stdout !== "") details.push(execOutput.stdout); + if (execOutput.stderr !== "") details.push(execOutput.stderr); + if (execOutput.error) details.push(execOutput.error); + logger.error(`Exception occurred when installing packages.`, details, { + showOutput: true, + showPopup: true, + }); + } else { + logger.error(`Exception occurred when installing packages.`, [err], { + showOutput: true, + showPopup: true, + }); + } return false; } }, @@ -234,18 +246,26 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin false, ); if (compileResult.exitCode !== 0) { + const details = []; + if (compileResult.stdout !== "") details.push(compileResult.stdout); + if (compileResult.stderr !== "") details.push(compileResult.stderr); + if (compileResult.error) details.push(compileResult.error); logger.error( `Generating ${selectedEmitter.emitterKind} code for ${selectedEmitter.language}...Failed`, - [], + [details], { showOutput: true, showPopup: true, }, ); } else { + const details = []; + if (compileResult.stdout !== "") details.push(compileResult.stdout); + if (compileResult.stderr !== "") details.push(compileResult.stderr); + if (compileResult.error) details.push(compileResult.error); logger.info( `Generating ${selectedEmitter.emitterKind} code for ${selectedEmitter.language}...Succeeded`, - [], + [details], { showOutput: true, showPopup: true, @@ -260,7 +280,7 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin if (execOutput.stderr !== "") details.push(execOutput.stderr); if (execOutput.error) details.push(execOutput.error); logger.error( - `Exception occurred when generating ${selectedEmitter.emitterKind} code for ${selectedEmitter.language}.`, + `Generating ${selectedEmitter.emitterKind} code for ${selectedEmitter.language}...Failed.`, details, { showOutput: true, @@ -269,7 +289,7 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin ); } else { logger.error( - `Exception occurred when generating ${selectedEmitter.emitterKind} code for ${selectedEmitter.language}.`, + `Generating ${selectedEmitter.emitterKind} code for ${selectedEmitter.language}...Failed.`, [err], { showOutput: true, From 303bf9d5ac30a7078bff237ae7882f6022d8f213 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Wed, 8 Jan 2025 13:35:29 +0800 Subject: [PATCH 57/66] remove duplicate error log --- .../src/vscode-cmd/emit-code/emit-code.ts | 24 ------------------- 1 file changed, 24 deletions(-) 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 76f3d0add1d..1debf371561 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 @@ -163,35 +163,11 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin try { const npmInstallResult = await npmUtil.npmInstallPackages(installPackages, undefined); if (npmInstallResult.exitCode !== 0) { - const details = []; - if (npmInstallResult.stdout !== "") details.push(npmInstallResult.stdout); - if (npmInstallResult.stderr !== "") details.push(npmInstallResult.stderr); - if (npmInstallResult.error) details.push(npmInstallResult.error); - logger.error(`Error occurred when installing packages.`, [details], { - showOutput: true, - showPopup: true, - }); return false; } else { return true; } } catch (err: any) { - if (typeof err === "object" && "stdout" in err && "stderr" in err && `error` in err) { - const execOutput = err as ExecOutput; - const details = []; - if (execOutput.stdout !== "") details.push(execOutput.stdout); - if (execOutput.stderr !== "") details.push(execOutput.stderr); - if (execOutput.error) details.push(execOutput.error); - logger.error(`Exception occurred when installing packages.`, details, { - showOutput: true, - showPopup: true, - }); - } else { - logger.error(`Exception occurred when installing packages.`, [err], { - showOutput: true, - showPopup: true, - }); - } return false; } }, From 168d11674fed660eec4974b8257a424ed0ff9ed2 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Wed, 8 Jan 2025 15:36:20 +0800 Subject: [PATCH 58/66] add more-detail button in each quick pick item --- .../src/vscode-cmd/emit-code/emit-code.ts | 37 ++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) 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 1debf371561..cfabed37ecc 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 @@ -31,7 +31,6 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin const baseDir = getDirectoryPath(mainTspFile); const toQuickPickItem = (e: Emitter): EmitQuickPickItem => { - const moreDetail = e.sourceRepo ? `[**More Detail**](${e.sourceRepo})` : ""; return { language: e.language, package: e.package, @@ -40,9 +39,15 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin sourceRepo: e.sourceRepo, emitterKind: e.kind, label: e.language, - detail: `Generate ${e.kind} code for ${e.language} by TypeSpec library ${e.package}.${moreDetail}`, + detail: `Generate ${e.kind} code for ${e.language} by TypeSpec library ${e.package}.`, picked: false, fromConfig: false, + buttons: [ + { + iconPath: new vscode.ThemeIcon("link-external"), + tooltip: "More details", + }, + ], iconPath: { light: Uri.file( context.asAbsolutePath(`./icons/${getLanguageAlias(e.language).toLowerCase()}.light.svg`), @@ -55,13 +60,27 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin }; const registerEmitters = getRegisterEmitters(kind); - const all = [...registerEmitters].map((e) => toQuickPickItem(e)); + const all: EmitQuickPickItem[] = [...registerEmitters].map((e) => toQuickPickItem(e)); - const selectedEmitter = await vscode.window.showQuickPick(all, { - title: `Generate from TypeSpec`, - canPickMany: false, - placeHolder: `Select a Language for ${kind} code generation`, - ignoreFocusOut: true, + const emitterSelector = vscode.window.createQuickPick(); + emitterSelector.items = all; + emitterSelector.title = `Generate from TypeSpec`; + emitterSelector.canSelectMany = false; + emitterSelector.placeholder = `Select a Language for ${kind} code generation`; + emitterSelector.ignoreFocusOut = true; + emitterSelector.onDidTriggerItemButton(async (e) => { + if (e.button.tooltip === "More details") { + const url = e.item.sourceRepo?.trim() ?? ""; + await vscode.env.openExternal(vscode.Uri.parse(url)); + } + }); + emitterSelector.show(); + const selectedEmitter = await new Promise((resolve) => { + emitterSelector.onDidAccept(() => { + resolve(emitterSelector.selectedItems[0]); + // emitterSelector.hide(); + emitterSelector.dispose(); + }); }); if (!selectedEmitter) { @@ -92,7 +111,7 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin ? ` Minimum requisites: install ${selectedEmitter.requisites?.join(", ")}` : ""; const moreDetail = selectedEmitter.sourceRepo - ? `[**More Detail**](${selectedEmitter.sourceRepo})` + ? `[**More details**](${selectedEmitter.sourceRepo.trim()})` : ""; let packageFullName = selectedEmitter.package; if (version) { From ab13f86c2bf107148482ab2430efd95809af3235 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Wed, 8 Jan 2025 17:09:29 +0800 Subject: [PATCH 59/66] add button for more-details in install packages pick item --- .../src/vscode-cmd/emit-code/emit-code.ts | 40 ++++++++++++++----- 1 file changed, 29 insertions(+), 11 deletions(-) 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 cfabed37ecc..4310d04baca 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 @@ -78,7 +78,6 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin const selectedEmitter = await new Promise((resolve) => { emitterSelector.onDidAccept(() => { resolve(emitterSelector.selectedItems[0]); - // emitterSelector.hide(); emitterSelector.dispose(); }); }); @@ -110,18 +109,23 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin const minimumRequisites = selectedEmitter.requisites ? ` Minimum requisites: install ${selectedEmitter.requisites?.join(", ")}` : ""; - const moreDetail = selectedEmitter.sourceRepo - ? `[**More details**](${selectedEmitter.sourceRepo.trim()})` - : ""; let packageFullName = selectedEmitter.package; if (version) { packageFullName = `${selectedEmitter.package}@${version}`; } packageQuickPickItems.push({ label: `${selectedEmitter.package}`, - detail: `TypeSpec library for emitting ${selectedEmitter.language} from TypeSpec files.${minimumRequisites} ${moreDetail}`, + description: `TypeSpec library for emitting ${selectedEmitter.language} from TypeSpec files.`, + detail: minimumRequisites, packageFullName: packageFullName, + sourceRepo: selectedEmitter.sourceRepo, picked: true, + buttons: [ + { + iconPath: new vscode.ThemeIcon("link-external"), + tooltip: "More details", + }, + ], }); packagesToInstall.push(packageFullName); } @@ -140,7 +144,7 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin const packageFullName = `${dep.name}@${version ?? "latest"}`; packageQuickPickItems.push({ label: `${dep.name}`, - detail: "Required for enabling the emitter library.", + description: "Required for enabling the emitter library.", packageFullName: packageFullName, picked: true, }); @@ -155,11 +159,25 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin ); if (installPackageQuickPickItems.length > 0) { - const selectedPackages = await vscode.window.showQuickPick(installPackageQuickPickItems, { - title: "Generate from TypeSpec", - canPickMany: true, - placeHolder: "Here are libraries to install or update.", - ignoreFocusOut: true, + const installPackagesSelector = vscode.window.createQuickPick(); + installPackagesSelector.items = installPackageQuickPickItems; + installPackagesSelector.title = `Generate from TypeSpec`; + installPackagesSelector.canSelectMany = true; + installPackagesSelector.placeholder = "Here are libraries to install or update."; + installPackagesSelector.ignoreFocusOut = true; + installPackagesSelector.selectedItems = [...installPackageQuickPickItems]; + installPackagesSelector.onDidTriggerItemButton(async (e) => { + if (e.button.tooltip === "More details") { + const url = e.item.sourceRepo?.trim() ?? ""; + await vscode.env.openExternal(vscode.Uri.parse(url)); + } + }); + installPackagesSelector.show(); + const selectedPackages = await new Promise((resolve) => { + installPackagesSelector.onDidAccept(() => { + resolve(installPackagesSelector.selectedItems); + installPackagesSelector.dispose(); + }); }); if (!selectedPackages || selectedPackages.length === 0) { logger.info("No package selected. Generating Cancelled.", [], { From dd365b40fe286f10ea2a17819f5b70ca1f6a8286 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Wed, 8 Jan 2025 17:49:36 +0800 Subject: [PATCH 60/66] only add more-details button when it has srcRepo defined --- .../src/vscode-cmd/emit-code/emit-code.ts | 40 ++++++++++++------- 1 file changed, 25 insertions(+), 15 deletions(-) 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 4310d04baca..0f06bbd5593 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 @@ -1,5 +1,5 @@ import path from "path"; -import vscode, { Uri } from "vscode"; +import vscode, { QuickInputButton, Uri } from "vscode"; import { Executable } from "vscode-languageclient/node.js"; import { StartFileName } from "../../const.js"; import logger from "../../log/logger.js"; @@ -30,7 +30,19 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin const baseDir = getDirectoryPath(mainTspFile); + interface EmitQuickPickButton extends QuickInputButton { + uri: string; + } const toQuickPickItem = (e: Emitter): EmitQuickPickItem => { + const buttons = e.sourceRepo + ? [ + { + iconPath: new vscode.ThemeIcon("link-external"), + tooltip: "More details", + uri: e.sourceRepo, + }, + ] + : undefined; return { language: e.language, package: e.package, @@ -42,12 +54,7 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin detail: `Generate ${e.kind} code for ${e.language} by TypeSpec library ${e.package}.`, picked: false, fromConfig: false, - buttons: [ - { - iconPath: new vscode.ThemeIcon("link-external"), - tooltip: "More details", - }, - ], + buttons: buttons, iconPath: { light: Uri.file( context.asAbsolutePath(`./icons/${getLanguageAlias(e.language).toLowerCase()}.light.svg`), @@ -70,7 +77,7 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin emitterSelector.ignoreFocusOut = true; emitterSelector.onDidTriggerItemButton(async (e) => { if (e.button.tooltip === "More details") { - const url = e.item.sourceRepo?.trim() ?? ""; + const url = (e.button as EmitQuickPickButton).uri; await vscode.env.openExternal(vscode.Uri.parse(url)); } }); @@ -120,12 +127,15 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin packageFullName: packageFullName, sourceRepo: selectedEmitter.sourceRepo, picked: true, - buttons: [ - { - iconPath: new vscode.ThemeIcon("link-external"), - tooltip: "More details", - }, - ], + buttons: selectedEmitter.sourceRepo + ? [ + { + iconPath: new vscode.ThemeIcon("link-external"), + tooltip: "More details", + uri: selectedEmitter.sourceRepo, + }, + ] + : undefined, }); packagesToInstall.push(packageFullName); } @@ -168,7 +178,7 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin installPackagesSelector.selectedItems = [...installPackageQuickPickItems]; installPackagesSelector.onDidTriggerItemButton(async (e) => { if (e.button.tooltip === "More details") { - const url = e.item.sourceRepo?.trim() ?? ""; + const url = (e.button as EmitQuickPickButton).uri; await vscode.env.openExternal(vscode.Uri.parse(url)); } }); From 01c693f90643830d1fa535ff8a963f43f78d6ec7 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Fri, 10 Jan 2025 00:00:36 +0800 Subject: [PATCH 61/66] read and update tspconfig.yam --- .../src/generate-third-party-notice.ts | 20 +++-- .../typespec-vscode/ThirdPartyNotices.txt | 31 ++++++- packages/typespec-vscode/package.json | 3 +- packages/typespec-vscode/src/const.ts | 1 + .../src/tsp-language-client.ts | 5 +- .../src/vscode-cmd/emit-code/emit-code.ts | 87 +++++++++++++++---- pnpm-lock.yaml | 3 + 7 files changed, 122 insertions(+), 28 deletions(-) diff --git a/packages/internal-build-utils/src/generate-third-party-notice.ts b/packages/internal-build-utils/src/generate-third-party-notice.ts index 68ecb9123b8..4e22acc3988 100644 --- a/packages/internal-build-utils/src/generate-third-party-notice.ts +++ b/packages/internal-build-utils/src/generate-third-party-notice.ts @@ -52,17 +52,23 @@ async function findThirdPartyPackages() { const sources = contents.sources; for (const source of sources) { const sourcePath = resolve(dirname(map), source); - const packageRoot = await getPackageRoot(sourcePath); + let packageRoot = await getPackageRoot(sourcePath); if (packageRoot === undefined) { continue; } - const pkg = JSON.parse(await readFile(join(packageRoot, "package.json"), "utf-8")); - - if (pkg.name === rootName || /microsoft/i.test(JSON.stringify(pkg.author))) { - continue; - } - if (!packages.has(packageRoot)) { + let pkg = JSON.parse(await readFile(join(packageRoot, "package.json"), "utf-8")); + + if (!pkg.name) { + packageRoot = await getPackageRoot(packageRoot); + while (!pkg.name && packageRoot) { + pkg = JSON.parse(await readFile(join(packageRoot, "package.json"), "utf-8")); + packageRoot = await getPackageRoot(packageRoot); + } + } + if (!pkg.name || pkg.name === rootName || /microsoft/i.test(JSON.stringify(pkg.author))) { + continue; + } packages.set(packageRoot, pkg); } } diff --git a/packages/typespec-vscode/ThirdPartyNotices.txt b/packages/typespec-vscode/ThirdPartyNotices.txt index 681acb4e65c..301be2d7f76 100644 --- a/packages/typespec-vscode/ThirdPartyNotices.txt +++ b/packages/typespec-vscode/ThirdPartyNotices.txt @@ -12,6 +12,7 @@ granted herein, whether by implication, estoppel or otherwise. 2. brace-expansion version 2.0.1 (https://github.com/juliangruber/brace-expansion) 3. minimatch version 5.1.6 (https://github.com/isaacs/minimatch) 4. semver version 7.6.3 (https://github.com/npm/node-semver) +5. yaml version 2.5.1 (github:eemeli/yaml) %% balanced-match NOTICES AND INFORMATION BEGIN HERE @@ -111,4 +112,32 @@ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ====================================================="); -END OF semver NOTICES AND INFORMATION \ No newline at end of file +END OF semver NOTICES AND INFORMATION + + +%% yaml NOTICES AND INFORMATION BEGIN HERE +===================================================== + MIT License + + Copyright (c) Microsoft Corporation. All rights reserved. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + +====================================================="); +END OF yaml NOTICES AND INFORMATION \ No newline at end of file diff --git a/packages/typespec-vscode/package.json b/packages/typespec-vscode/package.json index 2601aa13a03..ba57f58019e 100644 --- a/packages/typespec-vscode/package.json +++ b/packages/typespec-vscode/package.json @@ -348,6 +348,7 @@ "typescript": "~5.6.3", "vitest": "^2.1.5", "vscode-languageclient": "~9.0.1", - "semver": "^7.6.3" + "semver": "^7.6.3", + "yaml": "~2.5.1" } } diff --git a/packages/typespec-vscode/src/const.ts b/packages/typespec-vscode/src/const.ts index e7b898f945c..8a5d3e4b8f8 100644 --- a/packages/typespec-vscode/src/const.ts +++ b/packages/typespec-vscode/src/const.ts @@ -1 +1,2 @@ export const StartFileName = "main.tsp"; +export const TspConfigFileName = "tspconfig.yaml"; diff --git a/packages/typespec-vscode/src/tsp-language-client.ts b/packages/typespec-vscode/src/tsp-language-client.ts index 43b24433fb6..e72b263a8d3 100644 --- a/packages/typespec-vscode/src/tsp-language-client.ts +++ b/packages/typespec-vscode/src/tsp-language-client.ts @@ -7,6 +7,7 @@ import type { } from "@typespec/compiler"; import { ExtensionContext, LogOutputChannel, RelativePattern, workspace } from "vscode"; import { Executable, LanguageClient, LanguageClientOptions } from "vscode-languageclient/node.js"; +import { TspConfigFileName } from "./const.js"; import logger from "./log/logger.js"; import { resolveTypeSpecServer } from "./tsp-executable-resolver.js"; import { @@ -191,7 +192,7 @@ export class TspLanguageClient { workspace.createFileSystemWatcher("**/*.cadl"), workspace.createFileSystemWatcher("**/cadl-project.yaml"), workspace.createFileSystemWatcher("**/*.tsp"), - workspace.createFileSystemWatcher("**/tspconfig.yaml"), + workspace.createFileSystemWatcher(`**/${TspConfigFileName}`), // please be aware that the vscode watch with '**' will honer the files.watcherExclude settings // so we won't get notification for those package.json under node_modules // if our customers exclude the node_modules folder in files.watcherExclude settings. @@ -213,7 +214,7 @@ export class TspLanguageClient { documentSelector: [ { scheme: "file", language: "typespec" }, { scheme: "untitled", language: "typespec" }, - { scheme: "file", language: "yaml", pattern: "**/tspconfig.yaml" }, + { scheme: "file", language: "yaml", pattern: `**/${TspConfigFileName}` }, ], outputChannel, }; 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 0f06bbd5593..d0e9f08fb82 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 @@ -1,7 +1,9 @@ +import { readFile, writeFile } from "fs/promises"; import path from "path"; import vscode, { QuickInputButton, Uri } from "vscode"; import { Executable } from "vscode-languageclient/node.js"; -import { StartFileName } from "../../const.js"; +import { isScalar, isSeq, parseDocument } from "yaml"; +import { StartFileName, TspConfigFileName } from "../../const.js"; import logger from "../../log/logger.js"; import { InstallAction, npmDependencyType, NpmUtil } from "../../npm-utils.js"; import { getDirectoryPath } from "../../path-utils.js"; @@ -242,14 +244,73 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin ); return; } - const outputDir = path.join( - baseDir, - selectedEmitter.emitterKind, - getLanguageAlias(selectedEmitter.language), - ); + /*Config emitter output dir and emit in tspconfig.yaml. */ + const defaultEmitOutputDirInConfig = `{project-root}/${selectedEmitter.emitterKind}/${getLanguageAlias(selectedEmitter.language)}`; + const tspConfigFile = path.join(baseDir, TspConfigFileName); + let configYaml = parseDocument(""); //generate a empty yaml + if (await isFile(tspConfigFile)) { + const content = await readFile(tspConfigFile); + configYaml = parseDocument(content.toString()); + } + let outputDir = defaultEmitOutputDirInConfig; + try { + /*update emitter in config.yaml. */ + const emitNode = configYaml.get("emit"); + if (emitNode) { + if (isSeq(emitNode)) { + if (Array.isArray(emitNode.items)) { + const index = emitNode.items.findIndex((item) => { + if (isScalar(item)) { + return item.value === selectedEmitter.package; + } + return false; + }); + if (index === -1) { + emitNode.items.push(selectedEmitter.package); + } + } + } + } else { + configYaml.set("emit", [selectedEmitter.package]); + } + const optionsObject = configYaml.get("options"); + if (!optionsObject) { + configYaml.set("options", { + [selectedEmitter.package]: { + "emitter-output-dir": defaultEmitOutputDirInConfig, + }, + }); + } else { + const emitterOptions = configYaml.getIn(["options", selectedEmitter.package]); + if (!emitterOptions) { + const optionRecord = optionsObject as Record; + optionRecord[selectedEmitter.package] = { + "emitter-output-dir": defaultEmitOutputDirInConfig, + }; + } else { + const emitOutputDir = configYaml.getIn([ + "options", + selectedEmitter.package, + "emitter-output-dir", + ]); + if (emitOutputDir) { + outputDir = emitOutputDir as string; + } else { + configYaml.setIn( + ["options", selectedEmitter.package, "emitter-output-dir"], + defaultEmitOutputDirInConfig, + ); + } + } + } + const newYamlContent = configYaml.toString(); + await writeFile(tspConfigFile, newYamlContent); + } catch (error: any) { + logger.error(error); + } + outputDir = outputDir.replace("{project-root}", baseDir); const options: Record = {}; - options["emitter-output-dir"] = outputDir; logger.info( `Start to generate ${selectedEmitter.language} ${selectedEmitter.emitterKind} code under directory ${outputDir}`, ); @@ -269,26 +330,18 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin false, ); if (compileResult.exitCode !== 0) { - const details = []; - if (compileResult.stdout !== "") details.push(compileResult.stdout); - if (compileResult.stderr !== "") details.push(compileResult.stderr); - if (compileResult.error) details.push(compileResult.error); logger.error( `Generating ${selectedEmitter.emitterKind} code for ${selectedEmitter.language}...Failed`, - [details], + [], { showOutput: true, showPopup: true, }, ); } else { - const details = []; - if (compileResult.stdout !== "") details.push(compileResult.stdout); - if (compileResult.stderr !== "") details.push(compileResult.stderr); - if (compileResult.error) details.push(compileResult.error); logger.info( `Generating ${selectedEmitter.emitterKind} code for ${selectedEmitter.language}...Succeeded`, - [details], + [], { showOutput: true, showPopup: true, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4d84583b8d0..19af31207bb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1930,6 +1930,9 @@ importers: vscode-languageclient: specifier: ~9.0.1 version: 9.0.1 + yaml: + specifier: ~2.5.1 + version: 2.5.1 packages/versioning: devDependencies: From 69fe0bde7af9f046cb90fa95d2c70f25dfbdc073 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Fri, 10 Jan 2025 00:07:13 +0800 Subject: [PATCH 62/66] add changelog --- .chronus/changes/generateSDK-2025-0-10-0-6-24.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .chronus/changes/generateSDK-2025-0-10-0-6-24.md diff --git a/.chronus/changes/generateSDK-2025-0-10-0-6-24.md b/.chronus/changes/generateSDK-2025-0-10-0-6-24.md new file mode 100644 index 00000000000..3f6bd062e80 --- /dev/null +++ b/.chronus/changes/generateSDK-2025-0-10-0-6-24.md @@ -0,0 +1,7 @@ +--- +changeKind: fix +packages: + - "@typespec/internal-build-utils" +--- + +resolve the program crash when there is no package name in package.json \ No newline at end of file From 9bcc6fc7bf4f361f9c6ab9cb365739f1e954891f Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Fri, 10 Jan 2025 00:26:42 +0800 Subject: [PATCH 63/66] simplify the config yaml update logic --- .../src/vscode-cmd/emit-code/emit-code.ts | 39 ++++++------------- 1 file changed, 11 insertions(+), 28 deletions(-) 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 d0e9f08fb82..c871343578a 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 @@ -273,35 +273,18 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin } else { configYaml.set("emit", [selectedEmitter.package]); } - const optionsObject = configYaml.get("options"); - if (!optionsObject) { - configYaml.set("options", { - [selectedEmitter.package]: { - "emitter-output-dir": defaultEmitOutputDirInConfig, - }, - }); + const emitOutputDir = configYaml.getIn([ + "options", + selectedEmitter.package, + "emitter-output-dir", + ]); + if (!emitOutputDir) { + configYaml.setIn( + ["options", selectedEmitter.package, "emitter-output-dir"], + defaultEmitOutputDirInConfig, + ); } else { - const emitterOptions = configYaml.getIn(["options", selectedEmitter.package]); - if (!emitterOptions) { - const optionRecord = optionsObject as Record; - optionRecord[selectedEmitter.package] = { - "emitter-output-dir": defaultEmitOutputDirInConfig, - }; - } else { - const emitOutputDir = configYaml.getIn([ - "options", - selectedEmitter.package, - "emitter-output-dir", - ]); - if (emitOutputDir) { - outputDir = emitOutputDir as string; - } else { - configYaml.setIn( - ["options", selectedEmitter.package, "emitter-output-dir"], - defaultEmitOutputDirInConfig, - ); - } - } + outputDir = emitOutputDir as string; } const newYamlContent = configYaml.toString(); await writeFile(tspConfigFile, newYamlContent); From ae8a70d365e7aba5f0849688b386682211562ad1 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Mon, 13 Jan 2025 13:35:52 +0800 Subject: [PATCH 64/66] update npnm-lock.yaml --- pnpm-lock.yaml | 148 +++++++++++++++++++++++++++---------------------- 1 file changed, 82 insertions(+), 66 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 19af31207bb..afe94daeef2 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 @@ -117,7 +117,7 @@ importers: version: 0.9.4(prettier-plugin-astro@0.14.1)(prettier@3.3.3)(typescript@5.6.3) '@astrojs/starlight': specifier: ^0.29.0 - version: 0.29.2(astro@4.16.13(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3)) + version: 0.29.2(astro@4.16.18(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3)) '@expressive-code/core': specifier: ^0.38.3 version: 0.38.3 @@ -126,7 +126,7 @@ importers: version: link:../playground astro-expressive-code: specifier: ^0.38.3 - version: 0.38.3(astro@4.16.13(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3)) + version: 0.38.3(astro@4.16.18(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3)) react: specifier: ~18.3.1 version: 18.3.1 @@ -138,8 +138,8 @@ importers: specifier: ~18.3.11 version: 18.3.12 astro: - specifier: ^4.16.10 - version: 4.16.13(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3) + specifier: ^4.16.18 + version: 4.16.18(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3) packages/best-practices: devDependencies: @@ -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) @@ -630,12 +630,21 @@ importers: '@typespec/http': specifier: workspace:~ version: link:../http + '@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)) + '@vitest/ui': + specifier: ^2.1.2 + version: 2.1.5(vitest@2.1.5) tsx: specifier: ^4.19.2 version: 4.19.2 typescript: specifier: ~5.6.3 version: 5.6.3 + vitest: + specifier: ^2.1.5 + version: 2.1.5(@types/node@22.7.9)(@vitest/ui@2.1.5)(happy-dom@15.11.6)(jsdom@19.0.0) packages/http-specs: dependencies: @@ -712,7 +721,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 +764,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 +797,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 +825,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 +867,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 +928,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 +1170,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 +1246,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 +1307,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 +1352,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 +1422,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 +1516,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 +1738,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 +1771,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 +1839,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) @@ -1896,7 +1905,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) @@ -1950,7 +1959,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) @@ -1983,7 +1992,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) @@ -2010,13 +2019,13 @@ importers: version: 3.6.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@5.4.11(@types/node@22.7.9)) '@astrojs/starlight': specifier: ^0.29.0 - version: 0.29.2(astro@4.16.13(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3)) + version: 0.29.2(astro@4.16.18(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3)) '@astrojs/starlight-tailwind': specifier: ^2.0.3 - version: 2.0.3(@astrojs/starlight@0.29.2(astro@4.16.13(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3)))(@astrojs/tailwind@5.1.2(astro@4.16.13(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3))(tailwindcss@3.4.15))(tailwindcss@3.4.15) + version: 2.0.3(@astrojs/starlight@0.29.2(astro@4.16.18(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3)))(@astrojs/tailwind@5.1.2(astro@4.16.18(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3))(tailwindcss@3.4.15))(tailwindcss@3.4.15) '@astrojs/tailwind': specifier: ^5.1.2 - version: 5.1.2(astro@4.16.13(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3))(tailwindcss@3.4.15) + version: 5.1.2(astro@4.16.18(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3))(tailwindcss@3.4.15) '@docsearch/css': specifier: ^3.7.0 version: 3.8.0 @@ -2039,11 +2048,11 @@ importers: specifier: workspace:~ version: link:../packages/playground astro: - specifier: ^4.16.10 - version: 4.16.13(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3) + specifier: ^4.16.18 + version: 4.16.18(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3) astro-rehype-relative-markdown-links: specifier: ^0.15.0 - version: 0.15.0(astro@4.16.13(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3)) + version: 0.15.0(astro@4.16.18(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3)) clsx: specifier: ^2.1.1 version: 2.1.1 @@ -2131,7 +2140,7 @@ importers: version: link:../packages/xml astro-expressive-code: specifier: ^0.38.3 - version: 0.38.3(astro@4.16.13(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3)) + version: 0.38.3(astro@4.16.18(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3)) rehype-mermaid: specifier: ^3.0.0 version: 3.0.0(playwright@1.49.0) @@ -6120,8 +6129,8 @@ packages: peerDependencies: astro: '>=2.x <5' - astro@4.16.13: - resolution: {integrity: sha512-Mtd76+BC0zLWqoXpf9xc731AhdH4MNh5JFHYdLRvSH0Nqn48hA64dPGh/cWsJvh/DZFmC0NTZusM1Qq2gyNaVg==} + astro@4.16.18: + resolution: {integrity: sha512-G7zfwJt9BDHEZwlaLNvjbInIw2hPryyD654314KV/XT34pJU6SfN1S+mWa8RAkALcZNJnJXCJmT3JXLQStD3Lw==} engines: {node: ^18.17.1 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0'} hasBin: true @@ -8780,6 +8789,9 @@ packages: magic-string@0.30.13: resolution: {integrity: sha512-8rYBO+MsWkgjDSOvLomYnzhdwEG51olQ4zL5KXnNJWV5MNmrb4rTZdrtkhxjnD/QyZUqR/Z/XDsUs/4ej2nx0g==} + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + magicast@0.3.5: resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} @@ -11206,10 +11218,10 @@ packages: terser: optional: true - vitefu@1.0.3: - resolution: {integrity: sha512-iKKfOMBHob2WxEJbqbJjHAkmYgvFDPhuqrO82om83S8RLk+17FtyMBfcyeH8GqD0ihShtkMW/zzJgiA51hCNCQ==} + vitefu@1.0.5: + resolution: {integrity: sha512-h4Vflt9gxODPFNGPwp4zAMZRpZR7eslzwH2c5hn5kNZ5rhnKyRJ50U+yGCdc2IRaBs8O4haIgLNGrV5CrpMsCA==} peerDependencies: - vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0-beta.0 + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 peerDependenciesMeta: vite: optional: true @@ -11825,12 +11837,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@astrojs/mdx@3.1.9(astro@4.16.13(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3))': + '@astrojs/mdx@3.1.9(astro@4.16.18(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3))': dependencies: '@astrojs/markdown-remark': 5.3.0 '@mdx-js/mdx': 3.1.0(acorn@8.14.0) acorn: 8.14.0 - astro: 4.16.13(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3) + astro: 4.16.18(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3) es-module-lexer: 1.5.4 estree-util-visit: 2.0.0 gray-matter: 4.0.3 @@ -11867,21 +11879,21 @@ snapshots: stream-replace-string: 2.0.0 zod: 3.23.8 - '@astrojs/starlight-tailwind@2.0.3(@astrojs/starlight@0.29.2(astro@4.16.13(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3)))(@astrojs/tailwind@5.1.2(astro@4.16.13(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3))(tailwindcss@3.4.15))(tailwindcss@3.4.15)': + '@astrojs/starlight-tailwind@2.0.3(@astrojs/starlight@0.29.2(astro@4.16.18(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3)))(@astrojs/tailwind@5.1.2(astro@4.16.18(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3))(tailwindcss@3.4.15))(tailwindcss@3.4.15)': dependencies: - '@astrojs/starlight': 0.29.2(astro@4.16.13(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3)) - '@astrojs/tailwind': 5.1.2(astro@4.16.13(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3))(tailwindcss@3.4.15) + '@astrojs/starlight': 0.29.2(astro@4.16.18(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3)) + '@astrojs/tailwind': 5.1.2(astro@4.16.18(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3))(tailwindcss@3.4.15) tailwindcss: 3.4.15 - '@astrojs/starlight@0.29.2(astro@4.16.13(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3))': + '@astrojs/starlight@0.29.2(astro@4.16.18(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3))': dependencies: - '@astrojs/mdx': 3.1.9(astro@4.16.13(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3)) + '@astrojs/mdx': 3.1.9(astro@4.16.18(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3)) '@astrojs/sitemap': 3.2.1 '@pagefind/default-ui': 1.2.0 '@types/hast': 3.0.4 '@types/mdast': 4.0.4 - astro: 4.16.13(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3) - astro-expressive-code: 0.38.3(astro@4.16.13(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3)) + astro: 4.16.18(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3) + astro-expressive-code: 0.38.3(astro@4.16.18(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3)) bcp-47: 2.1.0 hast-util-from-html: 2.0.3 hast-util-select: 6.0.3 @@ -11902,9 +11914,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@astrojs/tailwind@5.1.2(astro@4.16.13(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3))(tailwindcss@3.4.15)': + '@astrojs/tailwind@5.1.2(astro@4.16.18(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3))(tailwindcss@3.4.15)': dependencies: - astro: 4.16.13(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3) + astro: 4.16.18(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3) autoprefixer: 10.4.20(postcss@8.4.49) postcss: 8.4.49 postcss-load-config: 4.0.2(postcss@8.4.49) @@ -16380,7 +16392,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 @@ -16889,14 +16901,14 @@ snapshots: astring@1.9.0: {} - astro-expressive-code@0.38.3(astro@4.16.13(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3)): + astro-expressive-code@0.38.3(astro@4.16.18(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3)): dependencies: - astro: 4.16.13(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3) + astro: 4.16.18(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3) rehype-expressive-code: 0.38.3 - astro-rehype-relative-markdown-links@0.15.0(astro@4.16.13(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3)): + astro-rehype-relative-markdown-links@0.15.0(astro@4.16.18(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3)): dependencies: - astro: 4.16.13(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3) + astro: 4.16.18(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3) catch-unknown: 2.0.0 debug: 4.3.7(supports-color@8.1.1) github-slugger: 2.0.0 @@ -16908,7 +16920,7 @@ snapshots: transitivePeerDependencies: - supports-color - astro@4.16.13(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3): + astro@4.16.18(@types/node@22.7.9)(rollup@4.24.4)(typescript@5.6.3): dependencies: '@astrojs/compiler': 2.10.3 '@astrojs/internal-helpers': 0.4.1 @@ -16947,7 +16959,7 @@ snapshots: http-cache-semantics: 4.1.1 js-yaml: 4.1.0 kleur: 4.1.5 - magic-string: 0.30.13 + magic-string: 0.30.17 magicast: 0.3.5 micromatch: 4.0.8 mrmime: 2.0.0 @@ -16965,7 +16977,7 @@ snapshots: unist-util-visit: 5.0.0 vfile: 6.0.3 vite: 5.4.11(@types/node@22.7.9) - vitefu: 1.0.3(vite@5.4.11(@types/node@22.7.9)) + vitefu: 1.0.5(vite@5.4.11(@types/node@22.7.9)) which-pm: 3.0.0 xxhash-wasm: 1.1.0 yargs-parser: 21.1.1 @@ -18559,7 +18571,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) @@ -20167,6 +20179,10 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 + magic-string@0.30.17: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + magicast@0.3.5: dependencies: '@babel/parser': 7.26.2 @@ -23221,7 +23237,7 @@ snapshots: '@types/node': 22.7.9 fsevents: 2.3.3 - vitefu@1.0.3(vite@5.4.11(@types/node@22.7.9)): + vitefu@1.0.5(vite@5.4.11(@types/node@22.7.9)): optionalDependencies: vite: 5.4.11(@types/node@22.7.9) From b12637fceb2b968fe949b61f8275e0df9ad06f2e Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Tue, 14 Jan 2025 10:11:07 +0800 Subject: [PATCH 65/66] show the existing emitters in the tspconfig.yaml --- .../src/vscode-cmd/emit-code/emit-code.ts | 331 +++++++++++++----- .../src/vscode-cmd/emit-code/emitter.ts | 10 + 2 files changed, 248 insertions(+), 93 deletions(-) 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 c871343578a..5c39792a025 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 @@ -16,25 +16,47 @@ import { EmitterKind, getLanguageAlias, getRegisterEmitters, + getRegisterEmittersByPackage, getRegisterEmitterTypes, PreDefinedEmitterPickItems, } from "./emitter.js"; -async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kind: EmitterKind) { - if (!mainTspFile || !(await isFile(mainTspFile))) { - logger.error( - "Invalid typespec project. There is no main tsp file in the project. Generating Cancelled.", - [], - { showOutput: false, showPopup: true }, - ); - return; - } - - const baseDir = getDirectoryPath(mainTspFile); +interface EmitQuickPickButton extends QuickInputButton { + uri: string; +} - interface EmitQuickPickButton extends QuickInputButton { - uri: string; +async function selectEmitter( + context: vscode.ExtensionContext, + existingEmitters: string[], +): Promise { + const emitterKinds = getRegisterEmitterTypes(); + const toEmitterTypeQuickPickItem = (kind: EmitterKind): any => { + const registerEmitters = getRegisterEmitters(kind); + const supportedLanguages = registerEmitters.map((e) => e.language).join(", "); + return { + label: PreDefinedEmitterPickItems[kind]?.label ?? kind, + detail: + PreDefinedEmitterPickItems[kind]?.detail ?? + `Generate ${kind} code from TypeSpec files. Supported languages are ${supportedLanguages}.`, + emitterKind: kind, + iconPath: { + light: Uri.file(context.asAbsolutePath(`./icons/${kind.toLowerCase()}.light.svg`)), + dark: Uri.file(context.asAbsolutePath(`./icons/${kind.toLowerCase()}.dark.svg`)), + }, + }; + }; + const codesToEmit = emitterKinds.map((kind) => toEmitterTypeQuickPickItem(kind)); + const codeType = await vscode.window.showQuickPick(codesToEmit, { + title: "Generate from TypeSpec", + canPickMany: false, + placeHolder: "Select an emitter type", + ignoreFocusOut: true, + }); + if (!codeType) { + logger.info("No emitter Type selected. Generating Cancelled."); + return undefined; } + const toQuickPickItem = (e: Emitter): EmitQuickPickItem => { const buttons = e.sourceRepo ? [ @@ -68,14 +90,17 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin }; }; - const registerEmitters = getRegisterEmitters(kind); + /* filter out already existing emitters. */ + const registerEmitters = getRegisterEmitters(codeType.emitterKind).filter( + (emitter) => !existingEmitters.includes(emitter.package), + ); const all: EmitQuickPickItem[] = [...registerEmitters].map((e) => toQuickPickItem(e)); const emitterSelector = vscode.window.createQuickPick(); emitterSelector.items = all; emitterSelector.title = `Generate from TypeSpec`; emitterSelector.canSelectMany = false; - emitterSelector.placeholder = `Select a Language for ${kind} code generation`; + emitterSelector.placeholder = `Select a Language for ${codeType} code generation`; emitterSelector.ignoreFocusOut = true; emitterSelector.onDidTriggerItemButton(async (e) => { if (e.button.tooltip === "More details") { @@ -93,17 +118,37 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin if (!selectedEmitter) { logger.info("No language selected. Generating Cancelled."); + return undefined; + } + return { + language: selectedEmitter.language, + package: selectedEmitter.package, + version: selectedEmitter.version, + sourceRepo: selectedEmitter.sourceRepo, + requisites: selectedEmitter.requisites, + kind: selectedEmitter.emitterKind, + }; +} +async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, emitter: Emitter) { + if (!mainTspFile || !(await isFile(mainTspFile))) { + logger.error( + "Invalid typespec project. There is no main tsp file in the project. Generating Cancelled.", + [], + { showOutput: false, showPopup: true }, + ); return; } + const baseDir = getDirectoryPath(mainTspFile); + const npmUtil = new NpmUtil(baseDir); const packagesToInstall: string[] = []; /* install emitter package. */ - logger.info(`select ${selectedEmitter.package}`); + logger.info(`select ${emitter.package}`); const { action, version } = await npmUtil.calculateNpmPackageInstallAction( - selectedEmitter.package, - selectedEmitter.version, + emitter.package, + emitter.version, ); const installPackageQuickPickItems = await vscode.window.withProgress( @@ -115,26 +160,26 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin async () => { const packageQuickPickItems = []; if (action === InstallAction.Upgrade || action === InstallAction.Install) { - const minimumRequisites = selectedEmitter.requisites - ? ` Minimum requisites: install ${selectedEmitter.requisites?.join(", ")}` + const minimumRequisites = emitter.requisites + ? ` Minimum requisites: install ${emitter.requisites?.join(", ")}` : ""; - let packageFullName = selectedEmitter.package; + let packageFullName = emitter.package; if (version) { - packageFullName = `${selectedEmitter.package}@${version}`; + packageFullName = `${emitter.package}@${version}`; } packageQuickPickItems.push({ - label: `${selectedEmitter.package}`, - description: `TypeSpec library for emitting ${selectedEmitter.language} from TypeSpec files.`, + label: `${emitter.package}`, + description: `TypeSpec library for emitting ${emitter.language} from TypeSpec files.`, detail: minimumRequisites, packageFullName: packageFullName, - sourceRepo: selectedEmitter.sourceRepo, + sourceRepo: emitter.sourceRepo, picked: true, - buttons: selectedEmitter.sourceRepo + buttons: emitter.sourceRepo ? [ { iconPath: new vscode.ThemeIcon("link-external"), tooltip: "More details", - uri: selectedEmitter.sourceRepo, + uri: emitter.sourceRepo, }, ] : undefined, @@ -245,7 +290,7 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin return; } /*Config emitter output dir and emit in tspconfig.yaml. */ - const defaultEmitOutputDirInConfig = `{project-root}/${selectedEmitter.emitterKind}/${getLanguageAlias(selectedEmitter.language)}`; + const defaultEmitOutputDirInConfig = `{project-root}/${emitter.kind}/${getLanguageAlias(emitter.language)}`; const tspConfigFile = path.join(baseDir, TspConfigFileName); let configYaml = parseDocument(""); //generate a empty yaml if (await isFile(tspConfigFile)) { @@ -261,26 +306,22 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin if (Array.isArray(emitNode.items)) { const index = emitNode.items.findIndex((item) => { if (isScalar(item)) { - return item.value === selectedEmitter.package; + return item.value === emitter.package; } return false; }); if (index === -1) { - emitNode.items.push(selectedEmitter.package); + emitNode.items.push(emitter.package); } } } } else { - configYaml.set("emit", [selectedEmitter.package]); + configYaml.set("emit", [emitter.package]); } - const emitOutputDir = configYaml.getIn([ - "options", - selectedEmitter.package, - "emitter-output-dir", - ]); + const emitOutputDir = configYaml.getIn(["options", emitter.package, "emitter-output-dir"]); if (!emitOutputDir) { configYaml.setIn( - ["options", selectedEmitter.package, "emitter-output-dir"], + ["options", emitter.package, "emitter-output-dir"], defaultEmitOutputDirInConfig, ); } else { @@ -295,41 +336,27 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin outputDir = outputDir.replace("{project-root}", baseDir); const options: Record = {}; logger.info( - `Start to generate ${selectedEmitter.language} ${selectedEmitter.emitterKind} code under directory ${outputDir}`, + `Start to generate ${emitter.language} ${emitter.kind} code under directory ${outputDir}`, ); await vscode.window.withProgress( { location: vscode.ProgressLocation.Notification, - title: `Generating ${selectedEmitter.emitterKind} code for ${selectedEmitter.language}...`, + title: `Generating ${emitter.kind} code for ${emitter.language}...`, cancellable: false, }, async () => { try { - const compileResult = await compile( - cli, - mainTspFile, - selectedEmitter.package, - options, - false, - ); + const compileResult = await compile(cli, mainTspFile, emitter.package, options, false); if (compileResult.exitCode !== 0) { - logger.error( - `Generating ${selectedEmitter.emitterKind} code for ${selectedEmitter.language}...Failed`, - [], - { - showOutput: true, - showPopup: true, - }, - ); + logger.error(`Generating ${emitter.kind} code for ${emitter.language}...Failed`, [], { + showOutput: true, + showPopup: true, + }); } else { - logger.info( - `Generating ${selectedEmitter.emitterKind} code for ${selectedEmitter.language}...Succeeded`, - [], - { - showOutput: true, - showPopup: true, - }, - ); + logger.info(`Generating ${emitter.kind} code for ${emitter.language}...Succeeded`, [], { + showOutput: true, + showPopup: true, + }); } } catch (err: any) { if (typeof err === "object" && "stdout" in err && "stderr" in err && `error` in err) { @@ -339,7 +366,7 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin if (execOutput.stderr !== "") details.push(execOutput.stderr); if (execOutput.error) details.push(execOutput.error); logger.error( - `Generating ${selectedEmitter.emitterKind} code for ${selectedEmitter.language}...Failed.`, + `Generating ${emitter.kind} code for ${emitter.language}...Failed.`, details, { showOutput: true, @@ -347,14 +374,10 @@ async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, kin }, ); } else { - logger.error( - `Generating ${selectedEmitter.emitterKind} code for ${selectedEmitter.language}...Failed.`, - [err], - { - showOutput: true, - showPopup: true, - }, - ); + logger.error(`Generating ${emitter.kind} code for ${emitter.language}...Failed.`, [err], { + showOutput: true, + showPopup: true, + }); } } }, @@ -416,35 +439,157 @@ export async function emitCode(context: vscode.ExtensionContext, uri: vscode.Uri } logger.info(`Generate from entrypoint file: ${tspProjectFile}`); + const baseDir = getDirectoryPath(tspProjectFile); + const tspConfigFile = path.join(baseDir, TspConfigFileName); + let configYaml = parseDocument(""); //generate a empty yaml + if (await isFile(tspConfigFile)) { + const content = await readFile(tspConfigFile); + configYaml = parseDocument(content.toString()); + } - const emitterKinds = getRegisterEmitterTypes(); - const toEmitterTypeQuickPickItem = (kind: EmitterKind): any => { - const registerEmitters = getRegisterEmitters(kind); - const supportedLanguages = registerEmitters.map((e) => e.language).join(", "); + let existingEmitters; + try { + const emitNode = configYaml.get("emit"); + if (emitNode) { + if (isSeq(emitNode)) { + if (Array.isArray(emitNode.items)) { + existingEmitters = emitNode.items.map((item) => { + if (isScalar(item)) { + return item.value as string; + } + return ""; + }); + } + } + } + } catch (error: any) { + logger.error(error); + } + + const toQuickPickItem = (e: Emitter): EmitQuickPickItem => { + const buttons = e.sourceRepo + ? [ + { + iconPath: new vscode.ThemeIcon("link-external"), + tooltip: "More details", + uri: e.sourceRepo, + }, + ] + : undefined; return { - label: PreDefinedEmitterPickItems[kind]?.label ?? kind, - detail: - PreDefinedEmitterPickItems[kind]?.detail ?? - `Generate ${kind} code from TypeSpec files. Supported languages are ${supportedLanguages}.`, - emitterKind: kind, + language: e.language, + package: e.package, + version: e.version, + requisites: e.requisites, + sourceRepo: e.sourceRepo, + emitterKind: e.kind, + label: `${e.language} ${e.kind} code emitter`, + description: `${e.package}.`, + // detail: `Generate ${e.kind} code for ${e.language} by TypeSpec library ${e.package}.`, + picked: false, + fromConfig: false, + buttons: buttons, iconPath: { - light: Uri.file(context.asAbsolutePath(`./icons/${kind.toLowerCase()}.light.svg`)), - dark: Uri.file(context.asAbsolutePath(`./icons/${kind.toLowerCase()}.dark.svg`)), + light: Uri.file( + context.asAbsolutePath(`./icons/${getLanguageAlias(e.language).toLowerCase()}.light.svg`), + ), + dark: Uri.file( + context.asAbsolutePath(`./icons/${getLanguageAlias(e.language).toLowerCase()}.dark.svg`), + ), }, }; }; - const codesToEmit = emitterKinds.map((kind) => toEmitterTypeQuickPickItem(kind)); - const codeType = await vscode.window.showQuickPick(codesToEmit, { - title: "Generate from TypeSpec", - canPickMany: false, - placeHolder: "Select an emitter type", - ignoreFocusOut: true, - }); - if (!codeType) { - logger.info("No emitter Type selected. Generating Cancelled."); - return; + /* display existing emitters in config.yaml. */ + if (existingEmitters && existingEmitters.length > 0) { + const existingEmitterQuickPickItems = existingEmitters.map((e) => { + const emitter = getRegisterEmittersByPackage(e); + if (emitter) { + return toQuickPickItem(emitter); + } else { + return { + label: e, + description: "Emit code by the emitter library.", + picked: true, + fromConfig: true, + package: e, + kind: vscode.QuickPickItemKind.Default, + }; + } + }); + const separatorItem = { + label: "Settings", + description: "settings", + kind: vscode.QuickPickItemKind.Separator, + info: undefined, + package: "", + fromConfig: false, + picked: false, + }; + const newEmitterQuickPickItem = { + label: "Configure a new emitter", + description: "Configure a new emitter for code generation", + picked: false, + fromConfig: false, + package: "", + kind: vscode.QuickPickItemKind.Default, + buttons: [ + { + iconPath: new vscode.ThemeIcon("settings-gear"), + tooltip: "Configure a new emitter for code generation", + }, + ], + }; + + const allPickItems = []; + allPickItems.push(...existingEmitterQuickPickItems, separatorItem, newEmitterQuickPickItem); + const existingEmittersSelector = vscode.window.createQuickPick(); + existingEmittersSelector.items = allPickItems; + existingEmittersSelector.title = `Generate from TypeSpec`; + existingEmittersSelector.canSelectMany = false; + existingEmittersSelector.placeholder = "Select an emitter for code generation"; + existingEmittersSelector.ignoreFocusOut = true; + existingEmittersSelector.show(); + const selectedExistingEmitter = await new Promise((resolve) => { + existingEmittersSelector.onDidAccept(async () => { + const selectedItem = existingEmittersSelector.selectedItems[0]; + if (selectedItem === newEmitterQuickPickItem) { + const newEmitter = await selectEmitter(context, existingEmitters); + const allPickItems = []; + if (newEmitter) { + allPickItems.push(toQuickPickItem(newEmitter)); + } + allPickItems.push(...existingEmitterQuickPickItems); + allPickItems.push(separatorItem, newEmitterQuickPickItem); + existingEmittersSelector.items = allPickItems; + existingEmittersSelector.show(); + } else { + resolve(existingEmittersSelector.selectedItems[0]); + existingEmittersSelector.dispose(); + } + }); + }); + if (!selectedExistingEmitter) { + logger.info("No emitter selected. Generating Cancelled."); + return; + } + await doEmit( + context, + tspProjectFile, + getRegisterEmittersByPackage(selectedExistingEmitter.package) ?? { + package: selectedExistingEmitter.package, + language: "Unknown", + kind: EmitterKind.Unknown, + }, + ); + } else { + const newEmitter = await selectEmitter(context, existingEmitters ?? []); + if (newEmitter) { + await doEmit(context, tspProjectFile, newEmitter); + } else { + logger.info("No emitter selected. Generating Cancelled."); + return; + } } - await doEmit(context, tspProjectFile, codeType.emitterKind); } async function compile( 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 aa68e513f86..3fe1fb41370 100644 --- a/packages/typespec-vscode/src/vscode-cmd/emit-code/emitter.ts +++ b/packages/typespec-vscode/src/vscode-cmd/emit-code/emitter.ts @@ -6,6 +6,7 @@ export enum EmitterKind { Schema = "openapi", Client = "client", Server = "server", + Unknown = "unknown", } export interface Emitter { @@ -75,6 +76,15 @@ export function getRegisterEmitterTypes(): ReadonlyArray { return Array.from(new Set(emitters.map((emitter) => emitter.kind))); } +export function getRegisterEmittersByPackage(packageName: string): Emitter | undefined { + const extensionConfig = vscode.workspace.getConfiguration(); + const emitters: ReadonlyArray = + extensionConfig.get(SettingName.GenerateCodeEmitters) ?? []; + return emitters.find( + (emitter) => emitter.package === packageName || emitter.package.startsWith(packageName + "@"), + ); +} + const languageAlias: Record = { ".NET": "dotnet", }; From 94dcc8f8a2d3cc2b88c790e52f257dcda9cd694f Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Tue, 14 Jan 2025 10:51:37 +0800 Subject: [PATCH 66/66] remove unused parameter --- .../src/vscode-cmd/emit-code/emit-code.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) 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 5c39792a025..a57114288e5 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 @@ -25,7 +25,7 @@ interface EmitQuickPickButton extends QuickInputButton { uri: string; } -async function selectEmitter( +async function configureEmitter( context: vscode.ExtensionContext, existingEmitters: string[], ): Promise { @@ -129,7 +129,7 @@ async function selectEmitter( kind: selectedEmitter.emitterKind, }; } -async function doEmit(context: vscode.ExtensionContext, mainTspFile: string, emitter: Emitter) { +async function doEmit(mainTspFile: string, emitter: Emitter) { if (!mainTspFile || !(await isFile(mainTspFile))) { logger.error( "Invalid typespec project. There is no main tsp file in the project. Generating Cancelled.", @@ -466,7 +466,7 @@ export async function emitCode(context: vscode.ExtensionContext, uri: vscode.Uri logger.error(error); } - const toQuickPickItem = (e: Emitter): EmitQuickPickItem => { + const toEmitterQuickPickItem = (e: Emitter): EmitQuickPickItem => { const buttons = e.sourceRepo ? [ { @@ -504,7 +504,7 @@ export async function emitCode(context: vscode.ExtensionContext, uri: vscode.Uri const existingEmitterQuickPickItems = existingEmitters.map((e) => { const emitter = getRegisterEmittersByPackage(e); if (emitter) { - return toQuickPickItem(emitter); + return toEmitterQuickPickItem(emitter); } else { return { label: e, @@ -553,10 +553,10 @@ export async function emitCode(context: vscode.ExtensionContext, uri: vscode.Uri existingEmittersSelector.onDidAccept(async () => { const selectedItem = existingEmittersSelector.selectedItems[0]; if (selectedItem === newEmitterQuickPickItem) { - const newEmitter = await selectEmitter(context, existingEmitters); + const newEmitter = await configureEmitter(context, existingEmitters); const allPickItems = []; if (newEmitter) { - allPickItems.push(toQuickPickItem(newEmitter)); + allPickItems.push(toEmitterQuickPickItem(newEmitter)); } allPickItems.push(...existingEmitterQuickPickItems); allPickItems.push(separatorItem, newEmitterQuickPickItem); @@ -573,7 +573,6 @@ export async function emitCode(context: vscode.ExtensionContext, uri: vscode.Uri return; } await doEmit( - context, tspProjectFile, getRegisterEmittersByPackage(selectedExistingEmitter.package) ?? { package: selectedExistingEmitter.package, @@ -582,9 +581,9 @@ export async function emitCode(context: vscode.ExtensionContext, uri: vscode.Uri }, ); } else { - const newEmitter = await selectEmitter(context, existingEmitters ?? []); + const newEmitter = await configureEmitter(context, existingEmitters ?? []); if (newEmitter) { - await doEmit(context, tspProjectFile, newEmitter); + await doEmit(tspProjectFile, newEmitter); } else { logger.info("No emitter selected. Generating Cancelled."); return;