Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed a bug where OpenAPI schemas with `format` but without `type` keyword would generate `UntypedNode` instead of proper types. [#7315](https://github.com/microsoft/kiota/issues/7315)
- Fixed a bug where discriminator mappings for oneOf types with allOf-inherited schemas would incorrectly use schema names as keys instead of resolving the base type discriminator mappings. [#7339](https://github.com/microsoft/kiota/issues/7339)
- Fixed TypeScript enum imports to use `import type` for type aliases to support `verbatimModuleSyntax`. [#7332](https://github.com/microsoft/kiota/pull/7332)
- Fixed a bug where the kiota npm package esm variant would fail to resolve modules.
- Fixes a bug where the kiota npm package type definitions would be misplaced.

## [1.30.0] - 2026-01-26

Expand Down Expand Up @@ -1706,4 +1708,3 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Initial GitHub release

6 changes: 4 additions & 2 deletions vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
},
"scripts": {
"lint": "npm run lint --w=\"@microsoft/kiota\" && npm run lint --w=kiota",
"build": "npm run build --w=\"@microsoft/kiota\" && npm run build --w=kiota",
"build": "npm run build:package && npm run build:vscode",
"build:package": "npm run build --w=\"@microsoft/kiota\"",
"build:vscode": "npm run build --w=kiota",
"test:vscode": "npm run build && npm run test --w=kiota",
"test:vscode:coverage": "npm run build && npm run test:coverage --w=kiota",
"test:package": "npm run build --w=\"@microsoft/kiota\" && npm run test --w=\"@microsoft/kiota\"",
Expand Down Expand Up @@ -36,4 +38,4 @@
"packages/npm-package",
"packages/microsoft-kiota"
]
}
}
2 changes: 1 addition & 1 deletion vscode/packages/npm-package/connect.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as cp from 'child_process';
import * as rpc from 'vscode-jsonrpc/node';
import { ensureKiotaIsPresent, getKiotaPath } from './install';
import { ensureKiotaIsPresent, getKiotaPath } from './install.js';


