@okfit/cli 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@okfit/cli",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "private": false,
5
5
  "description": "The okfit command line: validate, lint, index, and inspect Open Knowledge Format (OKF) bundles.",
6
6
  "keywords": [
@@ -31,16 +31,31 @@
31
31
  "import": "./index.js",
32
32
  "default": "./index.js"
33
33
  },
34
+ "./main": {
35
+ "types": "./main.d.ts",
36
+ "import": "./main.js",
37
+ "default": "./main.js"
38
+ },
34
39
  "./package.json": "./package.json"
35
40
  },
36
41
  "bin": {
37
42
  "okfit": "bin/okfit.js"
38
43
  },
39
44
  "dependencies": {
40
- "@effect/platform-node": "4.0.0-rc.109",
41
- "@okfit/core": "0.1.0",
42
- "@okfit/profiles": "0.1.0",
43
- "effect": "4.0.0-rc.109"
45
+ "@effect/platform-node": "4.0.0-rc.112",
46
+ "@effected/cli": "^0.3.1",
47
+ "@effected/config-file": "^0.7.0",
48
+ "@effected/git": "^0.12.0",
49
+ "@effected/glob": "^0.5.0",
50
+ "@effected/jsonc": "^0.9.0",
51
+ "@effected/markdown": "^0.9.1",
52
+ "@effected/toml": "^0.6.0",
53
+ "@effected/walker": "^0.7.0",
54
+ "@effected/yaml": "^0.14.0",
55
+ "@okfit/core": "0.2.0",
56
+ "@okfit/engine": "0.1.0",
57
+ "@okfit/profiles": "0.2.0",
58
+ "effect": "4.0.0-rc.112"
44
59
  },
