Termine gebündelt ausgeben #1
218
data/reminder_check.testdata.json
Normal file
218
data/reminder_check.testdata.json
Normal file
|
@ -0,0 +1,218 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "reminder_check.dueness-1",
|
||||||
|
"input": {
|
||||||
|
"reminder": {
|
||||||
|
"frequency": "daily",
|
||||||
|
"offset": 16,
|
||||||
|
"from": 24,
|
||||||
|
"to": 48
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
],
|
||||||
|
"datetime": {
|
||||||
|
"timezone_shift": 0,
|
||||||
|
"date": {
|
||||||
|
"year": 2025,
|
||||||
|
"month": 6,
|
||||||
|
"day": 27
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"hour": 15,
|
||||||
|
"minute": 30,
|
||||||
|
"second": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"interval": 1
|
||||||
|
},
|
||||||
|
"output": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "reminder_check.dueness-2",
|
||||||
|
"input": {
|
||||||
|
"reminder": {
|
||||||
|
"frequency": "daily",
|
||||||
|
"offset": 16,
|
||||||
|
"from": 24,
|
||||||
|
"to": 48
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
],
|
||||||
|
"datetime": {
|
||||||
|
"timezone_shift": 0,
|
||||||
|
"date": {
|
||||||
|
"year": 2025,
|
||||||
|
"month": 6,
|
||||||
|
"day": 27
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"hour": 16,
|
||||||
|
"minute": 30,
|
||||||
|
"second": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"interval": 1
|
||||||
|
},
|
||||||
|
"output": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "reminder_check.dueness-3",
|
||||||
|
"input": {
|
||||||
|
"reminder": {
|
||||||
|
"frequency": "daily",
|
||||||
|
"offset": 16,
|
||||||
|
"from": 24,
|
||||||
|
"to": 48
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
],
|
||||||
|
"datetime": {
|
||||||
|
"timezone_shift": 0,
|
||||||
|
"date": {
|
||||||
|
"year": 2025,
|
||||||
|
"month": 6,
|
||||||
|
"day": 27
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"hour": 17,
|
||||||
|
"minute": 30,
|
||||||
|
"second": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"interval": 1
|
||||||
|
},
|
||||||
|
"output": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "reminder_check.events-1",
|
||||||
|
"input": {
|
||||||
|
"reminder": {
|
||||||
|
"frequency": "daily",
|
||||||
|
"offset": 16,
|
||||||
|
"from": 24,
|
||||||
|
"to": 48
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"title": "e1",
|
||||||
|
"begin": {
|
||||||
|
"timezone_shift": 0,
|
||||||
|
"date": {
|
||||||
|
"year": 2025,
|
||||||
|
"month": 6,
|
||||||
|
"day": 27
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"hour": 12,
|
||||||
|
"minute": 0,
|
||||||
|
"second": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"datetime": {
|
||||||
|
"timezone_shift": 0,
|
||||||
|
"date": {
|
||||||
|
"year": 2025,
|
||||||
|
"month": 6,
|
||||||
|
"day": 27
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"hour": 16,
|
||||||
|
"minute": 30,
|
||||||
|
"second": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"interval": 1
|
||||||
|
},
|
||||||
|
"output": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "reminder_check.events-2",
|
||||||
|
"input": {
|
||||||
|
"reminder": {
|
||||||
|
"frequency": "daily",
|
||||||
|
"offset": 16,
|
||||||
|
"from": 24,
|
||||||
|
"to": 48
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"title": "e1",
|
||||||
|
"begin": {
|
||||||
|
"timezone_shift": 0,
|
||||||
|
"date": {
|
||||||
|
"year": 2025,
|
||||||
|
"month": 6,
|
||||||
|
"day": 28
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"hour": 12,
|
||||||
|
"minute": 0,
|
||||||
|
"second": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"datetime": {
|
||||||
|
"timezone_shift": 0,
|
||||||
|
"date": {
|
||||||
|
"year": 2025,
|
||||||
|
"month": 6,
|
||||||
|
"day": 27
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"hour": 16,
|
||||||
|
"minute": 30,
|
||||||
|
"second": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"interval": 1
|
||||||
|
},
|
||||||
|
"output": ["e1"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "reminder_check.events-3",
|
||||||
|
"input": {
|
||||||
|
"reminder": {
|
||||||
|
"frequency": "daily",
|
||||||
|
"offset": 16,
|
||||||
|
"from": 24,
|
||||||
|
"to": 48
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"title": "e1",
|
||||||
|
"begin": {
|
||||||
|
"timezone_shift": 0,
|
||||||
|
"date": {
|
||||||
|
"year": 2025,
|
||||||
|
"month": 6,
|
||||||
|
"day": 29
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"hour": 12,
|
||||||
|
"minute": 0,
|
||||||
|
"second": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"datetime": {
|
||||||
|
"timezone_shift": 0,
|
||||||
|
"date": {
|
||||||
|
"year": 2025,
|
||||||
|
"month": 6,
|
||||||
|
"day": 27
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"hour": 16,
|
||||||
|
"minute": 30,
|
||||||
|
"second": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"interval": 1
|
||||||
|
},
|
||||||
|
"output": []
|
||||||
|
}
|
||||||
|
]
|
|
@ -12,6 +12,8 @@
|
||||||
"email",
|
"email",
|
||||||
"telegram",
|
"telegram",
|
||||||
"url",
|
"url",
|
||||||
|
"json",
|
||||||
|
"file",
|
||||||
"conf",
|
"conf",
|
||||||
"log",
|
"log",
|
||||||
"args"
|
"args"
|
||||||
|
@ -20,6 +22,7 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"sources": [
|
"sources": [
|
||||||
|
"helpers/test.ts",
|
||||||
"types.ts",
|
"types.ts",
|
||||||
"sources/ical_feed.ts",
|
"sources/ical_feed.ts",
|
||||||
"sources/_functions.ts",
|
"sources/_functions.ts",
|
||||||
|
@ -27,6 +30,8 @@
|
||||||
"targets/email.ts",
|
"targets/email.ts",
|
||||||
"targets/_functions.ts",
|
"targets/_functions.ts",
|
||||||
"conf.ts",
|
"conf.ts",
|
||||||
|
"logic.ts",
|
||||||
|
"test.ts",
|
||||||
"main.ts"
|
"main.ts"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
351
source/conf.ts
351
source/conf.ts
|
@ -24,6 +24,24 @@ along with »munin«. If not, see <http://www.gnu.org/licenses/>.
|
||||||
namespace _munin.conf
|
namespace _munin.conf
|
||||||
{
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
type type_reminder_raw = {
|
||||||
|
frequency : (
|
||||||
|
"hourly"
|
||||||
|
|
|
||||||
|
"daily"
|
||||||
|
|
|
||||||
|
"weekly"
|
||||||
|
|
|
||||||
|
"monthly"
|
||||||
|
);
|
||||||
|
offset : int;
|
||||||
|
from : int;
|
||||||
|
to : int;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
type type_conf_v1 = {
|
type type_conf_v1 = {
|
||||||
|
@ -236,7 +254,72 @@ namespace _munin.conf
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
export type type_conf = type_conf_v4;
|
type type_conf_v5 = {
|
||||||
|
sources : Array<
|
||||||
|
(
|
||||||
|
{
|
||||||
|
kind : "ical_feed";
|
||||||
|
data : {
|
||||||
|
url : string;
|
||||||
|
filtration : {
|
||||||
|
category_whitelist : (null | Array<string>);
|
||||||
|
category_blacklist : (null | Array<string>);
|
||||||
|
title_whitelist : (null | Array<string>);
|
||||||
|
title_blacklist : (null | Array<string>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
>;
|
||||||
|
targets : Array<
|
||||||
|
(
|
||||||
|
{
|
||||||
|
kind : "email";
|
||||||
|
data : {
|
||||||
|
smtp_host : string;
|
||||||
|
smtp_port : int;
|
||||||
|
smtp_username : string;
|
||||||
|
smtp_password : string;
|
||||||
|
sender : string;
|
||||||
|
receivers : Array<string>;
|
||||||
|
hide_tags : boolean;
|
||||||
|
/**
|
||||||
|
* in hours
|
||||||
|
*/
|
||||||
|
reminders : Array<type_reminder_raw>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
{
|
||||||
|
kind : "telegram_bot";
|
||||||
|
data : {
|
||||||
|
bot_token : string;
|
||||||
|
chat_id : int;
|
||||||
|
hide_tags : boolean;
|
||||||
|
/**
|
||||||
|
* in hours
|
||||||
|
*/
|
||||||
|
reminders : Array<type_reminder_raw>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
>;
|
||||||
|
settings : {
|
||||||
|
interval : float;
|
||||||
|
};
|
||||||
|
labels : {
|
||||||
|
head : string;
|
||||||
|
title : string;
|
||||||
|
time : string;
|
||||||
|
location : string;
|
||||||
|
events : string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
export type type_conf = type_conf_v5;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -396,6 +479,65 @@ namespace _munin.conf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
function convert_from_v4(
|
||||||
|
conf_v4 : type_conf_v4
|
||||||
|
) : type_conf_v5
|
||||||
|
{
|
||||||
|
const map_reminder = hours => ({
|
||||||
|
"frequency": "hourly",
|
||||||
|
"offset": 0,
|
||||||
|
"from": hours,
|
||||||
|
"to": (hours + 1),
|
||||||
|
} as type_reminder_raw);
|
||||||
|
return {
|
||||||
|
"sources": conf_v4.sources,
|
||||||
|
"targets": conf_v4.targets.map(
|
||||||
|
target => {
|
||||||
|
switch (target.kind) {
|
||||||
|
case "email": {
|
||||||
|
return {
|
||||||
|
"kind": "email",
|
||||||
|
"data": {
|
||||||
|
"smtp_host": target.data.smtp_host,
|
||||||
|
"smtp_port": target.data.smtp_port,
|
||||||
|
"smtp_username": target.data.smtp_username,
|
||||||
|
"smtp_password": target.data.smtp_password,
|
||||||
|
"sender": target.data.sender,
|
||||||
|
"receivers": target.data.receivers,
|
||||||
|
"hide_tags": target.data.hide_tags,
|
||||||
|
"reminders": target.data.reminders.map(map_reminder),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "telegram_bot": {
|
||||||
|
return {
|
||||||
|
"kind": "telegram_bot",
|
||||||
|
"data": {
|
||||||
|
"bot_token": target.data.bot_token,
|
||||||
|
"chat_id": target.data.chat_id,
|
||||||
|
"hide_tags": target.data.hide_tags,
|
||||||
|
"reminders": target.data.reminders.map(map_reminder),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
// return target;
|
||||||
|
throw (new Error("unhandled target kind: " + String(target)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"settings": conf_v4.settings,
|
||||||
|
"labels": Object.assign({"events": "Termine"}, conf_v4.labels),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
function schema_source_kalender_digital(
|
function schema_source_kalender_digital(
|
||||||
|
@ -546,6 +688,136 @@ namespace _munin.conf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
function schema_sources(
|
||||||
|
version : string
|
||||||
|
) : lib_plankton.conf.type_schema
|
||||||
|
{
|
||||||
|
switch (version) {
|
||||||
|
case "1": {
|
||||||
|
return {
|
||||||
|
"nullable": false,
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"nullable": false,
|
||||||
|
"anyOf": [
|
||||||
|
schema_source_kalender_digital(version),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
case "2": {
|
||||||
|
return {
|
||||||
|
"nullable": false,
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"nullable": false,
|
||||||
|
"anyOf": [
|
||||||
|
schema_source_ical_feed(version),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
function schema_reminder(
|
||||||
|
version : string
|
||||||
|
) : lib_plankton.conf.type_schema
|
||||||
|
{
|
||||||
|
switch (version)
|
||||||
|
{
|
||||||
|
case "1":
|
||||||
|
case "2":
|
||||||
|
case "3":
|
||||||
|
case "4":
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
"type": "integer",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "5":
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
"nullable": false,
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"frequency": {
|
||||||
|
"nullable": false,
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"hourly",
|
||||||
|
"daily",
|
||||||
|
"weekly",
|
||||||
|
"monthly",
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"offset": {
|
||||||
|
"nullable": false,
|
||||||
|
"type": "integer",
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"from": {
|
||||||
|
"nullable": false,
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"to": {
|
||||||
|
"nullable": false,
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"frequency",
|
||||||
|
"from",
|
||||||
|
"to",
|
||||||
|
]
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
function default_reminder(
|
||||||
|
version : string
|
||||||
|
) : any
|
||||||
|
{
|
||||||
|
switch (version)
|
||||||
|
{
|
||||||
|
case "1":
|
||||||
|
case "2":
|
||||||
|
case "3":
|
||||||
|
case "4":
|
||||||
|
{
|
||||||
|
return [24];
|
||||||
|
}
|
||||||
|
case "5":
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"frequency": "hourly",
|
||||||
|
"from": 24,
|
||||||
|
"to": 25
|
||||||
|
}
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
function schema_target_email(
|
function schema_target_email(
|
||||||
|
@ -602,11 +874,8 @@ namespace _munin.conf
|
||||||
"reminders": {
|
"reminders": {
|
||||||
"nullable": false,
|
"nullable": false,
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": schema_reminder(version),
|
||||||
"nullable": false,
|
"default": default_reminder(version),
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"default": [24.0],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
|
@ -628,44 +897,6 @@ namespace _munin.conf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
function schema_sources(
|
|
||||||
version : string
|
|
||||||
) : lib_plankton.conf.type_schema
|
|
||||||
{
|
|
||||||
switch (version) {
|
|
||||||
case "1": {
|
|
||||||
return {
|
|
||||||
"nullable": false,
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"nullable": false,
|
|
||||||
"anyOf": [
|
|
||||||
schema_source_kalender_digital(version),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
case "2": {
|
|
||||||
return {
|
|
||||||
"nullable": false,
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"nullable": false,
|
|
||||||
"anyOf": [
|
|
||||||
schema_source_ical_feed(version),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
function schema_target_telegram_bot(
|
function schema_target_telegram_bot(
|
||||||
|
@ -701,11 +932,8 @@ namespace _munin.conf
|
||||||
"reminders": {
|
"reminders": {
|
||||||
"nullable": false,
|
"nullable": false,
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": schema_reminder(version),
|
||||||
"nullable": false,
|
"default": default_reminder(version),
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"default": [24.0],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
|
@ -737,13 +965,18 @@ namespace _munin.conf
|
||||||
"nullable": false,
|
"nullable": false,
|
||||||
"anyOf": (() => {
|
"anyOf": (() => {
|
||||||
switch (version) {
|
switch (version) {
|
||||||
default: {
|
case "1":
|
||||||
|
case "2":
|
||||||
|
{
|
||||||
return [
|
return [
|
||||||
schema_target_telegram_bot(version),
|
schema_target_telegram_bot(version),
|
||||||
];
|
];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "3": {
|
case "4":
|
||||||
|
case "5":
|
||||||
|
default:
|
||||||
|
{
|
||||||
return [
|
return [
|
||||||
schema_target_email(version),
|
schema_target_email(version),
|
||||||
schema_target_telegram_bot(version),
|
schema_target_telegram_bot(version),
|
||||||
|
@ -812,6 +1045,11 @@ namespace _munin.conf
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "wo"
|
"default": "wo"
|
||||||
},
|
},
|
||||||
|
"events": {
|
||||||
|
"nullable": false,
|
||||||
|
"type": "string",
|
||||||
|
"default": "Termine"
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"required": [
|
"required": [
|
||||||
|
@ -824,7 +1062,7 @@ namespace _munin.conf
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
export function schema(
|
export function schema(
|
||||||
version : string = "4"
|
version : string = "5"
|
||||||
) : lib_plankton.conf.type_schema
|
) : lib_plankton.conf.type_schema
|
||||||
{
|
{
|
||||||
switch (version) {
|
switch (version) {
|
||||||
|
@ -852,7 +1090,9 @@ namespace _munin.conf
|
||||||
}
|
}
|
||||||
case "2":
|
case "2":
|
||||||
case "3":
|
case "3":
|
||||||
case "4": {
|
case "4":
|
||||||
|
case "5":
|
||||||
|
{
|
||||||
return {
|
return {
|
||||||
"nullable": false,
|
"nullable": false,
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
@ -887,7 +1127,8 @@ namespace _munin.conf
|
||||||
"1": {"target": "2", "function": convert_from_v1},
|
"1": {"target": "2", "function": convert_from_v1},
|
||||||
"2": {"target": "3", "function": convert_from_v2},
|
"2": {"target": "3", "function": convert_from_v2},
|
||||||
"3": {"target": "4", "function": convert_from_v3},
|
"3": {"target": "4", "function": convert_from_v3},
|
||||||
"4": null,
|
"4": {"target": "5", "function": convert_from_v4},
|
||||||
|
"5": null,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -925,7 +1166,7 @@ namespace _munin.conf
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
return (conf_raw.content as type_conf_v4);
|
return (conf_raw.content as type_conf_v5);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
124
source/helpers/test.ts
Normal file
124
source/helpers/test.ts
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
/*
|
||||||
|
This file is part of »munin«.
|
||||||
|
|
||||||
|
Copyright 2025 'Fenris Wolf' <fenris@folksprak.org>
|
||||||
|
|
||||||
|
»munin« is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
»munin« is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public License
|
||||||
|
along with »munin«. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
namespace _munin.helpers.test
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo outsource
|
||||||
|
*/
|
||||||
|
type type_testcase_raw<type_input, type_output> = {
|
||||||
|
active ?: boolean;
|
||||||
|
name ?: string;
|
||||||
|
input : type_input;
|
||||||
|
output : type_output;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo outsource
|
||||||
|
*/
|
||||||
|
export type type_testcase<type_input, type_output> = {
|
||||||
|
name : string;
|
||||||
|
input : type_input;
|
||||||
|
output : type_output;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo outsource
|
||||||
|
*/
|
||||||
|
export async function get_data<type_input, type_output>(
|
||||||
|
path : string,
|
||||||
|
{
|
||||||
|
"default_active": default_active = true,
|
||||||
|
} : {
|
||||||
|
default_active ?: boolean;
|
||||||
|
} = {
|
||||||
|
}
|
||||||
|
) : Promise<Array<type_testcase<type_input, type_output>>>
|
||||||
|
{
|
||||||
|
const content : string = await lib_plankton.file.read(path);
|
||||||
|
const testcases_raw : Array<type_testcase_raw<type_input, type_output>> = (
|
||||||
|
lib_plankton.json.decode(content) as Array<type_testcase_raw<type_input, type_output>>
|
||||||
|
);
|
||||||
|
const testcases : Array<type_testcase<type_input, type_output>> = (
|
||||||
|
testcases_raw
|
||||||
|
.filter(
|
||||||
|
testcase_raw => (testcase_raw.active ?? default_active),
|
||||||
|
)
|
||||||
|
.map(
|
||||||
|
(testcase_raw, index) => ({
|
||||||
|
"name": (
|
||||||
|
testcase_raw.name
|
||||||
|
??
|
||||||
|
lib_plankton.string.coin(
|
||||||
|
"{{path}} | #{{index}}",
|
||||||
|
{
|
||||||
|
"path": path,
|
||||||
|
"index": (index + 1).toFixed(0),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"input": testcase_raw.input,
|
||||||
|
"output": testcase_raw.output,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return testcases;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo outsource
|
||||||
|
*/
|
||||||
|
export function start(
|
||||||
|
name : string
|
||||||
|
) : void
|
||||||
|
{
|
||||||
|
lib_plankton.log._info(
|
||||||
|
"test.run",
|
||||||
|
{
|
||||||
|
"details": {
|
||||||
|
"name": name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo outsource
|
||||||
|
*/
|
||||||
|
export function fail(
|
||||||
|
name : string
|
||||||
|
) : void
|
||||||
|
{
|
||||||
|
lib_plankton.log._error(
|
||||||
|
"test.failed",
|
||||||
|
{
|
||||||
|
"details": {
|
||||||
|
"name": name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
265
source/logic.ts
Normal file
265
source/logic.ts
Normal file
|
@ -0,0 +1,265 @@
|
||||||
|
/*
|
||||||
|
This file is part of »munin«.
|
||||||
|
|
||||||
|
Copyright 2025 'Fenris Wolf' <fenris@folksprak.org>
|
||||||
|
|
||||||
|
»munin« is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
»munin« is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public License
|
||||||
|
along with »munin«. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
namespace _munin.logic
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
export function frequency_decode(
|
||||||
|
frequency_encoded : string
|
||||||
|
) : _munin.enum_frequency
|
||||||
|
{
|
||||||
|
switch (frequency_encoded)
|
||||||
|
{
|
||||||
|
case "hourly": return _munin.enum_frequency.hourly;
|
||||||
|
case "daily": return _munin.enum_frequency.daily;
|
||||||
|
case "weekly": return _munin.enum_frequency.weekly;
|
||||||
|
case "monthly": return _munin.enum_frequency.monthly;
|
||||||
|
default: {throw new Error("unhandled");}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
function frequency_anchor(
|
||||||
|
frequency : _munin.enum_frequency,
|
||||||
|
{
|
||||||
|
"pit": pit = lib_plankton.pit.now(),
|
||||||
|
} : {
|
||||||
|
pit ?: lib_plankton.pit.type_pit,
|
||||||
|
} = {
|
||||||
|
}
|
||||||
|
) : lib_plankton.pit.type_pit
|
||||||
|
{
|
||||||
|
switch (frequency)
|
||||||
|
{
|
||||||
|
case _munin.enum_frequency.hourly: return lib_plankton.pit.trunc_hour(pit);
|
||||||
|
case _munin.enum_frequency.daily: return lib_plankton.pit.trunc_day(pit);
|
||||||
|
case _munin.enum_frequency.weekly: return lib_plankton.pit.trunc_week(pit);
|
||||||
|
case _munin.enum_frequency.monthly: return lib_plankton.pit.trunc_month(pit);
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
throw (new Error("unhandled frequency: " + frequency));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
export function reminder_check(
|
||||||
|
reminder : type_reminder,
|
||||||
|
events_all : Array<_munin.type_event>,
|
||||||
|
{
|
||||||
|
"pit": pit = lib_plankton.pit.now(),
|
||||||
|
"interval": interval = 1,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
pit ?: lib_plankton.pit.type_pit;
|
||||||
|
interval ?: int;
|
||||||
|
}
|
||||||
|
= {
|
||||||
|
}
|
||||||
|
) : (null | Array<_munin.type_event>)
|
||||||
|
{
|
||||||
|
const anchor : lib_plankton.pit.type_pit = frequency_anchor(
|
||||||
|
reminder.frequency,
|
||||||
|
{"pit": pit}
|
||||||
|
);
|
||||||
|
const dueness_window_from : lib_plankton.pit.type_pit = lib_plankton.pit.shift_hour(
|
||||||
|
anchor,
|
||||||
|
(reminder.offset + 0)
|
||||||
|
);
|
||||||
|
const dueness_window_to : lib_plankton.pit.type_pit = lib_plankton.pit.shift_hour(
|
||||||
|
anchor,
|
||||||
|
(reminder.offset + interval)
|
||||||
|
);
|
||||||
|
const due : boolean = lib_plankton.pit.is_between(
|
||||||
|
pit,
|
||||||
|
dueness_window_from,
|
||||||
|
dueness_window_to
|
||||||
|
);
|
||||||
|
if (! due)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const events_window_from : lib_plankton.pit.type_pit = lib_plankton.pit.shift_hour(
|
||||||
|
anchor,
|
||||||
|
reminder.from
|
||||||
|
);
|
||||||
|
const events_window_to : lib_plankton.pit.type_pit = lib_plankton.pit.shift_hour(
|
||||||
|
anchor,
|
||||||
|
reminder.to
|
||||||
|
);
|
||||||
|
const events : Array<_munin.type_event> = (
|
||||||
|
events_all
|
||||||
|
.filter(
|
||||||
|
(event) => lib_plankton.pit.is_between(
|
||||||
|
lib_plankton.pit.from_datetime(event.begin),
|
||||||
|
events_window_from,
|
||||||
|
events_window_to
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
async function run_iteration(
|
||||||
|
conf : _munin.conf.type_conf,
|
||||||
|
sources : Array<_munin.type_source>,
|
||||||
|
targets : Array<_munin.type_target>,
|
||||||
|
{
|
||||||
|
"dry_run": dry_run = false,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
dry_run ?: boolean;
|
||||||
|
}
|
||||||
|
= {
|
||||||
|
}
|
||||||
|
) : Promise<void>
|
||||||
|
{
|
||||||
|
const now : lib_plankton.pit.type_pit = lib_plankton.pit.now();
|
||||||
|
const events_all : Array<_munin.type_event> = (
|
||||||
|
(await Promise.all(sources.map(source => source.fetch())))
|
||||||
|
.reduce((x, y) => x.concat(y), [])
|
||||||
|
);
|
||||||
|
for (const target of targets)
|
||||||
|
{
|
||||||
|
for (const reminder of target.reminders)
|
||||||
|
{
|
||||||
|
const events : Array<_munin.type_event> = reminder_check(
|
||||||
|
reminder,
|
||||||
|
events_all,
|
||||||
|
{
|
||||||
|
"pit": now,
|
||||||
|
"interval": conf.settings.interval,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (events === null)
|
||||||
|
{
|
||||||
|
lib_plankton.log._info(
|
||||||
|
"munin.run_iteration.reminder_not_due",
|
||||||
|
{
|
||||||
|
"details": {
|
||||||
|
"reminder": reminder,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (events.length <= 0)
|
||||||
|
{
|
||||||
|
lib_plankton.log._info(
|
||||||
|
"munin.run_iteration.no_matching_events",
|
||||||
|
{
|
||||||
|
"details": {
|
||||||
|
"reminder": reminder,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
lib_plankton.log._info(
|
||||||
|
"munin.run_iteration.remind",
|
||||||
|
{
|
||||||
|
"details": {
|
||||||
|
"reminder": reminder,
|
||||||
|
"events": events,
|
||||||
|
"target": target.show(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (dry_run)
|
||||||
|
{
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await target.send(conf.labels, events);
|
||||||
|
}
|
||||||
|
catch (error)
|
||||||
|
{
|
||||||
|
lib_plankton.log.error(
|
||||||
|
"munin.remind.error",
|
||||||
|
{
|
||||||
|
"details": {
|
||||||
|
"message": String(error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
export async function run(
|
||||||
|
conf : _munin.conf.type_conf,
|
||||||
|
{
|
||||||
|
"single_run": single_run = false,
|
||||||
|
"dry_run": dry_run = false,
|
||||||
|
} : {
|
||||||
|
single_run ?: boolean;
|
||||||
|
dry_run ?: boolean;
|
||||||
|
} = {
|
||||||
|
}
|
||||||
|
) : Promise<void>
|
||||||
|
{
|
||||||
|
const sources : Array<_munin.type_source> = conf.sources.map(
|
||||||
|
source_raw => _munin.sources.factory(source_raw)
|
||||||
|
);
|
||||||
|
const targets : Array<_munin.type_target> = conf.targets.map(
|
||||||
|
target_raw => _munin.targets.factory(target_raw)
|
||||||
|
);
|
||||||
|
lib_plankton.log._info(
|
||||||
|
"munin.run.start",
|
||||||
|
{
|
||||||
|
"details": {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (single_run) {
|
||||||
|
await run_iteration(conf, sources, targets, {"dry_run": dry_run});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
while (true) {
|
||||||
|
await run_iteration(conf, sources, targets, {"dry_run": dry_run});
|
||||||
|
await lib_plankton.call.sleep(conf.settings.interval * 60 * 60);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
257
source/main.ts
257
source/main.ts
|
@ -21,145 +21,6 @@ along with »munin«. If not, see <http://www.gnu.org/licenses/>.
|
||||||
namespace _munin
|
namespace _munin
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
async function run_iteration(
|
|
||||||
conf : _munin.conf.type_conf,
|
|
||||||
sources : Array<_munin.type_source>,
|
|
||||||
targets : Array<_munin.type_target>,
|
|
||||||
{
|
|
||||||
"dry_run": dry_run = false,
|
|
||||||
} : {
|
|
||||||
dry_run ?: boolean;
|
|
||||||
} = {
|
|
||||||
}
|
|
||||||
) : Promise<void>
|
|
||||||
{
|
|
||||||
const now : lib_plankton.pit.type_pit = lib_plankton.pit.now();
|
|
||||||
const events : Array<_munin.type_event> = (
|
|
||||||
(await Promise.all(sources.map(source => source.fetch())))
|
|
||||||
.reduce((x, y) => x.concat(y), [])
|
|
||||||
);
|
|
||||||
for (const target of targets) {
|
|
||||||
const window_from : lib_plankton.pit.type_pit = lib_plankton.pit.shift_hour(
|
|
||||||
now,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
const window_to : lib_plankton.pit.type_pit = lib_plankton.pit.shift_hour(
|
|
||||||
now,
|
|
||||||
conf.settings.interval
|
|
||||||
);
|
|
||||||
lib_plankton.log._info(
|
|
||||||
"munin.run.iteration",
|
|
||||||
{
|
|
||||||
"details": {
|
|
||||||
"target": target.show(),
|
|
||||||
"window_from": lib_plankton.pit.to_date_object(window_from).toISOString(),
|
|
||||||
"window_to": lib_plankton.pit.to_date_object(window_to).toISOString(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
for (const reminder_hours of target.reminders) {
|
|
||||||
for (const event of events) {
|
|
||||||
const event_begin : lib_plankton.pit.type_pit = lib_plankton.pit.from_datetime(
|
|
||||||
event.begin
|
|
||||||
);
|
|
||||||
const reminder_time : lib_plankton.pit.type_pit = lib_plankton.pit.shift_hour(
|
|
||||||
event_begin,
|
|
||||||
(-reminder_hours)
|
|
||||||
);
|
|
||||||
lib_plankton.log._info(
|
|
||||||
"munin.run.check_dueness",
|
|
||||||
{
|
|
||||||
"details": {
|
|
||||||
"event_begin": lib_plankton.pit.to_date_object(event_begin).toISOString(),
|
|
||||||
"reminder_hours": reminder_hours,
|
|
||||||
"reminder_time": lib_plankton.pit.to_date_object(reminder_time).toISOString(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const remind : boolean = lib_plankton.pit.is_between(
|
|
||||||
reminder_time,
|
|
||||||
window_from,
|
|
||||||
window_to
|
|
||||||
);
|
|
||||||
if (! remind) {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
lib_plankton.log._info(
|
|
||||||
"munin.remind.do",
|
|
||||||
{
|
|
||||||
"details": {
|
|
||||||
"event": event,
|
|
||||||
"target": target.show(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
if (dry_run) {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
try {
|
|
||||||
await target.send(conf.labels, event);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
lib_plankton.log.error(
|
|
||||||
"munin.remind.error",
|
|
||||||
{
|
|
||||||
"details": {
|
|
||||||
"message": String(error),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
async function run(
|
|
||||||
conf : _munin.conf.type_conf,
|
|
||||||
{
|
|
||||||
"single_run": single_run = false,
|
|
||||||
"dry_run": dry_run = false,
|
|
||||||
} : {
|
|
||||||
single_run ?: boolean;
|
|
||||||
dry_run ?: boolean;
|
|
||||||
} = {
|
|
||||||
}
|
|
||||||
) : Promise<void>
|
|
||||||
{
|
|
||||||
const sources : Array<_munin.type_source> = conf.sources.map(
|
|
||||||
source_raw => _munin.sources.factory(source_raw)
|
|
||||||
);
|
|
||||||
const targets : Array<_munin.type_target> = conf.targets.map(
|
|
||||||
target_raw => _munin.targets.factory(target_raw)
|
|
||||||
);
|
|
||||||
lib_plankton.log._info(
|
|
||||||
"munin.run.start",
|
|
||||||
{
|
|
||||||
"details": {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
if (single_run) {
|
|
||||||
await run_iteration(conf, sources, targets, {"dry_run": dry_run});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
while (true) {
|
|
||||||
await run_iteration(conf, sources, targets, {"dry_run": dry_run});
|
|
||||||
await lib_plankton.call.sleep(conf.settings.interval * 60 * 60);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
export async function main(
|
export async function main(
|
||||||
|
@ -169,16 +30,14 @@ namespace _munin
|
||||||
// args
|
// args
|
||||||
const arg_handler : lib_plankton.args.class_handler = new lib_plankton.args.class_handler(
|
const arg_handler : lib_plankton.args.class_handler = new lib_plankton.args.class_handler(
|
||||||
{
|
{
|
||||||
/*
|
|
||||||
"action": lib_plankton.args.class_argument.positional({
|
"action": lib_plankton.args.class_argument.positional({
|
||||||
"index": 0,
|
"index": 0,
|
||||||
"type": lib_plankton.args.enum_type.string,
|
"type": lib_plankton.args.enum_type.string,
|
||||||
"mode": lib_plankton.args.enum_mode.replace,
|
"mode": lib_plankton.args.enum_mode.replace,
|
||||||
"default": "run",
|
"default": "run",
|
||||||
"info": "what to do : help | run",
|
"info": "what to do : test | run",
|
||||||
"name": "action",
|
"name": "action",
|
||||||
}),
|
}),
|
||||||
*/
|
|
||||||
"conf_path": lib_plankton.args.class_argument.volatile({
|
"conf_path": lib_plankton.args.class_argument.volatile({
|
||||||
"indicators_long": ["conf-path"],
|
"indicators_long": ["conf-path"],
|
||||||
"indicators_short": ["c"],
|
"indicators_short": ["c"],
|
||||||
|
@ -274,67 +133,77 @@ namespace _munin
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// init
|
switch (args.action) {
|
||||||
const conf : _munin.conf.type_conf = await _munin.conf.load(args.conf_path);
|
default:
|
||||||
lib_plankton.log.set_main_logger(
|
{
|
||||||
[
|
|
||||||
{
|
|
||||||
"kind": "filtered",
|
|
||||||
"data": {
|
|
||||||
"core": {
|
|
||||||
"kind": "std",
|
|
||||||
"data": {
|
|
||||||
"target": "stdout",
|
|
||||||
"format": {
|
|
||||||
"kind": "human_readable",
|
|
||||||
"data": {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"predicate": [
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"item": {
|
|
||||||
"kind": "level",
|
|
||||||
"data": {
|
|
||||||
"threshold": args.verbosity,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
// exec
|
|
||||||
if (args.conf_expose) {
|
|
||||||
process.stdout.write(
|
|
||||||
lib_plankton.json.encode(
|
|
||||||
conf,
|
|
||||||
{
|
|
||||||
"formatted": true,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
+
|
|
||||||
"\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
switch (/*args.action*/"run") {
|
|
||||||
default: {
|
|
||||||
throw (new Error("unhandled action: " + args.action));
|
throw (new Error("unhandled action: " + args.action));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "run": {
|
case "test":
|
||||||
run(
|
{
|
||||||
|
_munin.test.all();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "run":
|
||||||
|
{
|
||||||
|
// init
|
||||||
|
const conf : _munin.conf.type_conf = await _munin.conf.load(args.conf_path);
|
||||||
|
lib_plankton.log.set_main_logger(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"kind": "filtered",
|
||||||
|
"data": {
|
||||||
|
"core": {
|
||||||
|
"kind": "std",
|
||||||
|
"data": {
|
||||||
|
"target": "stdout",
|
||||||
|
"format": {
|
||||||
|
"kind": "jsonl",
|
||||||
|
"data": {
|
||||||
|
"structured": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"predicate": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"item": {
|
||||||
|
"kind": "level",
|
||||||
|
"data": {
|
||||||
|
"threshold": args.verbosity,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
// exec
|
||||||
|
if (args.conf_expose) {
|
||||||
|
process.stdout.write(
|
||||||
|
lib_plankton.json.encode(
|
||||||
|
conf,
|
||||||
|
{
|
||||||
|
"formatted": true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
+
|
||||||
|
"\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_munin.logic.run(
|
||||||
conf,
|
conf,
|
||||||
{
|
{
|
||||||
"single_run": args.single_run,
|
"single_run": args.single_run,
|
||||||
"dry_run": args.dry_run,
|
"dry_run": args.dry_run,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,19 +31,103 @@ namespace _munin.targets.email
|
||||||
sender : string;
|
sender : string;
|
||||||
receivers : Array<string>;
|
receivers : Array<string>;
|
||||||
hide_tags : boolean;
|
hide_tags : boolean;
|
||||||
/**
|
reminders : Array<_munin.type_reminder>;
|
||||||
* in hours
|
|
||||||
*/
|
|
||||||
reminders : Array<int>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
function summarize_event(
|
||||||
|
parameters : type_parameters,
|
||||||
|
labels : _munin.type_labels,
|
||||||
|
event : _munin.type_event
|
||||||
|
) : string
|
||||||
|
{
|
||||||
|
return lib_plankton.string.coin(
|
||||||
|
"[{{head}}] {{date}} : {{macro_tags}}{{title}}",
|
||||||
|
{
|
||||||
|
"head": labels.head,
|
||||||
|
"date": lib_plankton.pit.date_format(
|
||||||
|
event.begin.date
|
||||||
|
),
|
||||||
|
"macro_tags": (
|
||||||
|
(event.tags === null)
|
||||||
|
?
|
||||||
|
""
|
||||||
|
:
|
||||||
|
(event.tags.map(tag => ("{" + tag + "}")).join(" ") + " ")
|
||||||
|
),
|
||||||
|
"title": event.title,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
function render_event(
|
||||||
|
parameters : type_parameters,
|
||||||
|
labels : _munin.type_labels,
|
||||||
|
event : _munin.type_event
|
||||||
|
) : string
|
||||||
|
{
|
||||||
|
return lib_plankton.string.coin(
|
||||||
|
"{{title_label}} | {{macro_tags}}{{title_value}}\n{{time_label}} | {{time_value}}{{macro_location}}{{macro_description}}",
|
||||||
|
{
|
||||||
|
"title_label": labels.title.toUpperCase(),
|
||||||
|
"macro_tags": (
|
||||||
|
(parameters.hide_tags || (event.tags === null))
|
||||||
|
?
|
||||||
|
""
|
||||||
|
:
|
||||||
|
(event.tags.map(tag => ("{" + tag + "}")).join(" ") + " ")
|
||||||
|
),
|
||||||
|
"title_value": event.title,
|
||||||
|
"time_label": labels.time.toUpperCase(),
|
||||||
|
"time_value": lib_plankton.pit.timespan_format(
|
||||||
|
event.begin,
|
||||||
|
event.end,
|
||||||
|
{
|
||||||
|
"adjust_to_ce": true,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"macro_location": (
|
||||||
|
(event.location === null)
|
||||||
|
?
|
||||||
|
""
|
||||||
|
:
|
||||||
|
lib_plankton.string.coin(
|
||||||
|
"\n{{location_label}} | {{location_value}}",
|
||||||
|
{
|
||||||
|
"location_label": labels.location.toUpperCase(),
|
||||||
|
"location_value": event.location,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"macro_description": (
|
||||||
|
(event.description === null)
|
||||||
|
?
|
||||||
|
""
|
||||||
|
:
|
||||||
|
lib_plankton.string.coin(
|
||||||
|
"\n\n{{description_value}}",
|
||||||
|
{
|
||||||
|
"description_value": event.description,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
async function send(
|
async function send(
|
||||||
parameters : type_parameters,
|
parameters : type_parameters,
|
||||||
labels : _munin.type_labels,
|
labels : _munin.type_labels,
|
||||||
event : _munin.type_event
|
events : Array<_munin.type_event>
|
||||||
) : Promise<void>
|
) : Promise<void>
|
||||||
{
|
{
|
||||||
await lib_plankton.email.send(
|
await lib_plankton.email.send(
|
||||||
|
@ -55,69 +139,24 @@ namespace _munin.targets.email
|
||||||
},
|
},
|
||||||
parameters.sender,
|
parameters.sender,
|
||||||
parameters.receivers,
|
parameters.receivers,
|
||||||
lib_plankton.string.coin(
|
(
|
||||||
"[{{head}}] {{date}} : {{macro_tags}}{{title}}",
|
(events.length === 1)
|
||||||
{
|
?
|
||||||
"head": labels.head,
|
summarize_event(parameters, labels, events[0])
|
||||||
"date": lib_plankton.pit.date_format(
|
:
|
||||||
event.begin.date
|
lib_plankton.string.coin(
|
||||||
),
|
"[{{head}}] {{count}} {{events}}",
|
||||||
"macro_tags": (
|
{
|
||||||
(event.tags === null)
|
"head": labels.head,
|
||||||
?
|
"count": events.length.toFixed(0),
|
||||||
""
|
"events": labels.events,
|
||||||
:
|
}
|
||||||
(event.tags.map(tag => ("{" + tag + "}")).join(" ") + " ")
|
)
|
||||||
),
|
|
||||||
"title": event.title,
|
|
||||||
}
|
|
||||||
),
|
),
|
||||||
lib_plankton.string.coin(
|
(
|
||||||
"{{title_label}} | {{macro_tags}}{{title_value}}\n{{time_label}} | {{time_value}}{{macro_location}}{{macro_description}}",
|
events
|
||||||
{
|
.map(event => render_event(parameters, labels, event))
|
||||||
"title_label": labels.title.toUpperCase(),
|
.join("\n\n---\n\n")
|
||||||
"macro_tags": (
|
|
||||||
(parameters.hide_tags || (event.tags === null))
|
|
||||||
?
|
|
||||||
""
|
|
||||||
:
|
|
||||||
(event.tags.map(tag => ("{" + tag + "}")).join(" ") + " ")
|
|
||||||
),
|
|
||||||
"title_value": event.title,
|
|
||||||
"time_label": labels.time.toUpperCase(),
|
|
||||||
"time_value": lib_plankton.pit.timespan_format(
|
|
||||||
event.begin,
|
|
||||||
event.end,
|
|
||||||
{
|
|
||||||
"adjust_to_ce": true,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
"macro_location": (
|
|
||||||
(event.location === null)
|
|
||||||
?
|
|
||||||
""
|
|
||||||
:
|
|
||||||
lib_plankton.string.coin(
|
|
||||||
"\n{{location_label}} | {{location_value}}",
|
|
||||||
{
|
|
||||||
"location_label": labels.location.toUpperCase(),
|
|
||||||
"location_value": event.location,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
),
|
|
||||||
"macro_description": (
|
|
||||||
(event.description === null)
|
|
||||||
?
|
|
||||||
""
|
|
||||||
:
|
|
||||||
lib_plankton.string.coin(
|
|
||||||
"\n\n{{description_value}}",
|
|
||||||
{
|
|
||||||
"description_value": event.description,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -137,7 +176,7 @@ namespace _munin.targets.email
|
||||||
"receivers": parameters.receivers.join(","),
|
"receivers": parameters.receivers.join(","),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
"send": (labels, event) => send(parameters, labels, event),
|
"send": (labels, events) => send(parameters, labels, events),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,66 +27,100 @@ namespace _munin.targets.telegram_bot
|
||||||
bot_token : string;
|
bot_token : string;
|
||||||
chat_id : int;
|
chat_id : int;
|
||||||
hide_tags : boolean;
|
hide_tags : boolean;
|
||||||
reminders : Array<int>;
|
reminders : Array<_munin.type_reminder>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
function render_event(
|
||||||
|
parameters : type_parameters,
|
||||||
|
labels : _munin.type_labels,
|
||||||
|
event : _munin.type_event
|
||||||
|
) : string
|
||||||
|
{
|
||||||
|
return lib_plankton.string.coin(
|
||||||
|
"{{title_label}} | {{macro_tags}}{{title_value}}\n{{time_label}} | {{time_value}}{{macro_location}}{{macro_description}}",
|
||||||
|
{
|
||||||
|
"macro_tags": (
|
||||||
|
(parameters.hide_tags || (event.tags === null))
|
||||||
|
?
|
||||||
|
""
|
||||||
|
:
|
||||||
|
(event.tags.map(tag => ("{" + tag + "}")).join(" ") + " ")
|
||||||
|
),
|
||||||
|
"title_label": labels.title.toUpperCase(),
|
||||||
|
"title_value": event.title,
|
||||||
|
"time_label": labels.time.toUpperCase(),
|
||||||
|
"time_value": lib_plankton.pit.timespan_format(
|
||||||
|
event.begin,
|
||||||
|
event.end,
|
||||||
|
{
|
||||||
|
"adjust_to_ce": true,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"macro_location": (
|
||||||
|
(event.location === null)
|
||||||
|
?
|
||||||
|
""
|
||||||
|
:
|
||||||
|
lib_plankton.string.coin(
|
||||||
|
"\n{{location_label}} | {{location_value}}",
|
||||||
|
{
|
||||||
|
"location_label": labels.location.toUpperCase(),
|
||||||
|
"location_value": event.location,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"macro_description": (
|
||||||
|
(event.description === null)
|
||||||
|
?
|
||||||
|
""
|
||||||
|
:
|
||||||
|
lib_plankton.string.coin(
|
||||||
|
"\n\n{{description_value}}",
|
||||||
|
{
|
||||||
|
"description_value": event.description,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
async function send(
|
async function send(
|
||||||
parameters : type_parameters,
|
parameters : type_parameters,
|
||||||
labels : _munin.type_labels,
|
labels : _munin.type_labels,
|
||||||
event : _munin.type_event
|
events : Array<_munin.type_event>
|
||||||
) : Promise<void>
|
) : Promise<void>
|
||||||
{
|
{
|
||||||
await lib_plankton.telegram.bot_call_send_message(
|
await lib_plankton.telegram.bot_call_send_message(
|
||||||
parameters.bot_token,
|
parameters.bot_token,
|
||||||
parameters.chat_id,
|
parameters.chat_id,
|
||||||
lib_plankton.string.coin(
|
lib_plankton.string.coin(
|
||||||
"*{{head}}*\n\n\{{title_label}} | {{macro_tags}}{{title_value}}\n{{time_label}} | {{time_value}}{{macro_location}}{{macro_description}}",
|
"*{{head_core}}{{head_extra}}*\n\n{{events}}",
|
||||||
{
|
{
|
||||||
"head": labels.head,
|
"head_core": labels.head,
|
||||||
"macro_tags": (
|
"head_extra": (
|
||||||
(parameters.hide_tags || (event.tags === null))
|
(events.length <= 1)
|
||||||
?
|
|
||||||
""
|
|
||||||
:
|
|
||||||
(event.tags.map(tag => ("{" + tag + "}")).join(" ") + " ")
|
|
||||||
),
|
|
||||||
"title_label": labels.title.toUpperCase(),
|
|
||||||
"title_value": event.title,
|
|
||||||
"time_label": labels.time.toUpperCase(),
|
|
||||||
"time_value": lib_plankton.pit.timespan_format(
|
|
||||||
event.begin,
|
|
||||||
event.end,
|
|
||||||
{
|
|
||||||
"adjust_to_ce": true,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
"macro_location": (
|
|
||||||
(event.location === null)
|
|
||||||
?
|
?
|
||||||
""
|
""
|
||||||
:
|
:
|
||||||
lib_plankton.string.coin(
|
lib_plankton.string.coin(
|
||||||
"\n{{location_label}} | {{location_value}}",
|
" ({{count}} {{events}})",
|
||||||
{
|
{
|
||||||
"location_label": labels.location.toUpperCase(),
|
"count": events.length.toFixed(0),
|
||||||
"location_value": event.location,
|
"events": labels.events,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
"macro_description": (
|
"events": (
|
||||||
(event.description === null)
|
events
|
||||||
?
|
.map(event => render_event(parameters, labels, event))
|
||||||
""
|
.join("\n\n---\n\n")
|
||||||
:
|
|
||||||
lib_plankton.string.coin(
|
|
||||||
"\n\n{{description_value}}",
|
|
||||||
{
|
|
||||||
"description_value": event.description,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
@ -111,7 +145,7 @@ namespace _munin.targets.telegram_bot
|
||||||
"chat_id": parameters.chat_id.toFixed(0),
|
"chat_id": parameters.chat_id.toFixed(0),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
"send": (labels, event) => send(parameters, labels, event),
|
"send": (labels, events) => send(parameters, labels, events),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
139
source/test.ts
Normal file
139
source/test.ts
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
/*
|
||||||
|
This file is part of »munin«.
|
||||||
|
|
||||||
|
Copyright 2025 'Fenris Wolf' <fenris@folksprak.org>
|
||||||
|
|
||||||
|
»munin« is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
»munin« is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public License
|
||||||
|
along with »munin«. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
namespace _munin.test
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo outsource?
|
||||||
|
*/
|
||||||
|
function lists_equal<type_element>(
|
||||||
|
list1 : Array<type_element>,
|
||||||
|
list2 : Array<type_element>
|
||||||
|
) : boolean
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
(list1.length === list2.length)
|
||||||
|
&&
|
||||||
|
list1.every(
|
||||||
|
(element, index) => (element === list2[index])
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
async function reminder_check(
|
||||||
|
) : Promise<void>
|
||||||
|
{
|
||||||
|
type type_input = {
|
||||||
|
reminder : {
|
||||||
|
frequency : string;
|
||||||
|
offset : int;
|
||||||
|
from : int;
|
||||||
|
to : int;
|
||||||
|
};
|
||||||
|
events : Array<
|
||||||
|
{
|
||||||
|
title : string;
|
||||||
|
begin : lib_plankton.pit.type_datetime;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
datetime : lib_plankton.pit.type_datetime;
|
||||||
|
interval : int;
|
||||||
|
};
|
||||||
|
type type_output = (null | Array<string>);
|
||||||
|
const testcases : Array<
|
||||||
|
_munin.helpers.test.type_testcase<
|
||||||
|
type_input,
|
||||||
|
type_output
|
||||||
|
>
|
||||||
|
> = await _munin.helpers.test.get_data<type_input, type_output>(
|
||||||
|
"data/reminder_check.testdata.json",
|
||||||
|
{
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const testcase of testcases)
|
||||||
|
{
|
||||||
|
_munin.helpers.test.start(testcase.name);
|
||||||
|
|
||||||
|
// execution
|
||||||
|
const result : (null | Array<_munin.type_event>) = _munin.logic.reminder_check(
|
||||||
|
{
|
||||||
|
"frequency": _munin.logic.frequency_decode(testcase.input.reminder.frequency),
|
||||||
|
"offset": testcase.input.reminder.offset,
|
||||||
|
"from": testcase.input.reminder.from,
|
||||||
|
"to": testcase.input.reminder.to,
|
||||||
|
},
|
||||||
|
testcase.input.events.map(
|
||||||
|
event_raw => ({
|
||||||
|
"title": event_raw.title,
|
||||||
|
"begin": event_raw.begin,
|
||||||
|
"end": null,
|
||||||
|
"description": null,
|
||||||
|
"location": null,
|
||||||
|
"tags": null,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"pit": lib_plankton.pit.from_datetime(testcase.input.datetime),
|
||||||
|
"interval": testcase.input.interval,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// assertions
|
||||||
|
if (
|
||||||
|
(
|
||||||
|
(testcase.output === null)
|
||||||
|
&&
|
||||||
|
(result === null)
|
||||||
|
)
|
||||||
|
||
|
||||||
|
(
|
||||||
|
(testcase.output !== null)
|
||||||
|
&&
|
||||||
|
lists_equal<string>(
|
||||||
|
result.map(event => event.title),
|
||||||
|
testcase.output
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
// success
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_munin.helpers.test.fail(testcase.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
export async function all(
|
||||||
|
) : Promise<void>
|
||||||
|
{
|
||||||
|
await reminder_check();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -21,6 +21,16 @@ along with »munin«. If not, see <http://www.gnu.org/licenses/>.
|
||||||
namespace _munin
|
namespace _munin
|
||||||
{
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
export enum enum_frequency {
|
||||||
|
hourly = "hourly",
|
||||||
|
daily = "daily",
|
||||||
|
weekly = "weekly",
|
||||||
|
monthly = "monthly",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
export type type_labels = {
|
export type type_labels = {
|
||||||
|
@ -28,6 +38,7 @@ namespace _munin
|
||||||
title : string;
|
title : string;
|
||||||
time : string;
|
time : string;
|
||||||
location : string;
|
location : string;
|
||||||
|
events : string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -55,15 +66,26 @@ namespace _munin
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo rename
|
||||||
|
*/
|
||||||
|
export type type_reminder = {
|
||||||
|
frequency : enum_frequency;
|
||||||
|
offset : int;
|
||||||
|
from : int;
|
||||||
|
to : int;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
export type type_target = {
|
export type type_target = {
|
||||||
reminders : Array<int>;
|
reminders : Array<type_reminder>;
|
||||||
show : (() => string);
|
show : (() => string);
|
||||||
send : (
|
send : (
|
||||||
(
|
(
|
||||||
labels : type_labels,
|
labels : type_labels,
|
||||||
event : type_event
|
events : Array<type_event>
|
||||||
)
|
)
|
||||||
=>
|
=>
|
||||||
Promise<void>
|
Promise<void>
|
||||||
|
|
13
tools/build
13
tools/build
|
@ -1,4 +1,13 @@
|
||||||
#!/usr/bin/env sh
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
## core
|
||||||
tools/ivaldi build
|
tools/ivaldi build
|
||||||
cd build && npm install nodemailer ; cd -
|
|
||||||
|
## data
|
||||||
|
mkdir -p build/data
|
||||||
|
cp -r -u -v data/* build/data/
|
||||||
|
|
||||||
|
## node modules
|
||||||
|
node_modules=""
|
||||||
|
node_modules="${node_modules} nodemailer"
|
||||||
|
cd build && npm install ${node_modules} ; cd -
|
||||||
|
|
Loading…
Reference in a new issue