Initial commit 🎉

fbshipit-source-id: b6fc29740c6875d2e78953b8a7123890a67930f2
Co-authored-by: Sebastian McKenzie <sebmck@fb.com>
Co-authored-by: John Knox <jknox@fb.com>
Co-authored-by: Emil Sjölander <emilsj@fb.com>
Co-authored-by: Pritesh Nandgaonkar <prit91@fb.com>
This commit is contained in:
Daniel Büchele
2018-04-13 08:38:06 -07:00
committed by Daniel Buchele
commit fbbf8cf16b
659 changed files with 87130 additions and 0 deletions

205
scripts/build-release.js Executable file
View File

@@ -0,0 +1,205 @@
/**
* Copyright 2018-present Facebook.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* @format
*/
const path = require('path');
const tmp = require('tmp');
const fs = require('fs-extra');
const builder = require('electron-builder');
const Platform = builder.Platform;
const metro = require('../static/node_modules/metro');
const compilePlugins = require('../static/compilePlugins');
function generateManifest(versionNumber) {
const filePath = path.join(__dirname, '..', 'dist');
if (!fs.existsSync(filePath)) {
fs.mkdirSync(filePath);
}
fs.writeFileSync(
path.join(__dirname, '../dist/manifest.json'),
JSON.stringify({
package: 'com.facebook.sonar',
version_name: versionNumber,
}),
);
}
function buildFolder() {
// eslint-disable-next-line no-console
console.log('Creating build directory');
return new Promise((resolve, reject) => {
tmp.dir((err, buildFolder) => {
if (err) {
reject(err);
} else {
resolve(buildFolder);
}
});
}).catch(die);
}
function modifyPackageManifest(buildFolder) {
// eslint-disable-next-line no-console
console.log('Creating package.json manifest');
const manifest = require('../package.json');
const manifestStatic = require('../static/package.json');
// The manifest's dependencies are bundled with the final app by
// electron-builder. We want to bundle the dependencies from the static-folder
// because all dependencies from the root-folder are already bundled by metro.
manifest.dependencies = manifestStatic.dependencies;
manifest.main = 'index.js';
const BUILD_NUMBER_ARG = 'build-number=';
const buildNumber = (
process.argv.find(arg => arg.startsWith(BUILD_NUMBER_ARG)) || ''
).replace(BUILD_NUMBER_ARG, '');
if (buildNumber) {
manifest.version = [
...manifest.version.split('.').slice(0, 2),
buildNumber,
].join('.');
}
return new Promise((resolve, reject) => {
fs.writeFile(
path.join(buildFolder, 'package.json'),
JSON.stringify(manifest, null, ' '),
err => {
if (err) {
reject(err);
} else {
resolve(manifest.version);
}
},
);
}).catch(die);
}
function buildDist(buildFolder) {
const targetsRaw = [];
targetsRaw.push(Platform.MAC.createTarget(['zip']));
if (process.argv.slice(2).indexOf('macOnly') === -1) {
targetsRaw.push(Platform.LINUX.createTarget(['dir']));
targetsRaw.push(Platform.WINDOWS.createTarget(['dir']));
}
if (!targetsRaw.length) {
throw new Error('No targets specified. eg. --osx pkg,dmg --linux tar.gz');
}
// merge all target maps into a single map
let targetsMerged = [];
for (const target of targetsRaw) {
targetsMerged = targetsMerged.concat(Array.from(target));
}
const targets = new Map(targetsMerged);
const electronDownload = {};
if (process.env.electron_config_cache) {
electronDownload.cache = process.env.electron_config_cache;
}
return builder
.build({
appDir: buildFolder,
config: {
appId: `com.facebook.sonar`,
directories: {
buildResources: path.join(__dirname, '..', 'static'),
output: path.join(__dirname, '..', 'dist'),
},
electronDownload,
npmRebuild: false,
asarUnpack: 'PortForwardingMacApp.app/**/*',
},
projectDir: buildFolder,
targets,
})
.catch(die);
}
function die(err) {
console.error(err.stack);
process.exit(1);
}
function compile(buildFolder) {
// eslint-disable-next-line no-console
console.log(
'Building main bundle',
path.join(__dirname, '..', 'src', 'init.js'),
);
return metro
.runBuild({
config: {
getProjectRoots: () => [path.join(__dirname, '..')],
getTransformModulePath: () =>
path.join(__dirname, '..', 'static', 'transforms', 'index.js'),
},
resetCache: true,
dev: false,
entry: path.join(__dirname, '..', 'src', 'init.js'),
out: path.join(buildFolder, 'bundle.js'),
})
.catch(die);
}
function copyStaticFolder(buildFolder) {
return new Promise((resolve, reject) => {
fs.copy(
path.join(__dirname, '..', 'static'),
buildFolder,
{
dereference: true,
},
err => {
if (err) {
reject(err);
} else {
resolve();
}
},
);
}).catch(die);
}
function compileDefaultPlugins(buildFolder) {
const defaultPluginFolder = 'defaultPlugins';
const defaultPluginDir = path.join(buildFolder, defaultPluginFolder);
return compilePlugins(
null,
[
path.join(__dirname, '..', 'src', 'plugins'),
path.join(__dirname, '..', 'src', 'fb', 'plugins'),
],
defaultPluginDir,
).then(defaultPlugins =>
fs.writeFileSync(
path.join(defaultPluginDir, 'index.json'),
JSON.stringify(
defaultPlugins.map(plugin => ({
...plugin,
out: path.join(defaultPluginFolder, path.parse(plugin.out).base),
})),
),
),
);
}
(async () => {
const dir = await buildFolder();
// eslint-disable-next-line no-console
console.log('Created build directory', dir);
await copyStaticFolder(dir);
await compileDefaultPlugins(dir);
await compile(dir);
const versionNumber = await modifyPackageManifest(dir);
generateManifest(versionNumber);
await buildDist(dir);
// eslint-disable-next-line no-console
console.log('✨ Done');
process.exit();
})();

