From 6bd5c43f627ee920bdb93a18e7b673b670d88068 Mon Sep 17 00:00:00 2001 From: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com> Date: Thu, 23 Feb 2023 17:15:59 -0500 Subject: [PATCH] Tsify app.js (#4767) I forgot about app.js, now this is actually the last file in our ts conversion (other than test/) --- .idea/runConfigurations/app_js.xml | 2 +- .idea/scopes/Server.xml | 2 +- Makefile | 6 +-- app.js => app.ts | 74 ++++++++++++++++-------------- lib/common-utils.ts | 3 ++ lib/compiler-arguments.ts | 2 +- lib/handlers/api.ts | 2 +- lib/handlers/noscript.ts | 2 +- package-lock.json | 17 +++++++ package.json | 15 +++--- tsconfig.backend.json | 2 +- tsconfig.frontend.json | 2 +- 12 files changed, 78 insertions(+), 51 deletions(-) rename app.js => app.ts (93%) diff --git a/.idea/runConfigurations/app_js.xml b/.idea/runConfigurations/app_js.xml index 6dece056a..115014967 100644 --- a/.idea/runConfigurations/app_js.xml +++ b/.idea/runConfigurations/app_js.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/.idea/scopes/Server.xml b/.idea/scopes/Server.xml index 10628e53d..07a585a66 100644 --- a/.idea/scopes/Server.xml +++ b/.idea/scopes/Server.xml @@ -1,3 +1,3 @@ - + \ No newline at end of file diff --git a/Makefile b/Makefile index dc33af521..d608843ed 100644 --- a/Makefile +++ b/Makefile @@ -86,15 +86,15 @@ run: ## Runs the site like it runs in production .PHONY: dev dev: prereqs ## Runs the site as a developer; including live reload support and installation of git hooks - ./node_modules/.bin/supervisor -w app.js,lib,etc/config,static/tsconfig.json -e 'js|ts|node|properties|yaml' -n exit --exec $(NODE) $(NODE_ARGS) -- -r esm -r ts-node/register ./app.js $(EXTRA_ARGS) + ./node_modules/.bin/supervisor -w app.ts,lib,etc/config,static/tsconfig.json -e 'js|ts|node|properties|yaml' -n exit --exec $(NODE) $(NODE_ARGS) -- -r esm -r ts-node/register ./app.ts $(EXTRA_ARGS) .PHONY: gpu-dev gpu-dev: prereqs ## Runs the site as a developer; including live reload support and installation of git hooks - ./node_modules/.bin/supervisor -w app.js,lib,etc/config,static/tsconfig.json -e 'js|ts|node|properties|yaml' -n exit --exec $(NODE) $(NODE_ARGS) -- -r esm -r ts-node/register ./app.js --env gpu $(EXTRA_ARGS) + ./node_modules/.bin/supervisor -w app.ts,lib,etc/config,static/tsconfig.json -e 'js|ts|node|properties|yaml' -n exit --exec $(NODE) $(NODE_ARGS) -- -r esm -r ts-node/register ./app.ts --env gpu $(EXTRA_ARGS) .PHONY: debug debug: prereqs ## Runs the site as a developer with full debugging; including live reload support and installation of git hooks - ./node_modules/.bin/supervisor -w app.js,lib,etc/config,static/tsconfig.json -e 'js|ts|node|properties|yaml' -n exit --inspect 9229 --exec $(NODE) $(NODE_ARGS) -- -r esm -r ts-node/register ./app.js --debug $(EXTRA_ARGS) + ./node_modules/.bin/supervisor -w app.ts,lib,etc/config,static/tsconfig.json -e 'js|ts|node|properties|yaml' -n exit --inspect 9229 --exec $(NODE) $(NODE_ARGS) -- -r esm -r ts-node/register ./app.ts --debug $(EXTRA_ARGS) .PHONY: asm-docs: diff --git a/app.js b/app.ts similarity index 93% rename from app.js rename to app.ts index 2e38a40e1..17f92b9af 100755 --- a/app.js +++ b/app.ts @@ -45,6 +45,7 @@ import urljoin from 'url-join'; import * as aws from './lib/aws'; import * as normalizer from './lib/clientstate-normalizer'; +import {ElementType} from './lib/common-utils'; import {CompilationEnvironment} from './lib/compilation-env'; import {CompilationQueue} from './lib/compilation-queue'; import {CompilerFinder} from './lib/compiler-finder'; @@ -66,6 +67,7 @@ import {sources} from './lib/sources'; import {loadSponsorsFromString} from './lib/sponsors'; import {getStorageTypeByKey} from './lib/storage'; import * as utils from './lib/utils'; +import {Language, LanguageKey} from './types/languages.interfaces'; // Used by assert.ts global.ce_base_directory = __dirname; // eslint-disable-line unicorn/prefer-module @@ -106,7 +108,8 @@ if (opts.debug) logger.level = 'debug'; // AP: Detect if we're running under Windows Subsystem for Linux. Temporary modification // of process.env is allowed: https://nodejs.org/api/process.html#process_process_env if (process.platform === 'linux' && child_process.execSync('uname -a').toString().includes('Microsoft')) { - process.env.wsl = true; + // Node wants process.env is essentially a Record. Any non-empty string should be fine. + process.env.wsl = 'true'; } // AP: Allow setting of tmpDir (used in lib/base-compiler.js & lib/exec.js) through opts. @@ -214,22 +217,24 @@ props.initialize(configDir, propHierarchy); const ceProps = props.propsFor('compiler-explorer'); defArgs.wantedLanguages = ceProps('restrictToLanguages', defArgs.wantedLanguages); -let languages = allLanguages; -if (defArgs.wantedLanguages) { - const filteredLangs = {}; - const passedLangs = defArgs.wantedLanguages.split(','); - for (const wantedLang of passedLangs) { - for (const langId in languages) { - const lang = languages[langId]; - if (lang.id === wantedLang || lang.name === wantedLang || lang.alias === wantedLang) { - filteredLangs[lang.id] = lang; +const languages = (() => { + if (defArgs.wantedLanguages) { + const filteredLangs: Partial> = {}; + const passedLangs = defArgs.wantedLanguages.split(','); + for (const wantedLang of passedLangs) { + for (const lang of Object.values(allLanguages)) { + if (lang.id === wantedLang || lang.name === wantedLang || lang.alias === wantedLang) { + filteredLangs[lang.id] = lang; + } } } + // Always keep cmake for IDE mode, just in case + filteredLangs[allLanguages.cmake.id] = allLanguages.cmake; + return filteredLangs; + } else { + return allLanguages; } - // Always keep cmake for IDE mode, just in case - filteredLangs[languages.cmake.id] = languages.cmake; - languages = filteredLangs; -} +})(); if (Object.keys(languages).length === 0) { logger.error('Trying to start Compiler Explorer without a language'); @@ -253,15 +258,15 @@ function staticHeaders(res) { } } -function contentPolicyHeader(/*res*/) { +function contentPolicyHeader(res: express.Response) { // TODO: re-enable CSP // if (csp) { // res.setHeader('Content-Security-Policy', csp); // } } -function measureEventLoopLag(delayMs) { - return new Promise(resolve => { +function measureEventLoopLag(delayMs: number) { + return new Promise(resolve => { const start = process.hrtime.bigint(); setTimeout(() => { const elapsed = process.hrtime.bigint() - start; @@ -301,11 +306,11 @@ function setupEventLoopLagLogging() { } } -let pugRequireHandler = () => { +let pugRequireHandler: (path: string) => any = () => { logger.error('pug require handler not configured'); }; -async function setupWebPackDevMiddleware(router) { +async function setupWebPackDevMiddleware(router: express.Router) { logger.info(' using webpack dev middleware'); /* eslint-disable node/no-unpublished-import,import/extensions, */ @@ -313,8 +318,9 @@ async function setupWebPackDevMiddleware(router) { const {default: webpackConfig} = await import('./webpack.config.esm.js'); const {default: webpack} = await import('webpack'); /* eslint-enable */ + type WebpackConfiguration = ElementType[0]>; - const webpackCompiler = webpack(webpackConfig); + const webpackCompiler = webpack([webpackConfig as WebpackConfiguration]); router.use( webpackDevMiddleware(webpackCompiler, { publicPath: '/static', @@ -328,7 +334,7 @@ async function setupWebPackDevMiddleware(router) { pugRequireHandler = path => urljoin(httpRoot, 'static', path); } -async function setupStaticMiddleware(router) { +async function setupStaticMiddleware(router: express.Router) { const staticManifest = await fs.readJson(path.join(distPath, 'manifest.json')); if (staticUrl) { @@ -353,7 +359,7 @@ async function setupStaticMiddleware(router) { }; } -function shouldRedactRequestData(data) { +function shouldRedactRequestData(data: string) { try { const parsed = JSON.parse(data); return !parsed['allowStoreCodeDebug']; @@ -364,14 +370,14 @@ function shouldRedactRequestData(data) { const googleShortUrlResolver = new ShortLinkResolver(); -function oldGoogleUrlHandler(req, res, next) { +function oldGoogleUrlHandler(req: express.Request, res: express.Response, next: express.NextFunction) { const id = req.params.id; const googleUrl = `https://goo.gl/${encodeURIComponent(id)}`; googleShortUrlResolver .resolve(googleUrl) .then(resultObj => { const parsed = new url.URL(resultObj.longUrl); - const allowedRe = new RegExp(ceProps('allowedShortUrlHostRe')); + const allowedRe = new RegExp(ceProps('allowedShortUrlHostRe')); if (parsed.host.match(allowedRe) === null) { logger.warn(`Denied access to short URL ${id} - linked to ${resultObj.longUrl}`); return next({ @@ -394,13 +400,13 @@ function oldGoogleUrlHandler(req, res, next) { }); } -function startListening(server) { +function startListening(server: express.Express) { const ss = systemdSocket(); let _port; if (ss) { // ms (5 min default) const idleTimeout = process.env.IDLE_TIMEOUT; - const timeout = (idleTimeout === undefined ? 300 : idleTimeout) * 1000; + const timeout = (idleTimeout === undefined ? 300 : parseInt(idleTimeout)) * 1000; if (idleTimeout) { const exit = () => { logger.info('Inactivity timeout reached, exiting.'); @@ -440,7 +446,7 @@ function startListening(server) { } } -function setupSentry(sentryDsn) { +function setupSentry(sentryDsn: string) { if (!sentryDsn) { logger.info('Not configuring sentry'); return; @@ -489,7 +495,7 @@ async function main() { let prevCompilers; if (opts.prediscovered) { - const prediscoveredCompilersJson = await fs.readFile(opts.prediscovered); + const prediscoveredCompilersJson = await fs.readFile(opts.prediscovered, 'utf8'); initialCompilers = JSON.parse(prediscoveredCompilersJson); await compilerFinder.loadPrediscovered(initialCompilers); } else { @@ -538,7 +544,7 @@ async function main() { }; const noscriptHandler = new NoScriptHandler(router, handlerConfig); - const routeApi = new RouteAPI(router, handlerConfig, noscriptHandler.renderNoScriptLayout); + const routeApi = new RouteAPI(router, handlerConfig); async function onCompilerChange(compilers) { if (JSON.stringify(prevCompilers) === JSON.stringify(compilers)) { @@ -616,7 +622,7 @@ async function main() { loadSiteTemplates(configDir); - function renderConfig(extra, urlOptions) { + function renderConfig(extra, urlOptions?) { const urlOptionsAllowed = ['readOnly', 'hideEditorToolbars', 'language']; const filteredUrlOptions = _.mapObject(_.pick(urlOptions, urlOptionsAllowed), val => utils.toProperty(val)); const allExtraOptions = _.extend({}, filteredUrlOptions, extra); @@ -667,7 +673,7 @@ async function main() { ); } - const embeddedHandler = function (req, res) { + const embeddedHandler = function (req: express.Request, res: express.Response) { staticHeaders(res); contentPolicyHeader(res); res.render( @@ -684,7 +690,7 @@ async function main() { await (isDevMode() ? setupWebPackDevMiddleware(router) : setupStaticMiddleware(router)); - morgan.token('gdpr_ip', req => (req.ip ? utils.anonymizeIp(req.ip) : '')); + morgan.token('gdpr_ip', (req: any) => (req.ip ? utils.anonymizeIp(req.ip) : '')); // Based on combined format, but: GDPR compliant IP, no timestamp & no unused fields for our usecase const morganFormat = isDevMode() ? 'dev' : ':gdpr_ip ":method :url" :status'; @@ -837,14 +843,14 @@ process.on('SIGINT', signalHandler('SIGINT')); process.on('SIGTERM', signalHandler('SIGTERM')); process.on('SIGQUIT', signalHandler('SIGQUIT')); -function signalHandler(name) { +function signalHandler(name: string) { return () => { logger.info(`stopping process: ${name}`); process.exit(0); }; } -function uncaughtHandler(err, origin) { +function uncaughtHandler(err: Error, origin: NodeJS.UncaughtExceptionOrigin) { logger.info(`stopping process: Uncaught exception: ${err}\nException origin: ${origin}`); // The app will exit naturally from here, but if we call `process.exit()` we may lose log lines. // see https://github.com/winstonjs/winston/issues/1504#issuecomment-1033087411 diff --git a/lib/common-utils.ts b/lib/common-utils.ts index 21cab4651..9beb41bd1 100644 --- a/lib/common-utils.ts +++ b/lib/common-utils.ts @@ -55,3 +55,6 @@ export function basic_comparator(a: T, b: T) { return 0; } } + +// https://stackoverflow.com/questions/41253310/typescript-retrieve-element-type-information-from-array-type +export type ElementType = ArrayType extends readonly (infer T)[] ? T : never; diff --git a/lib/compiler-arguments.ts b/lib/compiler-arguments.ts index 6128a7b25..762a5a147 100644 --- a/lib/compiler-arguments.ts +++ b/lib/compiler-arguments.ts @@ -37,7 +37,7 @@ import {fileExists, resolvePathFromAppRoot} from './utils'; export class CompilerArguments implements ICompilerArguments { private readonly compilerId: string; - private possibleArguments: PossibleArguments = {}; + public possibleArguments: PossibleArguments = {}; private readonly maxPopularArguments = 5; private readonly storeSpecificArguments = false; private loadedFromFile = false; diff --git a/lib/handlers/api.ts b/lib/handlers/api.ts index 12de6ef32..0e0ea962b 100644 --- a/lib/handlers/api.ts +++ b/lib/handlers/api.ts @@ -55,7 +55,7 @@ export class ApiHandler { private usedLangIds: LanguageKey[] = []; private options: ClientOptionsHandler | null = null; public readonly handle: express.Router; - private readonly shortener: BaseShortener; + public readonly shortener: BaseShortener; private release = { gitReleaseName: '', releaseBuildNumber: '', diff --git a/lib/handlers/noscript.ts b/lib/handlers/noscript.ts index ae63e06d1..f1e9be124 100644 --- a/lib/handlers/noscript.ts +++ b/lib/handlers/noscript.ts @@ -51,7 +51,7 @@ export class NoScriptHandler { formDataParser: ReturnType | undefined; - /* the type for config makes the most sense to define in app.js or api.js */ + /* the type for config makes the most sense to define in app.ts or api.ts */ constructor(private readonly router: express.Router, config: any) { this.staticHeaders = config.staticHeaders; this.contentPolicyHeader = config.contentPolicyHeader; diff --git a/package-lock.json b/package-lock.json index 73c05e032..68a467f54 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@fortawesome/fontawesome-free": "^6.2.1", "@sentry/browser": "^7.28.1", "@sentry/node": "^7.28.1", + "@types/morgan": "^1.9.4", "aws-sdk": "^2.1048.0", "big-integer": "^1.6.51", "body-parser": "^1.19.1", @@ -1916,6 +1917,14 @@ "integrity": "sha512-ekGvFhFgrc2zYQoX4JeZPmVzZxw6Dtllga7iGHzfbYIYkAMUx/sAFP2GdFpLff+vdHXu5fl7WX9AT+TtqYcsyw==", "dev": true }, + "node_modules/@types/morgan": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.4.tgz", + "integrity": "sha512-cXoc4k+6+YAllH3ZHmx4hf7La1dzUk6keTR4bF4b4Sc0mZxU/zK4wO7l+ZzezXm/jkYj/qC+uYGZrarZdIVvyQ==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { "version": "18.11.18", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", @@ -16971,6 +16980,14 @@ "integrity": "sha512-ekGvFhFgrc2zYQoX4JeZPmVzZxw6Dtllga7iGHzfbYIYkAMUx/sAFP2GdFpLff+vdHXu5fl7WX9AT+TtqYcsyw==", "dev": true }, + "@types/morgan": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.4.tgz", + "integrity": "sha512-cXoc4k+6+YAllH3ZHmx4hf7La1dzUk6keTR4bF4b4Sc0mZxU/zK4wO7l+ZzezXm/jkYj/qC+uYGZrarZdIVvyQ==", + "requires": { + "@types/node": "*" + } + }, "@types/node": { "version": "18.11.18", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", diff --git a/package.json b/package.json index 94363370a..5c6c1782f 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "engines": { "node": ">=16" }, - "main": "./app.js", + "main": "./app.ts", "esm": { "mode": "all", "cache": false @@ -24,6 +24,7 @@ "@fortawesome/fontawesome-free": "^6.2.1", "@sentry/browser": "^7.28.1", "@sentry/node": "^7.28.1", + "@types/morgan": "^1.9.4", "aws-sdk": "^2.1048.0", "big-integer": "^1.6.51", "body-parser": "^1.19.1", @@ -100,8 +101,8 @@ "@types/file-saver": "^2.0.5", "@types/fs-extra": "^9.0.13", "@types/http-proxy": "^1.17.9", - "@types/js-cookie": "^3.0.2", "@types/jquery": "^3.5.10", + "@types/js-cookie": "^3.0.2", "@types/mocha": "^8.2.2", "@types/node-targz": "^0.2.0", "@types/qs": "^6.9.7", @@ -172,14 +173,14 @@ "lint": "eslint --max-warnings=0 . --fix", "lint-check": "eslint --max-warnings=0 .", "lint-files": "eslint --max-warnings=0", - "test": "mocha -b -r ts-node/register 'test/**/*.ts' 'test/**/*.js'", + "test": "mocha -b -r ts-node/register 'test/**/*.ts' 'test/**/*.ts'", "test-min": "mocha -b --config .mocharc-min.yml", "fix": "npm run lint && npm run format && npm run ts-check", "check": "npm run ts-check && npm run lint-check && npm run test-min -- --reporter min", - "dev": "cross-env NODE_ENV=DEV node -r esm -r ts-node/register app.js", - "debugger": "cross-env NODE_ENV=DEV node --inspect -r esm -r ts-node/register app.js --debug", - "debug": "cross-env NODE_ENV=DEV node -r esm -r ts-node/register app.js --debug", - "start": "webpack && cross-env NODE_ENV=LOCAL node -r esm -r ts-node/register app.js", + "dev": "cross-env NODE_ENV=DEV node -r esm -r ts-node/register app.ts", + "debugger": "cross-env NODE_ENV=DEV node --inspect -r esm -r ts-node/register app.ts --debug", + "debug": "cross-env NODE_ENV=DEV node -r esm -r ts-node/register app.ts --debug", + "start": "webpack && cross-env NODE_ENV=LOCAL node -r esm -r ts-node/register app.ts", "codecov": "codecov --disable=gcov", "sentry": "npx -p @sentry/cli sentry-cli", "update-browserslist": "npx browserslist@latest -- --update-db", diff --git a/tsconfig.backend.json b/tsconfig.backend.json index 7cdda8eb7..1ecd4cc06 100644 --- a/tsconfig.backend.json +++ b/tsconfig.backend.json @@ -1,6 +1,6 @@ { "extends": "./tsconfig.base.json", - "files": ["app.js", "compiler-args-app.ts"], + "files": ["app.ts", "compiler-args-app.ts"], "include": ["**/*.js", "**/*.ts"], "exclude": ["out", "test", "etc", "examples", "static", "**/*.d.ts"], "compilerOptions": { diff --git a/tsconfig.frontend.json b/tsconfig.frontend.json index fc7925398..91283d8b7 100644 --- a/tsconfig.frontend.json +++ b/tsconfig.frontend.json @@ -9,5 +9,5 @@ "strictPropertyInitialization": false, "lib": ["dom", "es5", "dom.iterable"] }, - "exclude": ["out", "test", "etc", "examples", "app.js", "compiler-args-app.ts", "lib", "examples"] + "exclude": ["out", "test", "etc", "examples", "app.ts", "compiler-args-app.ts", "lib", "examples"] }