@effected/workspaces 0.32.1 → 0.33.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.
@@ -0,0 +1,58 @@
1
+ import { Effect, FileSystem, Option, Path } from "effect";
2
+
3
+ //#region src/internal/sourceWalk.ts
4
+ /** The file extensions a walk reads when the caller names none. */
5
+ const DEFAULT_EXTENSIONS = [
6
+ ".ts",
7
+ ".mts",
8
+ ".cts",
9
+ ".js",
10
+ ".mjs",
11
+ ".cjs"
12
+ ];
13
+ /** Declaration files: always skipped, whatever extensions the caller names. */
14
+ const DECLARATION = /\.d\.[cm]?ts$/;
15
+ /**
16
+ * Every source file under `root`, in walk order.
17
+ *
18
+ * @remarks
19
+ * Walks with an explicit stack, visiting each real directory once (via
20
+ * `realPath`), so a symlink loop terminates and a linked directory is not
21
+ * read twice. `node_modules` is never entered. A missing root fails; it never
22
+ * walks nothing. A dangling symlink under the root is skipped, having nothing
23
+ * to read; any other entry that cannot be read fails the walk.
24
+ */
25
+ const walkSources = Effect.fn("walkSources")(function* (options) {
26
+ const fs = yield* FileSystem.FileSystem;
27
+ const path = yield* Path.Path;
28
+ const posix = (relative) => relative.split(path.sep).join("/");
29
+ const found = [];
30
+ const visited = /* @__PURE__ */ new Set();
31
+ const pending = [options.root];
32
+ while (pending.length > 0) {
33
+ const directory = pending.pop();
34
+ if (directory === void 0) break;
35
+ const real = yield* fs.realPath(directory);
36
+ if (visited.has(real)) continue;
37
+ visited.add(real);
38
+ for (const name of yield* fs.readDirectory(directory)) {
39
+ const full = path.join(directory, name);
40
+ const entry = yield* fs.stat(full).pipe(Effect.map(Option.some), Effect.catch((error) => error.reason._tag === "NotFound" ? fs.readLink(full).pipe(Effect.as(Option.none()), Effect.mapError(() => error)) : Effect.fail(error)));
41
+ if (Option.isNone(entry)) continue;
42
+ const info = entry.value;
43
+ if (info.type === "Directory") {
44
+ if (name !== "node_modules") pending.push(full);
45
+ continue;
46
+ }
47
+ if (info.type !== "File" || DECLARATION.test(name) || !options.extensions.some((extension) => name.endsWith(extension))) continue;
48
+ found.push({
49
+ file: posix(path.relative(options.root, full)),
50
+ path: full
51
+ });
52
+ }
53
+ }
54
+ return found;
55
+ });
56
+
57
+ //#endregion
58
+ export { DECLARATION, DEFAULT_EXTENSIONS, walkSources };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effected/workspaces",
3
- "version": "0.32.1",
3
+ "version": "0.33.0",
4
4
  "private": false,
5
5
  "description": "Monorepo workspace tooling as Effect services — root discovery, package enumeration, the dependency graph, package-manager detection, pnpm catalog resolution, lockfile IO and git-based change detection.",
6
6
  "keywords": [
@@ -51,23 +51,23 @@
51
51
  "./package.json": "./package.json"
52
52
  },
53
53
  "dependencies": {
54
- "@effected/commands": "^0.11.1",
55
- "@effected/git": "^0.20.1",
56
- "@effected/glob": "^0.10.1",
57
- "@effected/jsonc": "^0.15.2",
58
- "@effected/lockfiles": "^0.15.1",
59
- "@effected/npm": "^0.20.1",
60
- "@effected/package-json": "^0.20.2",
61
- "@effected/semver": "^0.11.1",
62
- "@effected/walker": "^0.15.1",
63
- "@effected/yaml": "^0.19.2",
54
+ "@effected/commands": "^0.12.0",
55
+ "@effected/git": "^0.21.0",
56
+ "@effected/glob": "^0.11.0",
57
+ "@effected/jsonc": "^0.16.0",
58
+ "@effected/lockfiles": "^0.16.0",
59
+ "@effected/npm": "^0.21.0",
60
+ "@effected/package-json": "^0.21.0",
61
+ "@effected/semver": "^0.12.0",
62
+ "@effected/walker": "^0.16.0",
63
+ "@effected/yaml": "^0.20.0",
64
64
  "@pnpm/catalogs.config": "^1100.0.5",
65
65
  "@pnpm/catalogs.protocol-parser": "^1100.0.0",
66
66
  "@pnpm/catalogs.resolver": "^1100.0.0",
67
67
  "@pnpm/catalogs.types": "^1100.0.0"
68
68
  },
