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
2 changes: 1 addition & 1 deletion bin/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ defaultScannerCommand("from <spec>")
defaultScannerCommand("auto [spec]", { includeOutput: false, strategy: vulnera.strategies.GITHUB_ADVISORY })
.describe(i18n.getTokenSync("cli.commands.auto.desc"))
.option("-k, --keep", i18n.getTokenSync("cli.commands.auto.option_keep"), false)
.option("-d, --developer", i18n.getTokenSync("cli.commands.open.option_developer"), false)
.option("--developer", i18n.getTokenSync("cli.commands.open.option_developer"), false)
.action(async(spec, options) => {
checkNodeSecureToken();
await commands.scanner.auto(spec, options);
Expand Down
143 changes: 143 additions & 0 deletions esbuild.dev.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// Import Node.js Dependencies
import fs from "node:fs";
import fsAsync from "node:fs/promises";
import http from "node:http";
import path from "node:path";
import { fileURLToPath } from "node:url";

// Import Third-party Dependencies
import { getBuildConfiguration } from "@nodesecure/documentation-ui/node";
import * as i18n from "@nodesecure/i18n";
import chokidar from "chokidar";
import esbuild from "esbuild";
import open from "open";
import sirv from "sirv";

// Import Internal Dependencies
import english from "./i18n/english.js";
import french from "./i18n/french.js";
import { context as alsContext } from "./workspaces/server/src/ALS.ts";
import { ViewBuilder } from "./workspaces/server/src/ViewBuilder.class.ts";
import { cache } from "./workspaces/server/src/cache.ts";
import { getApiRouter } from "./workspaces/server/src/endpoints/index.ts";

import { logger } from "./workspaces/server/src/logger.ts";
import { WebSocketServerInstanciator } from "./workspaces/server/src/websocket/index.ts";

// CONSTANTS
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const kPublicDir = path.join(__dirname, "public");
const kOutDir = path.join(__dirname, "dist");
const kImagesDir = path.join(kPublicDir, "img");
const kNodeModulesDir = path.join(__dirname, "node_modules");
const kComponentsDir = path.join(kPublicDir, "components");
const kDefaultPayloadPath = path.join(process.cwd(), "nsecure-result.json");
const kDevPort = Number(process.env.DEV_PORT ?? 8080);

await i18n.getLocalLang();
await i18n.extendFromSystemPath(path.join(__dirname, "i18n"));

const imagesFiles = await fsAsync.readdir(kImagesDir);

await Promise.all([
...imagesFiles
.map((name) => fsAsync.copyFile(path.join(kImagesDir, name), path.join(kOutDir, name))),
fsAsync.copyFile(path.join(kPublicDir, "favicon.ico"), path.join(kOutDir, "favicon.ico"))
]);

const buildContext = await esbuild.context({
entryPoints: [
path.join(kPublicDir, "main.js"),
path.join(kPublicDir, "main.css"),
path.join(kNodeModulesDir, "highlight.js", "styles", "github.css"),
...getBuildConfiguration().entryPoints
],

loader: {
".jpg": "file",
".png": "file",
".woff": "file",
".woff2": "file",
".eot": "file",
".ttf": "file",
".svg": "file"
},
platform: "browser",
bundle: true,
sourcemap: true,
treeShaking: true,
outdir: kOutDir
});

await buildContext.watch();

const { hosts: esbuildHosts, port: esbuildPort } = await buildContext.serve({
servedir: kOutDir
});

const htmlWatcher = chokidar.watch(kComponentsDir, {
persistent: false,
awaitWriteFinish: true,
ignored: (path, stats) => (stats?.isFile() ?? false) && !path.endsWith(".html")
});

const dataFilePath = fs.existsSync(kDefaultPayloadPath) ? kDefaultPayloadPath : undefined;

if (dataFilePath === undefined) {
cache.startFromZero = true;
}

const store = {
i18n: { english: { ui: english.ui }, french: { ui: french.ui } },
viewBuilder: new ViewBuilder({
projectRootDir: __dirname,
componentsDir: kComponentsDir
}),
dataFilePath
};

htmlWatcher.on("change", (filePath) => {
buildContext.rebuild();
store.viewBuilder.freeCache(filePath);
});

const serving = sirv(kOutDir, { dev: true });

function defaultRoute(req: http.IncomingMessage, res: http.ServerResponse) {
if (req.url === "/esbuild") {
const proxyReq = http.request(
{ hostname: esbuildHosts[0], port: esbuildPort, path: req.url, method: req.method, headers: req.headers },
(proxyRes) => {
res.writeHead(proxyRes.statusCode!, proxyRes.headers);
proxyRes.pipe(res);
}
);

proxyReq.on("error", (err) => {
console.error(`[proxy/esbuild] ${err.message}`);
res.writeHead(502);
res.end("Bad Gateway");
});

req.pipe(proxyReq);

return;
}

serving(req, res, () => {
res.writeHead(404);
res.end("Not Found");
});
}

const apiRouter = getApiRouter(defaultRoute);

http.createServer((req, res) => alsContext.run(store, () => apiRouter.lookup(req, res)))
.listen(kDevPort, () => {
console.log(`Dev server: http://localhost:${kDevPort}`);
open(`http://localhost:${kDevPort}`);
});

new WebSocketServerInstanciator({ cache, logger });

console.log("Watching...");
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
"lint-fix": "npm run lint -- --fix",
"prepublishOnly": "rimraf ./dist && npm run build && pkg-ok",
"build": "npm run build:front && npm run build:workspaces",
"build:dev": "npm run build:workspaces && npm run build:front:dev",
"build:front": "node ./esbuild.config.js",
"build:front:dev": "node --experimental-strip-types ./esbuild.dev.config.ts",
"build:workspaces": "npm run build --ws --if-present",
"test": "npm run test:cli && npm run lint && npm run lint:css",
"test:cli": "node --no-warnings --test test/**/*.test.js",
Expand Down
3 changes: 3 additions & 0 deletions public/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -291,3 +291,6 @@ function onSettingsSaved(defaultConfig = null) {
networkView.classList.remove("locked");
});
}

