[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/levelcodeai/levelcode/develop/tools/update-server/server.js [Back]  [Original]

/*---------------------------------------------------------------------------------------------
 *  LevelCode  reference update-feed server
 *
 *  A tiny, dependency-free implementation of the Code-OSS update-feed contract that BOTH the
 *  built-in updater and the notify-only levelcode-updater extension speak:
 *
 *     GET /api/update/{target}/{quality}/{commit}
 *         204  if {commit} is already the latest build
 *         200  { version, productVersion, url, sha256hash, timestamp }  otherwise
 *
 *  The "latest build" per (target, quality) is read from a releases file (RELEASES_FILE, default
 *  ./releases.json). Use it to dev/test the updater, to self-host an update feed, and as the spec
 *  LevelCode Cloud implements for managed releases.
 *
 *  Run:  PORT=9696 RELEASES_FILE=./releases.json node server.js
 *--------------------------------------------------------------------------------------------*/
// @ts-check
'use strict';

const http = require('http');
const fs = require('fs');
const path = require('path');

const PORT = parseInt(process.env.PORT || '9696', 10);
const LOG = require.main === module;

function releasesFile() { return process.env.RELEASES_FILE || path.join(__dirname, 'releases.json'); }

let warnedExample = false;
/** { "": { "": { commit, productVersion, url, sha256hash, timestamp } } }.
 *  Falls back to the committed releases.example.json (with a warning) so the notify flow works
 *  out of the box in dev  real self-hosts create their own releases.json. */
function readReleases() {
	try { return JSON.parse(fs.readFileSync(releasesFile(), 'utf8')); } catch { /* fall through to the example */ }
	try {
		const data = JSON.parse(fs.readFileSync(path.join(__dirname, 'releases.example.json'), 'utf8'));
		if (LOG && !warnedExample) { warnedExample = true; console.log('[update] no releases.json found  using releases.example.json (edit it, or create releases.json).'); }
		return data;
	} catch { return {}; }
}

/** The latest release for a target+quality, or null. */
function latestRelease(target, quality) {
	const rel = (readReleases()[target] || {})[quality];
	return rel && rel.commit ? rel : null;
}

function send(res, status, body, headers) {
	res.writeHead(status, Object.assign({ 'Cache-Control': 'no-store' }, headers || {}));
	res.end(body);
}

const server = http.createServer((req, res) => {
	if (LOG) {
		res.on('finish', () => {
			const u = req.url || '';
			if (u !== '/' && u !== '/health') { // log everything (incl. misdirected /v1  404)
				console.log('[update] ' + (req.method || 'GET') + ' ' + u + '  ' + res.statusCode);
			}
		});
	}
	const url = (req.url || '').split('?')[0];
	const method = req.method || 'GET';

	if (url === '/' || url === '/health') {
		return send(res, 200, 'LevelCode reference update-feed server\n', { 'Content-Type': 'text/plain' });
	}

	const m = /^\/api\/update\/([^/]+)\/([^/]+)\/([^/]+)$/.exec(url);
	if (m && method === 'GET') {
		const [, target, quality, commit] = m;
		const rel = latestRelease(target, quality);
		if (!rel || rel.commit === commit) { return send(res, 204, ''); } // up to date (or nothing published)
		return send(res, 200, JSON.stringify({
			version: rel.commit,
			productVersion: rel.productVersion,
			url: rel.url,
			sha256hash: rel.sha256hash,
			timestamp: rel.timestamp || 0,
			releaseNotesUrl: rel.releaseNotesUrl
		}), { 'Content-Type': 'application/json' });
	}

	return send(res, 404, 'Not found: ' + method + ' ' + url);
});

if (require.main === module) {
	server.listen(PORT, () => {
		// eslint-disable-next-line no-console
		console.log('LevelCode reference update-feed server on http://localhost:' + PORT + ' (releases: ' + releasesFile() + ')');
		console.log('Watch this terminal  every updater check prints as an [update] line.');
	});
}

module.exports = { server, latestRelease };

Web Proxy Viewer  |  New URL  |  Original Page