1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2026-02-19 06:19:19 +00:00

Merge branch 'master' into patch-1

This commit is contained in:
Chris Kruining 2020-11-02 16:03:48 +01:00 committed by GitHub
commit febd0f39ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
523 changed files with 60038 additions and 54683 deletions

8
.editorconfig Executable file
View File

@ -0,0 +1,8 @@
root = true
[{src, translations}/*]
end_of_line = crlf
insert_final_newline = true
indent_style = space
indent_size = 4
charset = utf-8

4
.gitattributes vendored
View File

@ -1,4 +0,0 @@
*.wav filter=lfs diff=lfs merge=lfs -text
*.webm filter=lfs diff=lfs merge=lfs -text
*.mp3 filter=lfs diff=lfs merge=lfs -text
*.psd filter=lfs diff=lfs merge=lfs -text

View File

@ -35,19 +35,23 @@ jobs:
cd gulp/ cd gulp/
yarn yarn
cd .. cd ..
- name: Lint - name: Lint
run: | run: |
yarn lint yarn lint
- name: YAML Lint
uses: ibiqlik/action-yamllint@v1.0.0
with:
file_or_dir: translations/*.yaml
- name: TSLint - name: TSLint
run: | run: |
cd gulp cd gulp
yarn gulp translations.fullBuild yarn gulp translations.fullBuild
cd .. cd ..
yarn tslint yarn tslint
yaml-lint:
name: yaml-lint
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v2
- name: YAML Lint
uses: ibiqlik/action-yamllint@v1.0.0
with:
file_or_dir: translations/*.yaml

66
.gitignore vendored
View File

@ -15,34 +15,11 @@ pids
*.seed *.seed
*.pid.lock *.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html) # Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release build/Release
# Dependency directories # Dependency directories
node_modules/ node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache # TypeScript cache
*.tsbuildinfo *.tsbuildinfo
@ -53,18 +30,9 @@ typings/
# Optional eslint cache # Optional eslint cache
.eslintcache .eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history # Optional REPL history
.node_repl_history .node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file # Yarn Integrity file
.yarn-integrity .yarn-integrity
@ -72,41 +40,11 @@ typings/
.env .env
.env.test .env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Buildfiles # Buildfiles
build build
res_built
gulp/runnable-texturepacker.jar
tmp_standalone_files tmp_standalone_files
# Local config # Local config

4
.gitpod.Dockerfile vendored Normal file
View File

@ -0,0 +1,4 @@
FROM gitpod/workspace-full
RUN sudo apt-get update \
&& sudo apt install ffmpeg -yq

10
.gitpod.yml Normal file
View File

@ -0,0 +1,10 @@
image:
file: .gitpod.Dockerfile
tasks:
- init: yarn && gp sync-done boot
- before: cd gulp
init: gp sync-await boot && yarn
command: yarn gulp
ports:
- port: 3005
onOpen: open-preview

View File

@ -1,3 +1,5 @@
{ {
"editor.defaultFormatter": "esbenp.prettier-vscode" "editor.defaultFormatter": "esbenp.prettier-vscode",
"files.trimTrailingWhitespace": true,
"editor.formatOnSave": true
} }

View File

@ -4,3 +4,4 @@ rules:
line-length: line-length:
level: warning level: warning
max: 200 max: 200
document-start: disable

31
Dockerfile Normal file
View File

@ -0,0 +1,31 @@
FROM node:12 as base
EXPOSE 3001 3005
WORKDIR /shapez.io
RUN apt-get update && apt-get install -y --no-install-recommends \
ffmpeg default-jre \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
COPY package.json yarn.lock ./
RUN yarn
COPY gulp ./gulp
WORKDIR /shapez.io/gulp
RUN yarn
WORKDIR /shapez.io
COPY res ./res
COPY src/html ./src/html
COPY src/css ./src/css
COPY version ./version
COPY sync-translations.js ./
COPY translations ./translations
COPY src/js ./src/js
COPY res_raw ./res_raw
COPY .git ./.git
WORKDIR /shapez.io/gulp
ENTRYPOINT ["yarn", "gulp"]

View File

@ -22,15 +22,24 @@ Your goal is to produce shapes by cutting, rotating, merging and painting parts
## Building ## Building
- Make sure git `git lfs` extension is on your path
- Run `git lfs pull` to download sound assets
- Make sure `ffmpeg` is on your path - Make sure `ffmpeg` is on your path
- Install Node.js and Yarn - Install Node.js and Yarn
- Install Java (required for textures)
- Run `yarn` in the root folder - Run `yarn` in the root folder
- Cd into `gulp` folder - Cd into `gulp` folder
- Run `yarn` and then `yarn gulp` - it should now open in your browser - Run `yarn` and then `yarn gulp` - it should now open in your browser
**Notice**: This will produce a debug build with several debugging flags enabled. If you want to disable them, modify `config.js`. **Notice**: This will produce a debug build with several debugging flags enabled. If you want to disable them, modify [`src/js/core/config.js`](src/js/core/config.js).
## Build Online with one-click setup
You can use [Gitpod](https://www.gitpod.io/) (an Online Open Source VS Code-like IDE which is free for Open Source) for working on issues and making PRs to this project. With a single click it will start a workspace and automatically:
- clone the `shapez.io` repo.
- install all of the dependencies.
- start `gulp` in `gulp/` directory.
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/from-referrer/)
## Helping translate ## Helping translate
@ -64,7 +73,7 @@ This project is based on ES5. Some ES2015 features are used but most of them are
5. Add a constructor. **The constructor must be called with optional parameters only!** `new MyFancyComponent({})` should always work. 5. Add a constructor. **The constructor must be called with optional parameters only!** `new MyFancyComponent({})` should always work.
6. Add any props you need in the constructor. 6. Add any props you need in the constructor.
7. Add the component in `src/js/game/component_registry.js` 7. Add the component in `src/js/game/component_registry.js`
8. Add the componetn in `src/js/game/entity_components.js` 8. Add the component in `src/js/game/entity_components.js`
9. Done! You can use your component now 9. Done! You can use your component now
#### Adding a new building #### Adding a new building
@ -81,7 +90,7 @@ This project is based on ES5. Some ES2015 features are used but most of them are
8. In `translations/base-en.yaml` add it to two sections: `buildings.[my_building].XXX` (See other buildings) and also `keybindings.mappings.[my_building]`. Be sure to do it the same way as other buildings do! 8. In `translations/base-en.yaml` add it to two sections: `buildings.[my_building].XXX` (See other buildings) and also `keybindings.mappings.[my_building]`. Be sure to do it the same way as other buildings do!
9. Create a icon (128x128, [prefab](https://github.com/tobspr/shapez.io-artwork/blob/master/ui/toolbar-icons.psd)) for your building and save it in `res/ui/buildings_icons` with the id of your building 9. Create a icon (128x128, [prefab](https://github.com/tobspr/shapez.io-artwork/blob/master/ui/toolbar-icons.psd)) for your building and save it in `res/ui/buildings_icons` with the id of your building
10. Create a tutorial image (600x600) for your building and save it in `res/ui/building_tutorials` 10. Create a tutorial image (600x600) for your building and save it in `res/ui/building_tutorials`
11. In `src/css/icons.scss` add your building to `$buildings` as well as `$buildingAndVariants` 11. In `src/css/resources.scss` add your building to `$buildings` as well as `$buildingAndVariants`
12. Done! Optional: Add a new reward for unlocking your building at some point. 12. Done! Optional: Add a new reward for unlocking your building at some point.
#### Adding a new game system #### Adding a new game system
@ -92,10 +101,32 @@ This project is based on ES5. Some ES2015 features are used but most of them are
4. Add the system in `src/js/game/game_system_manager.js` (To `this.systems` and also call `add` in the `internalInitSystems()` method) 4. Add the system in `src/js/game/game_system_manager.js` (To `this.systems` and also call `add` in the `internalInitSystems()` method)
5. If your system should draw stuff, this is a bit more complicated. Have a look at existing systems on how they do it. 5. If your system should draw stuff, this is a bit more complicated. Have a look at existing systems on how they do it.
#### Checklist for a new building / testing it
This is a quick checklist, if a new building is added this points should be fulfilled:
2. The translation for all variants is done and finalized
3. The artwork (regular sprite) is finalized
4. The blueprint sprite has been generated and is up to date
5. The building has been added to the appropriate toolbar
6. The building has a keybinding which makes sense
7. The building has a reward assigned and is unlocked at a meaningful point
8. The reward for the building has a proper translation
9. The reward for the building has a proper image
10. The building has a proper tutorial image assigned
11. The buliding has a proper toolbar icon
12. The reward requires a proper shape
13. The building has a proper silhouette color
14. The building has a proper matrix for being rendered on the minimap
15. The building has proper statistics in the dialog
16. The building properly contributes to the shapes produced analytics
17. The building is properly persisted in the savegame
18. The building is explained properly, ideally via an interactive tutorial
### Assets ### Assets
For most assets I use Adobe Photoshop, you can find them in `assets/`. For most assets I use Adobe Photoshop, you can find them <a href="//github.com/tobspr/shapez.io-artwork" target="_blank">here</a>.
You will need a <a href="https://www.codeandweb.com/texturepacker" target="_blank">Texture Packer</a> license in order to regenerate the atlas. If you don't have one but want to contribute assets, let me know and I might compile it for you. I'm currently switching to an open source solution but I can't give an estimate when thats done. All assets will be automatically rebuilt into the atlas once changed (Thanks to dengr1065!)
<img src="https://i.imgur.com/W25Fkl0.png" alt="shapez.io Screenshot"> <img src="https://i.imgur.com/W25Fkl0.png" alt="shapez.io Screenshot">

View File

@ -1,3 +0,0 @@
The artwork can be found here:
https://github.com/tobspr/shapez.io-artwork

View File

@ -4,7 +4,7 @@ const { app, BrowserWindow, Menu, MenuItem, session } = require("electron");
const path = require("path"); const path = require("path");
const url = require("url"); const url = require("url");
const childProcess = require("child_process"); const childProcess = require("child_process");
const { ipcMain } = require("electron"); const { ipcMain, shell } = require("electron");
const fs = require("fs"); const fs = require("fs");
const isDev = process.argv.indexOf("--dev") >= 0; const isDev = process.argv.indexOf("--dev") >= 0;
const isLocal = process.argv.indexOf("--local") >= 0; const isLocal = process.argv.indexOf("--local") >= 0;
@ -67,11 +67,7 @@ function createWindow() {
win.webContents.on("new-window", (event, pth) => { win.webContents.on("new-window", (event, pth) => {
event.preventDefault(); event.preventDefault();
if (process.platform == "win32") { shell.openExternal(pth);
childProcess.execSync("start " + pth);
} else if (process.platform == "linux") {
childProcess.execSync("xdg-open " + pth);
}
}); });
win.on("closed", () => { win.on("closed", () => {

View File

@ -1,16 +1,16 @@
{ {
"name": "electron", "name": "electron",
"version": "1.0.0", "version": "1.0.0",
"main": "index.js", "main": "index.js",
"license": "MIT", "license": "MIT",
"private": true, "private": true,
"scripts": { "scripts": {
"startDev": "electron --disable-direct-composition --in-process-gpu . --dev --local", "startDev": "electron --disable-direct-composition --in-process-gpu . --dev --local",
"startDevGpu": "electron --enable-gpu-rasterization --enable-accelerated-2d-canvas --num-raster-threads=8 --enable-zero-copy . --dev --local", "startDevGpu": "electron --enable-gpu-rasterization --enable-accelerated-2d-canvas --num-raster-threads=8 --enable-zero-copy . --dev --local",
"start": "electron --disable-direct-composition --in-process-gpu ." "start": "electron --disable-direct-composition --in-process-gpu ."
}, },
"devDependencies": { "devDependencies": {
"electron": "^6.1.12" "electron": "10.1.3"
}, },
"dependencies": {} "dependencies": {}
} }

File diff suppressed because it is too large Load Diff

1
gulp/.gitattributes vendored
View File

@ -1 +0,0 @@
*.wav filter=lfs diff=lfs merge=lfs -text

3
gulp/.gitignore vendored
View File

@ -1,2 +1 @@
additional_build_files additional_build_files
steampipe

127
gulp/atlas2json.js Normal file
View File

@ -0,0 +1,127 @@
const { join, resolve } = require("path");
const { readFileSync, readdirSync, writeFileSync } = require("fs");
const suffixToScale = {
lq: "0.25",
mq: "0.5",
hq: "0.75"
};
function convert(srcDir) {
const full = resolve(srcDir);
const srcFiles = readdirSync(full)
.filter(n => n.endsWith(".atlas"))
.map(n => join(full, n));
for (const atlas of srcFiles) {
console.log(`Processing: ${atlas}`);
// Read all text, split it into line array
// and filter all empty lines
const lines = readFileSync(atlas, "utf-8")
.split("\n")
.filter(n => n.trim());
// Get source image name
const image = lines.shift();
const srcMeta = {};
// Read all metadata (supports only one page)
while (true) {
const kv = lines.shift().split(":");
if (kv.length != 2) {
lines.unshift(kv[0]);
break;
}
srcMeta[kv[0]] = kv[1].trim();
}
const frames = {};
let current = null;
lines.push("Dummy line to make it convert last frame");
for (const line of lines) {
if (!line.startsWith(" ")) {
// New frame, convert previous if it exists
if (current != null) {
let { name, rotate, xy, size, orig, offset, index } = current;
// Convert to arrays because Node.js doesn't
// support latest JS features
xy = xy.split(",").map(v => Number(v));
size = size.split(",").map(v => Number(v));
orig = orig.split(",").map(v => Number(v));
offset = offset.split(",").map(v => Number(v));
// GDX TexturePacker removes index suffixes
const indexSuff = index != -1 ? `_${index}` : "";
const isTrimmed = size != orig;
frames[`${name}${indexSuff}.png`] = {
// Bounds on atlas
frame: {
x: xy[0],
y: xy[1],
w: size[0],
h: size[1]
},
// Whether image was rotated
rotated: rotate == "true",
trimmed: isTrimmed,
// How is the image trimmed
spriteSourceSize: {
x: offset[0],
y: (orig[1] - size[1]) - offset[1],
w: size[0],
h: size[1]
},
sourceSize: {
w: orig[0],
h: orig[1]
}
}
}
// Simple object that will hold other metadata
current = {
name: line
};
} else {
// Read and set current image metadata
const kv = line.split(":").map(v => v.trim());
current[kv[0]] = isNaN(Number(kv[1])) ? kv[1] : Number(kv[1]);
}
}
const atlasSize = srcMeta.size.split(",").map(v => Number(v));
const atlasScale = suffixToScale[atlas.match(/_(\w+)\.atlas$/)[1]];
const result = JSON.stringify({
frames,
meta: {
image,
format: srcMeta.format,
size: {
w: atlasSize[0],
h: atlasSize[1]
},
scale: atlasScale.toString()
}
});
writeFileSync(atlas.replace(".atlas", ".json"), result, {
encoding: "utf-8"
});
}
}
if (require.main == module) {
convert(process.argv[2]);
}
module.exports = { convert };

View File

@ -25,6 +25,14 @@ module.exports = {
}); });
}, },
getTag() {
try {
return execSync("git describe --tag --exact-match").toString("ascii");
} catch (e) {
throw new Error('Current git HEAD is not a version tag');
}
},
getVersion() { getVersion() {
return trim(fs.readFileSync(path.join(__dirname, "..", "version")).toString()); return trim(fs.readFileSync(path.join(__dirname, "..", "version")).toString());
}, },

View File

@ -1,96 +0,0 @@
// Converts the atlas description to a JSON file
String.prototype.replaceAll = function (search, replacement) {
var target = this;
return target.split(search).join(replacement);
};
const fs = require("fs");
const path = require("path");
const folder = path.join(__dirname, "res_built", "atlas");
const files = fs.readdirSync(folder);
const metadata = [];
files.forEach(filename => {
if (filename.endsWith(".atlas")) {
// Read content
const content = fs.readFileSync(path.join(folder, filename), "ascii");
const lines = content.replaceAll("\r", "").replaceAll("\t", "").split("\n");
const readLine = () => lines.splice(0, 1)[0];
const readValue = () => readLine().replaceAll(" ", "").split(":")[1];
const readVector = () =>
readValue()
.split(",")
.map(d => parseInt(d, 10));
let maxAtlas = 100;
atlasLoop: while (maxAtlas-- > 0 && lines.length >= 7) {
const result = {
entries: [],
};
// Extract header
const header_fileStart = readLine();
const header_fileName = readLine();
const header_size = readVector();
const header_format = readLine();
const header_filter = readLine();
const header_repeat = readLine();
const baseAtlasName = header_fileName.replace(".png", "");
// Store size
result.size = header_size;
lineLoop: while (lines.length >= 7) {
const entryResult = {};
const nextLine = lines[0];
if (nextLine.length === 0) {
break;
}
const entry_fileName = readLine() + ".png";
const entry_rotate = readValue();
const entry_xy = readVector();
const entry_size = readVector();
const entry_orig = readVector();
const entry_offset = readVector();
const entry_index = readValue();
entryResult.filename = entry_fileName;
entryResult.xy = entry_xy;
entryResult.size = entry_size;
// entryResult.offset = entry_offset;
entryResult.origSize = entry_orig;
let offset = [0, 0];
// GDX Atlas packer uses 1 - y coordinates. This sucks, and we have to convert it
offset[0] = entry_offset[0];
offset[1] = entry_orig[1] - entry_offset[1] - entry_size[1];
entryResult.offset = offset;
result.entries.push(entryResult);
}
console.log("[Atlas]", "'" + baseAtlasName + "'", "has", result.entries.length, "entries");
// fs.writeFileSync(path.join(folder, baseAtlasName + ".gen.json"), JSON.stringify(result));
metadata.push({
filename: baseAtlasName + ".png",
entries: result,
});
}
}
});
fs.writeFileSync(path.join(folder, "meta.gen.json"), JSON.stringify(metadata, null, 4));

View File

@ -1,99 +1,137 @@
const path = require("path"); const path = require("path");
const buildUtils = require("./buildutils"); const buildUtils = require("./buildutils");
function gulptasksCSS($, gulp, buildFolder, browserSync) { function gulptasksCSS($, gulp, buildFolder, browserSync) {
// The assets plugin copies the files // The assets plugin copies the files
const commitHash = buildUtils.getRevision(); const commitHash = buildUtils.getRevision();
const postcssAssetsPlugin = cachebust => const postcssAssetsPlugin = cachebust =>
$.postcssAssets({ $.postcssAssets({
loadPaths: [path.join(buildFolder, "res", "ui")], loadPaths: [path.join(buildFolder, "res", "ui")],
basePath: buildFolder, basePath: buildFolder,
baseUrl: ".", baseUrl: ".",
cachebuster: cachebust cachebuster: cachebust
? (filePath, urlPathname) => ({ ? (filePath, urlPathname) => ({
pathname: buildUtils.cachebust(urlPathname, commitHash), pathname: buildUtils.cachebust(urlPathname, commitHash),
}) })
: "", : "",
}); });
// Postcss configuration // Postcss configuration
const postcssPlugins = (prod, { cachebust = false }) => { const postcssPlugins = (prod, { cachebust = false }) => {
const plugins = [postcssAssetsPlugin(cachebust)]; const plugins = [postcssAssetsPlugin(cachebust)];
if (prod) { if (prod) {
plugins.unshift( plugins.unshift(
$.postcssUnprefix(), $.postcssUnprefix(),
$.postcssPresetEnv({ $.postcssPresetEnv({
browsers: ["> 0.1%"], browsers: ["> 0.1%"],
}) })
); );
plugins.push( plugins.push(
$.cssMqpacker({ $.cssMqpacker({
sort: true, sort: true,
}), }),
$.cssnano({ $.cssnano({
preset: [ preset: [
"advanced", "advanced",
{ {
cssDeclarationSorter: false, cssDeclarationSorter: false,
discardUnused: true, discardUnused: true,
mergeIdents: false, mergeIdents: false,
reduceIdents: true, reduceIdents: true,
zindex: true, zindex: true,
}, },
], ],
}), }),
$.postcssRoundSubpixels() $.postcssRoundSubpixels()
); );
} }
return plugins; return plugins;
}; };
// Performs linting on css // Performs linting on css
gulp.task("css.lint", () => { gulp.task("css.lint", () => {
return gulp return gulp
.src(["../src/css/**/*.scss"]) .src(["../src/css/**/*.scss"])
.pipe($.sassLint({ configFile: ".sasslint.yml" })) .pipe($.sassLint({ configFile: ".sasslint.yml" }))
.pipe($.sassLint.format()) .pipe($.sassLint.format())
.pipe($.sassLint.failOnError()); .pipe($.sassLint.failOnError());
}); });
// Builds the css in dev mode function resourcesTask({ cachebust, isProd }) {
gulp.task("css.dev", () => { return gulp
return gulp .src("../src/css/main.scss", { cwd: __dirname })
.src(["../src/css/main.scss"]) .pipe($.plumber())
.pipe($.plumber()) .pipe($.sass.sync().on("error", $.sass.logError))
.pipe($.sass.sync().on("error", $.sass.logError)) .pipe(
.pipe($.postcss(postcssPlugins(false, {}))) $.postcss([
.pipe(gulp.dest(buildFolder)) $.postcssCriticalSplit({
.pipe(browserSync.stream()); blockTag: "@load-async",
}); }),
])
// Builds the css in production mode (=minified) )
gulp.task("css.prod", () => { .pipe($.rename("async-resources.css"))
return ( .pipe($.postcss(postcssPlugins(isProd, { cachebust })))
gulp .pipe(gulp.dest(buildFolder))
.src("../src/css/main.scss", { cwd: __dirname }) .pipe(browserSync.stream());
.pipe($.plumber()) }
.pipe($.sass.sync({ outputStyle: "compressed" }).on("error", $.sass.logError))
.pipe($.postcss(postcssPlugins(true, { cachebust: true }))) // Builds the css resources
.pipe(gulp.dest(buildFolder)) gulp.task("css.resources.dev", () => {
); return resourcesTask({ cachebust: false, isProd: false });
}); });
// Builds the css in production mode (=minified), without cachebusting // Builds the css resources in prod (=minified)
gulp.task("css.prod-standalone", () => { gulp.task("css.resources.prod", () => {
return ( return resourcesTask({ cachebust: true, isProd: true });
gulp });
.src("../src/css/main.scss", { cwd: __dirname })
.pipe($.plumber()) // Builds the css resources in prod (=minified), without cachebusting
.pipe($.sass.sync({ outputStyle: "compressed" }).on("error", $.sass.logError)) gulp.task("css.resources.prod-standalone", () => {
.pipe($.postcss(postcssPlugins(true, { cachebust: false }))) return resourcesTask({ cachebust: false, isProd: true });
.pipe(gulp.dest(buildFolder)) });
);
}); function mainTask({ cachebust, isProd }) {
} return gulp
.src("../src/css/main.scss", { cwd: __dirname })
module.exports = { .pipe($.plumber())
gulptasksCSS, .pipe($.sass.sync().on("error", $.sass.logError))
}; .pipe(
$.postcss([
$.postcssCriticalSplit({
blockTag: "@load-async",
output: "rest",
}),
])
)
.pipe($.postcss(postcssPlugins(isProd, { cachebust })))
.pipe(gulp.dest(buildFolder))
.pipe(browserSync.stream());
}
// Builds the css main
gulp.task("css.main.dev", () => {
return mainTask({ cachebust: false, isProd: false });
});
// Builds the css main in prod (=minified)
gulp.task("css.main.prod", () => {
return mainTask({ cachebust: true, isProd: true });
});
// Builds the css main in prod (=minified), without cachebusting
gulp.task("css.main.prod-standalone", () => {
return mainTask({ cachebust: false, isProd: true });
});
gulp.task("css.dev", gulp.parallel("css.main.dev", "css.resources.dev"));
gulp.task("css.prod", gulp.parallel("css.main.prod", "css.resources.prod"));
gulp.task(
"css.prod-standalone",
gulp.parallel("css.main.prod-standalone", "css.resources.prod-standalone")
);
}
module.exports = {
gulptasksCSS,
};

