refactor: extract seed provision scripts to dedicated directory
Move bootstrap, default, and inform scripts from inline template strings in lib/init.ts to standalone files under seed/, loaded via an esbuild text-loader plugin. Add type-checking (provisions.d.ts, tsconfig.json) and ESLint config for the seed scripts. Set tsconfig module to Preserve to support import attributes.
This commit is contained in:
1 parent
f39e2ce7cf
commit
d445aa03cf
10 files changed
+236
-56
No files matched your search
+14
-1
@@ -92,6 +92,19 @@ const assetsPlugin = {
|
|||||||
},
|
},
|
||||||
} as esbuild.Plugin;
|
} as esbuild.Plugin;
|
||||||
|
|
||||||
|
const seedPlugin = {
|
||||||
|
name: "seed",
|
||||||
|
setup(build) {
|
||||||
|
build.onLoad({ filter: /\/seed\// }, (args) => {
|
||||||
|
if (args.with?.["type"] !== "text") return undefined;
|
||||||
|
let contents = fs.readFileSync(args.path, "utf8");
|
||||||
|
// Strip TypeScript directives that are only needed for type-checking
|
||||||
|
contents = contents.replace(/^\s*\/\/\s*@ts-.*\n/gm, "");
|
||||||
|
return { contents, loader: "text" };
|
||||||
|
});
|
||||||
|
},
|
||||||
|
} as esbuild.Plugin;
|
||||||
|
|
||||||
const packageDotJsonPlugin = {
|
const packageDotJsonPlugin = {
|
||||||
name: "packageDotJson",
|
name: "packageDotJson",
|
||||||
setup(build) {
|
setup(build) {
|
||||||
@@ -295,7 +308,7 @@ async function generateBackendJs(): Promise<void> {
|
|||||||
banner: { js: "#!/usr/bin/env node" },
|
banner: { js: "#!/usr/bin/env node" },
|
||||||
entryPoints: services.map((s) => `bin/${s}.ts`),
|
entryPoints: services.map((s) => `bin/${s}.ts`),
|
||||||
outdir: path.join(OUTPUT_DIR, "bin"),
|
outdir: path.join(OUTPUT_DIR, "bin"),
|
||||||
plugins: [packageDotJsonPlugin, assetsPlugin],
|
plugins: [packageDotJsonPlugin, assetsPlugin, seedPlugin],
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const bin of services) {
|
for (const bin of services) {
|
||||||
|
|||||||
+2
-2
@@ -5,7 +5,7 @@ const execPromise = promisify(exec);
|
|||||||
|
|
||||||
async function runEslint(): Promise<string> {
|
async function runEslint(): Promise<string> {
|
||||||
const CMD =
|
const CMD =
|
||||||
"eslint 'bin/*.ts' 'lib/**/*.ts' 'ui/**/*.ts' 'test/**/*.ts' 'build/**/*.ts'";
|
"eslint 'bin/*.ts' 'lib/**/*.ts' 'ui/**/*.ts' 'test/**/*.ts' 'build/**/*.ts' 'seed/*'";
|
||||||
const env = {
|
const env = {
|
||||||
...(process.stdout.isTTY && { FORCE_COLOR: "1" }),
|
...(process.stdout.isTTY && { FORCE_COLOR: "1" }),
|
||||||
...process.env,
|
...process.env,
|
||||||
@@ -21,7 +21,7 @@ async function runEslint(): Promise<string> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function runTsc(): Promise<string> {
|
async function runTsc(): Promise<string> {
|
||||||
const CMD = "tsc --noEmit";
|
const CMD = "tsc --noEmit && tsc -p seed/tsconfig.json";
|
||||||
const env = {
|
const env = {
|
||||||
...(process.stdout.isTTY && { FORCE_COLOR: "1" }),
|
...(process.stdout.isTTY && { FORCE_COLOR: "1" }),
|
||||||
...process.env,
|
...process.env,
|
||||||
|
|||||||
@@ -51,4 +51,27 @@ export default defineConfig(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
files: ["seed/*.js"],
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: {
|
||||||
|
project: null,
|
||||||
|
},
|
||||||
|
globals: {
|
||||||
|
declare: "readonly",
|
||||||
|
clear: "readonly",
|
||||||
|
commit: "readonly",
|
||||||
|
ext: "readonly",
|
||||||
|
log: "readonly",
|
||||||
|
args: "readonly",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
"@typescript-eslint/explicit-function-return-type": "off",
|
||||||
|
"@typescript-eslint/no-floating-promises": "off",
|
||||||
|
"@typescript-eslint/no-misused-promises": "off",
|
||||||
|
"@typescript-eslint/no-shadow": "off",
|
||||||
|
"no-shadow": ["error", { allow: ["err", "total"] }],
|
||||||
|
},
|
||||||
|
},
|
||||||
);
|
);
|
||||||
+3
-52
@@ -9,6 +9,9 @@ import {
|
|||||||
putUser,
|
putUser,
|
||||||
} from "./ui/db.ts";
|
} from "./ui/db.ts";
|
||||||
import { del } from "./cache.ts";
|
import { del } from "./cache.ts";
|
||||||
|
import BOOTSTRAP_SCRIPT from "../seed/bootstrap.js" with { type: "text" };
|
||||||
|
import DEFAULT_SCRIPT from "../seed/default.js" with { type: "text" };
|
||||||
|
import INFORM_SCRIPT from "../seed/inform.js" with { type: "text" };
|
||||||
|
|
||||||
interface Status {
|
interface Status {
|
||||||
users: boolean;
|
users: boolean;
|
||||||
@@ -19,58 +22,6 @@ interface Status {
|
|||||||
overview: boolean;
|
overview: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BOOTSTRAP_SCRIPT = `
|
|
||||||
const now = Date.now();
|
|
||||||
|
|
||||||
// Clear cached data model to force a refresh
|
|
||||||
clear("Device", now);
|
|
||||||
clear("InternetGatewayDevice", now);
|
|
||||||
`.trim();
|
|
||||||
|
|
||||||
const DEFAULT_SCRIPT = `
|
|
||||||
const hourly = Date.now(3600000);
|
|
||||||
|
|
||||||
// Refresh basic parameters hourly
|
|
||||||
declare("InternetGatewayDevice.DeviceInfo.HardwareVersion", {path: hourly, value: hourly});
|
|
||||||
declare("InternetGatewayDevice.DeviceInfo.SoftwareVersion", {path: hourly, value: hourly});
|
|
||||||
declare("InternetGatewayDevice.WANDevice.*.WANConnectionDevice.*.WANIPConnection.*.MACAddress", {path: hourly, value: hourly});
|
|
||||||
declare("InternetGatewayDevice.WANDevice.*.WANConnectionDevice.*.WANIPConnection.*.ExternalIPAddress", {path: hourly, value: hourly});
|
|
||||||
declare("InternetGatewayDevice.LANDevice.*.WLANConfiguration.*.SSID", {path: hourly, value: hourly});
|
|
||||||
// Don't refresh password field periodically because CPEs always report blank passowrds for security reasons
|
|
||||||
declare("InternetGatewayDevice.LANDevice.*.WLANConfiguration.*.KeyPassphrase", {path: hourly, value: 1});
|
|
||||||
declare("InternetGatewayDevice.LANDevice.*.Hosts.Host.*.HostName", {path: hourly, value: hourly});
|
|
||||||
declare("InternetGatewayDevice.LANDevice.*.Hosts.Host.*.IPAddress", {path: hourly, value: hourly});
|
|
||||||
declare("InternetGatewayDevice.LANDevice.*.Hosts.Host.*.MACAddress", {path: hourly, value: hourly});
|
|
||||||
`.trim();
|
|
||||||
|
|
||||||
const INFORM_SCRIPT = `
|
|
||||||
// Device ID as user name
|
|
||||||
const username = declare("DeviceID.ID", {value: 1}).value[0]
|
|
||||||
|
|
||||||
// Password will be fixed for a given device because Math.random() is seeded with device ID by default.
|
|
||||||
const password = Math.trunc(Math.random() * Number.MAX_SAFE_INTEGER).toString(36);
|
|
||||||
|
|
||||||
const informInterval = 300;
|
|
||||||
|
|
||||||
// Refresh values daily
|
|
||||||
const daily = Date.now(86400000);
|
|
||||||
|
|
||||||
// Unique inform offset per device for better load distribution
|
|
||||||
const informTime = daily % 86400000;
|
|
||||||
|
|
||||||
declare("InternetGatewayDevice.ManagementServer.ConnectionRequestUsername", {value: daily}, {value: username});
|
|
||||||
declare("InternetGatewayDevice.ManagementServer.ConnectionRequestPassword", {value: daily}, {value: password});
|
|
||||||
declare("InternetGatewayDevice.ManagementServer.PeriodicInformEnable", {value: daily}, {value: true});
|
|
||||||
declare("InternetGatewayDevice.ManagementServer.PeriodicInformInterval", {value: daily}, {value: informInterval});
|
|
||||||
declare("InternetGatewayDevice.ManagementServer.PeriodicInformTime", {value: daily}, {value: informTime});
|
|
||||||
|
|
||||||
declare("Device.ManagementServer.ConnectionRequestUsername", {value: daily}, {value: username});
|
|
||||||
declare("Device.ManagementServer.ConnectionRequestPassword", {value: daily}, {value: password});
|
|
||||||
declare("Device.ManagementServer.PeriodicInformEnable", {value: daily}, {value: true});
|
|
||||||
declare("Device.ManagementServer.PeriodicInformInterval", {value: daily}, {value: informInterval});
|
|
||||||
declare("Device.ManagementServer.PeriodicInformTime", {value: daily}, {value: informTime});
|
|
||||||
`.trim();
|
|
||||||
|
|
||||||
export async function getStatus(): Promise<Status> {
|
export async function getStatus(): Promise<Status> {
|
||||||
const [configSnapshot, presetCount] = await Promise.all([
|
const [configSnapshot, presetCount] = await Promise.all([
|
||||||
getRevision(),
|
getRevision(),
|
||||||
|
|||||||
Vendored
+5
@@ -0,0 +1,5 @@
|
|||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
// Clear cached data model to force a refresh
|
||||||
|
clear("Device", now);
|
||||||
|
clear("InternetGatewayDevice", now);
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
const hourly = Date.now(3600000);
|
||||||
|
|
||||||
|
// Refresh basic parameters hourly
|
||||||
|
declare("InternetGatewayDevice.DeviceInfo.HardwareVersion", {
|
||||||
|
path: hourly,
|
||||||
|
value: hourly,
|
||||||
|
});
|
||||||
|
declare("InternetGatewayDevice.DeviceInfo.SoftwareVersion", {
|
||||||
|
path: hourly,
|
||||||
|
value: hourly,
|
||||||
|
});
|
||||||
|
declare(
|
||||||
|
"InternetGatewayDevice.WANDevice.*.WANConnectionDevice.*.WANIPConnection.*.MACAddress",
|
||||||
|
{ path: hourly, value: hourly },
|
||||||
|
);
|
||||||
|
declare(
|
||||||
|
"InternetGatewayDevice.WANDevice.*.WANConnectionDevice.*.WANIPConnection.*.ExternalIPAddress",
|
||||||
|
{ path: hourly, value: hourly },
|
||||||
|
);
|
||||||
|
declare("InternetGatewayDevice.LANDevice.*.WLANConfiguration.*.SSID", {
|
||||||
|
path: hourly,
|
||||||
|
value: hourly,
|
||||||
|
});
|
||||||
|
// Don't refresh password field periodically because CPEs always report blank passowrds for security reasons
|
||||||
|
declare("InternetGatewayDevice.LANDevice.*.WLANConfiguration.*.KeyPassphrase", {
|
||||||
|
path: hourly,
|
||||||
|
value: 1,
|
||||||
|
});
|
||||||
|
declare("InternetGatewayDevice.LANDevice.*.Hosts.Host.*.HostName", {
|
||||||
|
path: hourly,
|
||||||
|
value: hourly,
|
||||||
|
});
|
||||||
|
declare("InternetGatewayDevice.LANDevice.*.Hosts.Host.*.IPAddress", {
|
||||||
|
path: hourly,
|
||||||
|
value: hourly,
|
||||||
|
});
|
||||||
|
declare("InternetGatewayDevice.LANDevice.*.Hosts.Host.*.MACAddress", {
|
||||||
|
path: hourly,
|
||||||
|
value: hourly,
|
||||||
|
});
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
// Device ID as user name
|
||||||
|
const username = declare("DeviceID.ID", { value: 1 }).value[0];
|
||||||
|
|
||||||
|
// Password will be fixed for a given device because Math.random() is seeded with device ID by default.
|
||||||
|
const password = Math.trunc(Math.random() * Number.MAX_SAFE_INTEGER).toString(
|
||||||
|
36,
|
||||||
|
);
|
||||||
|
|
||||||
|
const informInterval = 300;
|
||||||
|
|
||||||
|
// Refresh values daily
|
||||||
|
const daily = Date.now(86400000);
|
||||||
|
|
||||||
|
// Unique inform offset per device for better load distribution
|
||||||
|
const informTime = daily % 86400000;
|
||||||
|
|
||||||
|
declare(
|
||||||
|
"InternetGatewayDevice.ManagementServer.ConnectionRequestUsername",
|
||||||
|
{ value: daily },
|
||||||
|
{ value: username },
|
||||||
|
);
|
||||||
|
declare(
|
||||||
|
"InternetGatewayDevice.ManagementServer.ConnectionRequestPassword",
|
||||||
|
{ value: daily },
|
||||||
|
{ value: password },
|
||||||
|
);
|
||||||
|
declare(
|
||||||
|
"InternetGatewayDevice.ManagementServer.PeriodicInformEnable",
|
||||||
|
{ value: daily },
|
||||||
|
{ value: true },
|
||||||
|
);
|
||||||
|
declare(
|
||||||
|
"InternetGatewayDevice.ManagementServer.PeriodicInformInterval",
|
||||||
|
{ value: daily },
|
||||||
|
{ value: informInterval },
|
||||||
|
);
|
||||||
|
declare(
|
||||||
|
"InternetGatewayDevice.ManagementServer.PeriodicInformTime",
|
||||||
|
{ value: daily },
|
||||||
|
{ value: informTime },
|
||||||
|
);
|
||||||
|
|
||||||
|
declare(
|
||||||
|
"Device.ManagementServer.ConnectionRequestUsername",
|
||||||
|
{ value: daily },
|
||||||
|
{ value: username },
|
||||||
|
);
|
||||||
|
declare(
|
||||||
|
"Device.ManagementServer.ConnectionRequestPassword",
|
||||||
|
{ value: daily },
|
||||||
|
{ value: password },
|
||||||
|
);
|
||||||
|
declare(
|
||||||
|
"Device.ManagementServer.PeriodicInformEnable",
|
||||||
|
{ value: daily },
|
||||||
|
{ value: true },
|
||||||
|
);
|
||||||
|
declare(
|
||||||
|
"Device.ManagementServer.PeriodicInformInterval",
|
||||||
|
{ value: daily },
|
||||||
|
{ value: informInterval },
|
||||||
|
);
|
||||||
|
declare(
|
||||||
|
"Device.ManagementServer.PeriodicInformTime",
|
||||||
|
{ value: daily },
|
||||||
|
{ value: informTime },
|
||||||
|
);
|
||||||
Vendored
+71
@@ -0,0 +1,71 @@
|
|||||||
|
interface Timestamps {
|
||||||
|
path?: number;
|
||||||
|
object?: number;
|
||||||
|
writable?: number;
|
||||||
|
value?: number;
|
||||||
|
notification?: number;
|
||||||
|
accessList?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Values {
|
||||||
|
path?: number | [number, number];
|
||||||
|
object?: boolean;
|
||||||
|
writable?: boolean;
|
||||||
|
value?: string | number | boolean | [string | number | boolean, string?];
|
||||||
|
notification?: number;
|
||||||
|
accessList?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ParameterWrapper extends Iterable<ParameterWrapper> {
|
||||||
|
readonly path: string | undefined;
|
||||||
|
readonly size: number | undefined;
|
||||||
|
readonly object: 0 | 1 | undefined;
|
||||||
|
readonly writable: 0 | 1 | undefined;
|
||||||
|
readonly value: [string | number | boolean, string] | undefined;
|
||||||
|
readonly notification: number | undefined;
|
||||||
|
readonly accessList: string[] | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare function declare(
|
||||||
|
path: string,
|
||||||
|
timestamps?: Timestamps | null,
|
||||||
|
values?: Values | null,
|
||||||
|
): ParameterWrapper;
|
||||||
|
|
||||||
|
declare function clear(
|
||||||
|
path: string,
|
||||||
|
timestamp: number,
|
||||||
|
attributes?: Timestamps,
|
||||||
|
): void;
|
||||||
|
|
||||||
|
declare function commit(): void;
|
||||||
|
|
||||||
|
declare function ext(...args: unknown[]): unknown;
|
||||||
|
|
||||||
|
declare function log(msg: string, meta?: Record<string, unknown>): void;
|
||||||
|
|
||||||
|
declare const args: unknown[];
|
||||||
|
|
||||||
|
interface DateConstructor {
|
||||||
|
new (): Date;
|
||||||
|
new (
|
||||||
|
year: number,
|
||||||
|
monthIndex?: number,
|
||||||
|
day?: number,
|
||||||
|
hours?: number,
|
||||||
|
minutes?: number,
|
||||||
|
seconds?: number,
|
||||||
|
milliseconds?: number,
|
||||||
|
): Date;
|
||||||
|
now(intervalOrCron?: number | string, variance?: number): number;
|
||||||
|
parse(dateString: string): number;
|
||||||
|
UTC(
|
||||||
|
year: number,
|
||||||
|
monthIndex?: number,
|
||||||
|
day?: number,
|
||||||
|
hours?: number,
|
||||||
|
minutes?: number,
|
||||||
|
seconds?: number,
|
||||||
|
milliseconds?: number,
|
||||||
|
): number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowJs": true,
|
||||||
|
"checkJs": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"target": "ES2022",
|
||||||
|
"types": []
|
||||||
|
},
|
||||||
|
"include": ["./*.js", "./*.d.ts"]
|
||||||
|
}
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"lib": ["ES2022", "dom"],
|
"lib": ["ES2022", "dom"],
|
||||||
"module": "ES2022",
|
"module": "Preserve",
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"target": "ES2022",
|
"target": "ES2022",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
|
|||||||
Reference in new issue
Block a user