Skip to content
Open
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 @@ -63,7 +63,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)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

conflicts with -d of --depth, giving Infinity as value instead of boolean.
dev mode was always enabled.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Think to update the documentation (./docs/cli) for commands such as auto, etc

.option("--developer", i18n.getTokenSync("cli.commands.open.option_developer"), false)
.action(async(spec, options) => {
checkNodeSecureToken();
await commands.scanner.auto(spec, options);
Expand Down
141 changes: 141 additions & 0 deletions esbuild.dev.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// Import Node.js Dependencies
import fsAsync from "node:fs/promises";
import http from "node:http";
import path from "node:path";

// 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 kPublicDir = path.join(import.meta.dirname, "public");
const kOutDir = path.join(import.meta.dirname, "dist");
const kImagesDir = path.join(kPublicDir, "img");
const kNodeModulesDir = path.join(import.meta.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 Promise.all([
i18n.getLocalLang(),
i18n.extendFromSystemPath(path.join(import.meta.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 = await fsAsync.access(kDefaultPayloadPath).then(() => kDefaultPayloadPath, () => undefined);

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

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

htmlWatcher.on("change", async(filePath) => {
await buildContext.rebuild().catch(console.error);
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());

10 changes: 8 additions & 2 deletions src/commands/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import open from "open";
import * as SemVer from "semver";
import * as i18n from "@nodesecure/i18n";
import {
buildServer,
cache,
logger,
buildServer,
WebSocketServerInstanciator
} from "@nodesecure/server";

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
19 changes: 1 addition & 18 deletions workspaces/server/src/ViewBuilder.class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import fs from "node:fs/promises";
// Import Third-party Dependencies
import zup from "zup";
import * as i18n from "@nodesecure/i18n";
import chokidar from "chokidar";
import { globStream } from "glob";

// Import Internal Dependencies
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
3 changes: 1 addition & 2 deletions workspaces/server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 @@ -74,6 +72,7 @@ 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