12
gulp/entitlements.plist Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.debugger</key>
<true/>
</dict>
</plist>

View File

@ -1,325 +1,335 @@
/* eslint-disable */ /* eslint-disable */
require("colors"); require("colors");
const gulp = require("gulp"); const gulp = require("gulp");
const browserSync = require("browser-sync").create({}); const browserSync = require("browser-sync").create({});
const path = require("path"); const path = require("path");
const deleteEmpty = require("delete-empty"); const deleteEmpty = require("delete-empty");
const execSync = require("child_process").execSync; const execSync = require("child_process").execSync;
const lfsOutput = execSync("git lfs install", { encoding: "utf-8" }); // Load other plugins dynamically
if (!lfsOutput.toLowerCase().includes("git lfs initialized")) { const $ = require("gulp-load-plugins")({
console.error(` scope: ["devDependencies"],
Git LFS is not installed, unable to build. pattern: "*",
});
To install Git LFS on Linux:
- Arch: // Check environment variables
sudo pacman -S git-lfs
- Debian/Ubuntu: const envVars = [
sudo apt install git-lfs "SHAPEZ_CLI_SERVER_HOST",
// "SHAPEZ_CLI_PHONEGAP_KEY",
For other systems, see: "SHAPEZ_CLI_ALPHA_FTP_USER",
https://github.com/git-lfs/git-lfs/wiki/Installation "SHAPEZ_CLI_ALPHA_FTP_PW",
`); "SHAPEZ_CLI_STAGING_FTP_USER",
process.exit(1); "SHAPEZ_CLI_STAGING_FTP_PW",
} "SHAPEZ_CLI_LIVE_FTP_USER",
"SHAPEZ_CLI_LIVE_FTP_PW",
// Load other plugins dynamically "SHAPEZ_CLI_APPLE_ID",
const $ = require("gulp-load-plugins")({ "SHAPEZ_CLI_APPLE_CERT_NAME",
scope: ["devDependencies"], "SHAPEZ_CLI_GITHUB_USER",
pattern: "*", "SHAPEZ_CLI_GITHUB_TOKEN",
}); ];
// Check environment variables for (let i = 0; i < envVars.length; ++i) {
if (!process.env[envVars[i]]) {
const envVars = [ console.warn("Please set", envVars[i]);
"SHAPEZ_CLI_SERVER_HOST", // process.exit(1);
// "SHAPEZ_CLI_PHONEGAP_KEY", }
"SHAPEZ_CLI_ALPHA_FTP_USER", }
"SHAPEZ_CLI_ALPHA_FTP_PW",
"SHAPEZ_CLI_STAGING_FTP_USER", const baseDir = path.join(__dirname, "..");
"SHAPEZ_CLI_STAGING_FTP_PW", const buildFolder = path.join(baseDir, "build");
"SHAPEZ_CLI_LIVE_FTP_USER",
"SHAPEZ_CLI_LIVE_FTP_PW", const imgres = require("./image-resources");
]; imgres.gulptasksImageResources($, gulp, buildFolder);
for (let i = 0; i < envVars.length; ++i) { const css = require("./css");
if (!process.env[envVars[i]]) { css.gulptasksCSS($, gulp, buildFolder, browserSync);
console.warn("Please set", envVars[i]);
// process.exit(1); const sounds = require("./sounds");
} sounds.gulptasksSounds($, gulp, buildFolder);
}
const js = require("./js");
const baseDir = path.join(__dirname, ".."); js.gulptasksJS($, gulp, buildFolder, browserSync);
const buildFolder = path.join(baseDir, "build");
const html = require("./html");
const imgres = require("./image-resources"); html.gulptasksHTML($, gulp, buildFolder, browserSync);
imgres.gulptasksImageResources($, gulp, buildFolder);
const ftp = require("./ftp");
const css = require("./css"); ftp.gulptasksFTP($, gulp, buildFolder);
css.gulptasksCSS($, gulp, buildFolder, browserSync);
const docs = require("./docs");
const sounds = require("./sounds"); docs.gulptasksDocs($, gulp, buildFolder);
sounds.gulptasksSounds($, gulp, buildFolder);
const standalone = require("./standalone");
const js = require("./js"); standalone.gulptasksStandalone($, gulp, buildFolder);
js.gulptasksJS($, gulp, buildFolder, browserSync);
const releaseUploader = require("./release-uploader");
const html = require("./html"); releaseUploader.gulptasksReleaseUploader($, gulp, buildFolder);
html.gulptasksHTML($, gulp, buildFolder, browserSync);
const translations = require("./translations");
const ftp = require("./ftp"); translations.gulptasksTranslations($, gulp, buildFolder);
ftp.gulptasksFTP($, gulp, buildFolder);
///////////////////// BUILD TASKS /////////////////////
const docs = require("./docs");
docs.gulptasksDocs($, gulp, buildFolder); // Cleans up everything
gulp.task("utils.cleanBuildFolder", () => {
const standalone = require("./standalone"); return gulp.src(buildFolder, { read: false, allowEmpty: true }).pipe($.clean({ force: true }));
standalone.gulptasksStandalone($, gulp, buildFolder); });
gulp.task("utils.cleanBuildTempFolder", () => {
const translations = require("./translations"); return gulp
translations.gulptasksTranslations($, gulp, buildFolder); .src(path.join(__dirname, "..", "src", "js", "built-temp"), { read: false, allowEmpty: true })
.pipe($.clean({ force: true }));
// FIXME });
// const cordova = require("./cordova"); gulp.task("utils.cleanImageBuildFolder", () => {
// cordova.gulptasksCordova($, gulp, buildFolder); return gulp
.src(path.join(__dirname, "res_built"), { read: false, allowEmpty: true })
///////////////////// BUILD TASKS ///////////////////// .pipe($.clean({ force: true }));
});
// Cleans up everything
gulp.task("utils.cleanBuildFolder", () => { gulp.task(
return gulp.src(buildFolder, { read: false, allowEmpty: true }).pipe($.clean({ force: true })); "utils.cleanup",
}); gulp.series("utils.cleanBuildFolder", "utils.cleanImageBuildFolder", "utils.cleanBuildTempFolder")
gulp.task("utils.cleanBuildTempFolder", () => { );
return gulp
.src(path.join(__dirname, "..", "src", "js", "built-temp"), { read: false, allowEmpty: true }) // Requires no uncomitted files
.pipe($.clean({ force: true })); gulp.task("utils.requireCleanWorkingTree", cb => {
}); let output = $.trim(execSync("git status -su").toString("ascii")).replace(/\r/gi, "").split("\n");
gulp.task("utils.cleanup", gulp.series("utils.cleanBuildFolder", "utils.cleanBuildTempFolder")); // Filter files which are OK to be untracked
output = output
// Requires no uncomitted files .map(x => x.replace(/[\r\n]+/gi, ""))
gulp.task("utils.requireCleanWorkingTree", cb => { .filter(x => x.indexOf(".local.js") < 0)
let output = $.trim(execSync("git status -su").toString("ascii")).replace(/\r/gi, "").split("\n"); .filter(x => x.length > 0);
if (output.length > 0) {
// Filter files which are OK to be untracked console.error("\n\nYou have unstaged changes, please commit everything first!");
output = output console.error("Unstaged files:");
.map(x => x.replace(/[\r\n]+/gi, "")) console.error(output.map(x => "'" + x + "'").join("\n"));
.filter(x => x.indexOf(".local.js") < 0) process.exit(1);
.filter(x => x.length > 0); }
if (output.length > 0) { cb();
console.error("\n\nYou have unstaged changes, please commit everything first!"); });
console.error("Unstaged files:");
console.error(output.map(x => "'" + x + "'").join("\n")); gulp.task("utils.copyAdditionalBuildFiles", cb => {
process.exit(1); const additionalFolder = path.join("additional_build_files");
} const additionalSrcGlobs = [
cb(); path.join(additionalFolder, "**/*.*"),
}); path.join(additionalFolder, "**/.*"),
path.join(additionalFolder, "**/*"),
gulp.task("utils.copyAdditionalBuildFiles", cb => { ];
const additionalFolder = path.join("additional_build_files");
const additionalSrcGlobs = [ return gulp.src(additionalSrcGlobs).pipe(gulp.dest(buildFolder));
path.join(additionalFolder, "**/*.*"), });
path.join(additionalFolder, "**/.*"),
path.join(additionalFolder, "**/*"), // Starts a webserver on the built directory (useful for testing prod build)
]; gulp.task("main.webserver", () => {
return gulp.src(buildFolder).pipe(
return gulp.src(additionalSrcGlobs).pipe(gulp.dest(buildFolder)); $.webserver({
}); livereload: {
enable: true,
// Starts a webserver on the built directory (useful for testing prod build) },
gulp.task("main.webserver", () => { directoryListing: false,
return gulp.src(buildFolder).pipe( open: true,
$.webserver({ port: 3005,
livereload: { })
enable: true, );
}, });
directoryListing: false,
open: true, function serve({ standalone }) {
port: 3005, browserSync.init({
}) server: buildFolder,
); port: 3005,
}); ghostMode: {
clicks: false,
function serve({ standalone }) { scroll: false,
browserSync.init({ location: false,
server: buildFolder, forms: false,
port: 3005, },
ghostMode: { logLevel: "info",
clicks: false, logPrefix: "BS",
scroll: false, online: false,
location: false, xip: false,
forms: false, notify: false,
}, reloadDebounce: 100,
logLevel: "info", reloadOnRestart: true,
logPrefix: "BS", watchEvents: ["add", "change"],
online: false, });
xip: false,
notify: false, // Watch .scss files, those trigger a css rebuild
reloadDebounce: 100, gulp.watch(["../src/**/*.scss"], gulp.series("css.dev"));
reloadOnRestart: true,
watchEvents: ["add", "change"], // Watch .html files, those trigger a html rebuild
}); gulp.watch("../src/**/*.html", gulp.series(standalone ? "html.standalone-dev" : "html.dev"));
// Watch .scss files, those trigger a css rebuild // Watch sound files
gulp.watch(["../src/**/*.scss"], gulp.series("css.dev")); // gulp.watch(["../res_raw/sounds/**/*.mp3", "../res_raw/sounds/**/*.wav"], gulp.series("sounds.dev"));
// Watch .html files, those trigger a html rebuild // Watch translations
gulp.watch("../src/**/*.html", gulp.series(standalone ? "html.standalone-dev" : "html.dev")); gulp.watch("../translations/**/*.yaml", gulp.series("translations.convertToJson"));
// Watch sound files gulp.watch(
// gulp.watch(["../res_raw/sounds/**/*.mp3", "../res_raw/sounds/**/*.wav"], gulp.series("sounds.dev")); ["../res_raw/sounds/sfx/*.mp3", "../res_raw/sounds/sfx/*.wav"],
gulp.series("sounds.sfx", "sounds.copy")
// Watch translations );
gulp.watch("../translations/**/*.yaml", gulp.series("translations.convertToJson")); gulp.watch(
["../res_raw/sounds/music/*.mp3", "../res_raw/sounds/music/*.wav"],
gulp.watch( gulp.series("sounds.music", "sounds.copy")
["../res_raw/sounds/sfx/*.mp3", "../res_raw/sounds/sfx/*.wav"], );
gulp.series("sounds.sfx", "sounds.copy")
); // Watch resource files and copy them on change
gulp.watch( gulp.watch(imgres.rawImageResourcesGlobs, gulp.series("imgres.buildAtlas"));
["../res_raw/sounds/music/*.mp3", "../res_raw/sounds/music/*.wav"], gulp.watch(imgres.nonImageResourcesGlobs, gulp.series("imgres.copyNonImageResources"));
gulp.series("sounds.music", "sounds.copy") gulp.watch(imgres.imageResourcesGlobs, gulp.series("imgres.copyImageResources"));
);
// Watch .atlas files and recompile the atlas on change
// Watch resource files and copy them on change gulp.watch("../res_built/atlas/*.atlas", gulp.series("imgres.atlasToJson"));
gulp.watch(imgres.nonImageResourcesGlobs, gulp.series("imgres.copyNonImageResources")); gulp.watch("../res_built/atlas/*.json", gulp.series("imgres.atlas"));
gulp.watch(imgres.imageResourcesGlobs, gulp.series("imgres.copyImageResources"));
// Watch the build folder and reload when anything changed
// Watch .atlas files and recompile the atlas on change const extensions = ["html", "js", "png", "gif", "jpg", "svg", "mp3", "ico", "woff2", "json"];
gulp.watch("../res_built/atlas/*.json", gulp.series("imgres.atlas")); gulp.watch(extensions.map(ext => path.join(buildFolder, "**", "*." + ext))).on("change", function (path) {
return gulp.src(path).pipe(browserSync.reload({ stream: true }));
// Watch the build folder and reload when anything changed });
const extensions = ["html", "js", "png", "gif", "jpg", "svg", "mp3", "ico", "woff2", "json"];
gulp.watch(extensions.map(ext => path.join(buildFolder, "**", "*." + ext))).on("change", function (path) { gulp.watch("../src/js/built-temp/*.json").on("change", function (path) {
return gulp.src(path).pipe(browserSync.reload({ stream: true })); return gulp.src(path).pipe(browserSync.reload({ stream: true }));
}); });
gulp.watch("../src/js/built-temp/*.json").on("change", function (path) { // Start the webpack watching server (Will never return)
return gulp.src(path).pipe(browserSync.reload({ stream: true })); if (standalone) {
}); gulp.series("js.standalone-dev.watch")(() => true);
} else {
// Start the webpack watching server (Will never return) gulp.series("js.dev.watch")(() => true);
if (standalone) { }
gulp.series("js.standalone-dev.watch")(() => true); }
} else {
gulp.series("js.dev.watch")(() => true); ///////////////////// RUNNABLE TASKS /////////////////////
}
} // Pre and postbuild
gulp.task("step.baseResources", gulp.series("imgres.allOptimized"));
///////////////////// RUNNABLE TASKS ///////////////////// gulp.task("step.deleteEmpty", cb => {
deleteEmpty.sync(buildFolder);
// Pre and postbuild cb();
gulp.task("step.baseResources", gulp.series("imgres.allOptimized")); });
gulp.task("step.deleteEmpty", cb => {
deleteEmpty.sync(buildFolder); gulp.task("step.postbuild", gulp.series("imgres.cleanupUnusedCssInlineImages", "step.deleteEmpty"));
cb();
}); // Builds everything (dev)
gulp.task(
gulp.task("step.postbuild", gulp.series("imgres.cleanupUnusedCssInlineImages", "step.deleteEmpty")); "build.dev",
gulp.series(
// Builds everything (dev) "utils.cleanup",
gulp.task( "utils.copyAdditionalBuildFiles",
"build.dev", "imgres.buildAtlas",
gulp.series( "imgres.atlasToJson",
"utils.cleanup", "imgres.atlas",
"utils.copyAdditionalBuildFiles", "sounds.dev",
"imgres.atlas", "imgres.copyImageResources",
"sounds.dev", "imgres.copyNonImageResources",
"imgres.copyImageResources", "translations.fullBuild",
"imgres.copyNonImageResources", "css.dev",
"translations.fullBuild", "html.dev"
"css.dev", )
"html.dev" );
)
); // Builds everything (standalone -dev)
gulp.task(
// Builds everything (standalone -dev) "build.standalone.dev",
gulp.task( gulp.series(
"build.standalone.dev", "utils.cleanup",
gulp.series( "imgres.buildAtlas",
"utils.cleanup", "imgres.atlasToJson",
"imgres.atlas", "imgres.atlas",
"sounds.dev", "sounds.dev",
"imgres.copyImageResources", "imgres.copyImageResources",
"imgres.copyNonImageResources", "imgres.copyNonImageResources",
"translations.fullBuild", "translations.fullBuild",
"js.standalone-dev", "css.dev",
"css.dev", "html.standalone-dev"
"html.standalone-dev" )
) );
);
// Builds everything (staging)
// Builds everything (staging) gulp.task("step.staging.code", gulp.series("sounds.fullbuild", "translations.fullBuild", "js.staging"));
gulp.task("step.staging.code", gulp.series("sounds.fullbuild", "translations.fullBuild", "js.staging")); gulp.task(
gulp.task( "step.staging.mainbuild",
"step.staging.mainbuild", gulp.parallel("utils.copyAdditionalBuildFiles", "step.baseResources", "step.staging.code")
gulp.parallel("utils.copyAdditionalBuildFiles", "step.baseResources", "step.staging.code") );
); gulp.task("step.staging.all", gulp.series("step.staging.mainbuild", "css.prod", "html.staging"));
gulp.task("step.staging.all", gulp.series("step.staging.mainbuild", "css.prod", "html.staging")); gulp.task("build.staging", gulp.series("utils.cleanup", "step.staging.all", "step.postbuild"));
gulp.task("build.staging", gulp.series("utils.cleanup", "step.staging.all", "step.postbuild"));
// Builds everything (prod)
// Builds everything (prod) gulp.task("step.prod.code", gulp.series("sounds.fullbuild", "translations.fullBuild", "js.prod"));
gulp.task("step.prod.code", gulp.series("sounds.fullbuild", "translations.fullBuild", "js.prod")); gulp.task(
gulp.task( "step.prod.mainbuild",
"step.prod.mainbuild", gulp.parallel("utils.copyAdditionalBuildFiles", "step.baseResources", "step.prod.code")
gulp.parallel("utils.copyAdditionalBuildFiles", "step.baseResources", "step.prod.code") );
); gulp.task("step.prod.all", gulp.series("step.prod.mainbuild", "css.prod", "html.prod"));
gulp.task("step.prod.all", gulp.series("step.prod.mainbuild", "css.prod", "html.prod")); gulp.task("build.prod", gulp.series("utils.cleanup", "step.prod.all", "step.postbuild"));
gulp.task("build.prod", gulp.series("utils.cleanup", "step.prod.all", "step.postbuild"));
// Builds everything (standalone-beta)
// Builds everything (standalone-beta) gulp.task(
gulp.task( "step.standalone-beta.code",
"step.standalone-beta.code", gulp.series("sounds.fullbuildHQ", "translations.fullBuild", "js.standalone-beta")
gulp.series("sounds.fullbuildHQ", "translations.fullBuild", "js.standalone-beta") );
); gulp.task("step.standalone-beta.mainbuild", gulp.parallel("step.baseResources", "step.standalone-beta.code"));
gulp.task("step.standalone-beta.mainbuild", gulp.parallel("step.baseResources", "step.standalone-beta.code")); gulp.task(
gulp.task( "step.standalone-beta.all",
"step.standalone-beta.all", gulp.series("step.standalone-beta.mainbuild", "css.prod-standalone", "html.standalone-beta")
gulp.series("step.standalone-beta.mainbuild", "css.prod-standalone", "html.standalone-beta") );
); gulp.task(
gulp.task( "build.standalone-beta",
"build.standalone-beta", gulp.series("utils.cleanup", "step.standalone-beta.all", "step.postbuild")
gulp.series("utils.cleanup", "step.standalone-beta.all", "step.postbuild") );
);
// Builds everything (standalone-prod)
// Builds everything (standalone-prod) gulp.task(
gulp.task( "step.standalone-prod.code",
"step.standalone-prod.code", gulp.series("sounds.fullbuildHQ", "translations.fullBuild", "js.standalone-prod")
gulp.series("sounds.fullbuildHQ", "translations.fullBuild", "js.standalone-prod") );
); gulp.task("step.standalone-prod.mainbuild", gulp.parallel("step.baseResources", "step.standalone-prod.code"));
gulp.task("step.standalone-prod.mainbuild", gulp.parallel("step.baseResources", "step.standalone-prod.code")); gulp.task(
gulp.task( "step.standalone-prod.all",
"step.standalone-prod.all", gulp.series("step.standalone-prod.mainbuild", "css.prod-standalone", "html.standalone-prod")
gulp.series("step.standalone-prod.mainbuild", "css.prod-standalone", "html.standalone-prod") );
); gulp.task(
gulp.task( "build.standalone-prod",
"build.standalone-prod", gulp.series("utils.cleanup", "step.standalone-prod.all", "step.postbuild")
gulp.series("utils.cleanup", "step.standalone-prod.all", "step.postbuild") );
);
// OS X build and release upload
// Deploying! gulp.task(
gulp.task( "build.darwin64-prod",
"main.deploy.alpha", gulp.series(
gulp.series("utils.requireCleanWorkingTree", "build.staging", "ftp.upload.alpha") "build.standalone-prod",
); "standalone.prepare",
gulp.task( "standalone.package.prod.darwin64",
"main.deploy.staging", "standalone.uploadRelease.darwin64"
gulp.series("utils.requireCleanWorkingTree", "build.staging", "ftp.upload.staging") )
); );
gulp.task("main.deploy.prod", gulp.series("utils.requireCleanWorkingTree", "build.prod", "ftp.upload.prod"));
gulp.task("main.deploy.all", gulp.series("main.deploy.staging", "main.deploy.prod")); // Deploying!
gulp.task("main.standalone", gulp.series("build.standalone-prod", "standalone.package.prod")); gulp.task(
"main.deploy.alpha",
// Live-development gulp.series("utils.requireCleanWorkingTree", "build.staging", "ftp.upload.alpha")
gulp.task( );
"main.serveDev", gulp.task(
gulp.series("build.dev", () => serve({ standalone: false })) "main.deploy.staging",
); gulp.series("utils.requireCleanWorkingTree", "build.staging", "ftp.upload.staging")
gulp.task( );
"main.serveStandalone", gulp.task("main.deploy.prod", gulp.series("utils.requireCleanWorkingTree", "build.prod", "ftp.upload.prod"));
gulp.series("build.standalone.dev", () => serve({ standalone: true })) gulp.task("main.deploy.all", gulp.series("main.deploy.staging", "main.deploy.prod"));
); gulp.task("main.standalone", gulp.series("build.standalone-prod", "standalone.package.prod"));
gulp.task("default", gulp.series("main.serveDev")); // Live-development
gulp.task(
"main.serveDev",
gulp.series("build.dev", () => serve({ standalone: false }))
);
gulp.task(
"main.serveStandalone",
gulp.series("build.standalone.dev", () => serve({ standalone: true }))
);
gulp.task("default", gulp.series("main.serveDev"));

