mirror of
https://github.com/xoseperez/espurna.git
synced 2026-02-20 01:31:34 +01:00
277 lines
5.7 KiB
JavaScript
277 lines
5.7 KiB
JavaScript
import * as fs from 'node:fs/promises';
|
|
import * as path from 'node:path';
|
|
import * as url from 'node:url';
|
|
|
|
const ROOT_DIR = path.dirname(url.fileURLToPath(import.meta.url));
|
|
const PRESETS_DIR = path.join(ROOT_DIR, 'preset');
|
|
|
|
/**
|
|
* module names used internally, usually as element class-name
|
|
* @typedef {{[k: string]: boolean}} Modules
|
|
*/
|
|
|
|
/* used for the locally served .html, that is already merged but not yet inlined */
|
|
export const MODULE_DEV = 'dev';
|
|
|
|
/**
|
|
* declare some modules as optional, only to be included for specific builds
|
|
* @constant
|
|
* @type Modules
|
|
*/
|
|
export const DEFAULT_MODULES = {
|
|
'api': true,
|
|
'cmd': true,
|
|
'curtain': false,
|
|
'dbg': true,
|
|
'dcz': true,
|
|
'garland': false,
|
|
'ha': true,
|
|
'idb': true,
|
|
'led': true,
|
|
'light': false,
|
|
'lightfox': false,
|
|
'mqtt': true,
|
|
'nofuss': true,
|
|
'ntp': true,
|
|
'ota': true,
|
|
'relay': true,
|
|
'rfb': false,
|
|
'rfm69': false,
|
|
'rpn': true,
|
|
'sch': true,
|
|
'sns': false,
|
|
'telnet': true,
|
|
'thermostat': false,
|
|
'tspk': true,
|
|
[MODULE_DEV]: false,
|
|
};
|
|
|
|
/**
|
|
* special type of build when multiple single-module files are used
|
|
* currently, only possible way to combine both (besides modifying the targets manually)
|
|
* @constant
|
|
* @type Modules
|
|
*/
|
|
export const MODULES_ALL = Object.fromEntries(
|
|
Object.entries(DEFAULT_MODULES).map(
|
|
([key, _]) => {
|
|
if (MODULE_DEV === key) {
|
|
return [key, false];
|
|
}
|
|
|
|
return [key, true];
|
|
}));
|
|
/**
|
|
* always assume dev build is all plus dev
|
|
* @constant
|
|
* @type Modules
|
|
*/
|
|
export const MODULES_DEV =
|
|
Object.assign({}, MODULES_ALL, {[MODULE_DEV]: true});
|
|
|
|
/**
|
|
* name mapping for output filename, when specific module is used
|
|
* @typedef {{[k: string]: string}} NamedBuild
|
|
*/
|
|
|
|
/**
|
|
* generic output, usually this includes a single module
|
|
* @constant
|
|
* @type {NamedBuild}
|
|
*/
|
|
export const NAMED_BUILD = {
|
|
'curtain': 'curtain',
|
|
'garland': 'garland',
|
|
'light': 'light',
|
|
'lightfox': 'lightfox',
|
|
'rfbridge': 'rfb',
|
|
'rfm69': 'rfm69',
|
|
'sensor': 'sns',
|
|
'thermostat': 'thermostat',
|
|
};
|
|
|
|
/**
|
|
* @param {string} name
|
|
* @returns {Modules}
|
|
*/
|
|
export function makeModules(name) {
|
|
switch (name) {
|
|
case 'all':
|
|
return MODULES_ALL;
|
|
|
|
case MODULE_DEV:
|
|
return MODULES_DEV;
|
|
|
|
case 'small':
|
|
return DEFAULT_MODULES;
|
|
}
|
|
|
|
if (NAMED_BUILD[name] === undefined) {
|
|
throw new Error(`NAMED_BUILD['${name}'] is missing`);
|
|
}
|
|
|
|
const out = Object.assign({}, DEFAULT_MODULES);
|
|
out[NAMED_BUILD[name]] = true;
|
|
|
|
return out;
|
|
}
|
|
|
|
/**
|
|
* @param {string} name
|
|
* @returns {string}
|
|
*/
|
|
function nameToConstant(name) {
|
|
return `MODULE_${name.toUpperCase()}`;
|
|
}
|
|
|
|
/**
|
|
* ref. html/src/index.mjs
|
|
* @param {Modules} modules
|
|
*/
|
|
function asConstants(modules) {
|
|
const entries = Object.entries(modules);
|
|
const exports =
|
|
entries.map(([key, value]) => {
|
|
return [nameToConstant(key), value.toString()];
|
|
})
|
|
.map(([name, value]) => {
|
|
return `export const ${name} = ${value};`;
|
|
});
|
|
|
|
return exports.join('\n');
|
|
}
|
|
|
|
/**
|
|
* ref. vite.config.mjs
|
|
* @param {Modules} modules
|
|
*/
|
|
function asDictionary(modules) {
|
|
const entries = Object.keys(modules)
|
|
.map((name) => [name, nameToConstant(name)])
|
|
.map(([name, value]) => ` '${name}': ${value},`);
|
|
|
|
return `export const MODULES = {\n${entries.join('\n')}\n};`;
|
|
}
|
|
|
|
export const MODULE_PRESETS = new Set([
|
|
'all',
|
|
'small',
|
|
'curtain',
|
|
'garland',
|
|
'light',
|
|
'lightfox',
|
|
'rfbridge',
|
|
'rfm69',
|
|
'sensor',
|
|
'thermostat',
|
|
MODULE_DEV,
|
|
]);
|
|
|
|
/** @param {any} e */
|
|
function maybeRethrow(e) {
|
|
/** @type {Error?} */
|
|
let rethrow = null;
|
|
|
|
if (e instanceof Error) {
|
|
rethrow = e;
|
|
} else {
|
|
rethrow = new Error(e?.toString() ?? 'unknown error');
|
|
}
|
|
|
|
return rethrow;
|
|
}
|
|
|
|
/**
|
|
* @param {string} target
|
|
* @param {string} data
|
|
*/
|
|
async function maybe_write(target, data) {
|
|
let existing = '';
|
|
|
|
/** @type {fs.FileHandle?} */
|
|
let readable = null;
|
|
|
|
try {
|
|
readable = await fs.open(target, 'r');
|
|
existing = (await readable.read()).toString();
|
|
} catch (_) {
|
|
} finally {
|
|
if (readable) {
|
|
await readable.close();
|
|
}
|
|
}
|
|
|
|
if (existing === data) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await fs.mkdir(path.dirname(target));
|
|
} catch (_) {
|
|
}
|
|
|
|
/** @type {Error?} */
|
|
let rethrow = null;
|
|
|
|
/** @type {fs.FileHandle?} */
|
|
let writable = null;
|
|
|
|
try {
|
|
writable = await fs.open(target, 'w');
|
|
await writable.write(data);
|
|
} catch (e) {
|
|
rethrow = maybeRethrow(e);
|
|
} finally {
|
|
if (writable) {
|
|
await writable.close();
|
|
}
|
|
}
|
|
|
|
if (rethrow) {
|
|
throw rethrow;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {string} name
|
|
* @param {string} [outdir]
|
|
*/
|
|
async function builder(name, outdir = PRESETS_DIR) {
|
|
if (!outdir) {
|
|
outdir = PRESETS_DIR;
|
|
}
|
|
|
|
const target_dir = path.join(outdir, name);
|
|
|
|
const modules = makeModules(name);
|
|
const targets = [
|
|
[
|
|
path.join(target_dir, 'constants.mjs'),
|
|
[asConstants(modules), asDictionary(modules)].join('\n'),
|
|
],
|
|
];
|
|
|
|
let out = `Written: ${target_dir}`;
|
|
|
|
try {
|
|
for (const [target, data] of targets) {
|
|
await maybe_write(target, data);
|
|
}
|
|
} catch (e) {
|
|
out = `Error ${target_dir}: ${(e ?? 'unknown error').toString()}`;
|
|
for (const [target] of targets) {
|
|
await fs.rm(target, {force: true});
|
|
}
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
export async function build() {
|
|
return Promise.all(Array.from(MODULE_PRESETS).map((x) => builder(x)));
|
|
}
|
|
|
|
//if (process.argv[1] === import.meta.filename) {
|
|
// await build();
|
|
//}
|