@effected/workspaces 0.25.0 → 0.27.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/LayerPolicy.js +133 -0
- package/PackedInstall.js +761 -0
- package/README.md +244 -0
- package/SourceBoundary.js +624 -0
- package/WorkspaceLayering.js +199 -0
- package/internal/dependencyFields.js +17 -0
- package/internal/packedInstallPlan.js +239 -0
- package/internal/sourceText.js +359 -0
- package/package.json +6 -1
- package/testing.d.ts +1837 -0
- package/testing.js +10 -0
package/LayerPolicy.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { ALL_DEPENDENCY_FIELDS } from "./internal/dependencyFields.js";
|
|
2
|
+
import { Effect, FileSystem, Result, Schema } from "effect";
|
|
3
|
+
import { GlobSet } from "@effected/glob";
|
|
4
|
+
import { DependencyField } from "@effected/npm";
|
|
5
|
+
|
|
6
|
+
//#region src/LayerPolicy.ts
|
|
7
|
+
const REQUIRED_EDGE = /^\S+ -> \S+$/;
|
|
8
|
+
/** `input` without `keys`, when it is a plain object; anything else passes through for the schema to reject. */
|
|
9
|
+
const withoutKeys = (input, keys) => {
|
|
10
|
+
if (typeof input !== "object" || input === null || Array.isArray(input)) return input;
|
|
11
|
+
return Object.fromEntries(Object.entries(input).filter(([key]) => !keys.includes(key)));
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Raised when a layer policy cannot be read, parsed or decoded.
|
|
15
|
+
*
|
|
16
|
+
* @public
|
|
17
|
+
*/
|
|
18
|
+
var LayerPolicyError = class extends Schema.TaggedError()("LayerPolicyError", {
|
|
19
|
+
/** `read`: the file could not be read. `json`: it is not JSON. `decode`: it is not a `LayerPolicy`. */
|
|
20
|
+
reason: Schema.Literals([
|
|
21
|
+
"read",
|
|
22
|
+
"json",
|
|
23
|
+
"decode"
|
|
24
|
+
]),
|
|
25
|
+
/** The file, when the policy came from one. */
|
|
26
|
+
path: Schema.optionalKey(Schema.String),
|
|
27
|
+
/** The originating failure. */
|
|
28
|
+
cause: Schema.Defect()
|
|
29
|
+
}) {
|
|
30
|
+
/** Renders the failure kind and the file into one line. */
|
|
31
|
+
get message() {
|
|
32
|
+
const at = this.path === void 0 ? "" : ` at ${this.path}`;
|
|
33
|
+
switch (this.reason) {
|
|
34
|
+
case "read": return `Could not read the layer policy${at}`;
|
|
35
|
+
case "json": return `The layer policy${at} is not valid JSON`;
|
|
36
|
+
default: return `The layer policy${at} does not match the LayerPolicy shape${this.cause instanceof Error && this.cause.message !== "" ? `: ${this.cause.message}` : ""}`;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* A committed dependency-layering policy (`layers.json`).
|
|
42
|
+
*
|
|
43
|
+
* @remarks
|
|
44
|
+
* `layers` is top-down: an edge may only point from a layer to one BELOW it
|
|
45
|
+
* (a higher index), or into `tooling`. `tooling` packages may depend on each
|
|
46
|
+
* other but never reach a layer. `unconstrained` holds globs (through
|
|
47
|
+
* `@effected/glob`) for packages whose own edges are not checked (a test
|
|
48
|
+
* harness, the private root). `fields` narrows which dependency maps count,
|
|
49
|
+
* defaulting to all four. `requiredEdges` (`"a -> b"`) are edges that must
|
|
50
|
+
* exist in a checked field: the non-vacuity guard against a discovery that
|
|
51
|
+
* quietly drops real edges.
|
|
52
|
+
*
|
|
53
|
+
* Every entry matches a package's `name`, never its `relativePath`: `layers`
|
|
54
|
+
* and `tooling` list exact names, and `unconstrained` globs match names. A
|
|
55
|
+
* private root named `my-monorepo` at `relativePath` `"."` is classified as
|
|
56
|
+
* `"my-monorepo"`; an entry of `"."` or `"packages/*"` classifies nothing.
|
|
57
|
+
*
|
|
58
|
+
* Decoding is strict: a key the policy does not model fails `decode`, and the
|
|
59
|
+
* message names every such key. A typo on an optional key would otherwise be
|
|
60
|
+
* silently dropped: `requiredEdge` would remove the non-vacuity guard and
|
|
61
|
+
* leave the report green. `$schema` is always accepted. A file that carries
|
|
62
|
+
* keys of its own, such as systems' `harness`, passes them in `allowKeys`.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```ts
|
|
66
|
+
* import { NodeServices } from "@effect/platform-node";
|
|
67
|
+
* import { LayerPolicy } from "@effected/workspaces/testing";
|
|
68
|
+
* import { Effect } from "effect";
|
|
69
|
+
*
|
|
70
|
+
* const policy = LayerPolicy.load("/repo/layers.json").pipe(Effect.provide(NodeServices.layer));
|
|
71
|
+
* ```
|
|
72
|
+
*
|
|
73
|
+
* @public
|
|
74
|
+
*/
|
|
75
|
+
var LayerPolicy = class LayerPolicy extends Schema.Class("LayerPolicy")({
|
|
76
|
+
/** Exact package names (never relative paths) per layer, top layer first. */
|
|
77
|
+
layers: Schema.Array(Schema.Array(Schema.String)),
|
|
78
|
+
/** Exact package names (never relative paths) any layer may depend on that never depend on a layer. */
|
|
79
|
+
tooling: Schema.Array(Schema.String),
|
|
80
|
+
/** Globs matched against package names (never relative paths) for packages whose own edges are not checked. */
|
|
81
|
+
unconstrained: Schema.Array(Schema.String).check(Schema.makeFilter((patterns) => Result.isSuccess(GlobSet.compileResult(patterns)), { title: "compilable glob patterns" })),
|
|
82
|
+
/** The dependency maps to check. Absent means all four. */
|
|
83
|
+
fields: Schema.optionalKey(Schema.Array(DependencyField)),
|
|
84
|
+
/** Edges that must exist, written `"a -> b"`. */
|
|
85
|
+
requiredEdges: Schema.optionalKey(Schema.Array(Schema.String.check(Schema.isPattern(REQUIRED_EDGE))))
|
|
86
|
+
}) {
|
|
87
|
+
/** The dependency maps a check reads: `fields`, or all four. */
|
|
88
|
+
get effectiveFields() {
|
|
89
|
+
return this.fields ?? ALL_DEPENDENCY_FIELDS;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Decode a policy from a parsed JSON value.
|
|
93
|
+
*
|
|
94
|
+
* @remarks
|
|
95
|
+
* Every key the policy does not model fails, except `$schema` and the
|
|
96
|
+
* keys named in `options.allowKeys`, which are dropped before decoding.
|
|
97
|
+
* `options.path` names the file in the error.
|
|
98
|
+
*/
|
|
99
|
+
static decode = Effect.fn("LayerPolicy.decode")((input, options) => {
|
|
100
|
+
const path = options?.path;
|
|
101
|
+
return Schema.decodeUnknownEffect(LayerPolicy)(withoutKeys(input, ["$schema", ...options?.allowKeys ?? []]), {
|
|
102
|
+
onExcessProperty: "error",
|
|
103
|
+
errors: "all"
|
|
104
|
+
}).pipe(Effect.mapError((cause) => new LayerPolicyError({
|
|
105
|
+
reason: "decode",
|
|
106
|
+
cause,
|
|
107
|
+
...path === void 0 ? {} : { path }
|
|
108
|
+
})));
|
|
109
|
+
});
|
|
110
|
+
/** Read, parse and decode the policy file at `path`; `options.allowKeys` is passed to `decode`. */
|
|
111
|
+
static load = Effect.fn("LayerPolicy.load")(function* (path, options) {
|
|
112
|
+
const text = yield* (yield* FileSystem.FileSystem).readFileString(path).pipe(Effect.mapError((cause) => new LayerPolicyError({
|
|
113
|
+
reason: "read",
|
|
114
|
+
path,
|
|
115
|
+
cause
|
|
116
|
+
})));
|
|
117
|
+
const json = yield* Effect.try({
|
|
118
|
+
try: () => JSON.parse(text),
|
|
119
|
+
catch: (cause) => new LayerPolicyError({
|
|
120
|
+
reason: "json",
|
|
121
|
+
path,
|
|
122
|
+
cause
|
|
123
|
+
})
|
|
124
|
+
});
|
|
125
|
+
return yield* LayerPolicy.decode(json, {
|
|
126
|
+
path,
|
|
127
|
+
allowKeys: options?.allowKeys
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
//#endregion
|
|
133
|
+
export { LayerPolicy, LayerPolicyError };
|