30
scripts/eslint.sh Executable file
View File

@@ -0,0 +1,30 @@
#!/bin/bash
set -e
# This script is used by `arc lint`.
THIS_DIR=$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)
ROOT_DIR=$(cd "$THIS_DIR" && hg root)
cd "$ROOT_DIR/xplat/sonar"
# Sonar's Electron dependency downloads itself via a post-install script.
# When running in Sandcastle or devservers, the module install will fail
# because we can't reach the internet. Setting the fwdproxy is dangerous, so
# the next best thing is to install the modules with `--ignore-scripts`.
# However, we can't run `install-node-modules.sh` like this all of the time.
# `install-node-modules.sh` uses its args as keys for the "yarn watchman check"
# cache. So if we run `install-node-modules.sh` outside of this script without
# the flag, but then this script runs it with the flag, we're going to
# invalidate the cache.
# If `node_modules` exists, we can't tell if it was created with
# `--ignore-scripts` or not, so we play it safe, and avoid touching it.
if [[ ! -d "node_modules" ]]; then
"$ROOT_DIR/xplat/third-party/yarn/install-node-modules.sh" --ignore-scripts
fi
exec \
"$ROOT_DIR/xplat/third-party/node/bin/node" \
"$ROOT_DIR/xplat/sonar/node_modules/.bin/eslint" \
"$@"

31
scripts/flow.sh Executable file
View File