new EventSource("/esbuild").addEventListener("change", () => location.reload());

16 changes: 11 additions & 5 deletions src/commands/http.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
// Import Node.js Dependencies
import crypto from "node:crypto";
import fs from "node:fs";
import path from "node:path";
import crypto from "node:crypto";

// Import Third-party Dependencies
import open from "open";
import * as SemVer from "semver";
import * as i18n from "@nodesecure/i18n";
import {
buildServer,
cache,
logger,
buildServer,
WebSocketServerInstanciator
} from "@nodesecure/server";
import open from "open";
import * as SemVer from "semver";

// Import Internal Dependencies
import english from "../../i18n/english.js";
Expand Down Expand Up @@ -51,9 +51,15 @@ export async function start(
cache.prefix = crypto.randomBytes(4).toString("hex");
}

if (enableDeveloperMode) {
const link = "http://127.0.0.1:8080";
console.log(kleur.magenta().bold(await i18n.getToken("cli.http_server_started")), kleur.cyan().bold(link));
open(link);

return;
}
const httpServer = buildServer(dataFilePath, {
port: httpPort,
hotReload: enableDeveloperMode,
runFromPayload,
projectRootDir: kProjectRootDir,
componentsDir: kComponentsDir,
Expand Down
23 changes: 3 additions & 20 deletions workspaces/server/src/ViewBuilder.class.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
// Import Node.js Dependencies
import path from "node:path";
import fs from "node:fs/promises";
import path from "node:path";

// Import Third-party Dependencies
import zup from "zup";
import * as i18n from "@nodesecure/i18n";
import chokidar from "chokidar";
import { globStream } from "glob";
import zup from "zup";

// Import Internal Dependencies
import { logger } from "./logger.ts";
Expand All @@ -24,31 +23,15 @@ export class ViewBuilder {

constructor(options: ViewBuilderOptions) {
const {
autoReload = false,
projectRootDir,
componentsDir
} = options;

this.projectRootDir = projectRootDir;
this.componentsDir = componentsDir;

if (autoReload) {
this.#enableWatcher();
}
}

async #enableWatcher() {
logger.info("[ViewBuilder] autoReload is enabled");

const watcher = chokidar.watch(this.componentsDir, {
persistent: false,
awaitWriteFinish: true,
ignored: (path, stats) => (stats?.isFile() ?? false) && !path.endsWith(".html")
});
watcher.on("change", (filePath) => this.#freeCache(filePath));
}

async #freeCache(
freeCache(
filePath: string
) {
logger.info("[ViewBuilder] the cache has been released");
Expand Down
10 changes: 8 additions & 2 deletions workspaces/server/src/endpoints/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Import Node.js Dependencies
import http from "node:http";

// Import Third-party Dependencies
import router from "find-my-way";

Expand All @@ -13,9 +16,12 @@ import * as scorecard from "./ossf-scorecard.ts";
import * as locali18n from "./i18n.ts";
import * as report from "./report.ts";

export function getApiRouter() {
type DefaultRoute = (req: http.IncomingMessage, res: http.ServerResponse) => void;

export function getApiRouter(defaultRoute?: DefaultRoute) {
const apiRouter = router({
ignoreTrailingSlash: true
ignoreTrailingSlash: true,
defaultRoute
});

apiRouter.get("/", root.get);
Expand Down
12 changes: 5 additions & 7 deletions workspaces/server/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
// Import Node.js Dependencies
import fs from "node:fs";
import path from "node:path";
import http from "node:http";
import path from "node:path";

// Import Third-party Dependencies
import sirv from "sirv";

// Import Internal Dependencies
import { getApiRouter } from "./endpoints/index.ts";
import { ViewBuilder } from "./ViewBuilder.class.ts";
import {
context,
type AsyncStoreContext,
type NestedStringRecord
} from "./ALS.ts";
import { cache } from "./cache.ts";
import { getApiRouter } from "./endpoints/index.ts";
import { ViewBuilder } from "./ViewBuilder.class.ts";

export interface BuildServerOptions {
hotReload?: boolean;
Expand All @@ -32,15 +32,13 @@ export function buildServer(
options: BuildServerOptions
) {
const {
hotReload = true,
runFromPayload = true,
projectRootDir,
componentsDir,
i18n
} = options;

const viewBuilder = new ViewBuilder({
autoReload: hotReload,
projectRootDir,
componentsDir
});
Expand Down Expand Up @@ -72,8 +70,8 @@ export function buildServer(
}

export { WebSocketServerInstanciator } from "./websocket/index.ts";
export { logger } from "./logger.ts";
export * as config from "./config.ts";

export { getApiRouter } from "./endpoints/index.ts";

export {
cache
Expand Down
Loading