diff --git a/.bot_directives/sustainabot.a2ml b/.bot_directives/sustainabot.a2ml index 978c3a77..6f9ba227 100644 --- a/.bot_directives/sustainabot.a2ml +++ b/.bot_directives/sustainabot.a2ml @@ -20,8 +20,6 @@ watch = [ "Cargo.lock", "opsm_ex/mix.exs", "opsm_ex/mix.lock", - "deno.json", - "deno.lock", ".github/workflows/", "guix.scm", "flake.nix", diff --git a/.github/workflows/echidna-validation.yml b/.github/workflows/echidna-validation.yml index 44b7080c..e1f32c37 100644 --- a/.github/workflows/echidna-validation.yml +++ b/.github/workflows/echidna-validation.yml @@ -6,7 +6,6 @@ on: branches: [main] # only post-merge; feature-branch PRs are gated by the pull_request run below paths: - 'services/*/src/**' - - 'cli/**' - 'opsm_ex/native/**' - '.github/workflows/echidna-validation.yml' # No pull_request paths-filter: ECHIDNA Safety Audit is a REQUIRED status check, diff --git a/.machine_readable/6a2/STATE.a2ml b/.machine_readable/6a2/STATE.a2ml index f6530deb..64d458b2 100644 --- a/.machine_readable/6a2/STATE.a2ml +++ b/.machine_readable/6a2/STATE.a2ml @@ -1,11 +1,11 @@ # SPDX-License-Identifier: MPL-2.0 # STATE.a2ml — Project state checkpoint -# Updated 2026-07-09: toolchain single source of truth (opsm.toml [runtime]) +# Updated 2026-07-21: ReScript CLI removed (Elixir escript is the sole CLI) [metadata] project = "odds-and-sods-package-manager" version = "2.0.0" -last-updated = "2026-07-09" +last-updated = "2026-07-21" status = "first-class system active" [project-context] @@ -181,7 +181,23 @@ evidence-file = "READINESS.md" - [runtime] parser hardened: comment lines + inline comments (manager.ex) - Full story: docs/TOOLCHAIN.adoc (incl. bootstrap layering + Idris2/pack route) +[completed-2026-07-21] +- ReScript CLI removed: cli/ + lib/ocaml/ (byte-identical dead duplicate) + deleted, along with rescript.json / deno.json / deno.lock (their sole + purpose was building/running that CLI). The Elixir escript (Opsm.CLI, + mix escript.build) is the sole CLI; it already covered the same + trust-pipeline commands and clients, so no functionality was lost. +- Justfile + contractiles/Justfile rewired off deno/rescript onto the + escript; docs/TOOLCHAIN.adoc updated (Deno's remaining consumer is + opsm_mobile, not the root CLI). +- Scope was owner-approved as CLI-only; opsm_mobile ReScript deferred + (see next-actions). Governance ReScript check therefore stays red on + opsm_mobile/src/*.res until that follow-up lands. + [next-actions] +- NEXT VERSION (owner-marked): remove/migrate the remaining ReScript in + opsm_mobile/ (App/OpsmCommands/Route/RuntimeBridge/TauriFFI .res) — the + wider ReScript-elimination work. Clears the last governance red. - Grade A: requires external user confirmation outside hyperpolymath - guix.scm is a stub (source #f) — grow into real manifest (erlang, elixir, zig, just, nickel, idris2, chez-scheme, gmp) for reproducible bootstrap diff --git a/.machine_readable/contractiles/Justfile b/.machine_readable/contractiles/Justfile index 6152344f..82dac29e 100644 --- a/.machine_readable/contractiles/Justfile +++ b/.machine_readable/contractiles/Justfile @@ -22,15 +22,7 @@ default: # BUILD # ═══════════════════════════════════════════════════════════════════════════════ -# Build ReScript CLI -build-cli: - deno task build - -# Build ReScript CLI in watch mode -build-cli-watch: - deno task build:watch - -# Build Elixir escript (standalone binary) +# Build Elixir escript (standalone binary) — the OPSM CLI build-escript: cd opsm_ex && mix deps.get && mix escript.build @@ -56,11 +48,10 @@ build-services: done # Build everything -build: build-cli build-escript +build: build-escript # Clean all build artifacts clean: - deno task clean cd opsm_ex && mix clean rm -rf opsm_ex/opsm @@ -76,12 +67,8 @@ test *args: test-verbose: cd opsm_ex && mix test --trace -# Run Deno tests -test-cli: - deno task test - # Run all tests -test-all: test test-cli +test-all: test # ═══════════════════════════════════════════════════════════════════════════════ # LINT & FORMAT @@ -91,20 +78,16 @@ test-all: test test-cli fmt-ex: cd opsm_ex && mix format -# Format with Deno -fmt-deno: - deno fmt - -# Lint ReScript output with Deno +# Lint Elixir code (strict compile — warnings are errors) lint: - deno lint cli/*.res.js cli/clients/*.res.js + cd opsm_ex && mix deps.get && mix compile --warnings-as-errors # Check Elixir format fmt-check: cd opsm_ex && mix format --check-formatted # Format all code -fmt: fmt-ex fmt-deno +fmt: fmt-ex # ═══════════════════════════════════════════════════════════════════════════════ # RUN @@ -114,10 +97,6 @@ fmt: fmt-ex fmt-deno opsm *args: cd opsm_ex && ./opsm {{args}} -# Run OPSM via Deno (ReScript CLI — trust pipeline) -opsm-cli *args: - deno task opsm -- {{args}} - # Run OPSM Elixir in dev mode with IEx repl: cd opsm_ex && iex -S mix @@ -156,7 +135,6 @@ deps-ex: # Install all dependencies deps: deps-ex - @echo "Deno deps auto-resolved via import maps" # Audit Elixir dependencies deps-audit: @@ -207,13 +185,12 @@ ci: deps build test lint fmt-check loc: @echo "=== OPSM Lines of Code ===" @echo -n "Elixir: " && find opsm_ex/lib -name "*.ex" | xargs wc -l 2>/dev/null | tail -1 - @echo -n "ReScript: " && find cli -name "*.res" | xargs wc -l 2>/dev/null | tail -1 @echo -n "Rust (services): " && find services -name "*.rs" | xargs wc -l 2>/dev/null | tail -1 @echo -n "Rust (mobile): " && find opsm_mobile -name "*.rs" | xargs wc -l 2>/dev/null | tail -1 # Show TODO comments todos: - @grep -rn "TODO\|FIXME" --include="*.ex" --include="*.res" --include="*.rs" . 2>/dev/null | grep -v node_modules | grep -v _build | grep -v target || echo "No TODOs" + @grep -rn "TODO\|FIXME" --include="*.ex" --include="*.rs" . 2>/dev/null | grep -v node_modules | grep -v _build | grep -v target || echo "No TODOs" # Show git status status: @@ -234,7 +211,7 @@ doctor: @command -v just >/dev/null 2>&1 && echo " [OK] just" || echo " [FAIL] just not found" @command -v git >/dev/null 2>&1 && echo " [OK] git" || echo " [FAIL] git not found" @echo "Checking for hardcoded paths..." - @grep -rn '$HOME\|$ECLIPSE_DIR' --include='*.rs' --include='*.ex' --include='*.res' --include='*.gleam' --include='*.sh' . 2>/dev/null | head -5 || echo " [OK] No hardcoded paths" + @grep -rn '$HOME\|$ECLIPSE_DIR' --include='*.rs' --include='*.ex' --include='*.gleam' --include='*.sh' . 2>/dev/null | head -5 || echo " [OK] No hardcoded paths" @echo "Diagnostics complete." # Auto-repair common issues diff --git a/Justfile b/Justfile index 4a827418..66bca0a7 100644 --- a/Justfile +++ b/Justfile @@ -23,15 +23,7 @@ default: # BUILD # ═══════════════════════════════════════════════════════════════════════════════ -# Build ReScript CLI -build-cli: - deno task build - -# Build ReScript CLI in watch mode -build-cli-watch: - deno task build:watch - -# Build Elixir escript (standalone binary) +# Build Elixir escript (standalone binary) — the OPSM CLI build-escript: cd opsm_ex && mix deps.get && mix escript.build @@ -57,11 +49,10 @@ build-services: done # Build everything -build: build-cli build-escript +build: build-escript # Clean all build artifacts clean: - deno task clean cd opsm_ex && mix clean rm -rf opsm_ex/opsm @@ -77,12 +68,8 @@ test *args: test-verbose: cd opsm_ex && mix test --trace -# Run Deno tests -test-cli: - deno task test - # Run all tests -test-all: test test-cli +test-all: test # ═══════════════════════════════════════════════════════════════════════════════ # LINT & FORMAT @@ -92,20 +79,16 @@ test-all: test test-cli fmt-ex: cd opsm_ex && mix format -# Format with Deno -fmt-deno: - deno fmt - -# Lint ReScript output with Deno +# Lint Elixir code (strict compile — warnings are errors) lint: - deno lint cli/*.res.js cli/clients/*.res.js + cd opsm_ex && mix deps.get && mix compile --warnings-as-errors # Check Elixir format fmt-check: cd opsm_ex && mix format --check-formatted # Format all code -fmt: fmt-ex fmt-deno +fmt: fmt-ex # ═══════════════════════════════════════════════════════════════════════════════ # TOOLCHAIN (single source of truth: opsm.toml [runtime] — docs/TOOLCHAIN.adoc) @@ -123,14 +106,10 @@ toolchain-check: # RUN # ═══════════════════════════════════════════════════════════════════════════════ -# Run OPSM via escript (primary CLI) +# Run OPSM via escript (the CLI) opsm *args: cd opsm_ex && ./opsm {{args}} -# Run OPSM via Deno (ReScript CLI — trust pipeline) -opsm-cli *args: - deno task opsm -- {{args}} - # Run OPSM Elixir in dev mode with IEx repl: cd opsm_ex && iex -S mix @@ -169,7 +148,6 @@ deps-ex: # Install all dependencies deps: deps-ex - @echo "Deno deps auto-resolved via import maps" # Audit Elixir dependencies deps-audit: @@ -220,13 +198,11 @@ ci: deps build test lint fmt-check loc: @echo "=== OPSM Lines of Code ===" @echo -n "Elixir: " && find opsm_ex/lib -name "*.ex" | xargs wc -l 2>/dev/null | tail -1 - @echo -n "ReScript: " && find cli -name "*.res" | xargs wc -l 2>/dev/null | tail -1 @echo -n "Rust (services): " && find services -name "*.rs" | xargs wc -l 2>/dev/null | tail -1 - @echo -n "Rust (mobile): " && find opsm_mobile -name "*.rs" | xargs wc -l 2>/dev/null | tail -1 # Show TODO comments todos: - @grep -rn "TODO\|FIXME" --include="*.ex" --include="*.res" --include="*.rs" . 2>/dev/null | grep -v node_modules | grep -v _build | grep -v target || echo "No TODOs" + @grep -rn "TODO\|FIXME" --include="*.ex" --include="*.rs" . 2>/dev/null | grep -v node_modules | grep -v _build | grep -v target || echo "No TODOs" # Show git status status: @@ -247,7 +223,7 @@ doctor: @command -v just >/dev/null 2>&1 && echo " [OK] just" || echo " [FAIL] just not found" @command -v git >/dev/null 2>&1 && echo " [OK] git" || echo " [FAIL] git not found" @echo "Checking for hardcoded paths..." - @grep -rn '$HOME\|$ECLIPSE_DIR' --include='*.rs' --include='*.ex' --include='*.res' --include='*.gleam' --include='*.sh' . 2>/dev/null | head -5 || echo " [OK] No hardcoded paths" + @grep -rn '$HOME\|$ECLIPSE_DIR' --include='*.rs' --include='*.ex' --include='*.gleam' --include='*.sh' . 2>/dev/null | head -5 || echo " [OK] No hardcoded paths" @echo "Diagnostics complete." # Auto-repair common issues diff --git a/cli/Config.res b/cli/Config.res deleted file mode 100644 index 344ed288..00000000 --- a/cli/Config.res +++ /dev/null @@ -1,242 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -@@warning("-44-45") -open Types - -// ============================================================================= -// Deno FFI Bindings -// ============================================================================= - -module Deno = { - module Env = { - @val @scope(("Deno", "env")) external get: string => option = "get" - } - - @val @scope("Deno") external readTextFile: string => promise = "readTextFile" - - type fileInfo - @val @scope("Deno") external stat: string => promise = "stat" -} - -// ============================================================================= -// TOML FFI Bindings -// ============================================================================= - -module Toml = { - @module("@std/toml") external parse: string => Dict.t = "parse" -} - -// ============================================================================= -// Path FFI Bindings -// ============================================================================= - -module Path = { - @module("@std/path") @variadic external join: array => string = "join" -} - -// ============================================================================= -// Proven FFI Bindings -// ============================================================================= - -module Proven = { - module SafeUrl = { - type parseResult = {ok: bool, error: option} - - @module("@proven/mod.ts") @scope("SafeUrl") - external parse: string => parseResult = "parse" - } -} - -// ============================================================================= -// Default Configuration -// ============================================================================= - -let defaultHttpConfig: httpConfig = { - timeoutMs: 3000, - retries: 2, - backoffMs: 200, -} - -let defaultServiceConfig = (port: int): serviceConfig => { - baseUrl: `http://127.0.0.1:${port->Int.toString}`, - token: None, -} - -let exampleConfig = (): opsmConfig => { - http: defaultHttpConfig, - checkyMonkey: defaultServiceConfig(7002), - palimpsestLicense: defaultServiceConfig(7003), - oikos: defaultServiceConfig(7005), -} - -// ============================================================================= -// URL Validation -// ============================================================================= - -let validateServiceUrl = (url: string, serviceName: string): result => { - let parsed = Proven.SafeUrl.parse(url) - if parsed.ok { - Ok(url) - } else { - Error(`Invalid URL for ${serviceName}: ${url}`) - } -} - -// ============================================================================= -// Config Parsing Helpers -// ============================================================================= - -let getIntFromJson = (json: JSON.t, default: int): int => { - switch json->JSON.Classify.classify { - | Number(n) => n->Float.toInt - | _ => default - } -} - -let getStringFromJson = (json: JSON.t): option => { - switch json->JSON.Classify.classify { - | String(s) => Some(s) - | _ => None - } -} - -let parseHttpConfig = (raw: Dict.t): httpConfig => { - let httpRaw = switch raw->Dict.get("http") { - | Some(json) => - switch json->JSON.Classify.classify { - | Object(d) => d - | _ => Dict.make() - } - | None => Dict.make() - } - - { - timeoutMs: switch httpRaw->Dict.get("timeout_ms") { - | Some(v) => getIntFromJson(v, defaultHttpConfig.timeoutMs) - | None => defaultHttpConfig.timeoutMs - }, - retries: switch httpRaw->Dict.get("retries") { - | Some(v) => getIntFromJson(v, defaultHttpConfig.retries) - | None => defaultHttpConfig.retries - }, - backoffMs: switch httpRaw->Dict.get("backoff_ms") { - | Some(v) => getIntFromJson(v, defaultHttpConfig.backoffMs) - | None => defaultHttpConfig.backoffMs - }, - } -} - -let parseServiceConfig = (raw: Dict.t, key: string, defaultPort: int): result< - serviceConfig, -> => { - let svcRaw = switch raw->Dict.get(key) { - | Some(json) => - switch json->JSON.Classify.classify { - | Object(d) => d - | _ => Dict.make() - } - | None => Dict.make() - } - - let rawUrl = switch svcRaw->Dict.get("base_url") { - | Some(v) => getStringFromJson(v)->Option.getOr(`http://127.0.0.1:${defaultPort->Int.toString}`) - | None => `http://127.0.0.1:${defaultPort->Int.toString}` - } - - switch validateServiceUrl(rawUrl, key) { - | Ok(url) => - Ok({ - baseUrl: url, - token: switch svcRaw->Dict.get("token") { - | Some(v) => getStringFromJson(v) - | None => None - }, - }) - | Error(e) => Error(e) - } -} - -// ============================================================================= -// File Operations -// ============================================================================= - -let fileExists = async (path: string): bool => { - try { - let _ = await Deno.stat(path) - true - } catch { - | _ => false - } -} - -// ============================================================================= -// Config Loading -// ============================================================================= - -let loadConfigFrom = async (path: string): result => { - try { - let data = await Deno.readTextFile(path) - let raw = Toml.parse(data) - - let http = parseHttpConfig(raw) - - let checkyMonkey = switch parseServiceConfig(raw, "checky_monkey", 7002) { - | Ok(c) => c - | Error(e) => throw(JsError.throwWithMessage(e)) - } - - let palimpsestLicense = switch parseServiceConfig(raw, "palimpsest_license", 7003) { - | Ok(c) => c - | Error(e) => throw(JsError.throwWithMessage(e)) - } - - let oikos = switch parseServiceConfig(raw, "oikos", 7005) { - | Ok(c) => c - | Error(e) => throw(JsError.throwWithMessage(e)) - } - - Ok({ - http, - checkyMonkey, - palimpsestLicense, - oikos, - }) - } catch { - | JsExn(e) => Error(JsExn.message(e)->Option.getOr("Failed to load config")) - } -} - -let loadConfig = async (): result => { - // 1. Check OPSM_CONFIG env var - switch Deno.Env.get("OPSM_CONFIG") { - | Some(envPath) => await loadConfigFrom(envPath) - | None => { - // 2. Check ./opsm.toml - let localPath = "opsm.toml" - let localExists = await fileExists(localPath) - if localExists { - await loadConfigFrom(localPath) - } else { - // 3. Check ~/.config/opsm/opsm.toml - switch Deno.Env.get("HOME") { - | Some(home) => { - let userPath = Path.join([home, ".config", "opsm", "opsm.toml"]) - let userExists = await fileExists(userPath) - if userExists { - await loadConfigFrom(userPath) - } else { - Error("opsm config not found") - } - } - | None => Error("opsm config not found") - } - } - } - } -} - -let loadConfigOrExample = async (): opsmConfig => { - switch await loadConfig() { - | Ok(config) => config - | Error(_) => exampleConfig() - } -} diff --git a/cli/Config.res.js b/cli/Config.res.js deleted file mode 100644 index 6c96bfb8..00000000 --- a/cli/Config.res.js +++ /dev/null @@ -1,248 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -// Generated by ReScript, PLEASE EDIT WITH CARE - -import * as Path from "@std/path"; -import * as Toml from "@std/toml"; -import * as Core__JSON from "@rescript/core/src/Core__JSON.res.js"; -import * as Core__Option from "@rescript/core/src/Core__Option.res.js"; -import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js"; -import * as ModTs from "@proven/mod.ts"; -import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js"; -import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js"; - -let Env = {}; - -let $$Deno = { - Env: Env -}; - -let Toml$1 = {}; - -let Path$1 = {}; - -let SafeUrl = {}; - -let Proven = { - SafeUrl: SafeUrl -}; - -let defaultHttpConfig = { - timeoutMs: 3000, - retries: 2, - backoffMs: 200 -}; - -function defaultServiceConfig(port) { - return { - baseUrl: `http://127.0.0.1:` + port.toString(), - token: undefined - }; -} - -function exampleConfig() { - return { - http: defaultHttpConfig, - checkyMonkey: defaultServiceConfig(7002), - palimpsestLicense: defaultServiceConfig(7003), - oikos: defaultServiceConfig(7005) - }; -} - -function validateServiceUrl(url, serviceName) { - let parsed = ModTs.SafeUrl.parse(url); - if (parsed.ok) { - return { - TAG: "Ok", - _0: url - }; - } else { - return { - TAG: "Error", - _0: `Invalid URL for ` + serviceName + `: ` + url - }; - } -} - -function getIntFromJson(json, $$default) { - let n = Core__JSON.Classify.classify(json); - if (typeof n !== "object" || n.TAG !== "Number") { - return $$default; - } else { - return n._0 | 0; - } -} - -function getStringFromJson(json) { - let s = Core__JSON.Classify.classify(json); - if (typeof s !== "object" || s.TAG !== "String") { - return; - } else { - return s._0; - } -} - -function parseHttpConfig(raw) { - let json = raw["http"]; - let httpRaw; - if (json !== undefined) { - let d = Core__JSON.Classify.classify(json); - httpRaw = typeof d !== "object" ? ({}) : ( - d.TAG === "Object" ? d._0 : ({}) - ); - } else { - httpRaw = {}; - } - let v = httpRaw["timeout_ms"]; - let v$1 = httpRaw["retries"]; - let v$2 = httpRaw["backoff_ms"]; - return { - timeoutMs: v !== undefined ? getIntFromJson(v, 3000) : 3000, - retries: v$1 !== undefined ? getIntFromJson(v$1, 2) : 2, - backoffMs: v$2 !== undefined ? getIntFromJson(v$2, 200) : 200 - }; -} - -function parseServiceConfig(raw, key, defaultPort) { - let json = raw[key]; - let svcRaw; - if (json !== undefined) { - let d = Core__JSON.Classify.classify(json); - svcRaw = typeof d !== "object" ? ({}) : ( - d.TAG === "Object" ? d._0 : ({}) - ); - } else { - svcRaw = {}; - } - let v = svcRaw["base_url"]; - let rawUrl = v !== undefined ? Core__Option.getOr(getStringFromJson(v), `http://127.0.0.1:` + defaultPort.toString()) : `http://127.0.0.1:` + defaultPort.toString(); - let url = validateServiceUrl(rawUrl, key); - if (url.TAG !== "Ok") { - return { - TAG: "Error", - _0: url._0 - }; - } - let v$1 = svcRaw["token"]; - return { - TAG: "Ok", - _0: { - baseUrl: url._0, - token: v$1 !== undefined ? getStringFromJson(v$1) : undefined - } - }; -} - -async function fileExists(path) { - try { - await Deno.stat(path); - return true; - } catch (exn) { - return false; - } -} - -async function loadConfigFrom(path) { - try { - let data = await Deno.readTextFile(path); - let raw = Toml.parse(data); - let http = parseHttpConfig(raw); - let c$1 = parseServiceConfig(raw, "checky_monkey", 7002); - let checkyMonkey; - if (c$1.TAG === "Ok") { - checkyMonkey = c$1._0; - } else { - throw Stdlib_JsError.throwWithMessage(c$1._0); - } - let c$2 = parseServiceConfig(raw, "palimpsest_license", 7003); - let palimpsestLicense; - if (c$2.TAG === "Ok") { - palimpsestLicense = c$2._0; - } else { - throw Stdlib_JsError.throwWithMessage(c$2._0); - } - let c$4 = parseServiceConfig(raw, "oikos", 7005); - let oikos; - if (c$4.TAG === "Ok") { - oikos = c$4._0; - } else { - throw Stdlib_JsError.throwWithMessage(c$4._0); - } - return { - TAG: "Ok", - _0: { - http: http, - checkyMonkey: checkyMonkey, - palimpsestLicense: palimpsestLicense, - oikos: oikos - } - }; - } catch (raw_e) { - let e = Primitive_exceptions.internalToException(raw_e); - if (e.RE_EXN_ID === "JsExn") { - return { - TAG: "Error", - _0: Core__Option.getOr(Stdlib_JsExn.message(e._1), "Failed to load config") - }; - } - throw e; - } -} - -async function loadConfig() { - let envPath = Deno.env.get("OPSM_CONFIG"); - if (envPath !== undefined) { - return await loadConfigFrom(envPath); - } - let localPath = "opsm.toml"; - let localExists = await fileExists(localPath); - if (localExists) { - return await loadConfigFrom(localPath); - } - let home = Deno.env.get("HOME"); - if (home === undefined) { - return { - TAG: "Error", - _0: "opsm config not found" - }; - } - let userPath = Path.join(home, ".config", "opsm", "opsm.toml"); - let userExists = await fileExists(userPath); - if (userExists) { - return await loadConfigFrom(userPath); - } else { - return { - TAG: "Error", - _0: "opsm config not found" - }; - } -} - -async function loadConfigOrExample() { - let config = await loadConfig(); - if (config.TAG === "Ok") { - return config._0; - } else { - return exampleConfig(); - } -} - -export { - $$Deno, - Toml$1 as Toml, - Path$1 as Path, - Proven, - defaultHttpConfig, - defaultServiceConfig, - exampleConfig, - validateServiceUrl, - getIntFromJson, - getStringFromJson, - parseHttpConfig, - parseServiceConfig, - fileExists, - loadConfigFrom, - loadConfig, - loadConfigOrExample, -} -/* @std/path Not a pure module */ diff --git a/cli/Http.res b/cli/Http.res deleted file mode 100644 index 54688e9f..00000000 --- a/cli/Http.res +++ /dev/null @@ -1,202 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -@@warning("-44-45") -open Types - -// ============================================================================= -// Deno FFI Bindings -// ============================================================================= - -module Deno = { - type abortController - type abortSignal - type response - - @new external makeAbortController: unit => abortController = "AbortController" - @get external getSignal: abortController => abortSignal = "signal" - @send external abort: abortController => unit = "abort" - - @val external fetch: (string, {..}) => promise = "fetch" - - @get external responseOk: response => bool = "ok" - @get external responseStatus: response => int = "status" - @get external responseStatusText: response => string = "statusText" - @send external responseText: response => promise = "text" -} - -// ============================================================================= -// Proven FFI Bindings -// ============================================================================= - -module Proven = { - module SafeJson = { - type parseResult<'a> = {ok: bool, value: option<'a>, error: option} - - @module("@proven/mod.ts") @scope("SafeJson") - external parse: string => parseResult = "parse" - } -} - -// ============================================================================= -// Helper Functions -// ============================================================================= - -let sleep = (ms: int): promise => { - Promise.make((resolve, _reject) => { - let _ = setTimeout(() => resolve(), ms) - }) -} - -let buildUrl = (baseUrl: string, path: string): string => { - let trimmedBase = baseUrl->String.replaceRegExp(/\/+$/, "") - `${trimmedBase}${path}` -} - -// ============================================================================= -// POST JSON with retries -// ============================================================================= - -let postJson = async ( - baseUrl: string, - path: string, - token: option, - body: string, - opts: httpOptions, -): result => { - let url = buildUrl(baseUrl, path) - let attempts = opts.retries + 1 - - let headers = Dict.make() - headers->Dict.set("Content-Type", "application/json") - switch token { - | Some(t) => headers->Dict.set("Authorization", `Bearer ${t}`) - | None => () - } - - let lastError = ref("Request failed") - let success = ref(false) - - for attempt in 0 to attempts - 1 { - if !success.contents { - try { - let controller = Deno.makeAbortController() - let timeoutId = setTimeout(() => controller->Deno.abort, opts.timeoutMs) - - let response = await Deno.fetch( - url, - { - "method": "POST", - "headers": Obj.magic(headers), - "body": body, - "signal": controller->Deno.getSignal, - }, - ) - - clearTimeout(timeoutId) - - if response->Deno.responseOk { - success := true - } else { - lastError := - `HTTP error: ${response - ->Deno.responseStatus - ->Int.toString} ${response->Deno.responseStatusText}` - - if attempt + 1 < attempts { - await sleep(opts.backoffMs * (attempt + 1)) - } - } - } catch { - | JsExn(e) => { - lastError := JsExn.message(e)->Option.getOr("Unknown error") - if attempt + 1 < attempts { - await sleep(opts.backoffMs * (attempt + 1)) - } - } - } - } - } - - if success.contents { - Ok() - } else { - Error(lastError.contents) - } -} - -// ============================================================================= -// GET JSON with retries -// ============================================================================= - -let getJson = async ( - baseUrl: string, - path: string, - token: option, - opts: httpOptions, -): result => { - let url = buildUrl(baseUrl, path) - let attempts = opts.retries + 1 - - let headers = Dict.make() - headers->Dict.set("Accept", "application/json") - switch token { - | Some(t) => headers->Dict.set("Authorization", `Bearer ${t}`) - | None => () - } - - let lastError = ref("Request failed") - let result = ref(None) - - for attempt in 0 to attempts - 1 { - if result.contents->Option.isNone { - try { - let controller = Deno.makeAbortController() - let timeoutId = setTimeout(() => controller->Deno.abort, opts.timeoutMs) - - let response = await Deno.fetch( - url, - { - "method": "GET", - "headers": Obj.magic(headers), - "signal": controller->Deno.getSignal, - }, - ) - - clearTimeout(timeoutId) - - if response->Deno.responseOk { - let text = await response->Deno.responseText - let parsed = Proven.SafeJson.parse(text) - if parsed.ok { - switch parsed.value { - | Some(v) => result := Some(Ok(v)) - | None => result := Some(Error("JSON parse returned null")) - } - } else { - result := Some(Error(`JSON parse error: ${parsed.error->Option.getOr("unknown")}`)) - } - } else { - lastError := - `HTTP error: ${response - ->Deno.responseStatus - ->Int.toString} ${response->Deno.responseStatusText}` - - if attempt + 1 < attempts { - await sleep(opts.backoffMs * (attempt + 1)) - } - } - } catch { - | JsExn(e) => { - lastError := JsExn.message(e)->Option.getOr("Unknown error") - if attempt + 1 < attempts { - await sleep(opts.backoffMs * (attempt + 1)) - } - } - } - } - } - - switch result.contents { - | Some(r) => r - | None => Error(lastError.contents) - } -} diff --git a/cli/Http.res.js b/cli/Http.res.js deleted file mode 100644 index 2a818a49..00000000 --- a/cli/Http.res.js +++ /dev/null @@ -1,166 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -// Generated by ReScript, PLEASE EDIT WITH CARE - -import * as Core__Option from "@rescript/core/src/Core__Option.res.js"; -import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js"; -import * as ModTs from "@proven/mod.ts"; -import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js"; - -let $$Deno = {}; - -let SafeJson = {}; - -let Proven = { - SafeJson: SafeJson -}; - -function sleep(ms) { - return new Promise((resolve, _reject) => { - setTimeout(() => resolve(), ms); - }); -} - -function buildUrl(baseUrl, path) { - let trimmedBase = baseUrl.replace(/\/+$/, ""); - return trimmedBase + path; -} - -async function postJson(baseUrl, path, token, body, opts) { - let url = buildUrl(baseUrl, path); - let attempts = opts.retries + 1 | 0; - let headers = {}; - headers["Content-Type"] = "application/json"; - if (token !== undefined) { - headers["Authorization"] = `Bearer ` + token; - } - let lastError = "Request failed"; - let success = false; - for (let attempt = 0; attempt < attempts; ++attempt) { - if (!success) { - try { - let controller = new AbortController(); - let timeoutId = setTimeout(() => { - controller.abort(); - }, opts.timeoutMs); - let response = await fetch(url, { - method: "POST", - headers: headers, - body: body, - signal: controller.signal - }); - clearTimeout(timeoutId); - if (response.ok) { - success = true; - } else { - lastError = `HTTP error: ` + response.status.toString() + ` ` + response.statusText; - if ((attempt + 1 | 0) < attempts) { - await sleep(opts.backoffMs * (attempt + 1 | 0) | 0); - } - } - } catch (raw_e) { - let e = Primitive_exceptions.internalToException(raw_e); - if (e.RE_EXN_ID === "JsExn") { - lastError = Core__Option.getOr(Stdlib_JsExn.message(e._1), "Unknown error"); - if ((attempt + 1 | 0) < attempts) { - await sleep(opts.backoffMs * (attempt + 1 | 0) | 0); - } - } else { - throw e; - } - } - } - } - if (success) { - return { - TAG: "Ok", - _0: undefined - }; - } else { - return { - TAG: "Error", - _0: lastError - }; - } -} - -async function getJson(baseUrl, path, token, opts) { - let url = buildUrl(baseUrl, path); - let attempts = opts.retries + 1 | 0; - let headers = {}; - headers["Accept"] = "application/json"; - if (token !== undefined) { - headers["Authorization"] = `Bearer ` + token; - } - let lastError = "Request failed"; - let result; - for (let attempt = 0; attempt < attempts; ++attempt) { - if (Core__Option.isNone(result)) { - try { - let controller = new AbortController(); - let timeoutId = setTimeout(() => { - controller.abort(); - }, opts.timeoutMs); - let response = await fetch(url, { - method: "GET", - headers: headers, - signal: controller.signal - }); - clearTimeout(timeoutId); - if (response.ok) { - let text = await response.text(); - let parsed = ModTs.SafeJson.parse(text); - if (parsed.ok) { - let v = parsed.value; - result = v !== undefined ? ({ - TAG: "Ok", - _0: v - }) : ({ - TAG: "Error", - _0: "JSON parse returned null" - }); - } else { - result = { - TAG: "Error", - _0: `JSON parse error: ` + Core__Option.getOr(parsed.error, "unknown") - }; - } - } else { - lastError = `HTTP error: ` + response.status.toString() + ` ` + response.statusText; - if ((attempt + 1 | 0) < attempts) { - await sleep(opts.backoffMs * (attempt + 1 | 0) | 0); - } - } - } catch (raw_e) { - let e = Primitive_exceptions.internalToException(raw_e); - if (e.RE_EXN_ID === "JsExn") { - lastError = Core__Option.getOr(Stdlib_JsExn.message(e._1), "Unknown error"); - if ((attempt + 1 | 0) < attempts) { - await sleep(opts.backoffMs * (attempt + 1 | 0) | 0); - } - } else { - throw e; - } - } - } - } - let r = result; - if (r !== undefined) { - return r; - } else { - return { - TAG: "Error", - _0: lastError - }; - } -} - -export { - $$Deno, - Proven, - sleep, - buildUrl, - postJson, - getJson, -} -/* Stdlib_JsExn Not a pure module */ diff --git a/cli/Main.res b/cli/Main.res deleted file mode 100644 index a1f1dee0..00000000 --- a/cli/Main.res +++ /dev/null @@ -1,140 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -@@warning("-44-45") -open Types - -// ============================================================================= -// Deno FFI Bindings -// ============================================================================= - -module Deno = { - @val @scope("Deno") external args: array = "args" - @val @scope("Deno") external exit: int => unit = "exit" -} - -// ============================================================================= -// CLI Help -// ============================================================================= - -let printHelp = () => { - Console.log("opsm - Odds-and-sods Package Manager") - Console.log("") - Console.log("USAGE:") - Console.log(" opsm [options]") - Console.log("") - Console.log("COMMANDS:") - Console.log(" publish Publish a package through the trust pipeline") - Console.log(" audit Audit a package for sustainability and compliance") - Console.log(" status Show service status and configuration") - Console.log(" help Show this help message") - Console.log("") - Console.log("CONFIGURATION:") - Console.log(" Config file search order:") - Console.log(" 1. $OPSM_CONFIG environment variable") - Console.log(" 2. ./opsm.toml (local directory)") - Console.log(" 3. ~/.config/opsm/opsm.toml (user config)") - Console.log("") - Console.log("EXAMPLES:") - Console.log(" opsm publish ./my-package") - Console.log(" opsm audit @scope/package") - Console.log(" opsm status") -} - -let printVersion = () => { - Console.log("opsm 0.1.0") -} - -// ============================================================================= -// Command Parsing -// ============================================================================= - -type command = - | Publish(string) - | Audit(string) - | Status - | Help - | Version - | Unknown(string) - -let parseArgs = (args: array): command => { - switch args->Array.get(0) { - | None => Help - | Some("help") | Some("-h") | Some("--help") => Help - | Some("version") | Some("-v") | Some("--version") => Version - | Some("publish") => - switch args->Array.get(1) { - | Some(path) => Publish(path) - | None => { - Console.error("Error: publish requires a path argument") - Help - } - } - | Some("audit") => - switch args->Array.get(1) { - | Some(pkg) => Audit(pkg) - | None => { - Console.error("Error: audit requires a package argument") - Help - } - } - | Some("status") => Status - | Some(cmd) => Unknown(cmd) - } -} - -// ============================================================================= -// Main Entry Point -// ============================================================================= - -let main = async () => { - let args = Deno.args - let command = parseArgs(args) - - switch command { - | Help => { - printHelp() - Deno.exit(0) - } - | Version => { - printVersion() - Deno.exit(0) - } - | Unknown(cmd) => { - Console.error(`Unknown command: ${cmd}`) - Console.error("Run 'opsm help' for usage information") - Deno.exit(1) - } - | Publish(path) => { - let config = await Config.loadConfigOrExample() - switch await Wiring.runPublish(config, path) { - | Ok(_) => Deno.exit(0) - | Error(e) => { - Console.error(`Publish failed: ${e}`) - Deno.exit(1) - } - } - } - | Audit(pkg) => { - let config = await Config.loadConfigOrExample() - switch await Wiring.runAudit(config, pkg) { - | Ok(_) => Deno.exit(0) - | Error(e) => { - Console.error(`Audit failed: ${e}`) - Deno.exit(1) - } - } - } - | Status => { - let config = await Config.loadConfigOrExample() - switch await Wiring.runStatus(config) { - | Ok(_) => Deno.exit(0) - | Error(e) => { - Console.error(`Status check failed: ${e}`) - Deno.exit(1) - } - } - } - } -} - -// Run main -let _ = main() diff --git a/cli/Main.res.js b/cli/Main.res.js deleted file mode 100644 index e1a67658..00000000 --- a/cli/Main.res.js +++ /dev/null @@ -1,148 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -// Generated by ReScript, PLEASE EDIT WITH CARE - -import * as Config from "./Config.res.js"; -import * as Wiring from "./Wiring.res.js"; - -let $$Deno = {}; - -function printHelp() { - console.log("opsm - Odds-and-sods Package Manager"); - console.log(""); - console.log("USAGE:"); - console.log(" opsm [options]"); - console.log(""); - console.log("COMMANDS:"); - console.log(" publish Publish a package through the trust pipeline"); - console.log(" audit Audit a package for sustainability and compliance"); - console.log(" status Show service status and configuration"); - console.log(" help Show this help message"); - console.log(""); - console.log("CONFIGURATION:"); - console.log(" Config file search order:"); - console.log(" 1. $OPSM_CONFIG environment variable"); - console.log(" 2. ./opsm.toml (local directory)"); - console.log(" 3. ~/.config/opsm/opsm.toml (user config)"); - console.log(""); - console.log("EXAMPLES:"); - console.log(" opsm publish ./my-package"); - console.log(" opsm audit @scope/package"); - console.log(" opsm status"); -} - -function printVersion() { - console.log("opsm 0.1.0"); -} - -function parseArgs(args) { - let cmd = args[0]; - if (cmd === undefined) { - return "Help"; - } - switch (cmd) { - case "audit" : - let pkg = args[1]; - if (pkg !== undefined) { - return { - TAG: "Audit", - _0: pkg - }; - } else { - console.error("Error: audit requires a package argument"); - return "Help"; - } - case "--help" : - case "-h" : - case "help" : - return "Help"; - case "publish" : - let path = args[1]; - if (path !== undefined) { - return { - TAG: "Publish", - _0: path - }; - } else { - console.error("Error: publish requires a path argument"); - return "Help"; - } - case "status" : - return "Status"; - case "--version" : - case "-v" : - case "version" : - return "Version"; - default: - return { - TAG: "Unknown", - _0: cmd - }; - } -} - -async function main() { - let args = Deno.args; - let command = parseArgs(args); - if (typeof command !== "object") { - switch (command) { - case "Status" : - let config = await Config.loadConfigOrExample(); - let e = await Wiring.runStatus(config); - if (e.TAG === "Ok") { - Deno.exit(0); - return; - } - console.error(`Status check failed: ` + e._0); - Deno.exit(1); - return; - case "Help" : - printHelp(); - Deno.exit(0); - return; - case "Version" : - console.log("opsm 0.1.0"); - Deno.exit(0); - return; - } - } else { - switch (command.TAG) { - case "Publish" : - let config$1 = await Config.loadConfigOrExample(); - let e$1 = await Wiring.runPublish(config$1, command._0); - if (e$1.TAG === "Ok") { - Deno.exit(0); - return; - } - console.error(`Publish failed: ` + e$1._0); - Deno.exit(1); - return; - case "Audit" : - let config$2 = await Config.loadConfigOrExample(); - let e$2 = await Wiring.runAudit(config$2, command._0); - if (e$2.TAG === "Ok") { - Deno.exit(0); - return; - } - console.error(`Audit failed: ` + e$2._0); - Deno.exit(1); - return; - case "Unknown" : - console.error(`Unknown command: ` + command._0); - console.error("Run 'opsm help' for usage information"); - Deno.exit(1); - return; - } - } -} - -main(); - -export { - $$Deno, - printHelp, - printVersion, - parseArgs, - main, -} -/* Not a pure module */ diff --git a/cli/Types.res b/cli/Types.res deleted file mode 100644 index 0ee1842f..00000000 --- a/cli/Types.res +++ /dev/null @@ -1,285 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Types.res - Core type definitions for OPSM CLI - -// ============================================================================= -// Result type (compatible with proven) -// ============================================================================= - -type result<'a> = Ok('a) | Error(string) - -// ============================================================================= -// HTTP Configuration -// ============================================================================= - -type httpConfig = { - timeoutMs: int, - retries: int, - backoffMs: int, -} - -type httpOptions = { - timeoutMs: int, - retries: int, - backoffMs: int, -} - -// ============================================================================= -// Service Configuration -// ============================================================================= - -type serviceConfig = { - baseUrl: string, - token: option, -} - -type opsmConfig = { - http: httpConfig, - checkyMonkey: serviceConfig, - palimpsestLicense: serviceConfig, - oikos: serviceConfig, -} - -// ============================================================================= -// CLI Request/Response Types -// ============================================================================= - -type publishRequest = {path: string} - -type auditRequest = {package: string} - -type statusResponse = { - registryHub: string, - federation: string, -} - -// ============================================================================= -// OIKOS - Ecosystem Sustainability Analysis -// ============================================================================= - -type oikosAnalysisRequest = { - repositoryUrl: string, - branch: option, - commitSha: option, -} - -type sustainabilityScores = { - maintainability: int, - documentation: int, - testCoverage: int, - communityHealth: int, - securityPosture: int, - dependencyHealth: int, - releaseMaturity: int, - codeQuality: int, -} - -type riskSeverity = Low | Medium | High | Critical - -type oikosRisk = { - severity: riskSeverity, - category: string, - description: string, - remediation: option, -} - -type oikosAnalysisResponse = { - repositoryUrl: string, - analyzedAt: string, - overallScore: int, - scores: sustainabilityScores, - recommendations: array, - risks: array, -} - -type oikosDiffRequest = { - repositoryUrl: string, - baseRef: string, - headRef: string, -} - -type oikosDiffResponse = { - repositoryUrl: string, - baseRef: string, - headRef: string, - analyzedAt: string, - overallDelta: int, - impactSummary: string, -} - -type serviceStatus = Healthy | Degraded | Unhealthy - -type oikosHealthResponse = { - status: serviceStatus, - version: string, - uptime: int, -} - -// ============================================================================= -// Registry / Package Metadata -// (cicd-hyper-a service removed; publish/federation types deleted) -// ============================================================================= - -type packageMetadata = { - name: string, - version: string, - description: option, - license: string, - repository: option, - authors: array, - keywords: array, - dependencies: Dict.t, - devDependencies: option>, -} - -type attestationType = Sigstore | InToto - -type attestationRef = { - attestationType: attestationType, - uri: string, - digest: string, -} - -type ruleSeverity = RuleError | RuleWarning | RuleInfo - -type rule = { - id: string, - severity: ruleSeverity, - message: string, - check: string, -} - -type ruleset = { - id: string, - name: string, - version: string, - rules: array, - appliesTo: array, -} - -type packageQueryRequest = { - name: string, - version: option, - includeScores: option, -} - -type packageQueryResponse = { - package: packageMetadata, - versions: array, - latestVersion: string, - scores: option, - dependents: int, - downloads: int, -} - -// ============================================================================= -// CHECKY-MONKEY - Code Verification -// ============================================================================= - -type verificationType = - | PropertyTests - | FuzzTesting - | TypeChecking - | FormalVerification - | MutationTesting - -type checkyMonkeyRequest = { - repositoryUrl: string, - commitSha: string, - verificationTypes: array, - timeout: option, -} - -type verificationStatus = Queued | Running | Completed | Failed - -type codeLocation = { - file: string, - line: int, - column: option, - endLine: option, - endColumn: option, -} - -type findingSeverity = FindingLow | FindingMedium | FindingHigh | FindingCritical - -type finding = { - severity: findingSeverity, - category: string, - location: option, - message: string, - suggestion: option, -} - -type verificationResult = { - verificationType: verificationType, - passed: bool, - coverage: option, - findings: array, - duration: int, -} - -type checkyMonkeyResponse = { - requestId: string, - status: verificationStatus, - startedAt: option, - completedAt: option, - results: option>, -} - -// ============================================================================= -// PALIMPSEST-LICENSE - License Analysis -// ============================================================================= - -type licenseSource = FileHeader | LicenseFile | Manifest | Inferred - -type detectedLicense = { - spdxId: string, - confidence: float, - locations: array, - source: licenseSource, -} - -type conflictSeverity = ConflictWarning | ConflictError - -type licenseConflict = { - license1: string, - license2: string, - reason: string, - severity: conflictSeverity, -} - -type licenseCompatibility = { - compatible: bool, - targetLicense: option, - conflicts: array, -} - -type obligationType = Notice | SourceDisclosure | SameLicense | PatentGrant - -type licenseObligation = { - license: string, - obligation: string, - obligationType: obligationType, -} - -type licenseRiskSeverity = LicenseRiskLow | LicenseRiskMedium | LicenseRiskHigh - -type licenseRisk = { - severity: licenseRiskSeverity, - license: string, - risk: string, - recommendation: string, -} - -type palimpsestRequest = { - artifactPath: string, - includeTransitive: option, - targetLicense: option, -} - -type palimpsestResponse = { - analyzedAt: string, - detectedLicenses: array, - compatibility: licenseCompatibility, - obligations: array, - risks: array, -} diff --git a/cli/Types.res.js b/cli/Types.res.js deleted file mode 100644 index 747d24dc..00000000 --- a/cli/Types.res.js +++ /dev/null @@ -1,4 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -// Generated by ReScript, PLEASE EDIT WITH CARE -/* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ diff --git a/cli/Wiring.res b/cli/Wiring.res deleted file mode 100644 index 49a2b365..00000000 --- a/cli/Wiring.res +++ /dev/null @@ -1,183 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -@@warning("-44-45") -open Types - -// ============================================================================= -// Publish Pipeline -// ============================================================================= - -let runPublish = async (config: opsmConfig, path: string): result => { - let httpOpts: httpOptions = { - timeoutMs: config.http.timeoutMs, - retries: config.http.retries, - backoffMs: config.http.backoffMs, - } - - Console.log(`Publishing package from: ${path}`) - - // Step 1: Check license compliance via palimpsest-license - Console.log(" → Checking license compliance via palimpsest-license...") - let palimpsestClient = Palimpsest.make( - config.palimpsestLicense.baseUrl, - config.palimpsestLicense.token, - httpOpts, - ) - - let licenseResult = await palimpsestClient->Palimpsest.analyze({ - artifactPath: path, - includeTransitive: Some(true), - targetLicense: Some("PMPL-1.0"), - }) - - switch licenseResult { - | Error(e) => Console.log(` ✗ License check failed: ${e}`) - | Ok(resp) => - if resp.compatibility.compatible { - Console.log(" ✓ License compatible") - } else { - Console.log(" ✗ License conflicts detected") - } - } - - // Step 2: Verify with checky-monkey - Console.log(" → Running verification via checky-monkey...") - let checkyClient = CheckyMonkey.make( - config.checkyMonkey.baseUrl, - config.checkyMonkey.token, - httpOpts, - ) - - let verifyResult = await checkyClient->CheckyMonkey.submitVerification({ - repositoryUrl: path, - commitSha: "HEAD", - verificationTypes: [PropertyTests, TypeChecking], - timeout: Some(300), - }) - - switch verifyResult { - | Error(e) => { - Console.log(` ✗ Verification failed: ${e}`) - Error(e) - } - | Ok(resp) => { - Console.log(` ✓ Verification submitted: ${resp.requestId}`) - Ok() - } - } -} - -// ============================================================================= -// Audit Pipeline -// ============================================================================= - -let runAudit = async (config: opsmConfig, packageName: string): result => { - let httpOpts: httpOptions = { - timeoutMs: config.http.timeoutMs, - retries: config.http.retries, - backoffMs: config.http.backoffMs, - } - - Console.log(`Auditing package: ${packageName}`) - - // Step 1: Get sustainability scores from oikos - Console.log(" → Getting sustainability scores from oikos...") - let oikosClient = Oikos.make(config.oikos.baseUrl, config.oikos.token, httpOpts) - - let analysisResult = await oikosClient->Oikos.analyzeRepository({ - repositoryUrl: packageName, - branch: None, - commitSha: None, - }) - - switch analysisResult { - | Error(e) => Console.log(` ✗ Analysis failed: ${e}`) - | Ok(resp) => { - Console.log(` ✓ Overall score: ${resp.overallScore->Int.toString}/100`) - Console.log(` - Maintainability: ${resp.scores.maintainability->Int.toString}`) - Console.log(` - Documentation: ${resp.scores.documentation->Int.toString}`) - Console.log(` - Test Coverage: ${resp.scores.testCoverage->Int.toString}`) - Console.log(` - Security: ${resp.scores.securityPosture->Int.toString}`) - } - } - - // Step 2: Check licenses - Console.log(" → Checking licenses via palimpsest-license...") - let palimpsestClient = Palimpsest.make( - config.palimpsestLicense.baseUrl, - config.palimpsestLicense.token, - httpOpts, - ) - - let licenseResult = await palimpsestClient->Palimpsest.analyze({ - artifactPath: packageName, - includeTransitive: Some(true), - targetLicense: None, - }) - - switch licenseResult { - | Error(e) => Console.log(` ✗ License analysis failed: ${e}`) - | Ok(resp) => { - let count = resp.detectedLicenses->Array.length - Console.log(` ✓ Found ${count->Int.toString} licenses`) - } - } - - Ok() -} - -// ============================================================================= -// Status Check -// ============================================================================= - -let runStatus = async (config: opsmConfig): result => { - let httpOpts: httpOptions = { - timeoutMs: config.http.timeoutMs, - retries: config.http.retries, - backoffMs: config.http.backoffMs, - } - - Console.log("OPSM Service Status") - Console.log("==================") - - // Check oikos - let oikosClient = Oikos.make(config.oikos.baseUrl, config.oikos.token, httpOpts) - let oikosHealth = await oikosClient->Oikos.health - switch oikosHealth { - | Ok(h) => - let status = switch h.status { - | Healthy => "✓ healthy" - | Degraded => "⚠ degraded" - | Unhealthy => "✗ unhealthy" - } - Console.log(`oikos: ${status} (${h.version})`) - | Error(_) => Console.log("oikos: ✗ unreachable") - } - - // Check checky-monkey - let checkyClient = CheckyMonkey.make( - config.checkyMonkey.baseUrl, - config.checkyMonkey.token, - httpOpts, - ) - let checkyHealth = await checkyClient->CheckyMonkey.health - switch checkyHealth { - | Ok(h) => - let status = switch h.status { - | Healthy => "✓ healthy" - | Degraded => "⚠ degraded" - | Unhealthy => "✗ unhealthy" - } - Console.log(`checky-monkey: ${status} (${h.version})`) - | Error(_) => Console.log("checky-monkey: ✗ unreachable") - } - - // Show config info - Console.log("") - Console.log("Configuration") - Console.log("-------------") - Console.log(`checky-monkey: ${config.checkyMonkey.baseUrl}`) - Console.log(`palimpsest-license: ${config.palimpsestLicense.baseUrl}`) - Console.log(`oikos: ${config.oikos.baseUrl}`) - - Ok() -} diff --git a/cli/Wiring.res.js b/cli/Wiring.res.js deleted file mode 100644 index 2c35ea5d..00000000 --- a/cli/Wiring.res.js +++ /dev/null @@ -1,177 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -// Generated by ReScript, PLEASE EDIT WITH CARE - -import * as Oikos from "./clients/Oikos.res.js"; -import * as Palimpsest from "./clients/Palimpsest.res.js"; -import * as CheckyMonkey from "./clients/CheckyMonkey.res.js"; - -async function runPublish(config, path) { - let httpOpts_timeoutMs = config.http.timeoutMs; - let httpOpts_retries = config.http.retries; - let httpOpts_backoffMs = config.http.backoffMs; - let httpOpts = { - timeoutMs: httpOpts_timeoutMs, - retries: httpOpts_retries, - backoffMs: httpOpts_backoffMs - }; - console.log(`Publishing package from: ` + path); - console.log(" → Checking license compliance via palimpsest-license..."); - let palimpsestClient = Palimpsest.make(config.palimpsestLicense.baseUrl, config.palimpsestLicense.token, httpOpts); - let licenseResult = await Palimpsest.analyze(palimpsestClient, { - artifactPath: path, - includeTransitive: true, - targetLicense: "PMPL-1.0" - }); - if (licenseResult.TAG === "Ok") { - if (licenseResult._0.compatibility.compatible) { - console.log(" ✓ License compatible"); - } else { - console.log(" ✗ License conflicts detected"); - } - } else { - console.log(` ✗ License check failed: ` + licenseResult._0); - } - console.log(" → Running verification via checky-monkey..."); - let checkyClient = CheckyMonkey.make(config.checkyMonkey.baseUrl, config.checkyMonkey.token, httpOpts); - let verifyResult = await CheckyMonkey.submitVerification(checkyClient, { - repositoryUrl: path, - commitSha: "HEAD", - verificationTypes: [ - "PropertyTests", - "TypeChecking" - ], - timeout: 300 - }); - if (verifyResult.TAG === "Ok") { - console.log(` ✓ Verification submitted: ` + verifyResult._0.requestId); - return { - TAG: "Ok", - _0: undefined - }; - } - let e = verifyResult._0; - console.log(` ✗ Verification failed: ` + e); - return { - TAG: "Error", - _0: e - }; -} - -async function runAudit(config, packageName) { - let httpOpts_timeoutMs = config.http.timeoutMs; - let httpOpts_retries = config.http.retries; - let httpOpts_backoffMs = config.http.backoffMs; - let httpOpts = { - timeoutMs: httpOpts_timeoutMs, - retries: httpOpts_retries, - backoffMs: httpOpts_backoffMs - }; - console.log(`Auditing package: ` + packageName); - console.log(" → Getting sustainability scores from oikos..."); - let oikosClient = Oikos.make(config.oikos.baseUrl, config.oikos.token, httpOpts); - let analysisResult = await Oikos.analyzeRepository(oikosClient, { - repositoryUrl: packageName, - branch: undefined, - commitSha: undefined - }); - if (analysisResult.TAG === "Ok") { - let resp = analysisResult._0; - console.log(` ✓ Overall score: ` + resp.overallScore.toString() + `/100`); - console.log(` - Maintainability: ` + resp.scores.maintainability.toString()); - console.log(` - Documentation: ` + resp.scores.documentation.toString()); - console.log(` - Test Coverage: ` + resp.scores.testCoverage.toString()); - console.log(` - Security: ` + resp.scores.securityPosture.toString()); - } else { - console.log(` ✗ Analysis failed: ` + analysisResult._0); - } - console.log(" → Checking licenses via palimpsest-license..."); - let palimpsestClient = Palimpsest.make(config.palimpsestLicense.baseUrl, config.palimpsestLicense.token, httpOpts); - let licenseResult = await Palimpsest.analyze(palimpsestClient, { - artifactPath: packageName, - includeTransitive: true, - targetLicense: undefined - }); - if (licenseResult.TAG === "Ok") { - let count = licenseResult._0.detectedLicenses.length; - console.log(` ✓ Found ` + count.toString() + ` licenses`); - } else { - console.log(` ✗ License analysis failed: ` + licenseResult._0); - } - return { - TAG: "Ok", - _0: undefined - }; -} - -async function runStatus(config) { - let httpOpts_timeoutMs = config.http.timeoutMs; - let httpOpts_retries = config.http.retries; - let httpOpts_backoffMs = config.http.backoffMs; - let httpOpts = { - timeoutMs: httpOpts_timeoutMs, - retries: httpOpts_retries, - backoffMs: httpOpts_backoffMs - }; - console.log("OPSM Service Status"); - console.log("=================="); - let oikosClient = Oikos.make(config.oikos.baseUrl, config.oikos.token, httpOpts); - let oikosHealth = await Oikos.health(oikosClient); - if (oikosHealth.TAG === "Ok") { - let h = oikosHealth._0; - let match = h.status; - let status; - switch (match) { - case "Healthy" : - status = "✓ healthy"; - break; - case "Degraded" : - status = "⚠ degraded"; - break; - case "Unhealthy" : - status = "✗ unhealthy"; - break; - } - console.log(`oikos: ` + status + ` (` + h.version + `)`); - } else { - console.log("oikos: ✗ unreachable"); - } - let checkyClient = CheckyMonkey.make(config.checkyMonkey.baseUrl, config.checkyMonkey.token, httpOpts); - let checkyHealth = await CheckyMonkey.health(checkyClient); - if (checkyHealth.TAG === "Ok") { - let h$1 = checkyHealth._0; - let match$1 = h$1.status; - let status$1; - switch (match$1) { - case "Healthy" : - status$1 = "✓ healthy"; - break; - case "Degraded" : - status$1 = "⚠ degraded"; - break; - case "Unhealthy" : - status$1 = "✗ unhealthy"; - break; - } - console.log(`checky-monkey: ` + status$1 + ` (` + h$1.version + `)`); - } else { - console.log("checky-monkey: ✗ unreachable"); - } - console.log(""); - console.log("Configuration"); - console.log("-------------"); - console.log(`checky-monkey: ` + config.checkyMonkey.baseUrl); - console.log(`palimpsest-license: ` + config.palimpsestLicense.baseUrl); - console.log(`oikos: ` + config.oikos.baseUrl); - return { - TAG: "Ok", - _0: undefined - }; -} - -export { - runPublish, - runAudit, - runStatus, -} -/* Oikos Not a pure module */ diff --git a/cli/clients/CheckyMonkey.res b/cli/clients/CheckyMonkey.res deleted file mode 100644 index 13c248b9..00000000 --- a/cli/clients/CheckyMonkey.res +++ /dev/null @@ -1,164 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -@@warning("-44-45") -open Types - -// ============================================================================= -// Client Type -// ============================================================================= - -type t = { - baseUrl: string, - token: option, - opts: httpOptions, -} - -let make = (baseUrl: string, token: option, opts: httpOptions): t => { - baseUrl, - token, - opts, -} - -// ============================================================================= -// Service Health Type -// ============================================================================= - -type serviceHealth = { - status: serviceStatus, - version: string, - activeJobs: int, - queueLength: int, -} - -// ============================================================================= -// JSON Encoding -// ============================================================================= - -let encodeVerificationType = (t: verificationType): string => { - switch t { - | PropertyTests => "property-tests" - | FuzzTesting => "fuzz-testing" - | TypeChecking => "type-checking" - | FormalVerification => "formal-verification" - | MutationTesting => "mutation-testing" - } -} - -let decodeVerificationType = (s: string): verificationType => { - switch s { - | "property-tests" => PropertyTests - | "fuzz-testing" => FuzzTesting - | "type-checking" => TypeChecking - | "formal-verification" => FormalVerification - | "mutation-testing" => MutationTesting - | _ => PropertyTests - } -} - -let encodeVerificationRequest = (req: checkyMonkeyRequest): string => { - let obj = Dict.make() - obj->Dict.set("repositoryUrl", req.repositoryUrl->JSON.Encode.string) - obj->Dict.set("commitSha", req.commitSha->JSON.Encode.string) - obj->Dict.set( - "verificationTypes", - req.verificationTypes - ->Array.map(t => t->encodeVerificationType->JSON.Encode.string) - ->JSON.Encode.array, - ) - switch req.timeout { - | Some(t) => obj->Dict.set("timeout", t->Int.toFloat->JSON.Encode.float) - | None => () - } - obj->JSON.Encode.object->JSON.stringify -} - -// ============================================================================= -// Response Decoding -// ============================================================================= - -let decodeVerificationStatus = (s: string): verificationStatus => { - switch s { - | "queued" => Queued - | "running" => Running - | "completed" => Completed - | "failed" => Failed - | _ => Failed - } -} - -// ============================================================================= -// API Methods -// ============================================================================= - -let submitVerification = async (client: t, request: checkyMonkeyRequest): result< - checkyMonkeyResponse, -> => { - let body = encodeVerificationRequest(request) - - switch await Http.postJson(client.baseUrl, "/verify", client.token, body, client.opts) { - | Error(e) => Error(e) - | Ok(_) => { - let sha = request.commitSha->encodeURIComponent - switch await Http.getJson( - client.baseUrl, - `/verify/status/${sha}`, - client.token, - client.opts, - ) { - | Error(e) => Error(e) - | Ok(_json) => - Ok({ - requestId: sha, - status: Queued, - startedAt: None, - completedAt: None, - results: None, - }) - } - } - } -} - -let getStatus = async (client: t, requestId: string): result => { - let path = `/verify/${requestId->encodeURIComponent}` - - switch await Http.getJson(client.baseUrl, path, client.token, client.opts) { - | Error(e) => Error(e) - | Ok(_json) => - Ok({ - requestId, - status: Queued, - startedAt: None, - completedAt: None, - results: None, - }) - } -} - -let listVerificationTypes = async (client: t): result> => { - switch await Http.getJson(client.baseUrl, "/verification-types", client.token, client.opts) { - | Error(e) => Error(e) - | Ok(_json) => Ok([PropertyTests, FuzzTesting, TypeChecking, FormalVerification, MutationTesting]) - } -} - -let cancel = async (client: t, requestId: string): result => { - let path = `/verify/${requestId->encodeURIComponent}/cancel` - - switch await Http.postJson(client.baseUrl, path, client.token, "{}", client.opts) { - | Error(e) => Error(e) - | Ok(_) => Ok() - } -} - -let health = async (client: t): result => { - switch await Http.getJson(client.baseUrl, "/health", client.token, client.opts) { - | Error(e) => Error(e) - | Ok(_json) => - Ok({ - status: Healthy, - version: "unknown", - activeJobs: 0, - queueLength: 0, - }) - } -} diff --git a/cli/clients/CheckyMonkey.res.js b/cli/clients/CheckyMonkey.res.js deleted file mode 100644 index cbf15d51..00000000 --- a/cli/clients/CheckyMonkey.res.js +++ /dev/null @@ -1,191 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -// Generated by ReScript, PLEASE EDIT WITH CARE - -import * as Http from "../Http.res.js"; - -function make(baseUrl, token, opts) { - return { - baseUrl: baseUrl, - token: token, - opts: opts - }; -} - -function encodeVerificationType(t) { - switch (t) { - case "PropertyTests" : - return "property-tests"; - case "FuzzTesting" : - return "fuzz-testing"; - case "TypeChecking" : - return "type-checking"; - case "FormalVerification" : - return "formal-verification"; - case "MutationTesting" : - return "mutation-testing"; - } -} - -function decodeVerificationType(s) { - switch (s) { - case "formal-verification" : - return "FormalVerification"; - case "fuzz-testing" : - return "FuzzTesting"; - case "mutation-testing" : - return "MutationTesting"; - case "type-checking" : - return "TypeChecking"; - default: - return "PropertyTests"; - } -} - -function encodeVerificationRequest(req) { - let obj = {}; - obj["repositoryUrl"] = req.repositoryUrl; - obj["commitSha"] = req.commitSha; - obj["verificationTypes"] = req.verificationTypes.map(encodeVerificationType); - let t = req.timeout; - if (t !== undefined) { - obj["timeout"] = t; - } - return JSON.stringify(obj); -} - -function decodeVerificationStatus(s) { - switch (s) { - case "completed" : - return "Completed"; - case "queued" : - return "Queued"; - case "running" : - return "Running"; - default: - return "Failed"; - } -} - -async function submitVerification(client, request) { - let body = encodeVerificationRequest(request); - let e = await Http.postJson(client.baseUrl, "/verify", client.token, body, client.opts); - if (e.TAG !== "Ok") { - return { - TAG: "Error", - _0: e._0 - }; - } - let sha = encodeURIComponent(request.commitSha); - let e$1 = await Http.getJson(client.baseUrl, `/verify/status/` + sha, client.token, client.opts); - if (e$1.TAG === "Ok") { - return { - TAG: "Ok", - _0: { - requestId: sha, - status: "Queued", - startedAt: undefined, - completedAt: undefined, - results: undefined - } - }; - } else { - return { - TAG: "Error", - _0: e$1._0 - }; - } -} - -async function getStatus(client, requestId) { - let path = `/verify/` + encodeURIComponent(requestId); - let e = await Http.getJson(client.baseUrl, path, client.token, client.opts); - if (e.TAG === "Ok") { - return { - TAG: "Ok", - _0: { - requestId: requestId, - status: "Queued", - startedAt: undefined, - completedAt: undefined, - results: undefined - } - }; - } else { - return { - TAG: "Error", - _0: e._0 - }; - } -} - -async function listVerificationTypes(client) { - let e = await Http.getJson(client.baseUrl, "/verification-types", client.token, client.opts); - if (e.TAG === "Ok") { - return { - TAG: "Ok", - _0: [ - "PropertyTests", - "FuzzTesting", - "TypeChecking", - "FormalVerification", - "MutationTesting" - ] - }; - } else { - return { - TAG: "Error", - _0: e._0 - }; - } -} - -async function cancel(client, requestId) { - let path = `/verify/` + encodeURIComponent(requestId) + `/cancel`; - let e = await Http.postJson(client.baseUrl, path, client.token, "{}", client.opts); - if (e.TAG === "Ok") { - return { - TAG: "Ok", - _0: undefined - }; - } else { - return { - TAG: "Error", - _0: e._0 - }; - } -} - -async function health(client) { - let e = await Http.getJson(client.baseUrl, "/health", client.token, client.opts); - if (e.TAG === "Ok") { - return { - TAG: "Ok", - _0: { - status: "Healthy", - version: "unknown", - activeJobs: 0, - queueLength: 0 - } - }; - } else { - return { - TAG: "Error", - _0: e._0 - }; - } -} - -export { - make, - encodeVerificationType, - decodeVerificationType, - encodeVerificationRequest, - decodeVerificationStatus, - submitVerification, - getStatus, - listVerificationTypes, - cancel, - health, -} -/* Http Not a pure module */ diff --git a/cli/clients/Oikos.res b/cli/clients/Oikos.res deleted file mode 100644 index ae74e753..00000000 --- a/cli/clients/Oikos.res +++ /dev/null @@ -1,226 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -@@warning("-44-45") -open Types - -// ============================================================================= -// Oikos Client -// ============================================================================= - -type t = { - baseUrl: string, - token: option, - opts: httpOptions, -} - -let make = (baseUrl: string, token: option, opts: httpOptions): t => { - baseUrl, - token, - opts, -} - -// ============================================================================= -// JSON Encoding -// ============================================================================= - -let encodeAnalysisRequest = (req: oikosAnalysisRequest): string => { - let obj = Dict.make() - obj->Dict.set("repositoryUrl", req.repositoryUrl->JSON.Encode.string) - switch req.branch { - | Some(b) => obj->Dict.set("branch", b->JSON.Encode.string) - | None => () - } - switch req.commitSha { - | Some(s) => obj->Dict.set("commitSha", s->JSON.Encode.string) - | None => () - } - obj->JSON.Encode.object->JSON.stringify -} - -let encodeDiffRequest = (req: oikosDiffRequest): string => { - let obj = Dict.make() - obj->Dict.set("repositoryUrl", req.repositoryUrl->JSON.Encode.string) - obj->Dict.set("baseRef", req.baseRef->JSON.Encode.string) - obj->Dict.set("headRef", req.headRef->JSON.Encode.string) - obj->JSON.Encode.object->JSON.stringify -} - -// ============================================================================= -// JSON Decoding Helpers -// ============================================================================= - -let decodeString = (json: JSON.t, key: string): option => { - switch json->JSON.Classify.classify { - | Object(d) => - switch d->Dict.get(key) { - | Some(v) => - switch v->JSON.Classify.classify { - | String(s) => Some(s) - | _ => None - } - | None => None - } - | _ => None - } -} - -let decodeInt = (json: JSON.t, key: string): option => { - switch json->JSON.Classify.classify { - | Object(d) => - switch d->Dict.get(key) { - | Some(v) => - switch v->JSON.Classify.classify { - | Number(n) => Some(n->Float.toInt) - | _ => None - } - | None => None - } - | _ => None - } -} - -let decodeSeverity = (s: string): riskSeverity => { - switch s { - | "low" => Low - | "medium" => Medium - | "high" => High - | "critical" => Critical - | _ => Low - } -} - -let decodeServiceStatus = (s: string): serviceStatus => { - switch s { - | "healthy" => Healthy - | "degraded" => Degraded - | "unhealthy" => Unhealthy - | _ => Unhealthy - } -} - -// ============================================================================= -// Response Decoding -// ============================================================================= - -let decodeScores = (json: JSON.t): sustainabilityScores => { - { - maintainability: decodeInt(json, "maintainability")->Option.getOr(0), - documentation: decodeInt(json, "documentation")->Option.getOr(0), - testCoverage: decodeInt(json, "testCoverage")->Option.getOr(0), - communityHealth: decodeInt(json, "communityHealth")->Option.getOr(0), - securityPosture: decodeInt(json, "securityPosture")->Option.getOr(0), - dependencyHealth: decodeInt(json, "dependencyHealth")->Option.getOr(0), - releaseMaturity: decodeInt(json, "releaseMaturity")->Option.getOr(0), - codeQuality: decodeInt(json, "codeQuality")->Option.getOr(0), - } -} - -let decodeAnalysisResponse = (json: JSON.t): result => { - switch json->JSON.Classify.classify { - | Object(d) => - Ok({ - repositoryUrl: decodeString(json, "repositoryUrl")->Option.getOr(""), - analyzedAt: decodeString(json, "analyzedAt")->Option.getOr(""), - overallScore: decodeInt(json, "overallScore")->Option.getOr(0), - scores: switch d->Dict.get("scores") { - | Some(s) => decodeScores(s) - | None => { - maintainability: 0, - documentation: 0, - testCoverage: 0, - communityHealth: 0, - securityPosture: 0, - dependencyHealth: 0, - releaseMaturity: 0, - codeQuality: 0, - } - }, - recommendations: [], - risks: [], - }) - | _ => Error("Invalid response format") - } -} - -let decodeHealthResponse = (json: JSON.t): result => { - Ok({ - status: decodeString(json, "status")->Option.getOr("unhealthy")->decodeServiceStatus, - version: decodeString(json, "version")->Option.getOr("unknown"), - uptime: decodeInt(json, "uptime")->Option.getOr(0), - }) -} - -// ============================================================================= -// API Methods -// ============================================================================= - -let analyzeRepository = async (client: t, request: oikosAnalysisRequest): result< - oikosAnalysisResponse, -> => { - let body = encodeAnalysisRequest(request) - - switch await Http.postJson( - client.baseUrl, - "/analysis/repository", - client.token, - body, - client.opts, - ) { - | Error(e) => Error(e) - | Ok(_) => { - let encodedUrl = request.repositoryUrl->encodeURIComponent - switch await Http.getJson( - client.baseUrl, - `/analysis/repository/${encodedUrl}`, - client.token, - client.opts, - ) { - | Error(e) => Error(e) - | Ok(json) => decodeAnalysisResponse(json) - } - } - } -} - -let analyzeDiff = async (client: t, request: oikosDiffRequest): result => { - let body = encodeDiffRequest(request) - - switch await Http.postJson(client.baseUrl, "/analysis/diff", client.token, body, client.opts) { - | Error(e) => Error(e) - | Ok(_) => { - let encodedUrl = request.repositoryUrl->encodeURIComponent - switch await Http.getJson( - client.baseUrl, - `/analysis/diff/${encodedUrl}`, - client.token, - client.opts, - ) { - | Error(_) => - // Return minimal response on error - Ok({ - repositoryUrl: request.repositoryUrl, - baseRef: request.baseRef, - headRef: request.headRef, - analyzedAt: "", - overallDelta: 0, - impactSummary: "", - }) - | Ok(_json) => - Ok({ - repositoryUrl: request.repositoryUrl, - baseRef: request.baseRef, - headRef: request.headRef, - analyzedAt: "", - overallDelta: 0, - impactSummary: "", - }) - } - } - } -} - -let health = async (client: t): result => { - switch await Http.getJson(client.baseUrl, "/health", client.token, client.opts) { - | Error(e) => Error(e) - | Ok(json) => decodeHealthResponse(json) - } -} diff --git a/cli/clients/Oikos.res.js b/cli/clients/Oikos.res.js deleted file mode 100644 index 1b9f6778..00000000 --- a/cli/clients/Oikos.res.js +++ /dev/null @@ -1,236 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -// Generated by ReScript, PLEASE EDIT WITH CARE - -import * as Http from "../Http.res.js"; -import * as Core__JSON from "@rescript/core/src/Core__JSON.res.js"; -import * as Core__Option from "@rescript/core/src/Core__Option.res.js"; - -function make(baseUrl, token, opts) { - return { - baseUrl: baseUrl, - token: token, - opts: opts - }; -} - -function encodeAnalysisRequest(req) { - let obj = {}; - obj["repositoryUrl"] = req.repositoryUrl; - let b = req.branch; - if (b !== undefined) { - obj["branch"] = b; - } - let s = req.commitSha; - if (s !== undefined) { - obj["commitSha"] = s; - } - return JSON.stringify(obj); -} - -function encodeDiffRequest(req) { - let obj = {}; - obj["repositoryUrl"] = req.repositoryUrl; - obj["baseRef"] = req.baseRef; - obj["headRef"] = req.headRef; - return JSON.stringify(obj); -} - -function decodeString(json, key) { - let d = Core__JSON.Classify.classify(json); - if (typeof d !== "object") { - return; - } - if (d.TAG !== "Object") { - return; - } - let v = d._0[key]; - if (v === undefined) { - return; - } - let s = Core__JSON.Classify.classify(v); - if (typeof s !== "object" || s.TAG !== "String") { - return; - } else { - return s._0; - } -} - -function decodeInt(json, key) { - let d = Core__JSON.Classify.classify(json); - if (typeof d !== "object") { - return; - } - if (d.TAG !== "Object") { - return; - } - let v = d._0[key]; - if (v === undefined) { - return; - } - let n = Core__JSON.Classify.classify(v); - if (typeof n !== "object" || n.TAG !== "Number") { - return; - } else { - return n._0 | 0; - } -} - -function decodeSeverity(s) { - switch (s) { - case "critical" : - return "Critical"; - case "high" : - return "High"; - case "medium" : - return "Medium"; - default: - return "Low"; - } -} - -function decodeServiceStatus(s) { - switch (s) { - case "degraded" : - return "Degraded"; - case "healthy" : - return "Healthy"; - default: - return "Unhealthy"; - } -} - -function decodeScores(json) { - return { - maintainability: Core__Option.getOr(decodeInt(json, "maintainability"), 0), - documentation: Core__Option.getOr(decodeInt(json, "documentation"), 0), - testCoverage: Core__Option.getOr(decodeInt(json, "testCoverage"), 0), - communityHealth: Core__Option.getOr(decodeInt(json, "communityHealth"), 0), - securityPosture: Core__Option.getOr(decodeInt(json, "securityPosture"), 0), - dependencyHealth: Core__Option.getOr(decodeInt(json, "dependencyHealth"), 0), - releaseMaturity: Core__Option.getOr(decodeInt(json, "releaseMaturity"), 0), - codeQuality: Core__Option.getOr(decodeInt(json, "codeQuality"), 0) - }; -} - -function decodeAnalysisResponse(json) { - let d = Core__JSON.Classify.classify(json); - if (typeof d !== "object") { - return { - TAG: "Error", - _0: "Invalid response format" - }; - } - if (d.TAG !== "Object") { - return { - TAG: "Error", - _0: "Invalid response format" - }; - } - let s = d._0["scores"]; - return { - TAG: "Ok", - _0: { - repositoryUrl: Core__Option.getOr(decodeString(json, "repositoryUrl"), ""), - analyzedAt: Core__Option.getOr(decodeString(json, "analyzedAt"), ""), - overallScore: Core__Option.getOr(decodeInt(json, "overallScore"), 0), - scores: s !== undefined ? decodeScores(s) : ({ - maintainability: 0, - documentation: 0, - testCoverage: 0, - communityHealth: 0, - securityPosture: 0, - dependencyHealth: 0, - releaseMaturity: 0, - codeQuality: 0 - }), - recommendations: [], - risks: [] - } - }; -} - -function decodeHealthResponse(json) { - return { - TAG: "Ok", - _0: { - status: decodeServiceStatus(Core__Option.getOr(decodeString(json, "status"), "unhealthy")), - version: Core__Option.getOr(decodeString(json, "version"), "unknown"), - uptime: Core__Option.getOr(decodeInt(json, "uptime"), 0) - } - }; -} - -async function analyzeRepository(client, request) { - let body = encodeAnalysisRequest(request); - let e = await Http.postJson(client.baseUrl, "/analysis/repository", client.token, body, client.opts); - if (e.TAG !== "Ok") { - return { - TAG: "Error", - _0: e._0 - }; - } - let encodedUrl = encodeURIComponent(request.repositoryUrl); - let e$1 = await Http.getJson(client.baseUrl, `/analysis/repository/` + encodedUrl, client.token, client.opts); - if (e$1.TAG === "Ok") { - return decodeAnalysisResponse(e$1._0); - } else { - return { - TAG: "Error", - _0: e$1._0 - }; - } -} - -async function analyzeDiff(client, request) { - let body = encodeDiffRequest(request); - let e = await Http.postJson(client.baseUrl, "/analysis/diff", client.token, body, client.opts); - if (e.TAG !== "Ok") { - return { - TAG: "Error", - _0: e._0 - }; - } - let encodedUrl = encodeURIComponent(request.repositoryUrl); - await Http.getJson(client.baseUrl, `/analysis/diff/` + encodedUrl, client.token, client.opts); - return { - TAG: "Ok", - _0: { - repositoryUrl: request.repositoryUrl, - baseRef: request.baseRef, - headRef: request.headRef, - analyzedAt: "", - overallDelta: 0, - impactSummary: "" - } - }; -} - -async function health(client) { - let e = await Http.getJson(client.baseUrl, "/health", client.token, client.opts); - if (e.TAG === "Ok") { - return decodeHealthResponse(e._0); - } else { - return { - TAG: "Error", - _0: e._0 - }; - } -} - -export { - make, - encodeAnalysisRequest, - encodeDiffRequest, - decodeString, - decodeInt, - decodeSeverity, - decodeServiceStatus, - decodeScores, - decodeAnalysisResponse, - decodeHealthResponse, - analyzeRepository, - analyzeDiff, - health, -} -/* Http Not a pure module */ diff --git a/cli/clients/Palimpsest.res b/cli/clients/Palimpsest.res deleted file mode 100644 index 2f2d9c1e..00000000 --- a/cli/clients/Palimpsest.res +++ /dev/null @@ -1,268 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -@@warning("-44-45") -open Types - -// ============================================================================= -// HTTP Client Type -// ============================================================================= - -type t = { - baseUrl: string, - token: option, - opts: httpOptions, -} - -let make = (baseUrl: string, token: option, opts: httpOptions): t => { - baseUrl, - token, - opts, -} - -// ============================================================================= -// Additional Types -// ============================================================================= - -type compatibilityDirection = Both | L1ToL2 | L2ToL1 - -type compatibilityResult = { - license1: string, - license2: string, - compatible: bool, - direction: option, - reason: option, -} - -type copyleftStrength = Weak | Strong | Network - -type licenseInfo = { - spdxId: string, - name: string, - url: string, - osiApproved: bool, - fsfLibre: bool, - copyleft: bool, - copyleftStrength: option, - permissions: array, - conditions: array, - limitations: array, -} - -// ============================================================================= -// Deno Command FFI for CLI fallback -// ============================================================================= - -module DenoCommand = { - type commandOptions = { - args: array, - stdout: string, - stderr: string, - } - - type commandOutput = { - code: int, - stdout: Uint8Array.t, - stderr: Uint8Array.t, - } - - type command - - @new @scope("Deno") external makeCommand: (string, commandOptions) => command = "Command" - @send external output: command => promise = "output" -} - -module TextDecoder = { - type t - - @new external make: unit => t = "TextDecoder" - @send external decode: (t, Uint8Array.t) => string = "decode" -} - -// ============================================================================= -// JSON Encoding -// ============================================================================= - -let encodeAnalyzeRequest = (req: palimpsestRequest): string => { - let obj = Dict.make() - obj->Dict.set("artifactPath", req.artifactPath->JSON.Encode.string) - switch req.includeTransitive { - | Some(b) => obj->Dict.set("includeTransitive", b->JSON.Encode.bool) - | None => () - } - switch req.targetLicense { - | Some(l) => obj->Dict.set("targetLicense", l->JSON.Encode.string) - | None => () - } - obj->JSON.Encode.object->JSON.stringify -} - -let encodeCompatibilityRequest = (license1: string, license2: string): string => { - let obj = Dict.make() - obj->Dict.set("license1", license1->JSON.Encode.string) - obj->Dict.set("license2", license2->JSON.Encode.string) - obj->JSON.Encode.object->JSON.stringify -} - -// ============================================================================= -// API Methods -// ============================================================================= - -let analyze = async (client: t, request: palimpsestRequest): result => { - let body = encodeAnalyzeRequest(request) - - switch await Http.postJson(client.baseUrl, "/analyze", client.token, body, client.opts) { - | Error(e) => Error(e) - | Ok(_) => { - let encodedPath = request.artifactPath->encodeURIComponent - switch await Http.getJson( - client.baseUrl, - `/analyze/${encodedPath}`, - client.token, - client.opts, - ) { - | Error(e) => Error(e) - | Ok(_json) => - Ok({ - analyzedAt: "", - detectedLicenses: [], - compatibility: { - compatible: true, - targetLicense: request.targetLicense, - conflicts: [], - }, - obligations: [], - risks: [], - }) - } - } - } -} - -let checkCompatibility = async (client: t, license1: string, license2: string): result< - compatibilityResult, -> => { - let body = encodeCompatibilityRequest(license1, license2) - - switch await Http.postJson(client.baseUrl, "/compatibility", client.token, body, client.opts) { - | Error(e) => Error(e) - | Ok(_) => { - let l1 = license1->encodeURIComponent - let l2 = license2->encodeURIComponent - switch await Http.getJson( - client.baseUrl, - `/compatibility/${l1}/${l2}`, - client.token, - client.opts, - ) { - | Error(e) => Error(e) - | Ok(_json) => - Ok({ - license1, - license2, - compatible: true, - direction: Some(Both), - reason: None, - }) - } - } - } -} - -let getLicenseInfo = async (client: t, spdxId: string): result => { - let path = `/licenses/${spdxId->encodeURIComponent}` - - switch await Http.getJson(client.baseUrl, path, client.token, client.opts) { - | Error(e) => Error(e) - | Ok(_json) => - Ok({ - spdxId, - name: "", - url: "", - osiApproved: false, - fsfLibre: false, - copyleft: false, - copyleftStrength: None, - permissions: [], - conditions: [], - limitations: [], - }) - } -} - -let listLicenses = async (client: t): result> => { - switch await Http.getJson(client.baseUrl, "/licenses", client.token, client.opts) { - | Error(e) => Error(e) - | Ok(_json) => Ok([]) - } -} - -// ============================================================================= -// CLI Fallback -// ============================================================================= - -type cli = {binaryPath: string} - -let makeCli = (~binaryPath: string="palimpsest-license"): cli => { - binaryPath: binaryPath, -} - -let cliAnalyze = async (cli: cli, artifactPath: string, includeTransitive: bool): result< - string, -> => { - let args = ["analyze", artifactPath] - let args = if includeTransitive { - args->Array.concat(["--transitive"]) - } else { - args - } - - try { - let cmd = DenoCommand.makeCommand( - cli.binaryPath, - { - args, - stdout: "piped", - stderr: "piped", - }, - ) - - let output = await cmd->DenoCommand.output - let decoder = TextDecoder.make() - - if output.code != 0 { - let errorText = decoder->TextDecoder.decode(output.stderr) - Error(`palimpsest-license failed: ${errorText}`) - } else { - let outputText = decoder->TextDecoder.decode(output.stdout) - Ok(outputText) - } - } catch { - | JsExn(e) => Error(JsExn.message(e)->Option.getOr("Unknown error")) - } -} - -let cliCheckCompatibility = async (cli: cli, license1: string, license2: string): result => { - let args = ["check", license1, license2] - - try { - let cmd = DenoCommand.makeCommand( - cli.binaryPath, - { - args, - stdout: "piped", - stderr: "piped", - }, - ) - - let output = await cmd->DenoCommand.output - let decoder = TextDecoder.make() - - if output.code != 0 { - let errorText = decoder->TextDecoder.decode(output.stderr) - Error(`compatibility check failed: ${errorText}`) - } else { - Ok(true) - } - } catch { - | JsExn(e) => Error(JsExn.message(e)->Option.getOr("Unknown error")) - } -} diff --git a/cli/clients/Palimpsest.res.js b/cli/clients/Palimpsest.res.js deleted file mode 100644 index 49bd895d..00000000 --- a/cli/clients/Palimpsest.res.js +++ /dev/null @@ -1,246 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -// Generated by ReScript, PLEASE EDIT WITH CARE - -import * as Http from "../Http.res.js"; -import * as Core__Option from "@rescript/core/src/Core__Option.res.js"; -import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js"; -import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js"; - -function make(baseUrl, token, opts) { - return { - baseUrl: baseUrl, - token: token, - opts: opts - }; -} - -let DenoCommand = {}; - -let TextDecoder = {}; - -function encodeAnalyzeRequest(req) { - let obj = {}; - obj["artifactPath"] = req.artifactPath; - let b = req.includeTransitive; - if (b !== undefined) { - obj["includeTransitive"] = b; - } - let l = req.targetLicense; - if (l !== undefined) { - obj["targetLicense"] = l; - } - return JSON.stringify(obj); -} - -function encodeCompatibilityRequest(license1, license2) { - let obj = {}; - obj["license1"] = license1; - obj["license2"] = license2; - return JSON.stringify(obj); -} - -async function analyze(client, request) { - let body = encodeAnalyzeRequest(request); - let e = await Http.postJson(client.baseUrl, "/analyze", client.token, body, client.opts); - if (e.TAG !== "Ok") { - return { - TAG: "Error", - _0: e._0 - }; - } - let encodedPath = encodeURIComponent(request.artifactPath); - let e$1 = await Http.getJson(client.baseUrl, `/analyze/` + encodedPath, client.token, client.opts); - if (e$1.TAG === "Ok") { - return { - TAG: "Ok", - _0: { - analyzedAt: "", - detectedLicenses: [], - compatibility: { - compatible: true, - targetLicense: request.targetLicense, - conflicts: [] - }, - obligations: [], - risks: [] - } - }; - } else { - return { - TAG: "Error", - _0: e$1._0 - }; - } -} - -async function checkCompatibility(client, license1, license2) { - let body = encodeCompatibilityRequest(license1, license2); - let e = await Http.postJson(client.baseUrl, "/compatibility", client.token, body, client.opts); - if (e.TAG !== "Ok") { - return { - TAG: "Error", - _0: e._0 - }; - } - let l1 = encodeURIComponent(license1); - let l2 = encodeURIComponent(license2); - let e$1 = await Http.getJson(client.baseUrl, `/compatibility/` + l1 + `/` + l2, client.token, client.opts); - if (e$1.TAG === "Ok") { - return { - TAG: "Ok", - _0: { - license1: license1, - license2: license2, - compatible: true, - direction: "Both", - reason: undefined - } - }; - } else { - return { - TAG: "Error", - _0: e$1._0 - }; - } -} - -async function getLicenseInfo(client, spdxId) { - let path = `/licenses/` + encodeURIComponent(spdxId); - let e = await Http.getJson(client.baseUrl, path, client.token, client.opts); - if (e.TAG === "Ok") { - return { - TAG: "Ok", - _0: { - spdxId: spdxId, - name: "", - url: "", - osiApproved: false, - fsfLibre: false, - copyleft: false, - copyleftStrength: undefined, - permissions: [], - conditions: [], - limitations: [] - } - }; - } else { - return { - TAG: "Error", - _0: e._0 - }; - } -} - -async function listLicenses(client) { - let e = await Http.getJson(client.baseUrl, "/licenses", client.token, client.opts); - if (e.TAG === "Ok") { - return { - TAG: "Ok", - _0: [] - }; - } else { - return { - TAG: "Error", - _0: e._0 - }; - } -} - -function makeCli(binaryPathOpt) { - let binaryPath = binaryPathOpt !== undefined ? binaryPathOpt : "palimpsest-license"; - return { - binaryPath: binaryPath - }; -} - -async function cliAnalyze(cli, artifactPath, includeTransitive) { - let args = [ - "analyze", - artifactPath - ]; - let args$1 = includeTransitive ? args.concat(["--transitive"]) : args; - try { - let cmd = new (Deno.Command)(cli.binaryPath, { - args: args$1, - stdout: "piped", - stderr: "piped" - }); - let output = await cmd.output(); - let decoder = new TextDecoder(); - if (output.code !== 0) { - let errorText = decoder.decode(output.stderr); - return { - TAG: "Error", - _0: `palimpsest-license failed: ` + errorText - }; - } - let outputText = decoder.decode(output.stdout); - return { - TAG: "Ok", - _0: outputText - }; - } catch (raw_e) { - let e = Primitive_exceptions.internalToException(raw_e); - if (e.RE_EXN_ID === "JsExn") { - return { - TAG: "Error", - _0: Core__Option.getOr(Stdlib_JsExn.message(e._1), "Unknown error") - }; - } - throw e; - } -} - -async function cliCheckCompatibility(cli, license1, license2) { - let args = [ - "check", - license1, - license2 - ]; - try { - let cmd = new (Deno.Command)(cli.binaryPath, { - args: args, - stdout: "piped", - stderr: "piped" - }); - let output = await cmd.output(); - let decoder = new TextDecoder(); - if (output.code === 0) { - return { - TAG: "Ok", - _0: true - }; - } - let errorText = decoder.decode(output.stderr); - return { - TAG: "Error", - _0: `compatibility check failed: ` + errorText - }; - } catch (raw_e) { - let e = Primitive_exceptions.internalToException(raw_e); - if (e.RE_EXN_ID === "JsExn") { - return { - TAG: "Error", - _0: Core__Option.getOr(Stdlib_JsExn.message(e._1), "Unknown error") - }; - } - throw e; - } -} - -export { - make, - DenoCommand, - TextDecoder, - encodeAnalyzeRequest, - encodeCompatibilityRequest, - analyze, - checkCompatibility, - getLicenseInfo, - listLicenses, - makeCli, - cliAnalyze, - cliCheckCompatibility, -} -/* Http Not a pure module */ diff --git a/deno.json b/deno.json deleted file mode 100644 index 27087400..00000000 --- a/deno.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "$schema": "https://deno.land/x/deno/cli/schemas/config-file.v1.json", - "name": "@hyperpolymath/opsm", - "version": "0.1.0", - "license": "PMPL-1.0", - "exports": "./cli/Main.res.js", - "tasks": { - "build": "deno run --allow-read --allow-write --allow-env --allow-sys --allow-run npm:rescript build", - "build:watch": "deno run --allow-read --allow-write --allow-env --allow-sys --allow-run npm:rescript build -w", - "clean": "deno run --allow-read --allow-write --allow-env --allow-sys --allow-run npm:rescript clean", - "dev": "deno task build && deno run --allow-read --allow-env --allow-net --allow-run cli/Main.res.js", - "opsm": "deno run --allow-read --allow-env --allow-net --allow-run cli/Main.res.js", - "test": "deno test --allow-read --allow-env --allow-net", - "lint": "deno lint cli/*.res.js cli/clients/*.res.js", - "fmt": "deno fmt", - "check": "deno run --allow-read --allow-write --allow-env --allow-sys --allow-run npm:rescript build" - }, - "imports": { - "@std/cli": "jsr:@std/cli@^1.0.28", - "@std/fs": "jsr:@std/fs@^1.0.23", - "@std/path": "jsr:@std/path@^1.1.4", - "@std/toml": "jsr:@std/toml@^1.0.11", - "@proven/": "../proven/bindings/deno/", - "@rescript/core": "npm:@rescript/core@^1.6.1", - "rescript": "^12.0.0" - }, - "fmt": { - "useTabs": false, - "lineWidth": 100, - "indentWidth": 2, - "singleQuote": false, - "exclude": [ - "*.res", - "*.resi" - ] - }, - "lint": { - "rules": { - "tags": [ - "recommended" - ], - "exclude": [ - "prefer-const", - "no-case-declarations", - "no-unused-vars" - ] - }, - "exclude": [ - "*.res", - "*.resi" - ] - } -} \ No newline at end of file diff --git a/deno.lock b/deno.lock deleted file mode 100644 index adb869d9..00000000 --- a/deno.lock +++ /dev/null @@ -1,116 +0,0 @@ -{ - "version": "5", - "specifiers": { - "jsr:@std/cli@^1.0.28": "1.0.28", - "jsr:@std/collections@^1.1.3": "1.1.6", - "jsr:@std/fmt@^1.0.9": "1.0.9", - "jsr:@std/fs@^1.0.23": "1.0.23", - "jsr:@std/internal@^1.0.12": "1.0.12", - "jsr:@std/path@^1.1.4": "1.1.4", - "jsr:@std/toml@^1.0.11": "1.0.11", - "npm:@rescript/core@^1.6.1": "1.6.1_rescript@12.2.0", - "npm:rescript@^12.2.0": "12.2.0" - }, - "jsr": { - "@std/cli@1.0.28": { - "integrity": "74ef9b976db59ca6b23a5283469c9072be6276853807a83ec6c7ce412135c70a", - "dependencies": [ - "jsr:@std/fmt", - "jsr:@std/internal" - ] - }, - "@std/collections@1.1.6": { - "integrity": "b458160ce65ea5ad35da05d0a5cbee4b583677c8b443a10d7beb0c4ac63f2baa" - }, - "@std/fmt@1.0.9": { - "integrity": "2487343e8899fb2be5d0e3d35013e54477ada198854e52dd05ed0422eddcabe0" - }, - "@std/fs@1.0.23": { - "integrity": "3ecbae4ce4fee03b180fa710caff36bb5adb66631c46a6460aaad49515565a37", - "dependencies": [ - "jsr:@std/internal", - "jsr:@std/path" - ] - }, - "@std/internal@1.0.12": { - "integrity": "972a634fd5bc34b242024402972cd5143eac68d8dffaca5eaa4dba30ce17b027" - }, - "@std/path@1.1.4": { - "integrity": "1d2d43f39efb1b42f0b1882a25486647cb851481862dc7313390b2bb044314b5", - "dependencies": [ - "jsr:@std/internal" - ] - }, - "@std/toml@1.0.11": { - "integrity": "e084988b872ca4bad6aedfb7350f6eeed0e8ba88e9ee5e1590621c5b5bb8f715", - "dependencies": [ - "jsr:@std/collections" - ] - } - }, - "npm": { - "@rescript/core@1.6.1_rescript@12.2.0": { - "integrity": "sha512-vyb5k90ck+65Fgui+5vCja/mUfzKaK3kOPT4Z6aAJdHLH1eljEi1zKhXroCiCtpNLSWp8k4ulh1bdB5WS0hvqA==", - "dependencies": [ - "rescript" - ] - }, - "@rescript/darwin-arm64@12.2.0": { - "integrity": "sha512-xc3K/J7Ujl1vPiFY2009mRf3kWRlUe/VZyJWprseKxlcEtUQv89ter7r6pY+YFbtYvA/fcaEncL9CVGEdattAg==", - "os": ["darwin"], - "cpu": ["arm64"] - }, - "@rescript/darwin-x64@12.2.0": { - "integrity": "sha512-qqcTvnlSeoKkywLjG7cXfYvKZ1e4Gz2kUKcD6SiqDgCqm8TF+spwlFAiM6sloRUOFsc0bpC/0R0B3yr01FCB1A==", - "os": ["darwin"], - "cpu": ["x64"] - }, - "@rescript/linux-arm64@12.2.0": { - "integrity": "sha512-ODmpG3ji+Nj/8d5yvXkeHlfKkmbw1Q4t1iIjVuNwtmFpz7TiEa7n/sQqoYdE+WzbDX3DoJfmJNbp3Ob7qCUoOg==", - "os": ["linux"], - "cpu": ["arm64"] - }, - "@rescript/linux-x64@12.2.0": { - "integrity": "sha512-2W9Y9/g19Y4F/subl8yV3T8QBG2oRaP+HciNRcBjptyEdw9LmCKH8+rhWO6sp3E+nZLwoE2IAkwH0WKV3wqlxQ==", - "os": ["linux"], - "cpu": ["x64"] - }, - "@rescript/runtime@12.2.0": { - "integrity": "sha512-NwfljDRq1rjFPHUaca1nzFz13xsa9ZGkBkLvMhvVgavJT5+A4rMcLu8XAaVTi/oAhO/tlHf9ZDoOTF1AfyAk9Q==" - }, - "@rescript/win32-x64@12.2.0": { - "integrity": "sha512-fhf8CBj3p1lkIXPeNko3mVTKQfXXm4BoxJtR1xAXxUn43wDpd8Lox4w8/EPBbbW6C/YFQW6H7rtpY+2AKuNaDA==", - "os": ["win32"], - "cpu": ["x64"] - }, - "rescript@12.2.0": { - "integrity": "sha512-1Jf2cmNhyx5Mj2vwZ4XXPcXvNSjGj9D1jPBUcoqIOqRpLPo1ch2Ta/7eWh23xAHWHK5ow7BCDyYFjvZSjyjLzg==", - "dependencies": [ - "@rescript/runtime" - ], - "optionalDependencies": [ - "@rescript/darwin-arm64", - "@rescript/darwin-x64", - "@rescript/linux-arm64", - "@rescript/linux-x64", - "@rescript/win32-x64" - ], - "bin": true - } - }, - "workspace": { - "dependencies": [ - "jsr:@std/cli@^1.0.28", - "jsr:@std/fs@^1.0.23", - "jsr:@std/path@^1.1.4", - "jsr:@std/toml@^1.0.11", - "npm:@rescript/core@^1.6.1" - ], - "packageJson": { - "dependencies": [ - "npm:@rescript/core@^1.6.1", - "npm:rescript@^12.2.0" - ] - } - } -} diff --git a/docs/TOOLCHAIN.adoc b/docs/TOOLCHAIN.adoc index f9e6f83e..c1054494 100644 --- a/docs/TOOLCHAIN.adoc +++ b/docs/TOOLCHAIN.adoc @@ -78,8 +78,9 @@ versions. Any drift is a hard failure. |deno |2.6.10 -|The *sole* JS runtime: drives the ReScript CLI build (`deno.json` tasks), - tests, lint, fmt +|JS runtime for `opsm_mobile/` (its own `deno.json` + ReScript UI). The + root-tree ReScript CLI it used to build was removed — the Elixir escript + (`Opsm.CLI`) is now the CLI. |estate plugin: https://github.com/hyperpolymath/asdf-tool-plugins[`asdf-deno-plugin`] |nickel @@ -98,12 +99,14 @@ versions. Any drift is a hard failure. Tools that need *no* separate pin: -* *ReScript* — locked by `deno.lock` (`npm:rescript ^12.0.0` in `deno.json`). - Deno resolves it; there is no separate toolchain install. +* *ReScript* — no root-tree pin. The estate-authored ReScript CLI (`cli/`, + `lib/ocaml/`) was removed in favour of the Elixir escript; the only + remaining ReScript lives under `opsm_mobile/`, resolved by that + subproject's own `deno.json`. * *Node.js* — *dropped, 2026-07-09.* The old `.tool-versions` pinned - `nodejs 25.6.1`, but nothing uses Node: there is no `package.json`, the - ReScript build runs through Deno's `npm:` specifiers, and no script or - workflow invokes `node`. Deno is the single JS runtime, per estate policy. + `nodejs 25.6.1`, but nothing uses Node: there is no `package.json` and no + script or workflow invokes `node`. Deno is the single JS runtime, per + estate policy. == The bootstrap story (read this before calling OPSM an "asdf replacement") diff --git a/k9iser.toml b/k9iser.toml index 28eabe33..1e622414 100644 --- a/k9iser.toml +++ b/k9iser.toml @@ -8,11 +8,6 @@ name = "odds-and-sods-package-manager" safety_tier = "yard" -[[source]] -path = "deno.json" -type = "deno" -output = "generated/k9iser/deno-manifest.k9" - [[source]] path = "Justfile" type = "justfile" @@ -23,10 +18,6 @@ path = ".github/workflows/hypatia-scan.yml" type = "workflow" output = "generated/k9iser/ci-security.k9" -[[constraint]] -rule = "deno.imports has no npm: specifiers" -severity = "error" - [[constraint]] rule = "workflows includes hypatia-scan" severity = "error" diff --git a/lib/ocaml/CheckyMonkey.cmj b/lib/ocaml/CheckyMonkey.cmj deleted file mode 100644 index 936363d0..00000000 Binary files a/lib/ocaml/CheckyMonkey.cmj and /dev/null differ diff --git a/lib/ocaml/CheckyMonkey.res b/lib/ocaml/CheckyMonkey.res deleted file mode 100644 index 13c248b9..00000000 --- a/lib/ocaml/CheckyMonkey.res +++ /dev/null @@ -1,164 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -@@warning("-44-45") -open Types - -// ============================================================================= -// Client Type -// ============================================================================= - -type t = { - baseUrl: string, - token: option, - opts: httpOptions, -} - -let make = (baseUrl: string, token: option, opts: httpOptions): t => { - baseUrl, - token, - opts, -} - -// ============================================================================= -// Service Health Type -// ============================================================================= - -type serviceHealth = { - status: serviceStatus, - version: string, - activeJobs: int, - queueLength: int, -} - -// ============================================================================= -// JSON Encoding -// ============================================================================= - -let encodeVerificationType = (t: verificationType): string => { - switch t { - | PropertyTests => "property-tests" - | FuzzTesting => "fuzz-testing" - | TypeChecking => "type-checking" - | FormalVerification => "formal-verification" - | MutationTesting => "mutation-testing" - } -} - -let decodeVerificationType = (s: string): verificationType => { - switch s { - | "property-tests" => PropertyTests - | "fuzz-testing" => FuzzTesting - | "type-checking" => TypeChecking - | "formal-verification" => FormalVerification - | "mutation-testing" => MutationTesting - | _ => PropertyTests - } -} - -let encodeVerificationRequest = (req: checkyMonkeyRequest): string => { - let obj = Dict.make() - obj->Dict.set("repositoryUrl", req.repositoryUrl->JSON.Encode.string) - obj->Dict.set("commitSha", req.commitSha->JSON.Encode.string) - obj->Dict.set( - "verificationTypes", - req.verificationTypes - ->Array.map(t => t->encodeVerificationType->JSON.Encode.string) - ->JSON.Encode.array, - ) - switch req.timeout { - | Some(t) => obj->Dict.set("timeout", t->Int.toFloat->JSON.Encode.float) - | None => () - } - obj->JSON.Encode.object->JSON.stringify -} - -// ============================================================================= -// Response Decoding -// ============================================================================= - -let decodeVerificationStatus = (s: string): verificationStatus => { - switch s { - | "queued" => Queued - | "running" => Running - | "completed" => Completed - | "failed" => Failed - | _ => Failed - } -} - -// ============================================================================= -// API Methods -// ============================================================================= - -let submitVerification = async (client: t, request: checkyMonkeyRequest): result< - checkyMonkeyResponse, -> => { - let body = encodeVerificationRequest(request) - - switch await Http.postJson(client.baseUrl, "/verify", client.token, body, client.opts) { - | Error(e) => Error(e) - | Ok(_) => { - let sha = request.commitSha->encodeURIComponent - switch await Http.getJson( - client.baseUrl, - `/verify/status/${sha}`, - client.token, - client.opts, - ) { - | Error(e) => Error(e) - | Ok(_json) => - Ok({ - requestId: sha, - status: Queued, - startedAt: None, - completedAt: None, - results: None, - }) - } - } - } -} - -let getStatus = async (client: t, requestId: string): result => { - let path = `/verify/${requestId->encodeURIComponent}` - - switch await Http.getJson(client.baseUrl, path, client.token, client.opts) { - | Error(e) => Error(e) - | Ok(_json) => - Ok({ - requestId, - status: Queued, - startedAt: None, - completedAt: None, - results: None, - }) - } -} - -let listVerificationTypes = async (client: t): result> => { - switch await Http.getJson(client.baseUrl, "/verification-types", client.token, client.opts) { - | Error(e) => Error(e) - | Ok(_json) => Ok([PropertyTests, FuzzTesting, TypeChecking, FormalVerification, MutationTesting]) - } -} - -let cancel = async (client: t, requestId: string): result => { - let path = `/verify/${requestId->encodeURIComponent}/cancel` - - switch await Http.postJson(client.baseUrl, path, client.token, "{}", client.opts) { - | Error(e) => Error(e) - | Ok(_) => Ok() - } -} - -let health = async (client: t): result => { - switch await Http.getJson(client.baseUrl, "/health", client.token, client.opts) { - | Error(e) => Error(e) - | Ok(_json) => - Ok({ - status: Healthy, - version: "unknown", - activeJobs: 0, - queueLength: 0, - }) - } -} diff --git a/lib/ocaml/Config.cmj b/lib/ocaml/Config.cmj deleted file mode 100644 index cb5dcd0c..00000000 Binary files a/lib/ocaml/Config.cmj and /dev/null differ diff --git a/lib/ocaml/Config.res b/lib/ocaml/Config.res deleted file mode 100644 index 344ed288..00000000 --- a/lib/ocaml/Config.res +++ /dev/null @@ -1,242 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -@@warning("-44-45") -open Types - -// ============================================================================= -// Deno FFI Bindings -// ============================================================================= - -module Deno = { - module Env = { - @val @scope(("Deno", "env")) external get: string => option = "get" - } - - @val @scope("Deno") external readTextFile: string => promise = "readTextFile" - - type fileInfo - @val @scope("Deno") external stat: string => promise = "stat" -} - -// ============================================================================= -// TOML FFI Bindings -// ============================================================================= - -module Toml = { - @module("@std/toml") external parse: string => Dict.t = "parse" -} - -// ============================================================================= -// Path FFI Bindings -// ============================================================================= - -module Path = { - @module("@std/path") @variadic external join: array => string = "join" -} - -// ============================================================================= -// Proven FFI Bindings -// ============================================================================= - -module Proven = { - module SafeUrl = { - type parseResult = {ok: bool, error: option} - - @module("@proven/mod.ts") @scope("SafeUrl") - external parse: string => parseResult = "parse" - } -} - -// ============================================================================= -// Default Configuration -// ============================================================================= - -let defaultHttpConfig: httpConfig = { - timeoutMs: 3000, - retries: 2, - backoffMs: 200, -} - -let defaultServiceConfig = (port: int): serviceConfig => { - baseUrl: `http://127.0.0.1:${port->Int.toString}`, - token: None, -} - -let exampleConfig = (): opsmConfig => { - http: defaultHttpConfig, - checkyMonkey: defaultServiceConfig(7002), - palimpsestLicense: defaultServiceConfig(7003), - oikos: defaultServiceConfig(7005), -} - -// ============================================================================= -// URL Validation -// ============================================================================= - -let validateServiceUrl = (url: string, serviceName: string): result => { - let parsed = Proven.SafeUrl.parse(url) - if parsed.ok { - Ok(url) - } else { - Error(`Invalid URL for ${serviceName}: ${url}`) - } -} - -// ============================================================================= -// Config Parsing Helpers -// ============================================================================= - -let getIntFromJson = (json: JSON.t, default: int): int => { - switch json->JSON.Classify.classify { - | Number(n) => n->Float.toInt - | _ => default - } -} - -let getStringFromJson = (json: JSON.t): option => { - switch json->JSON.Classify.classify { - | String(s) => Some(s) - | _ => None - } -} - -let parseHttpConfig = (raw: Dict.t): httpConfig => { - let httpRaw = switch raw->Dict.get("http") { - | Some(json) => - switch json->JSON.Classify.classify { - | Object(d) => d - | _ => Dict.make() - } - | None => Dict.make() - } - - { - timeoutMs: switch httpRaw->Dict.get("timeout_ms") { - | Some(v) => getIntFromJson(v, defaultHttpConfig.timeoutMs) - | None => defaultHttpConfig.timeoutMs - }, - retries: switch httpRaw->Dict.get("retries") { - | Some(v) => getIntFromJson(v, defaultHttpConfig.retries) - | None => defaultHttpConfig.retries - }, - backoffMs: switch httpRaw->Dict.get("backoff_ms") { - | Some(v) => getIntFromJson(v, defaultHttpConfig.backoffMs) - | None => defaultHttpConfig.backoffMs - }, - } -} - -let parseServiceConfig = (raw: Dict.t, key: string, defaultPort: int): result< - serviceConfig, -> => { - let svcRaw = switch raw->Dict.get(key) { - | Some(json) => - switch json->JSON.Classify.classify { - | Object(d) => d - | _ => Dict.make() - } - | None => Dict.make() - } - - let rawUrl = switch svcRaw->Dict.get("base_url") { - | Some(v) => getStringFromJson(v)->Option.getOr(`http://127.0.0.1:${defaultPort->Int.toString}`) - | None => `http://127.0.0.1:${defaultPort->Int.toString}` - } - - switch validateServiceUrl(rawUrl, key) { - | Ok(url) => - Ok({ - baseUrl: url, - token: switch svcRaw->Dict.get("token") { - | Some(v) => getStringFromJson(v) - | None => None - }, - }) - | Error(e) => Error(e) - } -} - -// ============================================================================= -// File Operations -// ============================================================================= - -let fileExists = async (path: string): bool => { - try { - let _ = await Deno.stat(path) - true - } catch { - | _ => false - } -} - -// ============================================================================= -// Config Loading -// ============================================================================= - -let loadConfigFrom = async (path: string): result => { - try { - let data = await Deno.readTextFile(path) - let raw = Toml.parse(data) - - let http = parseHttpConfig(raw) - - let checkyMonkey = switch parseServiceConfig(raw, "checky_monkey", 7002) { - | Ok(c) => c - | Error(e) => throw(JsError.throwWithMessage(e)) - } - - let palimpsestLicense = switch parseServiceConfig(raw, "palimpsest_license", 7003) { - | Ok(c) => c - | Error(e) => throw(JsError.throwWithMessage(e)) - } - - let oikos = switch parseServiceConfig(raw, "oikos", 7005) { - | Ok(c) => c - | Error(e) => throw(JsError.throwWithMessage(e)) - } - - Ok({ - http, - checkyMonkey, - palimpsestLicense, - oikos, - }) - } catch { - | JsExn(e) => Error(JsExn.message(e)->Option.getOr("Failed to load config")) - } -} - -let loadConfig = async (): result => { - // 1. Check OPSM_CONFIG env var - switch Deno.Env.get("OPSM_CONFIG") { - | Some(envPath) => await loadConfigFrom(envPath) - | None => { - // 2. Check ./opsm.toml - let localPath = "opsm.toml" - let localExists = await fileExists(localPath) - if localExists { - await loadConfigFrom(localPath) - } else { - // 3. Check ~/.config/opsm/opsm.toml - switch Deno.Env.get("HOME") { - | Some(home) => { - let userPath = Path.join([home, ".config", "opsm", "opsm.toml"]) - let userExists = await fileExists(userPath) - if userExists { - await loadConfigFrom(userPath) - } else { - Error("opsm config not found") - } - } - | None => Error("opsm config not found") - } - } - } - } -} - -let loadConfigOrExample = async (): opsmConfig => { - switch await loadConfig() { - | Ok(config) => config - | Error(_) => exampleConfig() - } -} diff --git a/lib/ocaml/Http.cmj b/lib/ocaml/Http.cmj deleted file mode 100644 index 11511e0e..00000000 Binary files a/lib/ocaml/Http.cmj and /dev/null differ diff --git a/lib/ocaml/Http.res b/lib/ocaml/Http.res deleted file mode 100644 index 54688e9f..00000000 --- a/lib/ocaml/Http.res +++ /dev/null @@ -1,202 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -@@warning("-44-45") -open Types - -// ============================================================================= -// Deno FFI Bindings -// ============================================================================= - -module Deno = { - type abortController - type abortSignal - type response - - @new external makeAbortController: unit => abortController = "AbortController" - @get external getSignal: abortController => abortSignal = "signal" - @send external abort: abortController => unit = "abort" - - @val external fetch: (string, {..}) => promise = "fetch" - - @get external responseOk: response => bool = "ok" - @get external responseStatus: response => int = "status" - @get external responseStatusText: response => string = "statusText" - @send external responseText: response => promise = "text" -} - -// ============================================================================= -// Proven FFI Bindings -// ============================================================================= - -module Proven = { - module SafeJson = { - type parseResult<'a> = {ok: bool, value: option<'a>, error: option} - - @module("@proven/mod.ts") @scope("SafeJson") - external parse: string => parseResult = "parse" - } -} - -// ============================================================================= -// Helper Functions -// ============================================================================= - -let sleep = (ms: int): promise => { - Promise.make((resolve, _reject) => { - let _ = setTimeout(() => resolve(), ms) - }) -} - -let buildUrl = (baseUrl: string, path: string): string => { - let trimmedBase = baseUrl->String.replaceRegExp(/\/+$/, "") - `${trimmedBase}${path}` -} - -// ============================================================================= -// POST JSON with retries -// ============================================================================= - -let postJson = async ( - baseUrl: string, - path: string, - token: option, - body: string, - opts: httpOptions, -): result => { - let url = buildUrl(baseUrl, path) - let attempts = opts.retries + 1 - - let headers = Dict.make() - headers->Dict.set("Content-Type", "application/json") - switch token { - | Some(t) => headers->Dict.set("Authorization", `Bearer ${t}`) - | None => () - } - - let lastError = ref("Request failed") - let success = ref(false) - - for attempt in 0 to attempts - 1 { - if !success.contents { - try { - let controller = Deno.makeAbortController() - let timeoutId = setTimeout(() => controller->Deno.abort, opts.timeoutMs) - - let response = await Deno.fetch( - url, - { - "method": "POST", - "headers": Obj.magic(headers), - "body": body, - "signal": controller->Deno.getSignal, - }, - ) - - clearTimeout(timeoutId) - - if response->Deno.responseOk { - success := true - } else { - lastError := - `HTTP error: ${response - ->Deno.responseStatus - ->Int.toString} ${response->Deno.responseStatusText}` - - if attempt + 1 < attempts { - await sleep(opts.backoffMs * (attempt + 1)) - } - } - } catch { - | JsExn(e) => { - lastError := JsExn.message(e)->Option.getOr("Unknown error") - if attempt + 1 < attempts { - await sleep(opts.backoffMs * (attempt + 1)) - } - } - } - } - } - - if success.contents { - Ok() - } else { - Error(lastError.contents) - } -} - -// ============================================================================= -// GET JSON with retries -// ============================================================================= - -let getJson = async ( - baseUrl: string, - path: string, - token: option, - opts: httpOptions, -): result => { - let url = buildUrl(baseUrl, path) - let attempts = opts.retries + 1 - - let headers = Dict.make() - headers->Dict.set("Accept", "application/json") - switch token { - | Some(t) => headers->Dict.set("Authorization", `Bearer ${t}`) - | None => () - } - - let lastError = ref("Request failed") - let result = ref(None) - - for attempt in 0 to attempts - 1 { - if result.contents->Option.isNone { - try { - let controller = Deno.makeAbortController() - let timeoutId = setTimeout(() => controller->Deno.abort, opts.timeoutMs) - - let response = await Deno.fetch( - url, - { - "method": "GET", - "headers": Obj.magic(headers), - "signal": controller->Deno.getSignal, - }, - ) - - clearTimeout(timeoutId) - - if response->Deno.responseOk { - let text = await response->Deno.responseText - let parsed = Proven.SafeJson.parse(text) - if parsed.ok { - switch parsed.value { - | Some(v) => result := Some(Ok(v)) - | None => result := Some(Error("JSON parse returned null")) - } - } else { - result := Some(Error(`JSON parse error: ${parsed.error->Option.getOr("unknown")}`)) - } - } else { - lastError := - `HTTP error: ${response - ->Deno.responseStatus - ->Int.toString} ${response->Deno.responseStatusText}` - - if attempt + 1 < attempts { - await sleep(opts.backoffMs * (attempt + 1)) - } - } - } catch { - | JsExn(e) => { - lastError := JsExn.message(e)->Option.getOr("Unknown error") - if attempt + 1 < attempts { - await sleep(opts.backoffMs * (attempt + 1)) - } - } - } - } - } - - switch result.contents { - | Some(r) => r - | None => Error(lastError.contents) - } -} diff --git a/lib/ocaml/Main.cmj b/lib/ocaml/Main.cmj deleted file mode 100644 index d4c067c3..00000000 Binary files a/lib/ocaml/Main.cmj and /dev/null differ diff --git a/lib/ocaml/Main.res b/lib/ocaml/Main.res deleted file mode 100644 index a1f1dee0..00000000 --- a/lib/ocaml/Main.res +++ /dev/null @@ -1,140 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -@@warning("-44-45") -open Types - -// ============================================================================= -// Deno FFI Bindings -// ============================================================================= - -module Deno = { - @val @scope("Deno") external args: array = "args" - @val @scope("Deno") external exit: int => unit = "exit" -} - -// ============================================================================= -// CLI Help -// ============================================================================= - -let printHelp = () => { - Console.log("opsm - Odds-and-sods Package Manager") - Console.log("") - Console.log("USAGE:") - Console.log(" opsm [options]") - Console.log("") - Console.log("COMMANDS:") - Console.log(" publish Publish a package through the trust pipeline") - Console.log(" audit Audit a package for sustainability and compliance") - Console.log(" status Show service status and configuration") - Console.log(" help Show this help message") - Console.log("") - Console.log("CONFIGURATION:") - Console.log(" Config file search order:") - Console.log(" 1. $OPSM_CONFIG environment variable") - Console.log(" 2. ./opsm.toml (local directory)") - Console.log(" 3. ~/.config/opsm/opsm.toml (user config)") - Console.log("") - Console.log("EXAMPLES:") - Console.log(" opsm publish ./my-package") - Console.log(" opsm audit @scope/package") - Console.log(" opsm status") -} - -let printVersion = () => { - Console.log("opsm 0.1.0") -} - -// ============================================================================= -// Command Parsing -// ============================================================================= - -type command = - | Publish(string) - | Audit(string) - | Status - | Help - | Version - | Unknown(string) - -let parseArgs = (args: array): command => { - switch args->Array.get(0) { - | None => Help - | Some("help") | Some("-h") | Some("--help") => Help - | Some("version") | Some("-v") | Some("--version") => Version - | Some("publish") => - switch args->Array.get(1) { - | Some(path) => Publish(path) - | None => { - Console.error("Error: publish requires a path argument") - Help - } - } - | Some("audit") => - switch args->Array.get(1) { - | Some(pkg) => Audit(pkg) - | None => { - Console.error("Error: audit requires a package argument") - Help - } - } - | Some("status") => Status - | Some(cmd) => Unknown(cmd) - } -} - -// ============================================================================= -// Main Entry Point -// ============================================================================= - -let main = async () => { - let args = Deno.args - let command = parseArgs(args) - - switch command { - | Help => { - printHelp() - Deno.exit(0) - } - | Version => { - printVersion() - Deno.exit(0) - } - | Unknown(cmd) => { - Console.error(`Unknown command: ${cmd}`) - Console.error("Run 'opsm help' for usage information") - Deno.exit(1) - } - | Publish(path) => { - let config = await Config.loadConfigOrExample() - switch await Wiring.runPublish(config, path) { - | Ok(_) => Deno.exit(0) - | Error(e) => { - Console.error(`Publish failed: ${e}`) - Deno.exit(1) - } - } - } - | Audit(pkg) => { - let config = await Config.loadConfigOrExample() - switch await Wiring.runAudit(config, pkg) { - | Ok(_) => Deno.exit(0) - | Error(e) => { - Console.error(`Audit failed: ${e}`) - Deno.exit(1) - } - } - } - | Status => { - let config = await Config.loadConfigOrExample() - switch await Wiring.runStatus(config) { - | Ok(_) => Deno.exit(0) - | Error(e) => { - Console.error(`Status check failed: ${e}`) - Deno.exit(1) - } - } - } - } -} - -// Run main -let _ = main() diff --git a/lib/ocaml/Oikos.cmj b/lib/ocaml/Oikos.cmj deleted file mode 100644 index 1f30123e..00000000 Binary files a/lib/ocaml/Oikos.cmj and /dev/null differ diff --git a/lib/ocaml/Oikos.res b/lib/ocaml/Oikos.res deleted file mode 100644 index ae74e753..00000000 --- a/lib/ocaml/Oikos.res +++ /dev/null @@ -1,226 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -@@warning("-44-45") -open Types - -// ============================================================================= -// Oikos Client -// ============================================================================= - -type t = { - baseUrl: string, - token: option, - opts: httpOptions, -} - -let make = (baseUrl: string, token: option, opts: httpOptions): t => { - baseUrl, - token, - opts, -} - -// ============================================================================= -// JSON Encoding -// ============================================================================= - -let encodeAnalysisRequest = (req: oikosAnalysisRequest): string => { - let obj = Dict.make() - obj->Dict.set("repositoryUrl", req.repositoryUrl->JSON.Encode.string) - switch req.branch { - | Some(b) => obj->Dict.set("branch", b->JSON.Encode.string) - | None => () - } - switch req.commitSha { - | Some(s) => obj->Dict.set("commitSha", s->JSON.Encode.string) - | None => () - } - obj->JSON.Encode.object->JSON.stringify -} - -let encodeDiffRequest = (req: oikosDiffRequest): string => { - let obj = Dict.make() - obj->Dict.set("repositoryUrl", req.repositoryUrl->JSON.Encode.string) - obj->Dict.set("baseRef", req.baseRef->JSON.Encode.string) - obj->Dict.set("headRef", req.headRef->JSON.Encode.string) - obj->JSON.Encode.object->JSON.stringify -} - -// ============================================================================= -// JSON Decoding Helpers -// ============================================================================= - -let decodeString = (json: JSON.t, key: string): option => { - switch json->JSON.Classify.classify { - | Object(d) => - switch d->Dict.get(key) { - | Some(v) => - switch v->JSON.Classify.classify { - | String(s) => Some(s) - | _ => None - } - | None => None - } - | _ => None - } -} - -let decodeInt = (json: JSON.t, key: string): option => { - switch json->JSON.Classify.classify { - | Object(d) => - switch d->Dict.get(key) { - | Some(v) => - switch v->JSON.Classify.classify { - | Number(n) => Some(n->Float.toInt) - | _ => None - } - | None => None - } - | _ => None - } -} - -let decodeSeverity = (s: string): riskSeverity => { - switch s { - | "low" => Low - | "medium" => Medium - | "high" => High - | "critical" => Critical - | _ => Low - } -} - -let decodeServiceStatus = (s: string): serviceStatus => { - switch s { - | "healthy" => Healthy - | "degraded" => Degraded - | "unhealthy" => Unhealthy - | _ => Unhealthy - } -} - -// ============================================================================= -// Response Decoding -// ============================================================================= - -let decodeScores = (json: JSON.t): sustainabilityScores => { - { - maintainability: decodeInt(json, "maintainability")->Option.getOr(0), - documentation: decodeInt(json, "documentation")->Option.getOr(0), - testCoverage: decodeInt(json, "testCoverage")->Option.getOr(0), - communityHealth: decodeInt(json, "communityHealth")->Option.getOr(0), - securityPosture: decodeInt(json, "securityPosture")->Option.getOr(0), - dependencyHealth: decodeInt(json, "dependencyHealth")->Option.getOr(0), - releaseMaturity: decodeInt(json, "releaseMaturity")->Option.getOr(0), - codeQuality: decodeInt(json, "codeQuality")->Option.getOr(0), - } -} - -let decodeAnalysisResponse = (json: JSON.t): result => { - switch json->JSON.Classify.classify { - | Object(d) => - Ok({ - repositoryUrl: decodeString(json, "repositoryUrl")->Option.getOr(""), - analyzedAt: decodeString(json, "analyzedAt")->Option.getOr(""), - overallScore: decodeInt(json, "overallScore")->Option.getOr(0), - scores: switch d->Dict.get("scores") { - | Some(s) => decodeScores(s) - | None => { - maintainability: 0, - documentation: 0, - testCoverage: 0, - communityHealth: 0, - securityPosture: 0, - dependencyHealth: 0, - releaseMaturity: 0, - codeQuality: 0, - } - }, - recommendations: [], - risks: [], - }) - | _ => Error("Invalid response format") - } -} - -let decodeHealthResponse = (json: JSON.t): result => { - Ok({ - status: decodeString(json, "status")->Option.getOr("unhealthy")->decodeServiceStatus, - version: decodeString(json, "version")->Option.getOr("unknown"), - uptime: decodeInt(json, "uptime")->Option.getOr(0), - }) -} - -// ============================================================================= -// API Methods -// ============================================================================= - -let analyzeRepository = async (client: t, request: oikosAnalysisRequest): result< - oikosAnalysisResponse, -> => { - let body = encodeAnalysisRequest(request) - - switch await Http.postJson( - client.baseUrl, - "/analysis/repository", - client.token, - body, - client.opts, - ) { - | Error(e) => Error(e) - | Ok(_) => { - let encodedUrl = request.repositoryUrl->encodeURIComponent - switch await Http.getJson( - client.baseUrl, - `/analysis/repository/${encodedUrl}`, - client.token, - client.opts, - ) { - | Error(e) => Error(e) - | Ok(json) => decodeAnalysisResponse(json) - } - } - } -} - -let analyzeDiff = async (client: t, request: oikosDiffRequest): result => { - let body = encodeDiffRequest(request) - - switch await Http.postJson(client.baseUrl, "/analysis/diff", client.token, body, client.opts) { - | Error(e) => Error(e) - | Ok(_) => { - let encodedUrl = request.repositoryUrl->encodeURIComponent - switch await Http.getJson( - client.baseUrl, - `/analysis/diff/${encodedUrl}`, - client.token, - client.opts, - ) { - | Error(_) => - // Return minimal response on error - Ok({ - repositoryUrl: request.repositoryUrl, - baseRef: request.baseRef, - headRef: request.headRef, - analyzedAt: "", - overallDelta: 0, - impactSummary: "", - }) - | Ok(_json) => - Ok({ - repositoryUrl: request.repositoryUrl, - baseRef: request.baseRef, - headRef: request.headRef, - analyzedAt: "", - overallDelta: 0, - impactSummary: "", - }) - } - } - } -} - -let health = async (client: t): result => { - switch await Http.getJson(client.baseUrl, "/health", client.token, client.opts) { - | Error(e) => Error(e) - | Ok(json) => decodeHealthResponse(json) - } -} diff --git a/lib/ocaml/Palimpsest.cmj b/lib/ocaml/Palimpsest.cmj deleted file mode 100644 index 5c7b25a7..00000000 Binary files a/lib/ocaml/Palimpsest.cmj and /dev/null differ diff --git a/lib/ocaml/Palimpsest.res b/lib/ocaml/Palimpsest.res deleted file mode 100644 index 2f2d9c1e..00000000 --- a/lib/ocaml/Palimpsest.res +++ /dev/null @@ -1,268 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -@@warning("-44-45") -open Types - -// ============================================================================= -// HTTP Client Type -// ============================================================================= - -type t = { - baseUrl: string, - token: option, - opts: httpOptions, -} - -let make = (baseUrl: string, token: option, opts: httpOptions): t => { - baseUrl, - token, - opts, -} - -// ============================================================================= -// Additional Types -// ============================================================================= - -type compatibilityDirection = Both | L1ToL2 | L2ToL1 - -type compatibilityResult = { - license1: string, - license2: string, - compatible: bool, - direction: option, - reason: option, -} - -type copyleftStrength = Weak | Strong | Network - -type licenseInfo = { - spdxId: string, - name: string, - url: string, - osiApproved: bool, - fsfLibre: bool, - copyleft: bool, - copyleftStrength: option, - permissions: array, - conditions: array, - limitations: array, -} - -// ============================================================================= -// Deno Command FFI for CLI fallback -// ============================================================================= - -module DenoCommand = { - type commandOptions = { - args: array, - stdout: string, - stderr: string, - } - - type commandOutput = { - code: int, - stdout: Uint8Array.t, - stderr: Uint8Array.t, - } - - type command - - @new @scope("Deno") external makeCommand: (string, commandOptions) => command = "Command" - @send external output: command => promise = "output" -} - -module TextDecoder = { - type t - - @new external make: unit => t = "TextDecoder" - @send external decode: (t, Uint8Array.t) => string = "decode" -} - -// ============================================================================= -// JSON Encoding -// ============================================================================= - -let encodeAnalyzeRequest = (req: palimpsestRequest): string => { - let obj = Dict.make() - obj->Dict.set("artifactPath", req.artifactPath->JSON.Encode.string) - switch req.includeTransitive { - | Some(b) => obj->Dict.set("includeTransitive", b->JSON.Encode.bool) - | None => () - } - switch req.targetLicense { - | Some(l) => obj->Dict.set("targetLicense", l->JSON.Encode.string) - | None => () - } - obj->JSON.Encode.object->JSON.stringify -} - -let encodeCompatibilityRequest = (license1: string, license2: string): string => { - let obj = Dict.make() - obj->Dict.set("license1", license1->JSON.Encode.string) - obj->Dict.set("license2", license2->JSON.Encode.string) - obj->JSON.Encode.object->JSON.stringify -} - -// ============================================================================= -// API Methods -// ============================================================================= - -let analyze = async (client: t, request: palimpsestRequest): result => { - let body = encodeAnalyzeRequest(request) - - switch await Http.postJson(client.baseUrl, "/analyze", client.token, body, client.opts) { - | Error(e) => Error(e) - | Ok(_) => { - let encodedPath = request.artifactPath->encodeURIComponent - switch await Http.getJson( - client.baseUrl, - `/analyze/${encodedPath}`, - client.token, - client.opts, - ) { - | Error(e) => Error(e) - | Ok(_json) => - Ok({ - analyzedAt: "", - detectedLicenses: [], - compatibility: { - compatible: true, - targetLicense: request.targetLicense, - conflicts: [], - }, - obligations: [], - risks: [], - }) - } - } - } -} - -let checkCompatibility = async (client: t, license1: string, license2: string): result< - compatibilityResult, -> => { - let body = encodeCompatibilityRequest(license1, license2) - - switch await Http.postJson(client.baseUrl, "/compatibility", client.token, body, client.opts) { - | Error(e) => Error(e) - | Ok(_) => { - let l1 = license1->encodeURIComponent - let l2 = license2->encodeURIComponent - switch await Http.getJson( - client.baseUrl, - `/compatibility/${l1}/${l2}`, - client.token, - client.opts, - ) { - | Error(e) => Error(e) - | Ok(_json) => - Ok({ - license1, - license2, - compatible: true, - direction: Some(Both), - reason: None, - }) - } - } - } -} - -let getLicenseInfo = async (client: t, spdxId: string): result => { - let path = `/licenses/${spdxId->encodeURIComponent}` - - switch await Http.getJson(client.baseUrl, path, client.token, client.opts) { - | Error(e) => Error(e) - | Ok(_json) => - Ok({ - spdxId, - name: "", - url: "", - osiApproved: false, - fsfLibre: false, - copyleft: false, - copyleftStrength: None, - permissions: [], - conditions: [], - limitations: [], - }) - } -} - -let listLicenses = async (client: t): result> => { - switch await Http.getJson(client.baseUrl, "/licenses", client.token, client.opts) { - | Error(e) => Error(e) - | Ok(_json) => Ok([]) - } -} - -// ============================================================================= -// CLI Fallback -// ============================================================================= - -type cli = {binaryPath: string} - -let makeCli = (~binaryPath: string="palimpsest-license"): cli => { - binaryPath: binaryPath, -} - -let cliAnalyze = async (cli: cli, artifactPath: string, includeTransitive: bool): result< - string, -> => { - let args = ["analyze", artifactPath] - let args = if includeTransitive { - args->Array.concat(["--transitive"]) - } else { - args - } - - try { - let cmd = DenoCommand.makeCommand( - cli.binaryPath, - { - args, - stdout: "piped", - stderr: "piped", - }, - ) - - let output = await cmd->DenoCommand.output - let decoder = TextDecoder.make() - - if output.code != 0 { - let errorText = decoder->TextDecoder.decode(output.stderr) - Error(`palimpsest-license failed: ${errorText}`) - } else { - let outputText = decoder->TextDecoder.decode(output.stdout) - Ok(outputText) - } - } catch { - | JsExn(e) => Error(JsExn.message(e)->Option.getOr("Unknown error")) - } -} - -let cliCheckCompatibility = async (cli: cli, license1: string, license2: string): result => { - let args = ["check", license1, license2] - - try { - let cmd = DenoCommand.makeCommand( - cli.binaryPath, - { - args, - stdout: "piped", - stderr: "piped", - }, - ) - - let output = await cmd->DenoCommand.output - let decoder = TextDecoder.make() - - if output.code != 0 { - let errorText = decoder->TextDecoder.decode(output.stderr) - Error(`compatibility check failed: ${errorText}`) - } else { - Ok(true) - } - } catch { - | JsExn(e) => Error(JsExn.message(e)->Option.getOr("Unknown error")) - } -} diff --git a/lib/ocaml/Types.cmj b/lib/ocaml/Types.cmj deleted file mode 100644 index 9746d135..00000000 Binary files a/lib/ocaml/Types.cmj and /dev/null differ diff --git a/lib/ocaml/Types.res b/lib/ocaml/Types.res deleted file mode 100644 index 0ee1842f..00000000 --- a/lib/ocaml/Types.res +++ /dev/null @@ -1,285 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Types.res - Core type definitions for OPSM CLI - -// ============================================================================= -// Result type (compatible with proven) -// ============================================================================= - -type result<'a> = Ok('a) | Error(string) - -// ============================================================================= -// HTTP Configuration -// ============================================================================= - -type httpConfig = { - timeoutMs: int, - retries: int, - backoffMs: int, -} - -type httpOptions = { - timeoutMs: int, - retries: int, - backoffMs: int, -} - -// ============================================================================= -// Service Configuration -// ============================================================================= - -type serviceConfig = { - baseUrl: string, - token: option, -} - -type opsmConfig = { - http: httpConfig, - checkyMonkey: serviceConfig, - palimpsestLicense: serviceConfig, - oikos: serviceConfig, -} - -// ============================================================================= -// CLI Request/Response Types -// ============================================================================= - -type publishRequest = {path: string} - -type auditRequest = {package: string} - -type statusResponse = { - registryHub: string, - federation: string, -} - -// ============================================================================= -// OIKOS - Ecosystem Sustainability Analysis -// ============================================================================= - -type oikosAnalysisRequest = { - repositoryUrl: string, - branch: option, - commitSha: option, -} - -type sustainabilityScores = { - maintainability: int, - documentation: int, - testCoverage: int, - communityHealth: int, - securityPosture: int, - dependencyHealth: int, - releaseMaturity: int, - codeQuality: int, -} - -type riskSeverity = Low | Medium | High | Critical - -type oikosRisk = { - severity: riskSeverity, - category: string, - description: string, - remediation: option, -} - -type oikosAnalysisResponse = { - repositoryUrl: string, - analyzedAt: string, - overallScore: int, - scores: sustainabilityScores, - recommendations: array, - risks: array, -} - -type oikosDiffRequest = { - repositoryUrl: string, - baseRef: string, - headRef: string, -} - -type oikosDiffResponse = { - repositoryUrl: string, - baseRef: string, - headRef: string, - analyzedAt: string, - overallDelta: int, - impactSummary: string, -} - -type serviceStatus = Healthy | Degraded | Unhealthy - -type oikosHealthResponse = { - status: serviceStatus, - version: string, - uptime: int, -} - -// ============================================================================= -// Registry / Package Metadata -// (cicd-hyper-a service removed; publish/federation types deleted) -// ============================================================================= - -type packageMetadata = { - name: string, - version: string, - description: option, - license: string, - repository: option, - authors: array, - keywords: array, - dependencies: Dict.t, - devDependencies: option>, -} - -type attestationType = Sigstore | InToto - -type attestationRef = { - attestationType: attestationType, - uri: string, - digest: string, -} - -type ruleSeverity = RuleError | RuleWarning | RuleInfo - -type rule = { - id: string, - severity: ruleSeverity, - message: string, - check: string, -} - -type ruleset = { - id: string, - name: string, - version: string, - rules: array, - appliesTo: array, -} - -type packageQueryRequest = { - name: string, - version: option, - includeScores: option, -} - -type packageQueryResponse = { - package: packageMetadata, - versions: array, - latestVersion: string, - scores: option, - dependents: int, - downloads: int, -} - -// ============================================================================= -// CHECKY-MONKEY - Code Verification -// ============================================================================= - -type verificationType = - | PropertyTests - | FuzzTesting - | TypeChecking - | FormalVerification - | MutationTesting - -type checkyMonkeyRequest = { - repositoryUrl: string, - commitSha: string, - verificationTypes: array, - timeout: option, -} - -type verificationStatus = Queued | Running | Completed | Failed - -type codeLocation = { - file: string, - line: int, - column: option, - endLine: option, - endColumn: option, -} - -type findingSeverity = FindingLow | FindingMedium | FindingHigh | FindingCritical - -type finding = { - severity: findingSeverity, - category: string, - location: option, - message: string, - suggestion: option, -} - -type verificationResult = { - verificationType: verificationType, - passed: bool, - coverage: option, - findings: array, - duration: int, -} - -type checkyMonkeyResponse = { - requestId: string, - status: verificationStatus, - startedAt: option, - completedAt: option, - results: option>, -} - -// ============================================================================= -// PALIMPSEST-LICENSE - License Analysis -// ============================================================================= - -type licenseSource = FileHeader | LicenseFile | Manifest | Inferred - -type detectedLicense = { - spdxId: string, - confidence: float, - locations: array, - source: licenseSource, -} - -type conflictSeverity = ConflictWarning | ConflictError - -type licenseConflict = { - license1: string, - license2: string, - reason: string, - severity: conflictSeverity, -} - -type licenseCompatibility = { - compatible: bool, - targetLicense: option, - conflicts: array, -} - -type obligationType = Notice | SourceDisclosure | SameLicense | PatentGrant - -type licenseObligation = { - license: string, - obligation: string, - obligationType: obligationType, -} - -type licenseRiskSeverity = LicenseRiskLow | LicenseRiskMedium | LicenseRiskHigh - -type licenseRisk = { - severity: licenseRiskSeverity, - license: string, - risk: string, - recommendation: string, -} - -type palimpsestRequest = { - artifactPath: string, - includeTransitive: option, - targetLicense: option, -} - -type palimpsestResponse = { - analyzedAt: string, - detectedLicenses: array, - compatibility: licenseCompatibility, - obligations: array, - risks: array, -} diff --git a/lib/ocaml/Wiring.cmj b/lib/ocaml/Wiring.cmj deleted file mode 100644 index 03922084..00000000 Binary files a/lib/ocaml/Wiring.cmj and /dev/null differ diff --git a/lib/ocaml/Wiring.res b/lib/ocaml/Wiring.res deleted file mode 100644 index 49a2b365..00000000 --- a/lib/ocaml/Wiring.res +++ /dev/null @@ -1,183 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -@@warning("-44-45") -open Types - -// ============================================================================= -// Publish Pipeline -// ============================================================================= - -let runPublish = async (config: opsmConfig, path: string): result => { - let httpOpts: httpOptions = { - timeoutMs: config.http.timeoutMs, - retries: config.http.retries, - backoffMs: config.http.backoffMs, - } - - Console.log(`Publishing package from: ${path}`) - - // Step 1: Check license compliance via palimpsest-license - Console.log(" → Checking license compliance via palimpsest-license...") - let palimpsestClient = Palimpsest.make( - config.palimpsestLicense.baseUrl, - config.palimpsestLicense.token, - httpOpts, - ) - - let licenseResult = await palimpsestClient->Palimpsest.analyze({ - artifactPath: path, - includeTransitive: Some(true), - targetLicense: Some("PMPL-1.0"), - }) - - switch licenseResult { - | Error(e) => Console.log(` ✗ License check failed: ${e}`) - | Ok(resp) => - if resp.compatibility.compatible { - Console.log(" ✓ License compatible") - } else { - Console.log(" ✗ License conflicts detected") - } - } - - // Step 2: Verify with checky-monkey - Console.log(" → Running verification via checky-monkey...") - let checkyClient = CheckyMonkey.make( - config.checkyMonkey.baseUrl, - config.checkyMonkey.token, - httpOpts, - ) - - let verifyResult = await checkyClient->CheckyMonkey.submitVerification({ - repositoryUrl: path, - commitSha: "HEAD", - verificationTypes: [PropertyTests, TypeChecking], - timeout: Some(300), - }) - - switch verifyResult { - | Error(e) => { - Console.log(` ✗ Verification failed: ${e}`) - Error(e) - } - | Ok(resp) => { - Console.log(` ✓ Verification submitted: ${resp.requestId}`) - Ok() - } - } -} - -// ============================================================================= -// Audit Pipeline -// ============================================================================= - -let runAudit = async (config: opsmConfig, packageName: string): result => { - let httpOpts: httpOptions = { - timeoutMs: config.http.timeoutMs, - retries: config.http.retries, - backoffMs: config.http.backoffMs, - } - - Console.log(`Auditing package: ${packageName}`) - - // Step 1: Get sustainability scores from oikos - Console.log(" → Getting sustainability scores from oikos...") - let oikosClient = Oikos.make(config.oikos.baseUrl, config.oikos.token, httpOpts) - - let analysisResult = await oikosClient->Oikos.analyzeRepository({ - repositoryUrl: packageName, - branch: None, - commitSha: None, - }) - - switch analysisResult { - | Error(e) => Console.log(` ✗ Analysis failed: ${e}`) - | Ok(resp) => { - Console.log(` ✓ Overall score: ${resp.overallScore->Int.toString}/100`) - Console.log(` - Maintainability: ${resp.scores.maintainability->Int.toString}`) - Console.log(` - Documentation: ${resp.scores.documentation->Int.toString}`) - Console.log(` - Test Coverage: ${resp.scores.testCoverage->Int.toString}`) - Console.log(` - Security: ${resp.scores.securityPosture->Int.toString}`) - } - } - - // Step 2: Check licenses - Console.log(" → Checking licenses via palimpsest-license...") - let palimpsestClient = Palimpsest.make( - config.palimpsestLicense.baseUrl, - config.palimpsestLicense.token, - httpOpts, - ) - - let licenseResult = await palimpsestClient->Palimpsest.analyze({ - artifactPath: packageName, - includeTransitive: Some(true), - targetLicense: None, - }) - - switch licenseResult { - | Error(e) => Console.log(` ✗ License analysis failed: ${e}`) - | Ok(resp) => { - let count = resp.detectedLicenses->Array.length - Console.log(` ✓ Found ${count->Int.toString} licenses`) - } - } - - Ok() -} - -// ============================================================================= -// Status Check -// ============================================================================= - -let runStatus = async (config: opsmConfig): result => { - let httpOpts: httpOptions = { - timeoutMs: config.http.timeoutMs, - retries: config.http.retries, - backoffMs: config.http.backoffMs, - } - - Console.log("OPSM Service Status") - Console.log("==================") - - // Check oikos - let oikosClient = Oikos.make(config.oikos.baseUrl, config.oikos.token, httpOpts) - let oikosHealth = await oikosClient->Oikos.health - switch oikosHealth { - | Ok(h) => - let status = switch h.status { - | Healthy => "✓ healthy" - | Degraded => "⚠ degraded" - | Unhealthy => "✗ unhealthy" - } - Console.log(`oikos: ${status} (${h.version})`) - | Error(_) => Console.log("oikos: ✗ unreachable") - } - - // Check checky-monkey - let checkyClient = CheckyMonkey.make( - config.checkyMonkey.baseUrl, - config.checkyMonkey.token, - httpOpts, - ) - let checkyHealth = await checkyClient->CheckyMonkey.health - switch checkyHealth { - | Ok(h) => - let status = switch h.status { - | Healthy => "✓ healthy" - | Degraded => "⚠ degraded" - | Unhealthy => "✗ unhealthy" - } - Console.log(`checky-monkey: ${status} (${h.version})`) - | Error(_) => Console.log("checky-monkey: ✗ unreachable") - } - - // Show config info - Console.log("") - Console.log("Configuration") - Console.log("-------------") - Console.log(`checky-monkey: ${config.checkyMonkey.baseUrl}`) - Console.log(`palimpsest-license: ${config.palimpsestLicense.baseUrl}`) - Console.log(`oikos: ${config.oikos.baseUrl}`) - - Ok() -} diff --git a/lib/rescript.lock b/lib/rescript.lock deleted file mode 100644 index af1bf761..00000000 --- a/lib/rescript.lock +++ /dev/null @@ -1 +0,0 @@ -1209918 \ No newline at end of file diff --git a/rescript.json b/rescript.json deleted file mode 100644 index 982794fe..00000000 --- a/rescript.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "@hyperpolymath/opsm", - "sources": [ - { - "dir": "cli", - "subdirs": true - } - ], - "package-specs": [ - { - "module": "esmodule", - "in-source": true - } - ], - "suffix": ".res.js", - "dependencies": ["@rescript/core"], - "compiler-flags": ["-open RescriptCore"] -}