View File

@ -1,283 +1,301 @@
const buildUtils = require("./buildutils"); const buildUtils = require("./buildutils");
const fs = require("fs"); const fs = require("fs");
const path = require("path"); const path = require("path");
const crypto = require("crypto"); const crypto = require("crypto");
function computeIntegrityHash(fullPath, algorithm = "sha256") { function computeIntegrityHash(fullPath, algorithm = "sha256") {
const file = fs.readFileSync(fullPath); const file = fs.readFileSync(fullPath);
const hash = crypto.createHash(algorithm).update(file).digest("base64"); const hash = crypto.createHash(algorithm).update(file).digest("base64");
return algorithm + "-" + hash; return algorithm + "-" + hash;
} }
function gulptasksHTML($, gulp, buildFolder) { function gulptasksHTML($, gulp, buildFolder) {
const commitHash = buildUtils.getRevision(); const commitHash = buildUtils.getRevision();
async function buildHtml( async function buildHtml(
apiUrl, apiUrl,
{ analytics = false, standalone = false, app = false, integrity = true, enableCachebust = true } { analytics = false, standalone = false, app = false, integrity = true, enableCachebust = true }
) { ) {
function cachebust(url) { function cachebust(url) {
if (enableCachebust) { if (enableCachebust) {
return buildUtils.cachebust(url, commitHash); return buildUtils.cachebust(url, commitHash);
} }
return url; return url;
} }
const hasLocalFiles = standalone || app; const hasLocalFiles = standalone || app;
return gulp return gulp
.src("../src/html/" + (standalone ? "index.standalone.html" : "index.html")) .src("../src/html/" + (standalone ? "index.standalone.html" : "index.html"))
.pipe( .pipe(
$.dom(/** @this {Document} **/ function () { $.dom(
const document = this; /** @this {Document} **/ function () {
const document = this;
// Preconnect to api
const prefetchLink = document.createElement("link"); // Preconnect to api
prefetchLink.rel = "preconnect"; const prefetchLink = document.createElement("link");
prefetchLink.href = apiUrl; prefetchLink.rel = "preconnect";
prefetchLink.setAttribute("crossorigin", "anonymous"); prefetchLink.href = apiUrl;
document.head.appendChild(prefetchLink); prefetchLink.setAttribute("crossorigin", "anonymous");
document.head.appendChild(prefetchLink);
// Append css
const css = document.createElement("link"); // Append css
css.rel = "stylesheet"; const css = document.createElement("link");
css.type = "text/css"; css.rel = "stylesheet";
css.media = "none"; css.type = "text/css";
css.setAttribute("onload", "this.media='all'"); css.media = "none";
css.href = cachebust("main.css"); css.setAttribute("onload", "this.media='all'");
if (integrity) { css.href = cachebust("main.css");
css.setAttribute( if (integrity) {
"integrity", css.setAttribute(
computeIntegrityHash(path.join(buildFolder, "main.css")) "integrity",
); computeIntegrityHash(path.join(buildFolder, "main.css"))
} );
document.head.appendChild(css); }
document.head.appendChild(css);
if (app) {
// Append cordova link // Append async css
const cdv = document.createElement("script"); // const asyncCss = document.createElement("link");
cdv.src = "cordova.js"; // asyncCss.rel = "stylesheet";
cdv.type = "text/javascript"; // asyncCss.type = "text/css";
document.head.appendChild(cdv); // asyncCss.media = "none";
} // asyncCss.setAttribute("onload", "this.media='all'");
// asyncCss.href = cachebust("async-resources.css");
// Google analytics // if (integrity) {
if (analytics) { // asyncCss.setAttribute(
const tagManagerScript = document.createElement("script"); // "integrity",
tagManagerScript.src = "https://www.googletagmanager.com/gtag/js?id=UA-165342524-1"; // computeIntegrityHash(path.join(buildFolder, "async-resources.css"))
tagManagerScript.setAttribute("async", ""); // );
document.head.appendChild(tagManagerScript); // }
// document.head.appendChild(asyncCss);
const initScript = document.createElement("script");
initScript.textContent = ` if (app) {
window.dataLayer = window.dataLayer || []; // Append cordova link
function gtag(){dataLayer.push(arguments);} const cdv = document.createElement("script");
gtag('js', new Date()); cdv.src = "cordova.js";
gtag('config', 'UA-165342524-1', { anonymize_ip: true }); cdv.type = "text/javascript";
`; document.head.appendChild(cdv);
document.head.appendChild(initScript); }
const abTestingScript = document.createElement("script"); // Google analytics
abTestingScript.setAttribute( if (analytics) {
"src", const tagManagerScript = document.createElement("script");
"https://www.googleoptimize.com/optimize.js?id=OPT-M5NHCV7" tagManagerScript.src =
); "https://www.googletagmanager.com/gtag/js?id=UA-165342524-1";
abTestingScript.setAttribute("async", ""); tagManagerScript.setAttribute("async", "");
document.head.appendChild(abTestingScript); document.head.appendChild(tagManagerScript);
}
const initScript = document.createElement("script");
// Do not need to preload in app or standalone initScript.textContent = `
if (!hasLocalFiles) { window.dataLayer = window.dataLayer || [];
// Preload essentials function gtag(){dataLayer.push(arguments);}
const preloads = ["fonts/GameFont.woff2"]; gtag('js', new Date());
gtag('config', 'UA-165342524-1', { anonymize_ip: true });
preloads.forEach(src => { `;
const preloadLink = document.createElement("link"); document.head.appendChild(initScript);
preloadLink.rel = "preload";
preloadLink.href = cachebust("res/" + src); const abTestingScript = document.createElement("script");
if (src.endsWith(".woff2")) { abTestingScript.setAttribute(
preloadLink.setAttribute("crossorigin", "anonymous"); "src",
preloadLink.setAttribute("as", "font"); "https://www.googleoptimize.com/optimize.js?id=OPT-M5NHCV7"
} else { );
preloadLink.setAttribute("as", "image"); abTestingScript.setAttribute("async", "");
} document.head.appendChild(abTestingScript);
document.head.appendChild(preloadLink); }
});
} // Do not need to preload in app or standalone
if (!hasLocalFiles) {
const loadingSvg = `background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHN0eWxlPSJtYXJnaW46YXV0bztiYWNrZ3JvdW5kOjAgMCIgd2lkdGg9IjIwMCIgaGVpZ2h0PSIyMDAiIHZpZXdCb3g9IjAgMCAxMDAgMTAwIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCIgZGlzcGxheT0iYmxvY2siPjxjaXJjbGUgY3g9IjUwIiBjeT0iNTAiIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzM5Mzc0NyIgc3Ryb2tlLXdpZHRoPSIzIiByPSI0MiIgc3Ryb2tlLWRhc2hhcnJheT0iMTk3LjkyMDMzNzE3NjE1Njk4IDY3Ljk3MzQ0NTcyNTM4NTY2IiB0cmFuc2Zvcm09InJvdGF0ZSg0OC4yNjUgNTAgNTApIj48YW5pbWF0ZVRyYW5zZm9ybSBhdHRyaWJ1dGVOYW1lPSJ0cmFuc2Zvcm0iIHR5cGU9InJvdGF0ZSIgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiIGR1cj0iNS41NTU1NTU1NTU1NTU1NTVzIiB2YWx1ZXM9IjAgNTAgNTA7MzYwIDUwIDUwIiBrZXlUaW1lcz0iMDsxIi8+PC9jaXJjbGU+PC9zdmc+")`; // Preload essentials
const preloads = ["fonts/GameFont.woff2"];
const loadingCss = `
@font-face { preloads.forEach(src => {
font-family: 'GameFont'; const preloadLink = document.createElement("link");
font-style: normal; preloadLink.rel = "preload";
font-weight: normal; preloadLink.href = cachebust("res/" + src);
font-display: swap; if (src.endsWith(".woff2")) {
src: url('${cachebust("res/fonts/GameFont.woff2")}') format('woff2'); preloadLink.setAttribute("crossorigin", "anonymous");
} preloadLink.setAttribute("as", "font");
} else {
#ll_fp { preloadLink.setAttribute("as", "image");
font-family: GameFont; }
font-size: 14px; document.head.appendChild(preloadLink);
position: fixed; });
z-index: -1; }
top: 0;
left: 0; const loadingSvg = `background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHN0eWxlPSJtYXJnaW46YXV0bztiYWNrZ3JvdW5kOjAgMCIgd2lkdGg9IjIwMCIgaGVpZ2h0PSIyMDAiIHZpZXdCb3g9IjAgMCAxMDAgMTAwIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCIgZGlzcGxheT0iYmxvY2siPjxjaXJjbGUgY3g9IjUwIiBjeT0iNTAiIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzM5Mzc0NyIgc3Ryb2tlLXdpZHRoPSIzIiByPSI0MiIgc3Ryb2tlLWRhc2hhcnJheT0iMTk3LjkyMDMzNzE3NjE1Njk4IDY3Ljk3MzQ0NTcyNTM4NTY2IiB0cmFuc2Zvcm09InJvdGF0ZSg0OC4yNjUgNTAgNTApIj48YW5pbWF0ZVRyYW5zZm9ybSBhdHRyaWJ1dGVOYW1lPSJ0cmFuc2Zvcm0iIHR5cGU9InJvdGF0ZSIgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiIGR1cj0iNS41NTU1NTU1NTU1NTU1NTVzIiB2YWx1ZXM9IjAgNTAgNTA7MzYwIDUwIDUwIiBrZXlUaW1lcz0iMDsxIi8+PC9jaXJjbGU+PC9zdmc+")`;
opacity: 0.05;
} const loadingCss = `
@font-face {
#ll_p { font-family: 'GameFont';
display: flex; font-style: normal;
position: fixed; font-weight: normal;
z-index: 99999; font-display: swap;
top: 0; src: url('${cachebust("res/fonts/GameFont.woff2")}') format('woff2');
left: 0; }
right: 0;
bottom: 0; #ll_fp {
justify-content: font-family: GameFont;
center; font-size: 14px;
align-items: center; position: fixed;
} z-index: -1;
top: 0;
#ll_p > div { left: 0;
position: absolute; opacity: 0.05;
text-align: center; }
bottom: 40px;
left: 20px; #ll_p {
right: 20px; display: flex;
color: #393747; position: fixed;
font-family: 'GameFont', sans-serif; z-index: 99999;
font-size: 20px; top: 0;
} left: 0;
right: 0;
#ll_p > span { bottom: 0;
width: 60px; justify-content:
height: 60px; center;
display: inline-flex; align-items: center;
background: center center / contain no-repeat; }
${loadingSvg};
} #ll_p > div {
`; position: absolute;
text-align: center;
const style = document.createElement("style"); bottom: 40px;
style.setAttribute("type", "text/css"); left: 20px;
style.textContent = loadingCss; right: 20px;
document.head.appendChild(style); color: #393747;
font-family: 'GameFont', sans-serif;
// Append loader, but not in standalone (directly include bundle there) font-size: 20px;
if (standalone) { }
const bundleScript = document.createElement("script");
bundleScript.type = "text/javascript"; #ll_p > span {
bundleScript.src = "bundle.js"; width: 60px;
if (integrity) { height: 60px;
bundleScript.setAttribute( display: inline-flex;
"integrity", background: center center / contain no-repeat;
computeIntegrityHash(path.join(buildFolder, "bundle.js")) ${loadingSvg};
); }
} `;
document.head.appendChild(bundleScript);
} else { const style = document.createElement("style");
const loadJs = document.createElement("script"); style.setAttribute("type", "text/css");
loadJs.type = "text/javascript"; style.textContent = loadingCss;
let scriptContent = ""; document.head.appendChild(style);
scriptContent += `var bundleSrc = '${cachebust("bundle.js")}';\n`;
scriptContent += `var bundleSrcTranspiled = '${cachebust( // Append loader, but not in standalone (directly include bundle there)
"bundle-transpiled.js" if (standalone) {
)}';\n`; const bundleScript = document.createElement("script");
bundleScript.type = "text/javascript";
if (integrity) { bundleScript.src = "bundle.js";
scriptContent += if (integrity) {
"var bundleIntegrity = '" + bundleScript.setAttribute(
computeIntegrityHash(path.join(buildFolder, "bundle.js")) + "integrity",
"';\n"; computeIntegrityHash(path.join(buildFolder, "bundle.js"))
scriptContent += );
"var bundleIntegrityTranspiled = '" + }
computeIntegrityHash(path.join(buildFolder, "bundle-transpiled.js")) + document.head.appendChild(bundleScript);
"';\n"; } else {
} else { const loadJs = document.createElement("script");
scriptContent += "var bundleIntegrity = null;\n"; loadJs.type = "text/javascript";
scriptContent += "var bundleIntegrityTranspiled = null;\n"; let scriptContent = "";
} scriptContent += `var bundleSrc = '${cachebust("bundle.js")}';\n`;
scriptContent += `var bundleSrcTranspiled = '${cachebust(
scriptContent += fs.readFileSync("./bundle-loader.js").toString(); "bundle-transpiled.js"
loadJs.textContent = scriptContent; )}';\n`;
document.head.appendChild(loadJs);
} if (integrity) {
scriptContent +=
const bodyContent = ` "var bundleIntegrity = '" +
<div id="ll_fp">_</div> computeIntegrityHash(path.join(buildFolder, "bundle.js")) +
<div id="ll_p"> "';\n";
<span></span> scriptContent +=
<div>${hasLocalFiles ? "Loading" : "Downloading"} Game Files</div > "var bundleIntegrityTranspiled = '" +
</div > computeIntegrityHash(path.join(buildFolder, "bundle-transpiled.js")) +
`; "';\n";
} else {
document.body.innerHTML = bodyContent; scriptContent += "var bundleIntegrity = null;\n";
}) scriptContent += "var bundleIntegrityTranspiled = null;\n";
) }
.pipe(
$.htmlmin({ scriptContent += fs.readFileSync("./bundle-loader.js").toString();
caseSensitive: true, loadJs.textContent = scriptContent;
collapseBooleanAttributes: true, document.head.appendChild(loadJs);
collapseInlineTagWhitespace: true, }
collapseWhitespace: true,
preserveLineBreaks: true, const bodyContent = `
minifyJS: true, <div id="ll_fp">_</div>
minifyCSS: true, <div id="ll_p">
quoteCharacter: '"', <span></span>
useShortDoctype: true, <div>${hasLocalFiles ? "Loading" : "Downloading"} Game Files</div >
}) </div >
) `;
.pipe($.htmlBeautify())
.pipe($.rename("index.html")) document.body.innerHTML = bodyContent;
.pipe(gulp.dest(buildFolder)); }
} )
)
gulp.task("html.dev", () => { .pipe(
return buildHtml("http://localhost:5005", { $.htmlmin({
analytics: false, caseSensitive: true,
integrity: false, collapseBooleanAttributes: true,
enableCachebust: false, collapseInlineTagWhitespace: true,
}); collapseWhitespace: true,
}); preserveLineBreaks: true,
minifyJS: true,
gulp.task("html.staging", () => { minifyCSS: true,
return buildHtml("https://api-staging.shapez.io", { quoteCharacter: '"',
analytics: true, useShortDoctype: true,
}); })
}); )
.pipe($.htmlBeautify())
gulp.task("html.prod", () => { .pipe($.rename("index.html"))
return buildHtml("https://analytics.shapez.io", { .pipe(gulp.dest(buildFolder));
analytics: true, }
});
}); gulp.task("html.dev", () => {
return buildHtml("http://localhost:5005", {
gulp.task("html.standalone-dev", () => { analytics: false,
return buildHtml("https://localhost:5005", { integrity: false,
analytics: false, enableCachebust: false,
standalone: true, });
integrity: false, });
enableCachebust: false,
}); gulp.task("html.staging", () => {
}); return buildHtml("https://api-staging.shapez.io", {
analytics: true,
gulp.task("html.standalone-beta", () => { });
return buildHtml("https://api-staging.shapez.io", { });
analytics: false,
standalone: true, gulp.task("html.prod", () => {
enableCachebust: false, return buildHtml("https://analytics.shapez.io", {
}); analytics: true,
}); });
});
gulp.task("html.standalone-prod", () => {
return buildHtml("https://analytics.shapez.io", { gulp.task("html.standalone-dev", () => {
analytics: false, return buildHtml("https://localhost:5005", {
standalone: true, analytics: false,
enableCachebust: false, standalone: true,
}); integrity: false,
}); enableCachebust: false,
} });
});
module.exports = {
gulptasksHTML, gulp.task("html.standalone-beta", () => {
}; return buildHtml("https://api-staging.shapez.io", {
analytics: false,
standalone: true,
enableCachebust: false,
});
});
gulp.task("html.standalone-prod", () => {
return buildHtml("https://analytics.shapez.io", {
analytics: false,
standalone: true,
enableCachebust: false,
});
});
}
module.exports = {
gulptasksHTML,
};

View File

@ -1,148 +1,205 @@
// @ts-ignore const { existsSync } = require("fs");
const path = require("path"); // @ts-ignore
const path = require("path");
// Globs for non-ui resources const atlasToJson = require("./atlas2json");
const nonImageResourcesGlobs = ["../res/**/*.woff2", "../res/*.ico", "../res/**/*.webm"];
const execute = command =>
// Globs for ui resources require("child_process").execSync(command, {
const imageResourcesGlobs = ["../res/**/*.png", "../res/**/*.svg", "../res/**/*.jpg", "../res/**/*.gif"]; encoding: "utf-8",
});
function gulptasksImageResources($, gulp, buildFolder) {
// Lossless options // Globs for atlas resources
const minifyImagesOptsLossless = () => [ const rawImageResourcesGlobs = ["../res_raw/atlas.json", "../res_raw/**/*.png"];
$.imageminJpegtran({
progressive: true, // Globs for non-ui resources
}), const nonImageResourcesGlobs = ["../res/**/*.woff2", "../res/*.ico", "../res/**/*.webm"];
$.imagemin.svgo({}),
$.imagemin.optipng({ // Globs for ui resources
optimizationLevel: 3, const imageResourcesGlobs = ["../res/**/*.png", "../res/**/*.svg", "../res/**/*.jpg", "../res/**/*.gif"];
}),
$.imageminGifsicle({ // Link to download LibGDX runnable-texturepacker.jar
optimizationLevel: 3, const runnableTPSource = "https://libgdx.badlogicgames.com/ci/nightlies/runnables/runnable-texturepacker.jar";
colors: 128,
}), function gulptasksImageResources($, gulp, buildFolder) {
]; // Lossless options
const minifyImagesOptsLossless = () => [
// Lossy options $.imageminJpegtran({
const minifyImagesOpts = () => [ progressive: true,
$.imagemin.mozjpeg({ }),
quality: 80, $.imagemin.svgo({}),
maxMemory: 1024 * 1024 * 8, $.imagemin.optipng({
}), optimizationLevel: 3,
$.imagemin.svgo({}), }),
$.imageminPngquant({ $.imageminGifsicle({
speed: 1, optimizationLevel: 3,
strip: true, colors: 128,
quality: [0.65, 0.9], }),
dithering: false, ];
verbose: false,
}), // Lossy options
$.imagemin.optipng({ const minifyImagesOpts = () => [
optimizationLevel: 3, $.imagemin.mozjpeg({
}), quality: 80,
$.imageminGifsicle({ maxMemory: 1024 * 1024 * 8,
optimizationLevel: 3, }),
colors: 128, $.imagemin.svgo({}),
}), $.imageminPngquant({
]; speed: 1,
strip: true,
// Where the resources folder are quality: [0.65, 0.9],
const resourcesDestFolder = path.join(buildFolder, "res"); dithering: false,
verbose: false,
/** }),
* Determines if an atlas must use lossless compression $.imagemin.optipng({
* @param {string} fname optimizationLevel: 3,
*/ }),
function fileMustBeLossless(fname) { $.imageminGifsicle({
return fname.indexOf("lossless") >= 0; optimizationLevel: 3,
} colors: 128,
}),
/////////////// ATLAS ///////////////////// ];
// Copies the atlas to the final destination // Where the resources folder are
gulp.task("imgres.atlas", () => { const resourcesDestFolder = path.join(buildFolder, "res");
return gulp
.src(["../res_built/atlas/*.png"]) /**
.pipe($.cached("imgres.atlas")) * Determines if an atlas must use lossless compression
.pipe(gulp.dest(resourcesDestFolder)); * @param {string} fname
}); */
function fileMustBeLossless(fname) {
// Copies the atlas to the final destination after optimizing it (lossy compression) return fname.indexOf("lossless") >= 0;
gulp.task("imgres.atlasOptimized", () => { }
return gulp
.src(["../res_built/atlas/*.png"]) /////////////// ATLAS /////////////////////
.pipe($.cached("imgres.atlasOptimized"))
.pipe( gulp.task("imgres.buildAtlas", cb => {
$.if( const config = JSON.stringify("../res_raw/atlas.json");
fname => fileMustBeLossless(fname.history[0]), const source = JSON.stringify("../res_raw");
$.imagemin(minifyImagesOptsLossless()), const dest = JSON.stringify("../res_built/atlas");
$.imagemin(minifyImagesOpts())
) try {
) // First check whether Java is installed
.pipe(gulp.dest(resourcesDestFolder)); execute("java -version");
}); // Now check and try downloading runnable-texturepacker.jar (22MB)
if (!existsSync("./runnable-texturepacker.jar")) {
//////////////////// RESOURCES ////////////////////// const safeLink = JSON.stringify(runnableTPSource);
const commands = [
// Copies all resources which are no ui resources // linux/macos if installed
gulp.task("imgres.copyNonImageResources", () => { `wget -O runnable-texturepacker.jar ${safeLink}`,
return gulp // linux/macos, latest windows 10
.src(nonImageResourcesGlobs) `curl -o runnable-texturepacker.jar ${safeLink}`,
.pipe($.cached("imgres.copyNonImageResources")) // windows 10 / updated windows 7+
.pipe(gulp.dest(resourcesDestFolder)); "powershell.exe -Command (new-object System.Net.WebClient)" +
}); `.DownloadFile(${safeLink.replace(/"/g, "'")}, 'runnable-texturepacker.jar')`,
// windows 7+, vulnerability exploit
// Copies all ui resources `certutil.exe -urlcache -split -f ${safeLink} runnable-texturepacker.jar`,
gulp.task("imgres.copyImageResources", () => { ];
return gulp
.src(imageResourcesGlobs) while (commands.length) {
.pipe($.cached("copyImageResources")) try {
.pipe(gulp.dest(path.join(resourcesDestFolder))); execute(commands.shift());
}); break;
} catch {
// Copies all ui resources and optimizes them if (!commands.length) {
gulp.task("imgres.copyImageResourcesOptimized", () => { throw new Error("Failed to download runnable-texturepacker.jar!");
return gulp }
.src(imageResourcesGlobs) }
.pipe($.cached("imgres.copyImageResourcesOptimized")) }
.pipe( }
$.if(
fname => fileMustBeLossless(fname.history[0]), execute(`java -jar runnable-texturepacker.jar ${source} ${dest} atlas0 ${config}`);
$.imagemin(minifyImagesOptsLossless()), } catch {
$.imagemin(minifyImagesOpts()) console.warn("Building atlas failed. Java not found / unsupported version?");
) }
) cb();
.pipe(gulp.dest(path.join(resourcesDestFolder))); });
});
// Converts .atlas LibGDX files to JSON
// Copies all resources and optimizes them gulp.task("imgres.atlasToJson", cb => {
gulp.task( atlasToJson.convert("../res_built/atlas");
"imgres.allOptimized", cb();
gulp.parallel( });
"imgres.atlasOptimized",
"imgres.copyNonImageResources", // Copies the atlas to the final destination
"imgres.copyImageResourcesOptimized" gulp.task("imgres.atlas", () => {
) return gulp.src(["../res_built/atlas/*.png"]).pipe(gulp.dest(resourcesDestFolder));
); });
// Cleans up unused images which are instead inline into the css // Copies the atlas to the final destination after optimizing it (lossy compression)
gulp.task("imgres.cleanupUnusedCssInlineImages", () => { gulp.task("imgres.atlasOptimized", () => {
return gulp return gulp
.src( .src(["../res_built/atlas/*.png"])
[ .pipe(
path.join(buildFolder, "res", "ui", "**", "*.png"), $.if(
path.join(buildFolder, "res", "ui", "**", "*.jpg"), fname => fileMustBeLossless(fname.history[0]),
path.join(buildFolder, "res", "ui", "**", "*.svg"), $.imagemin(minifyImagesOptsLossless()),
path.join(buildFolder, "res", "ui", "**", "*.gif"), $.imagemin(minifyImagesOpts())
], )
{ read: false } )
) .pipe(gulp.dest(resourcesDestFolder));
.pipe($.if(fname => fname.history[0].indexOf("noinline") < 0, $.clean({ force: true }))); });
});
} //////////////////// RESOURCES //////////////////////
module.exports = { // Copies all resources which are no ui resources
nonImageResourcesGlobs, gulp.task("imgres.copyNonImageResources", () => {
imageResourcesGlobs, return gulp.src(nonImageResourcesGlobs).pipe(gulp.dest(resourcesDestFolder));
gulptasksImageResources, });
};
// Copies all ui resources
gulp.task("imgres.copyImageResources", () => {
return gulp
.src(imageResourcesGlobs)
.pipe($.cached("imgres.copyImageResources"))
.pipe(gulp.dest(path.join(resourcesDestFolder)));
});
// Copies all ui resources and optimizes them
gulp.task("imgres.copyImageResourcesOptimized", () => {
return gulp
.src(imageResourcesGlobs)
.pipe(
$.if(
fname => fileMustBeLossless(fname.history[0]),
$.imagemin(minifyImagesOptsLossless()),
$.imagemin(minifyImagesOpts())
)
)
.pipe(gulp.dest(path.join(resourcesDestFolder)));
});
// Copies all resources and optimizes them
gulp.task(
"imgres.allOptimized",
gulp.parallel(
"imgres.buildAtlas",
"imgres.atlasToJson",
"imgres.atlasOptimized",
"imgres.copyNonImageResources",
"imgres.copyImageResourcesOptimized"
)
);
// Cleans up unused images which are instead inline into the css
gulp.task("imgres.cleanupUnusedCssInlineImages", () => {
return gulp
.src(
[
path.join(buildFolder, "res", "ui", "**", "*.png"),
path.join(buildFolder, "res", "ui", "**", "*.jpg"),
path.join(buildFolder, "res", "ui", "**", "*.svg"),
path.join(buildFolder, "res", "ui", "**", "*.gif"),
],
{ read: false }
)
.pipe($.if(fname => fname.history[0].indexOf("noinline") < 0, $.clean({ force: true })));
});
}
module.exports = {
rawImageResourcesGlobs,
nonImageResourcesGlobs,
imageResourcesGlobs,
gulptasksImageResources,
};

