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/serverstub.svg b/packages/typespec-vscode/icons/server.svg
similarity index 99%
rename from packages/typespec-vscode/icons/serverstub.svg
rename to packages/typespec-vscode/icons/server.svg
index 828b762e17..f3fc2ab8ff 100644
--- a/packages/typespec-vscode/icons/serverstub.svg
+++ b/packages/typespec-vscode/icons/server.svg
@@ -6,4 +6,4 @@
-
\ No newline at end of file
+
diff --git a/packages/typespec-vscode/package.json b/packages/typespec-vscode/package.json
index d789c2cca2..096a1235ed 100644
--- a/packages/typespec-vscode/package.json
+++ b/packages/typespec-vscode/package.json
@@ -109,7 +109,7 @@
"default": "off",
"description": "Define whether/how the TypeSpec language server should send traces to client. For the traces to show properly in vscode Output, make sure 'Log Level' is also set to 'Trace' so that they won't be filtered at client side, which can be set through 'Developer: Set Log Level...' command."
},
- "typespec.emitters": {
+ "typespec.generateCode.emitters": {
"scope": "window",
"type": "array",
"items": {
@@ -215,7 +215,7 @@
"category": "TypeSpec"
},
{
- "command": "typespec.generate",
+ "command": "typespec.generateCode",
"title": "Generate from TypeSpec",
"category": "TypeSpec"
},
@@ -233,14 +233,14 @@
"menus": {
"explorer/context": [
{
- "command": "typespec.generate",
+ "command": "typespec.generateCode",
"when": "explorerResourceIsFolder || resourceLangId == typespec",
"group": "code_generation"
}
],
"editor/context": [
{
- "command": "typespec.generate",
+ "command": "typespec.generateCode",
"when": "resourceLangId == typespec",
"group": "code_generation"
}
diff --git a/packages/typespec-vscode/src/emit/emit-quick-pick-item.ts b/packages/typespec-vscode/src/emit/emit-quick-pick-item.ts
index 86391b1dc9..9be498b995 100644
--- a/packages/typespec-vscode/src/emit/emit-quick-pick-item.ts
+++ b/packages/typespec-vscode/src/emit/emit-quick-pick-item.ts
@@ -9,7 +9,3 @@ export interface EmitQuickPickItem extends vscode.QuickPickItem {
outputDir?: string;
emitterKind: EmitterKind;
}
-
-export interface TypeSpecProjectPickItem extends vscode.QuickPickItem {
- path: string;
-}
diff --git a/packages/typespec-vscode/src/emit/emit.ts b/packages/typespec-vscode/src/emit/emit.ts
index a01f7aba89..d6031e357a 100644
--- a/packages/typespec-vscode/src/emit/emit.ts
+++ b/packages/typespec-vscode/src/emit/emit.ts
@@ -1,37 +1,42 @@
-// import { TypeSpecConfig } from "@typespec/compiler";
-import path, { dirname } from "path";
+import path from "path";
import vscode, { Uri } from "vscode";
import { Executable } from "vscode-languageclient/node.js";
import { StartFileName } from "../const.js";
import logger from "../log/logger.js";
import { InstallationAction, npmDependencyType, NpmUtil } from "../npm-utils.js";
+import { getDirectoryPath } from "../path-utils.js";
+import { resolveTypeSpecCli } from "../tsp-executable-resolver.js";
import {
- getMainTspFile,
- resolveTypeSpecCli,
- toError,
- toOutput,
+ getEntrypointTspFile,
+ onStdioError,
+ onStdioOut,
TraverseMainTspFileInWorkspace,
} from "../typespec-utils.js";
import { ExecOutput, isFile, spawnExecution } from "../utils.js";
-import { EmitQuickPickItem, TypeSpecProjectPickItem } from "./emit-quick-pick-item.js";
-import { Emitter, EmitterKind, getRegisterEmitters } from "./emitter.js";
+import { EmitQuickPickItem } from "./emit-quick-pick-item.js";
+import {
+ Emitter,
+ EmitterKind,
+ getRegisterEmitters,
+ getRegisterEmitterTypes,
+ PreDefinedEmitterPickItems,
+} from "./emitter.js";
export async function doEmit(
context: vscode.ExtensionContext,
mainTspFile: string,
kind: EmitterKind,
- overallProgress: vscode.Progress<{ message?: string; increment?: number }>,
) {
if (!mainTspFile || !(await isFile(mainTspFile))) {
logger.info(
"Invalid typespec project. There is no main tsp file in the project. Generating Cancelled.",
[],
- { showOutput: false, showPopup: true, progress: overallProgress },
+ { showOutput: false, showPopup: true },
);
return;
}
- const baseDir = dirname(mainTspFile);
+ const baseDir = getDirectoryPath(mainTspFile);
const toQuickPickItem = (e: Emitter): EmitQuickPickItem => {
return {
@@ -60,37 +65,13 @@ export async function doEmit(
logger.info("No emitter selected. Generating Cancelled.", [], {
showOutput: false,
showPopup: false,
- progress: overallProgress,
});
return;
}
- /* TODO: verify the sdk runtime installation. */
- /* inform to install needed runtime. */
- const { valid, required } = await check(`${baseDir}/${StartFileName}`, selectedEmitter.package);
- if (!valid) {
- const toInstall = required.map((e) => e.name).join(", ");
- await vscode.window
- .showInformationMessage(
- `Please install the required runtime for the selected emitters\n\n. ${toInstall}`,
- "OK",
- )
- .then((selection) => {
- if (selection === "OK") {
- logger.info("Generating Cancelled.", [], {
- showOutput: false,
- showPopup: true,
- progress: overallProgress,
- });
- }
- });
- return;
- }
-
logger.info("npm install...", [], {
showOutput: false,
showPopup: false,
- progress: overallProgress,
});
const npmUtil = new NpmUtil(baseDir);
@@ -98,7 +79,7 @@ export async function doEmit(
/* install emitter package. */
logger.info(`select ${selectedEmitter.package}`);
- const { action, version } = await npmUtil.ensureNpmPackageInstall(
+ const { action, version } = await npmUtil.ensureNpmPackageInstallAction(
selectedEmitter.package,
selectedEmitter.version,
);
@@ -122,7 +103,6 @@ export async function doEmit(
logger.info(`Ignore upgrading emitter ${selectedEmitter.package} for generating`, [], {
showOutput: false,
showPopup: false,
- progress: overallProgress,
});
} else {
logger.info(
@@ -131,7 +111,6 @@ export async function doEmit(
{
showOutput: false,
showPopup: true,
- progress: overallProgress,
},
);
return;
@@ -143,7 +122,7 @@ export async function doEmit(
}
logger.info(`Installing ${packageFullName}`);
/* verify dependency packages. */
- const dependenciesToInstall = await npmUtil.ensureNpmPackageDependencyInstall(
+ const dependenciesToInstall = await npmUtil.ensureNpmPackageDependencyToUpgrade(
selectedEmitter.package,
version,
npmDependencyType.peerDependencies,
@@ -164,18 +143,16 @@ export async function doEmit(
logger.info(`Installing ${packagesToInstall.join("\n\n")} under ${baseDir}`, [], {
showOutput: true,
showPopup: true,
- progress: overallProgress,
});
try {
const npmInstallResult = await npmUtil.npmInstallPackages(packagesToInstall, undefined, {
- onStdioOut: toOutput,
- onStdioError: toError,
+ onStdioOut: onStdioOut,
+ onStdioError: onStdioError,
});
if (npmInstallResult.exitCode !== 0) {
logger.error(`Error occurred when installing packages.`, [`${npmInstallResult.stderr}`], {
showOutput: true,
showPopup: true,
- progress: overallProgress,
});
return;
}
@@ -184,7 +161,6 @@ export async function doEmit(
logger.error(`Exception occurred when installing packages.`, [err], {
showOutput: true,
showPopup: true,
- progress: overallProgress,
});
return;
}
@@ -194,7 +170,6 @@ export async function doEmit(
logger.info("Generating ...", [], {
showOutput: false,
showPopup: false,
- progress: overallProgress,
});
const cli = await resolveTypeSpecCli(baseDir);
@@ -205,7 +180,6 @@ export async function doEmit(
{
showOutput: true,
showPopup: true,
- progress: overallProgress,
},
);
return;
@@ -220,7 +194,6 @@ export async function doEmit(
{
showOutput: true,
showPopup: false,
- progress: overallProgress,
},
);
try {
@@ -232,7 +205,6 @@ export async function doEmit(
{
showOutput: true,
showPopup: true,
- progress: overallProgress,
},
);
} else {
@@ -242,7 +214,6 @@ export async function doEmit(
{
showOutput: true,
showPopup: true,
- progress: overallProgress,
},
);
}
@@ -253,19 +224,12 @@ export async function doEmit(
{
showOutput: true,
showPopup: true,
- progress: overallProgress,
},
);
}
-
- /*TODO: build sdk. */
}
-export async function emitCode(
- context: vscode.ExtensionContext,
- uri: vscode.Uri,
- overallProgress: vscode.Progress<{ message?: string; increment?: number }>,
-) {
+export async function emitCode(context: vscode.ExtensionContext, uri: vscode.Uri) {
let tspProjectFile: string = "";
if (!uri) {
const targetPathes = await TraverseMainTspFileInWorkspace();
@@ -274,11 +238,10 @@ export async function emitCode(
logger.info("No main tsp file found. Generating Cancelled.", [], {
showOutput: false,
showPopup: true,
- progress: overallProgress,
});
return;
}
- const toProjectPickItem = (filePath: string): TypeSpecProjectPickItem => {
+ const toProjectPickItem = (filePath: string): any => {
return {
label: filePath,
path: filePath,
@@ -288,7 +251,7 @@ export async function emitCode(
},
};
};
- const typespecProjectQuickPickItems: TypeSpecProjectPickItem[] = targetPathes.map((filePath) =>
+ const typespecProjectQuickPickItems: any[] = targetPathes.map((filePath) =>
toProjectPickItem(filePath),
);
const selectedProjectFile = await vscode.window.showQuickPick(typespecProjectQuickPickItems, {
@@ -301,49 +264,38 @@ export async function emitCode(
logger.info("No project selected. Generating Cancelled.", [], {
showOutput: false,
showPopup: true,
- progress: overallProgress,
});
return;
}
tspProjectFile = selectedProjectFile.path;
} else {
- const tspStartFile = await getMainTspFile(uri.fsPath);
+ const tspStartFile = await getEntrypointTspFile(uri.fsPath);
if (!tspStartFile) {
logger.info("No main file. Invalid typespec project.", [], {
showOutput: false,
showPopup: true,
- progress: overallProgress,
});
return;
}
tspProjectFile = tspStartFile;
}
- interface EmitTypeQuickPickItem extends vscode.QuickPickItem {
- emitterKind: EmitterKind;
- }
+ logger.info(`Generate from entrypoint file: ${tspProjectFile}`, [], {
+ showOutput: false,
+ showPopup: false,
+ });
- const codesToEmit = [
- {
- label: "Protocol Schema",
- detail: "Generating Protocol schema (OpenAPI for example) from TypeSpec",
- iconPath: Uri.file(context.asAbsolutePath(`./icons/schema.svg`)),
- emitterKind: EmitterKind.Schema,
- },
- {
- label: "Client Code",
- detail: "Generating Client Code from TypeSpec.",
- iconPath: Uri.file(context.asAbsolutePath(`./icons/sdk.svg`)),
- emitterKind: EmitterKind.Client,
- },
- {
- label: " Server Stub",
- detail: "Generating Server Stub from TypeSpec",
- iconPath: Uri.file(context.asAbsolutePath(`./icons/serverstub.svg`)),
- emitterKind: EmitterKind.Server,
- },
- ];
- const codeType = await vscode.window.showQuickPick(codesToEmit, {
+ const emitterKinds = getRegisterEmitterTypes();
+ const toEitTypeQuickPickItem = (kind: EmitterKind): any => {
+ return {
+ label: PreDefinedEmitterPickItems[kind]?.label ?? kind,
+ detail: PreDefinedEmitterPickItems[kind]?.detail ?? `Generate ${kind} code from TypeSpec`,
+ emitterKind: kind,
+ iconPath: Uri.file(context.asAbsolutePath(`./icons/${kind.toLowerCase()}.svg`)),
+ };
+ };
+ const codesToEmit = emitterKinds.map((kind) => toEitTypeQuickPickItem(kind));
+ const codeType = await vscode.window.showQuickPick(codesToEmit, {
title: "Select an Emitter Type",
canPickMany: false,
placeHolder: "Select an emitter type",
@@ -353,11 +305,10 @@ export async function emitCode(
logger.info("No emitter Type selected. Generating Cancelled.", [], {
showOutput: false,
showPopup: false,
- progress: overallProgress,
});
return;
}
- await doEmit(context, tspProjectFile, codeType.emitterKind, overallProgress);
+ await doEmit(context, tspProjectFile, codeType.emitterKind);
}
export async function compile(
@@ -377,19 +328,8 @@ export async function compile(
args.push("--option", `${emitter}.${key}=${value}`);
}
- return await spawnExecution(cli.command, args, dirname(startFile), {
- onStdioOut: toOutput,
- onStdioError: toError,
+ return await spawnExecution(cli.command, args, getDirectoryPath(startFile), {
+ onStdioOut: onStdioOut,
+ onStdioError: onStdioError,
});
}
-
-export async function check(
- startFile: string,
- emitter: string,
-): Promise<{
- valid: boolean;
- required: { name: string; version: string }[];
-}> {
- /* TODO: check the runtime. */
- return { valid: true, required: [] };
-}
diff --git a/packages/typespec-vscode/src/emit/emitter.ts b/packages/typespec-vscode/src/emit/emitter.ts
index bb6a760327..e3b1281a1d 100644
--- a/packages/typespec-vscode/src/emit/emitter.ts
+++ b/packages/typespec-vscode/src/emit/emitter.ts
@@ -1,4 +1,5 @@
import vscode from "vscode";
+import logger from "../log/logger.js";
import { SettingName } from "../types.js";
export enum EmitterKind {
@@ -14,10 +15,28 @@ export interface Emitter {
kind: EmitterKind;
}
-const extensionConfig = vscode.workspace.getConfiguration();
+export const PreDefinedEmitterPickItems: Record = {
+ schema: {
+ label: "Protocol Schema",
+ detail: "Generating Protocol schema (OpenAPI for example) from TypeSpec",
+ },
+ client: {
+ label: "Client Code",
+ detail: "Generating Client Code from TypeSpec.",
+ },
+ server: {
+ label: " Server Stub",
+ detail: "Generating Server Stub from TypeSpec",
+ },
+};
-function getEmitter(kind: EmitterKind, emitter: Emitter): Emitter {
- const packageFullName: string = emitter.package ?? "";
+function getEmitter(kind: EmitterKind, emitter: Emitter): Emitter | undefined {
+ let packageFullName: string = emitter.package;
+ if (!packageFullName) {
+ logger.error("Emitter package name is required.");
+ return undefined;
+ }
+ packageFullName = packageFullName.trim();
const index = packageFullName.lastIndexOf("@");
let version = undefined;
let packageName = packageFullName;
@@ -35,8 +54,18 @@ function getEmitter(kind: EmitterKind, emitter: Emitter): Emitter {
}
export function getRegisterEmitters(kind: EmitterKind): ReadonlyArray {
- const emitters: ReadonlyArray = extensionConfig.get(SettingName.Emitters) ?? [];
+ const extensionConfig = vscode.workspace.getConfiguration();
+ const emitters: ReadonlyArray =
+ extensionConfig.get(SettingName.GenerateCodeEmitters) ?? [];
return emitters
.filter((emitter) => emitter.kind === kind)
- .map((emitter) => getEmitter(kind, emitter));
+ .map((emitter) => getEmitter(kind, emitter))
+ .filter((emitter) => emitter !== undefined) as Emitter[];
+}
+
+export function getRegisterEmitterTypes(): ReadonlyArray {
+ const extensionConfig = vscode.workspace.getConfiguration();
+ const emitters: ReadonlyArray =
+ extensionConfig.get(SettingName.GenerateCodeEmitters) ?? [];
+ return Array.from(new Set(emitters.map((emitter) => emitter.kind)));
}
diff --git a/packages/typespec-vscode/src/extension.ts b/packages/typespec-vscode/src/extension.ts
index f5882c3324..5c3c43ff3f 100644
--- a/packages/typespec-vscode/src/extension.ts
+++ b/packages/typespec-vscode/src/extension.ts
@@ -47,14 +47,14 @@ export async function activate(context: ExtensionContext) {
/* emit command. */
context.subscriptions.push(
- commands.registerCommand(CommandName.Generate, async (uri: vscode.Uri) => {
+ commands.registerCommand(CommandName.GenerateCode, async (uri: vscode.Uri) => {
await vscode.window.withProgress(
{
location: vscode.ProgressLocation.Window,
title: "Generate from TypeSpec...",
cancellable: false,
},
- async (progress) => await emitCode(context, uri, progress),
+ async () => await emitCode(context, uri),
);
}),
);
diff --git a/packages/typespec-vscode/src/npm-utils.ts b/packages/typespec-vscode/src/npm-utils.ts
index 542f270e2d..e952ed86e5 100644
--- a/packages/typespec-vscode/src/npm-utils.ts
+++ b/packages/typespec-vscode/src/npm-utils.ts
@@ -1,4 +1,4 @@
-import fs from "fs";
+import { readFile } from "fs/promises";
import path from "path";
import semver from "semver";
import logger from "./log/logger.js";
@@ -40,7 +40,8 @@ export class NpmUtil {
return spawnExecution("npm", ["install", ...packages], this.cwd, on);
}
- public async ensureNpmPackageInstall(
+ /* identify the action to take for a package. install or skip or cancel or upgrade */
+ public async ensureNpmPackageInstallAction(
packageName: string,
version?: string,
): Promise<{ action: InstallationAction; version?: string }> {
@@ -48,10 +49,14 @@ export class NpmUtil {
await this.isPackageInstalled(packageName);
if (isPackageInstalled) {
if (version && installedVersion !== version) {
- return {
- action: InstallationAction.Upgrade,
- version: version,
- };
+ if (semver.gt(version, installedVersion!)) {
+ return { action: InstallationAction.Upgrade, version: version };
+ } else {
+ logger.info(
+ "The version to intall is less than the installed version. Skip installation.",
+ );
+ return { action: InstallationAction.Skip, version: installedVersion };
+ }
}
return { action: InstallationAction.Skip, version: installedVersion };
} else {
@@ -59,7 +64,8 @@ export class NpmUtil {
}
}
- public async ensureNpmPackageDependencyInstall(
+ /* identify the dependency packages need to be upgraded */
+ public async ensureNpmPackageDependencyToUpgrade(
packageName: string,
version?: string,
dependencyType: npmDependencyType = npmDependencyType.dependencies,
@@ -72,15 +78,15 @@ export class NpmUtil {
}
/* get dependencies. */
- const dependenciesResult = await spawnExecution(
- "npm",
- ["view", packageFullName, dependencyType],
- this.cwd,
- on,
- );
-
- if (dependenciesResult.exitCode === 0) {
- try {
+ try {
+ const dependenciesResult = await spawnExecution(
+ "npm",
+ ["view", packageFullName, dependencyType],
+ this.cwd,
+ on,
+ );
+
+ if (dependenciesResult.exitCode === 0) {
// Remove all newline characters
let dependenciesJsonStr = dependenciesResult.stdout.trim();
dependenciesJsonStr = dependenciesJsonStr.replace(/\n/g, "");
@@ -96,11 +102,12 @@ export class NpmUtil {
}
}
}
- } catch (err) {
- if (on && on.onError) {
- on.onError(err, "", "");
- }
}
+ } catch (err) {
+ if (on && on.onError) {
+ on.onError(err, "", "");
+ }
+ logger.error("Error getting dependencies.", [err]);
}
return dependenciesToInstall;
@@ -126,11 +133,11 @@ export class NpmUtil {
/* get the package version. */
let version;
try {
- const data = fs.readFileSync(packageJsonPath, "utf-8");
+ const data = await readFile(packageJsonPath, { encoding: "utf-8" });
const packageJson = JSON.parse(data);
version = packageJson.version;
} catch (error) {
- logger.error(`Error reading package.json: ${error}`);
+ logger.error("Error reading package.json.", [error]);
}
return {
name: packageName,
diff --git a/packages/typespec-vscode/src/types.ts b/packages/typespec-vscode/src/types.ts
index 6c748d6be8..899f88df1a 100644
--- a/packages/typespec-vscode/src/types.ts
+++ b/packages/typespec-vscode/src/types.ts
@@ -1,7 +1,7 @@
export const enum SettingName {
TspServerPath = "typespec.tsp-server.path",
InitTemplatesUrls = "typespec.initTemplatesUrls",
- Emitters = "typespec.emitters",
+ GenerateCodeEmitters = "typespec.generateCode.emitters",
}
export const enum CommandName {
@@ -10,7 +10,7 @@ export const enum CommandName {
InstallGlobalCompilerCli = "typespec.installGlobalCompilerCli",
CreateProject = "typespec.createProject",
OpenUrl = "typespec.openUrl",
- Generate = "typespec.generate",
+ GenerateCode = "typespec.generateCode",
}
export interface InstallGlobalCliCommandArgs {
diff --git a/packages/typespec-vscode/src/typespec-utils.ts b/packages/typespec-vscode/src/typespec-utils.ts
index f111f8b912..edf3ac05da 100644
--- a/packages/typespec-vscode/src/typespec-utils.ts
+++ b/packages/typespec-vscode/src/typespec-utils.ts
@@ -1,18 +1,18 @@
-import { readdir } from "fs";
-import path, { dirname } from "path";
+import { readdir } from "node:fs/promises";
+import path from "path";
import vscode from "vscode";
-import { Executable } from "vscode-languageclient/node.js";
import { StartFileName } from "./const.js";
import logger from "./log/logger.js";
-import { isFile, loadModule, normalizeSlash } from "./utils.js";
+import { getDirectoryPath, normalizeSlashes } from "./path-utils.js";
+import { isFile } from "./utils.js";
-export const toOutput = (str: string) => {
+export const onStdioOut = (str: string) => {
str
.trim()
.split("\n")
.forEach((line) => logger.info(line));
};
-export const toError = (str: string) => {
+export const onStdioError = (str: string) => {
str
.trim()
.split("\n")
@@ -24,24 +24,9 @@ export const toError = (str: string) => {
);
};
-export async function resolveTypeSpecCli(absolutePath: string): Promise {
- if (!path.isAbsolute(absolutePath) || (await isFile(absolutePath))) {
- return undefined;
- }
- const modelInfo = await loadModule(absolutePath, "@typespec/compiler");
- if (modelInfo) {
- const cmdPath = path.resolve(modelInfo.path, "cmd/tsp.js");
- return {
- command: "node",
- args: [cmdPath],
- };
- }
- return undefined;
-}
-
-export async function getMainTspFile(tspPath: string): Promise {
+export async function getEntrypointTspFile(tspPath: string): Promise {
const isFilePath = await isFile(tspPath);
- const baseDir = isFilePath ? dirname(tspPath) : tspPath;
+ const baseDir = isFilePath ? getDirectoryPath(tspPath) : tspPath;
const mainTspFile = path.resolve(baseDir, StartFileName);
if (await isFile(mainTspFile)) {
return mainTspFile;
@@ -52,19 +37,12 @@ export async function getMainTspFile(tspPath: string): Promise((resolve, reject) => {
- readdir(baseDir, (err, files) => {
- if (err) {
- reject(err);
- } else {
- resolve(files);
- }
- });
- });
- if (files.length === 1 && files[0].endsWith(".tsp")) {
+ const files = await readdir(baseDir);
+ if (files && files.length === 1 && files[0].endsWith(".tsp")) {
return path.resolve(baseDir, files[0]);
}
- } catch (error) {
+ } catch (err) {
+ logger.error("Error reading directory", [err]);
return undefined;
}
@@ -72,11 +50,11 @@ export async function getMainTspFile(tspPath: string): Promise
uris
.filter((uri) => uri.scheme === "file" && !uri.fsPath.includes("node_modules"))
- .map((uri) => normalizeSlash(uri.fsPath)),
+ .map((uri) => normalizeSlashes(uri.fsPath)),
);
}
diff --git a/packages/typespec-vscode/src/utils.ts b/packages/typespec-vscode/src/utils.ts
index ea70042f7d..f5b72bae7d 100644
--- a/packages/typespec-vscode/src/utils.ts
+++ b/packages/typespec-vscode/src/utils.ts
@@ -7,11 +7,6 @@ import { Executable } from "vscode-languageclient/node.js";
import logger from "./log/logger.js";
import { isUrl } from "./path-utils.js";
-/** normalize / and \\ to / */
-export function normalizeSlash(str: string): string {
- return str.replaceAll(/\\/g, "/");
-}
-
export async function isFile(path: string) {
try {
const stats = await stat(path);