@@ -0,0 +1,31 @@
#!/bin/bash
set -e
# This script is used by `arc lint`.
THIS_DIR=$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)
ROOT_DIR=$(cd "$THIS_DIR" && hg root)
cd "$ROOT_DIR/xplat/sonar"
# Sonar's Electron dependency downloads itself via a post-install script.
# When running in Sandcastle or devservers, the module install will fail
# because we can't reach the internet. Setting the fwdproxy is dangerous, so
# the next best thing is to install the modules with `--ignore-scripts`.
# However, we can't run `install-node-modules.sh` like this all of the time.
# `install-node-modules.sh` uses its args as keys for the "yarn watchman check"
# cache. So if we run `install-node-modules.sh` outside of this script without
# the flag, but then this script runs it with the flag, we're going to
# invalidate the cache.
# If `node_modules` exists, we can't tell if it was created with
# `--ignore-scripts` or not, so we play it safe, and avoid touching it.
if [[ ! -d "node_modules" ]]; then
"$ROOT_DIR/xplat/third-party/yarn/install-node-modules.sh" --ignore-scripts
fi
# Prefer the internal version of Flow, which should be in the PATH - but
# fallback to the OSS version (this is needed in Sandcastle).
FLOW_BINARY="$(which flow 2>/dev/null || echo "$ROOT_DIR/xplat/sonar/node_modules/.bin/flow")"
exec "$FLOW_BINARY" "$@"

29
scripts/install-dependencies.sh Executable file
View File

@@ -0,0 +1,29 @@
#!/bin/sh
set -e
main () {
local -r THIS_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
ROOT_DIR=$(cd "$THIS_DIR" && hg root)
source "$ROOT_DIR/xplat/sonar/scripts/setup-env.sh"
# save current cursor location
printf "Ensuring correct dependencies..."
PREV_DIR="`pwd`"
# install dependencies
cd "$INFINITY_DIR"
"$INSTALL_NODE_MODULES"
# ensure electron gets installed
node node_modules/electron/install.js
# go back
cd "$PREV_DIR"
# remove correct dependencies log
printf "\r"
}
main

View File

@@ -0,0 +1,63 @@
/**
* Copyright 2018-present Facebook.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* @format
*/
const generate = require('babel-generator').default;
const babylon = require('babylon');
const babel = require('babel-core');
const metro = require('metro');
exports.transform = function({
filename,
options,
src,
plugins: defaultPlugins,
}) {
const presets = [];
let ast = babylon.parse(src, {
filename,
plugins: ['jsx', 'flow', 'classProperties', 'objectRestSpread'],
sourceType: filename.includes('node_modules') ? 'script' : 'module',
});
// run babel
const plugins = [
...defaultPlugins,
require('./babel-plugins/electron-requires.js'),
require('./babel-plugins/dynamic-requires.js'),
];
if (!filename.includes('node_modules')) {
plugins.unshift(require('babel-plugin-transform-es2015-modules-commonjs'));
}
ast = babel.transformFromAst(ast, src, {
babelrc: !filename.includes('node_modules'),
code: false,
comments: false,
compact: false,
filename,
plugins,
presets,
sourceMaps: true,
}).ast;
const result = generate(
ast,
{
filename,
sourceFileName: filename,
sourceMaps: true,
},
src,
);
return {
ast,
code: result.code,
filename,
map: result.rawMappings.map(metro.sourceMaps.compactMapping),
};
};

20
scripts/public-build.json Normal file
View File

@@ -0,0 +1,20 @@
{
"command": "SandcastleUniversalCommand",
"args": {
"name": "Release public Sonar build",
"oncall": "danielbuechele",
"steps": [
{
"name": "sonar_release_public_build",
"required": true,
"shell": "cd ../xplat/sonar/scripts && ./public-build.sh"
}
]
},
"alias": "sonar_release_public_build",
"capabilities": {
"vcs": "fbcode-fbsource",
"type": "lego"
},
"hash": "master"
}

46
scripts/public-build.sh Executable file
View File

