web: review environment variables before the Datadog integration (#33994)

This commit is contained in:
Valery Bugakov 2022-04-19 02:22:48 -07:00 committed by GitHub
parent 33e7c8b494
commit f3a11939af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 256 additions and 230 deletions

View File

@ -4,3 +4,4 @@ export * from './webpack/css-loader'
export * from './webpack/getCacheConfig'
export * from './webpack/monaco-editor'
export * from './webpack/plugins'
export * from './utils/environment-config'

View File

@ -0,0 +1,6 @@
// Read the environment variable from `process.env` and cast it to `Boolean`.
export function getEnvironmentBoolean(name: string): boolean {
const variable = process.env[name]
return Boolean(variable && JSON.parse(variable))
}

View File

@ -24,3 +24,26 @@ export const getProvidePlugin = (): webpack.ProvidePlugin =>
})
export const getStatoscopePlugin = (): StatoscopeWebpackPlugin => new StatoscopeWebpackPlugin()
export const STATOSCOPE_STATS = {
all: false, // disable all the stats
hash: true, // compilation hash
entrypoints: true,
chunks: true,
chunkModules: true, // modules
reasons: true, // modules reasons
ids: true, // IDs of modules and chunks (webpack 5)
dependentModules: true, // dependent modules of chunks (webpack 5)
chunkRelations: true, // chunk parents, children and siblings (webpack 5)
cachedAssets: true, // information about the cached assets (webpack 5)
nestedModules: true, // concatenated modules
usedExports: true,
providedExports: true, // provided imports
assets: true,
chunkOrigins: true, // chunks origins stats (to find out which modules require a chunk)
version: true, // webpack version
builtAt: true, // build at time
timings: true, // modules timing information
performance: true, // info about oversized assets
}

View File

@ -36,3 +36,8 @@ export const createAggregateError = (errors: readonly ErrorLike[] = []): Error =
name: AGGREGATE_ERROR_NAME,
errors: errors.map(asError),
})
export class AbortError extends Error {
public readonly name = 'AbortError'
public readonly message = 'Aborted'
}

View File

