diff --git a/config/paths.js b/config/paths.js index 00fd844..05ea40f 100644 --- a/config/paths.js +++ b/config/paths.js @@ -12,11 +12,8 @@ module.exports = { root: appDirectory, dotenv: resolveApp(".env"), favicon: resolveApp("src/assets/logo/rasterized/logo_32.png"), - appBuild: resolveApp("build"), appBuild2: resolveApp("build2"), - appIndexJs: resolveApp("src/homepage/index.js"), appIndexJs2: resolveApp("src/ui/index.js"), - appServerSideRenderingIndexJs: resolveApp("src/homepage/server.js"), appServerSideRenderingIndexJs2: resolveApp("src/ui/server.js"), appPackageJson: resolveApp("package.json"), appSrc: resolveApp("src"), diff --git a/config/webpack.config.web.js b/config/webpack.config.web.js deleted file mode 100644 index f473649..0000000 --- a/config/webpack.config.web.js +++ /dev/null @@ -1,255 +0,0 @@ -// @flow -const express = require("express"); -/*:: -import type { - $Application as ExpressApp, - $Response as ExpressResponse, -} from "express"; -*/ -const os = require("os"); -const path = require("path"); -const webpack = require("webpack"); -const RemoveBuildDirectoryPlugin = require("./RemoveBuildDirectoryPlugin"); -const CopyPlugin = require("copy-webpack-plugin"); -const ManifestPlugin = require("webpack-manifest-plugin"); -const StaticSiteGeneratorPlugin = require("static-site-generator-webpack-plugin"); -const ModuleScopePlugin = require("react-dev-utils/ModuleScopePlugin"); -const paths = require("./paths"); -const getClientEnvironment = require("./env"); -const _getProjectIds = require("../src/core/_getProjectIds"); - -// Source maps are resource heavy and can cause out of memory issue for large source files. -const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== "false"; - -function loadProjectIds() /*: Promise<$ReadOnlyArray> */ { - const env = process.env.SOURCECRED_DIRECTORY; - // TODO(#945): de-duplicate finding the directory with src/cli/common.js - const defaultDirectory = path.join(os.tmpdir(), "sourcecred"); - const scDirectory = env != null ? env : defaultDirectory; - return _getProjectIds(scDirectory); -} - -async function makeConfig( - mode /*: "production" | "development" */ -) /*: Promise */ { - return { - // Don't attempt to continue if there are any errors. - bail: true, - // We generate sourcemaps in production. This is slow but gives good results. - // You can exclude the *.map files from the build during deployment. - devtool: shouldUseSourceMap ? "source-map" : false, - // In production, we only want to load the polyfills and the app code. - entry: { - main: [require.resolve("./polyfills"), paths.appIndexJs], - ssr: [ - require.resolve("./polyfills"), - paths.appServerSideRenderingIndexJs, - ], - }, - devServer: { - inline: false, - before: (app /*: ExpressApp */) => { - const apiRoot = "/api/v1/data"; - const rejectCache = (_unused_req, res /*: ExpressResponse */) => { - res.status(400).send("Bad Request: Cache unavailable at runtime\n"); - }; - app.get(`${apiRoot}/cache`, rejectCache); - app.get(`${apiRoot}/cache/*`, rejectCache); - app.use( - apiRoot, - express.static( - process.env.SOURCECRED_DIRECTORY || - path.join(os.tmpdir(), "sourcecred") - ) - ); - }, - }, - output: { - // The build folder. - path: paths.appBuild, - // Generated JS file names (with nested folders). - // There will be one main bundle, and one file per asynchronous chunk. - // We don't currently advertise code splitting but Webpack supports it. - filename: "static/js/[name].[chunkhash:8].js", - chunkFilename: "static/js/[name].[chunkhash:8].chunk.js", - // Point sourcemap entries to original disk location (format as URL on Windows) - devtoolModuleFilenameTemplate: ( - info /*: - {| - // https://webpack.js.org/configuration/output/#output-devtoolmodulefilenametemplate - +absoluteResourcePath: string, - +allLoaders: string, - +hash: string, - +id: string, - +loaders: string, - +resource: string, - +resourcePath: string, - +namespace: string, - |} - */ - ) => - path - .relative(paths.appSrc, info.absoluteResourcePath) - .replace(/\\/g, "/"), - // We need to use a UMD module to build the static site. - libraryTarget: "umd", - globalObject: "this", - }, - resolve: { - // This allows you to set a fallback for where Webpack should look for modules. - // We placed these paths second because we want `node_modules` to "win" - // if there are any conflicts. This matches Node resolution mechanism. - // https://github.com/facebookincubator/create-react-app/issues/253 - modules: [ - "node_modules", - paths.appNodeModules, - ...(process.env.NODE_PATH || "").split(path.delimiter).filter(Boolean), - ], - // These are the reasonable defaults supported by the Node ecosystem. - // We also include JSX as a common component filename extension to support - // some tools, although we do not recommend using it, see: - // https://github.com/facebookincubator/create-react-app/issues/290 - // `web` extension prefixes have been added for better support - // for React Native Web. - extensions: [".web.js", ".mjs", ".js", ".json", ".web.jsx", ".jsx"], - alias: { - // Support React Native Web - // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/ - "react-native": "react-native-web", - }, - plugins: [ - // Prevents users from importing files from outside of src/ (or node_modules/). - // This often causes confusion because we only process files within src/ with babel. - // To fix this, we prevent you from importing files out of src/ -- if you'd like to, - // please link the files into your node_modules/ and let module-resolution kick in. - // Make sure your source files are compiled, as they will not be processed in any way. - new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]), - ], - }, - module: { - strictExportPresence: true, - rules: [ - // TODO: Disable require.ensure as it's not a standard language feature. - // We are waiting for https://github.com/facebookincubator/create-react-app/issues/2176. - // { parser: { requireEnsure: false } }, - { - // "oneOf" will traverse all following loaders until one will - // match the requirements. When no loader matches it will fall - // back to the "file" loader at the end of the loader list. - oneOf: [ - // "url" loader works just like "file" loader but it also embeds - // assets smaller than specified size as data URLs to avoid requests. - { - test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/], - loader: require.resolve("url-loader"), - options: { - limit: 10000, - name: "static/media/[name].[hash:8].[ext]", - }, - }, - // Process JS with Babel. - { - test: /\.(js|jsx|mjs)$/, - include: paths.appSrc, - loader: require.resolve("babel-loader"), - options: { - compact: true, - }, - }, - { - test: /\.css$/, - loader: "css-loader", // TODO(@wchargin): add csso-loader - }, - { - test: /\.svg$/, - exclude: /node_modules/, - loader: "svg-react-loader", - }, - // "file" loader makes sure assets end up in the `build` folder. - // When you `import` an asset, you get its filename. - // This loader doesn't use a "test" so it will catch all modules - // that fall through the other loaders. - { - loader: require.resolve("file-loader"), - // Exclude `js` files to keep "css" loader working as it injects - // it's runtime that would otherwise processed through "file" loader. - // Also exclude `html` and `json` extensions so they get processed - // by webpacks internal loaders. - exclude: [/\.(js|jsx|mjs)$/, /\.html$/, /\.json$/], - options: { - name: "static/media/[name].[hash:8].[ext]", - }, - }, - // ** STOP ** Are you adding a new loader? - // Make sure to add the new loader(s) before the "file" loader. - ], - }, - ], - }, - plugins: await plugins(mode), - // Some libraries import Node modules but don't use them in the browser. - // Tell Webpack to provide empty mocks for them so importing them works. - node: { - dgram: "empty", - fs: "empty", - net: "empty", - tls: "empty", - child_process: "empty", - }, - mode, - }; -} - -async function plugins(mode /*: "development" | "production" */) { - const projectIds = await loadProjectIds(); - const env = getClientEnvironment(projectIds); - const basePlugins = [ - new StaticSiteGeneratorPlugin({ - entry: "ssr", - paths: require("../src/homepage/routeData") - .makeRouteData(projectIds) - .map(({path}) => path), - locals: {}, - }), - new CopyPlugin([{from: paths.favicon, to: "favicon.png"}]), - // Makes some environment variables available to the JS code, for example: - // if (process.env.NODE_ENV === 'production') { ... }. See `./env.js`. - // It is absolutely essential that NODE_ENV was set to production here. - // Otherwise React will be compiled in the very slow development mode. - new webpack.DefinePlugin(env.stringified), - // Generate a manifest file which contains a mapping of all asset filenames - // to their corresponding output file so that tools can pick it up without - // having to parse `index.html`. - new ManifestPlugin({ - fileName: "asset-manifest.json", - }), - // Moment.js is an extremely popular library that bundles large locale files - // by default due to how Webpack interprets its code. This is a practical - // solution that requires the user to opt into importing specific locales. - // https://github.com/jmblog/how-to-optimize-momentjs-with-webpack - // You can remove this if you don't use Moment.js: - new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), - ]; - const prodOnlyPlugins = [ - // Remove the output directory before starting the build. - new RemoveBuildDirectoryPlugin(), - ]; - switch (mode) { - case "development": - return basePlugins; - case "production": - return basePlugins.concat(prodOnlyPlugins); - default: - throw new Error(/*:: (*/ mode /*: empty) */); - } -} - -function getMode() { - const mode = process.env.NODE_ENV; - if (mode !== "production" && mode !== "development") { - throw new Error("unknown mode: " + String(mode)); - } - return mode; -} - -module.exports = makeConfig(getMode()); diff --git a/scripts/build_static_site.sh b/scripts/build_static_site.sh deleted file mode 100755 index 926aa7d..0000000 --- a/scripts/build_static_site.sh +++ /dev/null @@ -1,209 +0,0 @@ -#!/bin/bash -set -eu - -usage() { - printf 'usage: build_static_site.sh --target TARGET\n' - printf ' [--project PROJECT [...]]\n' - printf ' [--project-file PROJECT_FILE [...]]\n' - printf ' [--weights WEIGHTS_FILE]\n' - printf ' [--cname DOMAIN]\n' - printf ' [--no-backend]\n' - printf ' [-h|--help]\n' - printf '\n' - printf 'Build the static SourceCred website, including example data.\n' - printf '\n' - printf '%s\n' '--target TARGET' - printf '\t%s\n' 'an empty directory into which to build the site' - printf '%s\n' '--project PROJECT' - printf '\t%s\n' 'a project spec; see help for cli/load.js for details' - printf '%s\n' '--project-file PROJECT_FILE' - printf '\t%s\n' 'the path to a file containing a project config' - printf '%s\n' '--weights WEIGHTS_FILE' - printf '\t%s\n' 'path to a json file which contains a weights configuration.' - printf '\t%s\n' 'This will be used instead of the default weights and persisted.' - printf '%s\n' '--cname DOMAIN' - printf '\t%s\n' 'configure DNS for a GitHub Pages site to point to' - printf '\t%s\n' 'the provided custom domain' - printf '%s\n' '--no-backend' - printf '\t%s\n' 'do not run "yarn backend"; see also the SOURCECRED_BIN' - printf '\t%s\n' 'environment variable' - printf '%s\n' '-h|--help' - printf '\t%s\n' 'show this message' - printf '\n' - printf 'Environment variables:\n' - printf '\n' - printf '%s\n' 'SOURCECRED_BIN' - printf '\t%s\n' 'When using --no-backend, directory containing the' - printf '\t%s\n' 'SourceCred executables (output of "yarn backend").' - printf '\t%s\n' 'Default is ./bin. Ignored without --no-backend.' -} - -main() { - parse_args "$@" - - toplevel="$(git -C "$(dirname "$0")" rev-parse --show-toplevel)" - cd "${toplevel}" - - sourcecred_data= - sourcecred_bin= - trap cleanup EXIT - - build -} - -parse_args() { - BACKEND=1 - target= - cname= - weights= - repos=( ) - projects=( ) - project_files=( ) - while [ $# -gt 0 ]; do - case "$1" in - --target) - if [ -n "${target}" ]; then - die '--target specified multiple times' - fi - shift - if [ $# -eq 0 ]; then die 'missing value for --target'; fi - target="$1" - ;; - --weights) - if [ -n "${weights}" ]; then - die '--weights specified multiple times' - fi - shift - if [ $# -eq 0 ]; then die 'missing value for --weights'; fi - weights="$1" - ;; - --project) - shift - if [ $# -eq 0 ]; then die 'missing value for --project'; fi - projects+=( "$1" ) - ;; - --project-file) - shift - if [ $# -eq 0 ]; then die 'missing value for --project-file'; fi - project_files+=( "$1" ) - ;; - --cname) - shift - if [ $# -eq 0 ]; then die 'missing value for --cname'; fi - if [ -n "${cname}" ]; then - die '--cname specified multiple times' - fi - cname="$1" - if [ -z "${cname}" ]; then - die 'empty value for --cname' - fi - ;; - --no-backend) - BACKEND=0 - ;; - -h|--help) - usage - exit 0 - ;; - *) - printf >&2 'fatal: unknown argument: %s\n' "$1" - exit 1 - ;; - esac - shift - done - if [ -z "${target}" ]; then - die 'target directory not specified' - fi - if ! [ -e "${target}" ]; then - mkdir -p -- "${target}" - fi - if ! [ -d "${target}" ]; then - die "target is not a directory: ${target}" - fi - if [ "$(command ls -A "${target}" | wc -l)" != 0 ]; then - die "target directory is nonempty: ${target}" - fi - target="$(readlink -e "${target}")" - : "${SOURCECRED_BIN:=./bin}" -} - -build() { - sourcecred_data="$(mktemp -d --suffix ".sourcecred-data")" - - if [ -n "${SOURCECRED_DIRECTORY:-}" ]; then - # If $SOURCECRED_DIRECTORY is available, then give sourcecred access to - # the cache. This will greatly speed up site builds on repos that have - # already been loaded. - # Note this speedup will only apply if the SOURCECRED_DIRECTORY has been - # explicitly set. - ln -s "${SOURCECRED_DIRECTORY}/cache" "${sourcecred_data}/cache" - fi - - export SOURCECRED_DIRECTORY="${sourcecred_data}" - - if [ "${BACKEND}" -ne 0 ]; then - sourcecred_bin="$(mktemp -d --suffix ".sourcecred-bin")" - export SOURCECRED_BIN="${sourcecred_bin}" - yarn - yarn -s backend --output-path "${SOURCECRED_BIN}" - fi - - if [ "${#projects[@]}" -ne 0 ]; then - local weightsStr="" - if [ -n "${weights}" ]; then - weightsStr="--weights ${weights}" - fi - for project in "${projects[@]}"; do - NODE_PATH="./node_modules${NODE_PATH:+:${NODE_PATH}}" \ - node "${SOURCECRED_BIN:-./bin}/sourcecred.js" load "${project}" $weightsStr - done - fi - - if [ "${#project_files[@]}" -ne 0 ]; then - local weightsStr="" - if [ -n "${weights}" ]; then - weightsStr="--weights ${weights}" - fi - for project_file in "${project_files[@]}"; do - NODE_PATH="./node_modules${NODE_PATH:+:${NODE_PATH}}" \ - node "${SOURCECRED_BIN:-./bin}/sourcecred.js" load --project "${project_file}" $weightsStr - done - fi - - yarn -s build --output-path "${target}" - - # Copy the SourceCred data into the appropriate API route. Using - # `mkdir` here will fail in the case where an `api/` folder exists, - # which is the correct behavior. (In this case, our site's - # architecture conflicts with the required static structure, and we - # must fail.) - mkdir "${target}/api/" - mkdir "${target}/api/v1/" - # Eliminate the cache, which is only an intermediate target used to - # load the actual data. The development server similarly forbids - # access to the cache so that the dev and prod environments have the - # same semantics. - rm -rf "${sourcecred_data}/cache" - cp -r "${sourcecred_data}" "${target}/api/v1/data" - - if [ -n "${cname:-}" ]; then - cname_file="${target}/CNAME" - if [ -e "${cname_file}" ]; then - die 'CNAME file exists in static site output' - fi - printf '%s' "${cname}" >"${cname_file}" # no newline - fi -} - -cleanup() { - if [ -d "${sourcecred_data:-}" ]; then rm -rf "${sourcecred_data}"; fi - if [ -d "${sourcecred_bin:-}" ]; then rm -rf "${sourcecred_bin}"; fi -} - -die() { - printf >&2 'fatal: %s\n' "$@" - exit 1 -} - -main "$@" diff --git a/sharness/test_build_static_site.t b/sharness/test_build_static_site.t deleted file mode 100755 index fb1a5fa..0000000 --- a/sharness/test_build_static_site.t +++ /dev/null @@ -1,249 +0,0 @@ -#!/bin/sh - -# Disable these lint rules globally: -# 2034 = unused variable (used by sharness) -# 2016 = parameter expansion in single quotes -# 1004 = backslash-newline in single quotes -# shellcheck disable=SC2034,SC2016,SC1004 -: - -test_description='tests for scripts/build_static_site.sh' - -export GIT_CONFIG_NOSYSTEM=1 -export GIT_ATTR_NOSYSTEM=1 - -# shellcheck disable=SC1091 -. ./sharness.sh - -run() ( - set -eu - toplevel="$(git -C "$(dirname "$0")" rev-parse --show-toplevel)" - "${toplevel}"/scripts/build_static_site.sh "$@" -) - -# -# Start by checking a bunch of easy cases related to the argument -# parser, mostly about rejecting various ill-formed invocations. - -test_expect_success "should print a help message" ' - run --help >msg 2>err && - test_must_be_empty err && - test_path_is_file msg && - grep -qF "usage: build_static_site.sh" msg -' - -test_expect_success "should fail with no target" ' - test_must_fail run 2>err && - grep -qF -- "target directory not specified" err -' - -test_expect_success "should fail with missing target value" ' - test_must_fail run --target 2>err && - grep -qF -- "missing value for --target" err -' - -test_expect_success "should fail with multiple targets" ' - mkdir one two && - test_must_fail run --target one --target two 2>err && - grep -qF -- "--target specified multiple times" err -' - -test_expect_success "should fail with a file as target" ' - printf "important\nstuff" >important_data && - test_must_fail run --target important_data 2>err && - grep -qF -- "target is not a directory" err && - printf "important\nstuff" | test_cmp - important_data -' - -test_expect_success "should fail with a target under a file" ' - printf "important\nstuff" >important_data && - test_must_fail run --target important_data/something 2>err && - grep -q -- "cannot create directory.*Not a directory" err && - printf "important\nstuff" | test_cmp - important_data -' - -test_expect_success "should fail with a nonempty directory as target" ' - mkdir important_dir && - printf "redacted\n" >important_dir/.wallet.dat && - test_must_fail run --target important_dir 2>err && - grep -qF -- "target directory is nonempty: important_dir" err && - printf "redacted\n" | test_cmp - important_dir/.wallet.dat -' - -mkdir putative_output - -test_expect_success "should fail with missing project value" ' - test_must_fail run --target putative_output --project 2>err && - grep -qF -- "missing value for --project" err && - printf "redacted\n" | test_cmp - important_dir/.wallet.dat -' - -test_expect_success "should fail with missing cname value" ' - test_must_fail run --target putative_output --cname 2>err && - grep -qF -- "missing value for --cname" err && - printf "redacted\n" | test_cmp - important_dir/.wallet.dat -' - -test_expect_success "should fail with empty cname" ' - test_must_fail run --target putative_output --cname "" 2>err && - grep -qF -- "empty value for --cname" err && - printf "redacted\n" | test_cmp - important_dir/.wallet.dat -' - -test_expect_success "should fail with multiple cname values" ' - test_must_fail run --target putative_output \ - --cname a.com --cname b.com 2>err && - grep -qF -- "--cname specified multiple times" err && - printf "redacted\n" | test_cmp - important_dir/.wallet.dat -' - -# -# Now, actually generate output in two cases: one with projects, and -# one with no projects. We can only do this if we have a token. - -if [ -n "${SOURCECRED_GITHUB_TOKEN:-}" ]; then - test_set_prereq HAVE_GITHUB_TOKEN -fi - -# run_build PREREQ_NAME DESCRIPTION [FLAGS...] -# Build the site with the given FLAGS, and create a prereq PREREQ_NAME -# to be used in any tests that depend on this build. The build will -# itself have the EXPENSIVE prereq. -run_build() { - prereq_name="$1"; shift - description="$1"; shift - output_dir="build_output/output_${prereq_name}" - api_dir="${output_dir}/api/v1/data" - unsafe_arg= - for arg in "${output_dir}" "$@"; do - unusual_chars="$(printf '%s' "$arg" | sed -e 's#[A-Za-z0-9:/_.-]##g')" - if [ -n "${unusual_chars}" ]; then - unsafe_arg="${arg}" - break - fi - done - flags="--target $output_dir $*" # only used if ! [ -n "${unsafe_arg}" ] - test_expect_success EXPENSIVE,HAVE_GITHUB_TOKEN \ - "${prereq_name}: ${description}" ' - if [ -n "${unsafe_arg}" ]; then - printf >&2 "fatal: potentially unsafe argument: %s\n" "${arg}" && - false - fi && - run '"${flags}"' >out 2>err && - test_must_fail grep -vF \ - -e "Removing contents of build directory: " \ - -e "info: loading project" \ - -e "DeprecationWarning: Tapable.plugin is deprecated." \ - err && - test_path_is_dir "${output_dir}" && - test_path_is_dir "${api_dir}" && - test_set_prereq "${prereq_name}" - ' - test_expect_success "${prereq_name}" \ - "${prereq_name}: should have no cache" ' - test_must_fail test_path_is_dir "${api_dir}/cache" - ' - test_expect_success "${prereq_name}" \ - "${prereq_name}: should have a bundle" ' - js_bundle_path= && - js_bundle_path_glob="${output_dir}"/static/js/main.*.js && - for main_js in ${js_bundle_path_glob}; do - if ! [ -e "${main_js}" ]; then - printf >&2 "fatal: no main bundle found\n" && - return 1 - elif [ -n "${js_bundle_path}" ]; then - printf >&2 "fatal: multiple main bundles found:\n" && - printf >&2 " %s\n" ${js_bundle_path_glob} && - return 1 - else - js_bundle_path="${main_js}" - fi - done - ' -} - -# test_pages PREREQ_NAME -# Test that the PREREQ_NAME build output includes a valid home page, a -# valid prototype page, and a valid Discord invite page (which should be -# a redirect). -test_pages() { - prereq="$1" - test_expect_success "${prereq}" "${prereq}: should have a favicon" ' - test_path_is_file "${output_dir}/favicon.png" && - file -b --mime-type "${output_dir}/favicon.png" >./favicon_filetype && - printf "image/png\n" | test_cmp - ./favicon_filetype && - rm ./favicon_filetype - ' - test_expect_success "${prereq}" \ - "${prereq}: should have a home page and a prototype" ' - test_path_is_file "${output_dir}/index.html" && - grep -qF " - - - `; - callback(null, page); - } else { - // This shouldn't happen because we should only be visiting - // the right routes. - throw new Error(`unexpected 404 from ${path}`); - } - }); - } - - function renderRedirect(redirectTo: string) { - const component = ( - - - - ); - const {html, css} = StyleSheetServer.renderStatic(() => - ReactDOMServer.renderToStaticMarkup(component) - ); - const page = dedent`\ - - - - - - - - - - ${resolveTitleFromPath(routeData, path)} - - - - -
${html}
- - - `; - callback(null, page); - } -}