@@ -0,0 +1,46 @@
#!/bin/bash
TOKEN=$(secrets_tool get SONAR_GITHUB_TOKEN)
GITHUB_ORG="facebook"
GITHUB_REPO="Sonar"
cd ../../ || exit
function jsonValue() {
python -c 'import json,sys;obj=json.load(sys.stdin);print obj["'$1'"]' || echo ''
}
git -c http.proxy=fwdproxy:8080 -c https.proxy=fwdproxy:8080 clone https://github.com/facebook/Sonar.git sonar-public
cp sonar/scripts/sandcastle-build.sh sonar-public/scripts/sandcastle-build.sh
cd sonar-public/scripts && ./sandcastle-build.sh "$(git rev-list HEAD --count || echo 0)"
VERSION=$(plutil -p ./sonar-public/dist/mac/Sonar.app/Contents/Info.plist | awk '/CFBundleShortVersionString/ {print substr($3, 2, length($3)-2)}')
RELEASE_JSON=$(curl $(fwdproxy-config curl) --silent --data '{
"tag_name": "v'$VERSION'",
"target_commitish": "master",
"name": "v'$VERSION'",
"body": "",
"draft": false,
"prerelease": false
}' https://api.github.com/repos/$GITHUB_ORG/$GITHUB_REPO/releases?access_token=$TOKEN)
RELEASE_ID=$(echo $RELEASE_JSON | jsonValue id)
if [ -z "${RELEASE_ID}" ]; then
echo $RELEASE_JSON
exit 1
fi
echo "Created GitHub release ID: $RELEASE_ID"
UPLOAD_URL=$(echo $RELEASE_JSON | jsonValue upload_url| sed -e 's#{?name,label}##')
ASSET_JSON=$(curl $(fwdproxy-config curl) --silent $UPLOAD_URL'?access_token='$TOKEN'&name=Sonar.zip' --header 'Content-Type: application/zip' --upload-file ./sonar-public/dist/Sonar.zip -X POST)
DOWNLOAD_URL=$(echo $ASSET_JSON | jsonValue browser_download_url)
if [ -z "${DOWNLOAD_URL}" ]; then
echo $ASSET_JSON
exit 1
fi
echo "Released Sonar v$VERSION"
echo "Download: $DOWNLOAD_URL"

15
scripts/setup-env.sh Executable file
View File

@@ -0,0 +1,15 @@
#!/bin/sh
set -e
main () {
local -r THIS_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
ROOT_DIR=$(cd "$THIS_DIR" && hg root)
source "$ROOT_DIR/xplat/js/env-utils/setup_env_vars.sh"
export SONAR_DIR="$ROOT_DIR/xplat/infinity"
export PATH="$SONAR_DIR/node_modules/.bin:$ROOT_DIR/xplat/third-party/node/bin:$ROOT_DIR/xplat/third-party/yarn:$PATH"
}
main

176
scripts/start-dev-server.js Normal file
View File