View File

@ -1,110 +1,114 @@
{ {
"name": "builder", "name": "builder",
"version": "1.0.0", "version": "1.0.0",
"description": "builder", "description": "builder",
"private": true, "private": true,
"scripts": { "scripts": {
"gulp": "gulp" "gulp": "gulp"
}, },
"author": "tobspr", "author": "tobspr",
"license": "private", "license": "private",
"dependencies": { "dependencies": {
"@babel/core": "^7.9.0", "@babel/core": "^7.9.0",
"@babel/plugin-transform-block-scoping": "^7.4.4", "@babel/plugin-transform-block-scoping": "^7.4.4",
"@babel/plugin-transform-classes": "^7.5.5", "@babel/plugin-transform-classes": "^7.5.5",
"@babel/preset-env": "^7.5.4", "@babel/preset-env": "^7.5.4",
"@types/cordova": "^0.0.34", "@types/cordova": "^0.0.34",
"@types/filesystem": "^0.0.29", "@types/filesystem": "^0.0.29",
"@types/node": "^12.7.5", "@types/node": "^12.7.5",
"ajv": "^6.10.2", "ajv": "^6.10.2",
"audiosprite": "^0.7.2", "audiosprite": "^0.7.2",
"babel-loader": "^8.1.0", "babel-core": "^6.26.3",
"browser-sync": "^2.26.10", "babel-loader": "^8.1.0",
"circular-dependency-plugin": "^5.0.2", "browser-sync": "^2.26.10",
"circular-json": "^0.5.9", "circular-dependency-plugin": "^5.0.2",
"clipboard-copy": "^3.1.0", "circular-json": "^0.5.9",
"colors": "^1.3.3", "clipboard-copy": "^3.1.0",
"core-js": "3", "colors": "^1.3.3",
"crypto": "^1.0.1", "core-js": "3",
"cssnano-preset-advanced": "^4.0.7", "crypto": "^1.0.1",
"delete-empty": "^3.0.0", "cssnano-preset-advanced": "^4.0.7",
"email-validator": "^2.0.4", "delete-empty": "^3.0.0",
"eslint": "^5.9.0", "email-validator": "^2.0.4",
"fastdom": "^1.0.9", "eslint": "^5.9.0",
"flatted": "^2.0.1", "fastdom": "^1.0.9",
"fs-extra": "^8.1.0", "flatted": "^2.0.1",
"gulp-audiosprite": "^1.1.0", "fs-extra": "^8.1.0",
"howler": "^2.1.2", "gulp-audiosprite": "^1.1.0",
"html-loader": "^0.5.5", "howler": "^2.1.2",
"ignore-loader": "^0.1.2", "html-loader": "^0.5.5",
"lz-string": "^1.4.4", "ignore-loader": "^0.1.2",
"markdown-loader": "^5.1.0", "lz-string": "^1.4.4",
"node-sri": "^1.1.1", "markdown-loader": "^5.1.0",
"phonegap-plugin-mobile-accessibility": "^1.0.5", "node-sri": "^1.1.1",
"promise-polyfill": "^8.1.0", "phonegap-plugin-mobile-accessibility": "^1.0.5",
"query-string": "^6.8.1", "promise-polyfill": "^8.1.0",
"rusha": "^0.8.13", "query-string": "^6.8.1",
"serialize-error": "^3.0.0", "rusha": "^0.8.13",
"strictdom": "^1.0.1", "serialize-error": "^3.0.0",
"string-replace-webpack-plugin": "^0.1.3", "stream-browserify": "^3.0.0",
"terser-webpack-plugin": "^1.1.0", "strictdom": "^1.0.1",
"through2": "^3.0.1", "string-replace-webpack-plugin": "^0.1.3",
"uglify-template-string-loader": "^1.1.0", "strip-indent": "^3.0.0",
"unused-files-webpack-plugin": "^3.4.0", "terser-webpack-plugin": "^1.1.0",
"webpack": "^4.43.0", "through2": "^3.0.1",
"webpack-cli": "^3.1.0", "uglify-template-string-loader": "^1.1.0",
"webpack-deep-scope-plugin": "^1.6.0", "unused-files-webpack-plugin": "^3.4.0",
"webpack-plugin-replace": "^1.1.1", "webpack": "^4.43.0",
"webpack-strip-block": "^0.2.0", "webpack-cli": "^3.1.0",
"whatwg-fetch": "^3.0.0", "webpack-deep-scope-plugin": "^1.6.0",
"worker-loader": "^2.0.0" "webpack-plugin-replace": "^1.1.1",
}, "webpack-strip-block": "^0.2.0",
"devDependencies": { "whatwg-fetch": "^3.0.0",
"autoprefixer": "^9.4.3", "worker-loader": "^2.0.0"
"babel-plugin-closure-elimination": "^1.3.0", },
"babel-plugin-console-source": "^2.0.2", "devDependencies": {
"babel-plugin-danger-remove-unused-import": "^1.1.2", "autoprefixer": "^9.4.3",
"css-mqpacker": "^7.0.0", "babel-plugin-closure-elimination": "^1.3.0",
"cssnano": "^4.1.10", "babel-plugin-console-source": "^2.0.2",
"electron-packager": "^14.0.6", "babel-plugin-danger-remove-unused-import": "^1.1.2",
"faster.js": "^1.1.0", "css-mqpacker": "^7.0.0",
"glob": "^7.1.3", "cssnano": "^4.1.10",
"gulp": "^4.0.2", "electron-packager": "^14.0.6",
"gulp-cache": "^1.1.3", "faster.js": "^1.1.0",
"gulp-cached": "^1.1.1", "glob": "^7.1.3",
"gulp-clean": "^0.4.0", "gulp": "^4.0.2",
"gulp-dom": "^1.0.0", "gulp-cache": "^1.1.3",
"gulp-flatten": "^0.4.0", "gulp-cached": "^1.1.1",
"gulp-fluent-ffmpeg": "^2.0.0", "gulp-clean": "^0.4.0",
"gulp-html-beautify": "^1.0.1", "gulp-dom": "^1.0.0",
"gulp-htmlmin": "^5.0.1", "gulp-flatten": "^0.4.0",
"gulp-if": "^3.0.0", "gulp-fluent-ffmpeg": "^2.0.0",
"gulp-imagemin": "^7.1.0", "gulp-html-beautify": "^1.0.1",
"gulp-load-plugins": "^2.0.3", "gulp-htmlmin": "^5.0.1",
"gulp-phonegap-build": "^0.1.5", "gulp-if": "^3.0.0",
"gulp-plumber": "^1.2.1", "gulp-imagemin": "^7.1.0",
"gulp-pngquant": "^1.0.13", "gulp-load-plugins": "^2.0.3",
"gulp-postcss": "^8.0.0", "gulp-phonegap-build": "^0.1.5",
"gulp-rename": "^2.0.0", "gulp-plumber": "^1.2.1",
"gulp-sass": "^4.1.0", "gulp-pngquant": "^1.0.13",
"gulp-sass-lint": "^1.4.0", "gulp-postcss": "^8.0.0",
"gulp-sftp": "git+https://git@github.com/webksde/gulp-sftp", "gulp-rename": "^2.0.0",
"gulp-terser": "^1.2.0", "gulp-sass": "^4.1.0",
"gulp-webserver": "^0.9.1", "gulp-sass-lint": "^1.4.0",
"gulp-yaml": "^2.0.4", "gulp-sftp": "git+https://git@github.com/webksde/gulp-sftp",
"imagemin-gifsicle": "^7.0.0", "gulp-terser": "^1.2.0",
"imagemin-jpegtran": "^7.0.0", "gulp-webserver": "^0.9.1",
"imagemin-pngquant": "^9.0.0", "gulp-yaml": "^2.0.4",
"jimp": "^0.6.1", "imagemin-gifsicle": "^7.0.0",
"js-yaml": "^3.13.1", "imagemin-jpegtran": "^7.0.0",
"postcss-assets": "^5.0.0", "imagemin-pngquant": "^9.0.0",
"postcss-preset-env": "^6.5.0", "jimp": "^0.6.1",
"postcss-round-subpixels": "^1.2.0", "js-yaml": "^3.13.1",
"postcss-unprefix": "^2.1.3", "postcss-assets": "^5.0.0",
"sass-unused": "^0.3.0", "postcss-critical-split": "^2.5.3",
"strip-json-comments": "^3.0.1", "postcss-preset-env": "^6.5.0",
"trim": "^0.0.1", "postcss-round-subpixels": "^1.2.0",
"webpack-stream": "^5.2.1", "postcss-unprefix": "^2.1.3",
"yaml-loader": "^0.6.0" "sass-unused": "^0.3.0",
} "strip-json-comments": "^3.0.1",
} "trim": "^0.0.1",
"webpack-stream": "^5.2.1",
"yaml-loader": "^0.6.0"
}
}