45
60
  "engines": {
46
61
  "node": ">=24.11.0"
@@ -0,0 +1,29 @@
1
+ //#region src/render/context.ts
2
+ /**
3
+ * The `human` format: a short header block, then one line per type and one
4
+ * per tag. Pure; the caller pipes each line through `Console.log`.
5
+ *
6
+ * The `profile:` line reads `profile: (none) (requested NAME, unknown)`
7
+ * when `profile` and `profile_requested` disagree over an actually-unknown
8
+ * profile — never for the `"none"` or no-config cases, where a `null`
9
+ * `profile` is expected, not an error.
10
+ *
11
+ * @public
12
+ */
13
+ const humanContext = (envelope) => [
14
+ `project root: ${envelope.project_root}`,
15
+ `bundle root: ${envelope.bundle_root}`,
16
+ `config: ${envelope.config_path ?? "(none)"}`,
17
+ envelope.profile === null && envelope.profile_requested !== null && envelope.profile_requested !== "none" ? `profile: (none) (requested ${envelope.profile_requested}, unknown)` : `profile: ${envelope.profile ?? "(none)"}`,
18
+ `index.md: ${envelope.index_path} (${envelope.index_exists ? "exists" : "missing"})`,
19
+ `agent: ${envelope.actors.agent ?? "(unset)"}`,
20
+ "",
21
+ "types:",
22
+ ...envelope.types.map((t) => ` ${t.name} ${t.description ?? ""}`),
23
+ "",
24
+ "tags:",
25
+ ...envelope.tags.map((t) => ` ${t.name} ${t.description ?? ""}`)
26
+ ];
27
+
28
+ //#endregion
29
+ export { humanContext };
@@ -0,0 +1,68 @@
1
+ import { sort } from "@okfit/engine";
2
+
3
+ //#region src/render/human.ts
4
+ /** The ANSI escape character, built from its code point so the source never carries a raw control byte. */
5
+ const ESC = String.fromCharCode(27);
6
+ /** ANSI SGR codes for the severity word only (K-19); reset after, never applied elsewhere. */
7
+ const SEVERITY_COLOR = {
8
+ error: `${ESC}[31m`,
9
+ warning: `${ESC}[33m`,
10
+ info: `${ESC}[36m`
11
+ };
12
+ const RESET = `${ESC}[0m`;
13
+ const colorize = (severity, color) => color ? `${SEVERITY_COLOR[severity]}${severity}${RESET}` : severity;
14
+ /**
15
+ * K-16. With a range:
16
+ * `<file>:<range.line + 1>:<range.character + 1> <severity> <code> <message>`.
17
+ * Without one: `<file> <severity> <code> <message>`. `file: ""` renders as
18
+ * the literal `(bundle)`. Core's range is zero-based (D-32,
19
+ * `CORE/Diagnostic.ts:49-57`); the `+ 1`s here are the only place it becomes
20
+ * one-based. Colour, when `color` is `true`, wraps ONLY the severity word
21
+ * (K-19) — never the code, the path, or the message.
22
+ *
23
+ * @public
24
+ */
25
+ const line = (diagnostic, options) => {
26
+ const color = options?.color ?? false;
27
+ const file = diagnostic.file === "" ? "(bundle)" : diagnostic.file;
28
+ const severity = colorize(diagnostic.severity, color);
29
+ return `${diagnostic.range === void 0 ? file : `${file}:${diagnostic.range.line + 1}:${diagnostic.range.character + 1}`} ${severity} ${diagnostic.code} ${diagnostic.message}`;
30
+ };
31
+ /**
32
+ * `sort` then `line` over the whole set: the exact stdout body of
33
+ * `--format human`, one array element per stdout line.
34
+ *
35
+ * @public
36
+ */
37
+ const human = (diagnostics, options) => sort(diagnostics).map((d) => line(d, options));
38
+ /**
39
+ * K-20, verbatim and unpluralised —
40
+ * `<E> errors, <W> warnings, <I> info in <N> concepts (<root>)`. `root` is
41
+ * pre-rendered by the caller: relative to cwd when under it, absolute
42
+ * otherwise (K-51).
43
+ *
44
+ * @public
45
+ */
46
+ const summary = (counts, root) => `${counts.errors} errors, ${counts.warnings} warnings, ${counts.info} info in ${counts.concepts} concepts (${root})`;
47
+ /**
48
+ * The one K-51 display-path rule, shared by both `commands/validate.ts`'s
49
+ * `summary` root and `commands/init.ts`'s success line: `target` relative
50
+ * to `cwd` when it is under it, absolute otherwise. Three cases, in order:
51
+ *
52
+ * 1. `target === cwd` (`path.relative` returns `""`): the literal `.`.
53
+ * 2. The relative form starts with `..`, or is itself absolute (a target on
54
+ * a different root than `cwd`, where `Path.relative` can return an
55
+ * absolute path unchanged depending on the platform): `target`
56
+ * unchanged, absolute.
57
+ * 3. Otherwise: the relative form.
58
+ *
59
+ * @internal
60
+ */
61
+ const displayRoot = (cwd, target, path) => {
62
+ const relative = path.relative(cwd, target);
63
+ if (relative === "") return ".";
64
+ return relative.startsWith("..") || path.isAbsolute(relative) ? target : relative;
65
+ };
66
+
67
+ //#endregion
68
+ export { displayRoot, human, line, summary };
package/render/sync.js ADDED
@@ -0,0 +1,41 @@
1
+ //#region src/render/sync.ts
2
+ /** Contract §9.2's fixed reason-sentence table, closed over `SkipReason`. */
3
+ const REASON_SENTENCE = {
4
+ untracked: "not tracked by git",
5
+ dirty: "has uncommitted changes",
6
+ unborn: "the repository has no commits yet",
7
+ "generated-missing": "has no generated block",
8
+ "generated-unsupported": "generated.at is a shape sync cannot edit; edit it by hand",
9
+ "log-unparseable": "log.md could not be parsed; see okfit validate"
10
+ };
11
+ const humanMode = (name, mode) => {
12
+ if (!mode.selected) return [`${name}: not selected`];
13
+ const lines = [`${name}:`];
14
+ for (const id of mode.written) lines.push(` wrote ${id}`);
15
+ for (const id of mode.unchanged) lines.push(` unchanged ${id}`);
16
+ for (const entry of mode.skipped) lines.push(` skipped ${entry.id}: ${REASON_SENTENCE[entry.reason]}`);
17
+ return lines;
18
+ };
19
+ /**
20
+ * Contract §9.2 (design §2): per mode, three lists, then a one-line
21
+ * summary. A mode `--only` excluded from renders as `not selected` rather
22
+ * than three empty lists — an implementer choice within the fixed JSON
23
+ * shape (§14 makes no ruling on the human half; this is cosmetic).
24
+ *
25
+ * @public
26
+ */
27
+ const humanSync = (result) => {
28
+ const lines = [
29
+ ...humanMode("generated", result.generated),
30
+ ...humanMode("index", result.index),
31
+ ...humanMode("log", result.log)
32
+ ];
33
+ const totalWritten = result.generated.written.length + result.index.written.length + result.log.written.length;
34
+ const totalUnchanged = result.generated.unchanged.length + result.index.unchanged.length + result.log.unchanged.length;
35
+ const totalSkipped = result.generated.skipped.length + result.index.skipped.length + result.log.skipped.length;
36
+ lines.push(result.dryRun ? `would write ${totalWritten}, unchanged ${totalUnchanged}, skipped ${totalSkipped} (dry run, nothing written)` : `wrote ${totalWritten}, unchanged ${totalUnchanged}, skipped ${totalSkipped}`);
37
+ return lines;
38
+ };
39
+
40
+ //#endregion
41
+ export { humanSync };
@@ -0,0 +1,29 @@
1
+ //#region src/render/verify.ts
2
+ /**
3
+ * Indent every line of `fragment` two spaces for display under a
4
+ * `would write:` header, dropping the single trailing empty line a
5
+ * newline-terminated fragment produces on split (I3).
6
+ */
7
+ const indentFragment = (fragment) => {
8
+ const lines = fragment.split(/\r\n|\n/);
9
+ return (lines[lines.length - 1] === "" ? lines.slice(0, -1) : lines).map((line) => ` ${line}`);
10
+ };
11
+ /**
12
+ * V-11's human output: one line per fact. One `already verified` line per
13
+ * prior entry by the same actor, in list order (V-2 says "entries",
14
+ * plural), then the success line. A prior entry by a DIFFERENT actor is
15
+ * not called out: it stays on disk untouched (V-1) and is simply not this
16
+ * line's subject. Under `--dry-run` (I3), a `would write:` header and the
17
+ * exact fragment a real run would splice in follow, so the preview
18
+ * exercises — and shows — the same edit the write path would make.
19
+ *
20
+ * @public
21
+ */
22
+ const humanVerify = (input) => [
23
+ ...input.priorAt.map((at) => `already verified by ${input.by} at ${at}; appending`),
24
+ input.dryRun ? `would verify ${input.id} by ${input.by} at ${input.at} (dry run, nothing written)` : `verified ${input.id} by ${input.by} at ${input.at}`,
25
+ ...input.dryRun ? ["would write:", ...indentFragment(input.fragment)] : []
26
+ ];
27
+
28
+ //#endregion
29
+ export { humanVerify };
package/version.js ADDED
@@ -0,0 +1,14 @@
1
+ //#region src/version.ts
2
+ /**
3
+ * The version string reported by `okfit --version`. `@savvy-web/bundler`
4
+ * replaces `process.env.__PACKAGE_VERSION__` with this package's own
5
+ * version at build time (K-32), so a release can never desync from the
6
+ * printed version. `"0.0.0"` is the unbuilt-source fallback and reads as
7
+ * dev mode.
8
+ *
9
+ * @public
10
+ */
11
+ const CLI_VERSION = "0.3.0";
12
+
13
+ //#endregion
14
+ export { CLI_VERSION };