Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .eslintrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@
after: true
no-useless-escape: off
no-prototype-builtins: off
overrides:
- files: "*.mjs"
parserOptions:
sourceType: module
globals:
BigInt: readonly
_: readonly
Expand Down
21 changes: 11 additions & 10 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,17 @@ jobs:
name: Test
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js environment
uses: actions/setup-node@v2.1.2
with:
node-version: 14
- name: Setup Node.js environment
uses: actions/setup-node@v4
with:
node-version: lts/*
cache: npm

- name: Install dependencies
run: npm ci
- name: Install dependencies
run: npm ci

- name: Run tests
run: npm test
- name: Run tests
run: npm test
7 changes: 2 additions & 5 deletions .github/workflows/gh-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
- name: Setup Node.js environment
uses: actions/setup-node@v4
with:
node-version: 14
node-version: lts/*
cache: npm

- name: Install dependencies
Expand All @@ -32,11 +32,8 @@ jobs:
- name: Run tests
run: npm test

- name: Install gulp-cli
run: npm install gulp-cli

- name: Build distribution
run: gulp build
run: npm run build-extensions

- name: Push changes to gh-pages
run: >
Expand Down
174 changes: 174 additions & 0 deletions dev/build.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import fs from "node:fs/promises";
import path from "node:path";

const loadFile = filePath => fs.readFile(`.${filePath}`, "utf8");

async function build() {
const galleryData = await getGalleryData();
await fs.writeFile("Extensions/dist/page/gallery.json", JSON.stringify(galleryData), {
encoding: "utf8",
flag: "w+",
});

const listData = await getListData();
await fs.writeFile("Extensions/dist/page/list.json", JSON.stringify(listData), {encoding: "utf8", flag: "w+"});

const themeGalleryData = await getThemeGalleryData();
await fs.writeFile("Extensions/dist/page/themes.json", JSON.stringify(themeGalleryData), {
encoding: "utf8",
flag: "w+",
});

const extensionList = await getExtensionList();
for (const id of extensionList) {
await fs.writeFile(`Extensions/dist/${id}.json`, JSON.stringify(await getExtensionData(id)), {
encoding: "utf8",
flag: "w+",
});
}
}

build();

/** Each extension has the following fields:
* {string} script - Contents of the extension file
* {string} id - File name without extension
* {string} icon - Contents of the `id`.icon.js file
* {string} css - Contents of the `id`.css file
* {string} title - Value of the TITLE field in `script`
* {string} version - Value of the VERSION field in `script`
* {string} description - Value of the DESCRIPTION field in `script`
* {string} details - Value of the DETAILS field in `script`
* {string} developer - Value of the DEVELOPER field in `script`
* {boolean} frame - Value of the FRAME field in `script`
* {boolean} [beta] - Value of the optional BETA field in `script`
* {boolean} [slow] - Value of the optional SLOW field in `script`
*/
const extensionAttributes = [
{name: "title", default: null, required: true},
{name: "description", default: null, required: true},
{name: "developer", default: null, required: true},
{name: "version", default: null, required: true},
{name: "details", default: null, required: false},
{name: "frame", default: "false", required: false},
{name: "beta", default: "false", required: false},
{name: "slow", default: "false", required: false},
];

async function getExtensionData(id) {
const contents = await loadFile(`/Extensions/${id}.js`);

const extension = {
id,
script: contents,
file: "found",
server: "up",
errors: false,
};

try {
const icon = await loadFile(`/Extensions/${id}.icon.js`);
extension.icon = icon;
} catch (e) {}
try {
const css = await loadFile(`/Extensions/${id}.css`);
extension.css = css;
} catch (e) {}

extensionAttributes.forEach(({name: key, default: defaultValue}) => {
const match = contents.match(new RegExp("/\\*\\s*" + key.toUpperCase() + "\\s*(.+?)\\s*\\*\\*?/"));
if (match) {
extension[key] = match[1];
} else if (defaultValue) {
extension[key] = defaultValue;
}
});

return extension;
}

async function getGalleryData() {
const extensionList = await getExtensionList();
const extensionData = await Promise.all(extensionList.map(getExtensionData));

// eslint-disable-next-line id-length
extensionData.sort((a, b) => a.title.localeCompare(b.title));

return {
server: "up",
extensions: extensionData.map(({id, title, version, description, icon, details}) => ({
name: id,
title,
version,
description,
icon,
details,
})),
};
}

async function getExtensionList() {
return (await fs.readdir("Extensions"))
.filter(fileName => fileName.endsWith(".js") && !fileName.endsWith(".icon.js"))
.map(fileName => path.parse(fileName).name);
}

async function getListData() {
const extensionList = await getExtensionList();
const extensionData = await Promise.all(extensionList.map(getExtensionData));

return {
server: "up",
extensions: extensionData.map(({id, version}) => ({name: id, version})),
};
}

/** Each theme has the following fields:
* {string} file - Contents of the theme file
* {string} name - Value of the NAME field in `file`
* {string} version - Value of the VERSION field in `file`
* {string} description - Value of the DESCRIPTION field in `file`
* {string} developer - Value of the DEVELOPER field in `file`
*/
const themeAttributes = [
{name: "name", default: null, required: true},
{name: "description", default: null, required: true},
{name: "developer", default: null, required: true},
{name: "version", default: null, required: true},
];
async function getThemeData(id) {
const contents = await loadFile(`/Themes/${id}/${id}.css`);

const theme = {
file: `${id}.css`,
contents,
};

themeAttributes.forEach(({name: key, default: defaultValue}) => {
const match = contents.match(new RegExp("/\\*\\s*" + key.toUpperCase() + "\\s*(.+?)\\s*\\*\\*?/"));
if (match) {
theme[key] = match[1];
} else if (defaultValue) {
theme[key] = defaultValue;
}
});

return theme;
}

async function getThemeGalleryData() {
const themeList = await fs.readdir("Themes");
const themeData = await Promise.all(themeList.map(getThemeData));

return {
server: "up",
themes: themeData.map(({file, name, version, description, developer, contents}) => ({
file,
name,
version,
description,
developer,
contents,
})),
};
}
Loading