From 6300c164851920bc54025ef4e708f9f12412edb4 Mon Sep 17 00:00:00 2001 From: Fenris Wolf Date: Fri, 18 Sep 2026 02:13:11 +0200 Subject: [PATCH] [int] --- .gitignore | 1 + misc/ivaldi.schema.json | 152 ++++++++++ misc/wizard | 564 +++++++++++++++++++++++++++++++++++ source/helpers.ts | 185 ++++++++++++ source/main.ts | 635 +++++++++++++++++++++++++++------------- source/types.ts | 32 +- tools/makefile | 1 + 7 files changed, 1358 insertions(+), 212 deletions(-) create mode 100644 misc/ivaldi.schema.json create mode 100755 misc/wizard create mode 100644 source/helpers.ts diff --git a/.gitignore b/.gitignore index 1bff2e8..145537b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /build/ /temp/ +/.geany diff --git a/misc/ivaldi.schema.json b/misc/ivaldi.schema.json new file mode 100644 index 0000000..b8f64ac --- /dev/null +++ b/misc/ivaldi.schema.json @@ -0,0 +1,152 @@ +{ + "nullable": false, + "type": "object", + "properties": { + "version": { + "nullable": false, + "type": "string" + }, + "name": { + "nullable": false, + "type": "string" + }, + "kind": { + "nullable": false, + "type": "string", + "enum": ["node", "web"] + }, + "dependencies": { + "nullable": true, + "type": "object", + "properties": { + "plankton": { + "nullable": true, + "type": "array", + "item": { + "nullable": false, + "type": "string" + }, + "default": null + }, + "node_modules": { + "nullable": true, + "type": "array", + "item": { + "nullable": false, + "type": "string" + }, + "default": null + } + }, + "required": [ + ], + "default": null + }, + "core": { + "nullable": false, + "type": "array", + "item": { + "nullable": false, + "type": "string" + } + }, + "apps": { + "nullable": false, + "type": "object", + "properties": { + "name": { + "nullable": false, + "type": "string" + }, + "sources": { + "nullable": false, + "type": "array", + "item": { + "nullable": false, + "type": "string" + } + }, + "additional_dependencies": { + "nullable": false, + "type": "array", + "item": { + "nullable": false, + "type": "string" + }, + "default": [] + }, + "destination": { + "nullable": false, + "type": "string", + "default": "{{project}}-{{app}" + }, + "shebang": { + "nullable": true, + "type": "string", + "default": null + }, + "executable": { + "nullable": false, + "type": "boolean", + "default": false + } + }, + "additionalProperties": false, + "required": [ + "name", + "sources" + ] + }, + "custom_rules": { + "nullable": false, + "type": "array", + "item": { + "nullable": false, + "type": "object", + "properties": { + "targets": { + "nullable": false, + "type": "array", + "item": { + "nullable": false, + "type": "string" + } + }, + "dependencies": { + "nullable": false, + "type": "array", + "item": { + "nullable": false, + "type": "string" + } + }, + "actions": { + "nullable": false, + "type": "array", + "item": { + "nullable": false, + "type": "string" + } + }, + "info": { + "nullable": true, + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "targets", + "dependencies", + "actions" + ] + } + } + }, + "additionalProperties": false, + "required": [ + "version", + "name", + "kind", + "core" + ] +} diff --git a/misc/wizard b/misc/wizard new file mode 100755 index 0000000..b7945c5 --- /dev/null +++ b/misc/wizard @@ -0,0 +1,564 @@ +#!/usr/bin/env python3 + +import sys as _sys +import os as _os +import json as _json +import argparse as _argparse + + +def file_read(path): + handle = open(path, "r") + content = handle.read() + handle.close() + return content + + +def string_coin(pattern, arguments): + result = pattern + for (key, value, ) in arguments.items(): + result = result.replace("{{%s}}" % key, value) + return result + + +def command_log(content): + return string_coin( + "echo '-- {{content}}'", + { + "content": content, + } + ) + + +def command_mkdir(path): + return string_coin( + "mkdir --parents {{path}}", + { + "path": path, + } + ) + + +def command_cat(source_paths, target_path, options = None): + options = ( + { + "append": False, + } + | + (options or {}) + ) + return string_coin( + "cat {{source_paths}} {{operator}} {{target_path}}", + { + "source_paths": " ".join(source_paths), + "operator": (">>" if options["append"] else ">"), + "target_path": target_path, + } + ) + + +def command_copy(source_path, target_path, options = None): + options = ( + { + } + | + (options or {}) + ) + return string_coin( + "cp -r -u {{source_path}} {{target_path}}", + { + "source_path": source_path, + "target_path": target_path, + } + ) + + +def command_tsc(source_paths, target_path, options = None): + options = ( + { + "declaration": False, + "libs": [], + } + | + (options or {}) + ) + return string_coin( + "tsc{{libs}} {{source_paths}} --outFile {{target_path}}{{declaration}}", + { + "libs": "".join( + map( + lambda name: string_coin( + " --lib {{name}}", + {"name": name} + ), + options["libs"] + ), + ), + "source_paths": " ".join(source_paths), + "target_path": target_path, + "declaration": (" --declaration" if options["declaration"] else ""), + } + ) + + +def gnumake_rule(data): + return string_coin( + "{{phony}}{{targets}}: {{dependencies}}\n{{actions}}\n", + { + "phony": ( + string_coin(".PHONY: {{name}}\n", {"name": data["targets"][0]}) + if (data["phony"] and (len(data["targets"]) == 1)) else + "" + ), + "targets": " ".join(data["targets"]), + "dependencies": " ".join(data["dependencies"]), + "actions": "".join( + map( + lambda data_action: string_coin( + "\t{{prefix}}{{name}}\n", + { + "prefix": ("@ " if data_action["silent"] else ""), + "name": data_action["command"], + } + ), + data["actions"] + ) + ), + } + ) + + +def gnumake_sheet(data): + return string_coin( + "{{rules}}", + { + "rules": "".join( + map( + lambda data_rule: gnumake_rule(data_rule), + data["rules"] + ) + ), + } + ) + + +def action_update_plankton(conf, definition): + directory = _os.path.join(definition["directories"]["lib"], "plankton") + commands = [ + command_mkdir(directory), + string_coin("cd {{directory}}", {"directory": directory}), + string_coin( + "ptk bundle {{target}} {{modules}}", + { + "target": definition["kind"], + "modules": " ".join(definition["dependencies"]["plankton"]), + } + ), + "cd - > /dev/null", + ] + _sys.stdout.write( + "".join( + map( + lambda command: (command + "\n"), + commands + ) + ) + ) + + +def refine_definition(definition_raw): + return { + "name": definition_raw["name"], + "kind": definition_raw.get("kind", "node"), + "directories": { + "lib": definition_raw.get("directories", {}).get("lib", "lib"), + "misc": definition_raw.get("directories", {}).get("misc", "misc"), + "source": definition_raw.get("directories", {}).get("source", "source"), + }, + "dependencies": { + "plankton": definition_raw.get("dependencies", {}).get("plankton", []), + "node": definition_raw.get("dependencies", {}).get("node", []), + }, + "core": definition_raw["core"], + "apps": list( + map( + lambda app_raw: { + "name": app_raw["name"], + "sources": app_raw["sources"], + "additional_dependencies": app_raw.get("additional_dependencies", []), + "destination": app_raw.get("destination", "{{project}}-{{app}}"), + "shebang": app_raw.get("shebang", ""), + "executable": app_raw.get("executable", True), + }, + definition_raw["apps"] + ) + ), + "custom_rules": definition_raw.get("custom_rules", []), + } + + +def action_update_node_modules(conf, definition): + directory = _os.path.join(definition["directories"]["lib"], "node") + commands = [ + command_mkdir(directory), + string_coin("cd {{directory}}", {"directory": directory}), + string_coin( + "npm install {{modules}}", + { + "modules": " ".join(definition["dependencies"]["node"]), + } + ), + "cd - > /dev/null", + ] + _sys.stdout.write( + "".join( + map( + lambda command: (command + "\n"), + commands + ) + ) + ) + + +def action_build(conf, definition): + data = { + "rules": ( + [ + { + "phony": True, + "targets": ["_default"], + "dependencies": list( + map( + lambda data_app: string_coin("app-{{name}}", {"name": data_app["name"]}), + definition["apps"] + ) + ), + "actions": [], + }, + { + "phony": True, + "targets": [ + "node_modules" + ], + "dependencies": [], + "actions": [ + { + "silent": True, + "command": command_log("placing node modules …"), + }, + { + "silent": True, + "command": command_mkdir(_os.path.join(conf["directories"]["build"], "node_modules")), + }, + { + "silent": True, + "command": command_copy( + _os.path.join(definition["directories"]["lib"], "node", "node_modules", "*"), + _os.path.join(conf["directories"]["build"], "node_modules", "") + ), + }, + ] + }, + { + "phony": False, + "targets": [ + _os.path.join(conf["directories"]["temp"], "core-unlinked.js"), + _os.path.join(conf["directories"]["temp"], "core-unlinked.d.ts"), + ], + "dependencies": ( + [ + _os.path.join(definition["directories"]["lib"], "plankton", "plankton.d.ts"), + ] + + + list( + map( + lambda path: _os.path.join(definition["directories"]["source"], path), + definition["core"] + ) + ) + ), + "actions": [ + { + "silent": True, + "command": command_log("core | compiling …") + }, + { + "silent": True, + "command": command_mkdir("$(dir $@)") + }, + { + "silent": True, + "command": command_tsc( + ["$^"], + _os.path.join(conf["directories"]["temp"], "core-unlinked.js"), + { + "libs": ["es2020"], + "declaration": True, + } + ) + } + ] + }, + ] + + + list( + map( + lambda data_custom_rule: { + "phony": False, + "targets": list( + map( + lambda target: string_coin( + target, + { + "dir_build": conf["directories"]["build"], + } + ), + data_custom_rule["targets"] + ) + ), + "dependencies": list( + map( + lambda dependency: string_coin( + dependency, + { + "dir_source": definition["directories"]["source"], + "dir_misc": definition["directories"]["misc"], + } + ), + data_custom_rule["dependencies"] + ) + ), + "actions": ( + [ + { + "silent": True, + "command": command_log( + string_coin( + "{{info}} …", + { + "info": data_custom_rule["info"], + } + ), + ), + } + ] + + + list( + map( + lambda action: { + "silent": True, + "command": action, + }, + data_custom_rule["actions"] + ) + ) + ), + }, + definition["custom_rules"] + ) + ) + + + list( + map( + lambda data_app: { + "phony": False, + "targets": [ + _os.path.join(conf["directories"]["temp"], string_coin("app-{{name}}-unlinked.js", {"name": data_app["name"]})), + ], + "dependencies": ( + [ + _os.path.join(definition["directories"]["lib"], "plankton", "plankton.d.ts"), + _os.path.join(conf["directories"]["temp"], "core-unlinked.d.ts"), + ] + + + list( + map( + lambda path: _os.path.join(definition["directories"]["source"], path), + data_app["sources"] + ) + ) + ), + "actions": [ + { + "silent": True, + "command": command_log(string_coin("app:{{name}} | compiling …", {"name": data_app["name"]})), + }, + { + "silent": True, + "command": command_mkdir("$(dir $@)"), + }, + { + "silent": True, + "command": command_tsc(["$^"], "$@", {"libs": ["es2020"]}), + }, + ], + }, + definition["apps"] + ) + ) + + + list( + map( + lambda data_app: { + "phony": False, + "targets": [ + _os.path.join( + conf["directories"]["build"], + string_coin( + data_app["destination"], + { + "project": definition["name"], + "app": data_app["name"], + } + ) + ), + ], + "dependencies": ( + [ + _os.path.join(definition["directories"]["lib"], "plankton", "plankton.js"), + ] + + + [ + _os.path.join(conf["directories"]["temp"], "core-unlinked.js"), + _os.path.join(conf["directories"]["temp"], string_coin("app-{{name}}-unlinked.js", {"name": data_app["name"]})), + ] + ), + "actions": ( + [ + { + "silent": True, + "command": command_log(string_coin("app:{{name}} | linking …", {"name": data_app["name"]})) + }, + { + "silent": True, + "command": command_mkdir("$(dir $@)") + }, + { + "silent": True, + "command": string_coin( + "echo '{{shebang}}' > $@", + { + "shebang": data_app["shebang"], + } + ) + }, + { + "silent": True, + "command": command_cat(["$^"], "$@", {"append": True}) + }, + ] + + + ( + [ + { + "silent": True, + "command": "chmod +x $@", + }, + ] + if data_app["executable"] else + [ + ] + ) + ) + }, + definition["apps"] + ) + ) + + + list( + map( + lambda data_app: { + "phony": True, + "targets": [ + string_coin( + "app-{{name}}", + { + "name": data_app["name"], + } + ), + ], + "dependencies": ( + [ + "node_modules", + _os.path.join( + conf["directories"]["build"], + string_coin( + data_app["destination"], + { + "project": definition["name"], + "name": data_app["name"], + } + ) + ), + ] + + + list( + map( + lambda dependency: string_coin( + dependency, + { + "dir_source": definition["directories"]["source"], + "dir_misc": definition["directories"]["misc"], + "dir_temp": conf["directories"]["temp"], + "dir_build": conf["directories"]["build"], + } + ), + data_app["additional_dependencies"] + ) + ) + ), + "actions": [] + }, + definition["apps"] + ) + ) + ) + } + _sys.stdout.write(gnumake_sheet(data)) + + +def main(): + ## args + argument_parser = _argparse.ArgumentParser() + argument_parser.add_argument( + "action", + type = str, + metavar = "", + ) + argument_parser.add_argument( + "-d", + "--definition-path", + type = str, + default = "prj.json", + metavar = "", + ) + argument_parser.add_argument( + "-b", + "--build-directory-pattern", + type = str, + default = "/tmp/{{project}}", + metavar = "", + ) + args = argument_parser.parse_args() + + ## vars + definition = refine_definition(_json.loads(file_read(args.definition_path))) + conf = { + "directories": { + "temp": string_coin("/tmp/{{project}}-temp", {"project": definition["name"]}), + "build": string_coin(args.build_directory_pattern, {"project": definition["name"]}), + } + } + + ## exec + ### todo: refine + if (args.action == "update-plankton"): + action_update_plankton(conf, definition) + elif (args.action == "update-node"): + action_update_node_modules(conf, definition) + elif (args.action == "build"): + action_build(conf, definition) + else: + _sys.stderr.write("unhandled action") + + +main() diff --git a/source/helpers.ts b/source/helpers.ts new file mode 100644 index 0000000..ac57a9e --- /dev/null +++ b/source/helpers.ts @@ -0,0 +1,185 @@ +/* +This file is part of »ivaldi«. + +Copyright 2025 'Fenris Wolf' + +»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 . + */ + +namespace _ivaldi +{ + + /** + */ + export function command_log( + content : string + ) : string + { + return lib_plankton.string.coin( + "echo '-- {{content}}'", + { + "content": content, + } + ) + } + + + /** + */ + export function command_delete( + path : string + ) : string + { + return lib_plankton.string.coin( + "rm --recursive --force {{path}}", + { + "path": path, + } + ) + } + + + /** + */ + export function command_mkdir( + path : string + ) : string + { + return lib_plankton.string.coin( + "mkdir --parents {{path}}", + { + "path": path, + } + ) + } + + + /** + */ + export function command_cat( + source_paths : Array, + target_path : string, + { + "append": append = false, + } + : + { + append ?: boolean; + } + = + { + } + ) : string + { + return lib_plankton.string.coin( + "cat {{source_paths}} {{operator}} {{target_path}}", + { + "source_paths": source_paths.join(" "), + "operator": (append ? ">>" : ">"), + "target_path": target_path, + } + ) + } + + + /** + */ + export function command_copy( + source_path : string, + target_path : string + ) : string + { + return lib_plankton.string.coin( + "cp -r -u {{source_path}} {{target_path}}", + { + "source_path": source_path, + "target_path": target_path, + } + ) + } + + + /** + */ + export function command_ptk_bundle( + target : string, + modules : Array + ) : string + { + return lib_plankton.string.coin( + "ptk bundle {{target}} {{modules}}", + { + "target": target, + "modules": modules.join(" "), + } + ) + } + + + /** + */ + export function command_npm_install( + modules : Array + ) : string + { + return lib_plankton.string.coin( + "npm install {{modules}}", + { + "modules": modules.join(" "), + } + ) + } + + + /** + */ + export function command_tsc( + source_paths : Array, + target_path : string, + { + "declaration": declaration = false, + "libs": libs = [], + } + : + { + declaration ?: boolean; + libs ?: Array; + } + = + { + } + ) : string + { + return lib_plankton.string.coin( + "tsc{{libs}} {{source_paths}} --outFile {{target_path}}{{declaration}}", + { + "libs": ( + libs + .map( + name => lib_plankton.string.coin( + " --lib {{name}}", + {"name": name} + ), + + ) + .join("") + ), + "source_paths": source_paths.join(" "), + "target_path": target_path, + "declaration": (declaration ? " --declaration" : ""), + } + ) + } + +} diff --git a/source/main.ts b/source/main.ts index b53d4ad..3ea503e 100644 --- a/source/main.ts +++ b/source/main.ts @@ -91,6 +91,21 @@ namespace _ivaldi ) ); } + + + /** + */ + function get_app_facade_name( + app : type_app + ) : string + { + return lib_plankton.string.coin( + "app-{{name}}", + { + "name": app.name, + } + ); + } /** @@ -179,7 +194,8 @@ namespace _ivaldi ); // exec - if (args.help) { + if (args.help) + { process.stdout.write( arg_handler.generate_help( { @@ -190,14 +206,13 @@ namespace _ivaldi ) ); } - else { + 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"] ?? @@ -214,6 +229,11 @@ namespace _ivaldi ?? "source" ), + "misc": ( + (data_raw["misc"] ?? {})["misc"] + ?? + "misc" + ), "temp": ( (data_raw["misc"] ?? {})["temp"] ?? @@ -227,7 +247,7 @@ namespace _ivaldi "makefile": ( (data_raw["misc"] ?? {})["makefile"] ?? - "/tmp" + lib_plankton.string.coin("/tmp/", {}) ), }, "commands": { @@ -237,89 +257,122 @@ namespace _ivaldi "tsc" ), }, + "libs": (data_raw["libs"] ?? {}), + "core": data_raw["core"], + "apps": ( + data_raw["apps"].map( + (app_raw : Record) => ( + { + "name": app_raw["name"], + "sources": app_raw["sources"], + "additional_dependencies": (app_raw["additional_dependencies"] ?? []), + "destination": (app_raw["destination"] ?? "{{project}}-{{app}}"), + "shebang": (app_raw["shebang"] ?? null), + "executable": (app_raw["executable"] ?? false), + } + ) + ) + ), + "custom_rules": ( + (data_raw["custom_rules"] ?? []).map( + (custom_rule_raw : Record) => ( + { + "targets": custom_rule_raw["targets"], + "dependencies": custom_rule_raw["dependencies"], + "actions": custom_rule_raw["actions"], + "info": (custom_rule_raw["info"] ?? null), + } + ) + ) + ), }; - switch (args.action) { - default: { + switch (args.action) + { + default: + { return Promise.reject((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; - } + case "fetch": + { + // plankton + { + if (data.libs.plankton === null) + { + // do nothing + } + else + { + const directory : string = lib_plankton.string.coin( + "{{directory}}/plankton", + { + "directory": data.directories.lib, + } + ); + /** + * @todo do not use "execute" for that + */ + await execute( + _ivaldi.command_mkdir(directory), + ); + await execute( + _ivaldi.command_ptk_bundle(data.target, data.libs.plankton), + { + "working_directory": directory, + } + ); + } + } + // node + { + if (data.libs.node === null) + { + // do nothing + } + else + { + const directory : string = lib_plankton.string.coin( + "{{directory}}/node", + { + "directory": data.directories.lib, + } + ); + /** + * @todo do not use "execute" for that + */ + await execute( + _ivaldi.command_mkdir(directory) + ); + await execute( + _ivaldi.command_npm_install(data.libs.node), + { + "working_directory": directory, + } + ); } } return Promise.resolve(undefined); break; } - case "clear": { + case "clear": + { const commands : Array = [ - lib_plankton.string.coin( - "rm -r -f {{directory}}", - { - "directory": data.directories.temp, - } - ), - lib_plankton.string.coin( - "rm -r -f {{directory}}", - { - "directory": data.directories.build, - } - ), + _ivaldi.command_delete(data.directories.temp), + _ivaldi.command_delete(data.directories.build), ]; /** * @todo do not use "execute" for that */ - for (const command of commands) { + for (const command of commands) + { await execute(command); } return Promise.resolve(undefined); break; } - case "build": { - const has_plankton : boolean = data.libs.some(lib => (lib.kind === "plankton")); + case "build": + { + const has_plankton : boolean = ((data.libs.plankton !== null) && (data.libs.plankton.length > 0)); const path_plankton_declaration : string = lib_plankton.string.coin( "{{directory_libs}}/plankton/plankton.d.ts", { @@ -332,165 +385,326 @@ namespace _ivaldi "directory_libs": data.directories.lib, } ); - const paths_sources : Array = ( - 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", + const path_core_declaration : string = lib_plankton.string.coin( + "{{directory_temp}}/core-unlinked.d.ts", { "directory_temp": data.directories.temp, } ); - const path_result : string = lib_plankton.string.coin( - "{{directory_build}}/{{name}}", + const path_core_implementation : string = lib_plankton.string.coin( + "{{directory_temp}}/core-unlinked.js", { - "directory_build": data.directories.build, - "name": data.name, + "directory_temp": data.directories.temp, } ); - const dependencies_compilation : Array = ( - [] - .concat(has_plankton ? [path_plankton_declaration] : []) - .concat(paths_sources) - ); const gnu_make_sheet : lib_plankton.gnu_make.type_sheet = { - "rules": [ + "rules": ( + (new Array>) // 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 …", + .concat( + [ + { + "kind": "meta", + "data": { + "phony": true, + "name": "_root", + "dependencies": data.apps.map(app => get_app_facade_name(app)), + } + }, + ] + ) + // node modules + .concat( + [ + { + "kind": "concrete", + "data": { + "targets": [ + "node_modules" + ], + "dependencies": [], + "actions": [ + _ivaldi.command_log("placing node modules …"), + _ivaldi.command_mkdir( + lib_plankton.string.coin("{{directory}}/node_modules", {"directory": data.directories.build}) + ), + _ivaldi.command_copy( + lib_plankton.string.coin("{{directory}}/node/node_modules/*", {"directory": data.directories.lib}), + lib_plankton.string.coin("{{directory}}/node_modules/", {"directory": data.directories.build}) + ) + ] + } + }, + ] + ) + // custom rules + .concat( + [ + data.custom_rules + .map( + data_custom_rule => ({ + "kind": "concrete", + "data": { + "targets": ( + data_custom_rule.targets + .map( + target => lib_plankton.string.coin( + target, + { + "dir_build": data.directories.build, + } + ) + ) + ), + "dependencies": ( + data_custom_rule.dependencies.map( + dependency => lib_plankton.string.coin( + dependency, + { + "dir_source": data.directories.source, + "dir_misc": data.directories.misc, + } + ), + ) + ), + "actions": ( + (new Array()) + .concat( + (data_custom_rule.info === null) + ? + [] + : + [ + _ivaldi.command_log( + lib_plankton.string.coin( + "{{info}} …", + { + "info": data_custom_rule.info, + } + ), + ) + ] + ) + .concat( + /** + * @todo coin? + */ + data_custom_rule.actions + ) + ), } - ), - // mkdir - lib_plankton.string.coin( - "mkdir -p $(dir $@)", - { + }) + ) + ] + ) + // core + .concat( + [ + [ + { + "kind": "concrete", + "data": { + "targets": [ + path_core_declaration, + path_core_implementation, + ], + "dependencies": ( + (new Array()) + .concat(has_plankton ? [path_plankton_declaration] : []) + .concat( + data.core.map( + path => lib_plankton.string.coin( + "{{directory}}/{{path}}", + { + "directory": data.directories.source, + "path": path, + } + ) + ) + ) + ), + "actions": [ + // log + _ivaldi.command_log("core | compiling …"), + // mkdir + _ivaldi.command_mkdir("$(dir $@)"), + // command + _ivaldi.command_tsc( + ["$^"], + path_core_implementation, + { + "libs": ["es2020"], + "declaration": true, + } + ), + ] } - ), - // 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}}'", + ] + ) + // apps + .concat( + data.apps + .map( + app => { + const paths_sources : Array = ( + app.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}}/app-{{name}}-unlinked.js", { - "message": "linking …", + "directory_temp": data.directories.temp, + "name": app.name, } - ), - // mkdir - lib_plankton.string.coin( - "mkdir -p $(dir $@)", + ); + /* + const path_head : string = lib_plankton.string.coin( + "{{directory_temp}}/head.js", { + "directory_temp": data.directories.temp, } - ), - // command - lib_plankton.string.coin( - "cat $^ > $@", + ); + */ + + const path_result : string = lib_plankton.string.coin( + "{{directory_build}}/{{destination}}", { + "directory_build": data.directories.build, + "destination": lib_plankton.string.coin( + app.destination, + { + "project": data.name, + "app": app.name, + } + ), } - ), - // make executable - lib_plankton.string.coin( - "chmod +x {{path}}", + ); + return [ + // compile { - "path": path_result, - } - ), - ], - }, - }, - ], + "kind": "concrete", + "data": { + "targets": [ + path_unlinked, + ], + "dependencies": ( + (new Array()) + .concat(has_plankton ? [path_plankton_declaration] : []) + .concat([path_core_declaration]) + .concat(paths_sources) + ), + "actions": [ + _ivaldi.command_log(lib_plankton.string.coin("app:{{name}} | compiling …", {"name": app.name})), + _ivaldi.command_mkdir("$(dir $@)"), + _ivaldi.command_tsc( + ["$^"], + "$@", + { + /** + * @todo var + */ + "libs": ["es2020"], + } + ), + ] + } + }, + // link + { + "kind": "concrete", + "data": { + "targets": [ + path_result, + ], + "dependencies": ( + (new Array()) + // .concat([path_head]) + .concat(has_plankton ? [path_plankton_implementation] : []) + .concat([path_unlinked]) + ), + "actions": ( + (new Array()) + .concat( + [ + _ivaldi.command_log(lib_plankton.string.coin("app:{{name}} | linking …", {"name": app.name})), + _ivaldi.command_mkdir("$(dir $@)"), + lib_plankton.string.coin("echo '{{head}}' > {{path}}", {"head": (app.shebang ?? ""), "path": path_result}), + _ivaldi.command_cat(["$^"], "$@", {"append": true}), + ] + ) + // make executable + .concat( + ( + app.executable + ? + [ + /** + * @todo _ivaldi.command_ + */ + "chmod +x $@", + ] + : + [ + ] + ) + ) + ) + }, + }, + // facade + { + "kind": "meta", + "data": { + "phony": true, + "name": get_app_facade_name(app), + "dependencies": ( + (new Array()) + .concat( + [ + /** + * @todo variable + */ + "node_modules", + path_result, + ] + ) + // additional dependencies + .concat( + app.additional_dependencies + .map( + dependency => lib_plankton.string.coin( + dependency, + { + "dir_source": data.directories.source, + "dir_misc": data.directories.misc, + "dir_temp": data.directories.temp, + "dir_build": data.directories.build, + } + ) + ) + ) + ), + }, + }, + ]; + } + ) + ) + .reduce( + (x, y) => x.concat(y), + [] + ) + ), }; const makefile : string = lib_plankton.gnu_make.render_sheet( gnu_make_sheet, @@ -522,7 +736,8 @@ namespace _ivaldi return Promise.resolve(undefined); break; } - case "deploy": { + case "deploy": + { return Promise.reject(new Error("not yet implemented")); break; } diff --git a/source/types.ts b/source/types.ts index bdc4d69..8903068 100644 --- a/source/types.ts +++ b/source/types.ts @@ -37,6 +37,18 @@ namespace _ivaldi }; } ); + + + /** + */ + export type type_app = { + name : string; + sources : Array; + additional_dependencies : Array; + destination : string; + shebang : (null | string); + executable : boolean; + }; /** @@ -44,12 +56,11 @@ namespace _ivaldi export type type_data = { version : string; name : string; - libs : Array; - sources : Array; target : ("node" | "web"); directories : { lib : string; source : string; + misc : string; temp : string; build : string; makefile : string; @@ -57,6 +68,23 @@ namespace _ivaldi commands : { typescript_compile : string; }; + libs : { + plankton : (null | Array); + node : (null | Array); + }; + core : Array; + apps : Array< + type_app + >; + custom_rules: Array< + { + targets : Array; + dependencies : Array; + actions : Array; + info : (null | string); + + } + >; }; } diff --git a/tools/makefile b/tools/makefile index 47bf629..60b1c0c 100644 --- a/tools/makefile +++ b/tools/makefile @@ -17,6 +17,7 @@ _default: _root temp/ivaldi-unlinked.js: \ libs/plankton/plankton.d.ts \ source/types.ts \ + source/helpers.ts \ source/main.ts @ ${cmd_log} "compiling …" @ ${cmd_mkdir} temp