From c81f147973b185be8e6067f230982874a923c060 Mon Sep 17 00:00:00 2001 From: David Evans Date: Sat, 28 Oct 2023 22:55:58 +0100 Subject: [PATCH] Use mjs files wherever possible --- README.md | 8 +-- backend/src/basedir.ts | 3 +- backend/src/index.ts | 4 +- docs/SECURITY.md | 2 +- e2e/helpers/downloads.ts | 13 +++-- frontend/babel.config.js | 5 +- frontend/{jest.config.js => jest.config.mjs} | 2 +- .../webpack-loaders/{svgr.js => svgr.mjs} | 12 ++-- .../{webpack.config.js => webpack.config.mjs} | 25 +++++---- package.json | 4 +- scripts/build.sh | 4 +- scripts/{compress.js => compress.mjs} | 29 +++++----- scripts/e2e.sh | 2 +- scripts/lint.js | 55 ------------------- scripts/lint.mjs | 50 +++++++++++++++++ scripts/{mutate-json.js => mutate-json.mjs} | 4 +- .../{random-secrets.js => random-secrets.mjs} | 6 +- 17 files changed, 116 insertions(+), 112 deletions(-) rename frontend/{jest.config.js => jest.config.mjs} (94%) rename frontend/webpack-loaders/{svgr.js => svgr.mjs} (81%) rename frontend/{webpack.config.js => webpack.config.mjs} (80%) rename scripts/{compress.js => compress.mjs} (56%) delete mode 100755 scripts/lint.js create mode 100755 scripts/lint.mjs rename scripts/{mutate-json.js => mutate-json.mjs} (90%) rename scripts/{random-secrets.js => random-secrets.mjs} (83%) diff --git a/README.md b/README.md index 85561c53..1f6b8a16 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ By default: - an in-memory database is used (all data will be lost when the process ends); - blank secrets are used for encryption and password hashing - (you can use `./scripts/random-secrets.js` to generate a set of + (you can use `./scripts/random-secrets.mjs` to generate a set of secure random secrets for a deployment); - Giphy integration is not enabled; - haveibeenpwned integration _is_ enabled; @@ -76,9 +76,9 @@ DB_URL="mongodb://localhost:27017/refacto" \ GIPHY_API_KEY="" \ TRUST_PROXY="false" \ PASSWORD_WORK_FACTOR=10 \ -PASSWORD_SECRET_PEPPER="" \ -ENCRYPTION_SECRET_KEY="" \ -TOKEN_SECRET_PASSPHRASE="" \ +PASSWORD_SECRET_PEPPER="" \ +ENCRYPTION_SECRET_KEY="" \ +TOKEN_SECRET_PASSPHRASE="" \ ./index.js ``` diff --git a/backend/src/basedir.ts b/backend/src/basedir.ts index b0a10131..8139bb92 100644 --- a/backend/src/basedir.ts +++ b/backend/src/basedir.ts @@ -1,3 +1,4 @@ +import { fileURLToPath } from 'node:url'; import { dirname } from 'node:path'; // This file exists to get a consistent directory before @@ -6,4 +7,4 @@ import { dirname } from 'node:path'; // (without this hack, it is impossible to get a consistent // relative directory name in any non-root-directory script) -export const basedir = dirname(new URL(import.meta.url).pathname); +export const basedir = dirname(fileURLToPath(import.meta.url)); diff --git a/backend/src/index.ts b/backend/src/index.ts index 39fd61d1..ca691e3c 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -6,8 +6,8 @@ import { type ConfigT, config } from './config'; import { logError, logInfo } from './log'; // This file exists mainly to enable hot module replacement. -// app.js is the main entry point for the application. -// (changes to index.js will not trigger HMR) +// app.ts is the main entry point for the application. +// (changes to index.ts will not trigger HMR) let activeApp: App | null = null; const server = createServer(); diff --git a/docs/SECURITY.md b/docs/SECURITY.md index b5e6ce7c..5c59723a 100644 --- a/docs/SECURITY.md +++ b/docs/SECURITY.md @@ -108,7 +108,7 @@ The secret key should be 32 random bytes (256 bits) encoded in base16 (hex). You can generate a random key with: ```sh -./scripts/random-secrets.js +./scripts/random-secrets.mjs ``` Non-item data (such as the retro name, settings, and current state) diff --git a/e2e/helpers/downloads.ts b/e2e/helpers/downloads.ts index f1c8bbbd..68ce9cf1 100644 --- a/e2e/helpers/downloads.ts +++ b/e2e/helpers/downloads.ts @@ -1,10 +1,13 @@ -import { dirname, resolve, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { join, dirname } from 'node:path'; import { readFile, mkdir, rm } from 'node:fs/promises'; -export const selfdir = dirname(new URL(import.meta.url).pathname); - -export const downloadDir = resolve( - join(selfdir, '..', '..', 'build', 'downloads'), +export const downloadDir = join( + dirname(fileURLToPath(import.meta.url)), + '..', + '..', + 'build', + 'downloads', ); await rm(downloadDir, { recursive: true, force: true }); await mkdir(downloadDir, { recursive: true }); diff --git a/frontend/babel.config.js b/frontend/babel.config.js index e9aa5b62..5d66c70e 100644 --- a/frontend/babel.config.js +++ b/frontend/babel.config.js @@ -1,4 +1,7 @@ -// this config is only used by Jest - see webpack.config.js for babel config for the build +// This config is only used by Jest - see webpack.config.mjs for babel config for the build + +// This file cannot be .mjs because Jest is unable to load it asynchronously; +// See https://github.com/babel/babel-loader/issues/824 module.exports = { presets: ['@babel/preset-typescript'], diff --git a/frontend/jest.config.js b/frontend/jest.config.mjs similarity index 94% rename from frontend/jest.config.js rename to frontend/jest.config.mjs index 37eccc87..5204d52a 100644 --- a/frontend/jest.config.js +++ b/frontend/jest.config.mjs @@ -1,4 +1,4 @@ -module.exports = { +export default { moduleNameMapper: { '\\.svg$': '/src/test-helpers/svgr.ts', '\\.(less|png|woff2?)$': '/src/test-helpers/resource.ts', diff --git a/frontend/webpack-loaders/svgr.js b/frontend/webpack-loaders/svgr.mjs similarity index 81% rename from frontend/webpack-loaders/svgr.js rename to frontend/webpack-loaders/svgr.mjs index 2277263c..16ad28b9 100644 --- a/frontend/webpack-loaders/svgr.js +++ b/frontend/webpack-loaders/svgr.mjs @@ -1,13 +1,13 @@ -const { normalize } = require('node:path'); -const { callbackify } = require('node:util'); -const { transform } = require('@svgr/core'); -const jsx = require('@svgr/plugin-jsx'); +import { normalize } from 'node:path'; +import { callbackify } from 'node:util'; +import { transform } from '@svgr/core'; +import jsx from '@svgr/plugin-jsx'; // Adapted from https://github.com/gregberge/svgr/blob/main/packages/webpack/src/index.ts#L49 // (avoids heavy @webpack/env dependency) // https://github.com/gregberge/svgr/issues/900 -module.exports = function svgrLoader(contents) { +export default function svgrLoader(contents) { this.cacheable?.(); const callback = this.async(); const options = this.getOptions(); @@ -39,4 +39,4 @@ module.exports = function svgrLoader(contents) { tranformSvg(String(result), options, state, callback); }); } -}; +} diff --git a/frontend/webpack.config.js b/frontend/webpack.config.mjs similarity index 80% rename from frontend/webpack.config.js rename to frontend/webpack.config.mjs index 5509f3ff..333ae1ca 100644 --- a/frontend/webpack.config.js +++ b/frontend/webpack.config.mjs @@ -1,9 +1,12 @@ -const HtmlWebpackPlugin = require('html-webpack-plugin'); -const CopyWebpackPlugin = require('copy-webpack-plugin'); -const MiniCssExtractPlugin = require('mini-css-extract-plugin'); -const CssMinimizerWebpackPlugin = require('css-minimizer-webpack-plugin'); -const TerserWebpackPlugin = require('terser-webpack-plugin'); -const { join } = require('node:path'); +import { fileURLToPath } from 'node:url'; +import { join, dirname } from 'node:path'; +import HtmlWebpackPlugin from 'html-webpack-plugin'; +import CopyWebpackPlugin from 'copy-webpack-plugin'; +import MiniCssExtractPlugin from 'mini-css-extract-plugin'; +import CssMinimizerWebpackPlugin from 'css-minimizer-webpack-plugin'; +import TerserWebpackPlugin from 'terser-webpack-plugin'; + +const basedir = dirname(fileURLToPath(import.meta.url)); const babelLoader = { loader: 'babel-loader', @@ -20,19 +23,19 @@ const lessLoader = { }; const svgrLoader = { - loader: join(__dirname, 'webpack-loaders', 'svgr.js'), + loader: join(basedir, 'webpack-loaders', 'svgr.mjs'), options: { titleProp: true }, }; -module.exports = (env, argv) => ({ +export default (env, argv) => ({ target: 'web', - context: __dirname, + context: basedir, devtool: argv.mode === 'production' ? undefined : 'source-map', entry: { index: './src/index.tsx', }, output: { - path: join(__dirname, 'build'), + path: join(basedir, 'build'), publicPath: '/', filename: '[name].[contenthash:8].js', assetModuleFilename: '[name].[contenthash:8][ext][query]', @@ -115,7 +118,7 @@ module.exports = (env, argv) => ({ devServer: { port: process.env.PORT || 5000, host: 'localhost', - static: join(__dirname, 'resources', 'static'), + static: join(basedir, 'resources', 'static'), historyApiFallback: true, hot: false, liveReload: false, diff --git a/package.json b/package.json index 53bfdb48..b111daf1 100644 --- a/package.json +++ b/package.json @@ -6,9 +6,9 @@ "build": "SKIP_E2E_DEPS=true scripts/install.sh && scripts/build.sh", "clean": "scripts/clean.sh", "install": "scripts/install.sh --force", - "lint": "scripts/install.sh && scripts/lint.js", + "lint": "scripts/install.sh && scripts/lint.mjs", "start": "SKIP_E2E_DEPS=true scripts/install.sh && scripts/start.sh", - "test": "scripts/install.sh && scripts/lint.js && scripts/test.sh", + "test": "scripts/install.sh && scripts/lint.mjs && scripts/test.sh", "format": "npm --prefix=backend run format --quiet && npm --prefix=frontend run format --quiet && npm --prefix=e2e run format --quiet", "test:backend": "npm --prefix=backend test --quiet --", "test:frontend": "npm --prefix=frontend test --quiet --", diff --git a/scripts/build.sh b/scripts/build.sh index 72423a27..fcd6182b 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -63,7 +63,7 @@ cp -R "$BASEDIR/frontend/build" "$BUILDDIR/static"; chmod +x "$BUILDDIR/index.js"; echo 'Compressing static resources...'; -"$BASEDIR/scripts/compress.js" "$BUILDDIR/static"; +"$BASEDIR/scripts/compress.mjs" "$BUILDDIR/static"; if [ "$PRESERVE_NODE_MODULES" == 'true' ]; then echo 'Restoring node_modules...'; @@ -73,7 +73,7 @@ fi; echo 'Generating package.json...'; < "$BASEDIR/backend/package.json" \ grep -v '"file:' \ - | "$BASEDIR/scripts/mutate-json.js" \ + | "$BASEDIR/scripts/mutate-json.mjs" \ 'name="refacto-app"' \ 'scripts={"start": "./index.js"}' \ 'optionalDependencies=' \ diff --git a/scripts/compress.js b/scripts/compress.mjs similarity index 56% rename from scripts/compress.js rename to scripts/compress.mjs index 3fdd43bf..c033647a 100755 --- a/scripts/compress.js +++ b/scripts/compress.mjs @@ -1,15 +1,14 @@ #!/usr/bin/env node -const fs = require('fs'); -const path = require('path'); -const zlib = require('zlib'); -const { promisify } = require('util'); +import { readFile, writeFile } from 'node:fs/promises'; +import { statSync, readdirSync } from 'node:fs'; +import { brotliCompress, gzip, constants } from 'node:zlib'; +import { join } from 'node:path'; +import { promisify } from 'node:util'; -const readFile = promisify(fs.readFile); -const writeFile = promisify(fs.writeFile); -const brotliCompress = promisify(zlib.brotliCompress); -const gzipCompress = promisify(zlib.gzip); -const CONST = zlib.constants; +const asyncBrotliCompress = promisify(brotliCompress); +const asyncGzipCompress = promisify(gzip); +const CONST = constants; const root = process.argv[2]; if (!root) { @@ -18,10 +17,10 @@ if (!root) { const OVERHEAD = 300; -Promise.all(findFiles(root).map(async (file) => { +await Promise.all(findFiles(root).map(async (file) => { const raw = await readFile(file); - const brotli = await brotliCompress(raw, { + const brotli = await asyncBrotliCompress(raw, { params: { [CONST.BROTLI_PARAM_QUALITY]: CONST.BROTLI_MAX_QUALITY, [CONST.BROTLI_PARAM_SIZE_HINT]: raw.length, @@ -31,7 +30,7 @@ Promise.all(findFiles(root).map(async (file) => { await writeFile(file + '.br', brotli); } - const gzip = await gzipCompress(raw, { + const gzip = await asyncGzipCompress(raw, { level: CONST.Z_BEST_COMPRESSION, }); if (gzip.length + OVERHEAD < raw.length) { @@ -46,11 +45,11 @@ function findFiles(p) { } function findFilesR(p, output) { - const stat = fs.statSync(p); + const stat = statSync(p); if (stat.isDirectory()) { - const contents = fs.readdirSync(p); + const contents = readdirSync(p); contents.forEach((file) => { - findFilesR(path.join(p, file), output); + findFilesR(join(p, file), output); }); } else if (stat.isFile()) { output.push(p); diff --git a/scripts/e2e.sh b/scripts/e2e.sh index ee5d9847..f5619c0b 100755 --- a/scripts/e2e.sh +++ b/scripts/e2e.sh @@ -31,7 +31,7 @@ if [ -z "$TARGET_HOST" ]; then export SSO_GOOGLE_TOKEN_INFO_URL="$MOCK_SSO_HOST/tokeninfo"; echo 'Using randomised secrets'; - export $("$BASEDIR/scripts/random-secrets.js" | tee /dev/stderr | xargs); + export $("$BASEDIR/scripts/random-secrets.mjs" | tee /dev/stderr | xargs); # TODO replace express with something else to be able to add --disallow-code-generation-from-strings # TODO once https://github.com/nodejs/node/issues/50452 is resolved, add NODE_OPTIONS='--experimental-policy="'"$BUILDDIR/policy.json"'"' diff --git a/scripts/lint.js b/scripts/lint.js deleted file mode 100755 index 6151c15a..00000000 --- a/scripts/lint.js +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env node - -const path = require('path'); -const util = require('util'); -const childProcess = require('child_process'); -const execFile = util.promisify(childProcess.execFile); - -const packages = ['frontend', 'backend', 'e2e']; - -const baseDir = path.join(__dirname, '..'); - -(async () => { - process.stdout.write('Linting...\n'); - - const prettierCommand = 'npm'; - const prettierArgs = ['run', 'lint:prettier', '--quiet', '--']; - - const tscCommand = 'npm'; - const tscArgs = ['run', 'lint:tsc', '--quiet', '--']; - - if (process.stdout.isTTY) { - tscArgs.push('--pretty'); - } - - const failures = await Promise.all(packages.map(async (package) => { - try { - await execFile(tscCommand, tscArgs, { - cwd: path.join(baseDir, package), - stdio: ['ignore', 'pipe', 'inherit'], - }); - await execFile(prettierCommand, prettierArgs, { - cwd: path.join(baseDir, package), - stdio: ['ignore', 'pipe', 'inherit'], - }); - process.stderr.write(`Lint ${package} succeeded\n`); - return false; - } catch (err) { - process.stderr.write(`Lint ${package} failed:\n\n`); - process.stderr.write(err.stdout); - if (err.stderr) { - process.stderr.write('\n'); - process.stderr.write(err.stderr); - } - process.stderr.write('\n\n'); - return true; - } - })); - - if (failures.some((failure) => failure)) { - process.stdout.write('Linting failed\n'); - process.exit(1); - } - - process.stdout.write('Linting successful\n'); -})(); diff --git a/scripts/lint.mjs b/scripts/lint.mjs new file mode 100755 index 00000000..227d20a3 --- /dev/null +++ b/scripts/lint.mjs @@ -0,0 +1,50 @@ +#!/usr/bin/env node + +import { fileURLToPath } from 'node:url'; +import { join, dirname } from 'node:path'; +import { promisify } from 'node:util'; +import { execFile } from 'node:child_process'; +const asyncExecFile = promisify(execFile); + +const packages = ['frontend', 'backend', 'e2e']; + +const basedir = join(dirname(fileURLToPath(import.meta.url)), '..'); + +process.stdout.write('Linting...\n'); + +const prettierCommand = 'npm'; +const prettierArgs = ['run', 'lint:prettier', '--quiet', '--']; + +const tscCommand = 'npm'; +const tscArgs = ['run', 'lint:tsc', '--quiet', '--']; + +if (process.stdout.isTTY) { + tscArgs.push('--pretty'); +} + +const failures = await Promise.all(packages.map(async (pkg) => { + try { + const cwd = join(basedir, pkg); + const stdio = ['ignore', 'pipe', 'inherit']; + await asyncExecFile(tscCommand, tscArgs, { cwd, stdio }); + await asyncExecFile(prettierCommand, prettierArgs, { cwd, stdio }); + process.stderr.write(`Lint ${pkg} succeeded\n`); + return false; + } catch (err) { + process.stderr.write(`Lint ${pkg} failed:\n\n`); + process.stderr.write(err.stdout); + if (err.stderr) { + process.stderr.write('\n'); + process.stderr.write(err.stderr); + } + process.stderr.write('\n\n'); + return true; + } +})); + +if (failures.some((failure) => failure)) { + process.stdout.write('Linting failed\n'); + process.exit(1); +} + +process.stdout.write('Linting successful\n'); diff --git a/scripts/mutate-json.js b/scripts/mutate-json.mjs similarity index 90% rename from scripts/mutate-json.js rename to scripts/mutate-json.mjs index 2ac5a26a..6e60a257 100755 --- a/scripts/mutate-json.js +++ b/scripts/mutate-json.mjs @@ -1,6 +1,6 @@ #!/usr/bin/env node -const fs = require('fs'); +import { readFileSync } from 'node:fs'; function mutate(input = {}, path, value) { if (!path.length) { @@ -35,7 +35,7 @@ function mutateAll(input, commands) { return current; } -const sourceJson = JSON.parse(fs.readFileSync(0, 'utf-8')); +const sourceJson = JSON.parse(readFileSync(0, 'utf-8')); const resultJson = mutateAll(sourceJson, process.argv.slice(2)); diff --git a/scripts/random-secrets.js b/scripts/random-secrets.mjs similarity index 83% rename from scripts/random-secrets.js rename to scripts/random-secrets.mjs index 75ceafac..a16e645d 100755 --- a/scripts/random-secrets.js +++ b/scripts/random-secrets.mjs @@ -1,6 +1,6 @@ #!/usr/bin/env node -const crypto = require('crypto'); +import { randomBytes } from 'node:crypto'; const ASCII62 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; @@ -9,7 +9,7 @@ function makeRandomAscii(length) { const limit = Math.floor(256 / alphabet.length) * alphabet.length; return new Array(length).fill(0).map(() => { while (true) { - const v = crypto.randomBytes(1)[0]; + const v = randomBytes(1)[0]; if (v < limit) { return alphabet[v % alphabet.length]; } @@ -18,7 +18,7 @@ function makeRandomAscii(length) { } function makeRandomKey(bytes) { - return crypto.randomBytes(bytes).toString('hex'); + return randomBytes(bytes).toString('hex'); } process.stdout.write('PASSWORD_SECRET_PEPPER=' + makeRandomAscii(48) + '\n');