@okfit/engine 0.8.0 → 0.9.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/README.md +2 -0
- package/edits/FrontmatterEdits.js +76 -0
- package/index.d.ts +60 -9
- package/index.js +2 -1
- package/package.json +3 -2
- package/render/distribution.js +3 -6
- package/tsdoc-metadata.json +1 -1
- package/verify/locate.js +101 -33
- package/verify/splice.js +25 -1
- package/version.js +1 -1
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@ The shared engine behind [okfit](https://github.com/spencerbeggs/okfit): the pla
|
|
|
4
4
|
|
|
5
5
|
You probably want `@okfit/plugin` (both bins), `@okfit/cli` (the `okfit` bin) or `@okfit/mcp` (the `okfit-mcp` bin) instead. This package is what they are built from.
|
|
6
6
|
|
|
7
|
+
`FrontmatterEdits` is a public facade over the `verify` splice machinery: it produces `MarkdownEdit`s for a concept's top-level `status` scalar and its `verified` list, with whole-file offsets into the source as passed (BOM included), so a consumer such as `@okfit/lsp` can apply them directly or map them to editor ranges without adjustment.
|
|
8
|
+
|
|
7
9
|
## License
|
|
8
10
|
|
|
9
11
|
[MIT](LICENSE)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { documentNewline, locate, locateTopLevelScalar, stripBom } from "../verify/locate.js";
|
|
2
|
+
import { splice, spliceTopLevelScalar } from "../verify/splice.js";
|
|
3
|
+
import { Effect, Runtime, Schema } from "effect";
|
|
4
|
+
import { MarkdownEdit } from "@effected/markdown";
|
|
5
|
+
|
|
6
|
+
//#region src/edits/FrontmatterEdits.ts
|
|
7
|
+
/**
|
|
8
|
+
* A top-level frontmatter key `FrontmatterEdits` was asked to edit turned
|
|
9
|
+
* out to be a shape it cannot splice: `shape` is whichever locator string
|
|
10
|
+
* `verify/locate.ts` produced for that key (e.g. `"flow-mapping"`,
|
|
11
|
+
* `"alias"`, `"status-block-scalar"`). The file is never touched when this
|
|
12
|
+
* is raised -- callers should surface it and let a human edit by hand.
|
|
13
|
+
*
|
|
14
|
+
* @public
|
|
15
|
+
*/
|
|
16
|
+
var UnsupportedFrontmatterError = class extends Schema.TaggedError()("UnsupportedFrontmatterError", {
|
|
17
|
+
key: Schema.String,
|
|
18
|
+
shape: Schema.String
|
|
19
|
+
}) {
|
|
20
|
+
[Runtime.errorExitCode] = 3;
|
|
21
|
+
get message() {
|
|
22
|
+
return `"${this.key}"'s frontmatter value is a shape FrontmatterEdits cannot edit (${this.shape}); edit it by hand`;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
/** `edit` shifted forward by `by` bytes, or `edit` itself when `by` is zero. */
|
|
26
|
+
const shift = (edit, by) => by === 0 ? edit : MarkdownEdit.make({
|
|
27
|
+
offset: edit.offset + by,
|
|
28
|
+
length: edit.length,
|
|
29
|
+
content: edit.content
|
|
30
|
+
});
|
|
31
|
+
/**
|
|
32
|
+
* A public facade over the `verify/locate.ts` and `verify/splice.ts`
|
|
33
|
+
* machinery: byte-range `MarkdownEdit`s for a concept's top-level `status`
|
|
34
|
+
* scalar and its `verified` list. `verified` builds the exact same edit
|
|
35
|
+
* `okfit verify`'s `prepareVerify` builds -- same `locate`, same `splice`,
|
|
36
|
+
* same `documentNewline` newline choice -- so its output matches
|
|
37
|
+
* `okfit verify`'s splice byte for byte; `status` reuses the identical
|
|
38
|
+
* newline rule for consistency, over `locateTopLevelScalar` and
|
|
39
|
+
* `spliceTopLevelScalar` instead.
|
|
40
|
+
*
|
|
41
|
+
* Every offset returned is a WHOLE-FILE offset into `source` AS PASSED --
|
|
42
|
+
* BOM included when present -- so a caller can apply the edits with
|
|
43
|
+
* `MarkdownEdit.applyAll(source, edits)` directly, or map them to editor
|
|
44
|
+
* ranges without adjustment. Internally the BOM is stripped for parsing and
|
|
45
|
+
* its length added back to every offset before returning.
|
|
46
|
+
*
|
|
47
|
+
* @public
|
|
48
|
+
*/
|
|
49
|
+
var FrontmatterEdits = class {
|
|
50
|
+
constructor() {}
|
|
51
|
+
/** Edits that set top-level `status` to `status`, replacing an existing scalar or inserting after `title:` (else `type:`). */
|
|
52
|
+
static status = (source, status) => Effect.gen(function* () {
|
|
53
|
+
const { text, bom } = stripBom(source);
|
|
54
|
+
const target = yield* locateTopLevelScalar(text, "status");
|
|
55
|
+
if (target._tag === "unsupported") return yield* new UnsupportedFrontmatterError({
|
|
56
|
+
key: "status",
|
|
57
|
+
shape: target.shape
|
|
58
|
+
});
|
|
59
|
+
const edit = spliceTopLevelScalar(target, "status", status, documentNewline(text));
|
|
60
|
+
return [shift(edit, bom.length)];
|
|
61
|
+
});
|
|
62
|
+
/** Edits that append one verified entry, the same splice `okfit verify` performs. */
|
|
63
|
+
static verified = (source, entry) => Effect.gen(function* () {
|
|
64
|
+
const { text, bom } = stripBom(source);
|
|
65
|
+
const target = yield* locate(text);
|
|
66
|
+
if (target._tag === "unsupported") return yield* new UnsupportedFrontmatterError({
|
|
67
|
+
key: "verified",
|
|
68
|
+
shape: target.shape
|
|
69
|
+
});
|
|
70
|
+
const edit = splice(target, entry, documentNewline(text));
|
|
71
|
+
return [shift(edit, bom.length)];
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
//#endregion
|
|
76
|
+
export { FrontmatterEdits, UnsupportedFrontmatterError };
|
package/index.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { Actor, BundleLoadError, ConceptId, Diagnostic, DiagnosticRange, DiagnosticSeverity, GraphLink, GraphNode, LinkGraph, LoadedBundle, LoadedConcept, OkfitConfig, OkfitConfigFile, StaleConcept, ValidationReport } from "@okfit/core";
|
|
1
|
+
import { Actor, BundleLoadError, ConceptId, Diagnostic, DiagnosticRange, DiagnosticSeverity, GraphLink, GraphNode, LinkGraph, LoadedBundle, LoadedConcept, OkfitConfig, OkfitConfigFile, StaleConcept, Status, ValidationReport } from "@okfit/core";
|
|
2
2
|
import { Context, Crypto, DateTime, Effect, FileSystem, Layer, Option, Path, PlatformError, Result, Runtime, Schema } from "effect";
|
|
3
3
|
import { AppDirs, Xdg } from "@effected/xdg";
|
|
4
4
|
import { ConfigReadError } from "@effected/config-file";
|
|
5
5
|
import { GeneratedAtError, GitHistory, GitHistoryError, Layout, Profile, ProfileDiagnostic } from "@okfit/profiles";
|
|
6
|
-
import { FrontmatterWriteError, MarkdownParseError } from "@effected/markdown";
|
|
6
|
+
import { FrontmatterWriteError, MarkdownEdit, MarkdownParseError } from "@effected/markdown";
|
|
7
|
+
import { YamlParseError } from "@effected/yaml";
|
|
7
8
|
import * as NodeServices from "@effect/platform-node/NodeServices";
|
|
9
|
+
import { Distribution as Distribution$1 } from "@effected/engine";
|
|
8
10
|
import { Git, GitCommandError, UnknownRefError } from "@effected/git";
|
|
9
11
|
//#region src/config/anchor.d.ts
|
|
10
12
|
/**
|
|
@@ -375,6 +377,53 @@ interface ContextResult {
|
|
|
375
377
|
*/
|
|
376
378
|
export declare const runContext: (options: ContextRunOptions) => Effect.Effect<ContextResult, never, FileSystem.FileSystem | Path.Path>;
|
|
377
379
|
//#endregion
|
|
380
|
+
//#region src/edits/FrontmatterEdits.d.ts
|
|
381
|
+
declare const UnsupportedFrontmatterError_base: Schema.Class<UnsupportedFrontmatterError, Schema.TaggedStruct<"UnsupportedFrontmatterError", {
|
|
382
|
+
readonly key: Schema.String;
|
|
383
|
+
readonly shape: Schema.String;
|
|
384
|
+
}>, import("effect/Cause").YieldableError>;
|
|
385
|
+
/**
|
|
386
|
+
* A top-level frontmatter key `FrontmatterEdits` was asked to edit turned
|
|
387
|
+
* out to be a shape it cannot splice: `shape` is whichever locator string
|
|
388
|
+
* `verify/locate.ts` produced for that key (e.g. `"flow-mapping"`,
|
|
389
|
+
* `"alias"`, `"status-block-scalar"`). The file is never touched when this
|
|
390
|
+
* is raised -- callers should surface it and let a human edit by hand.
|
|
391
|
+
*
|
|
392
|
+
* @public
|
|
393
|
+
*/
|
|
394
|
+
export declare class UnsupportedFrontmatterError extends UnsupportedFrontmatterError_base {
|
|
395
|
+
readonly [Runtime.errorExitCode] = 3;
|
|
396
|
+
get message(): string;
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* A public facade over the `verify/locate.ts` and `verify/splice.ts`
|
|
400
|
+
* machinery: byte-range `MarkdownEdit`s for a concept's top-level `status`
|
|
401
|
+
* scalar and its `verified` list. `verified` builds the exact same edit
|
|
402
|
+
* `okfit verify`'s `prepareVerify` builds -- same `locate`, same `splice`,
|
|
403
|
+
* same `documentNewline` newline choice -- so its output matches
|
|
404
|
+
* `okfit verify`'s splice byte for byte; `status` reuses the identical
|
|
405
|
+
* newline rule for consistency, over `locateTopLevelScalar` and
|
|
406
|
+
* `spliceTopLevelScalar` instead.
|
|
407
|
+
*
|
|
408
|
+
* Every offset returned is a WHOLE-FILE offset into `source` AS PASSED --
|
|
409
|
+
* BOM included when present -- so a caller can apply the edits with
|
|
410
|
+
* `MarkdownEdit.applyAll(source, edits)` directly, or map them to editor
|
|
411
|
+
* ranges without adjustment. Internally the BOM is stripped for parsing and
|
|
412
|
+
* its length added back to every offset before returning.
|
|
413
|
+
*
|
|
414
|
+
* @public
|
|
415
|
+
*/
|
|
416
|
+
export declare class FrontmatterEdits {
|
|
417
|
+
private constructor();
|
|
418
|
+
/** Edits that set top-level `status` to `status`, replacing an existing scalar or inserting after `title:` (else `type:`). */
|
|
419
|
+
static readonly status: (source: string, status: Status) => Effect.Effect<ReadonlyArray<MarkdownEdit>, YamlParseError | UnsupportedFrontmatterError>;
|
|
420
|
+
/** Edits that append one verified entry, the same splice `okfit verify` performs. */
|
|
421
|
+
static readonly verified: (source: string, entry: {
|
|
422
|
+
readonly by: string;
|
|
423
|
+
readonly at: string;
|
|
424
|
+
}) => Effect.Effect<ReadonlyArray<MarkdownEdit>, YamlParseError | UnsupportedFrontmatterError>;
|
|
425
|
+
}
|
|
426
|
+
//#endregion
|
|
378
427
|
//#region src/external/ExternalReferences.d.ts
|
|
379
428
|
/**
|
|
380
429
|
* Reachability of an external reference: `unknown` means never checked.
|
|
@@ -756,16 +805,18 @@ export declare const contextEnvelope: (input: {
|
|
|
756
805
|
* reports; `engine_version` and `okf_version` are the pair a reader
|
|
757
806
|
* compares.
|
|
758
807
|
*
|
|
808
|
+
* Re-exported from `@effected/engine`'s own `Distribution` -- the shape
|
|
809
|
+
* both `@okfit/cli`'s `CurrentDistribution` (K-137/carrier-version-threading)
|
|
810
|
+
* and every JSON envelope in this package agree on, instead of two
|
|
811
|
+
* independently hand-rolled `{ name, version }` structs drifting apart.
|
|
812
|
+
*
|
|
759
813
|
* @public
|
|
760
814
|
*/
|
|
761
|
-
|
|
762
|
-
readonly name: string;
|
|
763
|
-
readonly version: string;
|
|
764
|
-
}
|
|
815
|
+
type Distribution = Distribution$1;
|
|
765
816
|
/** Every envelope's `distribution` field: a `Distribution` or `null`. @public */
|
|
766
|
-
export declare const DistributionField: Schema.NullOr<Schema.Struct<{
|
|
767
|
-
readonly name: Schema.String;
|
|
768
|
-
readonly version: Schema.String;
|
|
817
|
+
export declare const DistributionField: import("effect/Schema").NullOr<import("effect/Schema").Struct<{
|
|
818
|
+
readonly name: import("effect/Schema").String;
|
|
819
|
+
readonly version: import("effect/Schema").String;
|
|
769
820
|
}>>;
|
|
770
821
|
/** @public */
|
|
771
822
|
export type DistributionField = typeof DistributionField.Type;
|
package/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { ConfigMalformedError, ConfigPathNotFoundError, DocumentPathError, InitO
|
|
|
3
3
|
import { buildConfigLayer, provideConfig } from "./config/layer.js";
|
|
4
4
|
import { DEFAULT_PROFILE_NAME, resolveProjectConfig } from "./config/resolve.js";
|
|
5
5
|
import { runContext } from "./context/run.js";
|
|
6
|
+
import { FrontmatterEdits, UnsupportedFrontmatterError } from "./edits/FrontmatterEdits.js";
|
|
6
7
|
import { ExternalReferences, ReferenceCheck, ReferenceState } from "./external/ExternalReferences.js";
|
|
7
8
|
import { runGraph } from "./graph/run.js";
|
|
8
9
|
import { CONFIG_RELATIVE_PATH, configValue, files, targetPaths } from "./init/scaffold.js";
|
|
@@ -27,4 +28,4 @@ import { BundleSession } from "./session/BundleSession.js";
|
|
|
27
28
|
import { runStale } from "./stale/run.js";
|
|
28
29
|
import { runVerify, runVerifyBatch } from "./verify/run.js";
|
|
29
30
|
|
|
30
|
-
export { BundleSession, CONFIG_RELATIVE_PATH, ConfigMalformedError, ConfigPathNotFoundError, ContextEnvelope, ContextField, ContextFieldValue, ContextTag, ContextType, DEFAULT_PROFILE_NAME, DistributionField, DocumentPathError, ENGINE_VERSION, ExternalReferences, GraphEdgeEnvelope, GraphEnvelope, GraphNodeEnvelope, GraphSummary, InitOverwriteError, JsonDiagnostic, JsonEnvelope, JsonErrorEnvelope, JsonSummary, Now, OkfitPlatform, OverlayDocuments, ReferenceCheck, ReferenceState, SkipReason, StaleEnvelope, StaleItem, StaleSummary, SyncEnvelope, SyncModeEnvelope, SyncStagedLogError, VerifyBatchEnvelope, VerifyConceptNotFoundError, VerifyEnvelope, VerifySelectionError, VerifyUnsupportedFrontmatterError, buildConfigLayer, collect, conceptFor, configValue, contextEnvelope, files, forDiagnostics, graphEnvelope, json, jsonError, layerOverlayFileSystem, makeOverlayFileSystem, provideConfig, provideDocuments, resolveBundleRoot, resolveDocumentPath, resolveProjectConfig, resolveProjectRoot, run, runContext, runGraph, runStale, runSync, runVerify, runVerifyBatch, sort, staleEnvelope, syncEnvelope, tally, targetPaths, verifyBatchEnvelope, verifyEnvelope, withFallbackRange };
|
|
31
|
+
export { BundleSession, CONFIG_RELATIVE_PATH, ConfigMalformedError, ConfigPathNotFoundError, ContextEnvelope, ContextField, ContextFieldValue, ContextTag, ContextType, DEFAULT_PROFILE_NAME, DistributionField, DocumentPathError, ENGINE_VERSION, ExternalReferences, FrontmatterEdits, GraphEdgeEnvelope, GraphEnvelope, GraphNodeEnvelope, GraphSummary, InitOverwriteError, JsonDiagnostic, JsonEnvelope, JsonErrorEnvelope, JsonSummary, Now, OkfitPlatform, OverlayDocuments, ReferenceCheck, ReferenceState, SkipReason, StaleEnvelope, StaleItem, StaleSummary, SyncEnvelope, SyncModeEnvelope, SyncStagedLogError, UnsupportedFrontmatterError, VerifyBatchEnvelope, VerifyConceptNotFoundError, VerifyEnvelope, VerifySelectionError, VerifyUnsupportedFrontmatterError, buildConfigLayer, collect, conceptFor, configValue, contextEnvelope, files, forDiagnostics, graphEnvelope, json, jsonError, layerOverlayFileSystem, makeOverlayFileSystem, provideConfig, provideDocuments, resolveBundleRoot, resolveDocumentPath, resolveProjectConfig, resolveProjectRoot, run, runContext, runGraph, runStale, runSync, runVerify, runVerifyBatch, sort, staleEnvelope, syncEnvelope, tally, targetPaths, verifyBatchEnvelope, verifyEnvelope, withFallbackRange };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@okfit/engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "The okfit engine: platform layer, config discovery, and the validate, verify, sync, init and context programs shared by the okfit CLI and MCP server.",
|
|
6
6
|
"keywords": [
|
|
@@ -37,10 +37,11 @@
|
|
|
37
37
|
"@effect/platform-node": "4.0.0-rc.117",
|
|
38
38
|
"@effected/app": "^0.18.0",
|
|
39
39
|
"@effected/config-file": "^0.12.0",
|
|
40
|
+
"@effected/engine": "^0.1.0",
|
|
40
41
|
"@effected/git": "^0.17.0",
|
|
41
42
|
"@effected/glob": "^0.8.0",
|
|
42
43
|
"@effected/jsonc": "^0.13.0",
|
|
43
|
-
"@effected/markdown": "^0.12.
|
|
44
|
+
"@effected/markdown": "^0.12.1",
|
|
44
45
|
"@effected/store": "^0.10.0",
|
|
45
46
|
"@effected/toml": "^0.9.0",
|
|
46
47
|
"@effected/walker": "^0.12.0",
|
package/render/distribution.js
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DistributionField } from "@effected/engine";
|
|
2
2
|
|
|
3
3
|
//#region src/render/distribution.ts
|
|
4
4
|
/** Every envelope's `distribution` field: a `Distribution` or `null`. @public */
|
|
5
|
-
const DistributionField =
|
|
6
|
-
name: Schema.String,
|
|
7
|
-
version: Schema.String
|
|
8
|
-
}));
|
|
5
|
+
const DistributionField$1 = DistributionField;
|
|
9
6
|
|
|
10
7
|
//#endregion
|
|
11
|
-
export { DistributionField };
|
|
8
|
+
export { DistributionField$1 as DistributionField };
|
package/tsdoc-metadata.json
CHANGED
package/verify/locate.js
CHANGED
|
@@ -55,19 +55,16 @@ const hasMergeKey = (node) => {
|
|
|
55
55
|
return false;
|
|
56
56
|
};
|
|
57
57
|
/**
|
|
58
|
-
*
|
|
59
|
-
* `
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* plus the opening fence's own terminator, taken from `split`'s fidelity
|
|
65
|
-
* field rather than re-derived with `indexOf` (contract §12 note 9). Every
|
|
66
|
-
* whole-file offset below is `valueStart + <yaml offset>`.
|
|
58
|
+
* The shared prologue every locator in this file starts with: split
|
|
59
|
+
* `source`'s frontmatter, parse its value, and confirm the parsed document
|
|
60
|
+
* is a (non-flow) top-level mapping. Returns the same `"no-frontmatter"` /
|
|
61
|
+
* `"not-a-mapping"` `unsupported` shapes each locator returned inline
|
|
62
|
+
* before this helper existed -- callers still name those shapes themselves,
|
|
63
|
+
* this only removes the duplicated parsing steps that produced them.
|
|
67
64
|
*
|
|
68
65
|
* @internal
|
|
69
66
|
*/
|
|
70
|
-
const
|
|
67
|
+
const parseTopLevelMapping = Effect.fn("okfit/verify/parseTopLevelMapping")(function* (source) {
|
|
71
68
|
const block = FrontmatterSource.split(source).frontmatter;
|
|
72
69
|
if (block === void 0) return {
|
|
73
70
|
_tag: "unsupported",
|
|
@@ -80,6 +77,29 @@ const locate = Effect.fn("okfit/verify/locate")(function* (source) {
|
|
|
80
77
|
_tag: "unsupported",
|
|
81
78
|
shape: "not-a-mapping"
|
|
82
79
|
};
|
|
80
|
+
return {
|
|
81
|
+
value,
|
|
82
|
+
valueStart,
|
|
83
|
+
contents
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
/**
|
|
87
|
+
* Split `source`'s frontmatter, parse its value, find the top-level
|
|
88
|
+
* `verified` pair, and classify it (contract §3.1). Fails only when the
|
|
89
|
+
* frontmatter YAML is fatally unparseable — impossible for a concept that
|
|
90
|
+
* reached `bundle.concepts`, but typed rather than assumed.
|
|
91
|
+
*
|
|
92
|
+
* Offset translation is `valueStart = 3 + newline.length` — `"---".length`
|
|
93
|
+
* plus the opening fence's own terminator, taken from `split`'s fidelity
|
|
94
|
+
* field rather than re-derived with `indexOf` (contract §12 note 9). Every
|
|
95
|
+
* whole-file offset below is `valueStart + <yaml offset>`.
|
|
96
|
+
*
|
|
97
|
+
* @internal
|
|
98
|
+
*/
|
|
99
|
+
const locate = Effect.fn("okfit/verify/locate")(function* (source) {
|
|
100
|
+
const parsed = yield* parseTopLevelMapping(source);
|
|
101
|
+
if ("_tag" in parsed) return parsed;
|
|
102
|
+
const { value, valueStart, contents } = parsed;
|
|
83
103
|
const pair = contents.items.find((item) => item.key instanceof YamlScalar && item.key.value === "verified");
|
|
84
104
|
if (pair === void 0) return {
|
|
85
105
|
_tag: "absent",
|
|
@@ -158,18 +178,9 @@ const locate = Effect.fn("okfit/verify/locate")(function* (source) {
|
|
|
158
178
|
* @internal
|
|
159
179
|
*/
|
|
160
180
|
const locateGeneratedField = Effect.fn("okfit/verify/locateGeneratedField")(function* (source, field) {
|
|
161
|
-
const
|
|
162
|
-
if (
|
|
163
|
-
|
|
164
|
-
shape: "no-frontmatter"
|
|
165
|
-
};
|
|
166
|
-
const value = block.value;
|
|
167
|
-
const valueStart = 3 + (block.newline ?? "\n").length;
|
|
168
|
-
const contents = (yield* YamlDocument.parse(value)).contents;
|
|
169
|
-
if (!(contents instanceof YamlMap)) return {
|
|
170
|
-
_tag: "unsupported",
|
|
171
|
-
shape: "not-a-mapping"
|
|
172
|
-
};
|
|
181
|
+
const parsed = yield* parseTopLevelMapping(source);
|
|
182
|
+
if ("_tag" in parsed) return parsed;
|
|
183
|
+
const { value, valueStart, contents } = parsed;
|
|
173
184
|
const pair = contents.items.find((item) => item.key instanceof YamlScalar && item.key.value === "generated");
|
|
174
185
|
if (pair === void 0) return {
|
|
175
186
|
_tag: "unsupported",
|
|
@@ -254,20 +265,77 @@ const locateGenerated = (source) => locateGeneratedField(source, "at");
|
|
|
254
265
|
* @internal
|
|
255
266
|
*/
|
|
256
267
|
const locateGeneratedBodySha256 = (source) => locateGeneratedField(source, "body_sha256");
|
|
257
|
-
/**
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
268
|
+
/**
|
|
269
|
+
* Locate a top-level scalar key (`key`) inside a concept's frontmatter,
|
|
270
|
+
* mirroring `locateGeneratedField`'s parsing prologue. Unlike `generated`'s
|
|
271
|
+
* fields, this key is a direct top-level sibling, not nested inside a block
|
|
272
|
+
* mapping, so there is no intermediate `generated:` lookup and no indent to
|
|
273
|
+
* track for the insert case -- `title:`/`type:` anchor the insert instead.
|
|
274
|
+
*
|
|
275
|
+
* @internal
|
|
276
|
+
*/
|
|
277
|
+
const locateTopLevelScalar = Effect.fn("okfit/verify/locateTopLevelScalar")(function* (source, key) {
|
|
278
|
+
const parsed = yield* parseTopLevelMapping(source);
|
|
279
|
+
if ("_tag" in parsed) return parsed;
|
|
280
|
+
const { value, valueStart, contents } = parsed;
|
|
281
|
+
if (contents.style === "flow") return {
|
|
261
282
|
_tag: "unsupported",
|
|
262
|
-
shape: "
|
|
283
|
+
shape: "flow-mapping"
|
|
263
284
|
};
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
285
|
+
const pair = contents.items.find((item) => item.key instanceof YamlScalar && item.key.value === key);
|
|
286
|
+
if (pair !== void 0) {
|
|
287
|
+
const node = pair.value;
|
|
288
|
+
if (node === null) return {
|
|
289
|
+
_tag: "unsupported",
|
|
290
|
+
shape: `${key}-empty`
|
|
291
|
+
};
|
|
292
|
+
if (node instanceof YamlAlias) return {
|
|
293
|
+
_tag: "unsupported",
|
|
294
|
+
shape: "alias"
|
|
295
|
+
};
|
|
296
|
+
if (!(node instanceof YamlScalar)) return {
|
|
297
|
+
_tag: "unsupported",
|
|
298
|
+
shape: `${key}-not-scalar`
|
|
299
|
+
};
|
|
300
|
+
if (node.style === "block-literal" || node.style === "block-folded") return {
|
|
301
|
+
_tag: "unsupported",
|
|
302
|
+
shape: `${key}-block-scalar`
|
|
303
|
+
};
|
|
304
|
+
return {
|
|
305
|
+
_tag: "replaceScalar",
|
|
306
|
+
start: valueStart + node.offset,
|
|
307
|
+
end: valueStart + node.offset + node.length,
|
|
308
|
+
quote: node.style
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
const findKey = (name) => contents.items.find((item) => item.key instanceof YamlScalar && item.key.value === name);
|
|
312
|
+
const titlePair = findKey("title");
|
|
313
|
+
const anchor = titlePair !== void 0 ? {
|
|
314
|
+
pair: titlePair,
|
|
315
|
+
afterKey: "title"
|
|
316
|
+
} : void 0;
|
|
317
|
+
const typePair = anchor === void 0 ? findKey("type") : void 0;
|
|
318
|
+
const resolved = anchor ?? (typePair !== void 0 ? {
|
|
319
|
+
pair: typePair,
|
|
320
|
+
afterKey: "type"
|
|
321
|
+
} : void 0);
|
|
322
|
+
if (resolved === void 0) return {
|
|
268
323
|
_tag: "unsupported",
|
|
269
|
-
shape: "
|
|
324
|
+
shape: "no-anchor-key"
|
|
325
|
+
};
|
|
326
|
+
const end = resolved.pair.value !== null ? resolved.pair.value.offset + resolved.pair.value.length : resolved.pair.key.offset + resolved.pair.key.length;
|
|
327
|
+
const nl = value.indexOf("\n", end);
|
|
328
|
+
return {
|
|
329
|
+
_tag: "insertAfterKey",
|
|
330
|
+
insertAt: valueStart + (value[end - 1] === "\n" ? end : nl === -1 ? value.length : nl + 1),
|
|
331
|
+
afterKey: resolved.afterKey
|
|
270
332
|
};
|
|
333
|
+
});
|
|
334
|
+
/** @internal */
|
|
335
|
+
const locateGeneratedBlock = Effect.fn("okfit/verify/locateGeneratedBlock")(function* (source) {
|
|
336
|
+
const parsed = yield* parseTopLevelMapping(source);
|
|
337
|
+
if ("_tag" in parsed) return parsed;
|
|
338
|
+
const { value, valueStart, contents } = parsed;
|
|
271
339
|
if (contents.items.find((item) => item.key instanceof YamlScalar && item.key.value === "generated") !== void 0) return {
|
|
272
340
|
_tag: "unsupported",
|
|
273
341
|
shape: "generated-present"
|
|
@@ -279,4 +347,4 @@ const locateGeneratedBlock = Effect.fn("okfit/verify/locateGeneratedBlock")(func
|
|
|
279
347
|
});
|
|
280
348
|
|
|
281
349
|
//#endregion
|
|
282
|
-
export { detectNewline, documentNewline, indentAt, locate, locateGenerated, locateGeneratedBlock, locateGeneratedBodySha256, stripBom };
|
|
350
|
+
export { detectNewline, documentNewline, indentAt, locate, locateGenerated, locateGeneratedBlock, locateGeneratedBodySha256, locateTopLevelScalar, stripBom };
|
package/verify/splice.js
CHANGED
|
@@ -93,6 +93,30 @@ const spliceGeneratedFields = (atTarget, bodySha256Target, encodedAt, bodySha256
|
|
|
93
93
|
return [spliceGenerated(atTarget, encodedAt, newline, "at"), spliceGenerated(bodySha256Target, bodySha256, newline, "body_sha256")];
|
|
94
94
|
};
|
|
95
95
|
/**
|
|
96
|
+
* Build the one edit for `target` (a top-level scalar key such as
|
|
97
|
+
* `status`). Never calls a YAML stringifier: the only syntax emitted is
|
|
98
|
+
* `<key>: `, the preserved quote character (if any), and `newline`.
|
|
99
|
+
*
|
|
100
|
+
* @internal
|
|
101
|
+
*/
|
|
102
|
+
const spliceTopLevelScalar = (target, key, value, newline) => {
|
|
103
|
+
switch (target._tag) {
|
|
104
|
+
case "insertAfterKey": return MarkdownEdit.make({
|
|
105
|
+
offset: target.insertAt,
|
|
106
|
+
length: 0,
|
|
107
|
+
content: `${key}: ${value}${newline}`
|
|
108
|
+
});
|
|
109
|
+
case "replaceScalar": {
|
|
110
|
+
const quoted = target.quote === "single-quoted" ? `'${value}'` : target.quote === "double-quoted" ? `"${value}"` : value;
|
|
111
|
+
return MarkdownEdit.make({
|
|
112
|
+
offset: target.start,
|
|
113
|
+
length: target.end - target.start,
|
|
114
|
+
content: quoted
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
96
120
|
* A whole `generated:` block mapping, appended as the LAST top-level key
|
|
97
121
|
* (issue #73) — block style, matching how this repository writes every
|
|
98
122
|
* `generated:` and how `splice`'s own `absent` case writes `verified:`.
|
|
@@ -106,4 +130,4 @@ const spliceGeneratedBlock = (target, fields, newline) => MarkdownEdit.make({
|
|
|
106
130
|
});
|
|
107
131
|
|
|
108
132
|
//#endregion
|
|
109
|
-
export { splice, spliceGenerated, spliceGeneratedBlock, spliceGeneratedFields };
|
|
133
|
+
export { splice, spliceGenerated, spliceGeneratedBlock, spliceGeneratedFields, spliceTopLevelScalar };
|