@okfit/engine 0.11.1 → 0.12.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/edits/FrontmatterEdits.js +58 -2
- package/errors.js +13 -1
- package/index.d.ts +93 -1
- package/index.js +4 -3
- package/package.json +3 -3
- package/render/sync.js +28 -5
- package/sync/publication.js +98 -0
- package/validate/resources.js +3 -3
- package/validate/run.js +11 -2
- package/validate/surfaces.js +98 -0
- package/verify/locate.js +21 -4
- package/verify/splice.js +4 -3
- package/version.js +1 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { documentNewline, locate, locateTopLevelScalar, stripBom } from "../verify/locate.js";
|
|
2
2
|
import { splice, spliceTopLevelScalar } from "../verify/splice.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { DiagnosticRange } from "@okfit/core";
|
|
4
|
+
import { Effect, Result, Runtime, Schema } from "effect";
|
|
5
|
+
import { MarkdownDocument, MarkdownEdit } from "@effected/markdown";
|
|
5
6
|
|
|
6
7
|
//#region src/edits/FrontmatterEdits.ts
|
|
7
8
|
/**
|
|
@@ -70,6 +71,61 @@ var FrontmatterEdits = class {
|
|
|
70
71
|
const edit = splice(target, entry, documentNewline(text));
|
|
71
72
|
return [shift(edit, bom.length)];
|
|
72
73
|
});
|
|
74
|
+
/**
|
|
75
|
+
* Edits that stamp `renders[i].body_sha256` with `digests[i]`: an existing value is replaced in
|
|
76
|
+
* place, a missing key is inserted on the line after `renders[i].path` at that key's column.
|
|
77
|
+
* Block-style `renders` only; flow style fails with `UnsupportedFrontmatterError`.
|
|
78
|
+
*/
|
|
79
|
+
static rendersDigests = (source, digests) => Effect.gen(function* () {
|
|
80
|
+
const { text, bom } = stripBom(source);
|
|
81
|
+
const unsupported = (shape) => new UnsupportedFrontmatterError({
|
|
82
|
+
key: "renders",
|
|
83
|
+
shape
|
|
84
|
+
});
|
|
85
|
+
const parsed = MarkdownDocument.parseResult(text, { frontmatter: true });
|
|
86
|
+
if (Result.isFailure(parsed)) return yield* unsupported("unparseable");
|
|
87
|
+
const document = parsed.success;
|
|
88
|
+
const block = DiagnosticRange.forFrontmatterPath(document, []);
|
|
89
|
+
const rendersSpan = DiagnosticRange.forFrontmatterPath(document, ["renders"]);
|
|
90
|
+
if (block === void 0 || rendersSpan === void 0 || rendersSpan.offset === block.offset) return yield* unsupported("absent");
|
|
91
|
+
if (text.charAt(rendersSpan.offset) === "[") return yield* unsupported("flow-sequence");
|
|
92
|
+
const newline = documentNewline(text);
|
|
93
|
+
const edits = [];
|
|
94
|
+
for (const [index, digest] of digests.entries()) {
|
|
95
|
+
const digestSpan = DiagnosticRange.forFrontmatterPath(document, [
|
|
96
|
+
"renders",
|
|
97
|
+
index,
|
|
98
|
+
"body_sha256"
|
|
99
|
+
]);
|
|
100
|
+
if (digestSpan !== void 0 && digestSpan.offset !== block.offset) {
|
|
101
|
+
const content = digestSpan.length === 0 ? ` ${digest}` : digest;
|
|
102
|
+
edits.push(shift(MarkdownEdit.make({
|
|
103
|
+
offset: digestSpan.offset,
|
|
104
|
+
length: digestSpan.length,
|
|
105
|
+
content
|
|
106
|
+
}), bom.length));
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const pathSpan = DiagnosticRange.forFrontmatterPath(document, [
|
|
110
|
+
"renders",
|
|
111
|
+
index,
|
|
112
|
+
"path"
|
|
113
|
+
]);
|
|
114
|
+
if (pathSpan === void 0 || pathSpan.offset === block.offset) return yield* unsupported("missing-path");
|
|
115
|
+
const lineStart = text.lastIndexOf("\n", pathSpan.offset - 1) + 1;
|
|
116
|
+
const prefix = /^[ ]*(?:-[ ]+)*(?:"path"|'path'|path)[ ]*:[ ]*$/u.exec(text.slice(lineStart, pathSpan.offset));
|
|
117
|
+
if (prefix === null) return yield* unsupported("flow-mapping");
|
|
118
|
+
const column = /^[ ]*(?:-[ ]+)*/u.exec(prefix[0])?.[0].length ?? 0;
|
|
119
|
+
const newlineAt = text.indexOf("\n", pathSpan.offset + pathSpan.length);
|
|
120
|
+
const lineEnd = newlineAt === -1 ? text.length : newlineAt > 0 && text.charAt(newlineAt - 1) === "\r" ? newlineAt - 1 : newlineAt;
|
|
121
|
+
edits.push(shift(MarkdownEdit.make({
|
|
122
|
+
offset: lineEnd,
|
|
123
|
+
length: 0,
|
|
124
|
+
content: `${newline}${" ".repeat(column)}body_sha256: ${digest}`
|
|
125
|
+
}), bom.length));
|
|
126
|
+
}
|
|
127
|
+
return edits;
|
|
128
|
+
});
|
|
73
129
|
};
|
|
74
130
|
|
|
75
131
|
//#endregion
|
package/errors.js
CHANGED
|
@@ -121,6 +121,18 @@ var SyncStagedLogError = class extends Schema.TaggedError()("SyncStagedLogError"
|
|
|
121
121
|
}
|
|
122
122
|
};
|
|
123
123
|
/**
|
|
124
|
+
* `okfit sync --publication` runs nothing else, so combining it with a mode
|
|
125
|
+
* or selection flag (`--only`, `--staged`, `--since`) is a usage error. Exit 64.
|
|
126
|
+
*
|
|
127
|
+
* @public
|
|
128
|
+
*/
|
|
129
|
+
var SyncPublicationConflictError = class extends Schema.TaggedError()("SyncPublicationConflictError", { flags: Schema.Array(Schema.String) }) {
|
|
130
|
+
[Runtime.errorExitCode] = 64;
|
|
131
|
+
get message() {
|
|
132
|
+
return `--publication restamps one Publication and runs nothing else; it cannot combine with ${this.flags.join(", ")}`;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
/**
|
|
124
136
|
* Issue #138: `okfit verify`'s selection was contradictory or empty. Exit 64: a usage error.
|
|
125
137
|
*
|
|
126
138
|
* @public
|
|
@@ -222,4 +234,4 @@ var QuerySelectionError = class extends Schema.TaggedError()("QuerySelectionErro
|
|
|
222
234
|
};
|
|
223
235
|
|
|
224
236
|
//#endregion
|
|
225
|
-
export { ConfigMalformedError, ConfigPathNotFoundError, DocumentPathError, InitOverwriteError, QueryConceptNotFoundError, QuerySelectionError, QueryUnknownVocabularyError, SyncStagedLogError, VerifyConceptNotFoundError, VerifySelectionError, VerifyUnsupportedFrontmatterError };
|
|
237
|
+
export { ConfigMalformedError, ConfigPathNotFoundError, DocumentPathError, InitOverwriteError, QueryConceptNotFoundError, QuerySelectionError, QueryUnknownVocabularyError, SyncPublicationConflictError, SyncStagedLogError, VerifyConceptNotFoundError, VerifySelectionError, VerifyUnsupportedFrontmatterError };
|
package/index.d.ts
CHANGED
|
@@ -173,6 +173,19 @@ export declare class SyncStagedLogError extends SyncStagedLogError_base {
|
|
|
173
173
|
readonly [Runtime.errorExitCode] = 64;
|
|
174
174
|
get message(): string;
|
|
175
175
|
}
|
|
176
|
+
declare const SyncPublicationConflictError_base: Schema.Class<SyncPublicationConflictError, Schema.TaggedStruct<"SyncPublicationConflictError", {
|
|
177
|
+
readonly flags: Schema.$Array<Schema.String>;
|
|
178
|
+
}>, import("effect/Cause").YieldableError>;
|
|
179
|
+
/**
|
|
180
|
+
* `okfit sync --publication` runs nothing else, so combining it with a mode
|
|
181
|
+
* or selection flag (`--only`, `--staged`, `--since`) is a usage error. Exit 64.
|
|
182
|
+
*
|
|
183
|
+
* @public
|
|
184
|
+
*/
|
|
185
|
+
export declare class SyncPublicationConflictError extends SyncPublicationConflictError_base {
|
|
186
|
+
readonly [Runtime.errorExitCode] = 64;
|
|
187
|
+
get message(): string;
|
|
188
|
+
}
|
|
176
189
|
declare const VerifySelectionError_base: Schema.Class<VerifySelectionError, Schema.TaggedStruct<"VerifySelectionError", {
|
|
177
190
|
readonly reason: Schema.Literals<readonly ["no-selection", "id-and-batch", "unknown-type", "status-conflict", "status-and-batch"]>;
|
|
178
191
|
readonly detail: Schema.optionalKey<Schema.String>;
|
|
@@ -466,6 +479,12 @@ export declare class FrontmatterEdits {
|
|
|
466
479
|
readonly by: string;
|
|
467
480
|
readonly at: string;
|
|
468
481
|
}) => Effect.Effect<ReadonlyArray<MarkdownEdit>, YamlParseError | UnsupportedFrontmatterError>;
|
|
482
|
+
/**
|
|
483
|
+
* Edits that stamp `renders[i].body_sha256` with `digests[i]`: an existing value is replaced in
|
|
484
|
+
* place, a missing key is inserted on the line after `renders[i].path` at that key's column.
|
|
485
|
+
* Block-style `renders` only; flow style fails with `UnsupportedFrontmatterError`.
|
|
486
|
+
*/
|
|
487
|
+
static readonly rendersDigests: (source: string, digests: ReadonlyArray<string>) => Effect.Effect<ReadonlyArray<MarkdownEdit>, YamlParseError | UnsupportedFrontmatterError>;
|
|
469
488
|
}
|
|
470
489
|
//#endregion
|
|
471
490
|
//#region src/external/ExternalReferences.d.ts
|
|
@@ -1480,6 +1499,22 @@ export declare const SyncModeEnvelope: Schema.Struct<{
|
|
|
1480
1499
|
}>;
|
|
1481
1500
|
/** @public */
|
|
1482
1501
|
export type SyncModeEnvelope = typeof SyncModeEnvelope.Type;
|
|
1502
|
+
/**
|
|
1503
|
+
* One `okfit sync --publication` result: the Publication id, whether its
|
|
1504
|
+
* file changed, and the digest stamped for each rendered source.
|
|
1505
|
+
*
|
|
1506
|
+
* @public
|
|
1507
|
+
*/
|
|
1508
|
+
export declare const PublicationEnvelope: Schema.Struct<{
|
|
1509
|
+
readonly id: Schema.String;
|
|
1510
|
+
readonly written: Schema.Boolean;
|
|
1511
|
+
readonly digests: Schema.$Array<Schema.Struct<{
|
|
1512
|
+
readonly path: Schema.String;
|
|
1513
|
+
readonly body_sha256: Schema.String;
|
|
1514
|
+
}>>;
|
|
1515
|
+
}>;
|
|
1516
|
+
/** @public */
|
|
1517
|
+
export type PublicationEnvelope = typeof PublicationEnvelope.Type;
|
|
1483
1518
|
/**
|
|
1484
1519
|
* Contract §14 note 5: `schema: 1` is INFERRED, not in the design's own
|
|
1485
1520
|
* §2 JSON example — every other envelope in this codebase
|
|
@@ -1529,6 +1564,15 @@ export declare const SyncEnvelope: Schema.Struct<{
|
|
|
1529
1564
|
readonly reason: Schema.Literals<readonly ["untracked", "dirty", "unborn", "generated-missing", "generated-unsupported", "log-unparseable"]>;
|
|
1530
1565
|
}>>;
|
|
1531
1566
|
}>;
|
|
1567
|
+
/** Present only for `okfit sync --publication`; then the three modes are all `selected: false`. */
|
|
1568
|
+
readonly publication: Schema.optionalKey<Schema.Struct<{
|
|
1569
|
+
readonly id: Schema.String;
|
|
1570
|
+
readonly written: Schema.Boolean;
|
|
1571
|
+
readonly digests: Schema.$Array<Schema.Struct<{
|
|
1572
|
+
readonly path: Schema.String;
|
|
1573
|
+
readonly body_sha256: Schema.String;
|
|
1574
|
+
}>>;
|
|
1575
|
+
}>>;
|
|
1532
1576
|
}>;
|
|
1533
1577
|
/** @public */
|
|
1534
1578
|
export type SyncEnvelope = typeof SyncEnvelope.Type;
|
|
@@ -1543,7 +1587,8 @@ export declare const syncEnvelope: (input: {
|
|
|
1543
1587
|
readonly okfitVersion: string;
|
|
1544
1588
|
readonly root: string;
|
|
1545
1589
|
readonly dryRun: boolean;
|
|
1546
|
-
readonly result
|
|
1590
|
+
readonly result?: SyncResult;
|
|
1591
|
+
readonly publication?: PublicationEnvelope;
|
|
1547
1592
|
readonly distribution?: Distribution;
|
|
1548
1593
|
}) => SyncEnvelope;
|
|
1549
1594
|
//#endregion
|
|
@@ -1853,6 +1898,49 @@ interface StaleRunResult {
|
|
|
1853
1898
|
*/
|
|
1854
1899
|
export declare const runStale: (options: StaleRunOptions) => Effect.Effect<StaleRunResult, BundleLoadError, FileSystem.FileSystem | Path.Path>;
|
|
1855
1900
|
//#endregion
|
|
1901
|
+
//#region src/sync/publication.d.ts
|
|
1902
|
+
declare const PublicationNotFoundError_base: Schema.Class<PublicationNotFoundError, Schema.TaggedStruct<"PublicationNotFoundError", {
|
|
1903
|
+
readonly id: Schema.String;
|
|
1904
|
+
readonly publication: Schema.optionalKey<Schema.String>;
|
|
1905
|
+
}>, import("effect/Cause").YieldableError>;
|
|
1906
|
+
/**
|
|
1907
|
+
* No concept answers to `id` -- or one of a Publication's `renders` paths
|
|
1908
|
+
* resolves to no concept, in which case `id` names that missing source.
|
|
1909
|
+
*
|
|
1910
|
+
* @public
|
|
1911
|
+
*/
|
|
1912
|
+
export declare class PublicationNotFoundError extends PublicationNotFoundError_base {
|
|
1913
|
+
readonly [Runtime.errorExitCode] = 64;
|
|
1914
|
+
get message(): string;
|
|
1915
|
+
}
|
|
1916
|
+
declare const NotAPublicationError_base: Schema.Class<NotAPublicationError, Schema.TaggedStruct<"NotAPublicationError", {
|
|
1917
|
+
readonly id: Schema.String;
|
|
1918
|
+
readonly type: Schema.String;
|
|
1919
|
+
}>, import("effect/Cause").YieldableError>;
|
|
1920
|
+
/**
|
|
1921
|
+
* The concept at `id` exists but is not a Publication (`type` is what it is).
|
|
1922
|
+
*
|
|
1923
|
+
* @public
|
|
1924
|
+
*/
|
|
1925
|
+
export declare class NotAPublicationError extends NotAPublicationError_base {
|
|
1926
|
+
readonly [Runtime.errorExitCode] = 64;
|
|
1927
|
+
get message(): string;
|
|
1928
|
+
}
|
|
1929
|
+
/**
|
|
1930
|
+
* Restamps one Publication's `renders[].body_sha256` digests from the current
|
|
1931
|
+
* body of each rendered source concept, writing the Publication file
|
|
1932
|
+
* atomically unless `dryRun`. `written` is whether the text changed (so a
|
|
1933
|
+
* `dryRun` that would change the file reports `true` without writing).
|
|
1934
|
+
* A malformed `renders` fails `UnsupportedFrontmatterError`.
|
|
1935
|
+
*
|
|
1936
|
+
* @public
|
|
1937
|
+
*/
|
|
1938
|
+
export declare const stampPublication: (options: {
|
|
1939
|
+
readonly bundleRoot: string;
|
|
1940
|
+
readonly id: string;
|
|
1941
|
+
readonly dryRun: boolean;
|
|
1942
|
+
}) => Effect.Effect<PublicationEnvelope, PublicationNotFoundError | NotAPublicationError | BundleLoadError | YamlParseError | UnsupportedFrontmatterError | PlatformError.PlatformError, FileSystem.FileSystem | Path.Path | Crypto.Crypto>;
|
|
1943
|
+
//#endregion
|
|
1856
1944
|
//#region src/validate/run.d.ts
|
|
1857
1945
|
declare const Now_base: Context.ServiceClass<Now, "@okfit/engine/Now", DateTime.Utc>;
|
|
1858
1946
|
/**
|
|
@@ -1920,6 +2008,10 @@ interface RunResult {
|
|
|
1920
2008
|
* the same `report.lint` array every other core lint diagnostic already
|
|
1921
2009
|
* does.
|
|
1922
2010
|
*
|
|
2011
|
+
* It then appends `lintResources`, `Publications.lint` (`publication-drift`
|
|
2012
|
+
* and `publication-orphan`, only under the `software-project` profile) and `lintSurfaces` (`surface-unmatched`), each
|
|
2013
|
+
* of which gates itself on its own severity.
|
|
2014
|
+
*
|
|
1923
2015
|
* K-52 holds structurally: `Bundle.load`'s failure short-circuits the
|
|
1924
2016
|
* generator, so `profile.check` never runs on a bundle that did not load.
|
|
1925
2017
|
*
|
package/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { resolveBundleRoot, resolveProjectRoot } from "./config/anchor.js";
|
|
2
|
-
import { ConfigMalformedError, ConfigPathNotFoundError, DocumentPathError, InitOverwriteError, QueryConceptNotFoundError, QuerySelectionError, QueryUnknownVocabularyError, SyncStagedLogError, VerifyConceptNotFoundError, VerifySelectionError, VerifyUnsupportedFrontmatterError } from "./errors.js";
|
|
2
|
+
import { ConfigMalformedError, ConfigPathNotFoundError, DocumentPathError, InitOverwriteError, QueryConceptNotFoundError, QuerySelectionError, QueryUnknownVocabularyError, SyncPublicationConflictError, SyncStagedLogError, VerifyConceptNotFoundError, VerifySelectionError, VerifyUnsupportedFrontmatterError } from "./errors.js";
|
|
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";
|
|
@@ -21,14 +21,15 @@ import { JsonDiagnostic, JsonEnvelope, JsonErrorEnvelope, JsonSummary, json, jso
|
|
|
21
21
|
import { ConceptSummary, QueryGetEnvelope, QueryLink, QueryListEnvelope, QueryNeighbor, QueryNeighborsEnvelope, queryGetEnvelope, queryListEnvelope, queryNeighborsEnvelope, toConceptSummary } from "./render/query.js";
|
|
22
22
|
import { StaleEnvelope, StaleItem, StaleSummary, staleEnvelope } from "./render/stale.js";
|
|
23
23
|
import { SkipReason, runSync } from "./sync/run.js";
|
|
24
|
-
import { SyncEnvelope, SyncModeEnvelope, syncEnvelope } from "./render/sync.js";
|
|
24
|
+
import { PublicationEnvelope, SyncEnvelope, SyncModeEnvelope, syncEnvelope } from "./render/sync.js";
|
|
25
25
|
import { VerifyBatchEnvelope, VerifyEnvelope, verifyBatchEnvelope, verifyEnvelope } from "./render/verify.js";
|
|
26
26
|
import { Now, run } from "./validate/run.js";
|
|
27
27
|
import { conceptFor } from "./session/concept.js";
|
|
28
28
|
import { withFallbackRange } from "./session/range.js";
|
|
29
29
|
import { BundleSession } from "./session/BundleSession.js";
|
|
30
30
|
import { runStale } from "./stale/run.js";
|
|
31
|
+
import { NotAPublicationError, PublicationNotFoundError, stampPublication } from "./sync/publication.js";
|
|
31
32
|
import { resolveBatchTypes, selectAttestable, selectPickerCandidates } from "./verify/select.js";
|
|
32
33
|
import { loadPickerCandidates, runVerify, runVerifyBatch, runVerifyIds } from "./verify/run.js";
|
|
33
34
|
|
|
34
|
-
export { BundleSession, CONFIG_RELATIVE_PATH, ConceptQuery, ConceptSummary, 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, QueryConceptNotFoundError, QueryGetEnvelope, QueryLink, QueryListEnvelope, QueryNeighbor, QueryNeighborsEnvelope, QuerySelectionError, QueryUnknownVocabularyError, 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, loadPickerCandidates, makeOverlayFileSystem, provideConfig, provideDocuments, queryGetEnvelope, queryListEnvelope, queryNeighborsEnvelope, resolveBatchTypes, resolveBundleRoot, resolveDocumentPath, resolveProjectConfig, resolveProjectRoot, run, runContext, runGraph, runStale, runSync, runVerify, runVerifyBatch, runVerifyIds, selectAttestable, selectPickerCandidates, sort, staleEnvelope, syncEnvelope, tally, targetPaths, toConceptSummary, verifyBatchEnvelope, verifyEnvelope, withFallbackRange };
|
|
35
|
+
export { BundleSession, CONFIG_RELATIVE_PATH, ConceptQuery, ConceptSummary, 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, NotAPublicationError, Now, OkfitPlatform, OverlayDocuments, PublicationEnvelope, PublicationNotFoundError, QueryConceptNotFoundError, QueryGetEnvelope, QueryLink, QueryListEnvelope, QueryNeighbor, QueryNeighborsEnvelope, QuerySelectionError, QueryUnknownVocabularyError, ReferenceCheck, ReferenceState, SkipReason, StaleEnvelope, StaleItem, StaleSummary, SyncEnvelope, SyncModeEnvelope, SyncPublicationConflictError, SyncStagedLogError, UnsupportedFrontmatterError, VerifyBatchEnvelope, VerifyConceptNotFoundError, VerifyEnvelope, VerifySelectionError, VerifyUnsupportedFrontmatterError, buildConfigLayer, collect, conceptFor, configValue, contextEnvelope, files, forDiagnostics, graphEnvelope, json, jsonError, layerOverlayFileSystem, loadPickerCandidates, makeOverlayFileSystem, provideConfig, provideDocuments, queryGetEnvelope, queryListEnvelope, queryNeighborsEnvelope, resolveBatchTypes, resolveBundleRoot, resolveDocumentPath, resolveProjectConfig, resolveProjectRoot, run, runContext, runGraph, runStale, runSync, runVerify, runVerifyBatch, runVerifyIds, selectAttestable, selectPickerCandidates, sort, staleEnvelope, stampPublication, syncEnvelope, tally, targetPaths, toConceptSummary, verifyBatchEnvelope, verifyEnvelope, withFallbackRange };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@okfit/engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.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": [
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
"@effected/walker": "^0.15.0",
|
|
49
49
|
"@effected/xdg": "^0.9.0",
|
|
50
50
|
"@effected/yaml": "^0.19.0",
|
|
51
|
-
"@okfit/core": "0.
|
|
52
|
-
"@okfit/profiles": "0.
|
|
51
|
+
"@okfit/core": "0.9.0",
|
|
52
|
+
"@okfit/profiles": "0.9.0",
|
|
53
53
|
"effect": "^4.0.0"
|
|
54
54
|
},
|
|
55
55
|
"engines": {
|
package/render/sync.js
CHANGED
|
@@ -15,6 +15,20 @@ const SyncModeEnvelope = Schema.Struct({
|
|
|
15
15
|
}))
|
|
16
16
|
});
|
|
17
17
|
/**
|
|
18
|
+
* One `okfit sync --publication` result: the Publication id, whether its
|
|
19
|
+
* file changed, and the digest stamped for each rendered source.
|
|
20
|
+
*
|
|
21
|
+
* @public
|
|
22
|
+
*/
|
|
23
|
+
const PublicationEnvelope = Schema.Struct({
|
|
24
|
+
id: Schema.String,
|
|
25
|
+
written: Schema.Boolean,
|
|
26
|
+
digests: Schema.Array(Schema.Struct({
|
|
27
|
+
path: Schema.String,
|
|
28
|
+
body_sha256: Schema.String
|
|
29
|
+
}))
|
|
30
|
+
});
|
|
31
|
+
/**
|
|
18
32
|
* Contract §14 note 5: `schema: 1` is INFERRED, not in the design's own
|
|
19
33
|
* §2 JSON example — every other envelope in this codebase
|
|
20
34
|
* (`JsonEnvelope`, `VerifyEnvelope`, `ContextEnvelope`) opens with it, so
|
|
@@ -35,8 +49,16 @@ const SyncEnvelope = Schema.Struct({
|
|
|
35
49
|
exit_code: Schema.Literal(0),
|
|
36
50
|
generated: SyncModeEnvelope,
|
|
37
51
|
index: SyncModeEnvelope,
|
|
38
|
-
log: SyncModeEnvelope
|
|
52
|
+
log: SyncModeEnvelope,
|
|
53
|
+
/** Present only for `okfit sync --publication`; then the three modes are all `selected: false`. */
|
|
54
|
+
publication: Schema.optionalKey(PublicationEnvelope)
|
|
39
55
|
});
|
|
56
|
+
const NOT_SELECTED = {
|
|
57
|
+
selected: false,
|
|
58
|
+
written: [],
|
|
59
|
+
unchanged: [],
|
|
60
|
+
skipped: []
|
|
61
|
+
};
|
|
40
62
|
const toModeEnvelope = (mode) => ({
|
|
41
63
|
selected: mode.selected,
|
|
42
64
|
written: [...mode.written],
|
|
@@ -61,10 +83,11 @@ const syncEnvelope = (input) => ({
|
|
|
61
83
|
root: input.root,
|
|
62
84
|
dry_run: input.dryRun,
|
|
63
85
|
exit_code: 0,
|
|
64
|
-
generated: toModeEnvelope(input.result.generated),
|
|
65
|
-
index: toModeEnvelope(input.result.index),
|
|
66
|
-
log: toModeEnvelope(input.result.log)
|
|
86
|
+
generated: input.result === void 0 ? NOT_SELECTED : toModeEnvelope(input.result.generated),
|
|
87
|
+
index: input.result === void 0 ? NOT_SELECTED : toModeEnvelope(input.result.index),
|
|
88
|
+
log: input.result === void 0 ? NOT_SELECTED : toModeEnvelope(input.result.log),
|
|
89
|
+
...input.publication === void 0 ? {} : { publication: input.publication }
|
|
67
90
|
});
|
|
68
91
|
|
|
69
92
|
//#endregion
|
|
70
|
-
export { SyncEnvelope, SyncModeEnvelope, syncEnvelope };
|
|
93
|
+
export { PublicationEnvelope, SyncEnvelope, SyncModeEnvelope, syncEnvelope };
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { FrontmatterEdits, UnsupportedFrontmatterError } from "../edits/FrontmatterEdits.js";
|
|
2
|
+
import { writeAtomic } from "./write.js";
|
|
3
|
+
import { Bundle, ConceptId } from "@okfit/core";
|
|
4
|
+
import { Effect, Option, Path, Runtime, Schema } from "effect";
|
|
5
|
+
import { Derivation, PUBLICATION_TYPE, Publications } from "@okfit/profiles";
|
|
6
|
+
import { MarkdownEdit } from "@effected/markdown";
|
|
7
|
+
|
|
8
|
+
//#region src/sync/publication.ts
|
|
9
|
+
/**
|
|
10
|
+
* No concept answers to `id` -- or one of a Publication's `renders` paths
|
|
11
|
+
* resolves to no concept, in which case `id` names that missing source.
|
|
12
|
+
*
|
|
13
|
+
* @public
|
|
14
|
+
*/
|
|
15
|
+
var PublicationNotFoundError = class extends Schema.TaggedError()("PublicationNotFoundError", {
|
|
16
|
+
id: Schema.String,
|
|
17
|
+
publication: Schema.optionalKey(Schema.String)
|
|
18
|
+
}) {
|
|
19
|
+
[Runtime.errorExitCode] = 64;
|
|
20
|
+
get message() {
|
|
21
|
+
return this.publication === void 0 ? `no concept "${this.id}" in the bundle` : `a renders entry of "${this.publication}" points at "${this.id}", which is not a concept in the bundle; fix that renders entry`;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* The concept at `id` exists but is not a Publication (`type` is what it is).
|
|
26
|
+
*
|
|
27
|
+
* @public
|
|
28
|
+
*/
|
|
29
|
+
var NotAPublicationError = class extends Schema.TaggedError()("NotAPublicationError", {
|
|
30
|
+
id: Schema.String,
|
|
31
|
+
type: Schema.String
|
|
32
|
+
}) {
|
|
33
|
+
[Runtime.errorExitCode] = 64;
|
|
34
|
+
get message() {
|
|
35
|
+
return `"${this.id}" is a ${this.type}, not a ${PUBLICATION_TYPE}`;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Restamps one Publication's `renders[].body_sha256` digests from the current
|
|
40
|
+
* body of each rendered source concept, writing the Publication file
|
|
41
|
+
* atomically unless `dryRun`. `written` is whether the text changed (so a
|
|
42
|
+
* `dryRun` that would change the file reports `true` without writing).
|
|
43
|
+
* A malformed `renders` fails `UnsupportedFrontmatterError`.
|
|
44
|
+
*
|
|
45
|
+
* @public
|
|
46
|
+
*/
|
|
47
|
+
const stampPublication = (options) => Effect.gen(function* () {
|
|
48
|
+
const bundle = yield* Bundle.load({ root: options.bundleRoot });
|
|
49
|
+
const normalized = ConceptId.normalize(options.id);
|
|
50
|
+
const id = Option.getOrElse(normalized, () => options.id);
|
|
51
|
+
const concept = Option.isSome(normalized) ? bundle.concepts.get(normalized.value) : void 0;
|
|
52
|
+
if (concept === void 0) return yield* new PublicationNotFoundError({ id });
|
|
53
|
+
if (concept.frontmatter.type !== PUBLICATION_TYPE) return yield* new NotAPublicationError({
|
|
54
|
+
id,
|
|
55
|
+
type: concept.frontmatter.type
|
|
56
|
+
});
|
|
57
|
+
const renders = Publications.rendersOf(concept);
|
|
58
|
+
if (renders === void 0) return yield* new UnsupportedFrontmatterError({
|
|
59
|
+
key: "renders",
|
|
60
|
+
shape: "malformed"
|
|
61
|
+
});
|
|
62
|
+
const digests = [];
|
|
63
|
+
for (const entry of renders) {
|
|
64
|
+
const sourceId = Publications.resolveRef(concept.path, entry.path);
|
|
65
|
+
const source = Option.match(ConceptId.normalize(sourceId), {
|
|
66
|
+
onNone: () => void 0,
|
|
67
|
+
onSome: (key) => bundle.concepts.get(key)
|
|
68
|
+
});
|
|
69
|
+
if (source === void 0) return yield* new PublicationNotFoundError({
|
|
70
|
+
id: sourceId === "" ? entry.path : sourceId,
|
|
71
|
+
publication: id
|
|
72
|
+
});
|
|
73
|
+
digests.push({
|
|
74
|
+
path: entry.path,
|
|
75
|
+
body_sha256: yield* Derivation.bodyDigest(source.document.source)
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
const original = concept.document.source;
|
|
79
|
+
const edits = yield* FrontmatterEdits.rendersDigests(original, digests.map((d) => d.body_sha256));
|
|
80
|
+
const next = MarkdownEdit.applyAll(original, edits);
|
|
81
|
+
if (next === original) return {
|
|
82
|
+
id,
|
|
83
|
+
written: false,
|
|
84
|
+
digests
|
|
85
|
+
};
|
|
86
|
+
if (!options.dryRun) {
|
|
87
|
+
const path = yield* Path.Path;
|
|
88
|
+
yield* writeAtomic(path.join(bundle.root, concept.path), next);
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
id,
|
|
92
|
+
written: true,
|
|
93
|
+
digests
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
//#endregion
|
|
98
|
+
export { NotAPublicationError, PublicationNotFoundError, stampPublication };
|
package/validate/resources.js
CHANGED
|
@@ -4,8 +4,8 @@ import { Effect, FileSystem, Path } from "effect";
|
|
|
4
4
|
//#region src/validate/resources.ts
|
|
5
5
|
/** A `resource`/`sources[].resource` value that names a URL, not a bundle-relative path (D-24). */
|
|
6
6
|
const SCHEME_RE = /^[a-zA-Z][a-zA-Z0-9+.-]*:/;
|
|
7
|
-
/** Glob metacharacters that mark a value as a
|
|
8
|
-
const GLOB_RE = /[*?[{]/;
|
|
7
|
+
/** Glob metacharacters, extglob openers included, that mark a value as a pattern rather than a literal path. */
|
|
8
|
+
const GLOB_RE = /[*?[{]|[@+!]\(/;
|
|
9
9
|
/**
|
|
10
10
|
* True when `value` is a descriptor (a URL, a scope description, or a glob
|
|
11
11
|
* pattern) rather than a literal bundle-relative path — D-24 leaves
|
|
@@ -86,4 +86,4 @@ const lintResources = (bundle, config) => Effect.gen(function* () {
|
|
|
86
86
|
});
|
|
87
87
|
|
|
88
88
|
//#endregion
|
|
89
|
-
export { lintResources };
|
|
89
|
+
export { GLOB_RE, lintResources };
|
package/validate/run.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { lintResources } from "./resources.js";
|
|
2
|
+
import { lintSurfaces } from "./surfaces.js";
|
|
2
3
|
import { Bundle, OkfitConfig, Validate } from "@okfit/core";
|
|
3
4
|
import { Context, Effect, Option } from "effect";
|
|
4
|
-
import { Provenance } from "@okfit/profiles";
|
|
5
|
+
import { Provenance, Publications } from "@okfit/profiles";
|
|
5
6
|
|
|
6
7
|
//#region src/validate/run.ts
|
|
7
8
|
/**
|
|
@@ -37,6 +38,10 @@ var Now = class extends Context.Service()("@okfit/engine/Now") {};
|
|
|
37
38
|
* the same `report.lint` array every other core lint diagnostic already
|
|
38
39
|
* does.
|
|
39
40
|
*
|
|
41
|
+
* It then appends `lintResources`, `Publications.lint` (`publication-drift`
|
|
42
|
+
* and `publication-orphan`, only under the `software-project` profile) and `lintSurfaces` (`surface-unmatched`), each
|
|
43
|
+
* of which gates itself on its own severity.
|
|
44
|
+
*
|
|
40
45
|
* K-52 holds structurally: `Bundle.load`'s failure short-circuits the
|
|
41
46
|
* generator, so `profile.check` never runs on a bundle that did not load.
|
|
42
47
|
*
|
|
@@ -55,6 +60,8 @@ const run = (options) => Effect.gen(function* () {
|
|
|
55
60
|
});
|
|
56
61
|
const provenance = OkfitConfig.severityFor(options.config, "generated-at-drift") === "off" ? [] : yield* Provenance.lint(bundle, options.config, { skipGitTier: options.skipProvenance === true });
|
|
57
62
|
const resources = yield* lintResources(bundle, options.config);
|
|
63
|
+
const publications = Option.exists(options.profile, (profile) => profile.name === "software-project") ? yield* Publications.lint(bundle, options.config) : [];
|
|
64
|
+
const surfaces = yield* lintSurfaces(bundle, options.config);
|
|
58
65
|
return {
|
|
59
66
|
bundle,
|
|
60
67
|
report: {
|
|
@@ -62,7 +69,9 @@ const run = (options) => Effect.gen(function* () {
|
|
|
62
69
|
lint: [
|
|
63
70
|
...report.lint,
|
|
64
71
|
...provenance,
|
|
65
|
-
...resources
|
|
72
|
+
...resources,
|
|
73
|
+
...publications,
|
|
74
|
+
...surfaces
|
|
66
75
|
]
|
|
67
76
|
},
|
|
68
77
|
profileDiagnostics
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { GLOB_RE } from "./resources.js";
|
|
2
|
+
import { Diagnostic, DiagnosticRange, OkfitConfig } from "@okfit/core";
|
|
3
|
+
import { Effect, FileSystem, Path, Result } from "effect";
|
|
4
|
+
import { SURFACE_TYPE } from "@okfit/profiles";
|
|
5
|
+
import { GlobPattern } from "@effected/glob";
|
|
6
|
+
|
|
7
|
+
//#region src/validate/surfaces.ts
|
|
8
|
+
/**
|
|
9
|
+
* Lists `root` breadth-first, only as deep as `segments` (the glob's remaining
|
|
10
|
+
* segments) can match, or without a bound when the pattern contains `**`.
|
|
11
|
+
* `node_modules` and dot-entries are skipped unless a segment names them, and
|
|
12
|
+
* symlinked directories are listed but never descended into. Entry
|
|
13
|
+
* paths are `/`-joined and relative to `root`. An unreadable directory
|
|
14
|
+
* contributes nothing.
|
|
15
|
+
*/
|
|
16
|
+
const walk = (fs, path, root, segments) => Effect.gen(function* () {
|
|
17
|
+
const maxDepth = segments.some((segment) => segment.includes("**")) ? Number.POSITIVE_INFINITY : segments.length;
|
|
18
|
+
const namesHidden = segments.some((segment) => /(^|[{,(|[])\./.test(segment));
|
|
19
|
+
const namesModules = segments.some((segment) => segment.includes("node_modules"));
|
|
20
|
+
const skipped = (name) => name === "node_modules" && !namesModules || name.startsWith(".") && !namesHidden;
|
|
21
|
+
const out = [];
|
|
22
|
+
let level = [""];
|
|
23
|
+
for (let depth = 1; depth <= maxDepth && level.length > 0; depth++) {
|
|
24
|
+
const next = [];
|
|
25
|
+
for (const dir of level) {
|
|
26
|
+
const names = yield* fs.readDirectory(dir === "" ? root : path.join(root, dir)).pipe(Effect.orElseSucceed(() => []));
|
|
27
|
+
for (const name of names) {
|
|
28
|
+
if (skipped(name)) continue;
|
|
29
|
+
const relative = dir === "" ? name : `${dir}/${name}`;
|
|
30
|
+
out.push(relative);
|
|
31
|
+
if (depth < maxDepth) {
|
|
32
|
+
const full = path.join(root, relative);
|
|
33
|
+
const isDirectory = yield* fs.stat(full).pipe(Effect.map((info) => info.type === "Directory"), Effect.orElseSucceed(() => false));
|
|
34
|
+
const isSymlink = isDirectory ? yield* fs.readLink(full).pipe(Effect.as(true), Effect.orElseSucceed(() => false)) : false;
|
|
35
|
+
if (isDirectory && !isSymlink) next.push(relative);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
level = next;
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
});
|
|
43
|
+
/**
|
|
44
|
+
* A Surface's `resource` glob matches nothing on disk. Only concepts of type
|
|
45
|
+
* `Surface` whose `resource` contains a glob metacharacter are checked; a
|
|
46
|
+
* literal `resource` is `lintResources`'s concern (`source-resource-missing`).
|
|
47
|
+
* The pattern is split, as written, at its first metacharacter segment into a
|
|
48
|
+
* static `prefix` (resolved against the concept's directory) and a `rest`; the
|
|
49
|
+
* `prefix` directory is walked only as deep as `rest` can match (unbounded with
|
|
50
|
+
* `**`, skipping `node_modules` and dot-directories `rest` does not name) and
|
|
51
|
+
* `rest` is matched against the entries. A missing or unreadable `prefix` (for
|
|
52
|
+
* example a file), an empty match, or a `rest` that fails to compile all count
|
|
53
|
+
* as unmatched, and report a warning (a brand-new monorepo legitimately has no
|
|
54
|
+
* packages yet), ranged at the `["resource"]` frontmatter value. Total over
|
|
55
|
+
* severity (mirrors `lintResources`): returns `[]`, touching neither
|
|
56
|
+
* `FileSystem` nor `Path`, the moment
|
|
57
|
+
* `OkfitConfig.severityFor(config, "surface-unmatched")` is `"off"`.
|
|
58
|
+
*
|
|
59
|
+
* @public
|
|
60
|
+
*/
|
|
61
|
+
const lintSurfaces = (bundle, config) => Effect.gen(function* () {
|
|
62
|
+
const severity = OkfitConfig.severityFor(config, "surface-unmatched");
|
|
63
|
+
if (severity === "off") return [];
|
|
64
|
+
const fs = yield* FileSystem.FileSystem;
|
|
65
|
+
const path = yield* Path.Path;
|
|
66
|
+
const diagnostics = [];
|
|
67
|
+
for (const [, concept] of bundle.concepts) {
|
|
68
|
+
if (concept.frontmatter.type !== SURFACE_TYPE) continue;
|
|
69
|
+
const resource = concept.frontmatter.resource;
|
|
70
|
+
if (resource === void 0 || !GLOB_RE.test(resource)) continue;
|
|
71
|
+
const written = resource.split("/");
|
|
72
|
+
const firstGlob = written.findIndex((segment) => GLOB_RE.test(segment));
|
|
73
|
+
if (firstGlob < 0) continue;
|
|
74
|
+
const prefix = path.resolve(bundle.root, path.dirname(concept.path), ...written.slice(0, firstGlob));
|
|
75
|
+
const restSegments = written.slice(firstGlob);
|
|
76
|
+
const rest = restSegments.join("/");
|
|
77
|
+
let problem;
|
|
78
|
+
if (!(yield* fs.exists(prefix).pipe(Effect.orElseSucceed(() => false)))) problem = "";
|
|
79
|
+
else {
|
|
80
|
+
const compiled = GlobPattern.compileResult(rest);
|
|
81
|
+
if (Result.isFailure(compiled)) problem = `: ${compiled.failure.message}`;
|
|
82
|
+
else if (!(yield* walk(fs, path, prefix, restSegments)).some((entry) => compiled.success.matches(entry))) problem = "";
|
|
83
|
+
}
|
|
84
|
+
if (problem === void 0) continue;
|
|
85
|
+
const range = DiagnosticRange.forFrontmatterPath(concept.document, ["resource"]);
|
|
86
|
+
diagnostics.push(Diagnostic.make({
|
|
87
|
+
file: concept.path,
|
|
88
|
+
code: "surface-unmatched",
|
|
89
|
+
severity,
|
|
90
|
+
message: `Surface resource "${resource}" matches nothing under ${path.relative(bundle.root, prefix) || "."}${problem}`,
|
|
91
|
+
...range === void 0 ? {} : { range }
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
return diagnostics;
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
//#endregion
|
|
98
|
+
export { lintSurfaces };
|
package/verify/locate.js
CHANGED
|
@@ -336,10 +336,27 @@ const locateGeneratedBlock = Effect.fn("okfit/verify/locateGeneratedBlock")(func
|
|
|
336
336
|
const parsed = yield* parseTopLevelMapping(source);
|
|
337
337
|
if ("_tag" in parsed) return parsed;
|
|
338
338
|
const { value, valueStart, contents } = parsed;
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
339
|
+
const pair = contents.items.find((item) => item.key instanceof YamlScalar && item.key.value === "generated");
|
|
340
|
+
if (pair !== void 0) {
|
|
341
|
+
const node = pair.value;
|
|
342
|
+
if (!(node === null || node instanceof YamlScalar && node.style === "plain" && node.value === null && node.tag === void 0 && node.anchor === void 0) || contents.style === "flow") return {
|
|
343
|
+
_tag: "unsupported",
|
|
344
|
+
shape: "generated-present"
|
|
345
|
+
};
|
|
346
|
+
const lineStart = value.lastIndexOf("\n", pair.key.offset - 1) + 1;
|
|
347
|
+
const tail = node === null ? pair.key.offset + pair.key.length : node.offset + node.length;
|
|
348
|
+
const nl = value.indexOf("\n", tail);
|
|
349
|
+
const lineEnd = nl === -1 ? value.length : nl + 1;
|
|
350
|
+
if (lineStart >= lineEnd || value.slice(lineStart, pair.key.offset).trim() !== "") return {
|
|
351
|
+
_tag: "unsupported",
|
|
352
|
+
shape: "generated-present"
|
|
353
|
+
};
|
|
354
|
+
return {
|
|
355
|
+
_tag: "replace",
|
|
356
|
+
start: valueStart + lineStart,
|
|
357
|
+
end: valueStart + lineEnd
|
|
358
|
+
};
|
|
359
|
+
}
|
|
343
360
|
return {
|
|
344
361
|
_tag: "absent",
|
|
345
362
|
insertAt: valueStart + value.length
|
package/verify/splice.js
CHANGED
|
@@ -118,14 +118,15 @@ const spliceTopLevelScalar = (target, key, value, newline) => {
|
|
|
118
118
|
};
|
|
119
119
|
/**
|
|
120
120
|
* A whole `generated:` block mapping, appended as the LAST top-level key
|
|
121
|
-
* (issue #73)
|
|
121
|
+
* (issue #73), or written over a null-valued `generated:` line in place
|
|
122
|
+
* (issue #144) — block style, matching how this repository writes every
|
|
122
123
|
* `generated:` and how `splice`'s own `absent` case writes `verified:`.
|
|
123
124
|
*
|
|
124
125
|
* @internal
|
|
125
126
|
*/
|
|
126
127
|
const spliceGeneratedBlock = (target, fields, newline) => MarkdownEdit.make({
|
|
127
|
-
offset: target.insertAt,
|
|
128
|
-
length: 0,
|
|
128
|
+
offset: target._tag === "absent" ? target.insertAt : target.start,
|
|
129
|
+
length: target._tag === "absent" ? 0 : target.end - target.start,
|
|
129
130
|
content: `generated:${newline} by: ${fields.by}${newline} at: ${fields.at}${newline} body_sha256: ${fields.bodySha256}${newline}`
|
|
130
131
|
});
|
|
131
132
|
/**
|