66
gulp/release-uploader.js Normal file
View File

@ -0,0 +1,66 @@
const path = require("path");
const fs = require("fs");
const execSync = require("child_process").execSync;
const { Octokit } = require("@octokit/rest");
const buildutils = require("./buildutils");
function gulptasksReleaseUploader($, gulp, buildFolder) {
const standaloneDir = path.join(__dirname, "..", "tmp_standalone_files");
const darwinApp = path.join(standaloneDir, "shapez.io-standalone-darwin-x64", "shapez.io-standalone.app");
const dmgName = "shapez.io-standalone.dmg";
const dmgPath = path.join(standaloneDir, "shapez.io-standalone-darwin-x64", dmgName);
gulp.task("standalone.uploadRelease.darwin64.cleanup", () => {
return gulp.src(dmgPath, { read: false, allowEmpty: true }).pipe($.clean({ force: true }));
});
gulp.task("standalone.uploadRelease.darwin64.compress", cb => {
console.log("Packaging disk image", dmgPath);
execSync(`hdiutil create -format UDBZ -srcfolder ${darwinApp} ${dmgPath}`);
cb();
});
gulp.task("standalone.uploadRelease.darwin64.upload", async cb => {
const currentTag = buildutils.getTag();
const octokit = new Octokit({
auth: process.env.SHAPEZ_CLI_GITHUB_TOKEN
});
const createdRelease = await octokit.request("POST /repos/{owner}/{repo}/releases", {
owner: process.env.SHAPEZ_CLI_GITHUB_USER,
repo: "shapez.io",
tag_name: currentTag,
name: currentTag,
draft: true
});
const { data: { id, upload_url } } = createdRelease;
console.log(`Created release ${id} for tag ${currentTag}`);
const dmgContents = fs.readFileSync(dmgPath);
const dmgSize = fs.statSync(dmgPath).size;
console.log("Uploading", dmgContents.length / 1024 / 1024, "MB to", upload_url);
await octokit.request({
method: "POST",
url: upload_url,
headers: {
"content-type": "application/x-apple-diskimage"
},
name: dmgName,
data: dmgContents
});
cb();
});
gulp.task("standalone.uploadRelease.darwin64",
gulp.series(
"standalone.uploadRelease.darwin64.cleanup",
"standalone.uploadRelease.darwin64.compress",
"standalone.uploadRelease.darwin64.upload"
));
}
module.exports = { gulptasksReleaseUploader };