69
69
  "peerDependencies": {
70
- "effect": "4.0.2"
70
+ "effect": "^4.0.0"
71
71
  },
72
72
  "engines": {
73
73
  "node": ">=24.11.0"
package/testing.d.ts CHANGED
@@ -5,92 +5,6 @@ import { WorkspaceManifest } from "@effected/lockfiles";
5
5
  import { Package } from "@effected/package-json";
6
6
  import { CommandOutput } from "@effected/commands";
7
7
  import { ChildProcess, ChildProcessSpawner } from "effect/process";
8
- //#region src/LayerPolicy.d.ts
9
- declare const LayerPolicyError_base: Schema.Class<LayerPolicyError, Schema.TaggedStruct<"LayerPolicyError", {
10
- /** `read`: the file could not be read. `json`: it is not JSON. `decode`: it is not a `LayerPolicy`. */
11
- readonly reason: Schema.Literals<readonly ["read", "json", "decode"]>;
12
- /** The file, when the policy came from one. */
13
- readonly path: Schema.optionalKey<Schema.String>;
14
- /** The originating failure. */
15
- readonly cause: Schema.Defect;
16
- }>, import("effect/Cause").YieldableError>;
17
- /**
18
- * Raised when a layer policy cannot be read, parsed or decoded.
19
- *
20
- * @public
21
- */
22
- export declare class LayerPolicyError extends LayerPolicyError_base {
23
- /** Renders the failure kind and the file into one line. */
24
- get message(): string;
25
- }
26
- declare const LayerPolicy_base: Schema.Class<LayerPolicy, Schema.Struct<{
27
- /** Exact package names (never relative paths) per layer, top layer first. */
28
- readonly layers: Schema.$Array<Schema.$Array<Schema.String>>;
29
- /** Exact package names (never relative paths) any layer may depend on that never depend on a layer. */
30
- readonly tooling: Schema.$Array<Schema.String>;
31
- /** Globs matched against package names (never relative paths) for packages whose own edges are not checked. */
32
- readonly unconstrained: Schema.$Array<Schema.String>;
33
- /** The dependency maps to check. Absent means all four. */
34
- readonly fields: Schema.optionalKey<Schema.$Array<Schema.Literals<readonly ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]>>>;
35
- /** Edges that must exist, written `"a -> b"`. */
36
- readonly requiredEdges: Schema.optionalKey<Schema.$Array<Schema.String>>;
37
- }>, {}>;
38
- /**
39
- * A committed dependency-layering policy (`layers.json`).
40
- *
41
- * @remarks
42
- * `layers` is top-down: an edge may only point from a layer to one BELOW it
43
- * (a higher index), or into `tooling`. `tooling` packages may depend on each
44
- * other but never reach a layer. `unconstrained` holds globs (through
45
- * `@effected/glob`) for packages whose own edges are not checked (a test
46
- * harness, the private root). `fields` narrows which dependency maps count,
47
- * defaulting to all four. `requiredEdges` (`"a -> b"`) are edges that must
48
- * exist in a checked field: the non-vacuity guard against a discovery that
49
- * quietly drops real edges.
50
- *
51
- * Every entry matches a package's `name`, never its `relativePath`: `layers`
52
- * and `tooling` list exact names, and `unconstrained` globs match names. A
53
- * private root named `my-monorepo` at `relativePath` `"."` is classified as
54
- * `"my-monorepo"`; an entry of `"."` or `"packages/*"` classifies nothing.
55
- *
56
- * Decoding is strict: a key the policy does not model fails `decode`, and the
57
- * message names every such key. A typo on an optional key would otherwise be
58
- * silently dropped: `requiredEdge` would remove the non-vacuity guard and
59
- * leave the report green. `$schema` is always accepted. A file that carries
60
- * keys of its own, such as a `harness` block, passes them in `allowKeys`.
61
- *
62
- * @example
63
- * ```ts
64
- * import { NodeServices } from "@effect/platform-node";
65
- * import { LayerPolicy } from "@effected/workspaces/testing";
66
- * import { Effect } from "effect";
67
- *
68
- * const policy = LayerPolicy.load("/repo/layers.json").pipe(Effect.provide(NodeServices.layer));
69
- * ```
70
- *
71
- * @public
72
- */
73
- export declare class LayerPolicy extends LayerPolicy_base {
74
- /** The dependency maps a check reads: `fields`, or all four. */
75
- get effectiveFields(): ReadonlyArray<DependencyField>;
76
- /**
77
- * Decode a policy from a parsed JSON value.
78
- *
79
- * @remarks
80
- * Every key the policy does not model fails, except `$schema` and the
81
- * keys named in `options.allowKeys`, which are dropped before decoding.
82
- * `options.path` names the file in the error.
83
- */
84
- static readonly decode: (input: unknown, options?: {
85
- readonly path?: string | undefined;
86
- readonly allowKeys?: ReadonlyArray<string> | undefined;
87
- } | undefined) => Effect.Effect<LayerPolicy, LayerPolicyError, never>;
88
- /** Read, parse and decode the policy file at `path`; `options.allowKeys` is passed to `decode`. */
89
- static readonly load: (path: string, options?: {
90
- readonly allowKeys?: ReadonlyArray<string> | undefined;
91
- } | undefined) => Effect.Effect<LayerPolicy, LayerPolicyError, FileSystem.FileSystem>;
92
- }
93
- //#endregion
94
8
  //#region src/WorkspacePackage.d.ts
