diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..42bcb1b4 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +node_modules +.git +.github +.env +.env.* +!.env.example +public/js +public/css +coverage +*.log diff --git a/Dockerfile b/Dockerfile index b89c0434..07f9dc87 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,11 @@ -FROM node:20-alpine AS build-stage +FROM node:22-alpine AS build-stage WORKDIR /app # set CI environment variable for pnpm ENV CI=true +# ESLint flat config is ESM; babel-eslint-parser needs this on Node 22 +ENV NODE_OPTIONS=--experimental-require-module # install build dependencies and CA certificates RUN apk add --no-cache \ @@ -12,7 +14,7 @@ RUN apk add --no-cache \ # install application dependencies COPY package.json pnpm-lock.yaml ./ -RUN corepack enable && pnpm install --frozen-lockfile +RUN corepack enable && corepack prepare pnpm@9.15.9 --activate && pnpm install --frozen-lockfile # copy in application source COPY . . @@ -30,7 +32,7 @@ RUN pnpm build RUN pnpm install --frozen-lockfile --prod # copy built application to runtime image -FROM node:20-alpine +FROM node:22-alpine WORKDIR /app # install CA certificates in runtime image diff --git a/package.json b/package.json index 0b27a6ee..16900841 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "start": "./bin/www", "start-dev": "nodemon --ignore public/ ./bin/www | pino-pretty", "jest": "env $(grep -v '^#' .env.example | grep -v '^$' | tr \"\\\\n\" \" \") NODE_OPTIONS=--experimental-vm-modules jest", - "lint": "eslint", + "lint": "NODE_OPTIONS=--experimental-require-module eslint", "lint-fix": "eslint --fix", "test": "pnpm run lint && pnpm run jest", "test:smtp": "node test-smtp-config.js", @@ -85,7 +85,7 @@ "@babel/runtime": "^7.25.0", "@marsidev/react-turnstile": "^1.0.2", "@reduxjs/toolkit": "^2.2.7", - "@steemit/steem-js": "^1.0.11", + "@steemit/steem-js": "^1.0.16", "nodemailer": "^6.9.8", "body-parser": "^1.20.2", "express": "^4.19.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5dc9c8c4..33212a22 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,8 +18,8 @@ importers: specifier: ^2.2.7 version: 2.11.2(react-redux@9.2.0(@types/react@18.3.27)(react@18.3.1)(redux@5.0.1))(react@18.3.1) '@steemit/steem-js': - specifier: ^1.0.11 - version: 1.0.11 + specifier: ^1.0.16 + version: 1.0.16 body-parser: specifier: ^1.20.2 version: 1.20.4 @@ -1403,8 +1403,8 @@ packages: '@standard-schema/utils@0.3.0': resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} - '@steemit/steem-js@1.0.11': - resolution: {integrity: sha512-GLNedYF2GPNUWd7ZMCMs8Ir9fwo/Dka39FaEgb5vaeMPwZyBQJY4VSRTsCVMvj357vVdDJ+EWQ183vjOSAV1CQ==} + '@steemit/steem-js@1.0.16': + resolution: {integrity: sha512-uaN/2fEwvOjt4bFdbeJmj0UMJxssateZap/3OQYLghvIFyCpN3pUVp9ZnXZjHWwdglPg5gKCALOjcPzmqE0Qew==} engines: {node: '>=20.19.0'} '@types/babel__core@7.20.5': @@ -6763,7 +6763,7 @@ snapshots: '@standard-schema/utils@0.3.0': {} - '@steemit/steem-js@1.0.11': + '@steemit/steem-js@1.0.16': dependencies: '@noble/ciphers': 2.1.1 '@noble/hashes': 2.0.1 diff --git a/webpack/empty-undici.js b/webpack/empty-undici.js new file mode 100644 index 00000000..c5b9637c --- /dev/null +++ b/webpack/empty-undici.js @@ -0,0 +1,4 @@ +// Stub for steem-js browser bundle: node:undici is only used server-side with custom TLS. +export class Agent { + constructor() {} +} diff --git a/webpack/makeConfig.js b/webpack/makeConfig.js index a1918bbc..1c2078d6 100644 --- a/webpack/makeConfig.js +++ b/webpack/makeConfig.js @@ -28,11 +28,14 @@ function makePlugins(options) { let plugins = [ new webpack.DefinePlugin({ - 'process.env.NODE_ENV': - isDevelopment - ? JSON.stringify('development') - : JSON.stringify('production'), + 'process.env.NODE_ENV': isDevelopment + ? JSON.stringify('development') + : JSON.stringify('production'), }), + new webpack.NormalModuleReplacementPlugin( + /^node:undici$/, + join(__dirname, 'empty-undici.js') + ), new WebpackManifestPlugin({ fileName: '../manifest.json', publicPath: '/js/', @@ -109,10 +112,7 @@ function makeStyleLoaders(options) { return [ { test: /\.css$/, - use: [ - 'style-loader', - 'css-loader', - ], + use: ['style-loader', 'css-loader'], }, { test: /\.less$/, @@ -136,10 +136,7 @@ function makeStyleLoaders(options) { return [ { test: /\.css$/, - use: [ - MiniCssExtractPlugin.loader, - 'css-loader', - ], + use: [MiniCssExtractPlugin.loader, 'css-loader'], }, { test: /\.less$/, @@ -161,7 +158,7 @@ function makeStyleLoaders(options) { } function makeConfig(options) { - if (!options) options = {} + if (!options) options = {}; _defaults(options, DEFAULTS); const isDevelopment = options.isDevelopment; @@ -174,15 +171,18 @@ function makeConfig(options) { }, mode: isDevelopment ? 'development' : 'production', entry: { - app: ( - isDevelopment ? ['webpack-hot-middleware/client?reload=true'] : [] + app: (isDevelopment + ? ['webpack-hot-middleware/client?reload=true'] + : [] ).concat([join(options.baseDir, 'src/index.js')]), }, output: { path: join(options.baseDir, '/public/js'), publicPath: '/js', filename: isDevelopment ? '[name].js' : '[name].[chunkhash:5].js', - chunkFilename: isDevelopment ? '[id].chunk.js' : '[id].chunk.[chunkhash:5].js', + chunkFilename: isDevelopment + ? '[id].chunk.js' + : '[id].chunk.[chunkhash:5].js', clean: true, }, plugins: makePlugins(options), @@ -198,17 +198,14 @@ function makeConfig(options) { use: { loader: 'babel-loader', options: { - plugins: [ - isDevelopment && ReactRefreshBabel, - ].filter(Boolean), + plugins: [isDevelopment && ReactRefreshBabel].filter(Boolean), }, }, }, { test: /\.json$/, type: 'json', - parser: { - }, + parser: {}, }, { test: /\.(eot|ttf|woff|woff2)(\?.+)?$/,