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
5 changes: 5 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
tmp/
node_modules/
log/
samples/
tests/
120 changes: 120 additions & 0 deletions .github/workflows/build-and-publish-docker.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
name: Deploy application

on:
workflow_dispatch:
pull_request:
branches: ['*']
push:
branches: ['*']

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

jobs:
build:
name: Build docker image
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
attestations: write

outputs:
build_image: ${{ steps.output.outputs.build_image }}
build_image_tag: ${{ steps.output.outputs.build_image_tag }}
version: ${{ steps.output.outputs.version }}

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Log in to the Container registry
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

- name: Extract version number
id: package-version
uses: martinbeentjes/[email protected]

- name: Build and publish image
id: push
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
with:
context: .
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.package-version.outputs.current-version }}-build.${{ github.run_number }}
labels: ${{ steps.meta.outputs.labels }}

- name: Store outputs
id: output
run: |
echo "build_image=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}" >> "$GITHUB_OUTPUT"
echo "build_image_tag=${{ steps.package-version.outputs.current-version }}-build.${{ github.run_number }}" >> "$GITHUB_OUTPUT"
echo "version=${{ steps.package-version.outputs.current-version }}" >> "$GITHUB_OUTPUT"

deploy-dev:
name: Deploy to Development
runs-on: ubuntu-latest
needs: build
environment: 'development'
permissions:
contents: read
packages: write
attestations: write
steps:
- name: Deploy to Development
uses: shrink/actions-docker-registry-tag@v4
with:
registry: ghcr.io
repository: ${{ needs.build.outputs.build_image }}
target: ${{ needs.build.outputs.build_image_tag }}
tags: |
${{ needs.build.outputs.version }}-dev

deploy-staging:
name: Deploy to Staging
runs-on: ubuntu-latest
needs: build
environment: 'staging'
permissions:
contents: read
packages: write
attestations: write
steps:
- name: Deploy to Staging
uses: shrink/actions-docker-registry-tag@v4
with:
registry: ghcr.io
repository: ${{ needs.build.outputs.build_image }}
target: ${{ needs.build.outputs.build_image_tag }}
tags: |
${{ needs.build.outputs.version }}-staging

deploy-prod:
name: Deploy to Production
runs-on: ubuntu-latest
needs: build
environment: 'production'
permissions:
contents: read
packages: write
attestations: write
steps:
- name: Deploy to Production
uses: shrink/actions-docker-registry-tag@v4
with:
registry: ghcr.io
repository: ${{ needs.build.outputs.build_image }}
target: ${{ needs.build.outputs.build_image_tag }}
tags: |
${{ needs.build.outputs.version }}
1 change: 1 addition & 0 deletions .puppeteerrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ const { join } = require('path');
module.exports = {
cacheDirectory: join(__dirname, 'node_modules', '.puppeteer-cache')
};

46 changes: 46 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
FROM node:18-slim as base

# Build application
FROM base as build

WORKDIR /app

COPY package.json package-lock.json .puppeteerrc.cjs ./

RUN npm ci --ignore-scripts
RUN npm install highcharts

COPY . .

RUN npm run build

# Create runtime image with only the necessary files
FROM base as runtime

ENV NODE_ENV=production
ENV HIGHCHARTS_CDN_URL=http://127.0.0.1:5000/
ENV OTHER_BROWSER_SHELL_MODE=false

RUN apt update -y && \
apt-get install --no-install-recommends -y ca-certificates fonts-liberation libappindicator3-1 libasound2 libatk-bridge2.0-0 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgbm1 libgcc1 libglib2.0-0 libgtk-3-0 libnspr4 libnss3 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 lsb-release wget xdg-utils && \
rm -rf /var/lib/apt/lists/*

WORKDIR /app

COPY --from=build /app/package.json /app/package-lock.json /app/.puppeteerrc.cjs /app/
RUN npm ci --ignore-scripts && \
npx puppeteer browsers install chrome

COPY --from=build /app/dist/index.esm.mjs /app/dist/index.esm.mjs
COPY --from=build /app/templates /app/templates
COPY --from=build /app/public /app/public
COPY --from=build /app/node_modules/highcharts/*.js /app/static/
COPY --from=build /app/node_modules/highcharts/modules/*.js /app/static/modules/
COPY --from=build /app/node_modules/highcharts/indicators/indicators-all.js /app/static/stock/indicators/
COPY --from=build /app/node_modules/highcharts/modules/map.js /app/static/maps/modules/
COPY --from=build /app/msg /app/msg
COPY --from=build /app/bin/cli.docker.mjs /app/bin/cli.docker.mjs

EXPOSE 8080

ENTRYPOINT [ "node", "./bin/cli.docker.mjs", "--enableServer", "1", "--logLevel", "2", "--port", "8080" ]
111 changes: 111 additions & 0 deletions bin/cli.docker.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#!/usr/bin/env node
/*******************************************************************************

Highcharts Export Server

Copyright (c) 2016-2024, Highsoft

Licenced under the MIT licence.

Additionally a valid Highcharts license is required for use.

See LICENSE file in root for details.

*******************************************************************************/

