diff --git a/package.json b/package.json new file mode 100644 index 0000000..af912ea --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "krad", + "version": "0.0.0", + "private": true, + "type": "module", + "workspaces": [ + "packages/*" + ], + "bin": { + "krad": "./tools/krad.js" + }, + "scripts": { + "test": "node --test" + }, + "engines": { + "node": ">=22" + } +} diff --git a/packages/krad-clang/index.js b/packages/krad-clang/index.js new file mode 100644 index 0000000..3a35ae6 --- /dev/null +++ b/packages/krad-clang/index.js @@ -0,0 +1,13 @@ +import { defineComponent } from "../krad-utils/index.js"; + +export const component = defineComponent({ + name: "krad-clang", + source: { + directory: "cheerp-compiler", + focus: ["llvm/include/llvm/Cheerp", "llvm/lib/CheerpUtils", "llvm/lib/CheerpWriter", "clang/test/Cheerp"], + license: "Apache-2.0 WITH LLVM-exception", + licenseFile: "LICENSE.TXT", + repository: "https://github.com/leaningtech/cheerp-compiler", + revision: "0596bd128750d43569d17d56e443a95066d3461f", + }, +}); diff --git a/packages/krad-clang/package.json b/packages/krad-clang/package.json new file mode 100644 index 0000000..dae9875 --- /dev/null +++ b/packages/krad-clang/package.json @@ -0,0 +1,7 @@ +{ + "name": "krad-clang", + "version": "0.0.0", + "private": true, + "type": "module", + "exports": "./index.js" +} diff --git a/packages/krad-libs/index.js b/packages/krad-libs/index.js new file mode 100644 index 0000000..8699c38 --- /dev/null +++ b/packages/krad-libs/index.js @@ -0,0 +1,14 @@ +import { defineComponent } from "../krad-utils/index.js"; + +export const component = defineComponent({ + name: "krad-libs", + dependencies: ["krad-clang", "krad-utils", "krad-musl"], + source: { + directory: "cheerp-libs", + focus: ["system", "stdlibs", "wasm"], + license: "Apache-2.0 WITH LLVM-exception", + licenseFile: "LICENSE.TXT", + repository: "https://github.com/leaningtech/cheerp-libs", + revision: "a578cfaa28bde51f1ae8b17980e6bf7727333be7", + }, +}); diff --git a/packages/krad-libs/package.json b/packages/krad-libs/package.json new file mode 100644 index 0000000..462ddd1 --- /dev/null +++ b/packages/krad-libs/package.json @@ -0,0 +1,7 @@ +{ + "name": "krad-libs", + "version": "0.0.0", + "private": true, + "type": "module", + "exports": "./index.js" +} diff --git a/packages/krad-musl/index.js b/packages/krad-musl/index.js new file mode 100644 index 0000000..f0a562b --- /dev/null +++ b/packages/krad-musl/index.js @@ -0,0 +1,14 @@ +import { defineComponent } from "../krad-utils/index.js"; + +export const component = defineComponent({ + name: "krad-musl", + dependencies: ["krad-clang", "krad-utils"], + source: { + directory: "cheerp-musl", + focus: ["arch", "include", "src", "tools"], + license: "MIT", + licenseFile: "COPYRIGHT", + repository: "https://github.com/leaningtech/cheerp-musl", + revision: "dd07ec8e7c52f8dc4351bf9b329df3b1d190470a", + }, +}); diff --git a/packages/krad-musl/package.json b/packages/krad-musl/package.json new file mode 100644 index 0000000..81448ed --- /dev/null +++ b/packages/krad-musl/package.json @@ -0,0 +1,7 @@ +{ + "name": "krad-musl", + "version": "0.0.0", + "private": true, + "type": "module", + "exports": "./index.js" +} diff --git a/packages/krad-utils/index.js b/packages/krad-utils/index.js new file mode 100644 index 0000000..d6e49b6 --- /dev/null +++ b/packages/krad-utils/index.js @@ -0,0 +1,76 @@ +const namePattern = /^krad-[a-z][a-z0-9-]*$/; +const revisionPattern = /^[a-f0-9]{40}$/; +const pathPattern = /^[A-Za-z0-9._+-]+(?:\/[A-Za-z0-9._+-]+)*$/; + +const path = (value, field) => { + if (typeof value !== "string" || !pathPattern.test(value) || value.split("/").some((part) => part === "." || part === "..")) { + throw new TypeError(`${field} must be a relative path`); + } + return value; +}; + +export const defineComponent = ({ name, dependencies = [], source }) => { + if (!namePattern.test(name)) throw new TypeError("invalid component name"); + if (!Array.isArray(dependencies) || dependencies.some((dependency) => !namePattern.test(dependency))) { + throw new TypeError(`${name} has invalid dependencies`); + } + if (new Set(dependencies).size !== dependencies.length || dependencies.includes(name)) { + throw new TypeError(`${name} has duplicate or self dependencies`); + } + if (!source || typeof source !== "object") throw new TypeError(`${name} requires a source`); + const repository = new URL(source.repository); + if (repository.protocol !== "https:") throw new TypeError(`${name} repository must use HTTPS`); + if (!revisionPattern.test(source.revision)) throw new TypeError(`${name} requires a full Git revision`); + if (typeof source.license !== "string" || !source.license) throw new TypeError(`${name} requires a source license`); + if (!Array.isArray(source.focus ?? [])) throw new TypeError(`${name} focus must be an array`); + return Object.freeze({ + name, + dependencies: Object.freeze([...dependencies]), + source: Object.freeze({ + directory: path(source.directory, `${name} source directory`), + focus: Object.freeze((source.focus ?? []).map((entry) => path(entry, `${name} focus`))), + license: source.license, + licenseFile: path(source.licenseFile, `${name} license file`), + repository: repository.href.replace(/\/$/, ""), + revision: source.revision, + }), + }); +}; + +export const orderComponents = (components) => { + if (!Array.isArray(components)) throw new TypeError("components must be an array"); + const byName = new Map(); + for (const component of components) { + if (!component?.name || byName.has(component.name)) throw new TypeError("component names must be unique"); + byName.set(component.name, component); + } + const state = new Map(); + const ordered = []; + const visit = (component, chain = []) => { + if (state.get(component.name) === 2) return; + if (state.get(component.name) === 1) throw new Error(`component cycle: ${[...chain, component.name].join(" -> ")}`); + state.set(component.name, 1); + for (const dependency of component.dependencies) { + const required = byName.get(dependency); + if (!required) throw new Error(`${component.name} requires missing ${dependency}`); + visit(required, [...chain, component.name]); + } + state.set(component.name, 2); + ordered.push(component); + }; + for (const component of components) visit(component); + return Object.freeze(ordered); +}; + +export const component = defineComponent({ + name: "krad-utils", + dependencies: ["krad-clang"], + source: { + directory: "cheerp-utils", + focus: ["scripts", "tools"], + license: "Apache-2.0 WITH LLVM-exception", + licenseFile: "LICENSE.TXT", + repository: "https://github.com/leaningtech/cheerp-utils", + revision: "4b95822b860d215d669374be98c1bc4425040e08", + }, +}); diff --git a/packages/krad-utils/package.json b/packages/krad-utils/package.json new file mode 100644 index 0000000..d94fd4c --- /dev/null +++ b/packages/krad-utils/package.json @@ -0,0 +1,7 @@ +{ + "name": "krad-utils", + "version": "0.0.0", + "private": true, + "type": "module", + "exports": "./index.js" +}