@okfit/cli 0.2.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/index.js CHANGED
@@ -1,17 +1,8 @@
1
- import { ConfigMalformedError, ConfigPathNotFoundError, InitOverwriteError, VerifyConceptNotFoundError, VerifyUnsupportedFrontmatterError, renderFailure } from "./errors.js";
2
- import { buildConfigLayer, provideConfig } from "./config/layer.js";
3
- import { resolveBundleRoot, resolveProjectRoot } from "./config/anchor.js";
4
- import { DEFAULT_PROFILE_NAME, resolveProjectConfig } from "./config/resolve.js";
5
- import { runContext } from "./context/run.js";
6
- import { ContextEnvelope, ContextTag, ContextType, contextEnvelope, humanContext } from "./render/context.js";
7
- import { forDiagnostics, tally } from "./render/exit.js";
8
- import { collect, sort } from "./render/sort.js";
9
- import { JsonDiagnostic, JsonEnvelope, JsonErrorEnvelope, JsonSummary, json, jsonError } from "./render/json.js";
1
+ import { humanContext } from "./render/context.js";
10
2
  import { CLI_VERSION } from "./version.js";
11
- import { CONFIG_RELATIVE_PATH, configValue, files, targetPaths } from "./init/scaffold.js";
12
3
  import { human, line, summary } from "./render/human.js";
13
- import { run } from "./validate/run.js";
14
- import { VerifyEnvelope, humanVerify, verifyEnvelope } from "./render/verify.js";
4
+ import { humanVerify } from "./render/verify.js";
15
5
  import { rootCommand } from "./commands/root.js";
6
+ import { renderFailure } from "./errors.js";
16
7
 
17
- export { CLI_VERSION, CONFIG_RELATIVE_PATH, ConfigMalformedError, ConfigPathNotFoundError, ContextEnvelope, ContextTag, ContextType, DEFAULT_PROFILE_NAME, InitOverwriteError, JsonDiagnostic, JsonEnvelope, JsonErrorEnvelope, JsonSummary, VerifyConceptNotFoundError, VerifyEnvelope, VerifyUnsupportedFrontmatterError, buildConfigLayer, collect, configValue, contextEnvelope, files, forDiagnostics, human, humanContext, humanVerify, json, jsonError, line, provideConfig, renderFailure, resolveBundleRoot, resolveProjectConfig, resolveProjectRoot, rootCommand, run, runContext, sort, summary, tally, targetPaths, verifyEnvelope };
8
+ export { CLI_VERSION, human, humanContext, humanVerify, line, renderFailure, rootCommand, summary };
package/main.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ //#region src/main.d.ts
2
+ /**
3
+ * The assembled okfit CLI program.
4
+ *
5
+ * @packageDocumentation
6
+ */
7
+ /**
8
+ * Run the okfit CLI. Owns the process: installs the runtime teardown and
9
+ * sets the exit code. `NodeRuntime.runMain` does not return a promise.
10
+ *
11
+ * @public
12
+ */
13
+ export declare const main: () => void;
14
+ //#endregion
15
+ //# sourceMappingURL=main.d.ts.map
package/main.js ADDED
@@ -0,0 +1,44 @@
1
+ import { CLI_VERSION } from "./version.js";
2
+ import { rootCommand } from "./commands/root.js";
3
+ import { renderFailure } from "./errors.js";
4
+ import { Command } from "effect/unstable/cli";
5
+ import { Now, OkfitPlatform } from "@okfit/engine";
6
+ import { DateTime, Effect, Option } from "effect";
7
+ import { CliLogger, CliRuntime } from "@effected/cli";
8
+ import * as NodeRuntime from "@effect/platform-node/NodeRuntime";
9
+
10
+ //#region src/main.ts
11
+ /**
12
+ * The assembled okfit CLI program.
13
+ *
14
+ * @packageDocumentation
15
+ */
16
+ /**
17
+ * K-47: an ISO-8601 `OKFIT_NOW` when set, else the wall clock. A documented
18
+ * test hook, not user-facing. Resolved exactly once, here, and provided to
19
+ * the whole command tree through the `Now` tag so no command handler ever
20
+ * reads `process.env["OKFIT_NOW"]` itself.
21
+ */
22
+ const nowEffect = Option.fromNullishOr(process.env.OKFIT_NOW).pipe(Option.flatMap((iso) => DateTime.make(iso)), Option.match({
23
+ onNone: () => DateTime.now,
24
+ onSome: Effect.succeed
25
+ }));
26
+ /**
27
+ * Run the okfit CLI. Owns the process: installs the runtime teardown and
28
+ * sets the exit code. `NodeRuntime.runMain` does not return a promise.
29
+ *
30
+ * @public
31
+ */
32
+ const main = () => {
33
+ const program = Effect.gen(function* () {
34
+ const now = yield* nowEffect;
35
+ return yield* Command.run(rootCommand, { version: CLI_VERSION }).pipe(Effect.provideService(Now, now), Effect.catchTag("ShowHelp", (help) => Effect.fail(CliRuntime.reported(help, help.errors.length > 0 ? 64 : 0))));
36
+ }).pipe(Effect.provide(OkfitPlatform), CliRuntime.reportFailures({
37
+ exitCode: 3,
38
+ render: renderFailure
39
+ }));
40
+ NodeRuntime.runMain(program.pipe(Effect.provide(CliLogger.layer())));
41
+ };
42
+
43
+ //#endregion
44
+ export { main };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@okfit/cli",
3
- "version": "0.2.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,6 +31,11 @@
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": {
@@ -38,19 +43,17 @@
38
43
  },
