@ecoma-io/archkeep 0.15.0 → 0.16.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 +3 -3
- package/cli.mjs +126 -4
- package/commands.mjs +6 -0
- package/lsp.mjs +15 -2
- package/package.json +6 -2
- package/src/analysis/analyze.mjs +15 -0
- package/src/analysis/contract.md +36 -18
- package/src/analysis/csharp.mjs +485 -0
- package/src/analysis/dotnet/csproj.mjs +380 -0
- package/src/analysis/dotnet/mask.mjs +178 -0
- package/src/analysis/dotnet/namespaces.mjs +172 -0
- package/src/analysis/dotnet/resolve.mjs +89 -0
- package/src/analysis/go.mjs +289 -5
- package/src/analysis/java.mjs +329 -0
- package/src/analysis/jvm/gradle.mjs +545 -0
- package/src/analysis/jvm/mask.mjs +170 -0
- package/src/analysis/jvm/maven.mjs +612 -0
- package/src/analysis/jvm/packages.mjs +209 -0
- package/src/analysis/jvm/resolve.mjs +139 -0
- package/src/analysis/kotlin.mjs +210 -0
- package/src/analysis/manifest-util.mjs +30 -0
- package/src/analysis/python.mjs +3 -2
- package/src/analysis/registry.mjs +11 -0
- package/src/analysis/rust.mjs +171 -17
- package/src/analysis/source-util.mjs +155 -6
- package/src/analysis/typescript.mjs +9 -2
- package/src/commands/context.mjs +84 -14
- package/src/commands/provenance.mjs +7 -44
- package/src/commands/rules.mjs +775 -0
- package/src/governance/profile-registry.mjs +0 -1
- package/src/graph/create-dependencies.mjs +138 -15
- package/src/lsp/diagnose.mjs +1 -1
- package/src/lsp/server.mjs +97 -1
- package/src/lsp/workspace-index.mjs +106 -15
- package/src/options.mjs +30 -7
- package/src/process.mjs +10 -1
- package/src/providers/moon.mjs +287 -36
- package/src/providers/native/differential.fixtures.mjs +32 -6
- package/src/providers/native/discover.mjs +83 -4
- package/src/providers/native/graph.mjs +58 -0
- package/src/providers/native/model.mjs +59 -1
- package/src/rules/index.mjs +21 -6
- package/src/rules/reachability.mjs +2 -0
- package/src/rules/tags.mjs +7 -5
- package/src/rules/topology.mjs +5 -3
- package/src/workspace.mjs +115 -23
package/src/commands/context.mjs
CHANGED
|
@@ -26,9 +26,16 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
26
26
|
import { join } from "node:path";
|
|
27
27
|
|
|
28
28
|
import { containmentViolation } from "../containment.mjs";
|
|
29
|
+
import { dotnetIndexFailures } from "../analysis/dotnet/namespaces.mjs";
|
|
30
|
+
import { dotnetManifestFailures } from "../analysis/dotnet/csproj.mjs";
|
|
31
|
+
import { goManifestFailures } from "../analysis/go.mjs";
|
|
32
|
+
import { mavenManifestFailures } from "../analysis/jvm/maven.mjs";
|
|
33
|
+
import { gradleManifestFailures } from "../analysis/jvm/gradle.mjs";
|
|
34
|
+
import { jvmIndexFailures } from "../analysis/jvm/packages.mjs";
|
|
29
35
|
import { languageOf } from "../analysis/registry.mjs";
|
|
30
36
|
import { pythonUnmodelledFailures } from "../analysis/python.mjs";
|
|
31
|
-
import {
|
|
37
|
+
import { rustManifestFailures } from "../analysis/rust.mjs";
|
|
38
|
+
import { dedupeWholeFileFailures, fileFailure } from "../analysis/source-util.mjs";
|
|
32
39
|
import {
|
|
33
40
|
DEFAULT_OPTIONS,
|
|
34
41
|
NX_CONFIG_FILE,
|
|
@@ -39,13 +46,18 @@ import {
|
|
|
39
46
|
import { readProjectGraph } from "../providers/nx.mjs";
|
|
40
47
|
import { ARCHKEEP_MODEL_FILE } from "../providers/native/model.mjs";
|
|
41
48
|
import {
|
|
42
|
-
|
|
43
|
-
|
|
49
|
+
MOON_ALT_WORKSPACE_MARKER,
|
|
50
|
+
MOON_WORKSPACE_MARKER,
|
|
44
51
|
mergeImportEdges,
|
|
45
52
|
moonMarkerAt,
|
|
46
53
|
moonProvider,
|
|
47
54
|
} from "../providers/moon.mjs";
|
|
48
55
|
import { nativeProvider } from "../providers/native/index.mjs";
|
|
56
|
+
import { mergeDeclaredEdges } from "../providers/native/graph.mjs";
|
|
57
|
+
import {
|
|
58
|
+
resolveDeclaredManifestEdges,
|
|
59
|
+
resolveDeclaredManifestFailures,
|
|
60
|
+
} from "../graph/create-dependencies.mjs";
|
|
49
61
|
import {
|
|
50
62
|
analyzeWorkspace,
|
|
51
63
|
annotateMFERemotes,
|
|
@@ -173,9 +185,22 @@ export function requireSingleProjectModel(root, { exists = existsSync } = {}) {
|
|
|
173
185
|
* two callers still differ in posture — one throws where the other returns a
|
|
174
186
|
* default — but they may not differ in what a workspace root IS.
|
|
175
187
|
*
|
|
188
|
+
* The Moon entries are the SHAPED markers — `workspace.yml` inside the
|
|
189
|
+
* directory, not the directory alone (`../providers/moon.mjs`'s
|
|
190
|
+
* `MOON_WORKSPACE_MARKER` owns why): a walk reading directory existence
|
|
191
|
+
* selected the user's home directory as a workspace through `~/.moon`,
|
|
192
|
+
* moonrepo's user-level state directory (#339). A bare `.moon` beside an
|
|
193
|
+
* `nx.json` at a root already chosen is a different question, and stays with
|
|
194
|
+
* `requireSingleProjectModel`'s directory-presence gate below.
|
|
195
|
+
*
|
|
176
196
|
* @type {string[]}
|
|
177
197
|
*/
|
|
178
|
-
export const WORKSPACE_MARKERS = [
|
|
198
|
+
export const WORKSPACE_MARKERS = [
|
|
199
|
+
NX_CONFIG_FILE,
|
|
200
|
+
ARCHKEEP_MODEL_FILE,
|
|
201
|
+
MOON_WORKSPACE_MARKER,
|
|
202
|
+
MOON_ALT_WORKSPACE_MARKER,
|
|
203
|
+
];
|
|
179
204
|
|
|
180
205
|
/**
|
|
181
206
|
* @typedef {object} CommandContext
|
|
@@ -495,9 +520,11 @@ export function resolveCommandContext(
|
|
|
495
520
|
if (root === null) {
|
|
496
521
|
throw new Error(
|
|
497
522
|
`archkeep: no workspace root above ${cwd} — looked for an nx.json, a archkeep.json, or a ` +
|
|
498
|
-
`.moon (or .config/moon)
|
|
499
|
-
`
|
|
500
|
-
`
|
|
523
|
+
`.moon/workspace.yml (or .config/moon/workspace.yml) in every parent, stopping at the top ` +
|
|
524
|
+
`level of the enclosing git repository: beyond it, a marker such as ~/.moon is user-level ` +
|
|
525
|
+
`tooling state, not this workspace's root. The tree to judge is found from the working ` +
|
|
526
|
+
`directory, never from this tool's own location: installed from the registry, this tool ` +
|
|
527
|
+
`lives under the consumer's node_modules and the two are always different trees.`,
|
|
501
528
|
);
|
|
502
529
|
}
|
|
503
530
|
// Which provider may judge at all — the one gate
|
|
@@ -573,6 +600,22 @@ export function resolveCommandContext(
|
|
|
573
600
|
});
|
|
574
601
|
annotateMFERemotes(graph.nodes, workspace.readFile);
|
|
575
602
|
annotatePackageFacts(graph.nodes, workspace.readFile);
|
|
603
|
+
// The manifest track's edges, folded into the graph the verdicts judge.
|
|
604
|
+
// ADR 0006 Decision 3 and ADR 0005 Decision 4 draw declared edges with no
|
|
605
|
+
// face qualifier, and this branch's graph IS the verdict input — without
|
|
606
|
+
// the fold, a cycle closed only by a `<ProjectReference>` (or a Maven
|
|
607
|
+
// `<dependency>`, or a Gradle `project(":x")`) reports empty, the one
|
|
608
|
+
// result this tool may never produce. The failure list doubles as the
|
|
609
|
+
// no-throw guard (`../graph/create-dependencies.mjs`'s
|
|
610
|
+
// `resolveDeclaredManifestFailures` — each resolver refuses on exactly
|
|
611
|
+
// the failures its `*ManifestFailures` twin reports, same memoized
|
|
612
|
+
// model), and the same list joins the funnel below, so a tree that WOULD
|
|
613
|
+
// refuse still refuses through the structured could-not-complete
|
|
614
|
+
// envelope instead of an unhandled error.
|
|
615
|
+
const manifestRefusalFailures = resolveDeclaredManifestFailures(workspace);
|
|
616
|
+
if (manifestRefusalFailures.length === 0) {
|
|
617
|
+
mergeDeclaredEdges(graph, resolveDeclaredManifestEdges(workspace));
|
|
618
|
+
}
|
|
576
619
|
|
|
577
620
|
// `boundaryConfigDeclared` is carried straight off the model rather than
|
|
578
621
|
// re-derived here: `../providers/native/model.mjs`'s
|
|
@@ -605,15 +648,21 @@ export function resolveCommandContext(
|
|
|
605
648
|
// sources feeding the SAME whole-file failure shape a language analyzer
|
|
606
649
|
// produces for an unreadable file, so nothing downstream needs to know
|
|
607
650
|
// which provider found the gap.
|
|
608
|
-
failures = [
|
|
651
|
+
failures = dedupeWholeFileFailures([
|
|
609
652
|
...wholeTreeAnalysis.failures.filter((failure) => selectedFiles.has(failure.sourceFile)),
|
|
610
653
|
...discovered.failures,
|
|
611
654
|
// Workspace-scoped on purpose, the same posture the two unclaimed
|
|
612
655
|
// equivalents above hold: a wildcard run must not be able to hide a
|
|
613
656
|
// project whose manifest it cannot read by naming a path that excludes
|
|
614
|
-
// it (`../analysis/python.mjs`'s `pythonUnmodelledFailures`).
|
|
657
|
+
// it (`../analysis/python.mjs`'s `pythonUnmodelledFailures`). The four
|
|
658
|
+
// manifest lists are computed once above, where the declared-edge fold
|
|
659
|
+
// needs them as its no-throw guard.
|
|
615
660
|
...pythonUnmodelledFailures(workspace),
|
|
616
|
-
|
|
661
|
+
...manifestRefusalFailures,
|
|
662
|
+
...goManifestFailures(workspace),
|
|
663
|
+
...rustManifestFailures(workspace),
|
|
664
|
+
...jvmIndexFailures(workspace),
|
|
665
|
+
]);
|
|
617
666
|
analyzedFiles = wholeTreeAnalysis.analyzedFiles.filter((file) => selectedFiles.has(file));
|
|
618
667
|
analyzed = analyzedFiles.length;
|
|
619
668
|
// Unaffected by `paths`: an exempted file is by definition unowned by any
|
|
@@ -703,6 +752,16 @@ export function resolveCommandContext(
|
|
|
703
752
|
importSites: wholeTreeAnalysis.imports,
|
|
704
753
|
projectOf: (file) => projectOfFile.get(file),
|
|
705
754
|
});
|
|
755
|
+
// The manifest track folds in after the import sites, for the same
|
|
756
|
+
// no-face-qualifier reason the native branch above states at its own
|
|
757
|
+
// fold — Moon has no plugin hook to draw declared edges either, and its
|
|
758
|
+
// `dependsOn` entries are a different track (`moon.yml`), not a
|
|
759
|
+
// substitute for Maven/Gradle/csproj manifests tracked inside its
|
|
760
|
+
// projects. Same provable guard, same funnel reuse.
|
|
761
|
+
const manifestRefusalFailures = resolveDeclaredManifestFailures(workspace);
|
|
762
|
+
if (manifestRefusalFailures.length === 0) {
|
|
763
|
+
mergeDeclaredEdges(graph, resolveDeclaredManifestEdges(workspace));
|
|
764
|
+
}
|
|
706
765
|
|
|
707
766
|
const selected = selectFiles(
|
|
708
767
|
owned.map(({ file }) => file),
|
|
@@ -717,11 +776,15 @@ export function resolveCommandContext(
|
|
|
717
776
|
// analyzes the whole tree before `paths` narrows anything, for the same
|
|
718
777
|
// reason).
|
|
719
778
|
const unclaimedFiles = unclaimedAnalyzableFiles({ tracked, owned });
|
|
720
|
-
failures = [
|
|
779
|
+
failures = dedupeWholeFileFailures([
|
|
721
780
|
...wholeTreeAnalysis.failures.filter((failure) => selectedFiles.has(failure.sourceFile)),
|
|
722
781
|
...unclaimedFileFailures({ files: unclaimedFiles, providerLabel: "the Moon project graph" }),
|
|
723
782
|
...pythonUnmodelledFailures(workspace),
|
|
724
|
-
|
|
783
|
+
...manifestRefusalFailures,
|
|
784
|
+
...goManifestFailures(workspace),
|
|
785
|
+
...rustManifestFailures(workspace),
|
|
786
|
+
...jvmIndexFailures(workspace),
|
|
787
|
+
]);
|
|
725
788
|
unclaimedGap = { files: unclaimedFiles };
|
|
726
789
|
analyzedFiles = wholeTreeAnalysis.analyzedFiles.filter((file) => selectedFiles.has(file));
|
|
727
790
|
analyzed = analyzedFiles.length;
|
|
@@ -778,11 +841,18 @@ export function resolveCommandContext(
|
|
|
778
841
|
// `discovered.failures` has, so a scoped `check <path>` cannot hide an
|
|
779
842
|
// orphan file elsewhere in the tree by naming a path that excludes it.
|
|
780
843
|
const unclaimedFiles = unclaimedAnalyzableFiles({ tracked, owned });
|
|
781
|
-
failures = [
|
|
844
|
+
failures = dedupeWholeFileFailures([
|
|
782
845
|
...failures,
|
|
783
846
|
...unclaimedFileFailures({ files: unclaimedFiles, providerLabel: "the Nx project graph" }),
|
|
784
847
|
...pythonUnmodelledFailures(workspace),
|
|
785
|
-
|
|
848
|
+
...goManifestFailures(workspace),
|
|
849
|
+
...rustManifestFailures(workspace),
|
|
850
|
+
...mavenManifestFailures(workspace),
|
|
851
|
+
...gradleManifestFailures(workspace),
|
|
852
|
+
...jvmIndexFailures(workspace),
|
|
853
|
+
...dotnetManifestFailures(workspace),
|
|
854
|
+
...dotnetIndexFailures(workspace),
|
|
855
|
+
]);
|
|
786
856
|
unclaimedGap = { files: unclaimedFiles };
|
|
787
857
|
// Same reason as the Moon branch above: `coverage.exempt` is a native-only
|
|
788
858
|
// concept, so an Nx workspace has nothing to report here.
|
|
@@ -24,9 +24,7 @@
|
|
|
24
24
|
* timestamp is not.
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
|
-
import {
|
|
28
|
-
|
|
29
|
-
import { environmentForTree } from "../process.mjs";
|
|
27
|
+
import { runProcess } from "../process.mjs";
|
|
30
28
|
|
|
31
29
|
/**
|
|
32
30
|
* Resolves repository provenance from the workspace root.
|
|
@@ -49,22 +47,12 @@ import { environmentForTree } from "../process.mjs";
|
|
|
49
47
|
* @throws {Error} when `root` is a git repository with no commits.
|
|
50
48
|
*/
|
|
51
49
|
export function resolveProvenance(root) {
|
|
52
|
-
// G-09: every git spawn routes through the shared environment guard, so an
|
|
53
|
-
// ambient GIT_DIR/GIT_WORK_TREE from a wrapping tool (the editor hooks, an
|
|
54
|
-
// outer `git` call) can never make these spawns read a repository other
|
|
55
|
-
// than the tree at `root`.
|
|
56
|
-
const env = environmentForTree();
|
|
57
50
|
// First, the "is this even a git repository at all" question, asked before
|
|
58
51
|
// `rev-parse HEAD` so an unborn HEAD (a commitless repo) is distinguishable
|
|
59
52
|
// from "not a repo" — the two must not share the `null` answer, because
|
|
60
53
|
// only the first is a legitimate "no origin claim".
|
|
61
54
|
try {
|
|
62
|
-
|
|
63
|
-
cwd: root,
|
|
64
|
-
env,
|
|
65
|
-
encoding: "utf-8",
|
|
66
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
67
|
-
});
|
|
55
|
+
runProcess("git", ["rev-parse", "--is-inside-work-tree"], root);
|
|
68
56
|
} catch {
|
|
69
57
|
// git not available, or not a git repository. Return null — the envelope
|
|
70
58
|
// carries no origin claim rather than a false one.
|
|
@@ -72,12 +60,7 @@ export function resolveProvenance(root) {
|
|
|
72
60
|
}
|
|
73
61
|
let commit;
|
|
74
62
|
try {
|
|
75
|
-
commit =
|
|
76
|
-
cwd: root,
|
|
77
|
-
env,
|
|
78
|
-
encoding: "utf-8",
|
|
79
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
80
|
-
}).trim();
|
|
63
|
+
commit = runProcess("git", ["rev-parse", "--verify", "HEAD"], root).trim();
|
|
81
64
|
} catch {
|
|
82
65
|
// `--is-inside-work-tree` passed above, so a repo EXISTS here, and
|
|
83
66
|
// `--verify` (the two-argument form) failed to resolve HEAD. Two states
|
|
@@ -95,12 +78,7 @@ export function resolveProvenance(root) {
|
|
|
95
78
|
// means the HEAD ref is broken.
|
|
96
79
|
let reachable;
|
|
97
80
|
try {
|
|
98
|
-
reachable =
|
|
99
|
-
cwd: root,
|
|
100
|
-
env,
|
|
101
|
-
encoding: "utf-8",
|
|
102
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
103
|
-
}).trim();
|
|
81
|
+
reachable = runProcess("git", ["rev-list", "--count", "--all"], root).trim();
|
|
104
82
|
} catch {
|
|
105
83
|
// `rev-list --all` failing too is a degenerate repo; treat it as unborn
|
|
106
84
|
// rather than inventing a third class.
|
|
@@ -124,21 +102,11 @@ export function resolveProvenance(root) {
|
|
|
124
102
|
// the tree.
|
|
125
103
|
let remote = null;
|
|
126
104
|
try {
|
|
127
|
-
const remotes =
|
|
128
|
-
cwd: root,
|
|
129
|
-
env,
|
|
130
|
-
encoding: "utf-8",
|
|
131
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
132
|
-
}).trim();
|
|
105
|
+
const remotes = runProcess("git", ["remote"], root).trim();
|
|
133
106
|
if (remotes) {
|
|
134
107
|
// Use the first remote's URL — typically "origin".
|
|
135
108
|
const firstRemote = remotes.split("\n")[0].trim();
|
|
136
|
-
remote =
|
|
137
|
-
cwd: root,
|
|
138
|
-
env,
|
|
139
|
-
encoding: "utf-8",
|
|
140
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
141
|
-
}).trim();
|
|
109
|
+
remote = runProcess("git", ["remote", "get-url", firstRemote], root).trim();
|
|
142
110
|
}
|
|
143
111
|
} catch {
|
|
144
112
|
// No remotes configured — `remote` stays null.
|
|
@@ -147,12 +115,7 @@ export function resolveProvenance(root) {
|
|
|
147
115
|
// Dirty: any uncommitted change to tracked files means the working tree
|
|
148
116
|
// does not match the commit. A baseline from a dirty tree is not a
|
|
149
117
|
// reproducible claim about that commit.
|
|
150
|
-
const status =
|
|
151
|
-
cwd: root,
|
|
152
|
-
env,
|
|
153
|
-
encoding: "utf-8",
|
|
154
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
155
|
-
}).trim();
|
|
118
|
+
const status = runProcess("git", ["status", "--porcelain"], root).trim();
|
|
156
119
|
const dirty = status.length > 0;
|
|
157
120
|
|
|
158
121
|
return { commit, remote, dirty };
|