@okfit/engine 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -6
- package/validate/resources.js +73 -0
- package/validate/run.js +7 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@okfit/engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.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": [
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@effect/platform-node": "4.0.0-rc.115",
|
|
38
|
-
"@effected/app": "^0.16.
|
|
39
|
-
"@effected/config-file": "^0.
|
|
38
|
+
"@effected/app": "^0.16.2",
|
|
39
|
+
"@effected/config-file": "^0.10.0",
|
|
40
40
|
"@effected/git": "^0.15.1",
|
|
41
41
|
"@effected/glob": "^0.6.0",
|
|
42
42
|
"@effected/jsonc": "^0.11.0",
|
|
@@ -44,10 +44,10 @@
|
|
|
44
44
|
"@effected/store": "^0.8.0",
|
|
45
45
|
"@effected/toml": "^0.7.0",
|
|
46
46
|
"@effected/walker": "^0.9.0",
|
|
47
|
-
"@effected/xdg": "^0.5.
|
|
47
|
+
"@effected/xdg": "^0.5.2",
|
|
48
48
|
"@effected/yaml": "^0.15.1",
|
|
49
|
-
"@okfit/core": "0.
|
|
50
|
-
"@okfit/profiles": "0.
|
|
49
|
+
"@okfit/core": "0.5.0",
|
|
50
|
+
"@okfit/profiles": "0.7.0",
|
|
51
51
|
"effect": "4.0.0-rc.115"
|
|
52
52
|
},
|
|
53
53
|
"engines": {
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Diagnostic, OkfitConfig } from "@okfit/core";
|
|
2
|
+
import { Effect, FileSystem, Path } from "effect";
|
|
3
|
+
|
|
4
|
+
//#region src/validate/resources.ts
|
|
5
|
+
/** A `resource`/`sources[].resource` value that names a URL, not a bundle-relative path (D-24). */
|
|
6
|
+
const SCHEME_RE = /^[a-zA-Z][a-zA-Z0-9+.-]*:/;
|
|
7
|
+
/** Glob metacharacters that mark a value as a descriptor pattern, not a literal path. */
|
|
8
|
+
const GLOB_RE = /[*?[{]/;
|
|
9
|
+
/**
|
|
10
|
+
* True when `value` is a descriptor (a URL, a scope description, or a glob
|
|
11
|
+
* pattern) rather than a literal bundle-relative path — D-24 leaves
|
|
12
|
+
* `Source.resource` untyped for exactly this reason: "a URL, a bundle path or
|
|
13
|
+
* a scope descriptor; never resolved here". Anything with a URL scheme
|
|
14
|
+
* (`https:`, `git:`, …) or a bare `://`, anything containing whitespace (a
|
|
15
|
+
* prose description like "the git history"), and anything containing a glob
|
|
16
|
+
* metacharacter (`src/**`) is a descriptor and is skipped, never resolved
|
|
17
|
+
* against the filesystem.
|
|
18
|
+
*/
|
|
19
|
+
const isDescriptor = (value) => SCHEME_RE.test(value) || value.includes("://") || /\s/.test(value) || GLOB_RE.test(value);
|
|
20
|
+
/** Strips a trailing `#fragment` or `?query` suffix before filesystem resolution. */
|
|
21
|
+
const stripSuffix = (value) => {
|
|
22
|
+
const at = value.search(/[?#]/);
|
|
23
|
+
return at === -1 ? value : value.slice(0, at);
|
|
24
|
+
};
|
|
25
|
+
/** Every `resource` value on a concept: the top-level field, then each source's. */
|
|
26
|
+
const resourceValuesOf = (concept) => {
|
|
27
|
+
const values = [];
|
|
28
|
+
if (concept.frontmatter.resource !== void 0) values.push(concept.frontmatter.resource);
|
|
29
|
+
for (const source of concept.frontmatter.sources ?? []) values.push(source.resource);
|
|
30
|
+
return values;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* D-24's `resource`/`sources[].resource` value resolves to nothing in the
|
|
34
|
+
* bundle for every non-descriptor value on every concept (issue #106). A
|
|
35
|
+
* `resource` is a descriptor (a URL, a scope description, or a glob
|
|
36
|
+
* pattern) or a literal path resolved in D-23 order: relative to the
|
|
37
|
+
* concept's own directory, then (for a bare path) relative to the bundle
|
|
38
|
+
* root, or with a leading `/` relative to the bundle root only; only the
|
|
39
|
+
* latter is checked against the filesystem. Total over severity (mirrors
|
|
40
|
+
* `Provenance.lint`'s S-28 posture): returns `[]`, touching neither
|
|
41
|
+
* `FileSystem` nor `Path`, the moment
|
|
42
|
+
* `OkfitConfig.severityFor(config, "source-resource-missing")` is `"off"`.
|
|
43
|
+
*
|
|
44
|
+
* @public
|
|
45
|
+
*/
|
|
46
|
+
const lintResources = (bundle, config) => Effect.gen(function* () {
|
|
47
|
+
const severity = OkfitConfig.severityFor(config, "source-resource-missing");
|
|
48
|
+
if (severity === "off") return [];
|
|
49
|
+
const fs = yield* FileSystem.FileSystem;
|
|
50
|
+
const path = yield* Path.Path;
|
|
51
|
+
const diagnostics = [];
|
|
52
|
+
for (const [, concept] of bundle.concepts) for (const value of resourceValuesOf(concept)) {
|
|
53
|
+
if (isDescriptor(value)) continue;
|
|
54
|
+
const stripped = stripSuffix(value);
|
|
55
|
+
const candidates = stripped.startsWith("/") ? [path.join(bundle.root, stripped.slice(1))] : [path.resolve(bundle.root, path.dirname(concept.path), stripped), ...stripped.startsWith("./") || stripped.startsWith("../") ? [] : [path.resolve(bundle.root, stripped)]];
|
|
56
|
+
let exists = false;
|
|
57
|
+
for (const candidate of candidates) if (yield* fs.exists(candidate)) {
|
|
58
|
+
exists = true;
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
if (exists) continue;
|
|
62
|
+
diagnostics.push(Diagnostic.make({
|
|
63
|
+
file: concept.path,
|
|
64
|
+
code: "source-resource-missing",
|
|
65
|
+
severity,
|
|
66
|
+
message: `Resource "${value}" does not exist relative to ${concept.path}`
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
return diagnostics;
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
//#endregion
|
|
73
|
+
export { lintResources };
|
package/validate/run.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { lintResources } from "./resources.js";
|
|
1
2
|
import { Bundle, OkfitConfig, Validate } from "@okfit/core";
|
|
2
3
|
import { Context, Effect, Option } from "effect";
|
|
3
4
|
import { Provenance } from "@okfit/profiles";
|
|
@@ -53,11 +54,16 @@ const run = (options) => Effect.gen(function* () {
|
|
|
53
54
|
onSome: (profile) => profile.check(bundle)
|
|
54
55
|
});
|
|
55
56
|
const provenance = OkfitConfig.severityFor(options.config, "generated-at-drift") === "off" ? [] : yield* Provenance.lint(bundle, options.config, { skipGitTier: options.skipProvenance === true });
|
|
57
|
+
const resources = yield* lintResources(bundle, options.config);
|
|
56
58
|
return {
|
|
57
59
|
bundle,
|
|
58
60
|
report: {
|
|
59
61
|
...report,
|
|
60
|
-
lint: [
|
|
62
|
+
lint: [
|
|
63
|
+
...report.lint,
|
|
64
|
+
...provenance,
|
|
65
|
+
...resources
|
|
66
|
+
]
|
|
61
67
|
},
|
|
62
68
|
profileDiagnostics
|
|
63
69
|
};
|