View File

@ -16,6 +16,12 @@ function gulptasksSounds($, gulp, buildFolder) {
cacheDirName: "shapezio-precompiled-sounds", cacheDirName: "shapezio-precompiled-sounds",
}); });
function getFileCacheValue(file) {
const { _isVinyl, base, cwd, contents, history, stat, path } = file;
const encodedContents = Buffer.from(contents).toString("base64");
return { _isVinyl, base, cwd, contents: encodedContents, history, stat, path };
}
// Encodes the game music // Encodes the game music
gulp.task("sounds.music", () => { gulp.task("sounds.music", () => {
return gulp return gulp
@ -34,6 +40,7 @@ function gulptasksSounds($, gulp, buildFolder) {
{ {
name: "music", name: "music",
fileCache, fileCache,
value: getFileCacheValue,
} }
) )
) )
@ -58,6 +65,7 @@ function gulptasksSounds($, gulp, buildFolder) {
{ {
name: "music-high-quality", name: "music-high-quality",
fileCache, fileCache,
value: getFileCacheValue,
} }
) )
) )
@ -110,7 +118,6 @@ function gulptasksSounds($, gulp, buildFolder) {
return gulp return gulp
.src(path.join(builtSoundsDir, "**", "*.mp3")) .src(path.join(builtSoundsDir, "**", "*.mp3"))
.pipe($.plumber()) .pipe($.plumber())
.pipe($.cached("sounds.copy"))
.pipe(gulp.dest(path.join(buildFolder, "res", "sounds"))); .pipe(gulp.dest(path.join(buildFolder, "res", "sounds")));
}); });

