cli: add site subcommand (#1905)

Summary:
The `sourcecred site` command now sets up a static site for a cred
instance, which may be deployed to (e.g.) GitHub Pages or IPFS.

This implementation sets up a directory structure where the site is
contained in a `site/` subdirectory whose top-level entries are also
symbolically linked from the root of the instance. Thus:

```
my-instance/
    sourcecred.json
    config/
    output/
    site/
        index.html
        favicon.png
        static/
            static/js/...
    index.html -> site/index.html
    favicon.png -> site/favicon.png
    static -> site/static
```

The purpose of this is to enable the instance to be served directly as a
GitHub Pages site without the user having to navigate to a URL that
includes a `site/` path component.

It’s not a perfect solution. The top level of the instance is cluttered.
If we have any web pages other than the root directory, then they’ll
appear at top level, too. If we change the structure, we’ll need to
teach `sourcecred site` to clean up vestiges of old structures. We also
won’t be able to have any pages called `config` or `output` or `cache`
due to namespace collision. But we think that it’s at least a reasonable
stopgap, and doesn’t incur much conceptual overhead.

This isn’t tested or officially supported on Windows.

Paired with @decentralion.

Test Plan:
Run `yarn build` to build the frontend and backend, then `cd` into a
SourceCred instance and run `node "${OLDPWD}/bin/sourcecred.js site`.
Note that a `site/` directory is created, and when the static site is
served it works from either `site/` or the instance root. Note that this
all works when actually deployed to GitHub Pages as well.

wchargin-branch: cli-site
This commit is contained in:
William Chargin 2020-06-27 23:21:56 -07:00 committed by GitHub
parent a30c45bc05
commit 7ab3a74c3a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 47 additions and 2 deletions

View File

@ -16,6 +16,6 @@ ENV SOURCECRED_DIRECTORY ${SOURCECRED_DEFAULT_DIRECTORY}
# Install the remainder of our code.
COPY . /code
RUN yarn build:backend
RUN yarn build
ENTRYPOINT ["/bin/bash", "/code/scripts/docker-entrypoint.sh"]

View File

@ -94,7 +94,7 @@
"prettify": "prettier --write '**/*.js'",
"check-pretty": "prettier --list-different '**/*.js'",
"start": "NODE_ENV=development webpack-dev-server --config config/webpack.config.web.js",
"build": "run-p build:*",
"build": "run-p build:* && ln -sf ../build ./bin/site-template",
"build:frontend": "NODE_ENV=production webpack --config config/webpack.config.web.js",
"build:backend": "NODE_ENV=development webpack --config config/webpack.config.backend.js",
"api": "webpack --config config/webpack.config.api.js",

42
src/cli/site.js Normal file
View File

@ -0,0 +1,42 @@
// @flow
import fs from "fs-extra";
import type {Command} from "./command";
import {join as pathJoin} from "path";
const SITE_TEMPLATE_DIR = "site-template";
const SITE_OUTPUT = "site"; // under instance dir
const SITE_SYMLINKS = ["index.html", "favicon.png", "static"];
function die(std, message) {
std.err("fatal: " + message);
return 1;
}
const siteCommand: Command = async (args, std) => {
if (args.length !== 0) {
return die(std, "usage: sourcecred site");
}
const siteTemplate = pathJoin(__dirname, SITE_TEMPLATE_DIR);
await fs.copy(siteTemplate, SITE_OUTPUT, {dereference: true});
for (const symlink of SITE_SYMLINKS) {
await lnsf(pathJoin(SITE_OUTPUT, symlink), symlink);
}
return 0;
};
// Create a symlink, overwriting if it exists, like `ln -sf`.
async function lnsf(src: string, dst: string): Promise<void> {
try {
await fs.symlink(src, dst);
} catch (e) {
if (e.code !== "EEXIST") {
throw e;
}
}
await fs.unlink(dst);
await fs.symlink(src, dst);
}
export default siteCommand;

View File

@ -6,6 +6,7 @@ import {VERSION_SHORT} from "../core/version";
import load from "./load";
import graph from "./graph";
import score from "./score";
import site from "./site";
import help from "./help";
const sourcecred: Command = async (args, std) => {
@ -26,6 +27,8 @@ const sourcecred: Command = async (args, std) => {
return graph(args.slice(1), std);
case "score":
return score(args.slice(1), std);
case "site":
return site(args.slice(1), std);
default:
std.err("fatal: unknown command: " + JSON.stringify(args[0]));
std.err("fatal: run 'sourcecred help' for commands and usage");