1 Commits

Author SHA1 Message Date
1fea301ecf move to gitea
Some checks failed
App Image CI / Build app image (push) Failing after -18s
2025-04-29 20:56:09 -05:00
10 changed files with 455 additions and 454 deletions

View File

@@ -57,7 +57,7 @@ jobs:
uses: docker/build-push-action@v6 uses: docker/build-push-action@v6
with: with:
target: deploy target: deploy
push: ${{ startsWith(github.ref, 'refs/tags/v') && 'true' || 'false' }} push: startsWith(github.ref, 'refs/tags/v')
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64 platforms: linux/amd64

View File

@@ -1,27 +0,0 @@
name: NPM Audit Check
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
schedule:
- cron: '0 16 * * 5'
jobs:
npm_audit:
name: Check NPM audit
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
fail-fast: true
permissions:
contents: read
steps:
- name: Checkout repository
uses: https://github.com/actions/checkout@v4
- name: NPM Audit
run: npm audit

767
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "archery", "name": "archery",
"version": "0.1.8", "version": "0.1.6",
"description": "Build Arch packages through a web interface", "description": "Build Arch packages through a web interface",
"keywords": [ "keywords": [
"docker", "docker",
@@ -20,20 +20,21 @@
"type": "module", "type": "module",
"main": "index.ts", "main": "index.ts",
"dependencies": { "dependencies": {
"body-parser": "^2.2.0", "body-parser": "^1.20.3",
"ejs": "3.1.10", "ejs": "3.1.10",
"express": "^5.1.0", "express": "^4.21.2",
"express-ws": "^5.0.2", "express-ws": "^5.0.2",
"pg": "^8.16.3", "pg": "^8.15.6",
"pg-hstore": "^2.3.4", "pg-hstore": "^2.3.4",
"sequelize": "^6.37.7", "sequelize": "^6.37.7"
"sqids": "0.3.0"
}, },
"devDependencies": { "devDependencies": {
"@types/express": "^5.0.3", "@types/csso": "5.0.4",
"@types/express": "^5.0.0",
"@types/express-ws": "3.0.5", "@types/express-ws": "3.0.5",
"@types/node": "^24.5.2", "@types/node": "^22.10.5",
"forking-build-shit": "1.0.4" "@types/uglify-js": "3.17.5",
"forking-build-shit": "0.0.2"
}, },
"peerDependencies": { "peerDependencies": {
"typescript": "5.7.3" "typescript": "5.7.3"

View File

@@ -2,7 +2,6 @@ document.addEventListener('DOMContentLoaded', function () {
const logContainer = this.getElementById('logs'); const logContainer = this.getElementById('logs');
const followLogsBtn = this.getElementById('followCheckmark'); const followLogsBtn = this.getElementById('followCheckmark');
const buildStatusTxt = this.getElementById('buildStatus'); const buildStatusTxt = this.getElementById('buildStatus');
let started = logContainer.childElementCount > 0;
/** /**
* Get the correct path to establish a ws connection * Get the correct path to establish a ws connection
@@ -33,10 +32,6 @@ document.addEventListener('DOMContentLoaded', function () {
} }
} }
function handleScrollToggleClick() {
window.scrollTo(0, followLogsBtn.checked ? document.body.scrollHeight : 0);
}
/** /**
* Split string by newline char * Split string by newline char
* @param {string} str * @param {string} str
@@ -58,16 +53,9 @@ document.addEventListener('DOMContentLoaded', function () {
ws.onmessage = function (message) { ws.onmessage = function (message) {
const buildEvent = JSON.parse(message.data); const buildEvent = JSON.parse(message.data);
if (!started) {
started = true;
buildStatusTxt.replaceChild(document.createTextNode('running'), buildStatusTxt.firstChild);
}
if (buildEvent.type === 'finish') { if (buildEvent.type === 'finish') {
ws.close(); ws.close();
buildStatusTxt.replaceChild(document.createTextNode(buildEvent.message), buildStatusTxt.firstChild); buildStatusTxt.replaceChild(document.createTextNode(buildEvent.message), buildStatusTxt.firstChild);
appendLines([`finished: ${buildEvent.message}`]);
const cancelBtn = document.getElementById('cancelRow');
cancelBtn.parentElement.removeChild(cancelBtn);
} }
else { else {
appendLines(splitLines(buildEvent.message), buildEvent.type === 'err'); appendLines(splitLines(buildEvent.message), buildEvent.type === 'err');
@@ -78,5 +66,5 @@ document.addEventListener('DOMContentLoaded', function () {
connect(); connect();
followLogsBtn.checked = false; followLogsBtn.checked = false;
followLogsBtn.addEventListener('change', handleScrollToggleClick); followLogsBtn.addEventListener('change', scrollToBottom);
}); });

View File

@@ -24,7 +24,6 @@ interface Build {
endTime?: Date; endTime?: Date;
status: Status; status: Status;
pid?: number; pid?: number;
sqid?: string;
} }
interface LogChunk { interface LogChunk {
@@ -52,8 +51,7 @@ class DB {
this.sequelize = new Sequelize(config.db || 'archery', config.user || 'archery', process.env.PASSWORD || config.password || '', { this.sequelize = new Sequelize(config.db || 'archery', config.user || 'archery', process.env.PASSWORD || config.password || '', {
host: config.host || 'localhost', host: config.host || 'localhost',
port: config.port || 5432, port: config.port || 5432,
dialect: 'postgres', dialect: 'postgres'
logging: false
}); });
this.build = this.sequelize.define('builds', { this.build = this.sequelize.define('builds', {
id: { id: {

View File

@@ -4,7 +4,6 @@ import type { Express } from "express";
import express from 'express'; import express from 'express';
import expressWs from "express-ws"; import expressWs from "express-ws";
import bodyParser from "body-parser"; import bodyParser from "body-parser";
import Sqids from 'sqids';
import type { DB, LogChunk } from "./DB.ts"; import type { DB, LogChunk } from "./DB.ts";
import type { BuildController, BuildEvent } from "./BuildController.ts"; import type { BuildController, BuildEvent } from "./BuildController.ts";
@@ -42,10 +41,6 @@ class Web {
private port: number; private port: number;
constructor(options: WebConfig = {}) { constructor(options: WebConfig = {}) {
const sqids = new Sqids({
minLength: 6,
alphabet: 'abcdefghijkmnprstuvwxyz'
});
const app: Express = express(); const app: Express = express();
const wsApp = this.app = expressWs(app).app; const wsApp = this.app = expressWs(app).app;
this.port = notStupidParseInt(process.env.PORT) || options['port'] as number || 8080; this.port = notStupidParseInt(process.env.PORT) || options['port'] as number || 8080;
@@ -71,9 +66,6 @@ class Web {
app.get('/', async (req, res) => { app.get('/', async (req, res) => {
try { try {
const builds = 'q' in req.query ? await this.db.searchBuilds(req.query.q as string) : await this.db.getBuildsBy(req.query); const builds = 'q' in req.query ? await this.db.searchBuilds(req.query.q as string) : await this.db.getBuildsBy(req.query);
builds.forEach(b => {
b.sqid = sqids.encode([b.id]);
});
res.render('index', { res.render('index', {
page: { page: {
title: 'Archery', title: 'Archery',
@@ -90,19 +82,17 @@ class Web {
} }
}); });
app.get('/build{/}', async(req, res) => { app.get('/build/?', (_, res) => {
const query = ('id' in req.query && typeof req.query.id === 'string' && await this.db.getBuild(sqids.decode(req.query.id)?.[0])) || req.query;
res.render('build-new', { res.render('build-new', {
page: { page: {
title: 'Archery', title: 'Archery',
titlesuffix: 'New Build', titlesuffix: 'New Build',
description: 'Kick off a build', description: 'Kick off a build'
}, }
query
}); });
}); });
app.post('/build{/}', async (req, res) => { app.post('/build/?', async (req, res) => {
const buildId = await this.db.createBuild( const buildId = await this.db.createBuild(
req.body.repo, req.body.repo,
req.body.commit || null, req.body.commit || null,
@@ -110,23 +100,22 @@ class Web {
req.body.distro || 'arch', req.body.distro || 'arch',
req.body.dependencies || 'stable' req.body.dependencies || 'stable'
); );
res.redirect(`/build/${sqids.encode([buildId])}`); res.redirect(`/build/${buildId}`);
this.buildController.triggerBuild(); this.buildController.triggerBuild();
}); });
app.get('/build/:id{/}', async (req, res) => { app.get('/build/:num/?', async (req, res) => {
const build = await this.db.getBuild(sqids.decode(req.params.id)?.[0]); const build = await this.db.getBuild(parseInt(req.params.num));
if (!build) { if (!build) {
res.sendStatus(404); res.sendStatus(404);
return; return;
} }
build.sqid = sqids.encode([build.id]);
const log = splitLines(await this.db.getLog(build.id)); const log = splitLines(await this.db.getLog(build.id));
res.render('build', { res.render('build', {
page: { page: {
title: 'Archery', title: 'Archery',
titlesuffix: `Build #${build.id}`, titlesuffix: `Build #${req.params.num}`,
description: `Building ${build.repo} on ${build.distro}` description: `Building ${build.repo} on ${build.distro}`
}, },
build, build,
@@ -135,8 +124,8 @@ class Web {
}); });
}); });
app.get('/build/:id/cancel', async (req, res) => { app.get('/build/:num/cancel', async (req, res) => {
const build = await this.db.getBuild(sqids.decode(req.params.id)?.[0]); const build = await this.db.getBuild(parseInt(req.params.num));
if (!build) { if (!build) {
res.sendStatus(404); res.sendStatus(404);
return; return;
@@ -147,11 +136,11 @@ class Web {
catch (ex) { catch (ex) {
console.error(ex); console.error(ex);
} }
res.redirect(`/build/${req.params.id}`); res.redirect(`/build/${build.id}`);
}); });
app.get('/build/:id/logs{/}', async (req, res) => { app.get('/build/:num/logs/?', async (req, res) => {
const build = await this.db.getBuild(sqids.decode(req.params.id)?.[0]); const build = await this.db.getBuild(parseInt(req.params.num));
if (!build) { if (!build) {
res.sendStatus(404); res.sendStatus(404);
return; return;
@@ -160,8 +149,8 @@ class Web {
res.set('Content-Type', 'text/plain').send(log); res.set('Content-Type', 'text/plain').send(log);
}); });
app.get('/build/:id/patch{/}', async (req, res) => { app.get('/build/:num/patch/?', async (req, res) => {
const build = await this.db.getBuild(sqids.decode(req.params.id)?.[0]); const build = await this.db.getBuild(parseInt(req.params.num));
if (!build || !build.patch) { if (!build || !build.patch) {
res.sendStatus(404); res.sendStatus(404);
return; return;
@@ -173,10 +162,10 @@ class Web {
res.send('Healthy'); res.send('Healthy');
}); });
wsApp.ws('/build/:id/ws', (ws, req) => { wsApp.ws('/build/:num/ws', (ws, req) => {
console.log('WS Opened'); console.log('WS Opened');
const eventListener = (be: BuildEvent) => { const eventListener = (be: BuildEvent) => {
if (be.id === sqids.decode(req.params.id)?.[0]) { if (be.id === notStupidParseInt(req.params.num)) {
ws.send(JSON.stringify(be)); ws.send(JSON.stringify(be));
} }
}; };

View File

@@ -10,19 +10,19 @@
<div class="content"> <div class="content">
<h1>Start a build</h1> <h1>Start a build</h1>
<form action="/build" method="post" class="grid-2col"> <form action="/build" method="post" class="grid-2col">
<label for="repoTxt">Repo</label> <input type="text" name="repo" id="repoTxt" required <% if (typeof query.repo === 'string') { %>value="<%= query.repo %>"<% } %> /> <label for="repoTxt">Repo</label> <input type="text" name="repo" id="repoTxt" required />
<label for="commitTxt">Commit</label> <input type="text" name="commit" id="commitTxt" <% if (typeof query.commit === 'string') { %>value="<%= query.commit %>"<% } %> /> <label for="commitTxt">Commit</label> <input type="text" name="commit" id="commitTxt" />
<label for="patchTxt">Patch</label> <textarea name="patch" id="patchTxt"><% if (typeof query.patch === 'string') { %><%= query.patch %><% } %></textarea> <label for="patchTxt">Patch</label> <textarea name="patch" id="patchTxt"></textarea>
<label for="distroTxt">Distro</label> <label for="distroTxt">Distro</label>
<select name="distro" id="distroTxt" required> <select name="distro" id="distroTxt" required>
<option value="arch" <% if (query.distro === 'arch') { %>selected<% } %>>Arch</option> <option value="arch">Arch</option>
<option value="artix" <% if (query.distro === 'artix') { %>selected<% } %>>Artix</option> <option value="artix">Artix</option>
</select> </select>
<label for="dependsTxt">Dependencies</label> <label for="dependsTxt">Dependencies</label>
<select name="dependencies" id="dependsTxt" required> <select name="dependencies" id="dependsTxt" required>
<option value="stable" <% if (query.dependencies === 'stable') { %>selected<% } %>>Stable</option> <option value="stable">Stable</option>
<option value="testing" <% if (query.dependencies === 'testing') { %>selected<% } %>>Testing</option> <option value="testing">Testing</option>
<option value="staging" <% if (query.dependencies === 'staging') { %>selected<% } %>>Staging</option> <option value="staging">Staging</option>
</select> </select>
<div class="span-2"><button type="submit">Build</button></div> <div class="span-2"><button type="submit">Build</button></div>
</form> </form>

View File

@@ -14,21 +14,18 @@
<div class="grid-2col"> <div class="grid-2col">
<label>Repo</label> <span><%= build.repo %></span> <label>Repo</label> <span><%= build.repo %></span>
<label>Commit</label> <span><% if (build.commit) { %><%= build.commit %><% } else { %>latest<% } %></span> <label>Commit</label> <span><% if (build.commit) { %><%= build.commit %><% } else { %>latest<% } %></span>
<label>Patch</label> <span><% if (build.patch) { %><a href="/build/<%= build.sqid %>/patch">patch file</a><% } else { %>none<% } %></span> <label>Patch</label> <span><% if (build.patch) { %><a href="/build/<%= build.id %>/patch">patch file</a><% } else { %>none<% } %></span>
<label>Distro</label> <span><%= build.distro %></span> <label>Distro</label> <span><%= build.distro %></span>
<label>Dependencies</label> <span><%= build.dependencies %></span> <label>Dependencies</label> <span><%= build.dependencies %></span>
<label>Start time</label> <span class="to-local-time"><%= build.startTime %></span> <label>Start time</label> <span class="to-local-time"><%= build.startTime %></span>
</div> </div>
</div> </div>
<div>
<a href="/build?id=<%= build.sqid %>" class="button">Clone</a>
</div>
<% if (!ended) { %> <% if (!ended) { %>
<div id="cancelRow"> <div>
<a href="/build/<%= build.sqid %>/cancel" class="button">Cancel build</a> <a href="/build/<%= build.id %>/cancel" class="button">Cancel build</a>
</div> </div>
<% } %> <% } %>
<p><a href="/build/<%= build.sqid %>/logs">Full logs</a></p> <p><a href="/build/<%= build.id %>/logs">Full logs</a></p>
<pre class="overflow-x"><div class="logs" id="logs"><% (log || []).forEach(line => { %><p><%= line %></p><% }) %></div></pre> <pre class="overflow-x"><div class="logs" id="logs"><% (log || []).forEach(line => { %><p><%= line %></p><% }) %></div></pre>
<% if (!ended) { %> <% if (!ended) { %>
@@ -40,7 +37,7 @@
<%- include("footer", locals) %> <%- include("footer", locals) %>
<script src="/assets/js/timezone.js?v1" nonce="<%= cspNonce %>"></script> <script src="/assets/js/timezone.js?v1" nonce="<%= cspNonce %>"></script>
<% if (!ended) { %> <% if (!ended) { %>
<script src="/assets/js/build.js?v3" nonce="<%= cspNonce %>"></script> <script src="/assets/js/build.js?v2" nonce="<%= cspNonce %>"></script>
<% } %> <% } %>
</body> </body>
</html> </html>

View File

@@ -20,7 +20,7 @@
</tr> </tr>
<% builds.forEach(build => { %> <% builds.forEach(build => { %>
<tr> <tr>
<td><a href="/build/<%= build.sqid %>"><%= build.repo %></a></td> <td><a href="/build/<%= build.id %>"><%= build.repo %></a></td>
<td><%= build.distro %></td> <td><%= build.distro %></td>
<td class="to-local-time"><%= build.startTime %></td> <td class="to-local-time"><%= build.startTime %></td>
<td><%= timeElapsed(build.startTime, build.endTime) %></td> <td><%= timeElapsed(build.startTime, build.endTime) %></td>