View File

@ -1,8 +1,10 @@
require("colors");
const packager = require("electron-packager"); const packager = require("electron-packager");
const path = require("path"); const path = require("path");
const { getVersion } = require("./buildutils"); const { getVersion } = require("./buildutils");
const fs = require("fs"); const fs = require("fs");
const fse = require("fs-extra"); const fse = require("fs-extra");
const buildutils = require("./buildutils");
const execSync = require("child_process").execSync; const execSync = require("child_process").execSync;
function gulptasksStandalone($, gulp) { function gulptasksStandalone($, gulp) {
@ -46,6 +48,20 @@ function gulptasksStandalone($, gulp) {
cb(); cb();
}); });
gulp.task("standalone.prepareVDF", cb => {
const hash = buildutils.getRevision();
const steampipeDir = path.join(__dirname, "steampipe", "scripts");
const templateContents = fs
.readFileSync(path.join(steampipeDir, "app.vdf.template"), { encoding: "utf-8" })
.toString();
const convertedContents = templateContents.replace("$DESC$", "Commit " + hash);
fs.writeFileSync(path.join(steampipeDir, "app.vdf"), convertedContents);
cb();
});
gulp.task("standalone.prepare.minifyCode", () => { gulp.task("standalone.prepare.minifyCode", () => {
return gulp.src(path.join(electronBaseDir, "*.js")).pipe(gulp.dest(tempDestBuildDir)); return gulp.src(path.join(electronBaseDir, "*.js")).pipe(gulp.dest(tempDestBuildDir));
}); });
@ -80,8 +96,9 @@ function gulptasksStandalone($, gulp) {
* @param {'win32'|'linux'|'darwin'} platform * @param {'win32'|'linux'|'darwin'} platform
* @param {'x64'|'ia32'} arch * @param {'x64'|'ia32'} arch
* @param {function():void} cb * @param {function():void} cb
* @param {boolean=} isRelease
*/ */
function packageStandalone(platform, arch, cb) { function packageStandalone(platform, arch, cb, isRelease = true) {
const tomlFile = fs.readFileSync(path.join(__dirname, ".itch.toml")); const tomlFile = fs.readFileSync(path.join(__dirname, ".itch.toml"));
packager({ packager({
@ -99,6 +116,21 @@ function gulptasksStandalone($, gulp) {
overwrite: true, overwrite: true,
appBundleId: "io.shapez.standalone", appBundleId: "io.shapez.standalone",
appCategoryType: "public.app-category.games", appCategoryType: "public.app-category.games",
...(isRelease &&
platform === "darwin" && {
osxSign: {
"identity": process.env.SHAPEZ_CLI_APPLE_CERT_NAME,
"hardened-runtime": true,
"hardenedRuntime": true,
"entitlements": "entitlements.plist",
"entitlements-inherit": "entitlements.plist",
"signature-flags": "library",
},
osxNotarize: {
appleId: process.env.SHAPEZ_CLI_APPLE_ID,
appleIdPassword: "@keychain:SHAPEZ_CLI_APPLE_ID",
},
}),
}).then( }).then(
appPaths => { appPaths => {
console.log("Packages created:", appPaths); console.log("Packages created:", appPaths);
@ -123,7 +155,15 @@ function gulptasksStandalone($, gulp) {
fs.chmodSync(path.join(appPath, "play.sh"), 0o775); fs.chmodSync(path.join(appPath, "play.sh"), 0o775);
} }
if (platform === "darwin") { if (process.platform === "win32" && platform === "darwin") {
console.warn(
"Cross-building for macOS on Windows: dereferencing symlinks.\n".red +
"This will nearly double app size and make code signature invalid. Sorry!\n"
.red.bold +
"For more information, see " +
"https://github.com/electron/electron-packager/issues/71".underline
);
// Clear up framework folders // Clear up framework folders
fs.writeFileSync( fs.writeFileSync(
path.join(appPath, "play.sh"), path.join(appPath, "play.sh"),
@ -175,6 +215,9 @@ function gulptasksStandalone($, gulp) {
gulp.task("standalone.package.prod.linux64", cb => packageStandalone("linux", "x64", cb)); gulp.task("standalone.package.prod.linux64", cb => packageStandalone("linux", "x64", cb));
gulp.task("standalone.package.prod.linux32", cb => packageStandalone("linux", "ia32", cb)); gulp.task("standalone.package.prod.linux32", cb => packageStandalone("linux", "ia32", cb));
gulp.task("standalone.package.prod.darwin64", cb => packageStandalone("darwin", "x64", cb)); gulp.task("standalone.package.prod.darwin64", cb => packageStandalone("darwin", "x64", cb));
gulp.task("standalone.package.prod.darwin64.unsigned", cb =>
packageStandalone("darwin", "x64", cb, false)
);
gulp.task( gulp.task(
"standalone.package.prod", "standalone.package.prod",

2
gulp/steampipe/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
steamtemp
app.vdf

View File

@ -0,0 +1,15 @@
"appbuild"
{
"appid" "1318690"
"desc" "$DESC$"
"buildoutput" "C:\work\shapez\shapez.io\gulp\steampipe\steamtemp"
"contentroot" ""
"setlive" ""
"preview" "0"
"local" ""
"depots"
{
"1318691" "C:\work\shapez\shapez.io\gulp\steampipe\scripts\windows.vdf"
"1318692" "C:\work\shapez\shapez.io\gulp\steampipe\scripts\linux.vdf"
}
}

View File

@ -0,0 +1,12 @@
"DepotBuildConfig"
{
"DepotID" "1318692"
"contentroot" "C:\work\shapez\shapez.io\tmp_standalone_files\shapez.io-standalone-linux-x64"
"FileMapping"
{
"LocalPath" "*"
"DepotPath" "."
"recursive" "1"
}
"FileExclusion" "*.pdb"
}

View File

@ -0,0 +1,12 @@
"DepotBuildConfig"
{
"DepotID" "1318691"
"contentroot" "C:\work\shapez\shapez.io\tmp_standalone_files\shapez.io-standalone-win32-x64"
"FileMapping"
{
"LocalPath" "*"
"DepotPath" "."
"recursive" "1"
}
"FileExclusion" "*.pdb"
}

View File

@ -0,0 +1,4 @@
@echo off
cmd /c gulp standalone.prepareVDF
steamcmd +login %STEAM_UPLOAD_SHAPEZ_ID% %STEAM_UPLOAD_SHAPEZ_USER% +run_app_build %cd%/scripts/app.vdf +quit
start https://partner.steamgames.com/apps/builds/1318690

View File

@ -1,22 +1,89 @@
const path = require("path"); const path = require("path");
const fs = require("fs");
const yaml = require("gulp-yaml"); const gulpYaml = require("gulp-yaml");
const YAML = require("yaml");
const translationsSourceDir = path.join(__dirname, "..", "translations"); const stripIndent = require("strip-indent");
const translationsJsonDir = path.join(__dirname, "..", "src", "js", "built-temp"); const trim = require("trim");
function gulptasksTranslations($, gulp) { const translationsSourceDir = path.join(__dirname, "..", "translations");
gulp.task("translations.convertToJson", () => { const translationsJsonDir = path.join(__dirname, "..", "src", "js", "built-temp");
return gulp
.src(path.join(translationsSourceDir, "*.yaml")) function gulptasksTranslations($, gulp) {
.pipe($.plumber()) gulp.task("translations.convertToJson", () => {
.pipe(yaml({ space: 2, safe: true })) return gulp
.pipe(gulp.dest(translationsJsonDir)); .src(path.join(translationsSourceDir, "*.yaml"))
}); .pipe($.plumber())
.pipe(gulpYaml({ space: 2, safe: true }))
gulp.task("translations.fullBuild", gulp.series("translations.convertToJson")); .pipe(gulp.dest(translationsJsonDir));
} });
module.exports = { gulp.task("translations.fullBuild", gulp.series("translations.convertToJson"));
gulptasksTranslations,
}; gulp.task("translations.prepareSteamPage", cb => {
const files = fs.readdirSync(translationsSourceDir);
files
.filter(name => name.endsWith(".yaml"))
.forEach(fname => {
const languageName = fname.replace(".yaml", "");
const abspath = path.join(translationsSourceDir, fname);
const destpath = path.join(translationsSourceDir, "tmp", languageName + "-store.txt");
const contents = fs.readFileSync(abspath, { encoding: "utf-8" });
const data = YAML.parse(contents);
const storePage = data.steamPage;
const content = `
[img]{STEAM_APP_IMAGE}/extras/store_page_gif.gif[/img]
${storePage.intro.replace(/\n/gi, "\n\n")}
[h2]${storePage.title_advantages}[/h2]
[list]
${storePage.advantages
.map(x => "[*] " + x.replace(/<b>/, "[b]").replace(/<\/b>/, "[/b]"))
.join("\n")}
[/list]
[h2]${storePage.title_future}[/h2]
[list]
${storePage.planned
.map(x => "[*] " + x.replace(/<b>/, "[b]").replace(/<\/b>/, "[/b]"))
.join("\n")}
[/list]
[h2]${storePage.title_open_source}[/h2]
${storePage.text_open_source.replace(/\n/gi, "\n\n")}
[h2]${storePage.title_links}[/h2]
[list]
[*] [url=https://discord.com/invite/HN7EVzV]${storePage.links.discord}[/url]
[*] [url=https://trello.com/b/ISQncpJP/shapezio]${storePage.links.roadmap}[/url]
[*] [url=https://www.reddit.com/r/shapezio]${storePage.links.subreddit}[/url]
[*] [url=https://github.com/tobspr/shapez.io]${storePage.links.source_code}[/url]
[*] [url=https://github.com/tobspr/shapez.io/blob/master/translations/README.md]${
storePage.links.translate
}[/url]
[/list]
`;
fs.writeFileSync(destpath, trim(content.replace(/(\n[ \t\r]*)/gi, "\n")), {
encoding: "utf-8",
});
});
cb();
});
}
module.exports = {
gulptasksTranslations,
};

File diff suppressed because it is too large Load Diff

View File

@ -28,6 +28,7 @@
"@types/cordova": "^0.0.34", "@types/cordova": "^0.0.34",
"@types/filesystem": "^0.0.29", "@types/filesystem": "^0.0.29",
"ajv": "^6.10.2", "ajv": "^6.10.2",
"babel-core": "^6.26.3",
"babel-loader": "^8.0.4", "babel-loader": "^8.0.4",
"circular-dependency-plugin": "^5.0.2", "circular-dependency-plugin": "^5.0.2",
"circular-json": "^0.5.9", "circular-json": "^0.5.9",
@ -49,6 +50,7 @@
"markdown-loader": "^4.0.0", "markdown-loader": "^4.0.0",
"match-all": "^1.2.5", "match-all": "^1.2.5",
"phonegap-plugin-mobile-accessibility": "^1.0.5", "phonegap-plugin-mobile-accessibility": "^1.0.5",
"postcss": ">=5.0.0",
"promise-polyfill": "^8.1.0", "promise-polyfill": "^8.1.0",
"query-string": "^6.8.1", "query-string": "^6.8.1",
"rusha": "^0.8.13", "rusha": "^0.8.13",
@ -71,6 +73,7 @@
"yawn-yaml": "^1.5.0" "yawn-yaml": "^1.5.0"
}, },
"devDependencies": { "devDependencies": {
"@octokit/rest": "^18.0.6",
"@typescript-eslint/eslint-plugin": "3.0.1", "@typescript-eslint/eslint-plugin": "3.0.1",
"@typescript-eslint/parser": "3.0.1", "@typescript-eslint/parser": "3.0.1",
"autoprefixer": "^9.4.3", "autoprefixer": "^9.4.3",

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

View File

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

View File

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 287 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Some files were not shown because too many files have changed in this diff Show More