@@ -0,0 +1,176 @@
/**
* Copyright 2018-present Facebook.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* @format
*/
const electronBinary = require('electron');
const codeFrame = require('babel-code-frame');
const socketIo = require('socket.io');
const express = require('express');
const detect = require('detect-port');
const child = require('child_process');
const Convert = require('ansi-to-html');
const chalk = require('chalk');
const http = require('http');
const path = require('path');
const metro = require('../static/node_modules/metro');
const fs = require('fs');
const convertAnsi = new Convert();
const DEFAULT_PORT = process.env.PORT || 3000;
const STATIC_DIR = path.join(__dirname, '..', 'static');
function launchElectron({bundleURL, electronURL}) {
const args = [
path.join(STATIC_DIR, 'index.js'),
'--remote-debugging-port=9222',
'--dynamicPlugins=~/fbsource/xplat/sonar/src/plugins,~/fbsource/xplat/sonar/src/fb/plugins',
];
const proc = child.spawn(electronBinary, args, {
cwd: STATIC_DIR,
env: {
...process.env,
SONAR_ROOT: process.cwd(),
BUNDLE_URL: bundleURL,
ELECTRON_URL: electronURL,
},
stdio: 'inherit',
});
proc.on('close', () => {
process.exit();
});
process.on('exit', () => {
proc.kill();
});
}
function startMetroServer(port) {
return metro.runServer({
port,
watch: true,
config: {
getProjectRoots: () => [path.join(__dirname, '..')],
getTransformModulePath: () =>
path.join(__dirname, '..', 'static', 'transforms', 'index.js'),
},
});
}
function startAssetServer(port) {
const app = express();
app.use((req, res, next) => {
if (knownErrors[req.url] != null) {
delete knownErrors[req.url];
outputScreen();
}
next();
});
app.get('/', (req, res) => {
fs.readFile(path.join(STATIC_DIR, 'index.dev.html'), (err, content) => {
res.end(content);
});
});
app.use(express.static(STATIC_DIR));
app.use(function(err, req, res, next) {
knownErrors[req.url] = err;
outputScreen();
res.status(500).send('Something broke, check the console!');
});
const server = http.createServer(app);
return new Promise((resolve, reject) => {
server.listen(port, () => resolve(server));
});
}
function addWebsocket(server) {
const io = socketIo(server);
// notify connected clients that there's errors in the console
io.on('connection', client => {
if (hasErrors()) {
client.emit('hasErrors', convertAnsi.toHtml(buildErrorScreen()));
}
});
// refresh the app on changes to the src folder
// this can be removed once metroServer notifies us about file changes
fs.watch(path.join(__dirname, '..', 'src'), () => {
io.emit('refresh');
});
return io;
}
const knownErrors = {};
function hasErrors() {
return Object.keys(knownErrors).length > 0;
}
function buildErrorScreen() {
const lines = [
chalk.red(`✖ Found ${Object.keys(knownErrors).length} errors`),
'',
];
for (const url in knownErrors) {
const err = knownErrors[url];
if (err.filename != null && err.lineNumber != null && err.column != null) {
lines.push(chalk.inverse(err.filename));
lines.push();
lines.push(err.message);
lines.push(
codeFrame(
fs.readFileSync(err.filename, 'utf8'),
err.lineNumber,
err.column,
),
);
} else {
lines.push(err.stack);
}
lines.push('');
}
return lines.join('\n');
}
function outputScreen(socket) {
// output screen
if (hasErrors()) {
const errorScreen = buildErrorScreen();
console.error(errorScreen);
// notify live clients of errors
socket.emit('hasErrors', convertAnsi.toHtml(errorScreen));
} else {
// eslint-disable-next-line no-console
console.log(chalk.green('✔ No known errors'));
}
}
(async () => {
const assetServerPort = await detect(DEFAULT_PORT);
const assetServer = await startAssetServer(assetServerPort);
const socket = addWebsocket(assetServer);
const metroServerPort = await detect(DEFAULT_PORT + 1);
await startMetroServer(metroServerPort);
outputScreen(socket);
launchElectron({
bundleURL: `http://localhost:${metroServerPort}/src/init.bundle`,
electronURL: `http://localhost:${assetServerPort}/index.dev.html`,
});
})();

48
scripts/yarn-install.js Normal file
View File

@@ -0,0 +1,48 @@
/**
* Copyright 2018-present Facebook.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* @format
*/
const glob = require('glob');
const path = require('path');
const {spawn} = require('child_process');
const PACKAGES = ['static', 'src/plugins/*', 'src/fb/plugins/*'];
const YARN_PATH =
process.argv.length > 2 ? path.join(__dirname, process.argv[2]) : 'yarn';
Promise.all(
PACKAGES.map(
pattern =>
new Promise((resolve, reject) => {
glob(
path.join(__dirname, '..', pattern, 'package.json'),
(err, matches) => {
if (err) {
reject(err);
} else {
resolve(matches);
}
},
);
}),
),
)
.then(packages =>
Promise.all(
packages.reduce((acc, cv) => acc.concat(cv), []).map(
pkg =>
new Promise(resolve => {
const cwd = pkg.replace('/package.json', '');
const yarn = spawn(YARN_PATH, ['--mutex', 'file'], {
cwd,
});
yarn.stderr.on('data', e => console.error(e.toString()));
yarn.on('close', code => resolve(code));
}),
),
),
)
// eslint-disable-next-line
.then(() => console.log('📦 Installed all dependencies!'));