import main from '../dist/index.esm.mjs';

/**
* The primary function to initiate the server or perform the direct export.
*
* @throws {main.ExportError} Throws an ExportError if no valid options are provided.
* @throws {Error} Throws an Error if an unexpected error occurs during
* execution.
*/
const start = async () => {
try {
// Get the CLI arguments
const args = process.argv;

// Print the usage information if no arguments supplied
if (args.length <= 2) {
main.log(
2,
'[cli] The number of provided arguments is too small. Please refer to the section below.'
);
return main.printUsage();
}

// Set the options, keeping the priority order of setting values:
// 1. Options from the lib/schemas/config.js file
// 2. Options from a custom JSON file (loaded by the --loadConfig argument)
// 3. Options from the environment variables (the .env file)
// 4. Options from the CLI
const options = main.setOptions(null, args);

// If all options correctly parsed
if (options) {
// Print initial logo or text
main.printLogo(options.other.noLogo);

// In this case we want to prepare config manually
if (options.customLogic.createConfig) {
return main.manualConfig(options.customLogic.createConfig);
}

// Start server
if (options.server.enable) {
await main.startModulesServer();

// Init the export mechanism for the server configuration
await main.initExport(options);

// Run the server
await main.startServer(options.server);
} else {
// Perform batch exports
if (options.export.batch) {
// If not set explicitly, use default option for batch exports
if (!args.includes('--minWorkers', '--maxWorkers')) {
options.pool = {
...options.pool,
minWorkers: 2,
maxWorkers: 25
};
}

// Init a pool for the batch exports
await main.initExport(options);

// Start batch exports
await main.batchExport(options);
} else {
// No need for multiple workers in case of a single CLI export
options.pool = {
...options.pool,
minWorkers: 1,
maxWorkers: 1
};

// Init a pool for one export
await main.initExport(options);

// Start a single export
await main.singleExport(options);
}
}
} else {
throw new main.ExportError(
'[cli] No valid options provided. Please check your input and try again.'
);
}
} catch (error) {
// Log the error with stack
main.logWithStack(1, error);

// Gracefully shut down the process
await main.shutdownCleanUp(1);
}
};

start();
4 changes: 2 additions & 2 deletions dist/index.cjs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/index.esm.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/index.esm.js.map

Large diffs are not rendered by default.

9 changes: 7 additions & 2 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ import {
} from './logger.js';
import { initPool, killPool } from './pool.js';
import { shutdownCleanUp } from './resource_release.js';
import server, { startServer } from './server/server.js';
import server, { startServer, startModulesServer } from './server/server.js';
import { printLogo, printUsage } from './utils.js';
import ExportError from './errors/ExportError.js';

/**
* Attaches exit listeners to the process, ensuring proper cleanup of resources
Expand Down Expand Up @@ -115,6 +116,7 @@ export default {
// Server
server,
startServer,
startModulesServer,

// Exporting
initExport,
Expand All @@ -140,5 +142,8 @@ export default {
mapToNewConfig,
manualConfig,
printLogo,
printUsage
printUsage,

// Types
ExportError
};
26 changes: 26 additions & 0 deletions lib/server/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const activeServers = new Map();

// Create express app
const app = express();
const modulesApp = express();

// Disable the X-Powered-By header
app.disable('x-powered-by');
Expand Down Expand Up @@ -78,6 +79,30 @@ const attachServerErrorHandlers = (server) => {
});
};

/**
* Starts an HTTP server used for serving module files. This port should not be exposed externally.
*
* @throws {ExportError} - Throws an error if the server cannot be configured
* and started.
*/
export const startModulesServer = async () => {
try {
const port = 5000;

const httpServer = http.createServer(modulesApp);
httpServer.listen(port, '0.0.0.0');
activeServers.set(port, httpServer);

modulesApp.use(express.static(posix.join(__dirname, 'static')));

log(3, `[server] Started Internal Modules server on localhost:${port}.`);
} catch (error) {
throw new ExportError(
'[server] Could not configure and start the modules server.'
).setError(error);
}
};

/**
* Starts an HTTP server based on the provided configuration. The `serverConfig`
* object contains all server related properties (see the `server` section
Expand Down Expand Up @@ -282,6 +307,7 @@ export const post = (path, ...middlewares) => {

export default {
startServer,
startModulesServer,
closeServers,
getServers,
enableRateLimiting,
Expand Down
5 changes: 5 additions & 0 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ export default {
format: 'es',
sourcemap: true
},
{
file: 'dist/index.esm.mjs',
format: 'es',
sourcemap: true
},
{
file: 'dist/index.cjs',
format: 'cjs',
Expand Down
Loading