95
9
  declare const PublishConfig_base: Schema.Class<PublishConfig, Schema.Struct<{
96
10
  /** Scoped-package visibility. Its presence overrides `private`. */
@@ -328,20 +242,6 @@ export declare class WorkspacePackage extends WorkspacePackage_base {
328
242
  manifest(): Effect.Effect<Package, WorkspaceManifestError, FileSystem.FileSystem>;
329
243
  }
330
244
  //#endregion
331
- //#region src/PackageManagerName.d.ts
332
- /**
333
- * The four package managers this package understands.
334
- *
335
- * @public
336
- */
337
- export declare const PackageManagerName: Schema.Literals<readonly ["npm", "pnpm", "yarn", "bun"]>;
338
- /**
339
- * The decoded type of {@link (PackageManagerName:variable)}: `"npm" | "pnpm" | "yarn" | "bun"`.
340
- *
341
- * @public
342
- */
343
- export type PackageManagerName = typeof PackageManagerName.Type;
344
- //#endregion
345
245
  //#region src/WorkspaceRoot.d.ts
346
246
  /**
347
247
  * Options for {@link WorkspaceRoot}'s `find`.
@@ -888,6 +788,419 @@ export declare class WorkspaceDiscovery extends WorkspaceDiscovery_base {
888
788
  static readonly workspaceResolver: Layer.Layer<WorkspaceResolver, never, WorkspaceDiscovery>;
889
789
  }
890
790
  //#endregion
791
+ //#region src/ImportGraph.d.ts
792
+ declare const NoEntriesError_base: Schema.Class<NoEntriesError, Schema.TaggedStruct<"NoEntriesError", {
793
+ /** The root the walk would have reported against. */
794
+ readonly root: Schema.String;
795
+ }>, import("effect/Cause").YieldableError>;
796
+ /**
797
+ * The entries list named no file to start from, so a walk would prove nothing.
798
+ *
799
+ * @public
800
+ */
801
+ export declare class NoEntriesError extends NoEntriesError_base {
802
+ /** Renders the root into a one-line message. */
803
+ get message(): string;
804
+ }
805
+ declare const EntryNotFoundError_base: Schema.Class<EntryNotFoundError, Schema.TaggedStruct<"EntryNotFoundError", {
806
+ /** The entry, verbatim. */
807
+ readonly entry: Schema.String;
808
+ /** The root it was resolved against. */
809
+ readonly root: Schema.String;
810
+ }>, import("effect/Cause").YieldableError>;
811
+ /**
812
+ * An entry named a file no resolution candidate could find. The walk fails
813
+ * rather than starting from nothing: a typo'd entry must never read as clean.
814
+ *
815
+ * @public
816
+ */
817
+ export declare class EntryNotFoundError extends EntryNotFoundError_base {
818
+ /** Renders the entry and root into a one-line message. */
819
+ get message(): string;
820
+ }
821
+ declare const UnresolvedImport_base: Schema.Class<UnresolvedImport, Schema.Struct<{
822
+ /** The importing file, relative to the root, with `/` separators. */
823
+ readonly file: Schema.String;
824
+ /** The specifier, verbatim. */
825
+ readonly specifier: Schema.String;
826
+ }>, {}>;
827
+ /**
828
+ * One relative import no file candidate resolved: a link of the graph the
829
+ * walk could not follow, so whatever it would have reached is unproven.
830
+ *
831
+ * @public
832
+ */
833
+ export declare class UnresolvedImport extends UnresolvedImport_base {
834
+ /** `file specifier`, the form an assertion message reads best in. */
835
+ get label(): string;
836
+ }
837
+ declare const ReachOffence_base: Schema.Class<ReachOffence, Schema.Struct<{
838
+ /** The importing file, relative to the root, with `/` separators. */
839
+ readonly file: Schema.String;
840
+ /** The 1-based line. */
841
+ readonly line: Schema.Number;
842
+ /** The 1-based column, in UTF-16 code units. */
843
+ readonly column: Schema.Number;
844
+ /** The forbidden specifier, verbatim. */
845
+ readonly specifier: Schema.String;
846
+ /**
847
+ * The files the walk took from an entry to `file`, entry first and `file`
848
+ * last, each relative to the root with `/` separators. Breadth-first, so
849
+ * this is a shortest chain; it is the chain the walk first reached `file`
850
+ * by, and a file is visited once whatever else reaches it.
851
+ */
852
+ readonly chain: Schema.$Array<Schema.String>;
853
+ }>, {}>;
854
+ /**
855
+ * One forbidden import a walk from the entries reached.
856
+ *
857
+ * @public
858
+ */
859
+ export declare class ReachOffence extends ReachOffence_base {
860
+ /** `file:line:column specifier via chain`, the form an assertion message reads best in. */
861
+ get label(): string;
862
+ }
863
+ declare const ReachabilityScan_base: Schema.Class<ReachabilityScan, Schema.Struct<{
864
+ /**
865
+ * Every file the walk reached, entries included, relative to the root with
866
+ * `/` separators, sorted. Assert it is non-empty: a walk that reached
867
+ * nothing proves nothing.
868
+ */
869
+ readonly files: Schema.$Array<Schema.String>;
870
+ /**
871
+ * Every relative specifier no file candidate resolved, one per
872
+ * file-and-specifier pair, sorted by file then specifier. Assert it is
873
+ * empty, or exactly the generated files you expect: an unresolved link is
874
+ * a part of the graph the walk could not prove anything about.
875
+ */
876
+ readonly unresolved: Schema.$Array<typeof UnresolvedImport>;
877
+ /** Every forbidden import the walk reached, sorted by file, then line, then column. */
878
+ readonly offences: Schema.$Array<typeof ReachOffence>;
879
+ }>, {}>;
880
+ /**
881
+ * What a reachability walk read and found.
882
+ *
883
+ * @public
884
+ */
885
+ export declare class ReachabilityScan extends ReachabilityScan_base {
886
+ /** One `file:line:column specifier via chain` label per offence: `[]` means the entries reach nothing forbidden. */
887
+ get violations(): ReadonlyArray<string>;
888
+ }
889
+ /**
890
+ * Options for {@link ImportGraph.reachability}.
891
+ *
892
+ * @public
893
+ */
894
+ interface ReachabilityOptions {
895
+ /** The directory every entry and reported path is relative to. */
896
+ readonly root: string;
897
+ /**
898
+ * The files the walk starts from, relative to `root` with `/` separators,
899
+ * or absolute. Each resolves like a relative import (see `extensions`);
900
+ * one that resolves to no file fails `EntryNotFoundError`, and an empty
901
+ * list fails `NoEntriesError` — the walk never proves nothing silently.
902
+ */
903
+ readonly entries: ReadonlyArray<string>;
904
+ /**
905
+ * Specifiers no reached file may import, matched like a `SourceBoundary`
906
+ * `forbidImports` entry: equal to the specifier, a subpath of it, or its
907
+ * text before a trailing `*`.
908
+ */
909
+ readonly forbid: ReadonlyArray<string>;
910
+ /**
911
+ * File extensions a relative specifier resolves through: `./a.js` also
912
+ * tries `./a` with each extension, and `./a/index` with each. Defaults to
913
+ * `.ts`, `.mts`, `.cts`, `.js`, `.mjs` and `.cjs`; declaration files are
914
+ * never a candidate.
915
+ */
916
+ readonly extensions?: ReadonlyArray<string> | undefined;
917
+ /**
918
+ * The workspace-package extension point: called with every bare specifier
919
+ * a reached file imports, answering the file to continue the walk from
920
+ * (relative to `root` with `/` separators, or absolute, resolved like an
921
+ * entry), or `undefined` to stop at the specifier. A path that resolves to
922
+ * no file is reported in `unresolved` under the specifier it was reached
923
+ * by. The default stops at every bare specifier: the walk follows relative
924
+ * imports only.
925
+ */
926
+ readonly followPackage?: ((specifier: string) => string | undefined) | undefined;
927
+ }
928
+ declare const UndeclaredImport_base: Schema.Class<UndeclaredImport, Schema.Struct<{
929
+ /** The name of the package that owns the importing file. */
930
+ readonly package: Schema.String;
931
+ /** The importing file, relative to its package, with `/` separators. */
932
+ readonly file: Schema.String;
933
+ /** The 1-based line. */
934
+ readonly line: Schema.Number;
935
+ /** The 1-based column, in UTF-16 code units. */
936
+ readonly column: Schema.Number;
937
+ /** The specifier, verbatim. */
938
+ readonly specifier: Schema.String;
939
+ /** The package name the specifier names: `effect` for `effect/Schema`. */
940
+ readonly dependency: Schema.String;
941
+ }>, {}>;
942
+ /**
943
+ * One runtime import the owning package's manifest does not declare.
944
+ *
945
+ * @public
946
+ */
947
+ export declare class UndeclaredImport extends UndeclaredImport_base {
948
+ /** `package file:line:column undeclared dependency`, the form an assertion message reads best in. */
949
+ get label(): string;
950
+ }
951
+ declare const UndeclaredScan_base: Schema.Class<UndeclaredScan, Schema.Struct<{
952
+ /** Every package whose manifest the check read, sorted by name. Assert it is non-empty. */
953
+ readonly packages: Schema.$Array<Schema.String>;
954
+ /**
955
+ * Every file the check read, relative to the workspace root with `/`
956
+ * separators, sorted. Each file appears once, listed under its innermost
957
+ * package even when a wider package's glob also walked it. Assert it is
958
+ * non-empty: a check that read no file proves nothing, and a mistyped
959
+ * `include` shows up here.
960
+ */
961
+ readonly files: Schema.$Array<Schema.String>;
962
+ /** Every undeclared runtime import, sorted by package, then file, then line, then column. */
963
+ readonly undeclared: Schema.$Array<typeof UndeclaredImport>;
964
+ }>, {}>;
965
+ /**
966
+ * What an undeclared-import check read and found.
967
+ *
968
+ * @public
969
+ */
970
+ export declare class UndeclaredScan extends UndeclaredScan_base {
971
+ /** One `package file:line:column undeclared dependency` label per import: `[]` means every runtime import is declared. */
972
+ get violations(): ReadonlyArray<string>;
973
+ }
974
+ /**
975
+ * Options for {@link ImportGraph.undeclared}.
976
+ *
977
+ * @public
978
+ */
979
+ interface UndeclaredOptions {
980
+ /**
981
+ * Bare Node built-ins (`"fs"`, `"child_process"`), never flagged. A
982
+ * `node:`-prefixed specifier is always a built-in; no bare list ships
983
+ * here, because one would drift with Node releases — spread
984
+ * `builtinModules` from `node:module` in your test file. A bare built-in
985
+ * not on this list is flagged like any undeclared package.
986
+ */
987
+ readonly builtins?: ReadonlyArray<string> | undefined;
988
+ /**
989
+ * Globs, matched against each package-relative path with `/` separators,
990
+ * naming the runtime source files to read. Defaults to `["src/**"]`: the
991
+ * check compares against `dependencies`, `peerDependencies` and
992
+ * `optionalDependencies`, so test files importing devDependencies are out
993
+ * of scope by design. Each glob is walked from its static prefix (the text
994
+ * before its first pattern character), so the default walks `src/` and
995
+ * never `dist/` or `node_modules`; a glob with no static prefix walks the
996
+ * package directory. A file under a nested workspace package belongs to
997
+ * that innermost package alone: a root glob starting with `**` walks the
998
+ * whole repository but checks each nested file only against its own
999
+ * package's manifest.
1000
+ */
1001
+ readonly include?: ReadonlyArray<string> | undefined;
1002
+ /**
1003
+ * File extensions to read. Defaults to `.ts`, `.mts`, `.cts`, `.js`,
1004
+ * `.mjs` and `.cjs`; declaration files are always skipped.
1005
+ */
1006
+ readonly extensions?: ReadonlyArray<string> | undefined;
1007
+ }
1008
+ /**
1009
+ * Import-graph checks: properties of what the imports of a set of files
1010
+ * reach, which no one-file rule can express.
1011
+ *
1012
+ * @remarks
1013
+ * {@link SourceBoundary} checks one file at a time. Two guards consumers want
1014
+ * are properties of the import graph itself:
1015
+ *
1016
+ * - {@link ImportGraph.reachability} answers "nothing reachable from entry X
1017
+ * imports Y". A directory rule approximates that, but a refactor which adds
1018
+ * a new static path from the root entry into an allowed directory still
1019
+ * passes it; a reachability walk follows the real import edges and reports
1020
+ * the chain that reaches the forbidden specifier.
1021
+ * - {@link ImportGraph.undeclared} answers "every runtime import is
1022
+ * declared". Workspace hoisting resolves an import of a package the
1023
+ * manifest never declared, and only a packed install catches that today —
1024
+ * far too slow for the edit loop. This check is static and fast: each
1025
+ * package's literal specifiers against its own `dependencies`,
1026
+ * `peerDependencies` and `optionalDependencies`.
1027
+ *
1028
+ * Both are static analysis of literal specifiers: static imports,
1029
+ * re-exports, and `import()`/`require()` calls whose argument is one string
1030
+ * literal. There is no dynamic-import guarantee: a computed specifier
1031
+ * (`import("./ui/" + name)`) is invisible to both, and a packed install
1032
+ * (`PackedInstall`) remains the proof of what actually resolves at
1033
+ * runtime. Both reuse the `SourceBoundary` lexer, so a specifier in a
1034
+ * comment, a string, template text or a regex body is never an import, and
1035
+ * both refuse to pass vacuously: every result reports what it read.
1036
+ *
1037
+ * @example
1038
+ * ```ts
1039
+ * import { NodeServices } from "@effect/platform-node";
1040
+ * import { ImportGraph } from "@effected/workspaces/testing";
1041
+ * import { Effect } from "effect";
1042
+ *
1043
+ * // Nothing reachable from the root entry may import ink or react.
1044
+ * const walk = ImportGraph.reachability({
1045
+ * root: "/repo/packages/ui/src",
1046
+ * entries: ["index.ts"],
1047
+ * forbid: ["ink", "react"],
1048
+ * }).pipe(Effect.provide(NodeServices.layer));
1049
+ * ```
1050
+ *
1051
+ * @public
1052
+ */
1053
+ export declare class ImportGraph {
1054
+ private constructor();
1055
+ /**
1056
+ * Walk the import graph from `entries` through relative imports and report
1057
+ * every forbidden specifier a reached file imports.
1058
+ *
1059
+ * @remarks
1060
+ * The walk is breadth-first, so a violation's chain is a shortest path
1061
+ * from an entry, and each file is visited once through its `realPath`, so
1062
+ * a symlink loop terminates and a diamond reports one chain. The walk is
1063
+ * conservative: it follows every literal specifier, type-only imports
1064
+ * included, so a forbidden import only a type reaches is still reported.
1065
+ * A bare specifier is checked against `forbid` — the same matcher
1066
+ * `SourceBoundary.check` uses — but only walked into when `followPackage`
1067
+ * names a file for it. A relative specifier that resolves to no file is
1068
+ * reported in `unresolved`, never dropped: the part of the graph behind it
1069
+ * is unproven.
1070
+ */
1071
+ static readonly reachability: (options: ReachabilityOptions) => Effect.Effect<ReachabilityScan, EntryNotFoundError | NoEntriesError | import("effect/PlatformError").PlatformError, FileSystem.FileSystem | Path.Path>;
1072
+ /**
1073
+ * Check every workspace package's runtime sources for imports its own
1074
+ * manifest does not declare.
1075
+ *
1076
+ * @remarks
1077
+ * For each discovered package, reads the files its `include` globs name
1078
+ * (`src/**` by default) and compares every runtime specifier against the
1079
+ * package's `dependencies`, `peerDependencies` and `optionalDependencies`.
1080
+ * `devDependencies` never declares: an import of one from a runtime source
1081
+ * is exactly the hoisting bug this check exists for. `optionalDependencies`
1082
+ * does declare: npm and pnpm install one by default, so a packed install
1083
+ * resolves the import. A relative,
1084
+ * absolute, `#`-subpath or URL-scheme specifier is never a package import;
1085
+ * a `node:` specifier is always a built-in, and a bare built-in is one only
1086
+ * when `builtins` lists it. A package importing its own name (a
1087
+ * self-reference through `exports`) is declared by definition. A file
1088
+ * under two discovered packages (a nested package under the root) is
1089
+ * checked against its innermost package's manifest only, so a root-wide
1090
+ * `include` never double-reports it. Type-only imports are erased by the
1091
+ * compiler and never flagged: `import type { X } from`, `export type`
1092
+ * re-exports, and an `import()` in a type position (`typeof import("x")`,
1093
+ * an annotation, a type alias right-hand side, a generic argument). The
1094
+ * position is judged by the construct enclosing the call, not by the one
1095
+ * token before it, so a runtime `import("x")` in a typed initializer, an
1096
+ * object literal, a ternary branch or a call argument counts like any
1097
+ * static import. One `import type from "x"` is runtime too — TypeScript
1098
+ * reads it as a default import of a binding named `type`. What the static
1099
+ * pass cannot see — a computed specifier — only `PackedInstall` proves.
1100
+ */
1101
+ static readonly undeclared: (options?: UndeclaredOptions | undefined) => Effect.Effect<UndeclaredScan, import("@effected/glob").GlobPatternError | import("effect/PlatformError").PlatformError | WorkspaceDiscoveryFailure, FileSystem.FileSystem | Path.Path | WorkspaceDiscovery>;
1102
+ }
1103
+ //#endregion
1104
+ //#region src/LayerPolicy.d.ts
1105
+ declare const LayerPolicyError_base: Schema.Class<LayerPolicyError, Schema.TaggedStruct<"LayerPolicyError", {
1106
+ /** `read`: the file could not be read. `json`: it is not JSON. `decode`: it is not a `LayerPolicy`. */
1107
+ readonly reason: Schema.Literals<readonly ["read", "json", "decode"]>;
1108
+ /** The file, when the policy came from one. */
1109
+ readonly path: Schema.optionalKey<Schema.String>;
1110
+ /** The originating failure. */
1111
+ readonly cause: Schema.Defect;
1112
+ }>, import("effect/Cause").YieldableError>;
1113
+ /**
1114
+ * Raised when a layer policy cannot be read, parsed or decoded.
1115
+ *
1116
+ * @public
1117
+ */
1118
+ export declare class LayerPolicyError extends LayerPolicyError_base {
1119
+ /** Renders the failure kind and the file into one line. */
1120
+ get message(): string;
1121
+ }
1122
+ declare const LayerPolicy_base: Schema.Class<LayerPolicy, Schema.Struct<{
1123
+ /** Exact package names (never relative paths) per layer, top layer first. */
1124
+ readonly layers: Schema.$Array<Schema.$Array<Schema.String>>;
1125
+ /** Exact package names (never relative paths) any layer may depend on that never depend on a layer. */
1126
+ readonly tooling: Schema.$Array<Schema.String>;
1127
+ /** Globs matched against package names (never relative paths) for packages whose own edges are not checked. */
1128
+ readonly unconstrained: Schema.$Array<Schema.String>;
1129
+ /** The dependency maps to check. Absent means all four. */
1130
+ readonly fields: Schema.optionalKey<Schema.$Array<Schema.Literals<readonly ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]>>>;
1131
+ /** Edges that must exist, written `"a -> b"`. */
1132
+ readonly requiredEdges: Schema.optionalKey<Schema.$Array<Schema.String>>;
1133
+ }>, {}>;
1134
+ /**
1135
+ * A committed dependency-layering policy (`layers.json`).
1136
+ *
1137
+ * @remarks
1138
+ * `layers` is top-down: an edge may only point from a layer to one BELOW it
1139
+ * (a higher index), or into `tooling`. `tooling` packages may depend on each
1140
+ * other but never reach a layer. `unconstrained` holds globs (through
1141
+ * `@effected/glob`) for packages whose own edges are not checked (a test
1142
+ * harness, the private root). `fields` narrows which dependency maps count,
1143
+ * defaulting to all four. `requiredEdges` (`"a -> b"`) are edges that must
1144
+ * exist in a checked field: the non-vacuity guard against a discovery that
1145
+ * quietly drops real edges.
1146
+ *
1147
+ * Every entry matches a package's `name`, never its `relativePath`: `layers`
1148
+ * and `tooling` list exact names, and `unconstrained` globs match names. A
1149
+ * private root named `my-monorepo` at `relativePath` `"."` is classified as
1150
+ * `"my-monorepo"`; an entry of `"."` or `"packages/*"` classifies nothing.
1151
+ *
1152
+ * Decoding is strict: a key the policy does not model fails `decode`, and the
1153
+ * message names every such key. A typo on an optional key would otherwise be
1154
+ * silently dropped: `requiredEdge` would remove the non-vacuity guard and
1155
+ * leave the report green. `$schema` is always accepted. A file that carries
1156
+ * keys of its own, such as a `harness` block, passes them in `allowKeys`.
1157
+ *
1158
+ * @example
1159
+ * ```ts
1160
+ * import { NodeServices } from "@effect/platform-node";
1161
+ * import { LayerPolicy } from "@effected/workspaces/testing";
1162
+ * import { Effect } from "effect";
1163
+ *
1164
+ * const policy = LayerPolicy.load("/repo/layers.json").pipe(Effect.provide(NodeServices.layer));
1165
+ * ```
1166
+ *
1167
+ * @public
1168
+ */
1169
+ export declare class LayerPolicy extends LayerPolicy_base {
1170
+ /** The dependency maps a check reads: `fields`, or all four. */
1171
+ get effectiveFields(): ReadonlyArray<DependencyField>;
1172
+ /**
1173
+ * Decode a policy from a parsed JSON value.
1174
+ *
1175
+ * @remarks
1176
+ * Every key the policy does not model fails, except `$schema` and the
1177
+ * keys named in `options.allowKeys`, which are dropped before decoding.
1178
+ * `options.path` names the file in the error.
1179
+ */
1180
+ static readonly decode: (input: unknown, options?: {
1181
+ readonly path?: string | undefined;
1182
+ readonly allowKeys?: ReadonlyArray<string> | undefined;
1183
+ } | undefined) => Effect.Effect<LayerPolicy, LayerPolicyError, never>;
1184
+ /** Read, parse and decode the policy file at `path`; `options.allowKeys` is passed to `decode`. */
1185
+ static readonly load: (path: string, options?: {
1186
+ readonly allowKeys?: ReadonlyArray<string> | undefined;
1187
+ } | undefined) => Effect.Effect<LayerPolicy, LayerPolicyError, FileSystem.FileSystem>;
1188
+ }
1189
+ //#endregion
1190
+ //#region src/PackageManagerName.d.ts
1191
+ /**
1192
+ * The four package managers this package understands.
1193
+ *
1194
+ * @public
1195
+ */
1196
+ export declare const PackageManagerName: Schema.Literals<readonly ["npm", "pnpm", "yarn", "bun"]>;
1197
+ /**
1198
+ * The decoded type of {@link (PackageManagerName:variable)}: `"npm" | "pnpm" | "yarn" | "bun"`.
1199
+ *
1200
+ * @public
1201
+ */
1202
+ export type PackageManagerName = typeof PackageManagerName.Type;
1203
+ //#endregion
891
1204
  //#region src/PackedInstall.d.ts