export default async function connectToKiota<T>(callback: (connection: rpc.MessageConnection) => Promise<T | undefined>, workingDirectory: string = process.cwd()): Promise<T | undefined | Error> {
Expand Down
26 changes: 13 additions & 13 deletions vscode/packages/npm-package/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
export * from './config';
export * from './lib/generateClient';
export * from './lib/generatePlugin';
export * from './lib/getKiotaTree';
export * from './lib/getKiotaVersion';
export * from './lib/getManifestDetails';
export * from './lib/languageInformation';
export * from './lib/migrateFromLockFile';
export * from './lib/removeItem';
export * from './lib/searchDescription';
export * from './lib/updateClients';
export * from './types';
export * from './utils';
export * from './config.js';
export * from './lib/generateClient.js';
export * from './lib/generatePlugin.js';
export * from './lib/getKiotaTree.js';
export * from './lib/getKiotaVersion.js';
export * from './lib/getManifestDetails.js';
export * from './lib/languageInformation.js';
export * from './lib/migrateFromLockFile.js';
export * from './lib/removeItem.js';
export * from './lib/searchDescription.js';
export * from './lib/updateClients.js';
export * from './types.js';
export * from './utils.js';
142 changes: 101 additions & 41 deletions vscode/packages/npm-package/install.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,34 @@
import AdmZip from 'adm-zip';
import { createHash } from 'crypto';
import * as https from 'https';
import * as fs from 'fs';
import * as path from 'path';
import { getKiotaConfig } from './config';
import AdmZip from "adm-zip";
import { createHash } from "node:crypto";
import * as https from "node:https";
import * as fs from "node:fs";
import * as path from "node:path";
import { fileURLToPath } from "node:url";
import { getKiotaConfig } from "./config.js";

import runtimeJson from './runtime.json';
import runtimeJson from "./runtime.json" with { type: "json" };

const kiotaInstallStatusKey = "kiotaInstallStatus";
const installDelayInMs = 30000; // 30 seconds
const state: { [key: string]: any } = {};
const state: { [key: string]: number | undefined } = {};

let kiotaPath: string | undefined;
const binariesRootDirectory = '.kiotabin';
const binariesRootDirectory = ".kiotabin";
const baseDownloadUrl = "https://github.com/microsoft/kiota/releases/download";

// eslint-disable-next-line @typescript-eslint/naming-convention
const __filename = fileURLToPath(import.meta.url);
// eslint-disable-next-line @typescript-eslint/naming-convention
const __dirname = path.dirname(__filename);

export interface Package {
platformId: string;
sha256: string;
}

const windowsPlatform = 'win';
const osxPlatform = 'osx';
const linuxPlatform = 'linux';
const windowsPlatform = "win";
const osxPlatform = "osx";
const linuxPlatform = "linux";

/**
* Checks if a file or directory exists asynchronously
Expand Down Expand Up @@ -56,7 +62,10 @@ async function isDirectoryEmpty(dirPath: string): Promise<boolean> {
async function runIfNotLocked(action: () => Promise<void>) {
const installStartTimeStamp = state[kiotaInstallStatusKey];
const currentTimeStamp = new Date().getTime();
if (!installStartTimeStamp || (currentTimeStamp - installStartTimeStamp) > installDelayInMs) {
if (
!installStartTimeStamp ||
currentTimeStamp - installStartTimeStamp > installDelayInMs
) {
//locking the context to prevent multiple downloads across multiple instances
//overriding after 30 seconds to prevent stale locks
state[kiotaInstallStatusKey] = currentTimeStamp;
Expand All @@ -73,41 +82,64 @@ export async function ensureKiotaIsPresent() {
if (installPath) {
const runtimeDependencies = getRuntimeDependenciesPackages();
const currentPlatform = getCurrentPlatform();
await ensureKiotaIsPresentInPath(installPath, runtimeDependencies, currentPlatform);
await ensureKiotaIsPresentInPath(
installPath,
runtimeDependencies,
currentPlatform,
);
}
}

export async function ensureKiotaIsPresentInPath(installPath: string, runtimeDependencies: Package[], currentPlatform: string) {
export async function ensureKiotaIsPresentInPath(
installPath: string,
runtimeDependencies: Package[],
currentPlatform: string,
) {
if (installPath) {
if (!await checkFileExists(installPath) || await isDirectoryEmpty(installPath)) {
if (
!(await checkFileExists(installPath)) ||
(await isDirectoryEmpty(installPath))
) {
await runIfNotLocked(async () => {
try {
const packageToInstall = runtimeDependencies.find((p) => p.platformId === currentPlatform);
const packageToInstall = runtimeDependencies.find(
(p) => p.platformId === currentPlatform,
);
if (!packageToInstall) {
throw new Error("Could not find package to install");
}
fs.mkdirSync(installPath, { recursive: true });
const zipFilePath = `${installPath}.zip`;
// If env variable that points to kiota binary zip exists, use it to copy the file instead of downloading it
const kiotaBinaryZip = process.env.KIOTA_SIDELOADING_BINARY_ZIP_PATH;
if (kiotaBinaryZip && await checkFileExists(kiotaBinaryZip)) {
if (kiotaBinaryZip && (await checkFileExists(kiotaBinaryZip))) {
fs.copyFileSync(kiotaBinaryZip, zipFilePath);
} else {
const downloadUrl = getDownloadUrl(currentPlatform);
await downloadFileFromUrl(downloadUrl, zipFilePath);
if (!await doesFileHashMatch(zipFilePath, packageToInstall.sha256)) {
throw new Error("Hash validation of the downloaded file mismatch");
if (
!(await doesFileHashMatch(zipFilePath, packageToInstall.sha256))
) {
throw new Error(
"Hash validation of the downloaded file mismatch",
);
}
}
unzipFile(zipFilePath, installPath);
if ((currentPlatform.startsWith(linuxPlatform) || currentPlatform.startsWith(osxPlatform)) && installPath) {
if (
(currentPlatform.startsWith(linuxPlatform) ||
currentPlatform.startsWith(osxPlatform)) &&
installPath
) {
const fileName = getKiotaFileName();
const kiotaFilePath = path.join(installPath, fileName);
makeExecutable(kiotaFilePath);
}
} catch (error) {
} catch {
fs.rmSync(installPath, { recursive: true, force: true });
throw new Error("Kiota download failed. Check the logs for more information.");
throw new Error(
"Kiota download failed. Check the logs for more information.",
);
}
});
}
Expand Down Expand Up @@ -137,19 +169,25 @@ function getRuntimeVersion(): string {
}

function getKiotaFileName(): string {
return process.platform === 'win32' ? 'kiota.exe' : 'kiota';
return process.platform === "win32" ? "kiota.exe" : "kiota";
}

function getKiotaPathInternal(withFileName = true): string | undefined {
const fileName = getKiotaFileName();
const runtimeDependencies = getRuntimeDependenciesPackages();
const currentPlatform = getCurrentPlatform();
const packageToInstall = runtimeDependencies.find((p) => p.platformId === currentPlatform);
const packageToInstall = runtimeDependencies.find(
(p) => p.platformId === currentPlatform,
);
const baseDir = getBaseDir();
const runtimeVersion = getRuntimeVersion();
if (packageToInstall) {
const installPath = path.join(baseDir, binariesRootDirectory);
const directoryPath = path.join(installPath, runtimeVersion, currentPlatform);
const directoryPath = path.join(
installPath,
runtimeVersion,
currentPlatform,
);
if (withFileName) {
return path.join(directoryPath, fileName);
}
Expand All @@ -163,26 +201,41 @@ function unzipFile(zipFilePath: string, destinationPath: string) {
zip.extractAllTo(destinationPath, true);
}

async function doesFileHashMatch(destinationPath: string, hashValue: string): Promise<boolean> {
const hash = createHash('sha256');
return new Promise((resolve, reject) => {
fs.createReadStream(destinationPath).pipe(hash).on('finish', () => {
const computedValue = hash.digest('hex');
hash.destroy();
resolve(computedValue.toUpperCase() === hashValue.toUpperCase());
});
async function doesFileHashMatch(
destinationPath: string,
hashValue: string,
): Promise<boolean> {
const hash = createHash("sha256");
return new Promise((resolve) => {
fs.createReadStream(destinationPath)
.pipe(hash)
.on("finish", () => {
const computedValue = hash.digest("hex");
hash.destroy();
resolve(computedValue.toUpperCase() === hashValue.toUpperCase());
});
});
}

function downloadFileFromUrl(url: string, destinationPath: string): Promise<void> {
function downloadFileFromUrl(
url: string,
destinationPath: string,
): Promise<void> {
return new Promise((resolve) => {
https.get(url, (response: any) => {
if (response.statusCode && response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
resolve(downloadFileFromUrl(response.headers.location, destinationPath));
https.get(url, (response) => {
if (
response.statusCode &&
response.statusCode >= 300 &&
response.statusCode < 400 &&
response.headers.location
) {
resolve(
downloadFileFromUrl(response.headers.location, destinationPath),
);
} else {
const filePath = fs.createWriteStream(destinationPath);
response.pipe(filePath);
filePath.on('finish', () => {
filePath.on("finish", () => {
filePath.close();
resolve(undefined);
});
Expand All @@ -197,12 +250,19 @@ function getDownloadUrl(platform: string): string {

export function getRuntimeDependenciesPackages(): Package[] {
if (runtimeJson.runtimeDependencies) {
return JSON.parse(JSON.stringify(<Package[]>runtimeJson.runtimeDependencies));
return JSON.parse(
JSON.stringify(<Package[]>runtimeJson.runtimeDependencies),
);
}
throw new Error("No runtime dependencies found");
}

export function getCurrentPlatform(): string {
const binPathSegmentOS = process.platform === 'win32' ? windowsPlatform : process.platform === 'darwin' ? osxPlatform : linuxPlatform;
const binPathSegmentOS =
process.platform === "win32"
? windowsPlatform
: process.platform === "darwin"
? osxPlatform
: linuxPlatform;
return `${binPathSegmentOS}-${process.arch}`;
}
1 change: 1 addition & 0 deletions vscode/packages/npm-package/jest.common.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module.exports = {
testMatch: [
'**/?(*.)+(spec).ts?(x)'
],
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
transform: {
'^.+\\.ts?$': ['ts-jest', {
tsconfig: 'tsconfig.json',
Expand Down
Loading
Loading