@intentius/chant 0.124.44 → 0.124.46
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/dist/capabilities/aws.paths +148 -0
- package/dist/capabilities/base-mappings.json +122 -0
- package/dist/capabilities/catalog.d.ts +89 -0
- package/dist/capabilities/catalog.d.ts.map +1 -0
- package/dist/capabilities/catalog.js +204 -0
- package/dist/capabilities/language.d.ts +103 -0
- package/dist/capabilities/language.d.ts.map +1 -0
- package/dist/capabilities/language.js +335 -0
- package/dist/capabilities/matcher.d.ts +65 -0
- package/dist/capabilities/matcher.d.ts.map +1 -0
- package/dist/capabilities/matcher.js +299 -0
- package/dist/capabilities/view.d.ts +207 -0
- package/dist/capabilities/view.d.ts.map +1 -0
- package/dist/capabilities/view.js +484 -0
- package/dist/ci-identity.d.ts +183 -12
- package/dist/ci-identity.d.ts.map +1 -1
- package/dist/ci-identity.js +362 -53
- package/dist/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/commands/lint.js +1 -0
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.js +17 -1
- package/dist/cli/handlers/declared-graph.d.ts +44 -0
- package/dist/cli/handlers/declared-graph.d.ts.map +1 -0
- package/dist/cli/handlers/declared-graph.js +87 -0
- package/dist/cli/handlers/graph.d.ts +25 -0
- package/dist/cli/handlers/graph.d.ts.map +1 -1
- package/dist/cli/handlers/graph.js +93 -87
- package/dist/cli/handlers/pr-env.d.ts +8 -2
- package/dist/cli/handlers/pr-env.d.ts.map +1 -1
- package/dist/cli/handlers/pr-env.js +127 -225
- package/dist/cli/handlers/pr.d.ts +3 -0
- package/dist/cli/handlers/pr.d.ts.map +1 -1
- package/dist/cli/handlers/pr.js +1 -0
- package/dist/cli/handlers/search-count.d.ts +89 -0
- package/dist/cli/handlers/search-count.d.ts.map +1 -0
- package/dist/cli/handlers/search-count.js +132 -0
- package/dist/cli/handlers/search.d.ts +358 -6
- package/dist/cli/handlers/search.d.ts.map +1 -1
- package/dist/cli/handlers/search.js +1065 -155
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +23 -3
- package/dist/cli/mcp/server.d.ts.map +1 -1
- package/dist/cli/mcp/server.js +3 -0
- package/dist/cli/mcp/tools/capabilities.d.ts +7 -0
- package/dist/cli/mcp/tools/capabilities.d.ts.map +1 -0
- package/dist/cli/mcp/tools/capabilities.js +27 -0
- package/dist/cli/mcp/workspace-tools.d.ts +3 -1
- package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
- package/dist/cli/mcp/workspace-tools.js +47 -2
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +64 -17
- package/dist/cli/registry.d.ts +22 -0
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/components/cli-support.d.ts +2 -0
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/cli-support.js +4 -2
- package/dist/components/fan-out-support.d.ts +3 -0
- package/dist/components/fan-out-support.d.ts.map +1 -1
- package/dist/components/fan-out-support.js +1 -1
- package/dist/components/pr-env-ports.d.ts +29 -0
- package/dist/components/pr-env-ports.d.ts.map +1 -0
- package/dist/components/pr-env-ports.js +77 -0
- package/dist/components/pr-env-run.d.ts +254 -0
- package/dist/components/pr-env-run.d.ts.map +1 -0
- package/dist/components/pr-env-run.js +376 -0
- package/dist/components/pr-environment.d.ts +91 -15
- package/dist/components/pr-environment.d.ts.map +1 -1
- package/dist/components/pr-environment.js +95 -9
- package/dist/components/pr-pipeline.d.ts +10 -1
- package/dist/components/pr-pipeline.d.ts.map +1 -1
- package/dist/config-environments.d.ts +92 -0
- package/dist/config-environments.d.ts.map +1 -0
- package/dist/config-environments.js +102 -0
- package/dist/config.d.ts +24 -84
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +9 -96
- package/dist/graph-detail.d.ts.map +1 -1
- package/dist/graph-detail.js +15 -3
- package/dist/graph-effective.d.ts +33 -2
- package/dist/graph-effective.d.ts.map +1 -1
- package/dist/graph-effective.js +215 -68
- package/dist/graph-ir.d.ts +23 -0
- package/dist/graph-ir.d.ts.map +1 -1
- package/dist/graph-ir.js +130 -0
- package/dist/graph-sub-template.d.ts +11 -0
- package/dist/graph-sub-template.d.ts.map +1 -0
- package/dist/graph-sub-template.js +19 -0
- package/dist/lexicon.d.ts +9 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/observe.d.ts +28 -0
- package/dist/lifecycle/observe.d.ts.map +1 -1
- package/dist/lifecycle/observe.js +23 -1
- package/dist/lint/rule.d.ts +6 -0
- package/dist/lint/rule.d.ts.map +1 -1
- package/dist/live-endpoint.d.ts +1 -1
- package/dist/live-endpoint.d.ts.map +1 -1
- package/dist/live-endpoint.js +1 -1
- package/dist/observation.d.ts +48 -1
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +21 -5
- package/dist/op/activities/activity-contracts.d.ts +4 -4
- package/dist/op/op-waves.d.ts +17 -0
- package/dist/op/op-waves.d.ts.map +1 -1
- package/dist/op/op-waves.js +23 -1
- package/dist/pr-forge.d.ts +15 -1
- package/dist/pr-forge.d.ts.map +1 -1
- package/dist/pr-forge.js +28 -16
- package/dist/pr-requires.d.ts +5 -1
- package/dist/pr-requires.d.ts.map +1 -1
- package/dist/wave-runtime.mjs +251 -250
- package/dist/workspace/capabilities-contract.d.ts +66 -0
- package/dist/workspace/capabilities-contract.d.ts.map +1 -0
- package/dist/workspace/capabilities-contract.js +107 -0
- package/dist/workspace/capabilities.schema.json +229 -0
- package/dist/workspace/ci-cloud-identity.d.ts +3 -1
- package/dist/workspace/ci-cloud-identity.d.ts.map +1 -1
- package/dist/workspace/ci-cloud-identity.js +16 -1
- package/dist/workspace/ci-run.d.ts +35 -8
- package/dist/workspace/ci-run.d.ts.map +1 -1
- package/dist/workspace/ci-run.js +95 -21
- package/dist/workspace/ci-secrets.d.ts.map +1 -1
- package/dist/workspace/ci-secrets.js +4 -3
- package/dist/workspace/compose-graph.d.ts +9 -0
- package/dist/workspace/compose-graph.d.ts.map +1 -1
- package/dist/workspace/compose-graph.js +3 -1
- package/dist/workspace/conformance/index.d.ts +1 -1
- package/dist/workspace/conformance/index.d.ts.map +1 -1
- package/dist/workspace/conformance/index.js +39 -2
- package/dist/workspace/declaration.d.ts +10 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.js +17 -1
- package/dist/workspace/declaration.schema.json +36 -1
- package/dist/workspace/graph-cache.d.ts +8 -0
- package/dist/workspace/graph-cache.d.ts.map +1 -1
- package/dist/workspace/graph-cache.js +27 -3
- package/dist/workspace/graph-cli.d.ts +39 -2
- package/dist/workspace/graph-cli.d.ts.map +1 -1
- package/dist/workspace/graph-cli.js +66 -24
- package/dist/workspace/graph.schema.json +148 -0
- package/dist/workspace/kind-readers.d.ts +3 -0
- package/dist/workspace/kind-readers.d.ts.map +1 -1
- package/dist/workspace/kind-readers.js +7 -1
- package/dist/workspace/member-commands.d.ts +10 -1
- package/dist/workspace/member-commands.d.ts.map +1 -1
- package/dist/workspace/member-commands.js +17 -1
- package/dist/workspace/nested-graph.d.ts +2 -0
- package/dist/workspace/nested-graph.d.ts.map +1 -1
- package/dist/workspace/nested-graph.js +32 -1
- package/dist/workspace/reason-codes.d.ts +10 -0
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/reason-codes.js +12 -0
- package/dist/workspace/search-cli.d.ts +204 -0
- package/dist/workspace/search-cli.d.ts.map +1 -0
- package/dist/workspace/search-cli.js +466 -0
- package/dist/workspace/search-contract.d.ts +56 -0
- package/dist/workspace/search-contract.d.ts.map +1 -0
- package/dist/workspace/search-contract.js +70 -0
- package/dist/workspace/search.schema.json +822 -0
- package/package.json +1 -1
- package/src/__snapshots__/okf.test.ts.snap +10 -0
- package/src/capabilities/aws.paths +148 -0
- package/src/capabilities/base-mappings.json +122 -0
- package/src/capabilities/catalog.ts +230 -0
- package/src/capabilities/language.test.ts +191 -0
- package/src/capabilities/language.ts +373 -0
- package/src/capabilities/matcher.ts +315 -0
- package/src/capabilities/view-compose.test.ts +76 -0
- package/src/capabilities/view.ts +631 -0
- package/src/ci-identity-targets.test.ts +275 -0
- package/src/ci-identity.ts +394 -61
- package/src/cli/commands/lint.ts +1 -0
- package/src/cli/handlers/build.ts +15 -1
- package/src/cli/handlers/declared-graph.ts +99 -0
- package/src/cli/handlers/graph.test.ts +94 -3
- package/src/cli/handlers/graph.ts +123 -96
- package/src/cli/handlers/pr-env.ts +135 -227
- package/src/cli/handlers/pr.ts +4 -0
- package/src/cli/handlers/search-build-roots.test.ts +144 -0
- package/src/cli/handlers/search-count.test.ts +191 -0
- package/src/cli/handlers/search-count.ts +165 -0
- package/src/cli/handlers/search-json.test.ts +333 -0
- package/src/cli/handlers/search-live.test.ts +387 -0
- package/src/cli/handlers/search-paths.test.ts +235 -0
- package/src/cli/handlers/search.ts +1312 -166
- package/src/cli/main.test.ts +28 -3
- package/src/cli/main.ts +23 -3
- package/src/cli/mcp/server.test.ts +3 -3
- package/src/cli/mcp/server.ts +3 -0
- package/src/cli/mcp/tools/capabilities.ts +32 -0
- package/src/cli/mcp/workspace-tools.e2e.test.ts +21 -1
- package/src/cli/mcp/workspace-tools.ts +47 -2
- package/src/cli/parse-args.ts +52 -13
- package/src/cli/registry.ts +22 -0
- package/src/components/cli-support.ts +6 -1
- package/src/components/fan-out-support.ts +4 -1
- package/src/components/pr-env-ports.ts +89 -0
- package/src/components/pr-env-run-bundle.test.ts +54 -0
- package/src/components/pr-env-run.test.ts +290 -0
- package/src/components/pr-env-run.ts +563 -0
- package/src/components/pr-environment.ts +161 -13
- package/src/components/pr-pipeline.ts +10 -1
- package/src/config-environments.ts +133 -0
- package/src/config.ts +37 -124
- package/src/graph-detail.ts +15 -3
- package/src/graph-effective.ts +226 -75
- package/src/graph-ir.test.ts +163 -0
- package/src/graph-ir.ts +147 -0
- package/src/graph-sub-template.ts +22 -0
- package/src/lexicon.ts +10 -0
- package/src/lifecycle/observe.test.ts +27 -1
- package/src/lifecycle/observe.ts +43 -1
- package/src/lint/rule.ts +6 -0
- package/src/live-endpoint.ts +1 -1
- package/src/observation.test.ts +25 -0
- package/src/observation.ts +69 -5
- package/src/op/op-waves.ts +38 -1
- package/src/pr-forge-bundle.test.ts +1 -1
- package/src/pr-forge.ts +44 -17
- package/src/pr-requires.test.ts +43 -6
- package/src/pr-requires.ts +5 -1
- package/src/workspace/capabilities-contract.test.ts +88 -0
- package/src/workspace/capabilities-contract.ts +132 -0
- package/src/workspace/capabilities.schema.json +229 -0
- package/src/workspace/ci-cloud-identity.test.ts +20 -0
- package/src/workspace/ci-cloud-identity.ts +14 -1
- package/src/workspace/ci-run.test.ts +74 -1
- package/src/workspace/ci-run.ts +122 -29
- package/src/workspace/ci-secrets.test.ts +1 -1
- package/src/workspace/ci-secrets.ts +4 -3
- package/src/workspace/compose-graph.ts +10 -1
- package/src/workspace/conformance/conformance.e2e.test.ts +14 -1
- package/src/workspace/conformance/index.ts +31 -2
- package/src/workspace/declaration.schema.json +36 -1
- package/src/workspace/declaration.test.ts +7 -0
- package/src/workspace/declaration.ts +24 -2
- package/src/workspace/graph-cache.ts +27 -3
- package/src/workspace/graph-cli.ts +96 -24
- package/src/workspace/graph.schema.json +148 -0
- package/src/workspace/kind-readers.e2e.test.ts +28 -0
- package/src/workspace/kind-readers.test.ts +1 -1
- package/src/workspace/kind-readers.ts +7 -1
- package/src/workspace/member-commands.ts +22 -1
- package/src/workspace/nested-graph.ts +32 -1
- package/src/workspace/reason-codes.test.ts +7 -0
- package/src/workspace/reason-codes.ts +12 -0
- package/src/workspace/search-cli.e2e.test.ts +305 -0
- package/src/workspace/search-cli.ts +591 -0
- package/src/workspace/search-contract.ts +95 -0
- package/src/workspace/search.schema.json +822 -0
|
@@ -335,6 +335,12 @@ export interface CiPrEnvironment {
|
|
|
335
335
|
template: string;
|
|
336
336
|
/** How long the environment lives after its last apply: `72h` when absent. */
|
|
337
337
|
ttl: string;
|
|
338
|
+
/**
|
|
339
|
+
* A command that tears down what no lexicon does (#4330), such as a
|
|
340
|
+
* Terraform root's copy: run by `pr-env down` and `pr-env sweep` after the
|
|
341
|
+
* ownership check (../components/pr-env-run.ts, `commandTeardownAdapter`).
|
|
342
|
+
*/
|
|
343
|
+
teardown?: { name?: string; command: string[]; lexicons?: string[] };
|
|
338
344
|
}
|
|
339
345
|
|
|
340
346
|
/** The default `ci.prEnvironment.ttl`. */
|
|
@@ -1260,7 +1266,7 @@ function cloudIdentityOf(raw: CloudIdentityDeclaration, at: (pointer: string, ke
|
|
|
1260
1266
|
|
|
1261
1267
|
function ciOf(raw: unknown, at: (pointer: string, key?: boolean) => ErrorLocation): CiDeclaration | null {
|
|
1262
1268
|
if (raw === undefined) return null;
|
|
1263
|
-
const block = raw as { reportUrl?: string; workflow?: CiWorkflow; hooks?: unknown; identity?: { cloud?: CloudIdentityDeclaration }; branches?: Record<string, string[]>; labels?: Record<string, string[]>; locks?: CiLocks; policy?: unknown; prEnvironment?: { template: string; ttl?: string }; green?: { branch: string; window?: string; phases: Record<string, string[] | { runs: string[]; skipped?: CiSkipped; build?: CiBuild }>; require: string[]; preMerge?: string[]; signedSince?: string; source?: CiSource } };
|
|
1269
|
+
const block = raw as { reportUrl?: string; workflow?: CiWorkflow; hooks?: unknown; identity?: { cloud?: CloudIdentityDeclaration }; branches?: Record<string, string[]>; labels?: Record<string, string[]>; locks?: CiLocks; policy?: unknown; prEnvironment?: { template: string; ttl?: string; teardown?: { name?: string; command: string[]; lexicons?: string[] } }; green?: { branch: string; window?: string; phases: Record<string, string[] | { runs: string[]; skipped?: CiSkipped; build?: CiBuild }>; require: string[]; preMerge?: string[]; signedSince?: string; source?: CiSource } };
|
|
1264
1270
|
const hooks = ciHooksOf(block.hooks, "/ci/hooks", at);
|
|
1265
1271
|
const policy = ciPolicyOf(block.policy, at);
|
|
1266
1272
|
const workflow = {
|
|
@@ -1271,7 +1277,23 @@ function ciOf(raw: unknown, at: (pointer: string, key?: boolean) => ErrorLocatio
|
|
|
1271
1277
|
...(block.locks !== undefined ? { locks: block.locks } : {}),
|
|
1272
1278
|
...(policy ? { policy } : {}),
|
|
1273
1279
|
...(block.identity?.cloud !== undefined ? { identity: { cloud: cloudIdentityOf(block.identity.cloud, at) } } : {}),
|
|
1274
|
-
...(block.prEnvironment !== undefined
|
|
1280
|
+
...(block.prEnvironment !== undefined
|
|
1281
|
+
? {
|
|
1282
|
+
prEnvironment: {
|
|
1283
|
+
template: block.prEnvironment.template,
|
|
1284
|
+
ttl: block.prEnvironment.ttl ?? DEFAULT_PR_ENVIRONMENT_TTL,
|
|
1285
|
+
...(block.prEnvironment.teardown !== undefined
|
|
1286
|
+
? {
|
|
1287
|
+
teardown: {
|
|
1288
|
+
...(block.prEnvironment.teardown.name !== undefined ? { name: block.prEnvironment.teardown.name } : {}),
|
|
1289
|
+
command: [...block.prEnvironment.teardown.command],
|
|
1290
|
+
...(block.prEnvironment.teardown.lexicons !== undefined ? { lexicons: [...block.prEnvironment.teardown.lexicons] } : {}),
|
|
1291
|
+
},
|
|
1292
|
+
}
|
|
1293
|
+
: {}),
|
|
1294
|
+
},
|
|
1295
|
+
}
|
|
1296
|
+
: {}),
|
|
1275
1297
|
};
|
|
1276
1298
|
let reportUrl: string | null = null;
|
|
1277
1299
|
if (block.reportUrl !== undefined) {
|
|
@@ -368,9 +368,7 @@ export function splitCached(plan: MemberPlan, args: ParsedArgs, cache: GraphCach
|
|
|
368
368
|
// A member read through a generated reader project (#2874) is also keyed on
|
|
369
369
|
// the kind's graph block and the version of the package that reads it.
|
|
370
370
|
const reader = unit.reader ? readerStamp(unit.reader) : "";
|
|
371
|
-
const abs
|
|
372
|
-
const revision = at ? { commit: at, dir: unit.dir } : undefined;
|
|
373
|
-
const stamp = memberStamp(abs, unit.exclude, revision);
|
|
371
|
+
const { abs, revision, stamp } = unitStamp(plan, unit, at);
|
|
374
372
|
if (!stamp) continue;
|
|
375
373
|
stamps.set(unit.id, stamp.value);
|
|
376
374
|
const key = cacheKey({ member: unit.member, dir: unit.dir, stamp: stamp.value, toolchain: `${tool}\0${reader}`, argv, env });
|
|
@@ -385,6 +383,32 @@ export function splitCached(plan: MemberPlan, args: ParsedArgs, cache: GraphCach
|
|
|
385
383
|
return { hits, stamps, pending, started };
|
|
386
384
|
}
|
|
387
385
|
|
|
386
|
+
/** One unit's stamp, and what it was taken over: the member's directory on disk, and the commit for `--at`. */
|
|
387
|
+
function unitStamp(plan: MemberPlan, unit: RunUnit, at: string | null): { abs: string; revision?: { commit: string; dir: string }; stamp: Stamp | undefined } {
|
|
388
|
+
const abs = unit.dir === "." ? plan.workspace.root : join(plan.workspace.root, ...unit.dir.split("/"));
|
|
389
|
+
const revision = at ? { commit: at, dir: unit.dir } : undefined;
|
|
390
|
+
return { abs, ...(revision ? { revision } : {}), stamp: memberStamp(abs, unit.exclude, revision) };
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Every member unit's stamp, by unit id, by the rule {@link splitCached} keys
|
|
395
|
+
* the cache on (ws-059), with no cache read. A read that is never cached, such
|
|
396
|
+
* as `chant workspace search` (#4374), still stamps its members this way, so a
|
|
397
|
+
* reader compares stamps across documents with one meaning. A unit whose stamp
|
|
398
|
+
* can't be taken is left out.
|
|
399
|
+
*/
|
|
400
|
+
export function planStamps(plan: MemberPlan, at: string | null): Map<string, string> {
|
|
401
|
+
const stamps = new Map<string, string>();
|
|
402
|
+
for (const group of plan.groups) {
|
|
403
|
+
for (const unit of group.units) {
|
|
404
|
+
if (unit.group) continue;
|
|
405
|
+
const { stamp } = unitStamp(plan, unit, at);
|
|
406
|
+
if (stamp) stamps.set(unit.id, stamp.value);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return stamps;
|
|
410
|
+
}
|
|
411
|
+
|
|
388
412
|
/** The plan without the units in `ids`; a toolchain left with no units is dropped. */
|
|
389
413
|
export function withoutUnits(plan: MemberPlan, ids: ReadonlySet<string>): MemberPlan {
|
|
390
414
|
if (ids.size === 0) return plan;
|
|
@@ -31,11 +31,13 @@ import { dirname, join, resolve } from "node:path";
|
|
|
31
31
|
import { formatError } from "../cli/format";
|
|
32
32
|
import type { CommandContext, ParsedArgs } from "../cli/registry";
|
|
33
33
|
import { graphBox, plantability, type Plantable } from "./box-factory";
|
|
34
|
-
import { composeWorkspaceGraph, readMemberIr, type ComposedMember, type ComposeInput, type WorkspaceGraph } from "./compose-graph";
|
|
35
|
-
import {
|
|
36
|
-
import {
|
|
37
|
-
import {
|
|
38
|
-
import {
|
|
34
|
+
import { composedId, composeWorkspaceGraph, readMemberIr, type ComposedMember, type ComposeInput, type WorkspaceGraph } from "./compose-graph";
|
|
35
|
+
import { isViewDocument, prefixView } from "../capabilities/view";
|
|
36
|
+
import { refusedCapabilityView } from "./nested-graph";
|
|
37
|
+
import { openGraphCache, planStamps, splitCached, storeReads, withoutUnits } from "./graph-cache";
|
|
38
|
+
import { readDeclaration, readerVersion, WORKSPACE_ERROR_CODES, WorkspaceReadError, type Declaration, type ErrorLocation } from "./declaration";
|
|
39
|
+
import { describePlan, emitDocument, executePlan, memberStatus, planJson, planMembers, type MemberArgv, type MemberPlan, type Toolchain, type UnitResult } from "./member-commands";
|
|
40
|
+
import { loadKindRegistry, type KindRegistry } from "./kinds";
|
|
39
41
|
import { readKindLinks } from "./kind-link-readers";
|
|
40
42
|
import { prepareKindReaders, type PreparedReaders } from "./kind-readers";
|
|
41
43
|
import { recordLinkRows } from "./record-assets";
|
|
@@ -168,6 +170,7 @@ function compose(
|
|
|
168
170
|
only: string[] | undefined,
|
|
169
171
|
declarationMembers: { name: string; dir: string; kind: string }[],
|
|
170
172
|
live = false,
|
|
173
|
+
capabilityView = false,
|
|
171
174
|
): { inputs: ComposeInput[]; failed: boolean } {
|
|
172
175
|
// A member that ran was read live when --live was given, and says when (#2875).
|
|
173
176
|
const read = (member: ComposedMember, r: UnitResult): ComposedMember => {
|
|
@@ -203,6 +206,12 @@ function compose(
|
|
|
203
206
|
}
|
|
204
207
|
const member = read(memberStatus(m.name, m.dir, m.kind, "composed", null, r.chant), r);
|
|
205
208
|
member.irVersion = ir.irVersion;
|
|
209
|
+
// The member's own capability view (#4378), with composed ids; null when its chant printed none.
|
|
210
|
+
if (capabilityView) {
|
|
211
|
+
const view = (ir.ir as { capabilityView?: unknown }).capabilityView;
|
|
212
|
+
member.capabilityView = isViewDocument(view) ? prefixView(view, composedId(m.name, "")) : null;
|
|
213
|
+
}
|
|
214
|
+
delete (ir.ir as { capabilityView?: unknown }).capabilityView;
|
|
206
215
|
inputs.push({ member, ir: ir.ir });
|
|
207
216
|
}
|
|
208
217
|
return { inputs, failed };
|
|
@@ -212,51 +221,117 @@ function compose(
|
|
|
212
221
|
export function planGraph(query: GraphQuery): MemberPlan {
|
|
213
222
|
const located = locateWorkspace(query.cwd, query.at);
|
|
214
223
|
const declaration = readDeclaration(located.tree, "", { rootChant: true });
|
|
215
|
-
const kinds = loadKindRegistry(declaration.pins, located.rootOnDisk)
|
|
216
|
-
return planMembers("graph", located.rootOnDisk, { only: query.members, reader: query.reader, tree: located.tree, kinds });
|
|
224
|
+
const { registry: kinds, problems } = loadKindRegistry(declaration.pins, located.rootOnDisk);
|
|
225
|
+
return planMembers("graph", located.rootOnDisk, { only: query.members, reader: query.reader, tree: located.tree, kinds, kindProblems: problems.map((p) => p.message) });
|
|
217
226
|
}
|
|
218
227
|
|
|
219
|
-
/**
|
|
220
|
-
export
|
|
221
|
-
|
|
228
|
+
/** The members of a workspace read, each read under its own toolchain: what graph and search compose (#4374). */
|
|
229
|
+
export interface MemberReads {
|
|
230
|
+
located: LocatedWorkspace;
|
|
231
|
+
declaration: Declaration;
|
|
232
|
+
kinds: KindRegistry;
|
|
233
|
+
/** The plan the members ran from. */
|
|
234
|
+
plan: MemberPlan;
|
|
235
|
+
/** Each member's answer, the cache's included, in no particular order. */
|
|
236
|
+
results: UnitResult[];
|
|
237
|
+
/** The members the cache answered. */
|
|
238
|
+
served: ReadonlySet<string>;
|
|
239
|
+
/** Each member's stamp, by unit id (ws-059). */
|
|
240
|
+
stamps: ReadonlyMap<string, string>;
|
|
241
|
+
/** With {@link GraphQuery.components}: each member's component graph run. */
|
|
242
|
+
components?: UnitResult[];
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export interface MemberReadOptions {
|
|
246
|
+
/** The command line each member runs; `chant graph --format ir` and the verb's flags by default. */
|
|
247
|
+
argvFor?: MemberArgv;
|
|
248
|
+
/**
|
|
249
|
+
* Answer members from the per-member cache and store what they print
|
|
250
|
+
* (ws-059). Only a read whose output is a graph IR may be cached. When
|
|
251
|
+
* off, every member is still stamped by the same rule.
|
|
252
|
+
*/
|
|
253
|
+
cache: boolean;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Plan the members a workspace read covers, run each under its own toolchain
|
|
258
|
+
* (ws-021) at the revision asked for, and return what they printed. One
|
|
259
|
+
* fan-out, shared by `chant workspace graph` and `chant workspace search`
|
|
260
|
+
* (#4374): `--at` exports the tree once, a member of a package's kind runs in
|
|
261
|
+
* its reader project, and every member is stamped by the rule of ws-059. The
|
|
262
|
+
* exported tree and the reader projects are removed before this returns.
|
|
263
|
+
* Throws a {@link WorkspaceReadError} when the declaration can't be read.
|
|
264
|
+
*/
|
|
265
|
+
export async function readMembers(query: GraphQuery, options: MemberReadOptions): Promise<MemberReads> {
|
|
222
266
|
let exported: string | undefined;
|
|
223
267
|
let readers: PreparedReaders | undefined;
|
|
224
|
-
if (query.args?.live && query.at !== undefined) {
|
|
225
|
-
return { doc: { ...head, error: { code: "live-at-revision", message: LIVE_AT_REVISION, location: null } }, failed: true };
|
|
226
|
-
}
|
|
227
268
|
try {
|
|
228
269
|
const located = locateWorkspace(query.cwd, query.at);
|
|
229
270
|
const declaration = readDeclaration(located.tree, "", { rootChant: true });
|
|
230
271
|
// Kinds are read before planning: a package kind with a graph block runs (#2874).
|
|
231
|
-
const kinds = loadKindRegistry(declaration.pins, located.rootOnDisk)
|
|
232
|
-
|
|
272
|
+
const { registry: kinds, problems } = loadKindRegistry(declaration.pins, located.rootOnDisk);
|
|
273
|
+
const kindProblems = problems.map((p) => p.message);
|
|
274
|
+
let plan = planMembers("graph", located.rootOnDisk, { only: query.members, reader: query.reader, tree: located.tree, kinds, kindProblems });
|
|
233
275
|
const args = (query.args ?? {}) as ParsedArgs;
|
|
234
276
|
// Members whose last source read still stands are answered from the cache (#2876).
|
|
235
277
|
// A composites read runs every member's component graph as well, so it reads everything.
|
|
236
|
-
const cache = query.noCache || query.components ? undefined : openGraphCache(located.rootOnDisk);
|
|
278
|
+
const cache = !options.cache || query.noCache || query.components ? undefined : openGraphCache(located.rootOnDisk);
|
|
237
279
|
const split = cache ? splitCached(plan, args, cache, located.at) : undefined;
|
|
280
|
+
const stamps = split?.stamps ?? (options.cache ? new Map<string, string>() : planStamps(plan, located.at));
|
|
238
281
|
const served = new Set(split?.hits.map((h) => h.id));
|
|
239
282
|
if (located.at !== null && withoutUnits(plan, served).groups.length > 0) {
|
|
240
283
|
exported = exportRevision(located, withoutUnits(plan, served).groups.flatMap((g) => g.units.map((u) => u.dir)));
|
|
241
|
-
plan = planMembers("graph", exported, { only: query.members, reader: query.reader, tree: workingTree(exported), kinds });
|
|
284
|
+
plan = planMembers("graph", exported, { only: query.members, reader: query.reader, tree: workingTree(exported), kinds, kindProblems });
|
|
242
285
|
}
|
|
243
286
|
// Only the members that run get a reader project; a served member needs none.
|
|
244
287
|
const toRun = withoutUnits(plan, served);
|
|
245
288
|
readers = prepareKindReaders(toRun, exported ?? located.rootOnDisk);
|
|
246
|
-
const [
|
|
247
|
-
executePlan(toRun, args),
|
|
289
|
+
const [first, components] = await Promise.all([
|
|
290
|
+
executePlan(toRun, args, options.argvFor),
|
|
248
291
|
query.components ? executePlan(plan, args, () => componentGraphArgv(args)) : Promise.resolve(undefined),
|
|
249
292
|
]);
|
|
293
|
+
const ran = args.capabilityView && !options.argvFor ? await withoutViewWhereRefused(toRun, args, first) : first;
|
|
250
294
|
if (cache && split) storeReads(split, ran, cache);
|
|
251
295
|
const results = [...(split?.hits ?? []), ...ran];
|
|
252
296
|
if (query.inTree) await query.inTree(exported ?? located.rootOnDisk, declaration.members.filter((m) => !query.members?.length || query.members.includes(m.name)));
|
|
253
297
|
for (const r of components ?? []) if (r.stderr.trim() && query.onStderr) query.onStderr(r.stderr.endsWith("\n") ? r.stderr : `${r.stderr}\n`);
|
|
254
298
|
for (const r of results) if (r.stderr.trim() && query.onStderr) query.onStderr(r.stderr.endsWith("\n") ? r.stderr : `${r.stderr}\n`);
|
|
255
|
-
|
|
299
|
+
return { located, declaration, kinds, plan, results, served, stamps, ...(components ? { components } : {}) };
|
|
300
|
+
} finally {
|
|
301
|
+
readers?.cleanup();
|
|
302
|
+
if (exported) rmSync(dirname(exported), { recursive: true, force: true });
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* A member's chant from before #4378 refuses `--capability-view` as a flag
|
|
308
|
+
* it doesn't know. Read those members again without it, so the graph still
|
|
309
|
+
* composes them; they carry `capabilityView: null`, which a capability search
|
|
310
|
+
* reports as `member-chant-too-old`.
|
|
311
|
+
*/
|
|
312
|
+
async function withoutViewWhereRefused(plan: MemberPlan, args: ParsedArgs, results: UnitResult[]): Promise<UnitResult[]> {
|
|
313
|
+
const refused = new Set(results.filter((r) => r.exitCode !== 0 && refusedCapabilityView(r.stderr)).map((r) => r.id));
|
|
314
|
+
if (refused.size === 0) return results;
|
|
315
|
+
const others = new Set(results.filter((r) => !refused.has(r.id)).map((r) => r.id));
|
|
316
|
+
const again = await executePlan(withoutUnits(plan, others), { ...args, capabilityView: false });
|
|
317
|
+
const byId = new Map(again.map((r) => [r.id, r]));
|
|
318
|
+
return results.map((r) => byId.get(r.id) ?? r);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/** Read and compose the graph, and build the document. Never throws a {@link WorkspaceReadError}. */
|
|
322
|
+
export async function workspaceGraph(query: GraphQuery): Promise<GraphResult> {
|
|
323
|
+
const head: Head = { $schema: GRAPH_OUTPUT_SCHEMA_ID, contract: GRAPH_CONTRACT_VERSION, chant: readerVersion() };
|
|
324
|
+
if (query.args?.live && query.at !== undefined) {
|
|
325
|
+
return { doc: { ...head, error: { code: "live-at-revision", message: LIVE_AT_REVISION, location: null } }, failed: true };
|
|
326
|
+
}
|
|
327
|
+
try {
|
|
328
|
+
const { located, declaration, kinds, plan, results, served, stamps, components } = await readMembers(query, { cache: true });
|
|
329
|
+
const args = (query.args ?? {}) as ParsedArgs;
|
|
330
|
+
const { inputs, failed } = compose(plan, results, query.members, declaration.members, !!args.live, !!args.capabilityView);
|
|
256
331
|
for (const { member } of inputs) {
|
|
257
332
|
if (member.status !== "composed") continue;
|
|
258
333
|
member.cached = served.has(member.name);
|
|
259
|
-
member.stamp =
|
|
334
|
+
member.stamp = stamps.get(member.name) ?? null;
|
|
260
335
|
}
|
|
261
336
|
// Links (#2539) resolve against the declaration that was read, the revision's for --at, and the kinds installed now.
|
|
262
337
|
// A kind's link reader (#3932) reads the member's files in the tree read, as check does.
|
|
@@ -285,9 +360,6 @@ export async function workspaceGraph(query: GraphQuery): Promise<GraphResult> {
|
|
|
285
360
|
} catch (err) {
|
|
286
361
|
if (!(err instanceof WorkspaceReadError)) throw err;
|
|
287
362
|
return { doc: { ...head, error: { code: err.code, message: err.message, location: err.location ?? null } }, failed: true };
|
|
288
|
-
} finally {
|
|
289
|
-
readers?.cleanup();
|
|
290
|
-
if (exported) rmSync(dirname(exported), { recursive: true, force: true });
|
|
291
363
|
}
|
|
292
364
|
}
|
|
293
365
|
|
|
@@ -437,6 +437,17 @@
|
|
|
437
437
|
}
|
|
438
438
|
}
|
|
439
439
|
]
|
|
440
|
+
},
|
|
441
|
+
"capabilityView": {
|
|
442
|
+
"description": "Added in contract 1 by #4378. Only with `--capability-view`, which `chant workspace search` asks for when a query has a capability term. The lexicon-neutral view capability rules read, built by the member's own chant through the mappings and adapters of the lexicons the member loads (its `chant graph --format ir --capability-view`), with `<member>/<id>` ids. null on a composed member whose chant printed no view (one from before #4378); a capability search reports that member as `member-chant-too-old`. Absent without the flag, and on a member that was not composed.",
|
|
443
|
+
"oneOf": [
|
|
444
|
+
{
|
|
445
|
+
"type": "null"
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
"$ref": "#/$defs/capabilityView"
|
|
449
|
+
}
|
|
450
|
+
]
|
|
440
451
|
}
|
|
441
452
|
},
|
|
442
453
|
"if": {
|
|
@@ -1438,6 +1449,143 @@
|
|
|
1438
1449
|
]
|
|
1439
1450
|
}
|
|
1440
1451
|
}
|
|
1452
|
+
},
|
|
1453
|
+
"capabilityView": {
|
|
1454
|
+
"type": "object",
|
|
1455
|
+
"description": "One member's capability view (#4378), as `capabilities/view.ts` serializes it. Rules read labels, attributes and edges; a node's attributes are the neutral ones its lexicon's mapping names.",
|
|
1456
|
+
"required": [
|
|
1457
|
+
"version",
|
|
1458
|
+
"lexicons",
|
|
1459
|
+
"nodes",
|
|
1460
|
+
"edges",
|
|
1461
|
+
"unresolved"
|
|
1462
|
+
],
|
|
1463
|
+
"properties": {
|
|
1464
|
+
"version": {
|
|
1465
|
+
"const": 1,
|
|
1466
|
+
"description": "The view's own format version. A reader treats a view of a version it doesn't know as no view."
|
|
1467
|
+
},
|
|
1468
|
+
"lexicons": {
|
|
1469
|
+
"type": "array",
|
|
1470
|
+
"items": {
|
|
1471
|
+
"type": "string"
|
|
1472
|
+
},
|
|
1473
|
+
"description": "The lexicons whose nodes a mapping labelled."
|
|
1474
|
+
},
|
|
1475
|
+
"nodes": {
|
|
1476
|
+
"type": "array",
|
|
1477
|
+
"items": {
|
|
1478
|
+
"type": "object",
|
|
1479
|
+
"required": [
|
|
1480
|
+
"id",
|
|
1481
|
+
"label",
|
|
1482
|
+
"irId",
|
|
1483
|
+
"lexicon",
|
|
1484
|
+
"attrs"
|
|
1485
|
+
],
|
|
1486
|
+
"properties": {
|
|
1487
|
+
"id": {
|
|
1488
|
+
"type": "string",
|
|
1489
|
+
"description": "A node id of the composed graph, or, for a policy statement or an inline block, that id with `#<attribute path>`."
|
|
1490
|
+
},
|
|
1491
|
+
"label": {
|
|
1492
|
+
"type": "string",
|
|
1493
|
+
"description": "The neutral label (`Role`, `Bucket`, `Statement`, `IngressRule`)."
|
|
1494
|
+
},
|
|
1495
|
+
"irId": {
|
|
1496
|
+
"type": "string",
|
|
1497
|
+
"description": "The composed graph node this view node is, or sits inside."
|
|
1498
|
+
},
|
|
1499
|
+
"lexicon": {
|
|
1500
|
+
"type": "string"
|
|
1501
|
+
},
|
|
1502
|
+
"attrs": {
|
|
1503
|
+
"type": "object"
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
},
|
|
1508
|
+
"edges": {
|
|
1509
|
+
"type": "array",
|
|
1510
|
+
"items": {
|
|
1511
|
+
"type": "object",
|
|
1512
|
+
"required": [
|
|
1513
|
+
"from",
|
|
1514
|
+
"to",
|
|
1515
|
+
"name",
|
|
1516
|
+
"hops"
|
|
1517
|
+
],
|
|
1518
|
+
"properties": {
|
|
1519
|
+
"from": {
|
|
1520
|
+
"type": "string"
|
|
1521
|
+
},
|
|
1522
|
+
"to": {
|
|
1523
|
+
"type": "string"
|
|
1524
|
+
},
|
|
1525
|
+
"name": {
|
|
1526
|
+
"type": "string",
|
|
1527
|
+
"description": "The view edge's name (`subnet`, `statement`, `resource`, `attachedTo`)."
|
|
1528
|
+
},
|
|
1529
|
+
"hops": {
|
|
1530
|
+
"type": "array",
|
|
1531
|
+
"description": "The graph references the edge stands for, in walk order from `from`, each in the reference's own direction.",
|
|
1532
|
+
"items": {
|
|
1533
|
+
"type": "object",
|
|
1534
|
+
"required": [
|
|
1535
|
+
"from",
|
|
1536
|
+
"to",
|
|
1537
|
+
"edge"
|
|
1538
|
+
],
|
|
1539
|
+
"properties": {
|
|
1540
|
+
"from": {
|
|
1541
|
+
"type": "string"
|
|
1542
|
+
},
|
|
1543
|
+
"fromAttr": {
|
|
1544
|
+
"type": "string"
|
|
1545
|
+
},
|
|
1546
|
+
"to": {
|
|
1547
|
+
"type": "string"
|
|
1548
|
+
},
|
|
1549
|
+
"toAttr": {
|
|
1550
|
+
"type": "string"
|
|
1551
|
+
},
|
|
1552
|
+
"edge": {
|
|
1553
|
+
"type": "string"
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
},
|
|
1561
|
+
"unresolved": {
|
|
1562
|
+
"type": "array",
|
|
1563
|
+
"description": "ARN strings naming a role, bucket or table that the member declares nothing of. `chant workspace search` resolves them by name over every member's view, so a policy in one member reaches a bucket in another.",
|
|
1564
|
+
"items": {
|
|
1565
|
+
"type": "object",
|
|
1566
|
+
"required": [
|
|
1567
|
+
"from",
|
|
1568
|
+
"fromAttr",
|
|
1569
|
+
"edge",
|
|
1570
|
+
"value"
|
|
1571
|
+
],
|
|
1572
|
+
"properties": {
|
|
1573
|
+
"from": {
|
|
1574
|
+
"type": "string"
|
|
1575
|
+
},
|
|
1576
|
+
"fromAttr": {
|
|
1577
|
+
"type": "string"
|
|
1578
|
+
},
|
|
1579
|
+
"edge": {
|
|
1580
|
+
"type": "string"
|
|
1581
|
+
},
|
|
1582
|
+
"value": {
|
|
1583
|
+
"type": "string"
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1441
1589
|
}
|
|
1442
1590
|
}
|
|
1443
1591
|
}
|
|
@@ -65,4 +65,32 @@ describe("terraform and choudoufu members through the terraform lexicon (#2874)"
|
|
|
65
65
|
},
|
|
66
66
|
TIMEOUT,
|
|
67
67
|
);
|
|
68
|
+
|
|
69
|
+
// behold#919: the reader project sets `graph.lint: "report"`, so a member
|
|
70
|
+
// whose HCL fails one of the lexicon's error rules is composed, with the
|
|
71
|
+
// finding on its meta.lint. Before, it failed with command-failed.
|
|
72
|
+
test(
|
|
73
|
+
"a member with a lint error is composed, the finding on its meta.lint",
|
|
74
|
+
async () => {
|
|
75
|
+
const root = repo({
|
|
76
|
+
"chant.workspace.json": declaration([{ name: "db", dir: "estates/db", kind: "terraform" }], { pins: [{ package: LEXICON, version: VERSION }] }),
|
|
77
|
+
"estates/db/main.tf":
|
|
78
|
+
'resource "aws_db_instance" "orders" {\n identifier = "orders"\n engine = "postgres"\n instance_class = "db.t3.micro"\n allocated_storage = 20\n storage_encrypted = false\n}\n',
|
|
79
|
+
".gitignore": "node_modules\n",
|
|
80
|
+
});
|
|
81
|
+
symlinkSync(join(REPO, "node_modules"), join(root, "node_modules"), "dir");
|
|
82
|
+
|
|
83
|
+
const { doc, failed } = await workspaceGraph({ cwd: root });
|
|
84
|
+
const g = result(doc);
|
|
85
|
+
expectValid(g);
|
|
86
|
+
expect(g.members.map((m) => [m.name, m.status, m.reason?.code ?? null])).toEqual([["db", "composed", null]]);
|
|
87
|
+
expect(failed).toBe(false);
|
|
88
|
+
expect(g.nodes.map((n) => n.id)).toContain("db/db/aws_db_instance.orders");
|
|
89
|
+
const lint = (g.members[0] as { meta?: { lint?: { errors: number; findings: { ruleId: string; severity: string; entity?: string }[] } } }).meta?.lint;
|
|
90
|
+
expect(lint?.errors).toBeGreaterThanOrEqual(1);
|
|
91
|
+
// The finding names its entity, the id the member IR gives the node (before composition prefixes it).
|
|
92
|
+
expect(lint?.findings).toContainEqual(expect.objectContaining({ ruleId: "TF033", severity: "error", entity: "db/aws_db_instance.orders" }));
|
|
93
|
+
},
|
|
94
|
+
TIMEOUT,
|
|
95
|
+
);
|
|
68
96
|
});
|
|
@@ -80,7 +80,7 @@ describe("workspace graph reads a package kind with a graph block (#2874)", () =
|
|
|
80
80
|
]);
|
|
81
81
|
expect(g.nodes.map((n) => n.id)).toEqual(["net/root/Thing"]);
|
|
82
82
|
const attrs = g.nodes[0].attrs as { found: boolean; pwd: string; argv: string; config: Record<string, unknown> };
|
|
83
|
-
expect(attrs.config).toEqual({ lexicons: ["fake"], fake: { moduleRoot: root, roots: { net: { dir: `${root}/estates/net` } } } });
|
|
83
|
+
expect(attrs.config).toEqual({ lexicons: ["fake"], fake: { moduleRoot: root, roots: { net: { dir: `${root}/estates/net` } } }, graph: { lint: "report" } });
|
|
84
84
|
expect(attrs.found).toBe(true);
|
|
85
85
|
// A reader project declares no environments, so --env stays with the chant members.
|
|
86
86
|
expect(attrs.argv).toBe("graph . --format ir");
|
|
@@ -13,6 +13,9 @@
|
|
|
13
13
|
* (`substituteGraphConfig`). The terraform lexicon's config names one root
|
|
14
14
|
* after the member, at the member's directory, with `moduleRoot` at the
|
|
15
15
|
* workspace root, so a member that calls `../modules/x` keeps its modules.
|
|
16
|
+
* - It also sets `graph.lint: "report"` (behold#919), so a member whose
|
|
17
|
+
* source fails the lexicon's lint is still composed, with the findings on
|
|
18
|
+
* its `meta.lint`, instead of failing with `command-failed`.
|
|
16
19
|
* - `node_modules` links to the `node_modules` directory holding the lexicon
|
|
17
20
|
* package, found from the workspace root as the kind's pin was. The lexicon
|
|
18
21
|
* chant loads is then the one the declaration pins.
|
|
@@ -95,7 +98,10 @@ export function prepareKindReaders(plan: MemberPlan, root: string): PreparedRead
|
|
|
95
98
|
const project = join(scratch, unit.id);
|
|
96
99
|
mkdirSync(project, { recursive: true });
|
|
97
100
|
const config = substituteGraphConfig(unit.reader.config, { member: unit.member, dir: unit.abs, workspace: root });
|
|
98
|
-
|
|
101
|
+
// `graph.lint: "report"` (behold#919): chant did not author a reader
|
|
102
|
+
// member's source, so a lint error there is a finding about it, carried
|
|
103
|
+
// on the member's `meta.lint`, not a reason to leave it out of the graph.
|
|
104
|
+
writeFileSync(join(project, "chant.config.json"), `${JSON.stringify({ lexicons: [unit.reader.lexicon], [unit.reader.lexicon]: config, graph: { lint: "report" } }, null, 2)}\n`);
|
|
99
105
|
symlinkSync(found.modules, join(project, "node_modules"), "dir");
|
|
100
106
|
unit.abs = project;
|
|
101
107
|
return true;
|
|
@@ -12,7 +12,10 @@
|
|
|
12
12
|
* - For `graph` only, members of a pinned package's kind whose kinds file
|
|
13
13
|
* says how to read them (#2874, a `graph` block): each runs `chant graph`
|
|
14
14
|
* in a reader project chant writes for it (`kind-readers.ts`). Other
|
|
15
|
-
* package kinds are `kind-not-run`, as before.
|
|
15
|
+
* package kinds are `kind-not-run`, as before. A kind no built-in or
|
|
16
|
+
* pinned package supplies, such as `terraform` when the pinned lexicon is
|
|
17
|
+
* missing or at another version, is `unknown-kind` and fails the read,
|
|
18
|
+
* with the pin's problem in the message (#4380).
|
|
16
19
|
* - For `build` and `lint` only, every project an example group matches
|
|
17
20
|
* (ws-051): groups are built and linted, and have no ledger, audit or place
|
|
18
21
|
* in the graph. On a large repository this is most of the run, so
|
|
@@ -155,6 +158,12 @@ export interface PlanOptions {
|
|
|
155
158
|
reader?: Toolchain;
|
|
156
159
|
/** The files to plan from, such as a revision's (`--at`); the working tree under `root` by default. */
|
|
157
160
|
tree?: WorkspaceTree;
|
|
161
|
+
/**
|
|
162
|
+
* Why a pinned package's kinds couldn't be loaded (`loadKindRegistry`'s
|
|
163
|
+
* problems), such as a pin at one version with another installed. A graph
|
|
164
|
+
* read names them on a member whose kind is unknown (#4380).
|
|
165
|
+
*/
|
|
166
|
+
kindProblems?: readonly string[];
|
|
158
167
|
}
|
|
159
168
|
|
|
160
169
|
/** Work out which projects run and under which chant. Throws a {@link WorkspaceReadError} for an unreadable declaration or an unknown `--member` name. */
|
|
@@ -218,6 +227,16 @@ export function planMembers(verb: WorkspaceVerb, root: string, options: PlanOpti
|
|
|
218
227
|
});
|
|
219
228
|
continue;
|
|
220
229
|
}
|
|
230
|
+
// A kind no built-in or pinned package supplies is unknown, not a kind
|
|
231
|
+
// the read leaves out (#4380): a terraform member whose pinned lexicon is
|
|
232
|
+
// missing or at another version said kind-not-run before, as if the graph
|
|
233
|
+
// never read its kind. Say why, the pin's problem included.
|
|
234
|
+
if (verb === "graph" && !kinds.get(entry.kind)) {
|
|
235
|
+
const reason = memberReason(entry, tree, kinds) ?? { code: "unknown-kind", message: `no built-in kind or pinned package supplies kind ${entry.kind}` };
|
|
236
|
+
const problems = reason.code === "unknown-kind" ? (options.kindProblems ?? []) : [];
|
|
237
|
+
unreadable.push({ name: entry.name, dir: entry.dir, kind: entry.kind, reason: problems.length ? { ...reason, message: `${reason.message}; ${problems.join("; ")}` } : reason });
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
221
240
|
if (entry.kind !== "chant") {
|
|
222
241
|
const why =
|
|
223
242
|
entry.kind === "workspace"
|
|
@@ -308,6 +327,8 @@ export function memberArgv(verb: WorkspaceVerb, unit: RunUnit, args: ParsedArgs)
|
|
|
308
327
|
// The detail tier too, so `workspace graph --detail 1` composes collapsed
|
|
309
328
|
// composites with their rolled-up status (#4228).
|
|
310
329
|
if (args.detail !== undefined) argv.push("--detail", String(args.detail));
|
|
330
|
+
// Each member's chant maps its own capability view (#4378).
|
|
331
|
+
if (args.capabilityView) argv.push("--capability-view");
|
|
311
332
|
}
|
|
312
333
|
return argv;
|
|
313
334
|
}
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
import { spawn } from "node:child_process";
|
|
24
24
|
import type { ParsedArgs } from "../cli/registry";
|
|
25
|
+
import { isViewDocument, prefixView, VIEW_DOCUMENT_VERSION } from "../capabilities/view";
|
|
25
26
|
import { danglingKey, SCOPED_GROUPS, type ComposedMember, type ComposedNode, type WorkspaceGraph } from "./compose-graph";
|
|
26
27
|
|
|
27
28
|
export type { NestedWorkspace } from "./compose-graph";
|
|
@@ -45,6 +46,7 @@ export function nestedArgv(dir: string, args: Partial<ParsedArgs>, at: string |
|
|
|
45
46
|
...(args.traffic !== undefined ? ["--traffic", String(args.traffic)] : []),
|
|
46
47
|
...(args.detail !== undefined ? ["--detail", String(args.detail)] : []),
|
|
47
48
|
...(args.noCache ? ["--no-cache"] : []),
|
|
49
|
+
...(args.capabilityView ? ["--capability-view"] : []),
|
|
48
50
|
];
|
|
49
51
|
}
|
|
50
52
|
|
|
@@ -97,10 +99,21 @@ export async function readNested(
|
|
|
97
99
|
contract: number,
|
|
98
100
|
toolchain: Toolchain | undefined = undefined,
|
|
99
101
|
): Promise<NestedRead & { stderr: string }> {
|
|
100
|
-
const
|
|
102
|
+
const command = (toolchain ?? readerToolchain()).command;
|
|
103
|
+
let r = await spawnRead(command, nestedArgv(abs, args, at), abs);
|
|
104
|
+
// A nested chant from before #4378 has no --capability-view: read it
|
|
105
|
+
// without, and its members carry no view.
|
|
106
|
+
if (args.capabilityView && r.exitCode !== 0 && refusedCapabilityView(r.stderr)) {
|
|
107
|
+
r = await spawnRead(command, nestedArgv(abs, { ...args, capabilityView: false }, at), abs);
|
|
108
|
+
}
|
|
101
109
|
return { ...readNestedDocument(r.stdout, r.exitCode, r.stderr, schemaId, contract), stderr: r.stderr };
|
|
102
110
|
}
|
|
103
111
|
|
|
112
|
+
/** Whether a chant refused `--capability-view` as a flag it doesn't know (#4378): one from before it. */
|
|
113
|
+
export function refusedCapabilityView(stderr: string): boolean {
|
|
114
|
+
return /Unknown flag:?\s*--capability-view/i.test(stderr);
|
|
115
|
+
}
|
|
116
|
+
|
|
104
117
|
const prefixed = (outer: string, id: string): string => `${outer}/${id}`;
|
|
105
118
|
|
|
106
119
|
function prefixRefs(value: unknown, outer: string): unknown {
|
|
@@ -141,6 +154,24 @@ export function foldNested(graph: WorkspaceGraph, member: ComposedMember, inner:
|
|
|
141
154
|
for (const e of inner.exports ?? []) graph.exports.push({ ...e, ...(e.node ? { node: prefixed(outer, e.node) } : {}), member: outer });
|
|
142
155
|
for (const i of inner.imports ?? []) graph.imports.push({ ...i, node: prefixed(outer, i.node), member: outer });
|
|
143
156
|
graph.groups.byMember[outer] = ids.sort();
|
|
157
|
+
// The capability view (#4378): each nested member's, under the outer id. A
|
|
158
|
+
// nested member that read without one leaves the outer member without one.
|
|
159
|
+
const composedInner = inner.members.filter((m) => m.status === "composed");
|
|
160
|
+
if (composedInner.some((m) => m.capabilityView !== undefined)) {
|
|
161
|
+
const views = composedInner.map((m) => m.capabilityView);
|
|
162
|
+
if (views.every(isViewDocument)) {
|
|
163
|
+
const ps = views.map((v) => prefixView(v, `${outer}/`));
|
|
164
|
+
member.capabilityView = {
|
|
165
|
+
version: VIEW_DOCUMENT_VERSION,
|
|
166
|
+
lexicons: [...new Set(ps.flatMap((v) => v.lexicons))].sort(),
|
|
167
|
+
nodes: ps.flatMap((v) => v.nodes),
|
|
168
|
+
edges: ps.flatMap((v) => v.edges),
|
|
169
|
+
unresolved: ps.flatMap((v) => v.unresolved),
|
|
170
|
+
};
|
|
171
|
+
} else {
|
|
172
|
+
member.capabilityView = null;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
144
175
|
const groups = graph.groups as unknown as Record<string, Record<string, string[]> | undefined>;
|
|
145
176
|
const innerGroups = inner.groups as unknown as Record<string, Record<string, string[]> | undefined>;
|
|
146
177
|
for (const key of ["byLexicon", "byComposite"] as const) {
|
|
@@ -55,6 +55,8 @@ import { ROTATION_REFUSAL_CODES } from "./trust/rotation";
|
|
|
55
55
|
import { SIGNERS_ERROR_CODES } from "./trust/signers-cli";
|
|
56
56
|
import { EVIDENCE_ERROR_CODES } from "./trust/evidence";
|
|
57
57
|
import { METRICS_ERROR_CODES, METRICS_REASON_CODES } from "./delivery-metrics";
|
|
58
|
+
import { SEARCH_ERROR_CODES, SEARCH_MEMBER_REASON_CODES, WORKSPACE_SEARCH_ERROR_CODES } from "./search-contract";
|
|
59
|
+
import { CAPABILITIES_ERROR_CODES, CAPABILITY_SUPPORT_CODES } from "./capabilities-contract";
|
|
58
60
|
|
|
59
61
|
const HERE = import.meta.dirname;
|
|
60
62
|
|
|
@@ -132,6 +134,11 @@ const PER_COMMAND: Record<string, readonly string[]> = {
|
|
|
132
134
|
EVIDENCE_ERROR_CODES,
|
|
133
135
|
METRICS_ERROR_CODES,
|
|
134
136
|
METRICS_REASON_CODES,
|
|
137
|
+
SEARCH_ERROR_CODES,
|
|
138
|
+
WORKSPACE_SEARCH_ERROR_CODES,
|
|
139
|
+
SEARCH_MEMBER_REASON_CODES,
|
|
140
|
+
CAPABILITIES_ERROR_CODES,
|
|
141
|
+
CAPABILITY_SUPPORT_CODES,
|
|
135
142
|
};
|
|
136
143
|
|
|
137
144
|
/** Every string in an `enum` under a property named `code`, anywhere in a schema. */
|