892
1205
  /**
893
1206
  * Where each closure package is packed from.
@@ -1870,9 +2183,10 @@ export declare class SourceBoundary {
1870
2183
  * Check every source file under `root` against `rules`.
1871
2184
  *
1872
2185
  * @remarks
1873
- * Walks with an explicit stack, visiting each real directory once (via
2186
+ * Walks with the shared source walker (`internal/sourceWalk.ts`, which the
2187
+ * `ImportGraph` checks also use): each real directory is visited once (via
1874
2188
  * `realPath`), so a symlink loop terminates and a linked directory is not
1875
- * scanned twice. `node_modules` is never entered. Paths come back relative
2189
+ * scanned twice, and `node_modules` is never entered. Paths come back relative
1876
2190
  * and `/`-separated whatever the platform's separator, and that is also
1877
2191
  * what `allow` globs match against. A missing root fails; it never scans
1878
2192
  * nothing. A dangling symlink under the root is skipped, having nothing to
@@ -1890,7 +2204,7 @@ export declare class SourceBoundary {
1890
2204
  * }).pipe(Effect.provide(NodeServices.layer));
1891
2205
  * ```
1892
2206
  */
1893
- static readonly scan: (options: ScanOptions) => Effect.Effect<SourceScan, import("@effected/glob").GlobPatternError | import("effect/PlatformError").PlatformError, FileSystem.FileSystem | Path.Path>;
2207
+ static readonly scan: (options: ScanOptions) => Effect.Effect<SourceScan, import("@effected/glob").GlobPatternError | import("effect/PlatformError").PlatformError, FileSystem.FileSystem | import("effect/Path").Path>;
1894
2208
  /** The shipped positive- and negative-control snippets, at least one of each per rule kind. */
