Skip to content

Commit 4ff29b6

Browse files
Modernize build tooling, local development, and dependencies (#2194)
1 parent cf4e6fd commit 4ff29b6

File tree

14 files changed

+3795
-19704
lines changed

14 files changed

+3795
-19704
lines changed

.eslintrc.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@
4343
after: true
4444
no-useless-escape: off
4545
no-prototype-builtins: off
46+
overrides:
47+
- files: "*.mjs"
48+
parserOptions:
49+
sourceType: module
4650
globals:
4751
BigInt: readonly
4852
_: readonly

.github/workflows/ci.yml

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,17 @@ jobs:
1313
name: Test
1414
runs-on: ubuntu-latest
1515
steps:
16-
- name: Checkout
17-
uses: actions/checkout@v2
16+
- name: Checkout
17+
uses: actions/checkout@v4
1818

19-
- name: Setup Node.js environment
20-
uses: actions/setup-node@v2.1.2
21-
with:
22-
node-version: 14
19+
- name: Setup Node.js environment
20+
uses: actions/setup-node@v4
21+
with:
22+
node-version: lts/*
23+
cache: npm
2324

24-
- name: Install dependencies
25-
run: npm ci
25+
- name: Install dependencies
26+
run: npm ci
2627

27-
- name: Run tests
28-
run: npm test
28+
- name: Run tests
29+
run: npm test

.github/workflows/gh-pages.yml

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ jobs:
2323
- name: Setup Node.js environment
2424
uses: actions/setup-node@v4
2525
with:
26-
node-version: 14
26+
node-version: lts/*
2727
cache: npm
2828

2929
- name: Install dependencies
@@ -32,11 +32,8 @@ jobs:
3232
- name: Run tests
3333
run: npm test
3434

35-
- name: Install gulp-cli
36-
run: npm install gulp-cli
37-
3835
- name: Build distribution
39-
run: gulp build
36+
run: npm run build-extensions
4037

4138
- name: Push changes to gh-pages
4239
run: >

dev/build.mjs

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
import fs from "node:fs/promises";
2+
import path from "node:path";
3+
4+
const loadFile = filePath => fs.readFile(`.${filePath}`, "utf8");
5+
6+
async function build() {
7+
const galleryData = await getGalleryData();
8+
await fs.writeFile("Extensions/dist/page/gallery.json", JSON.stringify(galleryData), {
9+
encoding: "utf8",
10+
flag: "w+",
11+
});
12+
13+
const listData = await getListData();
14+
await fs.writeFile("Extensions/dist/page/list.json", JSON.stringify(listData), {encoding: "utf8", flag: "w+"});
15+
16+
const themeGalleryData = await getThemeGalleryData();
17+
await fs.writeFile("Extensions/dist/page/themes.json", JSON.stringify(themeGalleryData), {
18+
encoding: "utf8",
19+
flag: "w+",
20+
});
21+
22+
const extensionList = await getExtensionList();
23+
for (const id of extensionList) {
24+
await fs.writeFile(`Extensions/dist/${id}.json`, JSON.stringify(await getExtensionData(id)), {
25+
encoding: "utf8",
26+
flag: "w+",
27+
});
28+
}
29+
}
30+
31+
build();
32+
33+
/** Each extension has the following fields:
34+
* {string} script - Contents of the extension file
35+
* {string} id - File name without extension
36+
* {string} icon - Contents of the `id`.icon.js file
37+
* {string} css - Contents of the `id`.css file
38+
* {string} title - Value of the TITLE field in `script`
39+
* {string} version - Value of the VERSION field in `script`
40+
* {string} description - Value of the DESCRIPTION field in `script`
41+
* {string} details - Value of the DETAILS field in `script`
42+
* {string} developer - Value of the DEVELOPER field in `script`
43+
* {boolean} frame - Value of the FRAME field in `script`
44+
* {boolean} [beta] - Value of the optional BETA field in `script`
45+
* {boolean} [slow] - Value of the optional SLOW field in `script`
46+
*/
47+
const extensionAttributes = [
48+
{name: "title", default: null, required: true},
49+
{name: "description", default: null, required: true},
50+
{name: "developer", default: null, required: true},
51+
{name: "version", default: null, required: true},
52+
{name: "details", default: null, required: false},
53+
{name: "frame", default: "false", required: false},
54+
{name: "beta", default: "false", required: false},
55+
{name: "slow", default: "false", required: false},
56+
];
57+
58+
async function getExtensionData(id) {
59+
const contents = await loadFile(`/Extensions/${id}.js`);
60+
61+
const extension = {
62+
id,
63+
script: contents,
64+
file: "found",
65+
server: "up",
66+
errors: false,
67+
};
68+
69+
try {
70+
const icon = await loadFile(`/Extensions/${id}.icon.js`);
71+
extension.icon = icon;
72+
} catch (e) {}
73+
try {
74+
const css = await loadFile(`/Extensions/${id}.css`);
75+
extension.css = css;
76+
} catch (e) {}
77+
78+
extensionAttributes.forEach(({name: key, default: defaultValue}) => {
79+
const match = contents.match(new RegExp("/\\*\\s*" + key.toUpperCase() + "\\s*(.+?)\\s*\\*\\*?/"));
80+
if (match) {
81+
extension[key] = match[1];
82+
} else if (defaultValue) {
83+
extension[key] = defaultValue;
84+
}
85+
});
86+
87+
return extension;
88+
}
89+
90+
async function getGalleryData() {
91+
const extensionList = await getExtensionList();
92+
const extensionData = await Promise.all(extensionList.map(getExtensionData));
93+
94+
// eslint-disable-next-line id-length
95+
extensionData.sort((a, b) => a.title.localeCompare(b.title));
96+
97+
return {
98+
server: "up",
99+
extensions: extensionData.map(({id, title, version, description, icon, details}) => ({
100+
name: id,
101+
title,
102+
version,
103+
description,
104+
icon,
105+
details,
106+
})),
107+
};
108+
}
109+
110+
async function getExtensionList() {
111+
return (await fs.readdir("Extensions"))
112+
.filter(fileName => fileName.endsWith(".js") && !fileName.endsWith(".icon.js"))
113+
.map(fileName => path.parse(fileName).name);
114+
}
115+
116+
async function getListData() {
117+
const extensionList = await getExtensionList();
118+
const extensionData = await Promise.all(extensionList.map(getExtensionData));
119+
120+
return {
121+
server: "up",
122+
extensions: extensionData.map(({id, version}) => ({name: id, version})),
123+
};
124+
}
125+
126+
/** Each theme has the following fields:
127+
* {string} file - Contents of the theme file
128+
* {string} name - Value of the NAME field in `file`
129+
* {string} version - Value of the VERSION field in `file`
130+
* {string} description - Value of the DESCRIPTION field in `file`
131+
* {string} developer - Value of the DEVELOPER field in `file`
132+
*/
133+
const themeAttributes = [
134+
{name: "name", default: null, required: true},
135+
{name: "description", default: null, required: true},
136+
{name: "developer", default: null, required: true},
137+
{name: "version", default: null, required: true},
138+
];
139+
async function getThemeData(id) {
140+
const contents = await loadFile(`/Themes/${id}/${id}.css`);
141+
142+
const theme = {
143+
file: `${id}.css`,
144+
contents,
145+
};
146+
147+
themeAttributes.forEach(({name: key, default: defaultValue}) => {
148+
const match = contents.match(new RegExp("/\\*\\s*" + key.toUpperCase() + "\\s*(.+?)\\s*\\*\\*?/"));
149+
if (match) {
150+
theme[key] = match[1];
151+
} else if (defaultValue) {
152+
theme[key] = defaultValue;
153+
}
154+
});
155+
156+
return theme;
157+
}
158+
159+
async function getThemeGalleryData() {
160+
const themeList = await fs.readdir("Themes");
161+
const themeData = await Promise.all(themeList.map(getThemeData));
162+
163+
return {
164+
server: "up",
165+
themes: themeData.map(({file, name, version, description, developer, contents}) => ({
166+
file,
167+
name,
168+
version,
169+
description,
170+
developer,
171+
contents,
172+
})),
173+
};
174+
}

0 commit comments

Comments
 (0)