39
44
  "dependencies": {
40
45
  "@effect/platform-node": "4.0.0-rc.112",
41
- "@effected/app": "^0.15.0",
42
46
  "@effected/cli": "^0.3.1",
43
47
  "@effected/config-file": "^0.7.0",
44
48
  "@effected/git": "^0.12.0",
45
49
  "@effected/glob": "^0.5.0",
46
50
  "@effected/jsonc": "^0.9.0",
47
51
  "@effected/markdown": "^0.9.1",
48
- "@effected/store": "^0.7.0",
49
52
  "@effected/toml": "^0.6.0",
50
53
  "@effected/walker": "^0.7.0",
51
- "@effected/xdg": "^0.4.1",
52
54
  "@effected/yaml": "^0.14.0",
53
55
  "@okfit/core": "0.2.0",
56
+ "@okfit/engine": "0.1.0",
54
57
  "@okfit/profiles": "0.2.0",
55
58
  "effect": "4.0.0-rc.112"
56
59
  },
package/render/context.js CHANGED
@@ -1,83 +1,4 @@
1
- import { Schema } from "effect";
2
-
3
1
  //#region src/render/context.ts
4
- /** One `types[]` entry (M-15). @public */
5
- const ContextType = Schema.Struct({
6
- name: Schema.String,
7
- description: Schema.NullOr(Schema.String),
8
- guidance: Schema.NullOr(Schema.String)
9
- });
10
- /** One `tags[]` entry (M-15). @public */
11
- const ContextTag = Schema.Struct({
12
- name: Schema.String,
13
- description: Schema.NullOr(Schema.String)
14
- });
15
- /**
16
- * M-15's envelope: schema 1, snake_case, orientation data only. Distinct
17
- * from `JsonEnvelope` (`render/json.ts`) — `context` never runs conformance
18
- * or lint checks, so there is no `diagnostics` array and no `exit_code`
19
- * field at all.
20
- *
21
- * Every field is `Schema.NullOr`, never `Schema.optionalKey`: a consumer
22
- * (the two hook scripts) reads a fixed key set and gets JSON `null` for an
23
- * absent value, rather than having to distinguish a missing key from a
24
- * null one. This is a deliberate difference from `JsonDiagnostic`'s
25
- * `range`, which is `optionalKey` and omitted when absent
26
- * (`render/json.ts:14,68`).
27
- *
28
- * `profile_requested` (final-review Important 1) is the profile name the
29
- * config asked for after the K-4 default rule — `resolveProjectConfig`'s
30
- * own `profileName` — and is `null` only when no config file was found at
31
- * all. `profile` keeps its original meaning (the resolved profile's name,
32
- * or `null` when the requested name is unknown or `"none"`). The two
33
- * differ exactly when a config named an unrecognised profile: `profile`
34
- * is `null` but `profile_requested` still names what was asked for, so a
35
- * consumer can tell "no profile configured" apart from "an unknown profile
36
- * was configured".
37
- *
38
- * @public
39
- */
40
- const ContextEnvelope = Schema.Struct({
41
- schema: Schema.Literal(1),
42
- project_root: Schema.String,
43
- bundle_root: Schema.String,
44
- config_path: Schema.NullOr(Schema.String),
45
- profile: Schema.NullOr(Schema.String),
46
- profile_requested: Schema.NullOr(Schema.String),
47
- index_path: Schema.String,
48
- index_exists: Schema.Boolean,
49
- actors: Schema.Struct({ agent: Schema.NullOr(Schema.String) }),
50
- types: Schema.Array(ContextType),
51
- tags: Schema.Array(ContextTag)
52
- });
53
- /**
54
- * Build the envelope from the merged config. `types`/`tags` sort by `name`
55
- * with plain code-unit comparison, never locale-dependent — the same rule
56
- * `render/sort.ts`'s K-17 comparator and
57
- * `packages/profiles/src/SoftwareProject.ts:119`'s own sort use.
58
- *
59
- * @public
60
- */
61
- const contextEnvelope = (input) => ({
62
- schema: 1,
63
- project_root: input.projectRoot,
64
- bundle_root: input.bundleRoot,
65
- config_path: input.configPath,
66
- profile: input.profile,
67
- profile_requested: input.profileRequested,
68
- index_path: input.indexPath,
69
- index_exists: input.indexExists,
70
- actors: { agent: input.config.actors?.agent ?? null },
71
- types: Object.entries(input.config.types ?? {}).map(([name, decl]) => ({
72
- name,
73
- description: decl.description ?? null,
74
- guidance: decl.guidance ?? null
75
- })).toSorted((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0),
76
- tags: Object.entries(input.config.tags ?? {}).map(([name, decl]) => ({
77
- name,
78
- description: decl.description ?? null
79
- })).toSorted((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0)
80
- });
81
2
  /**
82
3
  * The `human` format: a short header block, then one line per type and one
83
4
  * per tag. Pure; the caller pipes each line through `Console.log`.
@@ -105,4 +26,4 @@ const humanContext = (envelope) => [
105
26
  ];
106
27
 
107
28
  //#endregion
108
- export { ContextEnvelope, ContextTag, ContextType, contextEnvelope, humanContext };
29
+ export { humanContext };
package/render/human.js CHANGED
@@ -1,4 +1,4 @@
1
- import { sort } from "./sort.js";
1
+ import { sort } from "@okfit/engine";
2
2
 
3
3
  //#region src/render/human.ts
4
4
  /** The ANSI escape character, built from its code point so the source never carries a raw control byte. */
package/render/sync.js CHANGED
@@ -1,64 +1,4 @@
1
- import { SkipReason } from "../sync/run.js";
2
- import { Schema } from "effect";
3
-
4
1
  //#region src/render/sync.ts
5
- /** @public */
6
- const SyncModeEnvelope = Schema.Struct({
7
- selected: Schema.Boolean,
8
- written: Schema.Array(Schema.String),
9
- unchanged: Schema.Array(Schema.String),
10
- skipped: Schema.Array(Schema.Struct({
11
- id: Schema.String,
12
- reason: SkipReason
13
- }))
14
- });
15
- /**
16
- * Contract §14 note 5: `schema: 1` is INFERRED, not in the design's own
17
- * §2 JSON example — every other envelope in this codebase
18
- * (`JsonEnvelope`, `VerifyEnvelope`, `ContextEnvelope`) opens with it, so
19
- * this one does too. `exit_code` is the literal `0`: `sync` has no
20
- * content tier (unlike `validate`'s `0 | 1 | 2`) and this envelope only
21
- * exists on the success path — a failure produces `JsonErrorEnvelope`
22
- * (K-22) instead, unchanged.
23
- *
24
- * @public
25
- */
26
- const SyncEnvelope = Schema.Struct({
27
- schema: Schema.Literal(1),
28
- okfit_version: Schema.String,
29
- root: Schema.String,
30
- dry_run: Schema.Boolean,
31
- exit_code: Schema.Literal(0),
32
- generated: SyncModeEnvelope,
33
- index: SyncModeEnvelope,
34
- log: SyncModeEnvelope
35
- });
36
- const toModeEnvelope = (mode) => ({
37
- selected: mode.selected,
38
- written: [...mode.written],
39
- unchanged: [...mode.unchanged],
40
- skipped: mode.skipped.map((entry) => ({
41
- id: entry.id,
42
- reason: entry.reason
43
- }))
44
- });
45
- /**
46
- * `root` alone goes through `displayRoot` at the call site
47
- * (`commands/sync.ts`) — `generated`/`index`/`log`'s own lists are
48
- * already bundle-relative and need no cwd-relativisation (contract §9.1).
49
- *
50
- * @public
51
- */
52
- const syncEnvelope = (input) => ({
53
- schema: 1,
54
- okfit_version: input.okfitVersion,
55
- root: input.root,
56
- dry_run: input.dryRun,
57
- exit_code: 0,
58
- generated: toModeEnvelope(input.result.generated),
59
- index: toModeEnvelope(input.result.index),
60
- log: toModeEnvelope(input.result.log)
61
- });
62
2
  /** Contract §9.2's fixed reason-sentence table, closed over `SkipReason`. */
63
3
  const REASON_SENTENCE = {
64
4
  untracked: "not tracked by git",
@@ -98,4 +38,4 @@ const humanSync = (result) => {
98
38
  };
99
39
 
100
40
  //#endregion
101
- export { SyncEnvelope, SyncModeEnvelope, humanSync, syncEnvelope };
41
+ export { humanSync };
package/render/verify.js CHANGED
@@ -1,48 +1,5 @@
1
- import { Schema } from "effect";
2
-
3
1
  //#region src/render/verify.ts
4
2
  /**
5
- * V-11's success envelope, schema 1, snake_case — the same convention as
6
- * `render/json.ts`'s `JsonEnvelope`. Identical in shape for a dry run,
7
- * which sets `dry_run: true` and still exits 0. There is no content tier,
8
- * so `exit_code` is the literal `0`; a failure produces K-22's
9
- * `JsonErrorEnvelope` instead, unchanged.
10
- *
11
- * @public
12
- */
13
- const VerifyEnvelope = Schema.Struct({
14
- schema: Schema.Literal(1),
15
- okfit_version: Schema.String,
16
- id: Schema.String,
17
- path: Schema.String,
18
- verified: Schema.Struct({
19
- by: Schema.String,
20
- at: Schema.String
21
- }),
22
- dry_run: Schema.Boolean,
23
- exit_code: Schema.Literal(0)
24
- });
25
- /**
26
- * `path` is already the display form — `displayRoot(cwd, bundle.root, path)`
27
- * joined to the bundle-relative `concept.path` — because `LoadedConcept.path`
28
- * alone would print `decisions/cli-exit-codes.md`, not
29
- * `okf/decisions/cli-exit-codes.md` (contract §12 note 5).
30
- *
31
- * @public
32
- */
33
- const verifyEnvelope = (input) => ({
34
- schema: 1,
35
- okfit_version: input.okfitVersion,
36
- id: input.id,
37
- path: input.path,
38
- verified: {
39
- by: input.by,
40
- at: input.at
41
- },
42
- dry_run: input.dryRun,
43
- exit_code: 0
44
- });
45
- /**
46
3
  * Indent every line of `fragment` two spaces for display under a
47
4
  * `would write:` header, dropping the single trailing empty line a
48
5
  * newline-terminated fragment produces on split (I3).
@@ -69,4 +26,4 @@ const humanVerify = (input) => [
69
26
  ];
70
27
 
71
28
  //#endregion
72
- export { VerifyEnvelope, humanVerify, verifyEnvelope };
29
+ export { humanVerify };
package/version.js CHANGED
@@ -1,14 +1,14 @@
1
- import { version } from "./package.js";
2
-
3
1
  //#region src/version.ts
4
2
  /**
5
- * The version string reported by `okfit --version`, read from the package's
6
- * own manifest (K-32) so a release can never desync from the printed
7
- * version. Consumed by `bin.ts` only.
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
8
  *
9
9
  * @public
10
10
  */
11
- const CLI_VERSION = version;
11
+ const CLI_VERSION = "0.3.0";
12
12
 
13
13
  //#endregion
14
14
  export { CLI_VERSION };
package/config/anchor.js DELETED
@@ -1,57 +0,0 @@
1
- //#region src/config/anchor.ts
2
- /**
3
- * The explicit `--config` anchor rule (C-8): the parent of `.config` "if the
4
- * file sits in a `.config` directory, else the file's directory", with no
5
- * exception for a custom name. Upstream's `ConfigResolver.explicitPath`
6
- * reports no `dir` in its `match` by design, so this stays a hand-rolled,
7
- * basename-based rule rather than reading `ConfigMatch.dir` — the one
8
- * remaining path-tail string match in the CLI outside `init/scaffold.ts`
9
- * (C1-4).
10
- */
11
- const anchorForExplicit = (configPath, path) => {
12
- const parent = path.dirname(configPath);
13
- return path.basename(parent) === ".config" ? path.dirname(parent) : parent;
14
- };
15
- /**
16
- * K-12's project root, as amended by K-58 and C1-2, in order:
17
- *
18
- * 1. `pathArg`, if given. `Argument.path` has already resolved it absolute.
19
- * 2. otherwise, if `--config` was given: `anchorForExplicit` applied to that
20
- * path.
21
- * 3. otherwise, if a config was discovered by the `"project"` resolver
22
- * (`ConfigResolver.upwardWalk`, named `"project"` in `layer.ts`) AND it
23
- * reported a `dir`: that `dir` verbatim — it is already the ancestor
24
- * `upwardWalk` anchored the match against (the parent of `.config` for a
25
- * `.config/okfit.toml` candidate, the candidate's own directory
26
- * otherwise), so no path-tail test is needed here any more.
27
- * 4. otherwise `cwd` — this covers every other resolver name (`"xdg"`,
28
- * `"native"`, `"system"`) as defence in depth, a `"project"` match with no
29
- * `dir` (should not happen, since `upwardWalk` always reports one), and
30
- * no discovery at all.
31
- *
32
- * The CLI never probes for `.git` (K-12), even though
33
- * `ConfigResolver.gitRoot` exists.
34
- *
35
- * @public
36
- */
37
- const resolveProjectRoot = (input) => {
38
- if (input.pathArg._tag === "Some") return input.pathArg.value;
39
- if (input.explicitConfigPath._tag === "Some") return anchorForExplicit(input.explicitConfigPath.value, input.path);
40
- if (input.discovered._tag === "Some") {
41
- const discovered = input.discovered.value;
42
- if (discovered.resolver === "project" && discovered.dir !== void 0) return discovered.dir;
43
- }
44
- return input.cwd;
45
- };
46
- /**
47
- * `<project root>/<bundle.path>`, absolute (K-2). `config` is the MERGED
48
- * config, so `bundle.path` is `OkfitConfig.DEFAULTS.bundle.path` (`"okf"`)
49
- * unless a file or profile overrode it. The bundle root is never what
50
- * `[path]` names.
51
- *
52
- * @public
53
- */
54
- const resolveBundleRoot = (projectRoot, config, path) => path.resolve(projectRoot, config.bundle?.path ?? "okf");
55
-
56
- //#endregion
57
- export { resolveBundleRoot, resolveProjectRoot };
package/config/layer.js DELETED
@@ -1,129 +0,0 @@
1
- import { ConfigMalformedError, ConfigPathNotFoundError } from "../errors.js";
2
- import { Cause, Effect, FileSystem, Option, Result } from "effect";
3
- import { AppConfig } from "@effected/app";
4
- import { ConfigCodecError, ConfigResolver, ConfigValidationError, MergeStrategy, TomlCodec } from "@effected/config-file";
5
- import { OkfitConfig, OkfitConfigFile } from "@okfit/core";
6
-
7
- //#region src/config/layer.ts
8
- /**
9
- * The `OkfitConfigFile` layer for one invocation (K-9 to K-11, K-57, C-6),
10
- * now one `AppConfig.layer(OkfitConfigFile, ...)` call in both branches; only
11
- * the chain options vary:
12
- *
13
- * - `explicitConfigPath` is `Some`: `resolvers: [ConfigResolver.explicitPath(path)]`
14
- * and `xdg: false` — K-10's "only that path" rule, so the chain is exactly
15
- * one resolver and no `systemEtc` tier is added either.
16
- * - `explicitConfigPath` is `None`: `resolvers: [ConfigResolver.upwardWalk({ filenames, ... })]`
17
- * carrying C-1's three per-directory candidates (`.okfit.toml`,
18
- * `okfit.toml`, `.config/okfit.toml`, directory-major so a child's later
19
- * candidate beats a parent's earlier one — C-2's ascent to the filesystem
20
- * root is `upwardWalk`'s own default with no `stopAt`), named `"project"`
21
- * so `resolve.ts`/`anchor.ts` can identify it without string-matching a
22
- * path tail. `systemEtc` is C-5's `/etc` tier, present unless
23
- * `systemConfigDir` overrides its root (tests only — no production call
24
- * site sets it). `xdg` and `native` stay on their defaults (C-4), which is
25
- * what puts personal defaults at `$XDG_CONFIG_HOME/okfit/config.toml` and
26
- * the native probe behind it.
27
- *
28
- * `filename: "config.toml"` is the same in both branches — it is the XDG/
29
- * native/system tiers' filename, unrelated to the C-1 project names above.
30
- * `defaultPath` is `AppConfig.layer`'s own `XdgConfig.savePath(filename)`, so
31
- * `save`/`update` do not fail with `ConfigDefaultPathMissingError`; nothing
32
- * here sets it explicitly any more.
33
- *
34
- * `discoveryCwd` is `[path]` when given, else `process.cwd()` (K-2), passed
35
- * straight through as `upwardWalk`'s own `cwd` so nothing in this module
36
- * reads the process.
37
- *
38
- * `App.layer`, `AppStore` and `AppCache` appear nowhere, so no
39
- * `store.db`/`cache.db` is ever created (K-9).
40
- *
41
- * @public
42
- */
43
- const buildConfigLayer = (options) => Option.isSome(options.explicitConfigPath) ? AppConfig.layer(OkfitConfigFile, {
44
- filename: "config.toml",
45
- schema: OkfitConfig,
46
- codec: TomlCodec,
47
- strategy: MergeStrategy.firstMatch(),
48
- resolvers: [ConfigResolver.explicitPath(options.explicitConfigPath.value)],
49
- xdg: false
50
- }) : AppConfig.layer(OkfitConfigFile, {
51
- filename: "config.toml",
52
- schema: OkfitConfig,
53
- codec: TomlCodec,
54
- strategy: MergeStrategy.firstMatch(),
55
- resolvers: [ConfigResolver.upwardWalk({
56
- filenames: [
57
- ".okfit.toml",
58
- "okfit.toml",
59
- ".config/okfit.toml"
60
- ],
61
- cwd: options.discoveryCwd,
62
- name: "project"
63
- })],
64
- systemEtc: options.systemConfigDir === void 0 ? true : { dir: options.systemConfigDir }
65
- });
66
- /**
67
- * The K-1 pre-flight and the provide, in that order and in one place: stat
68
- * `explicitConfigPath` with `FileSystem.exists` and fail with
69
- * `ConfigPathNotFoundError` BEFORE `buildConfigLayer` is called at all. This
70
- * is why the CLI does not use `Command.provide(cmd, (input) => layer)`,
71
- * which would construct the layer first (Judge notes 9) — here the stat
72
- * runs as the first step of one `Effect.gen`, and `buildConfigLayer` is only
73
- * reached, and only then actually run, on the step after it.
74
- *
75
- * `FileSystem.FileSystem.exists` is `(path: string) => Effect.Effect<boolean, PlatformError>`
76
- * (`EF/FileSystem.ts:143-145`), not infallible, so
77
- * `PlatformError.PlatformError` joins the error channel here (decision 7):
78
- * an unusual stat failure (e.g. a permission error on a parent directory)
79
- * renders through `renderFailure`'s catch-all rule rather than being
80
- * uncatchable by the type checker.
81
- *
82
- * K-46/K-63 fix: a `ConfigCodecError`/`ConfigValidationError` from
83
- * `buildConfigLayer`'s provided layer is wrapped into `ConfigMalformedError`
84
- * whenever the failing path is known, so `renderFailure` can name it:
85
- *
86
- * - `ConfigCodecError` now carries its own `path: string | undefined`
87
- * (config-file 0.7.0, `ConfigFile.discover` re-raises with `path` attached
88
- * at every site that fed the codec a path it resolved) — used when
89
- * present, so a malformed file found during DISCOVERY (no `--config`) now
90
- * also wraps into `ConfigMalformedError` naming the candidate that failed,
91
- * closing the K-63 gap. It falls back to `explicitConfigPath` only when
92
- * the library's own `path` is `undefined` and `--config` was given; only
93
- * when neither is known does the cause pass through unwrapped.
94
- * - `ConfigValidationError` carries its own `path: Option<string>` — used
95
- * when present (either branch), falling back to `explicitConfigPath` when
96
- * the library's own `path` is `None` and `--config` was given.
97
- *
98
- * @public
99
- */
100
- const provideConfig = (options) => (effect) => Effect.gen(function* () {
101
- if (Option.isSome(options.explicitConfigPath)) {
102
- const path = options.explicitConfigPath.value;
103
- if (!(yield* (yield* FileSystem.FileSystem).exists(path))) return yield* Effect.fail(new ConfigPathNotFoundError({ path }));
104
- }
105
- return yield* effect.pipe(Effect.provide(buildConfigLayer(options)), Effect.catchCause((cause) => {
106
- const found = Cause.findFail(cause);
107
- if (Result.isSuccess(found)) {
108
- const error = found.success.error;
109
- if (error instanceof ConfigCodecError) {
110
- const path = error.path !== void 0 ? error.path : Option.isSome(options.explicitConfigPath) ? options.explicitConfigPath.value : void 0;
111
- if (path !== void 0) return Effect.fail(new ConfigMalformedError({
112
- path,
113
- cause: error
114
- }));
115
- }
116
- if (error instanceof ConfigValidationError) {
117
- const path = Option.isSome(options.explicitConfigPath) ? options.explicitConfigPath : error.path;
118
- if (Option.isSome(path)) return Effect.fail(new ConfigMalformedError({
119
- path: path.value,
120
- cause: error
121
- }));
122
- }
123
- }
124
- return Effect.failCause(cause);
125
- }));
126
- });
127
-
128
- //#endregion
129
- export { buildConfigLayer, provideConfig };
package/config/resolve.js DELETED
@@ -1,67 +0,0 @@
1
- import { resolveBundleRoot, resolveProjectRoot } from "./anchor.js";
2
- import { Console, Effect, Option, Path } from "effect";
3
- import { OKF_SPEC_VERSION, OkfitConfig, OkfitConfigFile } from "@okfit/core";
4
- import { Profiles } from "@okfit/profiles";
5
-
6
- //#region src/config/resolve.ts
7
- /**
8
- * `OkfitConfig.DEFAULTS.bundle.profile` is `"software-project"` at runtime
9
- * (the frozen literal always sets it), but `bundle` and `profile` are both
10
- * `optionalKey` in the schema, so the type checker sees `string | undefined`
11
- * two levels deep. The `?? "software-project"` fallback here is unreachable
12
- * in practice; it exists only to satisfy `noUncheckedIndexedAccess`-style
13
- * strictness without a non-null assertion. Formerly duplicated identically
14
- * in `commands/validate.ts` and `commands/context.ts`; this is its one home
15
- * now that both commands delegate to {@link resolveProjectConfig}.
16
- *
17
- * @public
18
- */
19
- const DEFAULT_PROFILE_NAME = OkfitConfig.DEFAULTS.bundle?.profile ?? "software-project";
20
- /**
21
- * The config-resolution step byte-identical between `validate` and
22
- * `context`'s handlers (A2 review finding): discover, pick `sources[0]`,
23
- * default `{ extensions: {} }`, resolve the profile name and the K-4
24
- * unknown-profile warning, merge `DEFAULTS < profile < file` (D-28), warn on
25
- * an `okf_version` mismatch (K-15), then resolve the project and bundle
26
- * roots (K-12). Every message string and the merge order are unchanged from
27
- * the two commands' former inline copies.
28
- *
29
- * @public
30
- */
31
- const resolveProjectConfig = (input) => Effect.gen(function* () {
32
- const path = yield* Path.Path;
33
- const discoveredSource = (yield* (yield* OkfitConfigFile).discover)[0];
34
- const fileConfig = discoveredSource === void 0 ? { extensions: {} } : discoveredSource.value;
35
- const profileName = fileConfig.bundle?.profile ?? DEFAULT_PROFILE_NAME;
36
- const profile = profileName === "none" ? Option.none() : Profiles.get(profileName);
37
- if (profileName !== "none" && Option.isNone(profile)) yield* Console.error(`warning: unknown profile "${profileName}"; continuing with defaults`);
38
- const base = Option.match(profile, {
39
- onNone: () => OkfitConfig.DEFAULTS,
40
- onSome: (p) => OkfitConfig.merge(OkfitConfig.DEFAULTS, p.config)
41
- });
42
- const merged = OkfitConfig.merge(base, fileConfig);
43
- if (merged.okf_version !== void 0 && merged.okf_version !== OKF_SPEC_VERSION) yield* Console.error(`warning: okf_version "${merged.okf_version}" does not match this okfit's spec version "${OKF_SPEC_VERSION}"; continuing`);
44
- const discovered = discoveredSource === void 0 ? Option.none() : Option.some({
45
- path: discoveredSource.path,
46
- resolver: discoveredSource.resolver,
47
- ...discoveredSource.match?.dir !== void 0 ? { dir: discoveredSource.match.dir } : {}
48
- });
49
- const projectRoot = resolveProjectRoot({
50
- pathArg: input.pathArg,
51
- explicitConfigPath: input.explicitConfigPath,
52
- discovered,
53
- cwd: input.cwd,
54
- path
55
- });
56
- return {
57
- projectRoot,
58
- bundleRoot: resolveBundleRoot(projectRoot, merged, path),
59
- config: merged,
60
- profile,
61
- profileName,
62
- discovered
63
- };
64
- });
65
-
66
- //#endregion
67
- export { DEFAULT_PROFILE_NAME, resolveProjectConfig };
package/context/run.js DELETED
@@ -1,35 +0,0 @@
1
- import { Effect, FileSystem, Path } from "effect";
2
-
3
- //#region src/context/run.ts
4
- /**
5
- * The whole of `context`'s filesystem work: join `index.md` onto the bundle
6
- * root and stat it. Loading a bundle and running conformance/lint checks
7
- * over it never happen here — that is what makes `context` cheap enough
8
- * for a hook to run on every session start and every in-bundle write
9
- * (M-15).
10
- *
11
- * `"index.md"` is a literal here, not read from the profile's layout: the
12
- * spec fixes the reserved filename regardless of profile, and a
13
- * profile-less merge (`profile = "none"`) has no layout to read from at
14
- * all — `context` behaves identically with and without a profile.
15
- *
16
- * `exists` is `(path) => Effect.Effect<boolean, PlatformError>`, so an
17
- * unreadable parent directory would otherwise fail the command; here it is
18
- * absorbed to `false`, because "the hook could not stat index.md" and
19
- * "index.md is not there" are the same fact from a consumer's point of
20
- * view.
21
- *
22
- * @public
23
- */
24
- const runContext = (options) => Effect.gen(function* () {
25
- const path = yield* Path.Path;
26
- const fs = yield* FileSystem.FileSystem;
27
- const indexPath = path.join(options.bundleRoot, "index.md");
28
- return {
29
- indexPath,
30
- indexExists: yield* fs.exists(indexPath).pipe(Effect.orElseSucceed(() => false))
31
- };
32
- });
33
-
34
- //#endregion
35
- export { runContext };