540 lines
12 KiB
TypeScript
540 lines
12 KiB
TypeScript
/*
|
|
This file is part of »ivaldi«.
|
|
|
|
Copyright 2025 'Fenris Wolf' <fenris@folksprak.org>
|
|
|
|
»ivaldi« 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.
|
|
|
|
»ivaldi« 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 »ivaldi«. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
namespace _ivaldi
|
|
{
|
|
|
|
/**
|
|
* @todo outsource
|
|
*/
|
|
function execute(
|
|
command : string,
|
|
{
|
|
"log_only": log_only = false,
|
|
"working_directory": working_directory = null,
|
|
} : {
|
|
log_only ?: boolean;
|
|
working_directory ?: (null | string);
|
|
} = {
|
|
}
|
|
) : Promise<
|
|
{
|
|
code : int;
|
|
stdout : string;
|
|
stderr : string;
|
|
}
|
|
>
|
|
{
|
|
const nm_child_process = require("child_process");
|
|
return (
|
|
new Promise(
|
|
(resolve, reject) => {
|
|
lib_plankton.log._info(
|
|
"execute",
|
|
{
|
|
"details": {
|
|
"command": command,
|
|
}
|
|
}
|
|
);
|
|
if (log_only) {
|
|
resolve(
|
|
{
|
|
"code": 0,
|
|
"stdout": "",
|
|
"stderr": "",
|
|
}
|
|
);
|
|
}
|
|
else {
|
|
nm_child_process.exec(
|
|
command,
|
|
{
|
|
"cwd": ((working_directory === null) ? undefined : working_directory),
|
|
},
|
|
(error, stdout, stderr) => {
|
|
if (error) {
|
|
reject(error);
|
|
}
|
|
else {
|
|
resolve(
|
|
{
|
|
/**
|
|
* @todo assign correct value
|
|
*/
|
|
"code": 0,
|
|
"stdout": stdout,
|
|
"stderr": stderr,
|
|
}
|
|
);
|
|
}
|
|
}
|
|
);
|
|
}
|
|
}
|
|
)
|
|
);
|
|
}
|
|
|
|
|
|
/**
|
|
*/
|
|
export async function main(
|
|
args_raw : Array<string>
|
|
) : Promise<void>
|
|
{
|
|
// args
|
|
const arg_handler : lib_plankton.args.class_handler = new lib_plankton.args.class_handler(
|
|
{
|
|
"action": lib_plankton.args.class_argument.positional({
|
|
"index": 0,
|
|
"type": lib_plankton.args.enum_type.string,
|
|
"mode": lib_plankton.args.enum_mode.replace,
|
|
"default": "help",
|
|
"info": "what to do : fetch | clear | build | deploy",
|
|
"name": "action",
|
|
}),
|
|
"data_path": lib_plankton.args.class_argument.volatile({
|
|
"indicators_long": ["data-path"],
|
|
"indicators_short": ["d"],
|
|
"type": lib_plankton.args.enum_type.string,
|
|
"mode": lib_plankton.args.enum_mode.replace,
|
|
"default": "ivaldi.json",
|
|
"info": "path to data file",
|
|
"name": "data-path",
|
|
}),
|
|
"verbosity": lib_plankton.args.class_argument.volatile({
|
|
"indicators_long": ["verbosity"],
|
|
"indicators_short": ["v"],
|
|
"type": lib_plankton.args.enum_type.string,
|
|
"mode": lib_plankton.args.enum_mode.replace,
|
|
"default": "notice",
|
|
"info": "error | warning | notice | info | debug",
|
|
"name": "verbosity",
|
|
}),
|
|
"help": lib_plankton.args.class_argument.volatile({
|
|
"indicators_long": ["help"],
|
|
"indicators_short": ["h"],
|
|
"type": lib_plankton.args.enum_type.boolean,
|
|
"mode": lib_plankton.args.enum_mode.replace,
|
|
"default": false,
|
|
"info": "alias for action 'help'",
|
|
"name": "help",
|
|
}),
|
|
}
|
|
);
|
|
const args : Record<string, any> = arg_handler.read(
|
|
lib_plankton.args.enum_environment.cli,
|
|
args_raw.join(" ")
|
|
);
|
|
|
|
// init
|
|
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.help) {
|
|
process.stdout.write(
|
|
arg_handler.generate_help(
|
|
{
|
|
"programname": "ivaldi",
|
|
"description": "a collection of tools for building and deploying a software project",
|
|
"executable": "ivaldi",
|
|
}
|
|
)
|
|
);
|
|
}
|
|
else {
|
|
const content : string = await lib_plankton.file.read(args.data_path);
|
|
const data_raw : any = lib_plankton.json.decode(content);
|
|
const data : _ivaldi.type_data = {
|
|
"version": data_raw["version"],
|
|
"name": data_raw["name"],
|
|
"libs": (data_raw["libs"] ?? {}),
|
|
"sources": data_raw["sources"],
|
|
"target": (
|
|
data_raw["target"]
|
|
??
|
|
"node"
|
|
),
|
|
"directories": {
|
|
"lib": (
|
|
(data_raw["misc"] ?? {})["lib"]
|
|
??
|
|
"lib"
|
|
),
|
|
"source": (
|
|
(data_raw["misc"] ?? {})["source"]
|
|
??
|
|
"source"
|
|
),
|
|
"temp": (
|
|
(data_raw["misc"] ?? {})["temp"]
|
|
??
|
|
lib_plankton.string.coin("/tmp/_{{name}}", {"name": data_raw["name"]})
|
|
),
|
|
"build": (
|
|
(data_raw["misc"] ?? {})["build"]
|
|
??
|
|
lib_plankton.string.coin("/tmp/{{name}}", {"name": data_raw["name"]})
|
|
),
|
|
"makefile": (
|
|
(data_raw["misc"] ?? {})["makefile"]
|
|
??
|
|
"/tmp"
|
|
),
|
|
},
|
|
"commands": {
|
|
"typescript_compile": (
|
|
(data_raw["commands"] ?? {})["typescript_compile"]
|
|
??
|
|
"tsc"
|
|
),
|
|
},
|
|
};
|
|
switch (args.action) {
|
|
default: {
|
|
return Promise.reject<void>((new Error("unhandled action: " + args.action)));
|
|
break;
|
|
}
|
|
case "fetch": {
|
|
for (const lib of data.libs) {
|
|
switch (lib.kind) {
|
|
case "plankton": {
|
|
const directory : string = lib_plankton.string.coin(
|
|
"{{directory}}/plankton",
|
|
{
|
|
"directory": data.directories.lib,
|
|
}
|
|
);
|
|
/**
|
|
* @todo do not use "execute" for that
|
|
*/
|
|
await execute(
|
|
lib_plankton.string.coin(
|
|
"mkdir -p {{directory}}",
|
|
{
|
|
"directory": directory,
|
|
}
|
|
)
|
|
);
|
|
await execute(
|
|
lib_plankton.string.coin(
|
|
"ptk bundle {{target}}} {{modules}}",
|
|
{
|
|
"target": data.target,
|
|
"modules": lib.data.modules.join(" "),
|
|
}
|
|
),
|
|
{
|
|
"working_directory": directory,
|
|
}
|
|
);
|
|
break;
|
|
}
|
|
case "node": {
|
|
/**
|
|
* @todo
|
|
*/
|
|
throw (new Error("unhanled lib kind: " + "node"));
|
|
break;
|
|
}
|
|
default: {
|
|
throw (new Error("unhanled lib kind: " + lib["kind"]));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return Promise.resolve<void>(undefined);
|
|
break;
|
|
}
|
|
case "clear": {
|
|
const commands : Array<string> = [
|
|
lib_plankton.string.coin(
|
|
"rm -r -f {{directory}}",
|
|
{
|
|
"directory": data.directories.temp,
|
|
}
|
|
),
|
|
lib_plankton.string.coin(
|
|
"rm -r -f {{directory}}",
|
|
{
|
|
"directory": data.directories.build,
|
|
}
|
|
),
|
|
];
|
|
/**
|
|
* @todo do not use "execute" for that
|
|
*/
|
|
for (const command of commands) {
|
|
await execute(command);
|
|
}
|
|
return Promise.resolve<void>(undefined);
|
|
break;
|
|
}
|
|
case "build": {
|
|
const has_plankton : boolean = data.libs.some(lib => (lib.kind === "plankton"));
|
|
const path_plankton_declaration : string = lib_plankton.string.coin(
|
|
"{{directory_libs}}/plankton/plankton.d.ts",
|
|
{
|
|
"directory_libs": data.directories.lib,
|
|
}
|
|
);
|
|
const path_plankton_implementation : string = lib_plankton.string.coin(
|
|
"{{directory_libs}}/plankton/plankton.js",
|
|
{
|
|
"directory_libs": data.directories.lib,
|
|
}
|
|
);
|
|
const paths_sources : Array<string> = (
|
|
data.sources
|
|
.map(
|
|
source => lib_plankton.string.coin(
|
|
"{{directory}}/{{name}}",
|
|
{
|
|
"directory": data.directories.source,
|
|
"name": source,
|
|
}
|
|
)
|
|
)
|
|
);
|
|
const path_unlinked : string = lib_plankton.string.coin(
|
|
"{{directory_temp}}/{{name}}-unlinked.js",
|
|
{
|
|
"directory_temp": data.directories.temp,
|
|
"name": data.name,
|
|
}
|
|
);
|
|
const path_head : string = lib_plankton.string.coin(
|
|
"{{directory_temp}}/head.js",
|
|
{
|
|
"directory_temp": data.directories.temp,
|
|
}
|
|
);
|
|
const path_result : string = lib_plankton.string.coin(
|
|
"{{directory_build}}/{{name}}",
|
|
{
|
|
"directory_build": data.directories.build,
|
|
"name": data.name,
|
|
}
|
|
);
|
|
const dependencies_compilation : Array<string> = (
|
|
[]
|
|
.concat(has_plankton ? [path_plankton_declaration] : [])
|
|
.concat(paths_sources)
|
|
);
|
|
const gnu_make_sheet : lib_plankton.gnu_make.type_sheet = {
|
|
"rules": [
|
|
// root
|
|
{
|
|
"kind": "meta",
|
|
"data": {
|
|
"phony": true,
|
|
"name": "_root",
|
|
"dependencies": [
|
|
path_result,
|
|
],
|
|
}
|
|
},
|
|
// compile
|
|
{
|
|
"kind": "concrete",
|
|
"data": {
|
|
"targets": [
|
|
path_unlinked,
|
|
],
|
|
"dependencies": dependencies_compilation,
|
|
"actions": [
|
|
// log
|
|
lib_plankton.string.coin(
|
|
"echo '-- {{message}}'",
|
|
{
|
|
"message": "compiling …",
|
|
}
|
|
),
|
|
// mkdir
|
|
lib_plankton.string.coin(
|
|
"mkdir -p $(dir $@)",
|
|
{
|
|
}
|
|
),
|
|
// command
|
|
lib_plankton.string.coin(
|
|
"{{command}} $^ --lib es2020,dom --target es6 --outFile $@",
|
|
{
|
|
"command": data.commands.typescript_compile,
|
|
}
|
|
),
|
|
],
|
|
}
|
|
},
|
|
// head
|
|
{
|
|
"kind": "concrete",
|
|
"data": {
|
|
"targets": [
|
|
path_head,
|
|
],
|
|
"dependencies": [
|
|
],
|
|
"actions": [
|
|
// log
|
|
lib_plankton.string.coin(
|
|
"echo '-- {{message}}'",
|
|
{
|
|
"message": "creating head …",
|
|
}
|
|
),
|
|
// mkdir
|
|
lib_plankton.string.coin(
|
|
"mkdir -p $(dir $@)",
|
|
{
|
|
}
|
|
),
|
|
// create
|
|
lib_plankton.string.coin(
|
|
"echo '#!/usr/bin/env node' > {{path}}",
|
|
{
|
|
"path": path_head,
|
|
}
|
|
),
|
|
]
|
|
}
|
|
},
|
|
// link
|
|
{
|
|
"kind": "concrete",
|
|
"data": {
|
|
"targets": [
|
|
path_result,
|
|
],
|
|
"dependencies": (
|
|
[]
|
|
.concat([path_head])
|
|
.concat(has_plankton ? [path_plankton_implementation] : [])
|
|
.concat([path_unlinked])
|
|
),
|
|
"actions": [
|
|
// log
|
|
lib_plankton.string.coin(
|
|
"echo '-- {{message}}'",
|
|
{
|
|
"message": "linking …",
|
|
}
|
|
),
|
|
// mkdir
|
|
lib_plankton.string.coin(
|
|
"mkdir -p $(dir $@)",
|
|
{
|
|
}
|
|
),
|
|
// command
|
|
lib_plankton.string.coin(
|
|
"cat $^ > $@",
|
|
{
|
|
}
|
|
),
|
|
// make executable
|
|
lib_plankton.string.coin(
|
|
"chmod +x {{path}}",
|
|
{
|
|
"path": path_result,
|
|
}
|
|
),
|
|
],
|
|
},
|
|
},
|
|
],
|
|
};
|
|
const makefile : string = lib_plankton.gnu_make.render_sheet(
|
|
gnu_make_sheet,
|
|
{
|
|
"break_dependencies": true,
|
|
"silent_actions": true,
|
|
}
|
|
);
|
|
const makefile_path : string = lib_plankton.string.coin(
|
|
"{{directory}}/ivaldi-makefile-{{name}}",
|
|
{
|
|
"directory": data.directories.makefile,
|
|
"name": data.name,
|
|
}
|
|
);
|
|
await lib_plankton.file.write(
|
|
makefile_path,
|
|
makefile
|
|
);
|
|
const command : string = lib_plankton.string.coin(
|
|
"make -f {{path}}",
|
|
{
|
|
"path": makefile_path,
|
|
}
|
|
);
|
|
const result = await execute(command);
|
|
process.stdout.write(result.stdout + "\n");
|
|
process.stderr.write(result.stderr + "\n");
|
|
return Promise.resolve<void>(undefined);
|
|
break;
|
|
}
|
|
case "deploy": {
|
|
return Promise.reject<void>(new Error("not yet implemented"));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
_ivaldi.main(process.argv.slice(2))
|
|
.then(() => {})
|
|
.catch((reason) => {process.stderr.write(String(reason) + "\n");})
|
|
;
|
|
|