Skip to content

Commit cb42e57

Browse files
authored
Webpack (#824)
* initial webpack support Signed-off-by: James Talton <[email protected]> * webpack work Signed-off-by: James Talton <[email protected]> * fix Signed-off-by: James Talton <[email protected]> * cleanup Signed-off-by: James Talton <[email protected]> * fix Signed-off-by: James Talton <[email protected]> * cleanup Signed-off-by: James Talton <[email protected]> * work Signed-off-by: James Talton <[email protected]> * fix Signed-off-by: James Talton <[email protected]> * fixes Signed-off-by: James Talton <[email protected]> * work Signed-off-by: James Talton <[email protected]> * work Signed-off-by: James Talton <[email protected]> * hot module replacement Signed-off-by: James Talton <[email protected]> * proxies Signed-off-by: James Talton <[email protected]> * types Signed-off-by: James Talton <[email protected]> * tsconfig Signed-off-by: James Talton <[email protected]> * fix Signed-off-by: James Talton <[email protected]> * work Signed-off-by: James Talton <[email protected]> * hot reload fix Signed-off-by: James Talton <[email protected]> * update packages Signed-off-by: James Talton <[email protected]> * fixes Signed-off-by: James Talton <[email protected]> * fix Signed-off-by: James Talton <[email protected]> * updates Signed-off-by: James Talton <[email protected]> * work Signed-off-by: James Talton <[email protected]> * webpack working Signed-off-by: James Talton <[email protected]> * fix output directory Signed-off-by: James Talton <[email protected]> * eslint fix Signed-off-by: James Talton <[email protected]> * fix Signed-off-by: James Talton <[email protected]> * fix Signed-off-by: James Talton <[email protected]> * fixes Signed-off-by: James Talton <[email protected]> * fix file serve Signed-off-by: James Talton <[email protected]> * fix test Signed-off-by: James Talton <[email protected]> * fix production paths Signed-off-by: James Talton <[email protected]> * cleanup Signed-off-by: James Talton <[email protected]>
1 parent d38a36f commit cb42e57

File tree

196 files changed

+2980
-6820
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

196 files changed

+2980
-6820
lines changed

.gitignore

-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
**/certs/
44
**/node_modules/
55
**/coverage/
6-
**/lib/
76
**/dist/
87
**/build/
98
**/.eslintcache

.npmrc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
engine-strict = true

@open-cluster-management/resources/package.json

-34
This file was deleted.

@open-cluster-management/resources/tsconfig.json

-8
This file was deleted.

Dockerfile.prow

+22-8
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,33 @@
11
# Copyright Contributors to the Open Cluster Management project
2-
3-
FROM registry.ci.openshift.org/open-cluster-management/builder:nodejs14-linux as builder
2+
FROM registry.ci.openshift.org/open-cluster-management/builder:nodejs14-linux as packages
43
WORKDIR /app
5-
COPY . .
4+
COPY package.json yarn.lock ./
5+
COPY ./backend/package.json /app/backend/package.json
6+
COPY ./frontend/package.json /app/frontend/package.json
7+
8+
FROM packages as builder
69
RUN yarn install --frozen-lockfile --ignore-optional
10+
11+
FROM packages as production
12+
RUN yarn install --production --frozen-lockfile --ignore-optional
13+
14+
FROM builder as backend
15+
COPY ./backend ./backend
16+
WORKDIR /app/backend
17+
RUN yarn run build
18+
19+
FROM builder as frontend
20+
COPY ./frontend ./frontend
21+
WORKDIR /app/frontend
722
RUN yarn run build
8-
RUN rm -rf node_modules && yarn install --frozen-lockfile --production --ignore-optional
923

1024
FROM registry.access.redhat.com/ubi8/ubi-minimal
1125
COPY --from=builder /usr/bin/node /usr/bin/node
1226
WORKDIR /app
1327
ENV NODE_ENV production
14-
COPY --from=builder /app/node_modules ./node_modules
15-
COPY --from=builder /app/backend/node_modules ./backend/node_modules
16-
COPY --from=builder /app/backend/build ./backend
17-
COPY --from=builder /app/frontend/build ./public
28+
COPY --from=production /app/node_modules ./node_modules
29+
COPY --from=production /app/backend/node_modules ./backend/node_modules
30+
COPY --from=backend /app/backend/build ./backend
31+
COPY --from=frontend /app/frontend/build ./public
1832
USER 1001
1933
CMD ["node", "backend/lib/main.js"]

backend/package.json

+15-10
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,13 @@
22
"name": "@open-cluster-management/console-backend",
33
"version": "0.0.1",
44
"private": true,
5+
"engines": {
6+
"npm": "please-use-yarn",
7+
"yarn": ">= 1.17.3",
8+
"node": ">= 14"
9+
},
510
"scripts": {
6-
"watch": "yarn run start",
11+
"watch": "echo",
712
"postinstall": "[ ! -d ./certs ] && yarn run generate-certs || true",
813
"build": "tsc --sourceMap false --declaration false",
914
"clean": "rm -rf coverage build",
@@ -28,21 +33,21 @@
2833
"devDependencies": {
2934
"@types/dotenv": "^8.2.0",
3035
"@types/eslint": "^7.28.0",
31-
"@types/jest": "^26.0.24",
32-
"@types/node": "^16.4.3",
36+
"@types/jest": "^27.0.0",
37+
"@types/node": "^16.6.0",
3338
"@types/node-fetch": "^2.5.12",
34-
"@types/pino": "^6.3.10",
39+
"@types/pino": "^6.3.11",
3540
"@types/prettier": "^2.3.2",
3641
"@types/raw-body": "^2.3.0",
37-
"@typescript-eslint/eslint-plugin": "^4.28.5",
38-
"@typescript-eslint/parser": "^4.28.5",
39-
"eslint": "^7.31.0",
40-
"jest": "^26.x.x",
42+
"@typescript-eslint/eslint-plugin": "^4.29.1",
43+
"@typescript-eslint/parser": "^4.29.1",
44+
"eslint": "^7.32.0",
45+
"jest": "^27.x.x",
4146
"nock": "^13.1.1",
4247
"pino-zen": "^1.0.20",
4348
"prettier": "^2.3.2",
44-
"ts-jest": "^26.x.x",
45-
"ts-node": "^10.1.0",
49+
"ts-jest": "^27.x.x",
50+
"ts-node": "^10.2.0",
4651
"ts-node-dev": "^1.1.8",
4752
"typescript": "^4.3.5"
4853
},

backend/src/app.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,12 @@ router.get(`/logout/`, logout)
3838
router.get(`/events`, events)
3939
router.post(`/proxy/search`, search)
4040
router.get(`/authenticated`, authenticated)
41-
router.get(`/*`, serve)
4241
router.post(`/ansibletower`, ansibleTower)
42+
router.get(`/*`, serve)
4343

4444
export async function requestHandler(req: Http2ServerRequest, res: Http2ServerResponse): Promise<void> {
4545
if (process.env.NODE_ENV !== 'production') {
46-
cors(req, res)
46+
if (cors(req, res)) return
4747
await delay(req, res)
4848
}
4949

@@ -76,6 +76,7 @@ export function start(): Promise<Http2Server | undefined> {
7676
export async function stop(): Promise<void> {
7777
if (process.env.NODE_ENV === 'development') {
7878
setTimeout(() => {
79+
logger.warn('process stop timeout. exiting...')
7980
process.exit(1)
8081
}, 0.5 * 1000).unref()
8182
}

backend/src/lib/cors.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
import { Http2ServerRequest, Http2ServerResponse } from 'http2'
55

6-
export function cors(req: Http2ServerRequest, res: Http2ServerResponse): void {
6+
export function cors(req: Http2ServerRequest, res: Http2ServerResponse): boolean {
77
if (process.env.NODE_ENV !== 'production') {
88
if (req.headers['origin']) {
99
res.setHeader('Access-Control-Allow-Origin', req.headers['origin'])
@@ -18,7 +18,9 @@ export function cors(req: Http2ServerRequest, res: Http2ServerResponse): void {
1818
if (req.headers['access-control-request-headers']) {
1919
res.setHeader('Access-Control-Allow-Headers', req.headers['access-control-request-headers'])
2020
}
21-
return res.writeHead(200).end()
21+
res.writeHead(200).end()
22+
return true
2223
}
2324
}
25+
return false
2426
}

backend/src/lib/main.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,9 @@ process.on('SIGTERM', () => {
4040
})
4141

4242
process.on('uncaughtException', (err) => {
43-
logger.error({ msg: `process uncaughtException`, error: err.message })
44-
console.log(err.stack)
43+
// console.error(err)
44+
// logger.error({ msg: `process uncaughtException`, error: err.message })
45+
// console.log(err.stack)
4546
})
4647

4748
process.on('multipleResolves', (type, _promise, reason) => {

backend/src/lib/request-retry.ts

+6
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,12 @@ export function requestRetry(options: {
107107
options.onResponse(response)
108108
}
109109
})
110+
.on('error', (err) => {
111+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
112+
if ((err as any).code !== 'ABORT_ERR') {
113+
throw err
114+
}
115+
})
110116
.on('timeout', () => {
111117
// Emitted when the underlying socket times out from inactivity.
112118
// This only notifies that the socket has been idle.

backend/src/routes/serve.ts

+57-23
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
/* Copyright Contributors to the Open Cluster Management project */
2-
import { createReadStream } from 'fs'
2+
import { createReadStream, Stats } from 'fs'
3+
import { stat } from 'fs/promises'
34
import { constants, Http2ServerRequest, Http2ServerResponse } from 'http2'
45
import { extname } from 'path'
56
import { pipeline } from 'stream'
6-
import { parseCookies } from '../lib/cookies'
77
import { logger } from '../lib/logger'
8-
import { redirect } from '../lib/respond'
98

109
const cacheControl = process.env.NODE_ENV === 'production' ? 'public, max-age=604800' : 'no-store'
1110

12-
export function serve(req: Http2ServerRequest, res: Http2ServerResponse): void {
11+
export async function serve(req: Http2ServerRequest, res: Http2ServerResponse): Promise<void> {
1312
try {
1413
let url = req.url
1514

@@ -41,15 +40,50 @@ export function serve(req: Http2ServerRequest, res: Http2ServerResponse): void {
4140
logger.debug('unknown content type', `ext=${ext}`)
4241
return res.writeHead(404).end()
4342
}
43+
44+
const filePath = './public' + url
45+
let stats: Stats
46+
try {
47+
stats = await stat(filePath)
48+
} catch {
49+
return res.writeHead(404).end()
50+
}
51+
52+
if (/\bbr\b/.test(acceptEncoding)) {
53+
try {
54+
const brStats = await stat(filePath + '.br')
55+
const readStream = createReadStream('./public' + url + '.br', { autoClose: true })
56+
readStream
57+
.on('open', () => {
58+
res.writeHead(200, {
59+
[constants.HTTP2_HEADER_CONTENT_ENCODING]: 'br',
60+
[constants.HTTP2_HEADER_CONTENT_TYPE]: contentType,
61+
[constants.HTTP2_HEADER_CONTENT_LENGTH]: brStats.size.toString(),
62+
})
63+
})
64+
.on('error', (err) => {
65+
// logger.error(err)
66+
res.writeHead(404).end()
67+
})
68+
pipeline(readStream, res as unknown as NodeJS.WritableStream, (err) => {
69+
// if (err) logger.error(err)
70+
})
71+
return
72+
} catch {
73+
// Do nothing
74+
}
75+
}
76+
4477
if (/\bgzip\b/.test(acceptEncoding)) {
4578
try {
79+
const gzStats = await stat(filePath + '.gz')
4680
const readStream = createReadStream('./public' + url + '.gz', { autoClose: true })
4781
readStream
4882
.on('open', () => {
4983
res.writeHead(200, {
5084
[constants.HTTP2_HEADER_CONTENT_ENCODING]: 'gzip',
5185
[constants.HTTP2_HEADER_CONTENT_TYPE]: contentType,
52-
// [constants.HTTP2_HEADER_CONTENT_LENGTH]: stats.size.toString(),
86+
[constants.HTTP2_HEADER_CONTENT_LENGTH]: gzStats.size.toString(),
5387
})
5488
})
5589
.on('error', (err) => {
@@ -59,27 +93,27 @@ export function serve(req: Http2ServerRequest, res: Http2ServerResponse): void {
5993
pipeline(readStream, res as unknown as NodeJS.WritableStream, (err) => {
6094
// if (err) logger.error(err)
6195
})
62-
} catch (err) {
63-
logger.error(err)
64-
return res.writeHead(404).end()
96+
return
97+
} catch {
98+
// Do nothing
6599
}
66-
} else {
67-
const readStream = createReadStream('./public' + url, { autoClose: true })
68-
readStream
69-
.on('open', () => {
70-
res.writeHead(200, {
71-
[constants.HTTP2_HEADER_CONTENT_TYPE]: contentType,
72-
})
73-
})
74-
.on('error', (err) => {
75-
// logger.error(err)
76-
res.writeHead(404).end()
100+
}
101+
102+
const readStream = createReadStream('./public' + url, { autoClose: true })
103+
readStream
104+
.on('open', () => {
105+
res.writeHead(200, {
106+
[constants.HTTP2_HEADER_CONTENT_TYPE]: contentType,
107+
[constants.HTTP2_HEADER_CONTENT_LENGTH]: stats.size.toString(),
77108
})
78-
pipeline(readStream, res as unknown as NodeJS.WritableStream, (err) => {
79-
// if (err) logger.error(err)
80109
})
81-
}
82-
return
110+
.on('error', (err) => {
111+
// logger.error(err)
112+
res.writeHead(404).end()
113+
})
114+
pipeline(readStream, res as unknown as NodeJS.WritableStream, (err) => {
115+
// if (err) logger.error(err)
116+
})
83117
} catch (err) {
84118
logger.error(err)
85119
return res.writeHead(404).end()

frontend/babel.config.json

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// https://babeljs.io/docs/en/configuration
2+
{
3+
"presets": ["@babel/env", "@babel/react", "@babel/preset-typescript"]
4+
}

frontend/jest.config.js

+10-2
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,21 @@
33
module.exports = {
44
preset: 'ts-jest',
55
testEnvironment: 'jsdom',
6+
automock: false,
67
rootDir: './src',
78
testResultsProcessor: 'jest-sonar-reporter',
89
setupFilesAfterEnv: ['<rootDir>/setupTests.ts'],
910
moduleNameMapper: {
10-
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '<rootDir>/css.mock.js',
11-
'\\.(css|less)$': '<rootDir>/css.mock.js',
11+
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '<rootDir>/file.mock.js',
12+
'\\.(css|less)$': 'identity-obj-proxy',
13+
'monaco-editor': '<rootDir>/../../node_modules/react-monaco-editor',
1214
},
1315
watchPathIgnorePatterns: ['<rootDir>/../node_modules', '<rootDir>/../.eslintcache', '<rootDir>/../coverage'],
1416
moduleFileExtensions: ['js', 'json', 'jsx', 'node', 'ts', 'tsx'],
17+
transform: {
18+
'^.+\\.jsx?$': 'babel-jest',
19+
'^.+\\.hbs$': 'jest-raw-loader',
20+
'\\.(css|less)$': 'jest-raw-loader',
21+
},
22+
coverageReporters: ['text', 'text-summary', 'html', 'lcov'],
1523
}

0 commit comments

Comments
 (0)