Skip to content
Merged
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
4 changes: 2 additions & 2 deletions src/commands/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export async function start(
});

httpServer.listen(httpPort, async() => {
const link = `http://localhost:${httpServer.server.address().port}`;
const link = `http://localhost:${httpServer.address().port}`;
console.log(kleur.magenta().bold(await i18n.getToken("cli.http_server_started")), kleur.cyan().bold(link));

open(link);
Expand All @@ -77,7 +77,7 @@ export async function start(

for (const eventName of ["SIGINT", "SIGTERM"]) {
process.on(eventName, () => {
httpServer.server.close();
httpServer.close();

console.log(kleur.red().bold(`${eventName} signal received.`));
process.exit(0);
Expand Down
4 changes: 2 additions & 2 deletions test/helpers/utils.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
export function getExpectedScorecardLines(pkgName, body) {
export function getExpectedScorecardLines(packageName, body) {
const { date, score: scorePkg, checks } = body;

const expectedLines = [
"",
" OSSF Scorecard",
"",
mockScorecardCliLine("Repository", pkgName),
mockScorecardCliLine("Repository", packageName),
mockScorecardCliLine("Scan at", date),
mockScorecardCliLine("Score", scorePkg),
"--------------------------------------------------------------------------------"
Expand Down
130 changes: 77 additions & 53 deletions workspaces/server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ Scorecard](https://api.securityscorecards.dev/projects/github.com/NodeSecure/cli
![size](https://img.shields.io/github/languages/code-size/NodeSecure/server?style=for-the-badge)
[![build](https://img.shields.io/github/actions/workflow/status/NodeSecure/cli/server.yml?style=for-the-badge)](https://github.com/NodeSecure/cli/actions?query=workflow%3A%22server+CI%22)

NodeSecure CLI's http server based on `polka`.
NodeSecure CLI's http and websocket server.

## Requirements

- [Node.js](https://nodejs.org/en/) v20 or higher
- [Node.js](https://nodejs.org/en/) v22 or higher

## Getting Started

Expand Down Expand Up @@ -50,7 +50,7 @@ httpServer.listen(port, async() => {

### `buildServer(dataFilePath: string, options: BuildServerOptions): polka`

Creates and configures a Polka HTTP server instance for the NodeSecure platform.
Creates and configures a Node.js HTTP server instance for the NodeSecure CLI.

**Parameters**
- `dataFilePath` (`string`):
Expand All @@ -73,106 +73,130 @@ The i18n tokens required for the interface.

**Returns**
- `httpServer` (`object`):
A configured **Polka** server instance with all routes and middlewares registered.
A configured Node.js server instance with all routes and middlewares registered.

## API Endpoints

The server exposes the following REST API endpoints:

- `GET /`
### `GET /`

Render and return the main HTML page for the NodeSecure UI.

- `GET /data`
### `GET /data`

Returns the current analysis payload from the cache.

- **204**: No content if running from an empty cache.
- **200**: JSON payload with analysis data.
| Status | Description |
| ------ | ----------- |
| 204 | No content if running from an empty cache |
| 200 | JSON payload with analysis data |

### `GET /config`

- `GET /config`
Fetch the current server configuration.

- `PUT /config`
### `PUT /config`

Update and save the server configuration.

**Body**: JSON configuration object.
| Body | Description |
| ---- | ----------- |
| `config` | JSON configuration object |

### `GET /i18n`

- `GET /i18n`
Returns UI translations for supported languages (English and French).

- `GET /search/:packageName`
### `GET /search/:packageName`

Search for npm packages by name.

**Params**:
- `packageName`: The name (or partial name) of the npm package to search for.
| Param | Description |
| ----- | ----------- |
| `packageName` | The name (or partial name) of the npm package to search for |

**Response**:
- `count`: Number of results.
- `result`: Array of package objects (name, version, description).
| Response Field | Description |
| -------------- | ----------- |
| `count` | Number of results |
| `result` | Array of package objects (name, version, description) |

### `GET /search-versions/:packageName`

- `GET /search-versions/:packageName`
Get all available versions for a given npm package.

**Params**:
- `packageName`: The npm package name.
| Param | Description |
| ----- | ----------- |
| `packageName` | The npm package name |

**Response**:
Array of version strings.
**Response**: Array of version strings.

### `GET /flags`

- `GET /flags`
List all available NodeSecure flags and their metadata.

- `GET /flags/description/:title`
### `GET /flags/description/:title`

Get the HTML description for a specific flag.

**Params**:
- `title`: The flag name.
| Param | Description |
| ----- | ----------- |
| `title` | The flag name |

### `GET /bundle/:packageName`

- `GET /bundle/:pkgName`
Get bundle size information for a package from Bundlephobia.

**Params**:
- `pkgName`: The npm package name.
| Param | Description |
| ----- | ----------- |
| `packageName` | The npm package name |

### `GET /bundle/:packageName/:version`

- `GET /bundle/:pkgName/:version`
Get bundle size information for a specific version of a package from Bundlephobia.

**Params**:
- `pkgName`: The npm package name.
- `version`: The package version.
| Param | Description |
| ----- | ----------- |
| `packageName` | The npm package name |
| `version` | The package version |

### `GET /downloads/:packageName`

- `GET /downloads/:pkgName`
Get npm download statistics for the last week for a package.

**Params**:
- `pkgName`: The npm package name.
| Param | Description |
| ----- | ----------- |
| `packageName` | The npm package name |

### `GET /scorecard/:org/:packageName`

- `GET /scorecard/:org/:pkgName`
Get OSSF Scorecard results for a package repository.

**Params**:
- `org`: The organization or user.
- `pkgName`: The repository name.
| Param | Description |
| ----- | ----------- |
| `org` | The organization or user |
| `packageName` | The repository name |

| Query | Description |
| ----- | ----------- |
| `platform` *(optional)* | The platform (default: `github.com`) |

**Query**:
`platform` (*optional*): The platform (default: `github.com`).
### `POST /report`

- `POST /report`
Generate a PDF report for the current analysis.

**Body**:
- `title`: Report title.
- `includesAllDeps`: Boolean, include all dependencies or only the root.
- `theme`: Report theme.
| Body Field | Description |
| ---------- | ----------- |
| `title` | Report title |
| `includesAllDeps` | Boolean, include all dependencies or only the root |
| `theme` | Report theme |

**Response**:
PDF file as binary data.
**Response**: PDF file as binary data.

### Static Files

All static files (UI, assets, etc.) are served from the project root directory.

> [!NOTE]
> For more details on each endpoint, see the corresponding files in /src/endpoints.

## Websocket commands

The `WebSocketServerInstanciator` class sets up and manages a WebSocket server for real-time communication with NodeSecure clients. It provides live updates and cache management features for package analysis.
Expand Down
8 changes: 3 additions & 5 deletions workspaces/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,18 @@
"author": "GENTILHOMME Thomas <gentilhomme.thomas@gmail.com>",
"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"
"@types/ws": "^8.18.1",
"server-destroy": "1.0.1"
},
"dependencies": {
"@nodesecure/cache": "1.0.0",
"cacache": "20.0.3",
"chokidar": "5.0.0",
"find-my-way": "9.3.0",
"glob": "13.0.0",
"pino": "10.1.0",
"pino-pretty": "13.1.2",
"polka": "0.5.2",
"server-destroy": "1.0.1",
"sirv": "3.0.2",
"ts-pattern": "5.9.0",
"ws": "8.18.3",
Expand Down
17 changes: 15 additions & 2 deletions workspaces/server/src/ALS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@
import { AsyncLocalStorage } from "node:async_hooks";

// Import Internal Dependencies
import type { AyncStoreContext } from "./middlewares/context.ts";
import type { ViewBuilder } from "./ViewBuilder.class.ts";

export const context = new AsyncLocalStorage<AyncStoreContext>();
export type NestedStringRecord = {
[key: string]: string | NestedStringRecord;
};

export interface AsyncStoreContext {
dataFilePath?: string;
i18n: {
english: NestedStringRecord;
french: NestedStringRecord;
};
viewBuilder: ViewBuilder;
}

export const context = new AsyncLocalStorage<AsyncStoreContext>();
34 changes: 27 additions & 7 deletions workspaces/server/src/endpoints/bundle.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
// Import Node.js Dependencies
import type {
IncomingMessage,
ServerResponse
} from "node:http";

// Import Third-party Dependencies
import * as httpie from "@openally/httpie";
import send from "@polka/send-type";
import type { Request, Response } from "express-serve-static-core";

// Import Internal Dependencies
import { send } from "./util/send.ts";

// CONSTANTS
const kBaseBundlePhobiaUrl = "https://bundlephobia.com/api";
Expand All @@ -15,21 +22,34 @@ interface BundlePhobiaResponse {
}[];
}

export async function get(req: Request, res: Response) {
const { pkgName, version } = req.params;
export async function get(
_: IncomingMessage,
res: ServerResponse,
params: Record<string, string | undefined>
) {
const { packageName, version } = params;
if (!packageName) {
return send(res, {
error: "Package name is missing."
}, { code: 400 });
}

const pkgTemplate = version ? `${pkgName.replaceAll("%2F", "/")}@${version}` : pkgName;
const pkgTemplate = version ?
`${packageName.replaceAll("%2F", "/")}@${version}` :
packageName;
try {
const { data } = await httpie.get<BundlePhobiaResponse>(`${kBaseBundlePhobiaUrl}/size?package=${pkgTemplate}`);
const { gzip, size, dependencySizes } = data;

return send(res, 200, {
return send(res, {
gzip,
size,
dependencySizes
});
}
catch (error: any) {
return send(res, error.statusCode, { error: error.statusMessage });
return send(res, { error: error.statusMessage }, {
code: error.statusCode ?? 500
});
}
}
29 changes: 21 additions & 8 deletions workspaces/server/src/endpoints/config.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,33 @@
// Import Node.js Dependencies
import type {
IncomingMessage,
ServerResponse
} from "node:http";

// Import Third-party Dependencies
import send from "@polka/send-type";
import type { Request, Response } from "express-serve-static-core";
import type { AppConfig } from "@nodesecure/cache";

// Import Internal Dependencies
import * as config from "../config.ts";
import { bodyParser } from "../middlewares/bodyParser.ts";
import { bodyParser } from "./util/bodyParser.ts";
import { send } from "./util/send.ts";

export async function get(_req: Request, res: Response) {
export async function get(
_req: IncomingMessage,
res: ServerResponse
) {
const result = await config.get();

send(res, 200, result);
send(res, result);
}

export async function save(req: Request, res: Response) {
const data = await bodyParser(req);
export async function save(
req: IncomingMessage,
res: ServerResponse
) {
const data = await bodyParser<AppConfig>(req);
await config.set(data);

send(res, 204);
res.statusCode = 204;
res.end();
}
Loading