sourcegraph/client/browser/scripts/tasks.ts
Camden Cheek 55fdf1b105
Browser extension: disable inline extensions firefox (#57881)
This disables the bundling of code intel extensions with the firefox browser extension. The bundled files are minified, and I failed to run the build steps for the extensions locally (they're pretty out of date), so I can't really test the changes needed to build a non-minified version.

For now, rather than spending more time on extensions that live in a hazy state of support, this just removes the extensions from the firefox addon so we don't get removed from the store.
2023-10-26 00:43:45 +00:00

261 lines
9.2 KiB
TypeScript

/* eslint no-sync: warn */
import fs from 'fs'
import path from 'path'
import { omit, cloneDeep, curry } from 'lodash'
import shelljs from 'shelljs'
import signale from 'signale'
import utcVersion from 'utc-version'
import manifestSpec from '../src/browser-extension/manifest.spec.json'
import schema from '../src/browser-extension/schema.json'
/**
* If true, add <all_urls> to the permissions in the manifest.
* This is needed for e2e and integration tests because it is not possible to accept the
* permission prompt with puppeteer.
*/
const EXTENSION_PERMISSIONS_ALL_URLS = Boolean(
process.env.EXTENSION_PERMISSIONS_ALL_URLS && JSON.parse(process.env.EXTENSION_PERMISSIONS_ALL_URLS)
)
type BuildEnvironment = 'dev' | 'prod'
type Browser = 'firefox' | 'chrome' | 'safari' | 'edge'
const BUILDS_DIR = 'build'
/*
* Use a UTC-timestamp-based as the version string, generated at build-time.
*
* If enabled, the version string will depend on the timestamp when building, so
* it will vary with every build. Uses the `utc-version` module.
*
* To get a reproducible build, disable this and set a version manually in
* `manifest.spec.json`.
*/
const useUtcVersion = true
function ensurePaths(browser?: Browser): void {
shelljs.mkdir('-p', 'build/dist')
shelljs.mkdir('-p', 'build/bundles')
if (browser) {
shelljs.mkdir('-p', `build/${browser}`)
} else {
shelljs.mkdir('-p', 'build/chrome')
shelljs.mkdir('-p', 'build/edge')
shelljs.mkdir('-p', 'build/firefox')
shelljs.mkdir('-p', 'build/safari')
}
}
/**
* Create the Safari extension app Xcode project and build it.
*/
function buildSafariExtensionApp(): void {
const safariWebExtensionConverterOptions = [
'build/safari',
'--project-location',
'build/',
'--app-name',
'"Sourcegraph for Safari"',
'--bundle-identifier',
'"com.sourcegraph.Sourcegraph-for-Safari"',
'--copy-resources',
'--swift',
'--force',
'--no-open',
]
const xcodebuildOptions = [
'-quiet',
'-project',
'"./build/Sourcegraph for Safari/Sourcegraph for Safari.xcodeproj"',
'build',
]
shelljs.echo('y').exec(`xcrun safari-web-extension-converter ${safariWebExtensionConverterOptions.join(' ')}`)
shelljs.exec(`xcodebuild ${xcodebuildOptions.join(' ')}`)
shelljs.mv('./build/Sourcegraph for Safari/build/Release/Sourcegraph for Safari.app', './build/bundles')
}
export function copyAssets(): void {
signale.await('Copy assets')
const directory = 'build/dist'
shelljs.rm('-rf', directory)
shelljs.mkdir('-p', directory)
shelljs.cp('-R', 'assets/*', directory)
shelljs.cp('-R', 'src/browser-extension/pages/*', directory)
signale.success('Assets copied')
}
function copyExtensionAssets(toDirectory: string): void {
shelljs.mkdir('-p', `${toDirectory}/js`, `${toDirectory}/css`, `${toDirectory}/img`)
shelljs.cp('build/dist/js/*.bundle.js', `${toDirectory}/js`)
shelljs.cp('build/dist/css/*.bundle.css', `${toDirectory}/css`)
shelljs.cp('-R', 'build/dist/img/*', `${toDirectory}/img`)
shelljs.cp('build/dist/*.html', toDirectory)
}
/**
* When building with inline (bundled) Sourcegraph extensions, copy the built Sourcegraph extensions into the output.
* They will be available as `web_accessible_resources`.
*
* The pre-requisite step is to first clone, build, and copy into `build/extensions`.
*/
export function copyInlineExtensions(toDirectory: string): void {
shelljs.cp('-R', 'build/extensions', toDirectory)
}
export function copyIntegrationAssets(): void {
shelljs.mkdir('-p', 'build/integration/scripts')
shelljs.mkdir('-p', 'build/integration/css')
// The destination filename is hardcoded in
// https://sourcegraph.com/github.com/sourcegraph/phabricator-extension@master/-/blob/src/application/SourcegraphApplication.php?L33.
shelljs.cp(
'build/dist/js/phabricatorNativeIntegration.main.bundle.js',
'build/integration/scripts/phabricator.bundle.js'
)
// The destination filename is hardcoded in
// https://sourcegraph.com/github.com/sourcegraph/bitbucket-server-plugin@master/-/blob/src/main/resources/js/sourcegraph-bitbucket.js?L23:52.
shelljs.cp('build/dist/js/nativeIntegration.main.bundle.js', 'build/integration/scripts/integration.bundle.js')
shelljs.cp('build/dist/js/extensionHostWorker.bundle.js', 'build/integration/scripts')
shelljs.cp('build/dist/css/app.bundle.css', 'build/integration/css')
shelljs.cp('build/dist/css/contentPage.main.bundle.css', 'build/integration/css')
shelljs.cp('src/native-integration/extensionHostFrame.html', 'build/integration')
copyInlineExtensions('build/integration')
// Copy to the dist directory so that these files can be served by
// the webapp.
shelljs.mkdir('-p', '../../client/web/dist/extension')
shelljs.cp('-r', 'build/integration/*', '../../client/web/dist/extension')
}
const BROWSER_TITLES = {
firefox: 'Firefox',
chrome: 'Chrome',
edge: 'Edge',
safari: 'Safari',
} as const
/**
* Names of the zipped bundles for the browsers that use a zipped bundle.
*/
const BROWSER_BUNDLE_ZIPS: Partial<Record<Browser, string>> = {
firefox: 'firefox-bundle.xpi',
chrome: 'chrome-bundle.zip',
edge: 'edge-bundle.zip',
}
/**
* Fields to exclude from the manifest, for each browser.
*/
const BROWSER_BLOCKLIST = {
chrome: ['applications'] as const,
edge: ['applications'] as const,
firefox: ['key'] as const,
safari: [] as const,
}
function writeSchema(writeDirectory: string): void {
fs.writeFileSync(`${writeDirectory}/schema.json`, JSON.stringify(schema, null, 4))
}
const version = process.env.BROWSER_EXTENSION_VERSION || utcVersion()
function writeManifest(environment: BuildEnvironment, browser: Browser, writeDirectory: string): void {
const extensionInfo = cloneDeep(manifestSpec)
const manifest = {
...omit(extensionInfo, ['dev', 'prod', ...BROWSER_BLOCKLIST[browser]]),
...omit(extensionInfo[environment], BROWSER_BLOCKLIST[browser]),
}
if (EXTENSION_PERMISSIONS_ALL_URLS) {
manifest.permissions!.push('<all_urls>')
/** Set key to make extension id deterministic */
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
manifest.key = manifestSpec.dev.key
signale.info('Adding <all_urls> to permissions because of env var setting')
}
if (browser === 'firefox') {
manifest.permissions!.push('<all_urls>')
delete manifest.storage
}
if (browser === 'safari') {
// If any modifications need to be done to the manifest for Safari, they
// can be done here.
if (manifest.description!.length > 112) {
throw new Error('Manifest description field cannot be longer than 112 characters. (Safari)')
}
}
// Add the inline extensions to web accessible resources
manifest.web_accessible_resources = manifest.web_accessible_resources || []
manifest.web_accessible_resources.push('extensions/*')
delete manifest.$schema
if (environment === 'prod' && useUtcVersion) {
manifest.version = version
}
fs.writeFileSync(`${writeDirectory}/manifest.json`, JSON.stringify(manifest, null, 4))
}
const buildForBrowser = curry((browser: Browser, environment: BuildEnvironment): (() => void) => () => {
// Allow only building for specific browser targets.
// Useful in local dev for faster builds.
if (process.env.TARGETS && !process.env.TARGETS.includes(browser)) {
signale.info(`Skipping build ${browser} because TARGETS=${process.env.TARGETS}`)
return
}
const title = BROWSER_TITLES[browser]
signale.await(`Building the ${title} ${environment} bundle`)
ensurePaths(browser)
const buildDirectory = path.resolve(process.cwd(), `${BUILDS_DIR}/${browser}`)
writeManifest(environment, browser, buildDirectory)
writeSchema(buildDirectory)
copyExtensionAssets(buildDirectory)
// TODO(@camdencheek): figure out whether we actually want to continue
// shipping the inline extensions with the browser extensions. For now,
// skip them for the firefox extension because they are being detected
// as non-human-readable assets, and will cause removal from the addon store.
if (browser !== 'firefox') {
copyInlineExtensions(buildDirectory)
}
// Create a bundle by zipping the web extension directory.
const browserBundleZip = BROWSER_BUNDLE_ZIPS[browser]
if (browserBundleZip) {
const zipDestination = path.resolve(process.cwd(), `${BUILDS_DIR}/bundles/${browserBundleZip}`)
if (zipDestination) {
shelljs.mkdir('-p', `./${BUILDS_DIR}/bundles`)
shelljs.exec(`cd ${buildDirectory} && zip -q -r ${zipDestination} *`)
}
}
// Safari-specific build step
if (browser === 'safari') {
buildSafariExtensionApp()
}
signale.success(`Done building the ${title} ${environment} bundle`)
})
export const buildFirefox = buildForBrowser('firefox')
export const buildChrome = buildForBrowser('chrome')
export const buildEdge = buildForBrowser('edge')
export const buildSafari = buildForBrowser('safari')