1895
2209
  static readonly fixtures: ReadonlyArray<BoundaryFixture>;
1896
2210
  /** The names of shipped fixtures the scanner gets wrong; `[]` means it still flags and spares what it must. */
@@ -1992,5 +2306,5 @@ export declare class WorkspaceLayering {
1992
2306
  static readonly checkWorkspace: (policy: LayerPolicy) => Effect.Effect<LayeringReport, WorkspaceDiscoveryFailure, WorkspaceDiscovery>;
1993
2307
  }
1994
2308
  //#endregion
1995
- export type { BinCommandOptions, BinProvenance, BoundaryFixture, BoundaryRule, DependencyDiff, FindWorkspaceRootOptions, LayeringGraph, OffenceRule, PackSource, PackedInstallBudget, PackedInstallClosureOptions, PackedInstallGate, PackedInstallOptions, PackedInstallPreflight, PackedInstallPreflightOptions, ReferenceOptions, RunBinOptions, ScanOptions, WorkspaceDiscoveryFailure, WorkspaceDiscoveryOptions, WorkspaceDiscoveryShape, WorkspaceLookupFailure, WorkspaceRootShape };
2309
+ export type { BinCommandOptions, BinProvenance, BoundaryFixture, BoundaryRule, DependencyDiff, FindWorkspaceRootOptions, LayeringGraph, OffenceRule, PackSource, PackedInstallBudget, PackedInstallClosureOptions, PackedInstallGate, PackedInstallOptions, PackedInstallPreflight, PackedInstallPreflightOptions, ReachabilityOptions, ReferenceOptions, RunBinOptions, ScanOptions, UndeclaredOptions, WorkspaceDiscoveryFailure, WorkspaceDiscoveryOptions, WorkspaceDiscoveryShape, WorkspaceLookupFailure, WorkspaceRootShape };
1996
2310
  //# sourceMappingURL=testing.d.ts.map