@ -2,7 +2,7 @@ import { ProxyMarked, transferHandlers, releaseProxy, TransferHandler, Remote, p
import { Observable, Observer, PartialObserver, Subscription } from 'rxjs'
import { Subscribable, Unsubscribable } from 'sourcegraph'
import { hasProperty } from '@sourcegraph/common'
import { hasProperty, AbortError } from '@sourcegraph/common'
import { ProxySubscribable } from './extension/api/common'
@ -123,11 +123,6 @@ export const observableFromAsyncIterable = <T>(iterable: AsyncIterable<T>): Obse
}
})
export class AbortError extends Error {
public readonly name = 'AbortError'
public readonly message = 'Aborted'
}
/**
* Promisifies method calls and objects if specified, throws otherwise if there is no stub provided
* NOTE: it does not handle ProxyMethods and callbacks yet

View File

@ -1,10 +1,10 @@
const getEnvironmentBoolean = (value = 'false'): boolean => Boolean(JSON.parse(value))
import { getEnvironmentBoolean } from '@sourcegraph/build-config'
export const environment = {
customStoriesGlob: process.env.STORIES_GLOB,
isDLLPluginEnabled: getEnvironmentBoolean(process.env.WEBPACK_DLL_PLUGIN),
isProgressPluginEnabled: getEnvironmentBoolean(process.env.WEBPACK_PROGRESS_PLUGIN),
isBundleAnalyzerEnabled: getEnvironmentBoolean(process.env.WEBPACK_BUNDLE_ANALYZER),
isSpeedAnalyzerEnabled: getEnvironmentBoolean(process.env.WEBPACK_SPEED_ANALYZER),
shouldMinify: getEnvironmentBoolean(process.env.MINIFY),
export const ENVIRONMENT_CONFIG = {
STORIES_GLOB: process.env.STORIES_GLOB,
WEBPACK_DLL_PLUGIN: getEnvironmentBoolean('WEBPACK_DLL_PLUGIN'),
WEBPACK_PROGRESS_PLUGIN: getEnvironmentBoolean('WEBPACK_PROGRESS_PLUGIN'),
WEBPACK_BUNDLE_ANALYZER: getEnvironmentBoolean('WEBPACK_BUNDLE_ANALYZER'),
WEBPACK_SPEED_ANALYZER: getEnvironmentBoolean('WEBPACK_SPEED_ANALYZER'),
MINIFY: getEnvironmentBoolean('MINIFY'),
}

View File

@ -24,7 +24,7 @@ import {
} from '@sourcegraph/build-config'
import { ensureDllBundleIsReady } from './dllPlugin'
import { environment } from './environment-config'
import { ENVIRONMENT_CONFIG } from './environment-config'
import {
monacoEditorPath,
dllPluginConfig,
@ -34,8 +34,8 @@ import {
} from './webpack.config.common'
const getStoriesGlob = (): string[] => {
if (process.env.STORIES_GLOB) {
return [path.resolve(ROOT_PATH, process.env.STORIES_GLOB)]
if (ENVIRONMENT_CONFIG.STORIES_GLOB) {
return [path.resolve(ROOT_PATH, ENVIRONMENT_CONFIG.STORIES_GLOB)]
}
// Stories in `Chromatic.story.tsx` are guarded by the `isChromatic()` check. It will result in noop in all other environments.
@ -93,12 +93,12 @@ const config = {
// Include DLL bundle script tag into preview-head.html if DLLPlugin is enabled.
previewHead: (head: string) => `
${head}
${environment.isDLLPluginEnabled ? getDllScriptTag() : ''}
${ENVIRONMENT_CONFIG.WEBPACK_DLL_PLUGIN ? getDllScriptTag() : ''}
`,
webpackFinal: (config: Configuration, options: Options) => {
config.stats = 'errors-warnings'
config.mode = environment.shouldMinify ? 'production' : 'development'
config.mode = ENVIRONMENT_CONFIG.MINIFY ? 'production' : 'development'
// Check the default config is in an expected shape.
if (!config.module?.rules || !config.plugins) {
@ -115,7 +115,7 @@ const config = {
getProvidePlugin()
)
if (environment.shouldMinify) {
if (ENVIRONMENT_CONFIG.MINIFY) {
if (!config.optimization) {
throw new Error('The structure of the config changed, expected config.optimization to be not-null')
}
@ -155,7 +155,7 @@ const config = {
exclude: storybookPath,
use: getCSSLoaders(
'style-loader',
getCSSModulesLoader({ sourceMap: !environment.shouldMinify, url: false })
getCSSModulesLoader({ sourceMap: !ENVIRONMENT_CONFIG.MINIFY, url: false })
),
})
@ -200,11 +200,11 @@ const config = {
// Disable `ProgressPlugin` by default to speed up development build.
// Can be re-enabled by setting `WEBPACK_PROGRESS_PLUGIN` env variable.
if (!environment.isProgressPluginEnabled) {
if (!ENVIRONMENT_CONFIG.WEBPACK_PROGRESS_PLUGIN) {
remove(config.plugins, plugin => plugin instanceof ProgressPlugin)
}
if (environment.isDLLPluginEnabled && !options.webpackStatsJson) {
if (ENVIRONMENT_CONFIG.WEBPACK_DLL_PLUGIN && !options.webpackStatsJson) {
config.plugins.unshift(
new DllReferencePlugin({
context: dllPluginConfig.context,
@ -216,11 +216,11 @@ const config = {
config.module.rules.push(getMonacoCSSRule(), getMonacoTTFRule())
}
if (environment.isBundleAnalyzerEnabled) {
if (ENVIRONMENT_CONFIG.WEBPACK_BUNDLE_ANALYZER) {
config.plugins.push(getStatoscopePlugin())
}
if (environment.isSpeedAnalyzerEnabled) {
if (ENVIRONMENT_CONFIG.WEBPACK_SPEED_ANALYZER) {
const speedMeasurePlugin = new SpeedMeasurePlugin({
outputFormat: 'human',
})

View File

@ -4,5 +4,4 @@ out/
.eslintrc.js
GH2SG.bookmarklet.js
node_modules/
dev/
bookmarklet/

View File

@ -55,7 +55,7 @@ sg start oss-web-standalone-prod
Web app should be available at `https://${SOURCEGRAPH_HTTPS_DOMAIN}:${SOURCEGRAPH_HTTPS_PORT}`. Build artifacts will be served from `<rootRepoPath>/ui/assets`.
Note: If you are unable to use the above commands (e.g. you can't install Caddy), you can use `sg run web-standalone-http` instead. This will start a development server using only Node, and will be available at `http://localhost:${CLIENT_PROXY_DEVELOPMENT_PORT}`.
Note: If you are unable to use the above commands (e.g. you can't install Caddy), you can use `sg run web-standalone-http` instead. This will start a development server using only Node, and will be available at `http://localhost:${SOURCEGRAPH_HTTP_PORT}`.
### API proxy

View File

@ -5,7 +5,7 @@ import signale from 'signale'
import { MONACO_LANGUAGES_AND_FEATURES } from '@sourcegraph/build-config'
import { environmentConfig, ROOT_PATH, STATIC_ASSETS_PATH } from '../utils'
import { ENVIRONMENT_CONFIG, ROOT_PATH, STATIC_ASSETS_PATH } from '../utils'
import { manifestPlugin } from './manifestPlugin'
import { monacoPlugin } from './monacoPlugin'
@ -13,7 +13,7 @@ import { packageResolutionPlugin } from './packageResolutionPlugin'
import { stylePlugin } from './stylePlugin'
import { workerPlugin } from './workerPlugin'
const isEnterpriseBuild = environmentConfig.ENTERPRISE
const isEnterpriseBuild = ENVIRONMENT_CONFIG.ENTERPRISE
export const BUILD_OPTIONS: esbuild.BuildOptions = {
entryPoints: {
@ -67,7 +67,7 @@ export const BUILD_OPTIONS: esbuild.BuildOptions = {
],
define: {
...Object.fromEntries(
Object.entries({ ...environmentConfig, SOURCEGRAPH_API_URL: undefined }).map(([key, value]) => [
Object.entries({ ...ENVIRONMENT_CONFIG, SOURCEGRAPH_API_URL: undefined }).map(([key, value]) => [
`process.env.${key}`,
JSON.stringify(value),
])

View File

@ -8,7 +8,7 @@ import WebpackDevServer, { ProxyConfigArrayItem } from 'webpack-dev-server'
import { getManifest } from '../esbuild/manifestPlugin'
import { esbuildDevelopmentServer } from '../esbuild/server'
import {
environmentConfig,
ENVIRONMENT_CONFIG,
getAPIProxySettings,
shouldCompressResponse,
STATIC_ASSETS_PATH,
@ -16,22 +16,15 @@ import {
HTTPS_WEB_SERVER_URL,
HTTP_WEB_SERVER_URL,
PROXY_ROUTES,
printSuccessBanner,
} from '../utils'
import { getHTMLPage } from '../webpack/get-html-webpack-plugins'
// TODO: migrate webpack.config.js to TS to use `import` in this file.
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports
const webpackConfig = require('../../webpack.config') as Configuration
// TODO: migrate webpack.config.js to TS to use `import` in this file.
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports
const printSuccessBanner = require('../utils/success-banner') as (lines: string[], log: () => void) => void
const {
SOURCEGRAPH_API_URL,
SOURCEGRAPH_HTTPS_PORT,
CLIENT_PROXY_DEVELOPMENT_PORT,
IS_HOT_RELOAD_ENABLED,
} = environmentConfig
const { SOURCEGRAPH_API_URL, SOURCEGRAPH_HTTPS_PORT, SOURCEGRAPH_HTTP_PORT } = ENVIRONMENT_CONFIG
interface DevelopmentServerInit {
proxyRoutes: string[]
@ -39,10 +32,7 @@ interface DevelopmentServerInit {
}
async function startDevelopmentServer(): Promise<void> {
signale.start(
`Starting ${environmentConfig.DEV_WEB_BUILDER} dev server with environment config:\n`,
environmentConfig
)
signale.start(`Starting ${ENVIRONMENT_CONFIG.DEV_WEB_BUILDER} dev server.`, ENVIRONMENT_CONFIG)
if (!SOURCEGRAPH_API_URL) {
throw new Error('development.server.ts only supports *web-standalone* usage')
@ -55,7 +45,7 @@ async function startDevelopmentServer(): Promise<void> {
}),
}
switch (environmentConfig.DEV_WEB_BUILDER) {
switch (ENVIRONMENT_CONFIG.DEV_WEB_BUILDER) {
case 'webpack':
await startWebpackDevelopmentServer(init)
break
@ -79,11 +69,11 @@ async function startWebpackDevelopmentServer({
// react-refresh plugin triggers page reload if needed.
liveReload: false,
allowedHosts: 'all',
hot: IS_HOT_RELOAD_ENABLED,
hot: true,
historyApiFallback: {
disableDotRule: true,
},
port: CLIENT_PROXY_DEVELOPMENT_PORT,
port: SOURCEGRAPH_HTTP_PORT,
client: {
overlay: false,
webSocketTransport: 'ws',

View File

@ -8,21 +8,21 @@ import signale from 'signale'
import {
PROXY_ROUTES,
getAPIProxySettings,
environmentConfig,
ENVIRONMENT_CONFIG,
STATIC_ASSETS_PATH,
STATIC_INDEX_PATH,
HTTP_WEB_SERVER_URL,
HTTPS_WEB_SERVER_URL,
} from '../utils'
const { SOURCEGRAPH_API_URL, CLIENT_PROXY_DEVELOPMENT_PORT } = environmentConfig
const { SOURCEGRAPH_API_URL, SOURCEGRAPH_HTTP_PORT } = ENVIRONMENT_CONFIG
function startProductionServer(): void {
if (!SOURCEGRAPH_API_URL) {
throw new Error('production.server.ts only supports *web-standalone* usage')
}
signale.await('Production server', { ...environmentConfig })
signale.await('Starting production server', ENVIRONMENT_CONFIG)
const app = express()
@ -30,7 +30,6 @@ function startProductionServer(): void {
app.use(historyApiFallback() as RequestHandler)
// Serve build artifacts.
app.use(
'/.assets',
expressStaticGzip(STATIC_ASSETS_PATH, {
@ -53,7 +52,7 @@ function startProductionServer(): void {
// Redirect remaining routes to index.html
app.get('/*', (_request, response) => response.sendFile(STATIC_INDEX_PATH))
app.listen(CLIENT_PROXY_DEVELOPMENT_PORT, () => {
app.listen(SOURCEGRAPH_HTTP_PORT, () => {
signale.info(`Production HTTP server is ready at ${chalk.blue.bold(HTTP_WEB_SERVER_URL)}`)
signale.success(`Production HTTPS server is ready at ${chalk.blue.bold(HTTPS_WEB_SERVER_URL)}`)
})

View File

@ -4,14 +4,14 @@ export const ROOT_PATH = path.resolve(__dirname, '../../../../')
export const STATIC_ASSETS_PATH = path.resolve(ROOT_PATH, 'ui/assets')
export const STATIC_INDEX_PATH = path.resolve(STATIC_ASSETS_PATH, 'index.html')
export const STATIC_ASSETS_URL = '/.assets/'
export const DEV_SERVER_LISTEN_ADDR = { host: 'localhost', port: 3080 }
export const DEV_SERVER_PROXY_TARGET_ADDR = { host: 'localhost', port: 3081 }
export const DEV_SERVER_LISTEN_ADDR = { host: 'localhost', port: 3080 } as const
export const DEV_SERVER_PROXY_TARGET_ADDR = { host: 'localhost', port: 3081 } as const
export const DEFAULT_SITE_CONFIG_PATH = path.resolve(ROOT_PATH, '../dev-private/enterprise/dev/site-config.json')
// TODO: share with gulpfile.js
export const WEBPACK_STATS_OPTIONS = {
all: false,
timings: true,
errors: true,
warnings: true,
colors: true,
}
} as const

View File

@ -1,6 +1,6 @@
import { SourcegraphContext } from '../../src/jscontext'
import { environmentConfig } from './environment-config'
import { ENVIRONMENT_CONFIG } from './environment-config'
import { getSiteConfig } from './get-site-config'
// TODO: share with `client/web/src/integration/jscontext` which is not included into `tsconfig.json` now.
@ -50,7 +50,7 @@ export const createJsContext = ({ sourcegraphBaseUrl }: { sourcegraphBaseUrl: st
},
siteID: 'TestSiteID',
siteGQLID: 'TestGQLSiteID',
sourcegraphDotComMode: environmentConfig.SOURCEGRAPHDOTCOM_MODE,
sourcegraphDotComMode: ENVIRONMENT_CONFIG.SOURCEGRAPHDOTCOM_MODE,
githubAppCloudSlug: 'TestApp',
githubAppCloudClientID: 'TestClientID',
userAgentIsBot: false,

View File

@ -1,30 +1,67 @@
import path from 'path'
/**
* Unpack all `process.env.*` variables used during the build
* time of the web application in this module to keep one source of truth.
*/
import { getEnvironmentBoolean } from '@sourcegraph/build-config'
import { ROOT_PATH } from './constants'
import { DEFAULT_SITE_CONFIG_PATH } from './constants'
const DEFAULT_SITE_CONFIG_PATH = path.resolve(ROOT_PATH, '../dev-private/enterprise/dev/site-config.json')
type WEB_BUILDER = 'esbuild' | 'webpack'
export const environmentConfig = {
export const ENVIRONMENT_CONFIG = {
/**
* ----------------------------------------
* Build configuration.
* ----------------------------------------
*/
NODE_ENV: process.env.NODE_ENV || 'development',
// Determines if build is running on CI.
CI: getEnvironmentBoolean('CI'),
// Enables `embed` Webpack entry point.
EMBED_DEVELOPMENT: getEnvironmentBoolean('EMBED_DEVELOPMENT'),
// Should Webpack serve `index.html` with `HTMLWebpackPlugin`.
WEBPACK_SERVE_INDEX: getEnvironmentBoolean('WEBPACK_SERVE_INDEX'),
// Enables `StatoscopeWebpackPlugin` that allows to analyze application bundle.
WEBPACK_BUNDLE_ANALYZER: getEnvironmentBoolean('WEBPACK_BUNDLE_ANALYZER'),
// Allow overriding default Webpack naming behavior for debugging
WEBPACK_USE_NAMED_CHUNKS: getEnvironmentBoolean('WEBPACK_USE_NAMED_CHUNKS'),
// Webpack is the default web build tool, and esbuild is an experimental option (see
// https://docs.sourcegraph.com/dev/background-information/web/build#esbuild).
DEV_WEB_BUILDER: (process.env.DEV_WEB_BUILDER === 'esbuild' ? 'esbuild' : 'webpack') as WEB_BUILDER,
/**
* ----------------------------------------
* Application features configuration.
* ----------------------------------------
*/
ENTERPRISE: getEnvironmentBoolean('ENTERPRISE'),
SOURCEGRAPHDOTCOM_MODE: getEnvironmentBoolean('SOURCEGRAPHDOTCOM_MODE'),
// Is reporting to Datadog/Sentry enabled.
ENABLE_MONITORING: getEnvironmentBoolean('ENABLE_MONITORING'),
// Is event logging with `TelemetryService` enabled.
ENABLE_TELEMETRY: getEnvironmentBoolean('ENABLE_TELEMETRY'),
/**
* ----------------------------------------
* Local environment configuration.
* ----------------------------------------
*/
SOURCEGRAPH_API_URL: process.env.SOURCEGRAPH_API_URL,
SOURCEGRAPH_HTTPS_DOMAIN: process.env.SOURCEGRAPH_HTTPS_DOMAIN || 'sourcegraph.test',
SOURCEGRAPH_HTTPS_PORT: Number(process.env.SOURCEGRAPH_HTTPS_PORT) || 3443,
SOURCEGRAPHDOTCOM_MODE: process.env.SOURCEGRAPHDOTCOM_MODE === 'true',
CLIENT_PROXY_DEVELOPMENT_PORT: Number(process.env.CLIENT_PROXY_DEVELOPMENT_PORT) || 3080,
WEBPACK_SERVE_INDEX: process.env.WEBPACK_SERVE_INDEX === 'true',
SOURCEGRAPH_HTTP_PORT: Number(process.env.SOURCEGRAPH_HTTP_PORT) || 3080,
SITE_CONFIG_PATH: process.env.SITE_CONFIG_PATH || DEFAULT_SITE_CONFIG_PATH,
ENTERPRISE: Boolean(process.env.ENTERPRISE),
// Webpack is the default web build tool, and esbuild is an experimental option (see
// https://docs.sourcegraph.com/dev/background-information/web/build#esbuild).
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
DEV_WEB_BUILDER: (process.env.DEV_WEB_BUILDER === 'esbuild' ? 'esbuild' : 'webpack') as 'esbuild' | 'webpack',
// TODO: do we use process.env.NO_HOT anywhere?
IS_HOT_RELOAD_ENABLED: process.env.NO_HOT !== 'true',
}
const { SOURCEGRAPH_HTTPS_PORT, SOURCEGRAPH_HTTPS_DOMAIN, CLIENT_PROXY_DEVELOPMENT_PORT } = environmentConfig
const { NODE_ENV, SOURCEGRAPH_HTTPS_DOMAIN, SOURCEGRAPH_HTTPS_PORT, SOURCEGRAPH_HTTP_PORT } = ENVIRONMENT_CONFIG
export const IS_DEVELOPMENT = NODE_ENV === 'development'
export const IS_PRODUCTION = NODE_ENV === 'production'
export const HTTPS_WEB_SERVER_URL = `https://${SOURCEGRAPH_HTTPS_DOMAIN}:${SOURCEGRAPH_HTTPS_PORT}`
export const HTTP_WEB_SERVER_URL = `http://localhost:${CLIENT_PROXY_DEVELOPMENT_PORT}`
export const HTTP_WEB_SERVER_URL = `http://localhost:${SOURCEGRAPH_HTTP_PORT}`

View File

@ -5,9 +5,9 @@ import lodash from 'lodash'
import { SourcegraphContext } from '../../src/jscontext'
import { environmentConfig } from './environment-config'
import { ENVIRONMENT_CONFIG } from './environment-config'
const { SITE_CONFIG_PATH } = environmentConfig
const { SITE_CONFIG_PATH } = ENVIRONMENT_CONFIG
// Get site-config from `SITE_CONFIG_PATH` as an object with camel cased keys.
export const getSiteConfig = (): Partial<SourcegraphContext> => {

View File

@ -3,3 +3,4 @@ export * from './create-js-context'
export * from './environment-config'
export * from './get-api-proxy-settings'
export * from './should-compress-response'
export * from './success-banner'

View File

@ -1,37 +0,0 @@
/* eslint-disable @typescript-eslint/no-unsafe-return, @typescript-eslint/restrict-plus-operands, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call */
const chalk = require('chalk')
module.exports = function printSuccessBanner(lines, log = console.log.bind(console)) {
const lineLength = getLineLength(lines)
const banner = '='.repeat(lineLength)
const emptyLine = ' '.repeat(lineLength)
log(chalk.bgGreenBright.black(banner))
log(chalk.bgGreenBright.black(emptyLine))
for (const line of lines) {
log(chalk.bgGreenBright.black(padLine(line, lineLength)))
log(chalk.bgGreenBright.black(emptyLine))
}
log(chalk.bgGreenBright.black(banner))
}
function padLine(content, length) {
const spaceRequired = length - content.length
const half = spaceRequired / 2
let line = `${' '.repeat(half)}${content}${' '.repeat(half)}`
if (line.length < length) {
line += ' '
}
return line
}
function getLineLength(lines) {
const longestLine = lines.reduce((accumulator, current) => {
if (accumulator < current.length) {
return current.length
}
return accumulator
}, 0)
return Math.max(40, longestLine + 10)
}

View File

@ -0,0 +1,36 @@
import chalk from 'chalk'
export function printSuccessBanner(lines: string[], log = console.log.bind(console)): void {
const lineLength = getLineLength(lines)
const banner = '='.repeat(lineLength)
const emptyLine = ' '.repeat(lineLength)
log(chalk.bgGreenBright.black(banner))
log(chalk.bgGreenBright.black(emptyLine))
for (const line of lines) {
log(chalk.bgGreenBright.black(padLine(line, lineLength)))
log(chalk.bgGreenBright.black(emptyLine))
}
log(chalk.bgGreenBright.black(banner))
}
function padLine(content: string, length: number): string {
const spaceRequired = length - content.length
const half = spaceRequired / 2
let line = `${' '.repeat(half)}${content}${' '.repeat(half)}`
if (line.length < length) {
line += ' '
}
return line
}
function getLineLength(lines: string[]): number {
const longestLine = lines.reduce((accumulator, current) => {
if (accumulator < current.length) {
return current.length
}
return accumulator
}, 0)
return Math.max(40, longestLine + 10)
}

View File

@ -2,11 +2,12 @@ import path from 'path'
import HtmlWebpackHarddiskPlugin from 'html-webpack-harddisk-plugin'
import HtmlWebpackPlugin, { TemplateParameter, Options } from 'html-webpack-plugin'
import signale from 'signale'
import { WebpackPluginInstance } from 'webpack'
import { createJsContext, environmentConfig, STATIC_ASSETS_PATH } from '../utils'
import { createJsContext, ENVIRONMENT_CONFIG, STATIC_ASSETS_PATH } from '../utils'
const { SOURCEGRAPH_HTTPS_PORT, NODE_ENV } = environmentConfig
const { SOURCEGRAPH_HTTPS_PORT, NODE_ENV } = ENVIRONMENT_CONFIG
export interface WebpackManifest {
/** Main app entry JS bundle */
@ -45,7 +46,7 @@ export const getHTMLPage = ({
<meta name="color-scheme" content="light dark"/>
${cssBundle ? `<link rel="stylesheet" href="${cssBundle}">` : ''}
${
environmentConfig.SOURCEGRAPHDOTCOM_MODE
ENVIRONMENT_CONFIG.SOURCEGRAPHDOTCOM_MODE
? '<script src="https://js.sentry-cdn.com/ae2f74442b154faf90b5ff0f7cd1c618.min.js" crossorigin="anonymous"></script>'
: ''
}
@ -77,6 +78,8 @@ const getBundleFromPath = (files: string[], filePrefix: string): string | undefi
files.find(file => file.startsWith(`/.assets/${filePrefix}`))
export const getHTMLWebpackPlugins = (): WebpackPluginInstance[] => {
signale.info('Serving `index.html` with `HTMLWebpackPlugin`.')
const htmlWebpackPlugin = new HtmlWebpackPlugin({
// `TemplateParameter` can be mutated. We need to tell TS that we didn't touch it.
templateContent: (({ htmlWebpackPlugin }: TemplateParameter): string => {

View File

@ -7,7 +7,6 @@ require('ts-node').register({
})
const compression = require('compression')
const log = require('fancy-log')
const gulp = require('gulp')
const { createProxyMiddleware } = require('http-proxy-middleware')
const signale = require('signale')
@ -30,28 +29,34 @@ const {
const { build: buildEsbuild } = require('./dev/esbuild/build')
const { esbuildDevelopmentServer } = require('./dev/esbuild/server')
const { DEV_SERVER_LISTEN_ADDR, DEV_SERVER_PROXY_TARGET_ADDR, shouldCompressResponse } = require('./dev/utils')
const { DEV_WEB_BUILDER } = require('./dev/utils/environment-config').environmentConfig
const printSuccessBanner = require('./dev/utils/success-banner')
const {
ENVIRONMENT_CONFIG,
HTTPS_WEB_SERVER_URL,
WEBPACK_STATS_OPTIONS,
DEV_SERVER_LISTEN_ADDR,
DEV_SERVER_PROXY_TARGET_ADDR,
shouldCompressResponse,
printSuccessBanner,
} = require('./dev/utils')
const webpackConfig = require('./webpack.config')
const WEBPACK_STATS_OPTIONS = {
all: false,
timings: true,
errors: true,
warnings: true,
colors: true,
}
const { DEV_WEB_BUILDER, SOURCEGRAPH_HTTPS_DOMAIN, SOURCEGRAPH_HTTPS_PORT } = ENVIRONMENT_CONFIG
/**
* @param {import('webpack').Stats} stats
*/
const logWebpackStats = stats => {
log(stats.toString(WEBPACK_STATS_OPTIONS))
signale.info(stats.toString(WEBPACK_STATS_OPTIONS))
}
function createWebApplicationCompiler() {
signale.info('Building web application with the environment config', ENVIRONMENT_CONFIG)
return createWebpackCompiler(webpackConfig)
}
async function webpack() {
const compiler = createWebpackCompiler(webpackConfig)
const compiler = createWebApplicationCompiler()
/** @type {import('webpack').Stats} */
const stats = await new Promise((resolve, reject) => {
compiler.run((error, stats) => (error ? reject(error) : resolve(stats)))
@ -68,24 +73,21 @@ const webBuild = DEV_WEB_BUILDER === 'webpack' ? webpack : buildEsbuild
* Watch files and update the webpack bundle on disk without starting a dev server.
*/
async function watchWebpack() {
const compiler = createWebpackCompiler(webpackConfig)
compiler.hooks.watchRun.tap('Notify', () => log('Webpack compiling...'))
const compiler = createWebApplicationCompiler()
compiler.hooks.watchRun.tap('Notify', () => signale.info('Webpack compiling...'))
await new Promise(() => {
compiler.watch({ aggregateTimeout: 300 }, (error, stats) => {
logWebpackStats(stats)
if (error || stats.hasErrors()) {
log.error('Webpack compilation error')
signale.error('Webpack compilation error')
} else {
log('Webpack compilation done')
signale.info('Webpack compilation done')
}
})
})
}
async function webpackDevelopmentServer() {
const sockHost = process.env.SOURCEGRAPH_HTTPS_DOMAIN || 'sourcegraph.test'
const sockPort = Number(process.env.SOURCEGRAPH_HTTPS_PORT || 3443)
/** @type {import('webpack-dev-server').ProxyConfigMap } */
const proxyConfig = {
'/': {
@ -105,7 +107,7 @@ async function webpackDevelopmentServer() {
const options = {
// react-refresh plugin triggers page reload if needed.
liveReload: false,
hot: !process.env.NO_HOT,
hot: true,
host: DEV_SERVER_LISTEN_ADDR.host,
port: DEV_SERVER_LISTEN_ADDR.port,
// Disable default DevServer compression. We need more fine grained compression to support streaming search.
@ -119,8 +121,8 @@ async function webpackDevelopmentServer() {
webSocketTransport: 'ws',
logging: 'verbose',
webSocketURL: {
hostname: sockHost,
port: sockPort,
hostname: SOURCEGRAPH_HTTPS_DOMAIN,
port: SOURCEGRAPH_HTTPS_PORT,
protocol: 'wss',
},
},
@ -138,7 +140,7 @@ async function webpackDevelopmentServer() {
webpackConfig.plugins.push(new DevServerPlugin(options))
}
const compiler = createWebpackCompiler(webpackConfig)
const compiler = createWebApplicationCompiler()
let compilationDoneOnce = false
compiler.hooks.done.tap('Print external URL', stats => {
stats = stats.toJson()
@ -151,7 +153,7 @@ async function webpackDevelopmentServer() {
}
compilationDoneOnce = true
printSuccessBanner(['✱ Sourcegraph is really ready now!', `Click here: https://${sockHost}:${sockPort}`])
printSuccessBanner(['✱ Sourcegraph is really ready now!', `Click here: ${HTTPS_WEB_SERVER_URL}`])
})
const server = new WebpackDevServer(options, compiler)

View File

@ -30,7 +30,7 @@
"lint:js": "NODE_OPTIONS=\"--max_old_space_size=16192\" eslint --cache '**/*.[tj]s?(x)'",
"lint:css": "stylelint 'src/**/*.scss' --quiet",
"browserslist": "browserslist",
"analyze-bundle": "WEBPACK_USE_NAMED_CHUNKS=true NODE_ENV=production ENTERPRISE=1 WEBPACK_ANALYZER=1 yarn build",
"analyze-bundle": "WEBPACK_USE_NAMED_CHUNKS=true NODE_ENV=production ENTERPRISE=1 WEBPACK_BUNDLE_ANALYZER=1 yarn build",
"bundlesize": "bundlesize --config=./bundlesize.config.js"
}
}

View File

@ -1,5 +1,5 @@
import { AbortError } from '@sourcegraph/common'
import { HTTPStatusError } from '@sourcegraph/http-client'
import { AbortError } from '@sourcegraph/shared/src/api/util'
import { shouldErrorBeReported } from './shouldErrorBeReported'

View File

@ -13,7 +13,7 @@ export const COHORT_ID_KEY = 'sourcegraphCohortId'
export const FIRST_SOURCE_URL_KEY = 'sourcegraphSourceUrl'
export const LAST_SOURCE_URL_KEY = 'sourcegraphRecentSourceUrl'
export const DEVICE_ID_KEY = 'sourcegraphDeviceId'
const isTelemetryDisabled = process.env.DISABLE_TELEMETRY
const isTelemetryEnabled = process.env.ENABLE_TELEMETRY || process.env.NODE_ENV === 'production'
export class EventLogger implements TelemetryService {
private hasStrippedQueryParameters = false
@ -57,7 +57,7 @@ export class EventLogger implements TelemetryService {
private logViewEventInternal(eventName: string, eventProperties?: any, logAsActiveUser = true): void {
const props = pageViewQueryParameters(window.location.href)
if (!isTelemetryDisabled) {
if (isTelemetryEnabled) {
serverAdmin.trackPageView(eventName, logAsActiveUser, eventProperties)
}
this.logToConsole(eventName, props)
@ -114,7 +114,7 @@ export class EventLogger implements TelemetryService {
if (window.context?.userAgentIsBot || !eventLabel) {
return
}
if (!isTelemetryDisabled) {
if (isTelemetryEnabled) {
serverAdmin.trackAction(eventLabel, eventProperties, publicArgument)
}
this.logToConsole(eventLabel, eventProperties, publicArgument)

View File

@ -5,7 +5,7 @@ const path = require('path')
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin')
const CompressionPlugin = require('compression-webpack-plugin')
const CssMinimizerWebpackPlugin = require('css-minimizer-webpack-plugin')
const logger = require('gulplog')
const mapValues = require('lodash/mapValues')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const webpack = require('webpack')
const { WebpackManifestPlugin } = require('webpack-manifest-plugin')
@ -23,85 +23,55 @@ const {
getMonacoTTFRule,
getBasicCSSLoader,
getStatoscopePlugin,
STATOSCOPE_STATS,
} = require('@sourcegraph/build-config')
const { IS_PRODUCTION, IS_DEVELOPMENT, ENVIRONMENT_CONFIG } = require('./dev/utils')
const { getHTMLWebpackPlugins } = require('./dev/webpack/get-html-webpack-plugins')
const { isHotReloadEnabled } = require('./src/integration/environment')
const mode = process.env.NODE_ENV === 'production' ? 'production' : 'development'
logger.info('Using mode', mode)
const {
NODE_ENV,
CI: IS_CI,
ENTERPRISE,
EMBED_DEVELOPMENT,
ENABLE_TELEMETRY,
ENABLE_MONITORING,
SOURCEGRAPH_API_URL,
WEBPACK_SERVE_INDEX,
WEBPACK_BUNDLE_ANALYZER,
WEBPACK_USE_NAMED_CHUNKS,
} = ENVIRONMENT_CONFIG
const isDevelopment = mode === 'development'
const isProduction = mode === 'production'
const isCI = process.env.CI === 'true'
const isCacheEnabled = isDevelopment && !isCI
const isEmbedDevelopment = isDevelopment && process.env.EMBED_DEVELOPMENT === 'true'
const IS_PERSISTENT_CACHE_ENABLED = IS_DEVELOPMENT && !IS_CI
const IS_EMBED_ENTRY_POINT_ENABLED = ENTERPRISE && (IS_PRODUCTION || (IS_DEVELOPMENT && EMBED_DEVELOPMENT))
/** Allow overriding default Webpack naming behavior for debugging */
const useNamedChunks = process.env.WEBPACK_USE_NAMED_CHUNKS === 'true'
const devtool = isProduction ? 'source-map' : 'eval-cheap-module-source-map'
const shouldServeIndexHTML = process.env.WEBPACK_SERVE_INDEX === 'true'
if (shouldServeIndexHTML) {
logger.info('Serving index.html with HTMLWebpackPlugin')
}
const webServerEnvironmentVariables = {
WEBPACK_SERVE_INDEX: JSON.stringify(process.env.WEBPACK_SERVE_INDEX),
SOURCEGRAPH_API_URL: JSON.stringify(process.env.SOURCEGRAPH_API_URL),
}
const shouldAnalyze = process.env.WEBPACK_ANALYZER === '1'
const STATOSCOPE_STATS = {
all: false, // disable all the stats
hash: true, // compilation hash
entrypoints: true,
chunks: true,
chunkModules: true, // modules
reasons: true, // modules reasons
ids: true, // IDs of modules and chunks (webpack 5)
dependentModules: true, // dependent modules of chunks (webpack 5)
chunkRelations: true, // chunk parents, children and siblings (webpack 5)
cachedAssets: true, // information about the cached assets (webpack 5)
nestedModules: true, // concatenated modules
usedExports: true,
providedExports: true, // provided imports
assets: true,
chunkOrigins: true, // chunks origins stats (to find out which modules require a chunk)
version: true, // webpack version
builtAt: true, // build at time
timings: true, // modules timing information
performance: true, // info about oversized assets
}
if (shouldAnalyze) {
logger.info('Running bundle analyzer')
const RUNTIME_ENV_VARIABLES = {
NODE_ENV,
ENABLE_TELEMETRY,
ENABLE_MONITORING,
...(WEBPACK_SERVE_INDEX && { SOURCEGRAPH_API_URL }),
}
const hotLoadablePaths = ['branded', 'shared', 'web', 'wildcard'].map(workspace =>
path.resolve(ROOT_PATH, 'client', workspace, 'src')
)
const isEnterpriseBuild = process.env.ENTERPRISE && Boolean(JSON.parse(process.env.ENTERPRISE))
const isEmbedEntrypointEnabled = isEnterpriseBuild && (isProduction || isEmbedDevelopment)
const enterpriseDirectory = path.resolve(__dirname, 'src', 'enterprise')
const styleLoader = isDevelopment ? 'style-loader' : MiniCssExtractPlugin.loader
const styleLoader = IS_DEVELOPMENT ? 'style-loader' : MiniCssExtractPlugin.loader
const extensionHostWorker = /main\.worker\.ts$/
/** @type {import('webpack').Configuration} */
const config = {
context: __dirname, // needed when running `gulp webpackDevServer` from the root dir
mode,
stats: shouldAnalyze
mode: IS_PRODUCTION ? 'production' : 'development',
stats: WEBPACK_BUNDLE_ANALYZER
? STATOSCOPE_STATS
: {
// Minimize logging in case if Webpack is used along with multiple other services.
// Use `normal` output preset in case of running standalone web server.
preset: shouldServeIndexHTML || isProduction ? 'normal' : 'errors-warnings',
preset: WEBPACK_SERVE_INDEX || IS_PRODUCTION ? 'normal' : 'errors-warnings',
errorDetails: true,
timings: true,
},
@ -111,9 +81,11 @@ const config = {
},
target: 'browserslist',
// Use cache only in `development` mode to speed up production build.
cache: isCacheEnabled && getCacheConfig({ invalidateCacheFiles: [path.resolve(__dirname, 'babel.config.js')] }),
cache:
IS_PERSISTENT_CACHE_ENABLED &&
getCacheConfig({ invalidateCacheFiles: [path.resolve(__dirname, 'babel.config.js')] }),
optimization: {
minimize: isProduction,
minimize: IS_PRODUCTION,
minimizer: [getTerserPlugin(), new CssMinimizerWebpackPlugin()],
splitChunks: {
cacheGroups: {
@ -124,7 +96,7 @@ const config = {
},
},
},
...(isDevelopment && {
...(IS_DEVELOPMENT && {
// Running multiple entries on a single page that do not share a runtime chunk from the same compilation is not supported.
// https://github.com/webpack/webpack-dev-server/issues/2792#issuecomment-808328432
runtimeChunk: isHotReloadEnabled ? 'single' : false,
@ -136,51 +108,48 @@ const config = {
entry: {
// Enterprise vs. OSS builds use different entrypoints. The enterprise entrypoint imports a
// strict superset of the OSS entrypoint.
app: isEnterpriseBuild ? path.join(enterpriseDirectory, 'main.tsx') : path.join(__dirname, 'src', 'main.tsx'),
app: ENTERPRISE ? path.join(enterpriseDirectory, 'main.tsx') : path.join(__dirname, 'src', 'main.tsx'),
// Embedding entrypoint. It uses a small subset of the main webapp intended to be embedded into
// iframes on 3rd party sites. Added only in production enterprise builds or if embed development is enabled.
...(isEmbedEntrypointEnabled && { embed: path.join(enterpriseDirectory, 'embed', 'main.tsx') }),
...(IS_EMBED_ENTRY_POINT_ENABLED && { embed: path.join(enterpriseDirectory, 'embed', 'main.tsx') }),
},
output: {
path: path.join(ROOT_PATH, 'ui', 'assets'),
// Do not [hash] for development -- see https://github.com/webpack/webpack-dev-server/issues/377#issuecomment-241258405
// Note: [name] will vary depending on the Webpack chunk. If specified, it will use a provided chunk name, otherwise it will fallback to a deterministic id.
filename:
mode === 'production' && !useNamedChunks ? 'scripts/[name].[contenthash].bundle.js' : 'scripts/[name].bundle.js',
IS_PRODUCTION && !WEBPACK_USE_NAMED_CHUNKS
? 'scripts/[name].[contenthash].bundle.js'
: 'scripts/[name].bundle.js',
chunkFilename:
mode === 'production' && !useNamedChunks ? 'scripts/[name]-[contenthash].chunk.js' : 'scripts/[name].chunk.js',
IS_PRODUCTION && !WEBPACK_USE_NAMED_CHUNKS ? 'scripts/[name]-[contenthash].chunk.js' : 'scripts/[name].chunk.js',
publicPath: '/.assets/',
globalObject: 'self',
pathinfo: false,
},
devtool,
devtool: IS_PRODUCTION ? 'source-map' : 'eval-cheap-module-source-map',
plugins: [
// Needed for React
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify(mode),
DISABLE_TELEMETRY: process.env.DISABLE_TELEMETRY,
...(shouldServeIndexHTML && webServerEnvironmentVariables),
},
'process.env': mapValues(RUNTIME_ENV_VARIABLES, JSON.stringify),
}),
getProvidePlugin(),
new MiniCssExtractPlugin({
// Do not [hash] for development -- see https://github.com/webpack/webpack-dev-server/issues/377#issuecomment-241258405
filename: mode === 'production' ? 'styles/[name].[contenthash].bundle.css' : 'styles/[name].bundle.css',
filename: IS_PRODUCTION ? 'styles/[name].[contenthash].bundle.css' : 'styles/[name].bundle.css',
}),
getMonacoWebpackPlugin(),
!shouldServeIndexHTML &&
!WEBPACK_SERVE_INDEX &&
new WebpackManifestPlugin({
writeToFileEmit: true,
fileName: 'webpack.manifest.json',
// Only output files that are required to run the application.
filter: ({ isInitial, name }) => isInitial || name?.includes('react'),
}),
...(shouldServeIndexHTML ? getHTMLWebpackPlugins() : []),
shouldAnalyze && getStatoscopePlugin(),
...(WEBPACK_SERVE_INDEX ? getHTMLWebpackPlugins() : []),
WEBPACK_BUNDLE_ANALYZER && getStatoscopePlugin(),
isHotReloadEnabled && new webpack.HotModuleReplacementPlugin(),
isHotReloadEnabled && new ReactRefreshWebpackPlugin({ overlay: false }),
isProduction &&
IS_PRODUCTION &&
new CompressionPlugin({
filename: '[path][base].gz',
algorithm: 'gzip',
@ -190,7 +159,7 @@ const config = {
level: 9,
},
}),
isProduction &&
IS_PRODUCTION &&
new CompressionPlugin({
filename: '[path][base].br',
algorithm: 'brotliCompress',
@ -224,7 +193,7 @@ const config = {
include: hotLoadablePaths,
exclude: extensionHostWorker,
use: [
...(isProduction ? ['thread-loader'] : []),
...(IS_PRODUCTION ? ['thread-loader'] : []),
{
loader: 'babel-loader',
options: {
@ -237,13 +206,13 @@ const config = {
{
test: /\.[jt]sx?$/,
exclude: [...hotLoadablePaths, extensionHostWorker],
use: [...(isProduction ? ['thread-loader'] : []), getBabelLoader()],
use: [...(IS_PRODUCTION ? ['thread-loader'] : []), getBabelLoader()],
},
{
test: /\.(sass|scss)$/,
// CSS Modules loaders are only applied when the file is explicitly named as CSS module stylesheet using the extension `.module.scss`.
include: /\.module\.(sass|scss)$/,
use: getCSSLoaders(styleLoader, getCSSModulesLoader({ sourceMap: isDevelopment })),
use: getCSSLoaders(styleLoader, getCSSModulesLoader({ sourceMap: IS_DEVELOPMENT })),
},
{
test: /\.(sass|scss)$/,

View File

@ -903,14 +903,11 @@ commandsets:
- caddy
env:
ENTERPRISE: 1
DISABLE_TELEMETRY: true
oss-web-standalone:
commands:
- web-standalone-http
- caddy
env:
DISABLE_TELEMETRY: true
web-standalone-prod:
commands: