diff --git a/.github/workflows/cache.yml b/.github/workflows/cache.yml index 1476c60a..b04ba417 100644 --- a/.github/workflows/cache.yml +++ b/.github/workflows/cache.yml @@ -33,5 +33,7 @@ jobs: node-version: ${{ matrix.node-version }} - name: Install dependencies run: npm install + - name: Build + run: npm run build --ws --if-present - name: Run tests run: npm run test diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 250d6e86..40bd4bbd 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -30,7 +30,7 @@ jobs: - name: Install dependencies run: npm install - name: Build - run: npm run build + run: npm run build --ws --if-present - name: Run tests run: npm run coverage - name: Send coverage report to Codecov diff --git a/.github/workflows/server.yml b/.github/workflows/server.yml index 334c7268..8647868f 100644 --- a/.github/workflows/server.yml +++ b/.github/workflows/server.yml @@ -33,5 +33,7 @@ jobs: node-version: ${{ matrix.node-version }} - name: Install dependencies run: npm install + - name: Build + run: npm run build --ws --if-present - name: Run tests run: npm run test diff --git a/.gitignore b/.gitignore index d56cc501..3b9a5792 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,9 @@ jspm_packages/ # TypeScript v1 declaration files typings/ +# TypeScript cache +*.tsbuildinfo + # Optional npm cache directory .npm diff --git a/eslint.config.mjs b/eslint.config.mjs index 5e36c2fa..7bb434ca 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,19 +1,9 @@ -import { ESLintConfig, globals } from "@openally/config.eslint"; +// Import Third-party Dependencies +import { ESLintConfig, typescriptConfig, globals } from "@openally/config.eslint"; import jsdoc from "eslint-plugin-jsdoc"; export default [ ...ESLintConfig, - { - rules: { - "no-invalid-this": "off" - }, - languageOptions: { - sourceType: "module", - globals: { - ...globals.browser - } - } - }, { files: ["public/**/*.js"], plugins: { @@ -34,5 +24,16 @@ export default [ "**/coverage/", "**/fixtures/" ] - } + }, + ...typescriptConfig({ + rules: { + "no-invalid-this": "off" + }, + languageOptions: { + sourceType: "module", + globals: { + ...globals.browser + } + } + }) ]; diff --git a/package.json b/package.json index cf29cc48..d9a9615e 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "@nodesecure/size-satisfies": "^1.1.0", "@nodesecure/vis-network": "^1.4.0", "@openally/config.eslint": "^2.1.0", + "@openally/config.typescript": "^1.0.3", "@types/node": "^24.0.3", "c8": "^10.1.2", "cross-env": "^7.0.3", @@ -80,7 +81,9 @@ "rimraf": "^6.0.1", "server-destroy": "^1.0.1", "stylelint": "^16.20.0", - "stylelint-config-standard": "^38.0.0" + "stylelint-config-standard": "^38.0.0", + "tsx": "^4.20.3", + "typescript": "^5.8.3" }, "dependencies": { "@nodesecure/documentation-ui": "^1.3.0", diff --git a/src/commands/http.js b/src/commands/http.js index 4e074ee9..ea5a5eec 100644 --- a/src/commands/http.js +++ b/src/commands/http.js @@ -11,6 +11,10 @@ import * as i18n from "@nodesecure/i18n"; import { buildServer, WebSocketServerInstanciator } from "@nodesecure/server"; import { appCache } from "@nodesecure/cache"; +// Import Internal Dependencies +import english from "../../i18n/english.js"; +import french from "../../i18n/french.js"; + // CONSTANTS const kRequiredScannerRange = ">=5.1.0"; const kProjectRootDir = path.join(import.meta.dirname, "..", ".."); @@ -21,6 +25,7 @@ export async function start( options = {} ) { const port = Number(options.port); + const httpPort = Number.isNaN(port) ? 0 : port; const freshStart = Boolean(options.f); const enableDeveloperMode = Boolean(options.developer); @@ -43,15 +48,19 @@ export async function start( } const httpServer = buildServer(dataFilePath, { - port: Number.isNaN(port) ? 0 : port, + port: httpPort, hotReload: enableDeveloperMode, runFromPayload, projectRootDir: kProjectRootDir, - componentsDir: kComponentsDir + componentsDir: kComponentsDir, + i18n: { + english, + french + } }); - httpServer.listen(port, async() => { - const link = `http://localhost:${port}`; + httpServer.listen(httpPort, async() => { + const link = `http://localhost:${httpServer.server.address().port}`; console.log(kleur.magenta().bold(await i18n.getToken("cli.http_server_started")), kleur.cyan().bold(link)); open(link); diff --git a/tsconfig.base.json b/tsconfig.base.json new file mode 100644 index 00000000..7af87f94 --- /dev/null +++ b/tsconfig.base.json @@ -0,0 +1,6 @@ +{ + "extends": "@openally/config.typescript", + "compilerOptions": { + "composite": true + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..9649c49b --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "files": [], + "references": [ + { + "path": "./workspaces/cache" + }, + { + "path": "./workspaces/server" + } + ] +} diff --git a/workspaces/cache/README.md b/workspaces/cache/README.md index 300583be..9812e16a 100644 --- a/workspaces/cache/README.md +++ b/workspaces/cache/README.md @@ -41,15 +41,15 @@ await appCache.setRootPayload(payload); ## API -### `updateConfig(config)` +### `updateConfig(config: AppConfig): Promise` Stores a new configuration object in the cache. -### `getConfig()` +### `getConfig(): Promise` Retrieves the current configuration object from the cache. -### `updatePayload(pkg, payload)` +### `updatePayload(packageName: string, payload: Payload): void` Saves an analysis payload for a given package. @@ -60,18 +60,18 @@ Saves an analysis payload for a given package. > [!NOTE] > Payloads are stored in the user's home directory under `~/.nsecure/payloads/` -### `getPayload(pkg)` +### `getPayload(packageName: string): Payload` Loads an analysis payload for a given package. **Parameters**: `pkg` (`string`): Package name. -### `availablePayloads()` +### `availablePayloads(): string[]` Lists all available payloads (package names) in the cache. -### `getPayloadOrNull(pkg)` +### `getPayloadOrNull(packageName: string): Payload | null` Loads an analysis payload for a given package, or returns `null` if not found. @@ -81,7 +81,7 @@ Loads an analysis payload for a given package, or returns `null` if not found. Returns `null` if not found. -### `updatePayloadsList(payloadsList)` +### `updatePayloadsList(payloadsList: PayloadsList): Promise` Updates the internal MRU/LRU and available payloads list. @@ -89,11 +89,11 @@ Updates the internal MRU/LRU and available payloads list. - `payloadsList` (`object`): The new payloads list object. -### `payloadsList()` +### `payloadsList(): Promise` Retrieves the current MRU/LRU and available payloads list. -### `initPayloadsList(options = {})` +### `initPayloadsList(options: InitPayloadListOptions = {}): Promise` Initializes the payloads list, optionally resetting the cache. @@ -103,18 +103,18 @@ Initializes the payloads list, optionally resetting the cache. - `logging` (`boolean`, default: `true`): Enable logging. - `reset` (`boolean`, default: `false`): If `true`, reset the cache before initializing. -### `removePayload(pkg)` +### `removePayload(packageName: string): void` Removes a payload for a given package from the cache. **Parameters**: - `pkg` (`string`): Package name. -### `removeLastMRU()` +### `removeLastMRU(): Promise` Removes the least recently used payload if the MRU exceeds the maximum allowed. -### `setRootPayload(payload, options)` +### `setRootPayload(payload: Payload, options: SetRootPayloadOptions = {}): Promise` Sets a new root payload, updates MRU/LRU, and manages cache state. @@ -124,3 +124,38 @@ Sets a new root payload, updates MRU/LRU, and manages cache state. - `options` (`object`): - `logging` (`boolean`, default: `true`): Enable logging. - `local` (`boolean`, default: `false`): Mark the payload as local. + +## Interfaces + +```ts +interface AppConfig { + defaultPackageMenu: string; + ignore: { + flags: Flag[]; + warnings: WarningName[]; + }; + theme?: "light" | "dark"; + disableExternalRequests: boolean; +} + +interface PayloadsList { + mru: string[]; + lru: string[]; + current: string; + availables: string[]; + lastUsed: Record; + root: string | null; +} + +interface LoggingOption { + logging?: boolean; +} + +interface InitPayloadListOptions extends LoggingOption { + reset?: boolean; +} + +interface SetRootPayloadOptions extends LoggingOption { + local?: boolean; +} +``` diff --git a/workspaces/cache/src/index.js b/workspaces/cache/index.ts similarity index 69% rename from workspaces/cache/src/index.js rename to workspaces/cache/index.ts index aba19c67..c8e6dc83 100644 --- a/workspaces/cache/src/index.js +++ b/workspaces/cache/index.ts @@ -8,6 +8,9 @@ import cacache from "cacache"; // Import Internal Dependencies import { logger } from "@nodesecure/server"; +import type { Flag } from "@nodesecure/flags"; +import type { WarningName } from "@nodesecure/js-x-ray"; +import type { Payload } from "@nodesecure/scanner"; // CONSTANTS const kConfigCache = "___config"; @@ -19,6 +22,37 @@ const kSlashReplaceToken = "______"; export const CACHE_PATH = path.join(os.tmpdir(), "nsecure-cli"); export const DEFAULT_PAYLOAD_PATH = path.join(process.cwd(), "nsecure-result.json"); +export interface AppConfig { + defaultPackageMenu: string; + ignore: { + flags: Flag[]; + warnings: WarningName[]; + }; + theme?: "light" | "dark"; + disableExternalRequests: boolean; +} + +export interface PayloadsList { + mru: string[]; + lru: string[]; + current: string; + availables: string[]; + lastUsed: Record; + root: string | null; +} + +export interface LoggingOption { + logging?: boolean; +} + +export interface InitPayloadListOptions extends LoggingOption { + reset?: boolean; +} + +export interface SetRootPayloadOptions extends LoggingOption { + local?: boolean; +} + class _AppCache { prefix = ""; startFromZero = false; @@ -27,30 +61,33 @@ class _AppCache { fs.mkdirSync(kPayloadsPath, { recursive: true }); } - async updateConfig(newValue) { + async updateConfig(newValue: AppConfig) { await cacache.put(CACHE_PATH, kConfigCache, JSON.stringify(newValue)); } - async getConfig() { + async getConfig(): Promise { const { data } = await cacache.get(CACHE_PATH, kConfigCache); return JSON.parse(data.toString()); } - updatePayload(pkg, payload) { - if (pkg.includes(kSlashReplaceToken)) { - throw new Error(`Invalid package name: ${pkg}`); + updatePayload(packageName: string, payload: Payload) { + if (packageName.includes(kSlashReplaceToken)) { + throw new Error(`Invalid package name: ${packageName}`); } - fs.writeFileSync(path.join(kPayloadsPath, pkg.replaceAll("/", kSlashReplaceToken)), JSON.stringify(payload)); + const filePath = path.join(kPayloadsPath, packageName.replaceAll("/", kSlashReplaceToken)); + fs.writeFileSync(filePath, JSON.stringify(payload)); } - getPayload(pkg) { + getPayload(packageName: string): Payload { + const filePath = path.join(kPayloadsPath, packageName.replaceAll("/", kSlashReplaceToken)); + try { - return JSON.parse(fs.readFileSync(path.join(kPayloadsPath, pkg.replaceAll("/", kSlashReplaceToken)), "utf-8")); + return JSON.parse(fs.readFileSync(filePath, "utf-8")); } catch (err) { - logger.error(`[cache|get](pkg: ${pkg}|cache: not found)`); + logger.error(`[cache|get](pkg: ${packageName}|cache: not found)`); throw err; } @@ -62,20 +99,20 @@ class _AppCache { .map((filename) => filename.replaceAll(kSlashReplaceToken, "/")); } - getPayloadOrNull(pkg) { + getPayloadOrNull(packageName: string): Payload | null { try { - return this.getPayload(pkg); + return this.getPayload(packageName); } catch { return null; } } - async updatePayloadsList(payloadsList) { + async updatePayloadsList(payloadsList: PayloadsList) { await cacache.put(CACHE_PATH, `${this.prefix}${kPayloadsCache}`, JSON.stringify(payloadsList)); } - async payloadsList() { + async payloadsList(): Promise { try { const { data } = await cacache.get(CACHE_PATH, `${this.prefix}${kPayloadsCache}`); @@ -88,7 +125,7 @@ class _AppCache { } } - async #initDefaultPayloadsList(options = {}) { + async #initDefaultPayloadsList(options: LoggingOption = {}) { const { logging = true } = options; if (this.startFromZero) { @@ -130,7 +167,7 @@ class _AppCache { this.updatePayload(formatted, payload); } - async initPayloadsList(options = {}) { + async initPayloadsList(options: InitPayloadListOptions = {}) { const { logging = true, reset = false @@ -168,11 +205,12 @@ class _AppCache { })); } - removePayload(pkg) { - fs.rmSync(path.join(kPayloadsPath, pkg.replaceAll("/", kSlashReplaceToken)), { force: true }); + removePayload(packageName: string) { + const filePath = path.join(kPayloadsPath, packageName.replaceAll("/", kSlashReplaceToken)); + fs.rmSync(filePath, { force: true }); } - async removeLastMRU() { + async removeLastMRU(): Promise { const { mru, lastUsed, lru, ...cache } = await this.payloadsList(); if (mru.length < kMaxPayloads) { return { @@ -194,7 +232,7 @@ class _AppCache { }; } - async setRootPayload(payload, options) { + async setRootPayload(payload: Payload, options: SetRootPayloadOptions = {}) { const { logging = true, local = false } = options; const version = Object.keys(payload.dependencies[payload.rootDependencyName].versions)[0]; diff --git a/workspaces/cache/package.json b/workspaces/cache/package.json index 998d7d82..f9a6fdee 100644 --- a/workspaces/cache/package.json +++ b/workspaces/cache/package.json @@ -3,15 +3,21 @@ "version": "1.0.0", "description": "NodeSecure cache module", "type": "module", - "main": "./src/index.js", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", "files": [ - "src" + "dist" ], "scripts": { + "build": "rimraf dist && tsc", + "prepublishOnly": "npm run build", "lint": "eslint src test", - "test": "node --test", + "test": "tsx --test test/index.test.ts", "test:c8": "c8 npm run test" }, "author": "GENTILHOMME Thomas ", - "license": "MIT" + "license": "MIT", + "devDependencies": { + "@types/cacache": "^19.0.0" + } } diff --git a/workspaces/cache/test/index.test.js b/workspaces/cache/test/index.test.ts similarity index 96% rename from workspaces/cache/test/index.test.js rename to workspaces/cache/test/index.test.ts index 86e05cee..73d684d3 100644 --- a/workspaces/cache/test/index.test.js +++ b/workspaces/cache/test/index.test.ts @@ -9,7 +9,7 @@ import os from "node:os"; import cacache from "cacache"; // Import Internal Dependencies -import { appCache } from "../src/index.js"; +import { appCache } from "../index.js"; // CONSTANTS const kPayloadsPath = path.join(os.homedir(), ".nsecure", "payloads"); @@ -26,7 +26,7 @@ describe("appCache", () => { }); it("should update and get config", async() => { - await appCache.updateConfig({ foo: "bar" }); + await appCache.updateConfig({ foo: "bar" } as any); const updated = await appCache.getConfig(); assert.deepEqual(updated, { foo: "bar" }); @@ -40,14 +40,14 @@ describe("appCache", () => { writeValue = value; }); - appCache.updatePayload("foo/bar", { foo: "bar" }); + appCache.updatePayload("foo/bar", { foo: "bar" } as any); assert.equal(writePath, path.join(kPayloadsPath, "foo______bar")); assert.equal(writeValue, JSON.stringify({ foo: "bar" })); }); it("should throw given a package name that contains the slash replace token", () => { - assert.throws(() => appCache.updatePayload("foo______bar", { foo: "bar" }), { + assert.throws(() => appCache.updatePayload("foo______bar", { foo: "bar" } as any), { message: "Invalid package name: foo______bar" }); }); @@ -97,7 +97,7 @@ describe("appCache", () => { }); it("should update and get payloadsList", async() => { - await appCache.updatePayloadsList({ foo: "bar" }); + await appCache.updatePayloadsList({ foo: "bar" } as any); const updated = await appCache.payloadsList(); assert.deepEqual(updated, { foo: "bar" }); @@ -273,7 +273,7 @@ describe("appCache", () => { lastUsed: {}, root: null }); - const payload = { + const payload: any = { rootDependencyName: "test_runner-local", dependencies: { "test_runner-local": { @@ -310,7 +310,7 @@ describe("appCache", () => { lastUsed: {}, root: null }); - const payload = { + const payload: any = { rootDependencyName: "test_runner-local", dependencies: { "test_runner-local": { diff --git a/workspaces/cache/tsconfig.json b/workspaces/cache/tsconfig.json new file mode 100644 index 00000000..07917f19 --- /dev/null +++ b/workspaces/cache/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "./", + "outDir": "./dist" + }, + "exclude": ["test", "dist"] +} diff --git a/workspaces/server/README.md b/workspaces/server/README.md index 8dd8b79f..b19803d1 100644 --- a/workspaces/server/README.md +++ b/workspaces/server/README.md @@ -48,7 +48,7 @@ httpServer.listen(port, async() => { ## API -### `buildServer(dataFilePath, options): polka` +### `buildServer(dataFilePath: string, options: BuildServerOptions): polka` Creates and configures a Polka HTTP server instance for the NodeSecure platform. @@ -66,6 +66,10 @@ If true, the server will use the provided dataFilePath for reading and writing d The root directory of the project, used for serving static files and resolving paths. - `componentsDir` (`string`): Directory containing UI components. + - `i18n` (`object`) + - `english`: `NestedStringRecord` + - `french`: `NestedStringRecord` +The i18n tokens required for the interface. **Returns** - `httpServer` (`object`): @@ -206,3 +210,22 @@ Streams scan progress, payload data, and cache state updates. **Response**: Streams cache state updates after removal. + +## Interfaces + +```ts +type NestedStringRecord = { + [key: string]: string | NestedStringRecord; +}; + +interface BuildServerOptions { + hotReload?: boolean; + runFromPayload?: boolean; + projectRootDir: string; + componentsDir: string; + i18n: { + english: NestedStringRecord; + french: NestedStringRecord; + }; +} +``` diff --git a/workspaces/server/index.js b/workspaces/server/index.ts similarity index 77% rename from workspaces/server/index.js rename to workspaces/server/index.ts index 261c213b..24c2f4ae 100644 --- a/workspaces/server/index.js +++ b/workspaces/server/index.ts @@ -17,20 +17,39 @@ import * as scorecard from "./src/endpoints/ossf-scorecard.js"; import * as locali18n from "./src/endpoints/i18n.js"; import * as report from "./src/endpoints/report.js"; import * as middlewares from "./src/middlewares/index.js"; +import { type BuildContextMiddlewareOptions } from "./src/middlewares/context.js"; import { WebSocketServerInstanciator } from "./src/websocket/index.js"; import { logger } from "./src/logger.js"; -export function buildServer(dataFilePath, options) { +export type NestedStringRecord = { + [key: string]: string | NestedStringRecord; +}; + +export interface BuildServerOptions { + hotReload?: boolean; + runFromPayload?: boolean; + projectRootDir: string; + componentsDir: string; + i18n: { + english: NestedStringRecord; + french: NestedStringRecord; + }; +} + +export function buildServer(dataFilePath: string, options: BuildServerOptions) { const { hotReload = true, runFromPayload = true, projectRootDir, - componentsDir + componentsDir, + i18n } = options; const httpServer = polka(); - const asyncStoreProperties = {}; + const asyncStoreProperties: BuildContextMiddlewareOptions["storeProperties"] = { + i18n + }; if (runFromPayload) { fs.accessSync(dataFilePath, fs.constants.R_OK | fs.constants.W_OK); asyncStoreProperties.dataFilePath = dataFilePath; @@ -40,7 +59,7 @@ export function buildServer(dataFilePath, options) { } httpServer.use( middlewares.buildContextMiddleware({ - hotReload, + autoReload: hotReload, storeProperties: asyncStoreProperties, projectRootDir, componentsDir @@ -63,6 +82,7 @@ export function buildServer(dataFilePath, options) { httpServer.get("/bundle/:pkgName", bundle.get); httpServer.get("/bundle/:pkgName/:version", bundle.get); httpServer.get("/downloads/:pkgName", npmDownloads.get); + // @ts-ignore httpServer.get("/scorecard/:org/:pkgName", scorecard.get); httpServer.post("/report", report.post); diff --git a/workspaces/server/package.json b/workspaces/server/package.json index efdd5e11..f652381d 100644 --- a/workspaces/server/package.json +++ b/workspaces/server/package.json @@ -3,16 +3,24 @@ "version": "1.0.0", "description": "NodeSecure server module", "type": "module", - "main": "./index.js", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", "files": [ - "index.js", - "src" + "dist" ], "scripts": { + "build": "tsc", + "prepublishOnly": "npm run build", "lint": "eslint src test", - "test": "node --test --test-concurrency 1", + "test": "tsx --test --test-concurrency 1 \"./test/**/*.test.ts\"", "test:c8": "c8 npm run test" }, "author": "GENTILHOMME Thomas ", - "license": "MIT" + "license": "MIT", + "devDependencies": { + "@polka/send-type": "^0.5.2", + "@types/polka": "^0.5.7", + "@types/server-destroy": "^1.0.4", + "@types/ws": "^8.18.1" + } } diff --git a/workspaces/server/src/ALS.js b/workspaces/server/src/ALS.js deleted file mode 100644 index 9e87b5a7..00000000 --- a/workspaces/server/src/ALS.js +++ /dev/null @@ -1,4 +0,0 @@ -// Import Node.js Dependencies -import { AsyncLocalStorage } from "node:async_hooks"; - -export const context = new AsyncLocalStorage(); diff --git a/workspaces/server/src/ALS.ts b/workspaces/server/src/ALS.ts new file mode 100644 index 00000000..f0187897 --- /dev/null +++ b/workspaces/server/src/ALS.ts @@ -0,0 +1,7 @@ +// Import Node.js Dependencies +import { AsyncLocalStorage } from "node:async_hooks"; + +// Import Internal Dependencies +import type { AyncStoreContext } from "./middlewares/context.js"; + +export const context = new AsyncLocalStorage(); diff --git a/workspaces/server/src/ViewBuilder.class.js b/workspaces/server/src/ViewBuilder.class.ts similarity index 79% rename from workspaces/server/src/ViewBuilder.class.js rename to workspaces/server/src/ViewBuilder.class.ts index aee57cfb..6e96a562 100644 --- a/workspaces/server/src/ViewBuilder.class.js +++ b/workspaces/server/src/ViewBuilder.class.ts @@ -11,12 +11,18 @@ import { globStream } from "glob"; // Import Internal Dependencies import { logger } from "./logger.js"; +export interface ViewBuilderOptions { + autoReload?: boolean; + projectRootDir: string; + componentsDir: string; +} + export class ViewBuilder { - #cached = null; - projectRootDir = null; - componentsDir = null; + #cached: string | null = null; + projectRootDir: string; + componentsDir: string; - constructor(options) { + constructor(options: ViewBuilderOptions) { const { autoReload = false, projectRootDir, @@ -37,13 +43,13 @@ export class ViewBuilder { const watcher = chokidar.watch(this.componentsDir, { persistent: false, awaitWriteFinish: true, - ignored: (path, stats) => stats?.isFile() && !path.endsWith(".html") + ignored: (path, stats) => (stats?.isFile() ?? false) && !path.endsWith(".html") }); watcher.on("change", (filePath) => this.#freeCache(filePath)); } async #freeCache( - filePath + filePath: string ) { logger.info("[ViewBuilder] the cache has been released"); logger.info(`[ViewBuilder](filePath: ${filePath})`); @@ -61,7 +67,7 @@ export class ViewBuilder { "utf-8" ); - const componentsPromises = []; + const componentsPromises: Promise[] = []; for await ( const htmlComponentPath of globStream("**/*.html", { cwd: this.componentsDir }) ) { @@ -83,17 +89,14 @@ export class ViewBuilder { return HTMLStr; } - /** - * @returns {Promise} - */ - async render() { + async render(): Promise { const i18nLangName = await i18n.getLocalLang(); const HTMLStr = await this.#build(); const templateStr = zup(HTMLStr)({ lang: i18n.getTokenSync("lang"), i18nLangName, - token: (tokenName) => i18n.getTokenSync(`ui.${tokenName}`) + token: (tokenName: string) => i18n.getTokenSync(`ui.${tokenName}`) }); return templateStr; diff --git a/workspaces/server/src/config.js b/workspaces/server/src/config.ts similarity index 81% rename from workspaces/server/src/config.js rename to workspaces/server/src/config.ts index a96fe8b2..9e88f3d6 100644 --- a/workspaces/server/src/config.js +++ b/workspaces/server/src/config.ts @@ -1,12 +1,12 @@ // Import Third-party Dependencies -import { warnings } from "@nodesecure/js-x-ray"; -import { appCache } from "@nodesecure/cache"; +import { warnings, type WarningName } from "@nodesecure/js-x-ray"; +import { appCache, type AppConfig } from "@nodesecure/cache"; // Import Internal Dependencies import { logger } from "./logger.js"; const experimentalWarnings = Object.entries(warnings) - .flatMap(([warning, { experimental }]) => (experimental ? [warning] : [])); + .flatMap(([warning, { experimental }]) => (experimental ? [warning] : [])) as WarningName[]; // CONSTANTS const kDefaultConfig = { @@ -15,15 +15,15 @@ const kDefaultConfig = { disableExternalRequests: false }; -export async function get() { +export async function get(): Promise { try { const config = await appCache.getConfig(); const { defaultPackageMenu, ignore: { - flags, - warnings + flags = [], + warnings = [] } = {}, theme, disableExternalRequests = false @@ -43,7 +43,7 @@ export async function get() { disableExternalRequests }; } - catch (err) { + catch (err: any) { logger.error(`[config|get](error: ${err.message})`); await appCache.updateConfig(kDefaultConfig); @@ -54,14 +54,14 @@ export async function get() { } } -export async function set(newValue) { +export async function set(newValue: AppConfig) { logger.info(`[config|set](config: ${JSON.stringify(newValue)})`); try { await appCache.updateConfig(newValue); logger.info("[config|set](sucess)"); } - catch (err) { + catch (err: any) { logger.error(`[config|set](error: ${err.message})`); throw err; diff --git a/workspaces/server/src/endpoints/bundle.js b/workspaces/server/src/endpoints/bundle.ts similarity index 57% rename from workspaces/server/src/endpoints/bundle.js rename to workspaces/server/src/endpoints/bundle.ts index adc770e6..8788c7b8 100644 --- a/workspaces/server/src/endpoints/bundle.js +++ b/workspaces/server/src/endpoints/bundle.ts @@ -1,16 +1,26 @@ // Import Third-party Dependencies import * as httpie from "@myunisoft/httpie"; import send from "@polka/send-type"; +import type { Request, Response } from "express-serve-static-core"; // CONSTANTS const kBaseBundlePhobiaUrl = "https://bundlephobia.com/api"; -export async function get(req, res) { +interface BundlePhobiaResponse { + gzip: number; + size: number; + dependencySizes: { + approximateSize: number; + name: string; + }[]; +} + +export async function get(req: Request, res: Response) { const { pkgName, version } = req.params; const pkgTemplate = version ? `${pkgName.replaceAll("%2F", "/")}@${version}` : pkgName; try { - const { data } = await httpie.get(`${kBaseBundlePhobiaUrl}/size?package=${pkgTemplate}`); + const { data } = await httpie.get(`${kBaseBundlePhobiaUrl}/size?package=${pkgTemplate}`); const { gzip, size, dependencySizes } = data; return send(res, 200, { @@ -19,7 +29,7 @@ export async function get(req, res) { dependencySizes }); } - catch (error) { + catch (error: any) { return send(res, error.statusCode, { error: error.statusMessage }); } } diff --git a/workspaces/server/src/endpoints/config.js b/workspaces/server/src/endpoints/config.ts similarity index 65% rename from workspaces/server/src/endpoints/config.js rename to workspaces/server/src/endpoints/config.ts index a7650724..d78dc3e7 100644 --- a/workspaces/server/src/endpoints/config.js +++ b/workspaces/server/src/endpoints/config.ts @@ -1,17 +1,18 @@ // Import Third-party Dependencies import send from "@polka/send-type"; +import type { Request, Response } from "express-serve-static-core"; // Import Internal Dependencies import * as config from "../config.js"; import { bodyParser } from "../middlewares/bodyParser.js"; -export async function get(_req, res) { +export async function get(_req: Request, res: Response) { const result = await config.get(); send(res, 200, result); } -export async function save(req, res) { +export async function save(req: Request, res: Response) { const data = await bodyParser(req); await config.set(data); diff --git a/workspaces/server/src/endpoints/data.js b/workspaces/server/src/endpoints/data.ts similarity index 92% rename from workspaces/server/src/endpoints/data.js rename to workspaces/server/src/endpoints/data.ts index 25f9d382..21cf5263 100644 --- a/workspaces/server/src/endpoints/data.js +++ b/workspaces/server/src/endpoints/data.ts @@ -5,6 +5,7 @@ import path from "node:path"; // Import Third-party Dependencies import send from "@polka/send-type"; import { appCache } from "@nodesecure/cache"; +import type { Request, Response } from "express-serve-static-core"; // Import Internal Dependencies import { logger } from "../logger.js"; @@ -12,7 +13,7 @@ import { logger } from "../logger.js"; // CONSTANTS const kDefaultPayloadPath = path.join(process.cwd(), "nsecure-result.json"); -export async function get(_req, res) { +export async function get(_req: Request, res: Response) { if (appCache.startFromZero) { logger.info("[data|get](no content)"); send(res, 204); diff --git a/workspaces/server/src/endpoints/flags.js b/workspaces/server/src/endpoints/flags.ts similarity index 84% rename from workspaces/server/src/endpoints/flags.js rename to workspaces/server/src/endpoints/flags.ts index eb9883c3..875accbc 100644 --- a/workspaces/server/src/endpoints/flags.js +++ b/workspaces/server/src/endpoints/flags.ts @@ -4,6 +4,7 @@ import stream from "node:stream"; // Import Third-party Dependencies import send from "@polka/send-type"; import { getManifest, lazyFetchFlagFile, getFlags } from "@nodesecure/flags"; +import type { Request, Response } from "express-serve-static-core"; // CONSTANTS const kNodeSecureFlags = getFlags(); @@ -12,7 +13,7 @@ export function getAll(_req, res) { send(res, 200, getManifest()); } -export function get(req, res) { +export function get(req: Request, res: Response) { if (req.params.title !== "hasDuplicate" && !kNodeSecureFlags.has(req.params.title)) { return send(res, 404, { error: "Not Found" }); } diff --git a/workspaces/server/src/endpoints/i18n.js b/workspaces/server/src/endpoints/i18n.ts similarity index 55% rename from workspaces/server/src/endpoints/i18n.js rename to workspaces/server/src/endpoints/i18n.ts index 99e254d6..a83812ab 100644 --- a/workspaces/server/src/endpoints/i18n.js +++ b/workspaces/server/src/endpoints/i18n.ts @@ -2,16 +2,17 @@ import send from "@polka/send-type"; // Import Internal Dependencies -import english from "../../../../i18n/english.js"; -import french from "../../../../i18n/french.js"; +import { context } from "../ALS.js"; export async function get(_req, res) { + const { i18n } = context.getStore()!; + send( res, 200, { - english: english.ui, - french: french.ui + english: i18n.english.ui, + french: i18n.french.ui } ); } diff --git a/workspaces/server/src/endpoints/npm-downloads.js b/workspaces/server/src/endpoints/npm-downloads.ts similarity index 71% rename from workspaces/server/src/endpoints/npm-downloads.js rename to workspaces/server/src/endpoints/npm-downloads.ts index 87eb233b..01a468f1 100644 --- a/workspaces/server/src/endpoints/npm-downloads.js +++ b/workspaces/server/src/endpoints/npm-downloads.ts @@ -1,8 +1,9 @@ // Import Third-party Dependencies import { downloads } from "@nodesecure/npm-registry-sdk"; import send from "@polka/send-type"; +import type { Request, Response } from "express-serve-static-core"; -export async function get(req, res) { +export async function get(req: Request, res: Response) { const { pkgName } = req.params; try { @@ -10,7 +11,7 @@ export async function get(req, res) { return send(res, 200, data); } - catch (error) { + catch (error: any) { return send(res, error.statusCode, { error: error.statusMessage }); } } diff --git a/workspaces/server/src/endpoints/ossf-scorecard.js b/workspaces/server/src/endpoints/ossf-scorecard.ts similarity index 65% rename from workspaces/server/src/endpoints/ossf-scorecard.js rename to workspaces/server/src/endpoints/ossf-scorecard.ts index c415677c..8bb99612 100644 --- a/workspaces/server/src/endpoints/ossf-scorecard.js +++ b/workspaces/server/src/endpoints/ossf-scorecard.ts @@ -1,8 +1,18 @@ // Import Third-party Dependencies import * as scorecard from "@nodesecure/ossf-scorecard-sdk"; import send from "@polka/send-type"; +import type { Request, Response } from "express-serve-static-core"; -export async function get(req, res) { +interface Params { + org: string; + pkgName: string; +} + +interface Query { + platform?: "github.com" | "gitlab.com"; +} + +export async function get(req: Request, res: Response) { const { org, pkgName } = req.params; const { platform = "github.com" } = req.query; @@ -17,7 +27,7 @@ export async function get(req, res) { data }); } - catch (error) { + catch (error: any) { return send( res, error.statusCode ?? 404, diff --git a/workspaces/server/src/endpoints/report.js b/workspaces/server/src/endpoints/report.ts similarity index 83% rename from workspaces/server/src/endpoints/report.js rename to workspaces/server/src/endpoints/report.ts index e46d9ac4..e50f82c9 100644 --- a/workspaces/server/src/endpoints/report.js +++ b/workspaces/server/src/endpoints/report.ts @@ -4,6 +4,8 @@ import fs from "node:fs"; // Import Third-party Dependencies import { report } from "@nodesecure/report"; import send from "@polka/send-type"; +import type { Request, Response } from "express-serve-static-core"; +import { appCache } from "@nodesecure/cache"; // Import Internal Dependencies import { context } from "../ALS.js"; @@ -43,11 +45,13 @@ const kReportPayload = { ] }; -export async function post(req, res) { +export async function post(req: Request, res: Response) { const body = await bodyParser(req); const { title, includesAllDeps, theme } = body; - const { dataFilePath } = context.getStore(); - const scannerPayload = JSON.parse(fs.readFileSync(dataFilePath, "utf-8")); + const { dataFilePath } = context.getStore()!; + const scannerPayload = dataFilePath ? + JSON.parse(fs.readFileSync(dataFilePath, "utf-8")) : + appCache.getPayload((await appCache.payloadsList()).current); const reportPayload = structuredClone(kReportPayload); const rootDependencyName = scannerPayload.rootDependencyName; const [organizationPrefixOrRepo, repo] = rootDependencyName.split("/"); diff --git a/workspaces/server/src/endpoints/root.js b/workspaces/server/src/endpoints/root.ts similarity index 64% rename from workspaces/server/src/endpoints/root.js rename to workspaces/server/src/endpoints/root.ts index 7a09c04b..9df6b007 100644 --- a/workspaces/server/src/endpoints/root.js +++ b/workspaces/server/src/endpoints/root.ts @@ -1,21 +1,22 @@ // Import Third-party Dependencies import send from "@polka/send-type"; +import type { Request, Response } from "express-serve-static-core"; // Import Internal Dependencies import { context } from "../ALS.js"; -export async function get(_req, res) { +export async function get(_req: Request, res: Response) { try { res.writeHead(200, { "Content-Type": "text/html" }); - const { viewBuilder } = context.getStore(); + const { viewBuilder } = context.getStore()!; const templateStr = await viewBuilder.render(); res.end(templateStr); } - catch (err) { + catch (err: any) { send(res, 500, { error: err.message }); } } diff --git a/workspaces/server/src/endpoints/search.js b/workspaces/server/src/endpoints/search.ts similarity index 86% rename from workspaces/server/src/endpoints/search.js rename to workspaces/server/src/endpoints/search.ts index 2f221981..1c0ce1b3 100644 --- a/workspaces/server/src/endpoints/search.js +++ b/workspaces/server/src/endpoints/search.ts @@ -1,11 +1,12 @@ // Import Third-party Dependencies import send from "@polka/send-type"; import * as npm from "@nodesecure/npm-registry-sdk"; +import type { Request, Response } from "express-serve-static-core"; // Import Internal Dependencies import { logger } from "../logger.js"; -export async function get(req, res) { +export async function get(req: Request, res: Response) { const { packageName } = req.params; logger.info(`[search|get](packageName: ${packageName}|formatted: ${decodeURIComponent(packageName)})`); @@ -26,7 +27,7 @@ export async function get(req, res) { }); } -export async function versions(req, res) { +export async function versions(req: Request, res: Response) { const { packageName } = req.params; logger.info(`[search|versions](packageName: ${packageName}|formatted: ${decodeURIComponent(packageName)})`); diff --git a/workspaces/server/src/logger.js b/workspaces/server/src/logger.ts similarity index 88% rename from workspaces/server/src/logger.js rename to workspaces/server/src/logger.ts index e313792e..b1bbb188 100644 --- a/workspaces/server/src/logger.js +++ b/workspaces/server/src/logger.ts @@ -1,5 +1,5 @@ // Import Third-party Dependencies -import pino from "pino"; +import { pino } from "pino"; // CONSTANTS const kDefaultLogLevel = "info"; diff --git a/workspaces/server/src/middlewares/bodyParser.js b/workspaces/server/src/middlewares/bodyParser.ts similarity index 68% rename from workspaces/server/src/middlewares/bodyParser.js rename to workspaces/server/src/middlewares/bodyParser.ts index dd8415aa..cbffd063 100644 --- a/workspaces/server/src/middlewares/bodyParser.js +++ b/workspaces/server/src/middlewares/bodyParser.ts @@ -1,10 +1,13 @@ +// Import Third-party Dependencies +import type { Request } from "express-serve-static-core"; + /** * @async * @function bodyParser * @param {*} req * @returns {Promise} */ -export async function bodyParser(req) { +export async function bodyParser(req: Request) { let rawBody = ""; for await (const chunk of req) { rawBody += chunk; diff --git a/workspaces/server/src/middlewares/context.js b/workspaces/server/src/middlewares/context.js deleted file mode 100644 index 398bddbe..00000000 --- a/workspaces/server/src/middlewares/context.js +++ /dev/null @@ -1,23 +0,0 @@ -// Import Internal Dependencies -import { context } from "../ALS.js"; -import { ViewBuilder } from "../ViewBuilder.class.js"; - -export function buildContextMiddleware(options) { - const { - autoReload = false, - storeProperties = {}, - projectRootDir, - componentsDir - } = options; - - const viewBuilder = new ViewBuilder({ - autoReload, - projectRootDir, - componentsDir - }); - - return function addContext(_req, _res, next) { - const store = { ...storeProperties, viewBuilder }; - context.run(store, next); - }; -} diff --git a/workspaces/server/src/middlewares/context.ts b/workspaces/server/src/middlewares/context.ts new file mode 100644 index 00000000..66d8307c --- /dev/null +++ b/workspaces/server/src/middlewares/context.ts @@ -0,0 +1,43 @@ +// Import Third-party Dependencies +import type { Request, Response, NextFunction } from "express-serve-static-core"; + +// Import Internal Dependencies +import { context } from "../ALS.js"; +import { ViewBuilder } from "../ViewBuilder.class.js"; +import type { NestedStringRecord } from "../../index.js"; + +export interface AyncStoreContext { + dataFilePath?: string; + i18n: { + english: NestedStringRecord; + french: NestedStringRecord; + }; + viewBuilder: ViewBuilder; +} + +export interface BuildContextMiddlewareOptions { + autoReload?: boolean; + storeProperties: Omit; + projectRootDir: string; + componentsDir: string; +} + +export function buildContextMiddleware(options: BuildContextMiddlewareOptions) { + const { + autoReload = false, + storeProperties, + projectRootDir, + componentsDir + } = options; + + const viewBuilder = new ViewBuilder({ + autoReload, + projectRootDir, + componentsDir + }); + + return function addContext(_req: Request, _res: Response, next: NextFunction) { + const store = { ...storeProperties, viewBuilder }; + context.run(store, next); + }; +} diff --git a/workspaces/server/src/middlewares/index.js b/workspaces/server/src/middlewares/index.ts similarity index 100% rename from workspaces/server/src/middlewares/index.js rename to workspaces/server/src/middlewares/index.ts diff --git a/workspaces/server/src/middlewares/static.js b/workspaces/server/src/middlewares/static.ts similarity index 65% rename from workspaces/server/src/middlewares/static.js rename to workspaces/server/src/middlewares/static.ts index 38ef5061..68628862 100644 --- a/workspaces/server/src/middlewares/static.js +++ b/workspaces/server/src/middlewares/static.ts @@ -4,7 +4,11 @@ import path from "node:path"; // Import Third-party Dependencies import sirv from "sirv"; -export function addStaticFiles(options) { +export interface AddStaticFilesOptions { + projectRootDir: string; +} + +export function addStaticFiles(options: AddStaticFilesOptions) { const { projectRootDir } = options; diff --git a/workspaces/server/src/websocket/commands/remove.js b/workspaces/server/src/websocket/commands/remove.ts similarity index 83% rename from workspaces/server/src/websocket/commands/remove.js rename to workspaces/server/src/websocket/commands/remove.ts index 54639a6a..25e88fac 100644 --- a/workspaces/server/src/websocket/commands/remove.js +++ b/workspaces/server/src/websocket/commands/remove.ts @@ -1,6 +1,12 @@ +// Import Third-party Dependencies +import type { PayloadsList } from "@nodesecure/cache"; + +// Import Internal Dependencies +import type { WebSocketContext } from "../index.js"; + export async function* remove( - pkg, - context + pkg: string, + context: WebSocketContext ) { const { cache, logger } = context; @@ -8,15 +14,16 @@ export async function* remove( try { const { mru, lru, current, lastUsed, root, availables } = await cache.payloadsList(); + delete lastUsed[pkg]; if (availables.includes(pkg)) { logger.info("[ws|remove] remove from availables"); cache.removePayload(pkg); - const updatedList = { + const updatedList: PayloadsList = { mru, + current, lru, lastUsed: { - ...lastUsed, - [pkg]: void 0 + ...lastUsed }, root, availables: availables.filter((pkgName) => pkgName !== pkg) @@ -59,12 +66,11 @@ export async function* remove( lru.splice(lru.indexOf(olderLruPkg[0]), 1); } - const updatedList = { + const updatedList: PayloadsList = { mru: updatedMru, lru, lastUsed: { - ...lastUsed, - [pkg]: void 0 + ...lastUsed }, current: current === pkg ? updatedMru[0] : current, root, @@ -80,13 +86,12 @@ export async function* remove( else { logger.info("[ws|remove](remove from lru)"); const updatedLru = lru.filter((pkgName) => pkgName !== pkg); - const updatedList = { + const updatedList: PayloadsList = { mru, lru: updatedLru, availables, lastUsed: { - ...lastUsed, - [pkg]: void 0 + ...lastUsed }, current, root @@ -101,7 +106,7 @@ export async function* remove( cache.removePayload(pkg); } - catch (error) { + catch (error: any) { logger.error(`[ws|remove](error: ${error.message})`); logger.debug(error); diff --git a/workspaces/server/src/websocket/commands/search.js b/workspaces/server/src/websocket/commands/search.ts similarity index 83% rename from workspaces/server/src/websocket/commands/search.js rename to workspaces/server/src/websocket/commands/search.ts index 5c34251f..d5b8c0f0 100644 --- a/workspaces/server/src/websocket/commands/search.js +++ b/workspaces/server/src/websocket/commands/search.ts @@ -1,9 +1,13 @@ // Import Third-party Dependencies import * as Scanner from "@nodesecure/scanner"; +import type { PayloadsList } from "@nodesecure/cache"; + +// Import Internal Dependencies +import type { WebSocketContext } from "../index.js"; export async function* search( - pkg, - context + pkg: string, + context: WebSocketContext ) { const { logger, cache } = context; logger.info(`[ws|search](pkg: ${pkg})`); @@ -14,7 +18,7 @@ export async function* search( const cacheList = await cache.payloadsList(); if (cacheList.mru.includes(pkg)) { logger.info(`[ws|search](payload: ${pkg} is already in the MRU)`); - const updatedList = { + const updatedList: PayloadsList = { ...cacheList, current: pkg, lastUsed: { ...cacheList.lastUsed, [pkg]: Date.now() } @@ -24,7 +28,7 @@ export async function* search( if (cache.startFromZero) { yield { - status: "RELOAD", + status: "RELOAD" as const, ...updatedList }; cache.startFromZero = false; @@ -34,7 +38,7 @@ export async function* search( } const { mru, lru, availables, lastUsed, ...updatedCache } = await cache.removeLastMRU(); - const updatedList = { + const updatedList: PayloadsList = { ...updatedCache, mru: [...new Set([...mru, pkg])], current: pkg, @@ -46,7 +50,7 @@ export async function* search( yield cachedPayload; yield { - status: "RELOAD", + status: "RELOAD" as const, ...updatedList }; @@ -57,7 +61,7 @@ export async function* search( // at this point we don't have the payload in cache so we have to scan it. logger.info(`[ws|search](scan ${pkg} in progress)`); - yield { status: "SCAN", pkg }; + yield { status: "SCAN" as const, pkg }; const payload = await Scanner.from(pkg, { maxDepth: 4 }); const name = payload.rootDependencyName; @@ -72,7 +76,7 @@ export async function* search( const { mru, lru, availables, lastUsed, ...appCache } = await cache.removeLastMRU(); mru.push(pkg); cache.updatePayload(pkg, payload); - const updatedList = { + const updatedList: PayloadsList = { ...appCache, mru: [...new Set(mru)], lru, @@ -84,7 +88,7 @@ export async function* search( yield payload; yield { - status: "RELOAD", + status: "RELOAD" as const, ...updatedList }; diff --git a/workspaces/server/src/websocket/index.js b/workspaces/server/src/websocket/index.ts similarity index 71% rename from workspaces/server/src/websocket/index.js rename to workspaces/server/src/websocket/index.ts index 2f00e371..7050d353 100644 --- a/workspaces/server/src/websocket/index.js +++ b/workspaces/server/src/websocket/index.ts @@ -1,13 +1,31 @@ // Import Third-party Dependencies -import { WebSocketServer } from "ws"; +import { WebSocketServer, type WebSocket } from "ws"; import { match } from "ts-pattern"; -import { appCache } from "@nodesecure/cache"; +import { appCache, type PayloadsList } from "@nodesecure/cache"; +import type { Payload } from "@nodesecure/scanner"; // Import Internal Dependencies import { logger } from "../logger.js"; import { search } from "./commands/search.js"; import { remove } from "./commands/remove.js"; +export interface WebSocketContext { + socket: WebSocket; + cache: typeof appCache; + logger: typeof logger; +} + +export type WebSocketMessage = { + action: "SEARCH" | "REMOVE"; + pkg: string; + [key: string]: any; +}; + +type WebSocketResponse = Payload | PayloadsList | { + status: "RELOAD" | "SCAN"; + pkg: string; +}; + export class WebSocketServerInstanciator { constructor() { const websocket = new WebSocketServer({ @@ -16,8 +34,8 @@ export class WebSocketServerInstanciator { websocket.on("connection", this.onConnectionHandler.bind(this)); } - async onConnectionHandler(socket) { - socket.on("message", (rawData) => { + async onConnectionHandler(socket: WebSocket) { + socket.on("message", (rawData: string) => { logger.info(`[ws](message: ${rawData})`); this.onMessageHandler(socket, JSON.parse(rawData)) @@ -29,12 +47,12 @@ export class WebSocketServerInstanciator { } async onMessageHandler( - socket, - message + socket: WebSocket, + message: WebSocketMessage ) { const ctx = { socket, cache: appCache, logger }; - const socketMessages = await match(message.action) + const socketMessages = match(message.action) .with("SEARCH", () => search(message.pkg, ctx)) .with("REMOVE", () => remove(message.pkg, ctx)) .exhaustive(); @@ -78,8 +96,8 @@ export class WebSocketServerInstanciator { } function sendSocketResponse( - socket, - message + socket: WebSocket, + message: WebSocketResponse | null ) { if (message !== null) { socket.send(JSON.stringify(message)); diff --git a/workspaces/server/test/bodyParser.test.js b/workspaces/server/test/bodyParser.test.ts similarity index 94% rename from workspaces/server/test/bodyParser.test.js rename to workspaces/server/test/bodyParser.test.ts index b8b3b444..de6606b6 100644 --- a/workspaces/server/test/bodyParser.test.js +++ b/workspaces/server/test/bodyParser.test.ts @@ -5,7 +5,7 @@ import assert from "node:assert"; // Import Internal Dependencies import { bodyParser } from "../src/middlewares/bodyParser.js"; -function generateFakeReq(headers = {}) { +function generateFakeReq(headers = {}): any { return { headers, async* [Symbol.asyncIterator]() { diff --git a/workspaces/server/test/config.test.js b/workspaces/server/test/config.test.ts similarity index 93% rename from workspaces/server/test/config.test.js rename to workspaces/server/test/config.test.ts index 03d4d2a8..a12a90b0 100644 --- a/workspaces/server/test/config.test.js +++ b/workspaces/server/test/config.test.ts @@ -5,7 +5,7 @@ import assert from "node:assert"; // Import Third-party Dependencies import cacache from "cacache"; import { warnings } from "@nodesecure/js-x-ray"; -import { CACHE_PATH } from "@nodesecure/cache"; +import { AppConfig, CACHE_PATH } from "@nodesecure/cache"; // Import Internal Dependencies import { get, set } from "../src/config.js"; @@ -14,7 +14,7 @@ import { get, set } from "../src/config.js"; const kConfigKey = "___config"; describe("config", () => { - let actualConfig; + let actualConfig: AppConfig; before(async() => { actualConfig = await get(); @@ -63,7 +63,7 @@ describe("config", () => { theme: "galactic", disableExternalRequests: true }; - await set(expectedConfig); + await set(expectedConfig as any); const value = await get(); assert.deepStrictEqual(value, expectedConfig); diff --git a/workspaces/server/test/httpServer.test.js b/workspaces/server/test/httpServer.test.ts similarity index 88% rename from workspaces/server/test/httpServer.test.js rename to workspaces/server/test/httpServer.test.ts index 65f7690b..cd0ad24a 100644 --- a/workspaces/server/test/httpServer.test.js +++ b/workspaces/server/test/httpServer.test.ts @@ -14,6 +14,7 @@ import * as i18n from "@nodesecure/i18n"; import * as flags from "@nodesecure/flags"; import enableDestroy from "server-destroy"; import cacache from "cacache"; +import { type Polka } from "polka"; // Import Internal Dependencies import { buildServer } from "../index.js"; @@ -36,7 +37,7 @@ const kProjectRootDir = path.join(import.meta.dirname, "..", "..", ".."); const kComponentsDir = path.join(kProjectRootDir, "public", "components"); describe("httpServer", { concurrency: 1 }, () => { - let httpServer; + let httpServer: Polka; before(async() => { setGlobalDispatcher(kMockAgent); @@ -45,16 +46,21 @@ describe("httpServer", { concurrency: 1 }, () => { ); httpServer = buildServer(JSON_PATH, { - port: kHttpPort, - openLink: false, - enableWS: false, projectRootDir: kProjectRootDir, - componentsDir: kComponentsDir + componentsDir: kComponentsDir, + i18n: { + english: { + ui: {} + }, + french: { + ui: {} + } + } }); httpServer.listen(kHttpPort); - await once(httpServer.server, "listening"); + await once(httpServer.server!, "listening"); - enableDestroy(httpServer.server); + enableDestroy(httpServer.server!); if (fs.existsSync(kDefaultPayloadPath) === false) { // When running tests on CI, we need to create the nsecure-result.json file @@ -64,7 +70,7 @@ describe("httpServer", { concurrency: 1 }, () => { }, { timeout: 5000 }); after(async() => { - httpServer.server.destroy(); + httpServer.server!.destroy(); kBundlephobiaPool.close(); setGlobalDispatcher(kGlobalDispatcher); }); @@ -78,18 +84,22 @@ describe("httpServer", { concurrency: 1 }, () => { test("'/' should fail", async(ctx) => { class Response { + body: string; + headers: Record; + statusCode: number; + constructor() { this.body = ""; this.headers = {}; this.statusCode = 200; } - end(str) { + end(str: string) { this.body = str; } - writeHead(int) { + writeHead(int: number) { this.statusCode = int; } - getHeader(key) { + getHeader(key: string) { return this.headers[key]; } } @@ -100,8 +110,8 @@ describe("httpServer", { concurrency: 1 }, () => { } ctx.mock.method(Response.prototype, "writeHead", toThrow, { times: 1 }); - const response = new Response(); - await rootEndpoint.get({}, response); + const response: any = new Response(); + await rootEndpoint.get({} as any, response); assert.strictEqual(response.body, JSON.stringify({ error: fakeError })); assert.strictEqual(response.statusCode, 500); @@ -135,10 +145,10 @@ describe("httpServer", { concurrency: 1 }, () => { test("'/flags/description/:title' should fail", async(ctx) => { ctx.mock.method(stream, "pipeline", (_stream, _res, err) => err("fake error")); - const logs = []; - console.error = (data) => logs.push(data); + const logs: string[] = []; + console.error = (data: string) => logs.push(data); - await flagsEndpoint.get({ params: { title: "hasWarnings" } }, ({ writeHead: () => true })); + await flagsEndpoint.get({ params: { title: "hasWarnings" } } as any, ({ writeHead: () => true }) as any); assert.deepEqual(logs, ["fake error"]); }); @@ -262,7 +272,7 @@ describe("httpServer", { concurrency: 1 }, () => { }); test("GET '/i18n' should return i18n", async() => { - const result = await get(new URL("/i18n", kHttpURL)); + const result = await get(new URL("/i18n", kHttpURL)); assert.equal(result.statusCode, 200); const keys = Object.keys(result.data); @@ -270,7 +280,7 @@ describe("httpServer", { concurrency: 1 }, () => { }); test("'/download/:pkgName' should return package downloads", async() => { - const result = await get(new URL("/downloads/fastify", kHttpURL)); + const result = await get(new URL("/downloads/fastify", kHttpURL)); assert.equal(result.statusCode, 200); assert.equal(result.data.package, "fastify"); @@ -292,14 +302,14 @@ describe("httpServer", { concurrency: 1 }, () => { }); test("'/scorecard/:org/:pkgName' should return scorecard data", async() => { - const result = await get(new URL("/scorecard/NodeSecure/cli", kHttpURL)); + const result = await get(new URL("/scorecard/NodeSecure/cli", kHttpURL)); assert.equal(result.statusCode, 200); assert.equal(result.data.data.repo.name, "github.com/NodeSecure/cli"); }); test("'/scorecard/:org/:pkgName' should return scorecard data for GitLab repo", async() => { - const result = await get(new URL("/scorecard/gitlab-org/gitlab-ui?platform=gitlab.com", kHttpURL)); + const result = await get(new URL("/scorecard/gitlab-org/gitlab-ui?platform=gitlab.com", kHttpURL)); assert.equal(result.statusCode, 200); assert.equal(result.data.data.repo.name, "gitlab.com/gitlab-org/gitlab-ui"); @@ -318,7 +328,7 @@ describe("httpServer", { concurrency: 1 }, () => { }); test("'/report' should return a Buffer", async() => { - const result = await post(new URL("/report", kHttpURL), { body: { title: "foo" } }); + const result = await post(new URL("/report", kHttpURL), { body: { title: "foo" } }); assert.equal(result.statusCode, 200); const json = JSON.parse(result.data); @@ -326,7 +336,7 @@ describe("httpServer", { concurrency: 1 }, () => { }); test("'/search' should return the package list", async() => { - const result = await get(new URL("/search/nodesecure", kHttpURL)); + const result = await get(new URL("/search/nodesecure", kHttpURL)); assert.equal(result.statusCode, 200); assert.ok(result.data); @@ -343,7 +353,15 @@ describe("httpServer without options", () => { before(async() => { httpServer = buildServer(JSON_PATH, { projectRootDir: kProjectRootDir, - componentsDir: kComponentsDir + componentsDir: kComponentsDir, + i18n: { + english: { + ui: {} + }, + french: { + ui: {} + } + } }); httpServer.listen(); await once(httpServer.server, "listening"); diff --git a/workspaces/server/tsconfig.json b/workspaces/server/tsconfig.json new file mode 100644 index 00000000..6b088cd8 --- /dev/null +++ b/workspaces/server/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "./", + "outDir": "dist", + }, + "include": ["index.ts", "src"] +}