mirror of
https://github.com/zen-browser/surfer.git
synced 2025-07-08 01:10:03 +02:00
♻️ Refactor download to work with new addons
This also makes things significantly easier to read
This commit is contained in:
parent
ac3ae4a5db
commit
27a74575b3
4 changed files with 351 additions and 367 deletions
|
@ -1,40 +1,25 @@
|
||||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
import {
|
|
||||||
existsSync,
|
|
||||||
mkdirSync,
|
|
||||||
readFileSync,
|
|
||||||
rmdirSync,
|
|
||||||
unlinkSync,
|
|
||||||
writeFileSync,
|
|
||||||
} from 'fs'
|
|
||||||
import { join, posix, resolve, sep } from 'path'
|
|
||||||
|
|
||||||
import execa from 'execa'
|
|
||||||
import Listr from 'listr'
|
|
||||||
|
|
||||||
import { bin_name, config } from '..'
|
import { bin_name, config } from '..'
|
||||||
import { BASH_PATH, ENGINE_DIR, MELON_TMP_DIR } from '../constants'
|
|
||||||
import {
|
|
||||||
commandExistsSync,
|
|
||||||
configDispatch,
|
|
||||||
delay,
|
|
||||||
ensureDir,
|
|
||||||
getConfig,
|
|
||||||
walkDirectoryTree,
|
|
||||||
windowsPathToUnix,
|
|
||||||
} from '../utils'
|
|
||||||
import { downloadFileToLocation } from '../utils/download'
|
|
||||||
import { readItem } from '../utils/store'
|
|
||||||
import { discard } from './discard'
|
|
||||||
import { init } from './init'
|
|
||||||
import { log } from '../log'
|
import { log } from '../log'
|
||||||
|
|
||||||
const gFFVersion = getConfig().version.version
|
import {
|
||||||
|
setupFirefoxSource,
|
||||||
|
shouldSetupFirefoxSource,
|
||||||
|
} from './download/firefox'
|
||||||
|
import {
|
||||||
|
addAddonsToMozBuild,
|
||||||
|
downloadAddon,
|
||||||
|
generateAddonMozBuild,
|
||||||
|
initializeAddon,
|
||||||
|
resolveAddonDownloadUrl,
|
||||||
|
unpackAddon,
|
||||||
|
} from './download/addon'
|
||||||
|
|
||||||
export const download = async (): Promise<void> => {
|
export const download = async (): Promise<void> => {
|
||||||
const version = gFFVersion
|
const version = config.version.version
|
||||||
|
|
||||||
// If gFFVersion isn't specified, provide legible error
|
// If gFFVersion isn't specified, provide legible error
|
||||||
if (!version) {
|
if (!version) {
|
||||||
|
@ -49,330 +34,28 @@ export const download = async (): Promise<void> => {
|
||||||
...config.addons[addon],
|
...config.addons[addon],
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// Listr and typescript do not mix. Just specify any and move on with the
|
if (shouldSetupFirefoxSource()) {
|
||||||
// rest of our life
|
await setupFirefoxSource(version)
|
||||||
//
|
}
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
await new Listr<Record<string, string | any>>(
|
|
||||||
[
|
|
||||||
{
|
|
||||||
title: 'Downloading firefox source',
|
|
||||||
skip: () => {
|
|
||||||
if (
|
|
||||||
existsSync(ENGINE_DIR) &&
|
|
||||||
existsSync(resolve(ENGINE_DIR, 'toolkit', 'moz.build'))
|
|
||||||
) {
|
|
||||||
return 'Firefox has already been downloaded, unpacked and inited'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
task: async (ctx, task) => {
|
|
||||||
ctx.firefoxSourceTar = await downloadFirefoxSource(version, task)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Unpack firefox source',
|
|
||||||
enabled: (ctx) => ctx.firefoxSourceTar,
|
|
||||||
task: async (ctx, task) => {
|
|
||||||
await unpackFirefoxSource(ctx.firefoxSourceTar, task)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Init firefox',
|
|
||||||
enabled: (ctx) => ctx.firefoxSourceTar && !process.env.CI_SKIP_INIT,
|
|
||||||
task: async (_ctx, task) => await init(ENGINE_DIR, task),
|
|
||||||
},
|
|
||||||
...addons
|
|
||||||
.map((addon) => includeAddon(addon.name, addon.url, addon.id))
|
|
||||||
.reduce((acc, cur) => [...acc, ...cur], []),
|
|
||||||
{
|
|
||||||
title: 'Add addons to mozbuild',
|
|
||||||
task: async () => {
|
|
||||||
// Discard the file to make sure it has no changes
|
|
||||||
await discard('browser/extensions/moz.build')
|
|
||||||
|
|
||||||
const path = join(ENGINE_DIR, 'browser', 'extensions', 'moz.build')
|
for (const addon of addons) {
|
||||||
|
const downloadUrl = await resolveAddonDownloadUrl(addon)
|
||||||
|
const downloadedXPI = await downloadAddon(downloadUrl, addon)
|
||||||
|
|
||||||
// Append all the files to the bottom
|
if (!downloadedXPI) {
|
||||||
writeFileSync(
|
log.info(`Skipping ${addon.name}... Already installed`)
|
||||||
path,
|
continue
|
||||||
`${readFileSync(path).toString()}\nDIRS += [${addons
|
|
||||||
.map((addon) => addon.name)
|
|
||||||
.sort()
|
|
||||||
.map((addon) => `"${addon}"`)
|
|
||||||
.join(',')}]`
|
|
||||||
)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Cleanup',
|
|
||||||
task: (ctx) => {
|
|
||||||
if (ctx.firefoxSourceTar) {
|
|
||||||
if (typeof ctx.firefoxSourceTar !== 'string') {
|
|
||||||
log.askForReport()
|
|
||||||
log.error(
|
|
||||||
`The type ctx.firefoxSourceTar was ${typeof ctx.firefoxSourceTar} when it should have been a string`
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
unlinkSync(resolve(MELON_TMP_DIR, ctx.firefoxSourceTar))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
{
|
|
||||||
renderer: log.isDebug ? 'verbose' : 'default',
|
|
||||||
}
|
}
|
||||||
).run()
|
|
||||||
|
await unpackAddon(downloadedXPI, addon)
|
||||||
|
await generateAddonMozBuild(addon)
|
||||||
|
await initializeAddon(addon)
|
||||||
|
}
|
||||||
|
|
||||||
|
await addAddonsToMozBuild(addons)
|
||||||
|
|
||||||
log.success(
|
log.success(
|
||||||
`You should be ready to make changes to ${config.name}.\n\n\t You should import the patches next, run |${bin_name} import|.\n\t To begin building ${config.name}, run |${bin_name} build|.`
|
`You should be ready to make changes to ${config.name}.\n\n\t You should import the patches next, run |${bin_name} import|.\n\t To begin building ${config.name}, run |${bin_name} build|.`
|
||||||
)
|
)
|
||||||
console.log()
|
console.log()
|
||||||
}
|
}
|
||||||
|
|
||||||
const includeAddon = (
|
|
||||||
name: string,
|
|
||||||
downloadURL: string,
|
|
||||||
id: string
|
|
||||||
): Listr.ListrTask<Record<string, string>>[] => {
|
|
||||||
const tempFile = join(MELON_TMP_DIR, name + '.xpi')
|
|
||||||
const outPath = join(ENGINE_DIR, 'browser', 'extensions', name)
|
|
||||||
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
title: `Download addon from ${downloadURL}`,
|
|
||||||
skip: () => {
|
|
||||||
if (existsSync(outPath)) {
|
|
||||||
// Now we need to do some tests. First, if there is no cache file,
|
|
||||||
// we must discard the existing folder and download the file again.
|
|
||||||
// If there is a cache file and the cache file points to the same path
|
|
||||||
// we can return and skip the download.
|
|
||||||
|
|
||||||
const extensionCache = readItem<{ url: string }>(name)
|
|
||||||
|
|
||||||
if (extensionCache.isNone()) {
|
|
||||||
// We haven't stored it in the cache, therefore we need to redonwload
|
|
||||||
// it
|
|
||||||
} else {
|
|
||||||
const cache = extensionCache.unwrap()
|
|
||||||
if (cache.url == downloadURL) {
|
|
||||||
return `${downloadURL} has already been loaded to ${name}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
task: async (ctx, task) => {
|
|
||||||
if (existsSync(tempFile)) {
|
|
||||||
unlinkSync(tempFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
await downloadFileToLocation(
|
|
||||||
downloadURL,
|
|
||||||
tempFile,
|
|
||||||
(msg) => (task.output = msg)
|
|
||||||
)
|
|
||||||
ctx[name] = tempFile
|
|
||||||
|
|
||||||
// I do not know why, but this delay causes unzip to work reliably
|
|
||||||
await delay(200)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: `Unpack to ${name}`,
|
|
||||||
enabled: (ctx) => typeof ctx[name] !== 'undefined',
|
|
||||||
task: async (ctx, task) => {
|
|
||||||
task.output = `Unpacking extension...`
|
|
||||||
|
|
||||||
// I do not know why, but this delay causes unzip to work reliably
|
|
||||||
await delay(200)
|
|
||||||
|
|
||||||
if (existsSync(outPath)) {
|
|
||||||
rmdirSync(outPath, { recursive: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
mkdirSync(outPath, {
|
|
||||||
recursive: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
await configDispatch('unzip', {
|
|
||||||
args: [
|
|
||||||
windowsPathToUnix(ctx[name]),
|
|
||||||
'-d',
|
|
||||||
windowsPathToUnix(outPath),
|
|
||||||
],
|
|
||||||
killOnError: true,
|
|
||||||
logger: (data) => (task.output = data),
|
|
||||||
shell: 'unix',
|
|
||||||
})
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Generate mozbuild',
|
|
||||||
enabled: (ctx) => typeof ctx[name] !== 'undefined',
|
|
||||||
task: async () => {
|
|
||||||
const files = await walkDirectoryTree(outPath)
|
|
||||||
|
|
||||||
// Because the tree has the potential of being infinitely recursive, we
|
|
||||||
// cannot possibly know the the type of the tree
|
|
||||||
//
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
function runTree(tree: any, parent: string): string {
|
|
||||||
if (Array.isArray(tree)) {
|
|
||||||
return tree
|
|
||||||
.sort()
|
|
||||||
.map(
|
|
||||||
(file) =>
|
|
||||||
`FINAL_TARGET_FILES.features["${id}"]${parent} += ["${file
|
|
||||||
.replace(outPath + '/', '')
|
|
||||||
.replace(outPath, '')}"]`
|
|
||||||
)
|
|
||||||
.join('\n')
|
|
||||||
}
|
|
||||||
|
|
||||||
const current = (tree['.'] as string[])
|
|
||||||
.sort()
|
|
||||||
// Don't use windows path, which brick mozbuild
|
|
||||||
.map((f) => windowsPathToUnix(f))
|
|
||||||
.map(
|
|
||||||
(f) =>
|
|
||||||
`FINAL_TARGET_FILES.features["${id}"]${parent} += ["${f
|
|
||||||
.replace(outPath + '/', '')
|
|
||||||
.replace(outPath, '')}"]`
|
|
||||||
)
|
|
||||||
.join('\n')
|
|
||||||
|
|
||||||
const children = Object.keys(tree)
|
|
||||||
.filter((folder) => folder !== '.')
|
|
||||||
.filter((folder) => typeof tree[folder] !== 'undefined')
|
|
||||||
.map((folder) => runTree(tree[folder], `${parent}["${folder}"]`))
|
|
||||||
.join('\n')
|
|
||||||
|
|
||||||
return `${current}\n${children}`
|
|
||||||
}
|
|
||||||
|
|
||||||
writeFileSync(
|
|
||||||
join(outPath, 'moz.build'),
|
|
||||||
`
|
|
||||||
DEFINES["MOZ_APP_VERSION"] = CONFIG["MOZ_APP_VERSION"]
|
|
||||||
DEFINES["MOZ_APP_MAXVERSION"] = CONFIG["MOZ_APP_MAXVERSION"]
|
|
||||||
|
|
||||||
${runTree(files, '')}`
|
|
||||||
)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// This step allows patches to be applied to extensions that are downloaded
|
|
||||||
// providing more flexibility to the browser developers
|
|
||||||
title: 'Initializing',
|
|
||||||
enabled: (ctx) => typeof ctx[name] !== 'undefined',
|
|
||||||
task: async (ctx, task) => {
|
|
||||||
await configDispatch('git', {
|
|
||||||
args: ['add', '-f', '.'],
|
|
||||||
cwd: outPath,
|
|
||||||
logger: (data) => (task.output = data),
|
|
||||||
})
|
|
||||||
await configDispatch('git', {
|
|
||||||
args: ['commit', '-m', name],
|
|
||||||
cwd: ENGINE_DIR,
|
|
||||||
logger: (data) => (task.output = data),
|
|
||||||
})
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
async function unpackFirefoxSource(
|
|
||||||
name: string,
|
|
||||||
task: Listr.ListrTaskWrapper<never>
|
|
||||||
): Promise<void> {
|
|
||||||
let cwd = process.cwd().split(sep).join(posix.sep)
|
|
||||||
|
|
||||||
if (process.platform == 'win32') {
|
|
||||||
cwd = './'
|
|
||||||
}
|
|
||||||
|
|
||||||
task.output = `Unpacking Firefox...`
|
|
||||||
|
|
||||||
if (existsSync(ENGINE_DIR)) rmdirSync(ENGINE_DIR)
|
|
||||||
mkdirSync(ENGINE_DIR)
|
|
||||||
|
|
||||||
let tarExec = 'tar'
|
|
||||||
|
|
||||||
// On MacOS, we need to use gnu tar, otherwise tar doesn't behave how we
|
|
||||||
// would expect it to behave, so this section is responsible for handling
|
|
||||||
// that
|
|
||||||
//
|
|
||||||
// If BSD tar adds --transform support in the future, we can use that
|
|
||||||
// instead
|
|
||||||
if (process.platform == 'darwin') {
|
|
||||||
// GNU Tar doesn't come preinstalled on any MacOS machines, so we need to
|
|
||||||
// check for it and ask for the user to install it if necessary
|
|
||||||
if (!commandExistsSync('gtar')) {
|
|
||||||
throw new Error(
|
|
||||||
`GNU Tar is required to extract Firefox's source on MacOS. Please install it using the command |brew install gnu-tar| and try again`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
tarExec = 'gtar'
|
|
||||||
}
|
|
||||||
|
|
||||||
await execa(
|
|
||||||
tarExec,
|
|
||||||
[
|
|
||||||
'--strip-components=1',
|
|
||||||
process.platform == 'win32' ? '--force-local' : null,
|
|
||||||
'-xf',
|
|
||||||
windowsPathToUnix(resolve(MELON_TMP_DIR, name)),
|
|
||||||
'-C',
|
|
||||||
windowsPathToUnix(ENGINE_DIR),
|
|
||||||
].filter((x) => x) as string[],
|
|
||||||
{
|
|
||||||
// HACK: Use bash shell on windows to get a sane version of tar that works
|
|
||||||
shell: BASH_PATH || false,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Make this function cache its output
|
|
||||||
async function downloadFirefoxSource(
|
|
||||||
version: string,
|
|
||||||
task: Listr.ListrTaskWrapper<never>
|
|
||||||
) {
|
|
||||||
const base = `https://archive.mozilla.org/pub/firefox/releases/${version}/source/`
|
|
||||||
const filename = `firefox-${version}.source.tar.xz`
|
|
||||||
|
|
||||||
const url = base + filename
|
|
||||||
|
|
||||||
const fsParent = MELON_TMP_DIR
|
|
||||||
const fsSaveLocation = resolve(fsParent, filename)
|
|
||||||
|
|
||||||
task.output = `Locating Firefox release ${version}...`
|
|
||||||
|
|
||||||
await ensureDir(fsParent)
|
|
||||||
|
|
||||||
if (existsSync(fsSaveLocation)) {
|
|
||||||
task.output = 'Using cached download'
|
|
||||||
return filename
|
|
||||||
}
|
|
||||||
|
|
||||||
if (version.includes('b'))
|
|
||||||
task.output =
|
|
||||||
'WARNING Version includes non-numeric characters. This is probably a beta.'
|
|
||||||
|
|
||||||
// Do not re-download if there is already an existing workspace present
|
|
||||||
if (existsSync(ENGINE_DIR)) {
|
|
||||||
log.error(
|
|
||||||
`Workspace already exists.\nRemove that workspace and run |${bin_name} download ${version}| again.`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
task.output = `Downloading Firefox release ${version}...`
|
|
||||||
|
|
||||||
await downloadFileToLocation(
|
|
||||||
url,
|
|
||||||
resolve(MELON_TMP_DIR, filename),
|
|
||||||
(message) => (task.output = message)
|
|
||||||
)
|
|
||||||
return filename
|
|
||||||
}
|
|
||||||
|
|
218
src/commands/download/addon.ts
Normal file
218
src/commands/download/addon.ts
Normal file
|
@ -0,0 +1,218 @@
|
||||||
|
import {
|
||||||
|
existsSync,
|
||||||
|
mkdirSync,
|
||||||
|
readFileSync,
|
||||||
|
rmdirSync,
|
||||||
|
unlinkSync,
|
||||||
|
writeFileSync,
|
||||||
|
} from 'fs'
|
||||||
|
import { join } from 'path'
|
||||||
|
import { isMatch } from 'picomatch'
|
||||||
|
import { ENGINE_DIR, MELON_TMP_DIR } from '../../constants'
|
||||||
|
import { log } from '../../log'
|
||||||
|
|
||||||
|
import {
|
||||||
|
AddonInfo,
|
||||||
|
configDispatch,
|
||||||
|
delay,
|
||||||
|
walkDirectoryTree,
|
||||||
|
windowsPathToUnix,
|
||||||
|
} from '../../utils'
|
||||||
|
import { downloadFileToLocation } from '../../utils/download'
|
||||||
|
import { readItem } from '../../utils/store'
|
||||||
|
import { discard } from '../discard'
|
||||||
|
|
||||||
|
export async function resolveAddonDownloadUrl(
|
||||||
|
addon: AddonInfo
|
||||||
|
): Promise<string> {
|
||||||
|
switch (addon.platform) {
|
||||||
|
case 'url':
|
||||||
|
return addon.url
|
||||||
|
|
||||||
|
case 'amo':
|
||||||
|
return (
|
||||||
|
await (
|
||||||
|
await fetch(
|
||||||
|
`https://addons.mozilla.org/api/v4/addons/addon/${addon.amoId}/versions/`
|
||||||
|
)
|
||||||
|
).json()
|
||||||
|
).results[0].files[0].url
|
||||||
|
|
||||||
|
case 'github':
|
||||||
|
return (
|
||||||
|
(
|
||||||
|
((
|
||||||
|
await (
|
||||||
|
await fetch(
|
||||||
|
`https://api.github.com/repos/${addon.repo}/releases/tags/${addon.version}`
|
||||||
|
)
|
||||||
|
).json()
|
||||||
|
).assets as {
|
||||||
|
url: string
|
||||||
|
browser_download_url: string
|
||||||
|
name: string
|
||||||
|
}[]) || []
|
||||||
|
).find((asset) => isMatch(asset.name, addon.fileGlob))
|
||||||
|
?.browser_download_url || 'failed'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function downloadAddon(
|
||||||
|
url: string,
|
||||||
|
addon: AddonInfo & { name: string }
|
||||||
|
): Promise<string | false> {
|
||||||
|
const tempFile = join(MELON_TMP_DIR, addon.name + '.xpi')
|
||||||
|
const outPath = join(ENGINE_DIR, 'browser', 'extensions', addon.name)
|
||||||
|
|
||||||
|
log.info(`Download addon from ${url}`)
|
||||||
|
|
||||||
|
if (existsSync(outPath)) {
|
||||||
|
// Now we need to do some tests. First, if there is no cache file,
|
||||||
|
// we must discard the existing folder and download the file again.
|
||||||
|
// If there is a cache file and the cache file points to the same path
|
||||||
|
// we can return and skip the download.
|
||||||
|
|
||||||
|
const extensionCache = readItem<{ url: string }>(addon.name)
|
||||||
|
|
||||||
|
if (extensionCache.isNone()) {
|
||||||
|
// We haven't stored it in the cache, therefore we need to redonwload
|
||||||
|
// it
|
||||||
|
} else {
|
||||||
|
const cache = extensionCache.unwrap()
|
||||||
|
if (cache.url == url) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existsSync(tempFile)) {
|
||||||
|
unlinkSync(tempFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
await downloadFileToLocation(url, tempFile)
|
||||||
|
|
||||||
|
// I do not know why, but this delay causes unzip to work reliably
|
||||||
|
await delay(200)
|
||||||
|
|
||||||
|
return tempFile
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function unpackAddon(
|
||||||
|
path: string,
|
||||||
|
addon: AddonInfo & { name: string }
|
||||||
|
) {
|
||||||
|
const outPath = join(ENGINE_DIR, 'browser', 'extensions', addon.name)
|
||||||
|
|
||||||
|
log.info(`Unpacking extension...`)
|
||||||
|
|
||||||
|
// I do not know why, but this delay causes unzip to work reliably
|
||||||
|
await delay(200)
|
||||||
|
|
||||||
|
if (existsSync(outPath)) {
|
||||||
|
rmdirSync(outPath, { recursive: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
mkdirSync(outPath, {
|
||||||
|
recursive: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
await configDispatch('unzip', {
|
||||||
|
args: [windowsPathToUnix(path), '-d', windowsPathToUnix(outPath)],
|
||||||
|
killOnError: true,
|
||||||
|
shell: 'unix',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateAddonMozBuild(
|
||||||
|
addon: AddonInfo & { name: string }
|
||||||
|
) {
|
||||||
|
const outPath = join(ENGINE_DIR, 'browser', 'extensions', addon.name)
|
||||||
|
|
||||||
|
log.info(`Generating addon mozbuild...`)
|
||||||
|
|
||||||
|
const files = await walkDirectoryTree(outPath)
|
||||||
|
|
||||||
|
// Because the tree has the potential of being infinitely recursive, we
|
||||||
|
// cannot possibly know the the type of the tree
|
||||||
|
//
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
function runTree(tree: any, parent: string): string {
|
||||||
|
if (Array.isArray(tree)) {
|
||||||
|
return tree
|
||||||
|
.sort()
|
||||||
|
.map(
|
||||||
|
(file) =>
|
||||||
|
`FINAL_TARGET_FILES.features["${addon.id}"]${parent} += ["${file
|
||||||
|
.replace(outPath + '/', '')
|
||||||
|
.replace(outPath, '')}"]`
|
||||||
|
)
|
||||||
|
.join('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
const current = (tree['.'] as string[])
|
||||||
|
.sort()
|
||||||
|
// Don't use windows path, which brick mozbuild
|
||||||
|
.map((f) => windowsPathToUnix(f))
|
||||||
|
.map(
|
||||||
|
(f) =>
|
||||||
|
`FINAL_TARGET_FILES.features["${addon.id}"]${parent} += ["${f
|
||||||
|
.replace(outPath + '/', '')
|
||||||
|
.replace(outPath, '')}"]`
|
||||||
|
)
|
||||||
|
.join('\n')
|
||||||
|
|
||||||
|
const children = Object.keys(tree)
|
||||||
|
.filter((folder) => folder !== '.')
|
||||||
|
.filter((folder) => typeof tree[folder] !== 'undefined')
|
||||||
|
.map((folder) => runTree(tree[folder], `${parent}["${folder}"]`))
|
||||||
|
.join('\n')
|
||||||
|
|
||||||
|
return `${current}\n${children}`
|
||||||
|
}
|
||||||
|
|
||||||
|
writeFileSync(
|
||||||
|
join(outPath, 'moz.build'),
|
||||||
|
`
|
||||||
|
DEFINES["MOZ_APP_VERSION"] = CONFIG["MOZ_APP_VERSION"]
|
||||||
|
DEFINES["MOZ_APP_MAXVERSION"] = CONFIG["MOZ_APP_MAXVERSION"]
|
||||||
|
|
||||||
|
${runTree(files, '')}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function initializeAddon(addon: AddonInfo & { name: string }) {
|
||||||
|
const outPath = join(ENGINE_DIR, 'browser', 'extensions', addon.name)
|
||||||
|
|
||||||
|
log.info(`Initializing addon...`)
|
||||||
|
|
||||||
|
await configDispatch('git', {
|
||||||
|
args: ['add', '-f', '.'],
|
||||||
|
cwd: outPath,
|
||||||
|
})
|
||||||
|
await configDispatch('git', {
|
||||||
|
args: ['commit', '-m', addon.name],
|
||||||
|
cwd: ENGINE_DIR,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addAddonsToMozBuild(
|
||||||
|
addons: (AddonInfo & { name: string })[]
|
||||||
|
) {
|
||||||
|
log.info('Adding addons to mozbuild...')
|
||||||
|
|
||||||
|
// Discard the file to make sure it has no changes
|
||||||
|
await discard('browser/extensions/moz.build')
|
||||||
|
|
||||||
|
const path = join(ENGINE_DIR, 'browser', 'extensions', 'moz.build')
|
||||||
|
|
||||||
|
// Append all the files to the bottom
|
||||||
|
writeFileSync(
|
||||||
|
path,
|
||||||
|
`${readFileSync(path).toString()}\nDIRS += [${addons
|
||||||
|
.map((addon) => addon.name)
|
||||||
|
.sort()
|
||||||
|
.map((addon) => `"${addon}"`)
|
||||||
|
.join(',')}]`
|
||||||
|
)
|
||||||
|
}
|
101
src/commands/download/firefox.ts
Normal file
101
src/commands/download/firefox.ts
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
import execa from 'execa'
|
||||||
|
import { existsSync, mkdirSync, rmdirSync } from 'fs'
|
||||||
|
import { resolve } from 'path'
|
||||||
|
import { bin_name } from '../..'
|
||||||
|
import { BASH_PATH, ENGINE_DIR, MELON_TMP_DIR } from '../../constants'
|
||||||
|
import { log } from '../../log'
|
||||||
|
import { commandExistsSync } from '../../utils/commandExists'
|
||||||
|
import { downloadFileToLocation } from '../../utils/download'
|
||||||
|
import { ensureDir, windowsPathToUnix } from '../../utils/fs'
|
||||||
|
import { init } from '../init'
|
||||||
|
|
||||||
|
export function shouldSetupFirefoxSource() {
|
||||||
|
return !(
|
||||||
|
existsSync(ENGINE_DIR) &&
|
||||||
|
existsSync(resolve(ENGINE_DIR, 'toolkit', 'moz.build'))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setupFirefoxSource(version: string) {
|
||||||
|
const firefoxSourceTar = await downloadFirefoxSource(version)
|
||||||
|
|
||||||
|
await unpackFirefoxSource(firefoxSourceTar)
|
||||||
|
|
||||||
|
if (!process.env.CI_SKIP_INIT) {
|
||||||
|
log.info('Init firefox')
|
||||||
|
await init(ENGINE_DIR)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function unpackFirefoxSource(name: string): Promise<void> {
|
||||||
|
log.info(`Unpacking Firefox...`)
|
||||||
|
|
||||||
|
if (existsSync(ENGINE_DIR)) rmdirSync(ENGINE_DIR)
|
||||||
|
mkdirSync(ENGINE_DIR)
|
||||||
|
|
||||||
|
let tarExec = 'tar'
|
||||||
|
|
||||||
|
// On MacOS, we need to use gnu tar, otherwise tar doesn't behave how we
|
||||||
|
// would expect it to behave, so this section is responsible for handling
|
||||||
|
// that
|
||||||
|
//
|
||||||
|
// If BSD tar adds --transform support in the future, we can use that
|
||||||
|
// instead
|
||||||
|
if (process.platform == 'darwin') {
|
||||||
|
// GNU Tar doesn't come preinstalled on any MacOS machines, so we need to
|
||||||
|
// check for it and ask for the user to install it if necessary
|
||||||
|
if (!commandExistsSync('gtar')) {
|
||||||
|
throw new Error(
|
||||||
|
`GNU Tar is required to extract Firefox's source on MacOS. Please install it using the command |brew install gnu-tar| and try again`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
tarExec = 'gtar'
|
||||||
|
}
|
||||||
|
|
||||||
|
await execa(
|
||||||
|
tarExec,
|
||||||
|
[
|
||||||
|
'--strip-components=1',
|
||||||
|
process.platform == 'win32' ? '--force-local' : null,
|
||||||
|
'-xf',
|
||||||
|
windowsPathToUnix(resolve(MELON_TMP_DIR, name)),
|
||||||
|
'-C',
|
||||||
|
windowsPathToUnix(ENGINE_DIR),
|
||||||
|
].filter((x) => x) as string[],
|
||||||
|
{
|
||||||
|
// HACK: Use bash shell on windows to get a sane version of tar that works
|
||||||
|
shell: BASH_PATH || false,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function downloadFirefoxSource(version: string) {
|
||||||
|
const base = `https://archive.mozilla.org/pub/firefox/releases/${version}/source/`
|
||||||
|
const filename = `firefox-${version}.source.tar.xz`
|
||||||
|
|
||||||
|
const url = base + filename
|
||||||
|
|
||||||
|
const fsParent = MELON_TMP_DIR
|
||||||
|
const fsSaveLocation = resolve(fsParent, filename)
|
||||||
|
|
||||||
|
log.info(`Locating Firefox release ${version}...`)
|
||||||
|
|
||||||
|
await ensureDir(fsParent)
|
||||||
|
|
||||||
|
if (existsSync(fsSaveLocation)) {
|
||||||
|
log.info('Using cached download')
|
||||||
|
return filename
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not re-download if there is already an existing workspace present
|
||||||
|
if (existsSync(ENGINE_DIR))
|
||||||
|
log.error(
|
||||||
|
`Workspace already exists.\nRemove that workspace and run |${bin_name} download ${version}| again.`
|
||||||
|
)
|
||||||
|
|
||||||
|
log.info(`Downloading Firefox release ${version}...`)
|
||||||
|
|
||||||
|
await downloadFileToLocation(url, resolve(MELON_TMP_DIR, filename))
|
||||||
|
return filename
|
||||||
|
}
|
|
@ -3,24 +3,12 @@
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
import { Command } from 'commander'
|
import { Command } from 'commander'
|
||||||
import { existsSync, readFileSync } from 'fs'
|
import { existsSync, readFileSync } from 'fs'
|
||||||
import Listr from 'listr'
|
|
||||||
import { resolve } from 'path'
|
import { resolve } from 'path'
|
||||||
import { bin_name } from '..'
|
import { bin_name } from '..'
|
||||||
import { log } from '../log'
|
import { log } from '../log'
|
||||||
import { config, configDispatch } from '../utils'
|
import { config, configDispatch } from '../utils'
|
||||||
|
|
||||||
export const init = async (
|
export const init = async (directory: Command | string): Promise<void> => {
|
||||||
directory: Command | string,
|
|
||||||
task?: Listr.ListrTaskWrapper<unknown>
|
|
||||||
): Promise<void> => {
|
|
||||||
function logInfo(data: string) {
|
|
||||||
if (task) {
|
|
||||||
task.output = data
|
|
||||||
} else {
|
|
||||||
log.info(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const cwd = process.cwd()
|
const cwd = process.cwd()
|
||||||
|
|
||||||
const dir = resolve(cwd as string, directory.toString())
|
const dir = resolve(cwd as string, directory.toString())
|
||||||
|
@ -49,49 +37,43 @@ export const init = async (
|
||||||
version = version.trim().replace(/\\n/g, '')
|
version = version.trim().replace(/\\n/g, '')
|
||||||
|
|
||||||
// TODO: Use bash on windows, this may significantly improve performance. Still needs testing though
|
// TODO: Use bash on windows, this may significantly improve performance. Still needs testing though
|
||||||
logInfo('Initializing git, this may take some time')
|
log.info('Initializing git, this may take some time')
|
||||||
|
|
||||||
await configDispatch('git', {
|
await configDispatch('git', {
|
||||||
args: ['init'],
|
args: ['init'],
|
||||||
cwd: dir,
|
cwd: dir,
|
||||||
logger: logInfo,
|
|
||||||
shell: 'unix',
|
shell: 'unix',
|
||||||
})
|
})
|
||||||
|
|
||||||
await configDispatch('git', {
|
await configDispatch('git', {
|
||||||
args: ['init'],
|
args: ['init'],
|
||||||
cwd: dir,
|
cwd: dir,
|
||||||
logger: logInfo,
|
|
||||||
shell: 'unix',
|
shell: 'unix',
|
||||||
})
|
})
|
||||||
|
|
||||||
await configDispatch('git', {
|
await configDispatch('git', {
|
||||||
args: ['checkout', '--orphan', version],
|
args: ['checkout', '--orphan', version],
|
||||||
cwd: dir,
|
cwd: dir,
|
||||||
logger: logInfo,
|
|
||||||
shell: 'unix',
|
shell: 'unix',
|
||||||
})
|
})
|
||||||
|
|
||||||
await configDispatch('git', {
|
await configDispatch('git', {
|
||||||
args: ['add', '-f', '.'],
|
args: ['add', '-f', '.'],
|
||||||
cwd: dir,
|
cwd: dir,
|
||||||
logger: logInfo,
|
|
||||||
shell: 'unix',
|
shell: 'unix',
|
||||||
})
|
})
|
||||||
|
|
||||||
logInfo('Committing...')
|
log.info('Committing...')
|
||||||
|
|
||||||
await configDispatch('git', {
|
await configDispatch('git', {
|
||||||
args: ['commit', '-aqm', `"Firefox ${version}"`],
|
args: ['commit', '-aqm', `"Firefox ${version}"`],
|
||||||
cwd: dir,
|
cwd: dir,
|
||||||
logger: logInfo,
|
|
||||||
shell: 'unix',
|
shell: 'unix',
|
||||||
})
|
})
|
||||||
|
|
||||||
await configDispatch('git', {
|
await configDispatch('git', {
|
||||||
args: ['checkout', '-b', config.name.toLowerCase().replace(/\s/g, '_')],
|
args: ['checkout', '-b', config.name.toLowerCase().replace(/\s/g, '_')],
|
||||||
cwd: dir,
|
cwd: dir,
|
||||||
logger: logInfo,
|
|
||||||
shell: 'unix',
|
shell: 'unix',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue