@intentius/chant 0.101.0 → 0.104.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/dist/agents/checks.d.ts.map +1 -1
- package/dist/attrref.d.ts.map +1 -1
- package/dist/audit/core.d.ts +2 -0
- package/dist/audit/core.d.ts.map +1 -1
- package/dist/audit/discover.d.ts.map +1 -1
- package/dist/audit/nginx.d.ts.map +1 -1
- package/dist/audit/prior-art.d.ts +48 -0
- package/dist/audit/prior-art.d.ts.map +1 -1
- package/dist/behaviour-kinds.d.ts.map +1 -1
- package/dist/builder.d.ts.map +1 -1
- package/dist/change-set.d.ts +232 -0
- package/dist/change-set.d.ts.map +1 -0
- package/dist/cli/commands/audit.d.ts +7 -0
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/carve-emit.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon.d.ts +16 -0
- package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/import.d.ts +8 -0
- package/dist/cli/commands/import.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/init.d.ts +19 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/onboard.d.ts +9 -0
- package/dist/cli/commands/onboard.d.ts.map +1 -1
- package/dist/cli/commands/pinned-upgrade.d.ts +19 -5
- package/dist/cli/commands/pinned-upgrade.d.ts.map +1 -1
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/change-set.d.ts +11 -0
- package/dist/cli/handlers/change-set.d.ts.map +1 -0
- package/dist/cli/handlers/dev.d.ts.map +1 -1
- package/dist/cli/handlers/fan-out.d.ts +33 -0
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/misc.d.ts.map +1 -1
- package/dist/cli/handlers/operator.d.ts +16 -0
- package/dist/cli/handlers/operator.d.ts.map +1 -1
- package/dist/cli/handlers/pr.d.ts +31 -0
- package/dist/cli/handlers/pr.d.ts.map +1 -0
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/lsp/server.d.ts.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/mcp/op-tools.d.ts.map +1 -1
- package/dist/cli/mcp/server.d.ts.map +1 -1
- package/dist/cli/mcp/tools/build.d.ts.map +1 -1
- package/dist/cli/mcp/tools/composites.d.ts.map +1 -1
- package/dist/cli/mcp/tools/explain.d.ts.map +1 -1
- package/dist/cli/mcp/tools/import.d.ts.map +1 -1
- package/dist/cli/mcp/tools/lint.d.ts.map +1 -1
- package/dist/cli/mcp/tools/scaffold.d.ts.map +1 -1
- package/dist/cli/mcp/tools/search.d.ts.map +1 -1
- package/dist/cli/mcp/workspace-tools.d.ts +4 -3
- package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +74 -3
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/fetch.d.ts.map +1 -1
- package/dist/codegen/naming.d.ts.map +1 -1
- package/dist/codegen/pinned-upgrade.d.ts +67 -3
- package/dist/codegen/pinned-upgrade.d.ts.map +1 -1
- package/dist/codegen/registry.d.ts.map +1 -1
- package/dist/components/auto-release.d.ts +16 -0
- package/dist/components/auto-release.d.ts.map +1 -1
- package/dist/components/capability-plugin-loader.d.ts.map +1 -1
- package/dist/components/capability.d.ts +53 -0
- package/dist/components/capability.d.ts.map +1 -1
- package/dist/components/changed-files.d.ts +31 -0
- package/dist/components/changed-files.d.ts.map +1 -0
- package/dist/components/cli-support.d.ts +9 -1
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/component.d.ts +24 -0
- package/dist/components/component.d.ts.map +1 -1
- package/dist/components/deploy-units.d.ts.map +1 -1
- package/dist/components/driver.d.ts +13 -0
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/fan-out-output.d.ts +6 -0
- package/dist/components/fan-out-output.d.ts.map +1 -1
- package/dist/components/fan-out-record.d.ts +41 -0
- package/dist/components/fan-out-record.d.ts.map +1 -0
- package/dist/components/fan-out-run.d.ts +65 -0
- package/dist/components/fan-out-run.d.ts.map +1 -1
- package/dist/components/fan-out-support.d.ts +2 -0
- package/dist/components/fan-out-support.d.ts.map +1 -1
- package/dist/components/fan-out.d.ts +6 -0
- package/dist/components/fan-out.d.ts.map +1 -1
- package/dist/components/gated-wave-pipeline.d.ts +50 -0
- package/dist/components/gated-wave-pipeline.d.ts.map +1 -0
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/layers.d.ts +25 -0
- package/dist/components/layers.d.ts.map +1 -0
- package/dist/components/pr-pipeline.d.ts +61 -0
- package/dist/components/pr-pipeline.d.ts.map +1 -0
- package/dist/components/pr-run.d.ts +119 -0
- package/dist/components/pr-run.d.ts.map +1 -0
- package/dist/components/promote.d.ts.map +1 -1
- package/dist/components/starter-plugin.d.ts.map +1 -1
- package/dist/components/verbs/process-runner.d.ts.map +1 -1
- package/dist/components/verbs/r2-sync.d.ts.map +1 -1
- package/dist/components/verbs/run-agent.d.ts.map +1 -1
- package/dist/components/verbs/sbom-generator.d.ts.map +1 -1
- package/dist/components/verbs/shell.d.ts.map +1 -1
- package/dist/components/verbs/sign.d.ts.map +1 -1
- package/dist/components/verbs/verify.d.ts.map +1 -1
- package/dist/components/verbs/vuln-gate.d.ts.map +1 -1
- package/dist/components/verbs/vuln-scan.d.ts.map +1 -1
- package/dist/components/verbs/wrangler.d.ts.map +1 -1
- package/dist/components/wave-plan.d.ts +49 -0
- package/dist/components/wave-plan.d.ts.map +1 -0
- package/dist/composite.d.ts.map +1 -1
- package/dist/config.d.ts +19 -7
- package/dist/config.d.ts.map +1 -1
- package/dist/declarable.d.ts +16 -0
- package/dist/declarable.d.ts.map +1 -1
- package/dist/discovery/cache.d.ts.map +1 -1
- package/dist/discovery/fold-import.d.ts +15 -5
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/param-deps.d.ts +16 -0
- package/dist/discovery/param-deps.d.ts.map +1 -1
- package/dist/discovery/sandbox/policy-wire.d.ts.map +1 -1
- package/dist/discovery/specifier-resolve.d.ts +37 -0
- package/dist/discovery/specifier-resolve.d.ts.map +1 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/fold/fold.d.ts +3 -1
- package/dist/fold/fold.d.ts.map +1 -1
- package/dist/gated-waves.d.ts +147 -0
- package/dist/gated-waves.d.ts.map +1 -0
- package/dist/graph-detail.d.ts.map +1 -1
- package/dist/graph-ir.d.ts.map +1 -1
- package/dist/graph-layout.d.ts.map +1 -1
- package/dist/import/base-parser.d.ts.map +1 -1
- package/dist/import/embedded.d.ts +7 -0
- package/dist/import/embedded.d.ts.map +1 -1
- package/dist/import/parser-options.d.ts +14 -0
- package/dist/import/parser-options.d.ts.map +1 -0
- package/dist/import/parser.d.ts +17 -0
- package/dist/import/parser.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/kubectl-context.d.ts.map +1 -1
- package/dist/lexicon-output.d.ts.map +1 -1
- package/dist/lexicon-schema.d.ts +3 -3
- package/dist/lexicon.d.ts +103 -3
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/assert-live.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.d.ts +22 -0
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/lifecycle/git.d.ts +46 -1
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/member-ledger.d.ts.map +1 -1
- package/dist/lifecycle/observe.d.ts.map +1 -1
- package/dist/lifecycle/plan-ledger.d.ts.map +1 -1
- package/dist/lifecycle/release-ledger.d.ts.map +1 -1
- package/dist/lifecycle/scenario.d.ts.map +1 -1
- package/dist/lifecycle/status.d.ts.map +1 -1
- package/dist/lifecycle/work-lease.d.ts +61 -2
- package/dist/lifecycle/work-lease.d.ts.map +1 -1
- package/dist/lint/config.d.ts +8 -8
- package/dist/lint/engine.d.ts +6 -0
- package/dist/lint/engine.d.ts.map +1 -1
- package/dist/lint/post-synth.d.ts +18 -0
- package/dist/lint/post-synth.d.ts.map +1 -1
- package/dist/lsp/lexicon-providers.d.ts.map +1 -1
- package/dist/op/activities/activity-contracts.d.ts +137 -11
- package/dist/op/activities/activity-contracts.d.ts.map +1 -1
- package/dist/op/activities/apply.d.ts +79 -9
- package/dist/op/activities/apply.d.ts.map +1 -1
- package/dist/op/activities/change-set.d.ts +62 -0
- package/dist/op/activities/change-set.d.ts.map +1 -0
- package/dist/op/activities/decide.d.ts +2 -0
- package/dist/op/activities/decide.d.ts.map +1 -1
- package/dist/op/activities/factory.d.ts +137 -0
- package/dist/op/activities/factory.d.ts.map +1 -0
- package/dist/op/activities/index.d.ts +5 -1
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activities/lexicon-upgrade.d.ts +44 -12
- package/dist/op/activities/lexicon-upgrade.d.ts.map +1 -1
- package/dist/op/activity-contract.d.ts +1 -1
- package/dist/op/activity-contract.d.ts.map +1 -1
- package/dist/op/activity-failure.d.ts.map +1 -1
- package/dist/op/activity-profiles.d.ts.map +1 -1
- package/dist/op/builders.d.ts +11 -0
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/composites/apply-op.d.ts +12 -5
- package/dist/op/composites/apply-op.d.ts.map +1 -1
- package/dist/op/composites/lexicon-upgrade-op.d.ts +22 -6
- package/dist/op/composites/lexicon-upgrade-op.d.ts.map +1 -1
- package/dist/op/decide-backend.d.ts +5 -1
- package/dist/op/decide-backend.d.ts.map +1 -1
- package/dist/op/decide-read-inputs.d.ts +4 -2
- package/dist/op/decide-read-inputs.d.ts.map +1 -1
- package/dist/op/emulator-lifecycle.d.ts +21 -2
- package/dist/op/emulator-lifecycle.d.ts.map +1 -1
- package/dist/op/factory.d.ts +83 -0
- package/dist/op/factory.d.ts.map +1 -0
- package/dist/op/gate-approval.d.ts +5 -1
- package/dist/op/gate-approval.d.ts.map +1 -1
- package/dist/op/gate-wait.d.ts.map +1 -1
- package/dist/op/gate.d.ts +19 -3
- package/dist/op/gate.d.ts.map +1 -1
- package/dist/op/index.d.ts +6 -1
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/local-executor.d.ts +6 -0
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/op-ir.d.ts +2 -0
- package/dist/op/op-ir.d.ts.map +1 -1
- package/dist/op/op-verb-class.d.ts.map +1 -1
- package/dist/op/operator.d.ts.map +1 -1
- package/dist/op/run-context.d.ts +39 -0
- package/dist/op/run-context.d.ts.map +1 -0
- package/dist/op/run-live.d.ts.map +1 -1
- package/dist/op/runtimes/local.d.ts.map +1 -1
- package/dist/op/source-archive.d.ts.map +1 -1
- package/dist/op/steward-beside.d.ts.map +1 -1
- package/dist/op/steward-points.d.ts.map +1 -1
- package/dist/op/steward.d.ts.map +1 -1
- package/dist/op/types.d.ts +16 -3
- package/dist/op/types.d.ts.map +1 -1
- package/dist/op/work-lease-run.d.ts +24 -4
- package/dist/op/work-lease-run.d.ts.map +1 -1
- package/dist/plan-summary.d.ts +207 -0
- package/dist/plan-summary.d.ts.map +1 -0
- package/dist/pr-forge.d.ts +105 -0
- package/dist/pr-forge.d.ts.map +1 -0
- package/dist/pr-loop.d.ts +178 -0
- package/dist/pr-loop.d.ts.map +1 -0
- package/dist/project-root.d.ts.map +1 -1
- package/dist/provenance.d.ts +21 -0
- package/dist/provenance.d.ts.map +1 -1
- package/dist/pseudo-parameter.d.ts.map +1 -1
- package/dist/reconcile.d.ts.map +1 -1
- package/dist/secret-materialization.d.ts.map +1 -1
- package/dist/telemetry-attribution.d.ts +31 -0
- package/dist/telemetry-attribution.d.ts.map +1 -1
- package/dist/terraform/emit-source.d.ts.map +1 -1
- package/dist/terraform/hcl2json.d.ts +45 -0
- package/dist/terraform/hcl2json.d.ts.map +1 -0
- package/dist/terraform/parse.d.ts +4 -40
- package/dist/terraform/parse.d.ts.map +1 -1
- package/dist/testing.d.ts.map +1 -1
- package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -1
- package/dist/workspace/__fixtures__/decide-workspace.d.ts +1 -1
- package/dist/workspace/__fixtures__/decide-workspace.d.ts.map +1 -1
- package/dist/workspace/agent-cli.d.ts +5 -0
- package/dist/workspace/agent-cli.d.ts.map +1 -1
- package/dist/workspace/box-cli.d.ts +12 -0
- package/dist/workspace/box-cli.d.ts.map +1 -0
- package/dist/workspace/box-factory.d.ts +97 -0
- package/dist/workspace/box-factory.d.ts.map +1 -0
- package/dist/workspace/box-listing.d.ts +85 -0
- package/dist/workspace/box-listing.d.ts.map +1 -0
- package/dist/workspace/box-publish.d.ts +147 -0
- package/dist/workspace/box-publish.d.ts.map +1 -0
- package/dist/workspace/broker-protocol.d.ts +190 -0
- package/dist/workspace/broker-protocol.d.ts.map +1 -0
- package/dist/workspace/changes.d.ts.map +1 -1
- package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts.map +1 -1
- package/dist/workspace/checks/boxes.d.ts.map +1 -1
- package/dist/workspace/checks.d.ts +5 -0
- package/dist/workspace/checks.d.ts.map +1 -1
- package/dist/workspace/compose-graph.d.ts +10 -0
- package/dist/workspace/compose-graph.d.ts.map +1 -1
- package/dist/workspace/composites.d.ts +2 -2
- package/dist/workspace/composites.d.ts.map +1 -1
- package/dist/workspace/conformance/__fixtures__/reference-broker.d.ts +17 -0
- package/dist/workspace/conformance/__fixtures__/reference-broker.d.ts.map +1 -0
- package/dist/workspace/conformance/broker.d.ts +202 -0
- package/dist/workspace/conformance/broker.d.ts.map +1 -0
- package/dist/workspace/conformance/index.d.ts +68 -6
- package/dist/workspace/conformance/index.d.ts.map +1 -1
- package/dist/workspace/conformance/vitest.d.ts +19 -3
- package/dist/workspace/conformance/vitest.d.ts.map +1 -1
- package/dist/workspace/conformance/writer.d.ts +462 -0
- package/dist/workspace/conformance/writer.d.ts.map +1 -0
- package/dist/workspace/decide.d.ts +126 -12
- package/dist/workspace/decide.d.ts.map +1 -1
- package/dist/workspace/decision-points.schema.json +11 -4
- package/dist/workspace/declaration.d.ts +156 -4
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.schema.json +383 -5
- package/dist/workspace/environments.d.ts +36 -10
- package/dist/workspace/environments.d.ts.map +1 -1
- package/dist/workspace/factory-rules.d.ts +180 -0
- package/dist/workspace/factory-rules.d.ts.map +1 -0
- package/dist/workspace/generated-files.d.ts.map +1 -1
- package/dist/workspace/graph-cache.d.ts.map +1 -1
- package/dist/workspace/graph-cli.d.ts +2 -0
- package/dist/workspace/graph-cli.d.ts.map +1 -1
- package/dist/workspace/identity.d.ts +111 -0
- package/dist/workspace/identity.d.ts.map +1 -0
- package/dist/workspace/init.d.ts +21 -0
- package/dist/workspace/init.d.ts.map +1 -1
- package/dist/workspace/intent-cli.d.ts +7 -4
- package/dist/workspace/intent-cli.d.ts.map +1 -1
- package/dist/workspace/intent-record.d.ts +36 -2
- package/dist/workspace/intent-record.d.ts.map +1 -1
- package/dist/workspace/intent.d.ts +249 -8
- package/dist/workspace/intent.d.ts.map +1 -1
- package/dist/workspace/json-edit.d.ts +24 -0
- package/dist/workspace/json-edit.d.ts.map +1 -0
- package/dist/workspace/jsonc.d.ts +11 -0
- package/dist/workspace/jsonc.d.ts.map +1 -1
- package/dist/workspace/kinds.d.ts +66 -4
- package/dist/workspace/kinds.d.ts.map +1 -1
- package/dist/workspace/lexicon-symbols.d.ts +29 -0
- package/dist/workspace/lexicon-symbols.d.ts.map +1 -0
- package/dist/workspace/lineage-hash-index.d.ts.map +1 -1
- package/dist/workspace/lineage-lock.d.ts +2 -2
- package/dist/workspace/lineage-lock.d.ts.map +1 -1
- package/dist/workspace/lineage-upgrade.d.ts +31 -2
- package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
- package/dist/workspace/links.d.ts.map +1 -1
- package/dist/workspace/point-answer.schema.json +42 -2
- package/dist/workspace/points-cli.d.ts +10 -3
- package/dist/workspace/points-cli.d.ts.map +1 -1
- package/dist/workspace/points.d.ts +66 -5
- package/dist/workspace/points.d.ts.map +1 -1
- package/dist/workspace/principal-classes.d.ts +114 -0
- package/dist/workspace/principal-classes.d.ts.map +1 -0
- package/dist/workspace/reason-codes.d.ts +53 -4
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/record-assets.d.ts.map +1 -1
- package/dist/workspace/record-sessions.d.ts +5 -7
- package/dist/workspace/record-sessions.d.ts.map +1 -1
- package/dist/workspace/records-checkout.d.ts +54 -0
- package/dist/workspace/records-checkout.d.ts.map +1 -0
- package/dist/workspace/records-cli.d.ts +34 -1
- package/dist/workspace/records-cli.d.ts.map +1 -1
- package/dist/workspace/records-close.d.ts +7 -3
- package/dist/workspace/records-close.d.ts.map +1 -1
- package/dist/workspace/records-since.d.ts.map +1 -1
- package/dist/workspace/records-write.d.ts +68 -7
- package/dist/workspace/records-write.d.ts.map +1 -1
- package/dist/workspace/records.d.ts +43 -9
- package/dist/workspace/records.d.ts.map +1 -1
- package/dist/workspace/runs-cli.d.ts +296 -0
- package/dist/workspace/runs-cli.d.ts.map +1 -0
- package/dist/workspace/runs.d.ts +544 -0
- package/dist/workspace/runs.d.ts.map +1 -0
- package/dist/workspace/session-kinds.d.ts +1 -1
- package/dist/workspace/session-kinds.d.ts.map +1 -1
- package/dist/workspace/source-block.d.ts +1 -1
- package/dist/workspace/source-block.d.ts.map +1 -1
- package/dist/workspace/squash.d.ts +80 -0
- package/dist/workspace/squash.d.ts.map +1 -0
- package/dist/workspace/status-gates.d.ts +17 -3
- package/dist/workspace/status-gates.d.ts.map +1 -1
- package/dist/workspace/status.d.ts +64 -0
- package/dist/workspace/status.d.ts.map +1 -1
- package/dist/workspace/symbols.d.ts +63 -0
- package/dist/workspace/symbols.d.ts.map +1 -0
- package/dist/workspace/trailer-joins.d.ts +46 -0
- package/dist/workspace/trailer-joins.d.ts.map +1 -0
- package/dist/workspace/trailers.d.ts +89 -0
- package/dist/workspace/trailers.d.ts.map +1 -0
- package/dist/workspace/trust/attestor.d.ts.map +1 -1
- package/dist/workspace/trust/rotation.d.ts.map +1 -1
- package/dist/workspace/trust/run-commits.d.ts +46 -0
- package/dist/workspace/trust/run-commits.d.ts.map +1 -0
- package/dist/workspace/trust/run-statement.d.ts +128 -0
- package/dist/workspace/trust/run-statement.d.ts.map +1 -0
- package/dist/workspace/trust/seal.d.ts +38 -0
- package/dist/workspace/trust/seal.d.ts.map +1 -1
- package/dist/workspace/trust/test-repo.d.ts.map +1 -1
- package/dist/workspace/trust/verify-cli.d.ts +1 -1
- package/dist/workspace/trust/verify.d.ts +16 -2
- package/dist/workspace/trust/verify.d.ts.map +1 -1
- package/dist/workspace/wip-cli.d.ts +21 -0
- package/dist/workspace/wip-cli.d.ts.map +1 -0
- package/dist/workspace/wip.d.ts +268 -0
- package/dist/workspace/wip.d.ts.map +1 -0
- package/dist/workspace/work-cli.d.ts +39 -3
- package/dist/workspace/work-cli.d.ts.map +1 -1
- package/dist/workspace/work-evidence.d.ts +1 -1
- package/dist/workspace/work-evidence.d.ts.map +1 -1
- package/dist/workspace/work-size.d.ts +55 -0
- package/dist/workspace/work-size.d.ts.map +1 -0
- package/dist/workspace/work.d.ts +26 -1
- package/dist/workspace/work.d.ts.map +1 -1
- package/dist/workspace/workspace-kinds.schema.json +122 -0
- package/dist/workspace/workspace-principals.schema.json +60 -0
- package/dist/workspace/write-lock-cli.d.ts +60 -0
- package/dist/workspace/write-lock-cli.d.ts.map +1 -0
- package/dist/workspace/write-lock.d.ts +152 -0
- package/dist/workspace/write-lock.d.ts.map +1 -0
- package/dist/workspace/write-scope.d.ts +83 -19
- package/dist/workspace/write-scope.d.ts.map +1 -1
- package/dist/yaml.d.ts +21 -39
- package/dist/yaml.d.ts.map +1 -1
- package/package.json +7 -2
- package/src/__fixtures__/change-set/lifecycle-plan.floci.json +59 -0
- package/src/__fixtures__/change-set/lifecycle-v1.ts.txt +4 -0
- package/src/__fixtures__/change-set/lifecycle-v2.ts.txt +4 -0
- package/src/__fixtures__/change-set/lifecycle.change-set.golden.json +95 -0
- package/src/__fixtures__/change-set/warden.change-set.golden.json +106 -0
- package/src/__fixtures__/change-set/warden.plan.json +44 -0
- package/src/__fixtures__/plan-summary/normalization-vectors.json +1593 -0
- package/src/__fixtures__/pr-loop/pr-apply-applied.note.golden.md +31 -0
- package/src/__fixtures__/pr-loop/pr-apply-refused.note.golden.md +31 -0
- package/src/__fixtures__/pr-loop/pr-plan.golden.json +127 -0
- package/src/__fixtures__/pr-loop/pr-plan.note.golden.md +37 -0
- package/src/audit/core.test.ts +15 -0
- package/src/audit/core.ts +8 -2
- package/src/audit/discover.test.ts +11 -0
- package/src/audit/discover.ts +7 -2
- package/src/audit/prior-art.ts +15 -0
- package/src/change-set-bundle.test.ts +57 -0
- package/src/change-set.test.ts +215 -0
- package/src/change-set.ts +445 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +1 -1
- package/src/cli/commands/audit-opt-in.test.ts +54 -0
- package/src/cli/commands/audit.ts +29 -2
- package/src/cli/commands/build.test.ts +19 -0
- package/src/cli/commands/build.ts +3 -2
- package/src/cli/commands/carve-emit-state.test.ts +4 -0
- package/src/cli/commands/carve-emit.ts +5 -19
- package/src/cli/commands/check-lexicon.test.ts +62 -1
- package/src/cli/commands/check-lexicon.ts +36 -6
- package/src/cli/commands/import-live.test.ts +18 -0
- package/src/cli/commands/import-parser-options.test.ts +133 -0
- package/src/cli/commands/import.ts +21 -3
- package/src/cli/commands/init-lexicon/templates/project.ts +1 -1
- package/src/cli/commands/init.test.ts +8 -1
- package/src/cli/commands/init.ts +23 -8
- package/src/cli/commands/lint.test.ts +37 -0
- package/src/cli/commands/onboard.test.ts +53 -3
- package/src/cli/commands/onboard.ts +52 -4
- package/src/cli/commands/pinned-upgrade.ts +73 -33
- package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +2 -2
- package/src/cli/handlers/build.ts +13 -1
- package/src/cli/handlers/change-set.test.ts +56 -0
- package/src/cli/handlers/change-set.ts +44 -0
- package/src/cli/handlers/components.ts +2 -2
- package/src/cli/handlers/dev.ts +8 -6
- package/src/cli/handlers/fan-out.test.ts +221 -2
- package/src/cli/handlers/fan-out.ts +165 -75
- package/src/cli/handlers/graph.ts +3 -0
- package/src/cli/handlers/lifecycle.test.ts +67 -0
- package/src/cli/handlers/lifecycle.ts +6 -5
- package/src/cli/handlers/misc.ts +2 -0
- package/src/cli/handlers/operator.test.ts +21 -0
- package/src/cli/handlers/operator.ts +121 -6
- package/src/cli/handlers/pr.ts +373 -0
- package/src/cli/handlers/run.test.ts +111 -0
- package/src/cli/handlers/run.ts +86 -24
- package/src/cli/handlers/scenario.ts +2 -2
- package/src/cli/main.test.ts +21 -0
- package/src/cli/main.ts +246 -30
- package/src/cli/mcp/never-over-mcp.test.ts +101 -0
- package/src/cli/mcp/op-tools.ts +24 -2
- package/src/cli/mcp/workspace-tools.test.ts +5 -2
- package/src/cli/mcp/workspace-tools.ts +62 -7
- package/src/cli/registry.ts +74 -3
- package/src/codegen/pinned-upgrade-multi.test.ts +157 -0
- package/src/codegen/pinned-upgrade-tags.test.ts +112 -0
- package/src/codegen/pinned-upgrade.test.ts +104 -0
- package/src/codegen/pinned-upgrade.ts +250 -56
- package/src/codegen/testdata/postgres-tags.json +242 -0
- package/src/components/auto-release.ts +1 -1
- package/src/components/capability.ts +55 -0
- package/src/components/changed-files.ts +66 -0
- package/src/components/cli-support.ts +28 -5
- package/src/components/component.schema.json +17 -0
- package/src/components/component.ts +25 -0
- package/src/components/deploy-units.ts +3 -0
- package/src/components/driver.test.ts +52 -0
- package/src/components/driver.ts +31 -13
- package/src/components/fan-out-output.ts +42 -1
- package/src/components/fan-out-record.ts +69 -0
- package/src/components/fan-out-run.test.ts +17 -0
- package/src/components/fan-out-run.ts +304 -1
- package/src/components/fan-out-support.ts +3 -0
- package/src/components/fan-out-waves.test.ts +375 -0
- package/src/components/fan-out.ts +18 -17
- package/src/components/gated-wave-pipeline.ts +82 -0
- package/src/components/index.ts +1 -0
- package/src/components/layers.ts +30 -0
- package/src/components/pr-pipeline.ts +91 -0
- package/src/components/pr-run.test.ts +205 -0
- package/src/components/pr-run.ts +280 -0
- package/src/components/promote.ts +5 -0
- package/src/components/verbs/shell.test.ts +11 -0
- package/src/components/verbs/shell.ts +7 -3
- package/src/components/wave-plan.ts +124 -0
- package/src/config.test.ts +21 -0
- package/src/config.ts +15 -0
- package/src/declarable.test.ts +21 -0
- package/src/declarable.ts +19 -0
- package/src/discovery/fold-composite.test.ts +41 -5
- package/src/discovery/fold-import.test.ts +129 -0
- package/src/discovery/fold-import.ts +643 -131
- package/src/discovery/fold-package-composite.test.ts +264 -0
- package/src/discovery/fold-specifiers.test.ts +191 -0
- package/src/discovery/param-deps.test.ts +66 -1
- package/src/discovery/param-deps.ts +45 -0
- package/src/discovery/sandbox/bundle.ts +1 -1
- package/src/discovery/specifier-resolve.ts +344 -0
- package/src/fold/fold.test.ts +39 -1
- package/src/fold/fold.ts +83 -9
- package/src/fold-provenance.test.ts +140 -0
- package/src/gated-waves-bundle.test.ts +62 -0
- package/src/gated-waves.test.ts +118 -0
- package/src/gated-waves.ts +240 -0
- package/src/graph-ir.test.ts +83 -0
- package/src/graph-ir.ts +29 -0
- package/src/import/embedded.test.ts +20 -0
- package/src/import/embedded.ts +13 -0
- package/src/import/parser-options.ts +46 -0
- package/src/import/parser.ts +20 -0
- package/src/index.ts +1 -0
- package/src/lexicon.ts +106 -3
- package/src/lifecycle/gate-ledger.ts +23 -0
- package/src/lifecycle/git.ts +102 -16
- package/src/lifecycle/observe.test.ts +18 -0
- package/src/lifecycle/observe.ts +6 -4
- package/src/lifecycle/snapshot.ts +2 -2
- package/src/lifecycle/work-lease.test.ts +1 -1
- package/src/lifecycle/work-lease.ts +93 -2
- package/src/lint/engine.test.ts +55 -0
- package/src/lint/engine.ts +30 -4
- package/src/lint/output-docs.test.ts +59 -56
- package/src/lint/post-synth.test.ts +19 -0
- package/src/lint/post-synth.ts +19 -1
- package/src/meta/declared-imports.test.ts +1 -1
- package/src/op/activities/activity-contracts.ts +84 -0
- package/src/op/activities/apply.test.ts +189 -5
- package/src/op/activities/apply.ts +230 -8
- package/src/op/activities/change-set.ts +114 -0
- package/src/op/activities/decide.ts +3 -0
- package/src/op/activities/factory.ts +675 -0
- package/src/op/activities/index.ts +9 -0
- package/src/op/activities/lexicon-upgrade.test.ts +160 -6
- package/src/op/activities/lexicon-upgrade.ts +154 -51
- package/src/op/activity-contract.test.ts +21 -1
- package/src/op/activity-contract.ts +19 -9
- package/src/op/builders.ts +23 -0
- package/src/op/composites/apply-op.ts +12 -5
- package/src/op/composites/composites.test.ts +30 -3
- package/src/op/composites/lexicon-upgrade-op.test.ts +7 -2
- package/src/op/composites/lexicon-upgrade-op.ts +29 -8
- package/src/op/decide-backend.ts +5 -1
- package/src/op/decide-read-inputs.ts +33 -5
- package/src/op/emulator-lifecycle.test.ts +23 -0
- package/src/op/emulator-lifecycle.ts +52 -14
- package/src/op/factory.e2e.test.ts +232 -0
- package/src/op/factory.ts +118 -0
- package/src/op/gate-approval.test.ts +74 -0
- package/src/op/gate-approval.ts +5 -1
- package/src/op/gate.ts +31 -5
- package/src/op/index.ts +11 -1
- package/src/op/local-executor.test.ts +15 -0
- package/src/op/local-executor.ts +40 -12
- package/src/op/local-output.ts +1 -1
- package/src/op/op-ir.test.ts +9 -0
- package/src/op/op-ir.ts +4 -0
- package/src/op/op-verb-class.ts +3 -0
- package/src/op/operator.ts +4 -0
- package/src/op/run-context.test.ts +141 -0
- package/src/op/run-context.ts +51 -0
- package/src/op/runtimes/local.test.ts +14 -0
- package/src/op/runtimes/local.ts +2 -0
- package/src/op/steward-beside.ts +2 -0
- package/src/op/types.ts +16 -3
- package/src/op/work-lease-run.test.ts +47 -0
- package/src/op/work-lease-run.ts +76 -11
- package/src/plan-summary.test.ts +263 -0
- package/src/plan-summary.ts +845 -0
- package/src/pr-forge.test.ts +237 -0
- package/src/pr-forge.ts +332 -0
- package/src/pr-loop-bundle.test.ts +46 -0
- package/src/pr-loop.test.ts +171 -0
- package/src/pr-loop.ts +324 -0
- package/src/provenance.ts +35 -0
- package/src/telemetry-attribution.test.ts +23 -1
- package/src/telemetry-attribution.ts +51 -0
- package/src/terraform/hcl2json.test.ts +21 -0
- package/src/terraform/hcl2json.ts +81 -0
- package/src/terraform/parse.ts +11 -74
- package/src/workspace/agent-cli.ts +7 -3
- package/src/workspace/agent.schema.json +22 -0
- package/src/workspace/app-kind.test.ts +196 -0
- package/src/workspace/box-cli.ts +113 -0
- package/src/workspace/box-factory.test.ts +323 -0
- package/src/workspace/box-factory.ts +136 -0
- package/src/workspace/box-listing-write.schema.json +204 -0
- package/src/workspace/box-listing.test.ts +234 -0
- package/src/workspace/box-listing.ts +324 -0
- package/src/workspace/box-publish.schema.json +498 -0
- package/src/workspace/box-publish.test.ts +204 -0
- package/src/workspace/box-publish.ts +420 -0
- package/src/workspace/box-ship.test.ts +96 -0
- package/src/workspace/broker-protocol.schema.json +186 -0
- package/src/workspace/broker-protocol.test.ts +122 -0
- package/src/workspace/broker-protocol.ts +317 -0
- package/src/workspace/builder-tiers.test.ts +146 -0
- package/src/workspace/change-set.schema.json +460 -0
- package/src/workspace/changes.schema.json +12 -20
- package/src/workspace/changes.ts +4 -1
- package/src/workspace/chant-migrations.e2e.test.ts +17 -5
- package/src/workspace/checks/boxes.test.ts +5 -0
- package/src/workspace/checks.ts +39 -8
- package/src/workspace/component-environments.test.ts +105 -0
- package/src/workspace/compose-graph.ts +7 -0
- package/src/workspace/composites.schema.json +23 -4
- package/src/workspace/composites.test.ts +8 -8
- package/src/workspace/composites.ts +13 -2
- package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +2 -2
- package/src/workspace/conformance/__fixtures__/reference-broker.ts +207 -0
- package/src/workspace/conformance/__writer_fixture__/answers/answer.kind.mjs +32 -0
- package/src/workspace/conformance/__writer_fixture__/answers/answer.schema.json +135 -0
- package/src/workspace/conformance/__writer_fixture__/contracts/contract.kind.mjs +37 -0
- package/src/workspace/conformance/__writer_fixture__/contracts/contract.schema.json +213 -0
- package/src/workspace/conformance/__writer_fixture__/decisions/points.json +228 -0
- package/src/workspace/conformance/__writer_fixture__/drivers/driver.kind.mjs +15 -0
- package/src/workspace/conformance/__writer_fixture__/drivers/driver.schema.json +38 -0
- package/src/workspace/conformance/__writer_fixture__/sessions/session.kind.mjs +54 -0
- package/src/workspace/conformance/__writer_fixture__/sessions/session.schema.json +289 -0
- package/src/workspace/conformance/__writer_fixture__/work/W-001-the-writer-suite-works-this-item.md +25 -0
- package/src/workspace/conformance/__writer_fixture__/work/work.kind.mjs +60 -0
- package/src/workspace/conformance/__writer_fixture__/work/work.schema.json +603 -0
- package/src/workspace/conformance/broker.test.ts +122 -0
- package/src/workspace/conformance/broker.ts +792 -0
- package/src/workspace/conformance/conformance.e2e.test.ts +36 -3
- package/src/workspace/conformance/index.mjs +51 -3
- package/src/workspace/conformance/index.ts +107 -15
- package/src/workspace/conformance/vitest.ts +76 -2
- package/src/workspace/conformance/writer-conformance.e2e.test.ts +177 -0
- package/src/workspace/conformance/writer.test.ts +186 -0
- package/src/workspace/conformance/writer.ts +1191 -0
- package/src/workspace/decide.test.ts +208 -4
- package/src/workspace/decide.ts +433 -73
- package/src/workspace/decision-points.schema.json +11 -4
- package/src/workspace/declaration.schema.json +383 -5
- package/src/workspace/declaration.ts +315 -10
- package/src/workspace/environments.ts +66 -14
- package/src/workspace/factory-rules.test.ts +62 -0
- package/src/workspace/factory-rules.ts +218 -0
- package/src/workspace/graph-cli.ts +6 -3
- package/src/workspace/graph.schema.json +314 -0
- package/src/workspace/identity.test.ts +219 -0
- package/src/workspace/identity.ts +186 -0
- package/src/workspace/init.ts +82 -2
- package/src/workspace/intent-cli.ts +84 -13
- package/src/workspace/intent-patch-id.test.ts +162 -0
- package/src/workspace/intent-record.schema.json +336 -1
- package/src/workspace/intent-record.ts +96 -16
- package/src/workspace/intent-reviews.test.ts +76 -0
- package/src/workspace/intent-squash.test.ts +213 -0
- package/src/workspace/intent-why.test.ts +274 -0
- package/src/workspace/intent.schema.json +950 -9
- package/src/workspace/intent.test.ts +11 -2
- package/src/workspace/intent.ts +722 -65
- package/src/workspace/json-edit.test.ts +121 -0
- package/src/workspace/json-edit.ts +142 -0
- package/src/workspace/jsonc.ts +20 -0
- package/src/workspace/kinds.ts +201 -39
- package/src/workspace/lexicon-symbols.test.ts +148 -0
- package/src/workspace/lexicon-symbols.ts +125 -0
- package/src/workspace/lineage-upgrade.test.ts +63 -0
- package/src/workspace/lineage-upgrade.ts +146 -5
- package/src/workspace/plan-summary.schema.json +155 -0
- package/src/workspace/point-answer.schema.json +42 -2
- package/src/workspace/points-cli.ts +42 -16
- package/src/workspace/points-write.schema.json +163 -4
- package/src/workspace/points.schema.json +157 -2
- package/src/workspace/points.test.ts +13 -1
- package/src/workspace/points.ts +118 -12
- package/src/workspace/pr-report.schema.json +95 -0
- package/src/workspace/principal-classes.test.ts +175 -0
- package/src/workspace/principal-classes.ts +307 -0
- package/src/workspace/profiles.test.ts +159 -0
- package/src/workspace/read-contract.test.ts +2 -2
- package/src/workspace/reason-codes.test.ts +23 -3
- package/src/workspace/reason-codes.ts +61 -4
- package/src/workspace/record-sessions.ts +9 -8
- package/src/workspace/records-amend.schema.json +80 -0
- package/src/workspace/records-checkout.ts +106 -0
- package/src/workspace/records-cli.ts +123 -17
- package/src/workspace/records-close.schema.json +79 -0
- package/src/workspace/records-close.ts +31 -7
- package/src/workspace/records-concurrent.e2e.test.ts +135 -0
- package/src/workspace/records-concurrent.test.ts +145 -0
- package/src/workspace/records-formats.test.ts +46 -10
- package/src/workspace/records-new.schema.json +9 -0
- package/src/workspace/records-review.schema.json +80 -0
- package/src/workspace/records-sessions-write.test.ts +69 -0
- package/src/workspace/records-sessions.test.ts +22 -0
- package/src/workspace/records-uncommitted.test.ts +171 -0
- package/src/workspace/records-write.ts +219 -30
- package/src/workspace/records.schema.json +88 -1
- package/src/workspace/records.ts +76 -8
- package/src/workspace/reference-kinds/app/package.json +10 -0
- package/src/workspace/reference-kinds/app/workspace-kinds.json +45 -0
- package/src/workspace/run-statement.schema.json +622 -0
- package/src/workspace/runs-cli.ts +710 -0
- package/src/workspace/runs-statement.test.ts +239 -0
- package/src/workspace/runs-write.schema.json +994 -0
- package/src/workspace/runs.schema.json +1257 -0
- package/src/workspace/runs.test.ts +281 -0
- package/src/workspace/runs.ts +970 -0
- package/src/workspace/session-kinds.ts +3 -3
- package/src/workspace/squash.ts +198 -0
- package/src/workspace/status-contract.test.ts +10 -2
- package/src/workspace/status-gates.ts +25 -9
- package/src/workspace/status.schema.json +252 -4
- package/src/workspace/status.ts +110 -1
- package/src/workspace/symbols.ts +191 -0
- package/src/workspace/trailer-joins.test.ts +171 -0
- package/src/workspace/trailer-joins.ts +82 -0
- package/src/workspace/trailers.ts +148 -0
- package/src/workspace/trust/run-commits.ts +115 -0
- package/src/workspace/trust/run-statement.ts +226 -0
- package/src/workspace/trust/seal.ts +58 -1
- package/src/workspace/trust/verify-cli.ts +6 -5
- package/src/workspace/trust/verify.ts +39 -6
- package/src/workspace/wip-cli.ts +97 -0
- package/src/workspace/wip-write.schema.json +504 -0
- package/src/workspace/wip.schema.json +334 -0
- package/src/workspace/wip.test.ts +294 -0
- package/src/workspace/wip.ts +753 -0
- package/src/workspace/work-cli.e2e.test.ts +28 -1
- package/src/workspace/work-cli.ts +136 -8
- package/src/workspace/work-evidence.schema.json +5 -0
- package/src/workspace/work-history.schema.json +73 -1
- package/src/workspace/work-history.test.ts +48 -5
- package/src/workspace/work-kind-fields.test.ts +154 -0
- package/src/workspace/work-lease.schema.json +14 -1
- package/src/workspace/work-size.test.ts +77 -0
- package/src/workspace/work-size.ts +93 -0
- package/src/workspace/work.ts +82 -0
- package/src/workspace/workspace-kinds.schema.json +122 -0
- package/src/workspace/workspace-principals.schema.json +60 -0
- package/src/workspace/write-lock-cli.ts +131 -0
- package/src/workspace/write-lock.schema.json +172 -0
- package/src/workspace/write-lock.test.ts +189 -0
- package/src/workspace/write-lock.ts +474 -0
- package/src/workspace/write-scope.test.ts +144 -3
- package/src/workspace/write-scope.ts +192 -45
- package/src/yaml.test.ts +111 -1
- package/src/yaml.ts +141 -549
|
@@ -0,0 +1,845 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The grouped plan summary (#3188): a view over a change-set document
|
|
3
|
+
* (`./change-set.ts`) that says how many members take the same change, which
|
|
4
|
+
* take something more, and every destroy, replacement, failure and hole by
|
|
5
|
+
* name. It is what reviewers read in an MR or PR note. Gates never bind it;
|
|
6
|
+
* they bind the document's digest.
|
|
7
|
+
*
|
|
8
|
+
* ## What "the same change" means
|
|
9
|
+
*
|
|
10
|
+
* A unit is a member of the document, or, when the document has one member,
|
|
11
|
+
* one resource instance of it; instances group only with instances of the
|
|
12
|
+
* same expansion (the address with its instance keys removed). Two units
|
|
13
|
+
* take the same change when their normalized changes are equal as
|
|
14
|
+
* multisets. A normalized change is:
|
|
15
|
+
*
|
|
16
|
+
* - the action. `no-op` entries are dropped.
|
|
17
|
+
* - the address, with every instance key removed and the unit's tokens
|
|
18
|
+
* stripped from module call names and the resource name. The type is
|
|
19
|
+
* never touched.
|
|
20
|
+
* - each attribute the entry carries (the change-set adapters carry only
|
|
21
|
+
* the attributes a change writes), with the unit's tokens stripped from
|
|
22
|
+
* every string value. A sensitive attribute compares by its path alone,
|
|
23
|
+
* since the document carries no value for it.
|
|
24
|
+
*
|
|
25
|
+
* A unit's tokens are what names it: for a member, its scope (the estate or
|
|
26
|
+
* environment) and the last path segment of its name; for an instance, its
|
|
27
|
+
* own instance keys. Each is also tried with `-` and `_` swapped. A token
|
|
28
|
+
* is stripped only where it stands as a whole word, and one under three
|
|
29
|
+
* characters must also touch a `-` or `_`, so the key `1` leaves
|
|
30
|
+
* `10.1.0.0/16` alone. choudoufu's `tofu-estate` and `tofu-address` markers
|
|
31
|
+
* normalize away only when they hold the unit's own estate or address.
|
|
32
|
+
* Nothing else is stripped.
|
|
33
|
+
*
|
|
34
|
+
* An `import` block, a forget (a `removed` block or `destroy = false`, the
|
|
35
|
+
* plan action `forget`) and a triggered Terraform action (`action_invocations`)
|
|
36
|
+
* each get a named list of their own. A forget is not a destroy and is never
|
|
37
|
+
* in `destroys`; an import is part of its entry's change, so an entry that
|
|
38
|
+
* imports never groups with the same change without the import, and a triggered
|
|
39
|
+
* action is part of its unit's change the same way.
|
|
40
|
+
*
|
|
41
|
+
* A provisional member (one planned before what it reads applied, #3416)
|
|
42
|
+
* groups only with other provisional members, and its group says so: the
|
|
43
|
+
* flag is part of the group's id, so the same change planned for real and
|
|
44
|
+
* planned on stand-in values never share a group.
|
|
45
|
+
*
|
|
46
|
+
* These are choudoufu's rules for `live-summary` (choudoufu#1753). The two
|
|
47
|
+
* implementations are kept together by one table of test vectors both run
|
|
48
|
+
* (`__fixtures__/plan-summary/normalization-vectors.json`), per the ruling
|
|
49
|
+
* recorded on #3188.
|
|
50
|
+
*
|
|
51
|
+
* ## The import graph
|
|
52
|
+
*
|
|
53
|
+
* terragucci runs this in customer CI from one bundled file (#3421). It
|
|
54
|
+
* imports `./lifecycle/plan-digest` and types only, as `./change-set` does,
|
|
55
|
+
* and `change-set-bundle.test.ts` holds that.
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
import { computePlanDigest, PLAN_DIGEST_PREFIX } from "./lifecycle/plan-digest";
|
|
59
|
+
import type { ChangeSetAction, ChangeSetAttribute, ChangeSetDocument, ChangeSetEntry, ChangeSetMember, ChangeSetSideEffect } from "./change-set";
|
|
60
|
+
|
|
61
|
+
/** The schema a plan summary names in `$schema`. */
|
|
62
|
+
export const PLAN_SUMMARY_SCHEMA_ID = "https://intentius.io/chant/schemas/workspace/plan-summary/v1/plan-summary.schema.json";
|
|
63
|
+
|
|
64
|
+
/** The read-contract version the summary follows. */
|
|
65
|
+
export const PLAN_SUMMARY_CONTRACT = 1;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The most characters a GitHub issue or pull request comment body may hold.
|
|
69
|
+
* The REST API refuses a longer one with "Body is too long (maximum is 65536
|
|
70
|
+
* characters)". GitHub documents no figure; this is the refusal's own.
|
|
71
|
+
*/
|
|
72
|
+
export const GITHUB_COMMENT_LIMIT = 65_536;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The most characters a GitLab note may hold, merge-request notes included:
|
|
76
|
+
* "Limited to 1,000,000 characters" (https://docs.gitlab.com/api/notes/).
|
|
77
|
+
*/
|
|
78
|
+
export const GITLAB_NOTE_LIMIT = 1_000_000;
|
|
79
|
+
|
|
80
|
+
/** What a group's units are: whole members, or the instances of one member. */
|
|
81
|
+
export type PlanSummaryUnit = "member" | "instance";
|
|
82
|
+
|
|
83
|
+
/** One distinct normalized change of a group. */
|
|
84
|
+
export interface PlanSummaryChange {
|
|
85
|
+
/** What a reader sees: the action's symbol, the normalized address and the attributes written. */
|
|
86
|
+
line: string;
|
|
87
|
+
action: ChangeSetAction;
|
|
88
|
+
/** How many times one unit makes this change. */
|
|
89
|
+
count: number;
|
|
90
|
+
/** The baseline group whose change at the same address and action this one differs from. */
|
|
91
|
+
differsFrom?: string;
|
|
92
|
+
/** The attributes it differs in. Values are never printed. */
|
|
93
|
+
differsIn?: string[];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** One destroy or replacement, by its real address. */
|
|
97
|
+
export interface PlanSummaryDestroy {
|
|
98
|
+
member: string;
|
|
99
|
+
address: string;
|
|
100
|
+
type: string;
|
|
101
|
+
action: "delete" | "replace";
|
|
102
|
+
deposed?: string;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** One resource an `import` block brings into state. */
|
|
106
|
+
export interface PlanSummaryImport {
|
|
107
|
+
member: string;
|
|
108
|
+
address: string;
|
|
109
|
+
type: string;
|
|
110
|
+
/** What else the plan does to it: `no-op` when the import changes nothing, else `update`, `create` and so on. */
|
|
111
|
+
action: ChangeSetAction;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** One resource a plan stops tracking and leaves running (`removed` with `destroy = false`). It is not a destroy. */
|
|
115
|
+
export interface PlanSummaryForget {
|
|
116
|
+
member: string;
|
|
117
|
+
address: string;
|
|
118
|
+
type: string;
|
|
119
|
+
deposed?: string;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** A provider-defined side effect an apply runs: a triggered Terraform action. */
|
|
123
|
+
export type PlanSummarySideEffect = ChangeSetSideEffect;
|
|
124
|
+
|
|
125
|
+
export interface PlanSummaryGroup {
|
|
126
|
+
/** Twelve hex characters over the group's normalized changes: the same change gets the same id in every run. */
|
|
127
|
+
id: string;
|
|
128
|
+
/** For instance groups, the expansion the instances belong to. */
|
|
129
|
+
resource?: string;
|
|
130
|
+
/** Member names, or instance addresses. */
|
|
131
|
+
units: string[];
|
|
132
|
+
/** A group of one beside other groups of the same expansion. */
|
|
133
|
+
outlier: boolean;
|
|
134
|
+
/** Units whose plans change nothing. */
|
|
135
|
+
noChanges: boolean;
|
|
136
|
+
changes: PlanSummaryChange[];
|
|
137
|
+
/** The group whose whole change this one contains; `plus` is what it adds. */
|
|
138
|
+
extends?: string;
|
|
139
|
+
plus?: PlanSummaryChange[];
|
|
140
|
+
/** Every destroy and replacement any unit of the group makes. */
|
|
141
|
+
destroys: PlanSummaryDestroy[];
|
|
142
|
+
/** Every triggered action any unit of the group runs. */
|
|
143
|
+
sideEffects: PlanSummarySideEffect[];
|
|
144
|
+
/** Its units are provisional plans: previews no approval covers. Absent on a group of real plans. */
|
|
145
|
+
provisional?: true;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** A member that failed to plan. It is never in a group. */
|
|
149
|
+
export interface PlanSummaryFailure {
|
|
150
|
+
member: string;
|
|
151
|
+
scope?: string;
|
|
152
|
+
reason: string;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** An address a member's planner could not read. */
|
|
156
|
+
export interface PlanSummaryHole {
|
|
157
|
+
member: string;
|
|
158
|
+
address: string;
|
|
159
|
+
type?: string;
|
|
160
|
+
reason: string;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export interface PlanSummary {
|
|
164
|
+
$schema: typeof PLAN_SUMMARY_SCHEMA_ID;
|
|
165
|
+
contract: typeof PLAN_SUMMARY_CONTRACT;
|
|
166
|
+
/** The digest of the change-set document summarized. */
|
|
167
|
+
changeSet: string;
|
|
168
|
+
unit: PlanSummaryUnit;
|
|
169
|
+
/** Members, failed ones included, or the changing instances of the one member. */
|
|
170
|
+
units: number;
|
|
171
|
+
groups: PlanSummaryGroup[];
|
|
172
|
+
failed: PlanSummaryFailure[];
|
|
173
|
+
/** Every destroy and replacement in the document, failed members' included, sorted by member and address. */
|
|
174
|
+
destroys: PlanSummaryDestroy[];
|
|
175
|
+
/** Every import, in the document, sorted by member and address. Imports are never destroys. */
|
|
176
|
+
imports: PlanSummaryImport[];
|
|
177
|
+
/** Every forget in the document, sorted by member and address. A forget is never in `destroys`. */
|
|
178
|
+
forgets: PlanSummaryForget[];
|
|
179
|
+
/** Every triggered action in the document, sorted by member and address. */
|
|
180
|
+
sideEffects: PlanSummarySideEffect[];
|
|
181
|
+
holes: PlanSummaryHole[];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export interface GroupChangeSetOptions {
|
|
185
|
+
/** `member` groups whole members; `instance` groups one member's instances. Default: `instance` for a one-member document, else `member`. */
|
|
186
|
+
unit?: PlanSummaryUnit;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ── normalization ────────────────────────────────────────────────────────────
|
|
190
|
+
|
|
191
|
+
const ESTATE = "<estate>";
|
|
192
|
+
const ADDRESS = "<address>";
|
|
193
|
+
const KEY = "<key>";
|
|
194
|
+
const UNKNOWN = "<known after apply>";
|
|
195
|
+
|
|
196
|
+
/** choudoufu's marker keys (choudoufu internal/live/markers). */
|
|
197
|
+
const TAG_ESTATE = "tofu-estate";
|
|
198
|
+
const TAG_ADDRESS = "tofu-address";
|
|
199
|
+
const ADDRESS_ANNOTATION = "choudoufu.intentius.io/tofu-address";
|
|
200
|
+
const MAX_CONTINUATIONS = 4;
|
|
201
|
+
|
|
202
|
+
interface Token {
|
|
203
|
+
text: string;
|
|
204
|
+
placeholder: string;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
interface Identity {
|
|
208
|
+
/** Longest first. */
|
|
209
|
+
tokens: Token[];
|
|
210
|
+
/** What a `tofu-estate` marker may hold and still normalize away. */
|
|
211
|
+
estate?: string;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function identity(estate: string | undefined, keys: readonly string[], names: readonly string[]): Identity {
|
|
215
|
+
const tokens: Token[] = [];
|
|
216
|
+
const seen = new Set<string>();
|
|
217
|
+
const add = (s: string, placeholder: string) => {
|
|
218
|
+
for (const v of [s, s.replaceAll("-", "_"), s.replaceAll("_", "-")]) {
|
|
219
|
+
if (v === "" || seen.has(v)) continue;
|
|
220
|
+
seen.add(v);
|
|
221
|
+
tokens.push({ text: v, placeholder });
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
if (estate) add(estate, ESTATE);
|
|
225
|
+
for (const n of names) add(n, ESTATE);
|
|
226
|
+
for (const k of keys) add(k, KEY);
|
|
227
|
+
tokens.sort((a, b) => b.text.length - a.text.length);
|
|
228
|
+
return { tokens, ...(estate ? { estate } : {}) };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const isAlnum = (c: string | undefined): boolean => c !== undefined && /^[A-Za-z0-9]$/.test(c);
|
|
232
|
+
const isJoin = (c: string | undefined): boolean => c === "-" || c === "_";
|
|
233
|
+
|
|
234
|
+
/** Replace `t` where it stands as a whole word; a token under three characters must also touch `-` or `_`. */
|
|
235
|
+
function replaceToken(s: string, t: Token): string {
|
|
236
|
+
if (!s.includes(t.text)) return s;
|
|
237
|
+
let out = "";
|
|
238
|
+
let i = 0;
|
|
239
|
+
while (i < s.length) {
|
|
240
|
+
const j = s.indexOf(t.text, i);
|
|
241
|
+
if (j < 0) {
|
|
242
|
+
out += s.slice(i);
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
const end = j + t.text.length;
|
|
246
|
+
let ok = !isAlnum(s[j - 1]) && !isAlnum(s[end]);
|
|
247
|
+
if (ok && t.text.length < 3) ok = isJoin(s[j - 1]) || isJoin(s[end]);
|
|
248
|
+
if (ok) {
|
|
249
|
+
out += s.slice(i, j) + t.placeholder;
|
|
250
|
+
i = end;
|
|
251
|
+
} else {
|
|
252
|
+
out += s.slice(i, j + 1);
|
|
253
|
+
i = j + 1;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return out;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function replaceTokens(s: string, id: Identity): string {
|
|
260
|
+
for (const t of id.tokens) s = replaceToken(s, t);
|
|
261
|
+
return s;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** An address without its instance keys, and the keys, decoded: `module.a["x"].b.c[0]` is `module.a.b.c` with `["x", "0"]`. */
|
|
265
|
+
export function splitInstanceKeys(address: string): { bare: string; keys: string[] } {
|
|
266
|
+
let bare = "";
|
|
267
|
+
const keys: string[] = [];
|
|
268
|
+
let i = 0;
|
|
269
|
+
while (i < address.length) {
|
|
270
|
+
if (address[i] !== "[") {
|
|
271
|
+
bare += address[i++];
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
let j = i + 1;
|
|
275
|
+
let quoted = false;
|
|
276
|
+
for (; j < address.length; j++) {
|
|
277
|
+
if (quoted && address[j] === "\\") j++;
|
|
278
|
+
else if (address[j] === '"') quoted = !quoted;
|
|
279
|
+
else if (!quoted && address[j] === "]") break;
|
|
280
|
+
}
|
|
281
|
+
const raw = address.slice(i + 1, Math.min(j, address.length));
|
|
282
|
+
let key = raw;
|
|
283
|
+
if (raw.startsWith('"')) {
|
|
284
|
+
try {
|
|
285
|
+
key = JSON.parse(raw) as string;
|
|
286
|
+
} catch {
|
|
287
|
+
// kept raw
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
keys.push(key);
|
|
291
|
+
i = j + 1;
|
|
292
|
+
}
|
|
293
|
+
return { bare, keys };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function normalizeAddress(address: string, id: Identity): string {
|
|
297
|
+
const parts = splitInstanceKeys(address).bare.split(".");
|
|
298
|
+
for (let i = 0; i < parts.length; i++) {
|
|
299
|
+
if (parts[i] === "module" && i + 1 < parts.length) {
|
|
300
|
+
parts[i + 1] = replaceTokens(parts[i + 1], id);
|
|
301
|
+
i++;
|
|
302
|
+
} else if (i === parts.length - 1) {
|
|
303
|
+
parts[i] = replaceTokens(parts[i], id);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return parts.join(".");
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// choudoufu's marker escaping (internal/live/markers EscapeAddress, markerkey Encode).
|
|
310
|
+
const KEY_EXTRAS = "+-=_/@.:";
|
|
311
|
+
const legalKeyRune = (r: string): boolean => /^[\p{L}\p{Nd} ]$/u.test(r) || KEY_EXTRAS.includes(r);
|
|
312
|
+
|
|
313
|
+
function encodeMarkerKey(key: string): string {
|
|
314
|
+
const runes = [...key];
|
|
315
|
+
if (!runes.some((r) => r === "+" || !legalKeyRune(r))) return key;
|
|
316
|
+
return runes.map((r) => (r === "+" ? "++" : legalKeyRune(r) ? r : "+" + r.codePointAt(0)!.toString(16).toUpperCase().padStart(6, "0"))).join("");
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const escapeMarkerKey = (key: string): string => encodeMarkerKey(key).replaceAll("@", "@@").replaceAll(".", "@d").replaceAll(":", "@c");
|
|
320
|
+
|
|
321
|
+
/** The value a `tofu-address` marker holds for `address`. */
|
|
322
|
+
export function escapeMarkerAddress(address: string): string {
|
|
323
|
+
if (!address.includes("[")) return address;
|
|
324
|
+
const runes = [...address];
|
|
325
|
+
let out = "";
|
|
326
|
+
for (let i = 0; i < runes.length; ) {
|
|
327
|
+
if (runes[i] !== "[") {
|
|
328
|
+
if (runes[i] !== "]" && runes[i] !== '"') out += runes[i];
|
|
329
|
+
i++;
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
let j = i + 1;
|
|
333
|
+
while (j < runes.length && runes[j] !== "]") j++;
|
|
334
|
+
out += ":" + escapeMarkerKey(runes.slice(i + 1, j).join("").replace(/^"+|"+$/g, ""));
|
|
335
|
+
i = j < runes.length ? j + 1 : j;
|
|
336
|
+
}
|
|
337
|
+
return out;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/** A `tofu-address` marker's value with its continuations, or undefined when there is none or the chain has a gap. */
|
|
341
|
+
function gatherMarkerAddress(map: Record<string, unknown>): string | undefined {
|
|
342
|
+
const tag = (n: number) => (n === 1 ? TAG_ADDRESS : `${TAG_ADDRESS}-${n}`);
|
|
343
|
+
const str = (k: string) => (typeof map[k] === "string" ? (map[k] as string) : undefined);
|
|
344
|
+
if (str(TAG_ADDRESS) === undefined) return undefined;
|
|
345
|
+
let out = "";
|
|
346
|
+
let n = 1;
|
|
347
|
+
for (; n <= MAX_CONTINUATIONS && str(tag(n)) !== undefined; n++) out += str(tag(n));
|
|
348
|
+
for (let m = n + 1; m <= MAX_CONTINUATIONS; m++) if (str(tag(m)) !== undefined) return undefined;
|
|
349
|
+
return out;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const isAddressKey = (k: string): boolean => k === TAG_ADDRESS || /^tofu-address-\d+$/.test(k);
|
|
353
|
+
|
|
354
|
+
function normalizeValue(v: unknown, id: Identity, ownAddress: string): unknown {
|
|
355
|
+
if (typeof v === "string") return replaceTokens(v, id);
|
|
356
|
+
if (Array.isArray(v)) return v.map((e) => normalizeValue(e, id, ownAddress));
|
|
357
|
+
if (v === null || typeof v !== "object") return v;
|
|
358
|
+
const map = v as Record<string, unknown>;
|
|
359
|
+
const own = escapeMarkerAddress(ownAddress);
|
|
360
|
+
const addressIsOwn = gatherMarkerAddress(map) === own;
|
|
361
|
+
const out: Record<string, unknown> = {};
|
|
362
|
+
for (const [k, e] of Object.entries(map)) {
|
|
363
|
+
if (typeof e === "string" && k === TAG_ESTATE && id.estate !== undefined && e === id.estate) out[k] = ESTATE;
|
|
364
|
+
else if (typeof e === "string" && addressIsOwn && isAddressKey(k)) out[k] = ADDRESS;
|
|
365
|
+
else if (typeof e === "string" && k === ADDRESS_ANNOTATION && e === own) out[k] = ADDRESS;
|
|
366
|
+
else out[k] = normalizeValue(e, id, ownAddress);
|
|
367
|
+
}
|
|
368
|
+
return out;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const SYMBOL: Record<ChangeSetAction, string> = { create: "+", update: "~", replace: "-/+", delete: "-", read: "<=", "no-op": "", forget: "forget" };
|
|
372
|
+
|
|
373
|
+
/** One entry with everything unit-specific stripped. `key` is what grouping compares. */
|
|
374
|
+
interface Normalized {
|
|
375
|
+
key: string;
|
|
376
|
+
line: string;
|
|
377
|
+
action: ChangeSetAction;
|
|
378
|
+
address: string;
|
|
379
|
+
/** Each attribute's canonical form, for naming what two changes differ in. */
|
|
380
|
+
attrs: Map<string, string>;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function normalizeAttribute(a: ChangeSetAttribute, id: Identity, ownAddress: string): Record<string, unknown> {
|
|
384
|
+
if (a.sensitive) return { sensitive: true, ...(a.forcesReplacement ? { forcesReplacement: true } : {}) };
|
|
385
|
+
return {
|
|
386
|
+
...(a.before !== undefined && a.before !== null ? { before: normalizeValue(a.before, id, ownAddress) } : {}),
|
|
387
|
+
...(a.unknown ? { after: UNKNOWN } : a.after !== undefined && a.after !== null ? { after: normalizeValue(a.after, id, ownAddress) } : {}),
|
|
388
|
+
...(a.forcesReplacement ? { forcesReplacement: true } : {}),
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function normalizeEntry(e: ChangeSetEntry, id: Identity): Normalized | undefined {
|
|
393
|
+
if (e.action === "no-op" && !e.importing) return undefined;
|
|
394
|
+
const address = normalizeAddress(e.address, id) + (e.deposed !== undefined ? " (deposed)" : "");
|
|
395
|
+
const attrs = new Map<string, string>();
|
|
396
|
+
const body: Record<string, unknown> = {};
|
|
397
|
+
if (e.action !== "delete" && e.action !== "forget") {
|
|
398
|
+
for (const a of [...e.attributes].sort((x, y) => (x.path < y.path ? -1 : x.path > y.path ? 1 : 0))) {
|
|
399
|
+
body[a.path] = normalizeAttribute(a, id, e.address);
|
|
400
|
+
attrs.set(a.path, JSON.stringify(body[a.path]));
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
const key = canonical({ type: e.type, action: e.action, address, attrs: body, ...(e.importing ? { importing: true } : {}) });
|
|
404
|
+
const names = [...attrs.keys()];
|
|
405
|
+
let line = `${SYMBOL[e.action] || "import"} ${address}`;
|
|
406
|
+
if (e.importing && e.action !== "no-op") line += " (import)";
|
|
407
|
+
if (names.length > 0 && e.action !== "create" && e.action !== "read") line += `: ${names.join(", ")}`;
|
|
408
|
+
return { key, line, action: e.action, address, attrs };
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/** Key-sorted JSON, so two equal values give one string. */
|
|
412
|
+
function canonical(v: unknown): string {
|
|
413
|
+
if (Array.isArray(v)) return `[${v.map(canonical).join(",")}]`;
|
|
414
|
+
if (v !== null && typeof v === "object") {
|
|
415
|
+
const o = v as Record<string, unknown>;
|
|
416
|
+
return `{${Object.keys(o).sort().map((k) => `${JSON.stringify(k)}:${canonical(o[k])}`).join(",")}}`;
|
|
417
|
+
}
|
|
418
|
+
return JSON.stringify(v) ?? "null";
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// ── grouping ─────────────────────────────────────────────────────────────────
|
|
422
|
+
|
|
423
|
+
interface Unit {
|
|
424
|
+
name: string;
|
|
425
|
+
family: string;
|
|
426
|
+
provisional: boolean;
|
|
427
|
+
changes: Normalized[];
|
|
428
|
+
destroys: PlanSummaryDestroy[];
|
|
429
|
+
/** Normalized triggered actions, as canonical strings. */
|
|
430
|
+
effects: string[];
|
|
431
|
+
sideEffects: PlanSummarySideEffect[];
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
interface Line extends PlanSummaryChange {
|
|
435
|
+
key: string;
|
|
436
|
+
norm: Normalized;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const byString = (a: string, b: string): number => (a < b ? -1 : a > b ? 1 : 0);
|
|
440
|
+
|
|
441
|
+
function destroyOf(e: ChangeSetEntry): PlanSummaryDestroy | undefined {
|
|
442
|
+
if (e.action !== "delete" && e.action !== "replace") return undefined;
|
|
443
|
+
return { member: e.member, address: e.address, type: e.type, action: e.action, ...(e.deposed !== undefined ? { deposed: e.deposed } : {}) };
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function effectKey(se: PlanSummarySideEffect, id: Identity): string {
|
|
447
|
+
return canonical({ type: se.type, trigger: se.trigger === undefined ? null : normalizeAddress(se.trigger, id), event: se.event ?? null });
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function lastSegment(name: string): string {
|
|
451
|
+
const parts = name.replaceAll("\\", "/").split("/").filter((p) => p !== "" && p !== ".");
|
|
452
|
+
return parts[parts.length - 1] ?? "";
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function memberUnits(doc: ChangeSetDocument, failed: Set<string>): Unit[] {
|
|
456
|
+
const byMember = new Map<string, ChangeSetEntry[]>();
|
|
457
|
+
for (const e of doc.entries) {
|
|
458
|
+
const list = byMember.get(e.member);
|
|
459
|
+
if (list) list.push(e);
|
|
460
|
+
else byMember.set(e.member, [e]);
|
|
461
|
+
}
|
|
462
|
+
const units: Unit[] = [];
|
|
463
|
+
for (const m of doc.members) {
|
|
464
|
+
if (failed.has(m.member)) continue;
|
|
465
|
+
const base = lastSegment(m.member);
|
|
466
|
+
const id = identity(m.scope, [], base && base !== m.scope ? [base] : []);
|
|
467
|
+
const unit: Unit = { name: m.member, family: "", provisional: m.provisional === true, changes: [], destroys: [], effects: [], sideEffects: [] };
|
|
468
|
+
for (const se of doc.sideEffects ?? []) {
|
|
469
|
+
if (se.member !== m.member) continue;
|
|
470
|
+
unit.effects.push(effectKey(se, id));
|
|
471
|
+
unit.sideEffects.push(se);
|
|
472
|
+
}
|
|
473
|
+
for (const e of byMember.get(m.member) ?? []) {
|
|
474
|
+
const n = normalizeEntry(e, id);
|
|
475
|
+
if (!n) continue;
|
|
476
|
+
unit.changes.push(n);
|
|
477
|
+
const d = destroyOf(e);
|
|
478
|
+
if (d) unit.destroys.push(d);
|
|
479
|
+
}
|
|
480
|
+
units.push(unit);
|
|
481
|
+
}
|
|
482
|
+
return units;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function instanceUnits(doc: ChangeSetDocument, failed: Set<string>): Unit[] {
|
|
486
|
+
const provisional = new Set(doc.members.filter((m) => m.provisional === true).map((m) => m.member));
|
|
487
|
+
const units: Unit[] = [];
|
|
488
|
+
for (const e of doc.entries) {
|
|
489
|
+
if (failed.has(e.member)) continue;
|
|
490
|
+
const { bare, keys } = splitInstanceKeys(e.address);
|
|
491
|
+
const id = identity(undefined, keys, []);
|
|
492
|
+
const n = normalizeEntry(e, id);
|
|
493
|
+
if (!n) continue;
|
|
494
|
+
const d = destroyOf(e);
|
|
495
|
+
const name = e.deposed !== undefined ? `${e.address} (deposed ${e.deposed})` : e.address;
|
|
496
|
+
const own = (doc.sideEffects ?? []).filter((se) => se.member === e.member && se.trigger === e.address);
|
|
497
|
+
units.push({ name, family: bare, provisional: provisional.has(e.member), changes: [n], destroys: d ? [d] : [], effects: own.map((se) => effectKey(se, id)), sideEffects: own });
|
|
498
|
+
}
|
|
499
|
+
return units;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function changeLines(changes: Normalized[]): Line[] {
|
|
503
|
+
const byKey = new Map<string, Line>();
|
|
504
|
+
for (const c of changes) {
|
|
505
|
+
const l = byKey.get(c.key);
|
|
506
|
+
if (l) l.count++;
|
|
507
|
+
else byKey.set(c.key, { line: c.line, action: c.action, count: 1, key: c.key, norm: c });
|
|
508
|
+
}
|
|
509
|
+
return [...byKey.values()].sort((a, b) => byString(a.line, b.line) || byString(a.key, b.key));
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/** What `have` holds beyond `base`, when it holds all of `base`. */
|
|
513
|
+
function extraLines(base: Line[], have: Line[]): Line[] | undefined {
|
|
514
|
+
const need = new Map(base.map((l) => [l.key, l.count]));
|
|
515
|
+
const plus: Line[] = [];
|
|
516
|
+
for (const l of have) {
|
|
517
|
+
const n = need.get(l.key) ?? 0;
|
|
518
|
+
if (n === 0) plus.push(l);
|
|
519
|
+
else if (l.count > n) plus.push({ ...l, count: l.count - n });
|
|
520
|
+
need.delete(l.key);
|
|
521
|
+
}
|
|
522
|
+
return [...need.values()].some((n) => n > 0) ? undefined : plus;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function markDifference(l: Line, base: { id: string; lines: Line[] }): void {
|
|
526
|
+
if (base.lines.some((b) => b.key === l.key)) return;
|
|
527
|
+
const b = base.lines.find((x) => x.norm.address === l.norm.address && x.norm.action === l.norm.action);
|
|
528
|
+
if (!b) return;
|
|
529
|
+
const names = new Set<string>();
|
|
530
|
+
for (const [k, v] of l.norm.attrs) if (b.norm.attrs.get(k) !== v) names.add(k);
|
|
531
|
+
for (const k of b.norm.attrs.keys()) if (!l.norm.attrs.has(k)) names.add(k);
|
|
532
|
+
l.differsFrom = base.id;
|
|
533
|
+
l.differsIn = [...names].sort();
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const publicLine = (l: Line): PlanSummaryChange => ({
|
|
537
|
+
line: l.line,
|
|
538
|
+
action: l.action,
|
|
539
|
+
count: l.count,
|
|
540
|
+
...(l.differsFrom !== undefined ? { differsFrom: l.differsFrom, differsIn: l.differsIn ?? [] } : {}),
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
function groupUnits(units: Unit[], perFamily: boolean): PlanSummaryGroup[] {
|
|
544
|
+
interface Acc {
|
|
545
|
+
id: string;
|
|
546
|
+
family: string;
|
|
547
|
+
provisional: boolean;
|
|
548
|
+
units: string[];
|
|
549
|
+
destroys: PlanSummaryDestroy[];
|
|
550
|
+
sideEffects: PlanSummarySideEffect[];
|
|
551
|
+
lines: Line[];
|
|
552
|
+
noChanges: boolean;
|
|
553
|
+
}
|
|
554
|
+
const byId = new Map<string, Acc>();
|
|
555
|
+
for (const u of units) {
|
|
556
|
+
const keys = u.changes.map((c) => c.key).sort(byString);
|
|
557
|
+
const effects = [...u.effects].sort(byString);
|
|
558
|
+
const id = computePlanDigest("plan-summary-group", {
|
|
559
|
+
resource: u.family,
|
|
560
|
+
changes: keys,
|
|
561
|
+
...(effects.length > 0 ? { effects } : {}),
|
|
562
|
+
...(u.provisional ? { provisional: true } : {}),
|
|
563
|
+
}).slice(PLAN_DIGEST_PREFIX.length, PLAN_DIGEST_PREFIX.length + 12);
|
|
564
|
+
let acc = byId.get(id);
|
|
565
|
+
if (!acc) {
|
|
566
|
+
acc = { id, family: u.family, provisional: u.provisional, units: [], destroys: [], sideEffects: [], lines: changeLines(u.changes), noChanges: u.changes.length === 0 && u.effects.length === 0 };
|
|
567
|
+
byId.set(id, acc);
|
|
568
|
+
}
|
|
569
|
+
acc.units.push(u.name);
|
|
570
|
+
acc.destroys.push(...u.destroys);
|
|
571
|
+
acc.sideEffects.push(...u.sideEffects);
|
|
572
|
+
}
|
|
573
|
+
const accs = [...byId.values()];
|
|
574
|
+
for (const a of accs) a.units.sort(byString);
|
|
575
|
+
// Real plans first, provisional ones after them.
|
|
576
|
+
accs.sort(
|
|
577
|
+
(a, b) =>
|
|
578
|
+
Number(a.provisional) - Number(b.provisional) ||
|
|
579
|
+
(perFamily && a.family !== b.family ? byString(a.family, b.family) : 0) ||
|
|
580
|
+
b.units.length - a.units.length ||
|
|
581
|
+
byString(a.units[0], b.units[0]),
|
|
582
|
+
);
|
|
583
|
+
|
|
584
|
+
// A family's baseline is its largest group that changes anything; the others are described against it.
|
|
585
|
+
// Provisional groups are a family of their own, never described against a real plan.
|
|
586
|
+
const familyOf = (a: Acc): string => (a.provisional ? `${a.family}\u0000provisional` : a.family);
|
|
587
|
+
const baseline = new Map<string, Acc>();
|
|
588
|
+
const familySize = new Map<string, number>();
|
|
589
|
+
for (const a of accs) {
|
|
590
|
+
familySize.set(familyOf(a), (familySize.get(familyOf(a)) ?? 0) + 1);
|
|
591
|
+
if (!baseline.has(familyOf(a)) && !a.noChanges) baseline.set(familyOf(a), a);
|
|
592
|
+
}
|
|
593
|
+
return accs.map((a) => {
|
|
594
|
+
const base = baseline.get(familyOf(a));
|
|
595
|
+
let plus: Line[] | undefined;
|
|
596
|
+
if (base && base !== a && !a.noChanges) {
|
|
597
|
+
const extra = extraLines(base.lines, a.lines);
|
|
598
|
+
if (extra && extra.length > 0) plus = extra;
|
|
599
|
+
for (const l of a.lines) markDifference(l, base);
|
|
600
|
+
for (const l of plus ?? []) markDifference(l, base);
|
|
601
|
+
}
|
|
602
|
+
return {
|
|
603
|
+
id: a.id,
|
|
604
|
+
...(a.family ? { resource: a.family } : {}),
|
|
605
|
+
units: a.units,
|
|
606
|
+
outlier: a.units.length === 1 && (familySize.get(familyOf(a)) ?? 0) > 1,
|
|
607
|
+
noChanges: a.noChanges,
|
|
608
|
+
changes: a.lines.map(publicLine),
|
|
609
|
+
...(plus && base ? { extends: base.id, plus: plus.map(publicLine) } : {}),
|
|
610
|
+
destroys: a.destroys.sort(byDestroy),
|
|
611
|
+
sideEffects: a.sideEffects.sort(bySideEffect),
|
|
612
|
+
...(a.provisional ? { provisional: true as const } : {}),
|
|
613
|
+
};
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
const byDestroy = (a: PlanSummaryDestroy, b: PlanSummaryDestroy): number =>
|
|
618
|
+
byString(a.member, b.member) || byString(a.address, b.address) || byString(a.deposed ?? "", b.deposed ?? "");
|
|
619
|
+
|
|
620
|
+
const bySideEffect = (a: PlanSummarySideEffect, b: PlanSummarySideEffect): number =>
|
|
621
|
+
byString(a.member, b.member) || byString(a.address, b.address) || byString(a.trigger ?? "", b.trigger ?? "");
|
|
622
|
+
|
|
623
|
+
const byEntry = (a: { member: string; address: string; deposed?: string }, b: { member: string; address: string; deposed?: string }): number =>
|
|
624
|
+
byString(a.member, b.member) || byString(a.address, b.address) || byString(a.deposed ?? "", b.deposed ?? "");
|
|
625
|
+
|
|
626
|
+
function failureReason(m: ChangeSetMember): string {
|
|
627
|
+
return m.error ?? "the member failed to plan";
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* Group a change-set document's units by their normalized changes. Failed
|
|
632
|
+
* members are listed with their reasons and never grouped. Every delete and
|
|
633
|
+
* replace the document holds is in `destroys`, and each also in its unit's
|
|
634
|
+
* group; every hole is in `holes`.
|
|
635
|
+
*/
|
|
636
|
+
export function groupChangeSet(doc: ChangeSetDocument, options: GroupChangeSetOptions = {}): PlanSummary {
|
|
637
|
+
const unit = options.unit ?? (doc.members.length === 1 ? "instance" : "member");
|
|
638
|
+
const failedMembers = doc.members.filter((m) => m.status === "failed");
|
|
639
|
+
const failed = new Set(failedMembers.map((m) => m.member));
|
|
640
|
+
const units = unit === "member" ? memberUnits(doc, failed) : instanceUnits(doc, failed);
|
|
641
|
+
const destroys = doc.entries.map(destroyOf).filter((d): d is PlanSummaryDestroy => d !== undefined).sort(byDestroy);
|
|
642
|
+
const holes = doc.members.flatMap((m) => m.holes.map((h) => ({ member: m.member, address: h.address, ...(h.type ? { type: h.type } : {}), reason: h.reason })));
|
|
643
|
+
return {
|
|
644
|
+
$schema: PLAN_SUMMARY_SCHEMA_ID,
|
|
645
|
+
contract: PLAN_SUMMARY_CONTRACT,
|
|
646
|
+
changeSet: doc.digest,
|
|
647
|
+
unit,
|
|
648
|
+
units: unit === "member" ? doc.members.length : units.length,
|
|
649
|
+
groups: groupUnits(units, unit === "instance"),
|
|
650
|
+
failed: failedMembers.map((m) => ({ member: m.member, ...(m.scope ? { scope: m.scope } : {}), reason: failureReason(m) })),
|
|
651
|
+
destroys,
|
|
652
|
+
imports: doc.entries
|
|
653
|
+
.filter((e) => e.importing)
|
|
654
|
+
.map((e) => ({ member: e.member, address: e.address, type: e.type, action: e.action }))
|
|
655
|
+
.sort(byEntry),
|
|
656
|
+
forgets: doc.entries
|
|
657
|
+
.filter((e) => e.action === "forget")
|
|
658
|
+
.map((e) => ({ member: e.member, address: e.address, type: e.type, ...(e.deposed !== undefined ? { deposed: e.deposed } : {}) }))
|
|
659
|
+
.sort(byEntry),
|
|
660
|
+
sideEffects: [...(doc.sideEffects ?? [])].sort(bySideEffect),
|
|
661
|
+
holes,
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// ── rendering ────────────────────────────────────────────────────────────────
|
|
666
|
+
|
|
667
|
+
const plural = (n: number, one: string, many: string): string => `${n} ${n === 1 ? one : many}`;
|
|
668
|
+
|
|
669
|
+
function unitWord(s: PlanSummary, n: number): string {
|
|
670
|
+
return s.unit === "instance" ? plural(n, "instance", "instances") : plural(n, "member", "members");
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/** The first line: units, groups, failures, destroys and holes. */
|
|
674
|
+
export function planSummaryHeadline(s: PlanSummary): string {
|
|
675
|
+
const parts = [plural(s.groups.length, "group", "groups")];
|
|
676
|
+
const provisional = s.groups.filter((g) => g.provisional).length;
|
|
677
|
+
if (provisional > 0) parts.push(`${provisional} provisional`);
|
|
678
|
+
if (s.failed.length > 0) parts.push(`${s.failed.length} failed`);
|
|
679
|
+
parts.push(plural(s.destroys.length, "destroy or replacement", "destroys or replacements"));
|
|
680
|
+
if (s.forgets.length > 0) parts.push(plural(s.forgets.length, "forget", "forgets"));
|
|
681
|
+
if (s.imports.length > 0) parts.push(plural(s.imports.length, "import", "imports"));
|
|
682
|
+
if (s.sideEffects.length > 0) parts.push(plural(s.sideEffects.length, "triggered action", "triggered actions"));
|
|
683
|
+
if (s.holes.length > 0) parts.push(plural(s.holes.length, "hole", "holes"));
|
|
684
|
+
return `${unitWord(s, s.units)}: ${parts.join(", ")}.`;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
function groupTitle(s: PlanSummary, g: PlanSummaryGroup): string {
|
|
688
|
+
let t = `Group ${g.id}: ${unitWord(s, g.units.length)}`;
|
|
689
|
+
if (g.resource) t += ` of ${g.resource}`;
|
|
690
|
+
if (g.outlier) t += " (outlier)";
|
|
691
|
+
if (g.provisional) t += " (provisional)";
|
|
692
|
+
if (g.noChanges) t += ", no changes";
|
|
693
|
+
else if (g.extends) t += `, group ${g.extends}'s change plus`;
|
|
694
|
+
else if (g.units.length > 1) t += ", identical change";
|
|
695
|
+
else t += ", change";
|
|
696
|
+
return t;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
function changeText(l: PlanSummaryChange): string {
|
|
700
|
+
let t = l.count > 1 ? `${l.line} (x${l.count})` : l.line;
|
|
701
|
+
if (l.differsFrom) t += l.differsIn && l.differsIn.length > 0 ? ` [differs from group ${l.differsFrom} in: ${l.differsIn.join(", ")}]` : ` [differs from group ${l.differsFrom}]`;
|
|
702
|
+
return t;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
const shownChanges = (g: PlanSummaryGroup): PlanSummaryChange[] => (g.extends ? (g.plus ?? []) : g.changes);
|
|
706
|
+
|
|
707
|
+
function destroyText(d: PlanSummaryDestroy): string {
|
|
708
|
+
return `${d.member}: ${d.address}${d.deposed !== undefined ? ` (deposed ${d.deposed})` : ""} (${d.action})`;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
const forgetText = (f: PlanSummaryForget): string => `${f.member}: ${f.address}${f.deposed !== undefined ? ` (deposed ${f.deposed})` : ""} (forget, the object keeps running)`;
|
|
712
|
+
const importText = (i: PlanSummaryImport): string => `${i.member}: ${i.address} (${i.action === "no-op" ? "import, no other change" : `import, then ${i.action}`})`;
|
|
713
|
+
const sideEffectText = (e: PlanSummarySideEffect): string =>
|
|
714
|
+
`${e.member}: ${e.address}${e.trigger !== undefined ? `, triggered by ${e.trigger}${e.event !== undefined ? ` ${e.event}` : ""}` : ""}`;
|
|
715
|
+
|
|
716
|
+
const PROVISIONAL_NOTE = "Provisional: planned before what it reads applied, so it may show stand-in values. No approval covers it; it is planned again in its own wave.";
|
|
717
|
+
|
|
718
|
+
const oneLine = (s: string): string => s.split(/\s+/).filter(Boolean).join(" ");
|
|
719
|
+
|
|
720
|
+
const failureLabel = (f: PlanSummaryFailure): string => (f.scope && f.scope !== f.member ? `${f.member} (${f.scope})` : f.member);
|
|
721
|
+
|
|
722
|
+
/** The summary for a terminal. Destroys, failures and holes come first, then every group. */
|
|
723
|
+
export function renderPlanSummaryText(s: PlanSummary): string {
|
|
724
|
+
const out: string[] = [planSummaryHeadline(s)];
|
|
725
|
+
if (s.destroys.length > 0) {
|
|
726
|
+
out.push("", `Destroys and replacements (${s.destroys.length}):`);
|
|
727
|
+
for (const d of s.destroys) out.push(` ${destroyText(d)}`);
|
|
728
|
+
}
|
|
729
|
+
if (s.forgets.length > 0) {
|
|
730
|
+
out.push("", `Forgets (${s.forgets.length}), no longer tracked, not destroyed:`);
|
|
731
|
+
for (const f of s.forgets) out.push(` ${forgetText(f)}`);
|
|
732
|
+
}
|
|
733
|
+
if (s.imports.length > 0) {
|
|
734
|
+
out.push("", `Imports (${s.imports.length}):`);
|
|
735
|
+
for (const i of s.imports) out.push(` ${importText(i)}`);
|
|
736
|
+
}
|
|
737
|
+
if (s.sideEffects.length > 0) {
|
|
738
|
+
out.push("", `Triggered actions (${s.sideEffects.length}), side effects of apply:`);
|
|
739
|
+
for (const e of s.sideEffects) out.push(` ${sideEffectText(e)}`);
|
|
740
|
+
}
|
|
741
|
+
if (s.failed.length > 0) {
|
|
742
|
+
out.push("", `Failed (${s.failed.length}), not grouped:`);
|
|
743
|
+
for (const f of s.failed) out.push(` ${failureLabel(f)}: ${oneLine(f.reason)}`);
|
|
744
|
+
}
|
|
745
|
+
if (s.holes.length > 0) {
|
|
746
|
+
out.push("", `Holes (${s.holes.length}), addresses the plan says nothing about:`);
|
|
747
|
+
for (const h of s.holes) out.push(` ${h.member}: ${h.address}: ${oneLine(h.reason)}`);
|
|
748
|
+
}
|
|
749
|
+
for (const g of s.groups) {
|
|
750
|
+
out.push("", `${groupTitle(s, g)}:`);
|
|
751
|
+
for (const l of shownChanges(g)) out.push(` ${changeText(l)}`);
|
|
752
|
+
out.push(` ${s.unit === "instance" ? "instances" : "members"}: ${g.units.join(", ")}`);
|
|
753
|
+
if (g.provisional) out.push(` ${PROVISIONAL_NOTE}`);
|
|
754
|
+
if (g.destroys.length > 0) out.push(` destroys and replacements: ${g.destroys.length}, listed above`);
|
|
755
|
+
if (g.sideEffects.length > 0) out.push(` triggered actions: ${g.sideEffects.length}, listed above`);
|
|
756
|
+
}
|
|
757
|
+
return out.join("\n") + "\n";
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
export interface RenderMarkdownOptions {
|
|
761
|
+
/** The most characters (code points) the note may hold. Default {@link GITHUB_COMMENT_LIMIT}. */
|
|
762
|
+
limit?: number;
|
|
763
|
+
/** The command that prints the whole summary, named when the note is cut. */
|
|
764
|
+
command?: string;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
const codePoints = (s: string): number => {
|
|
768
|
+
let n = 0;
|
|
769
|
+
for (const _ of s) n++;
|
|
770
|
+
return n;
|
|
771
|
+
};
|
|
772
|
+
|
|
773
|
+
const code = (s: string): string => "`" + s.replaceAll("`", "'") + "`";
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* The summary as an MR or PR note of at most `limit` characters.
|
|
777
|
+
*
|
|
778
|
+
* Blocks come in a fixed order: the headline, every destroy and
|
|
779
|
+
* replacement, every forget, import and triggered action, every failed member, every hole, then the groups largest
|
|
780
|
+
* first. When the whole does not fit, groups are dropped from the end, never
|
|
781
|
+
* cut in the middle, and a closing line names how many groups, units and
|
|
782
|
+
* destroys it left out. The destroy, failure and hole lines are cut only
|
|
783
|
+
* when they alone exceed the limit, from the end, and the closing line
|
|
784
|
+
* counts those too.
|
|
785
|
+
*/
|
|
786
|
+
export function renderPlanSummaryMarkdown(s: PlanSummary, options: RenderMarkdownOptions = {}): string {
|
|
787
|
+
const limit = options.limit ?? GITHUB_COMMENT_LIMIT;
|
|
788
|
+
const command = options.command ?? "chant change-set summary";
|
|
789
|
+
const header = `### Plan summary\n\n${planSummaryHeadline(s)}\n`;
|
|
790
|
+
|
|
791
|
+
interface Block {
|
|
792
|
+
text: string;
|
|
793
|
+
kind: "destroy" | "forget" | "import" | "effect" | "failed" | "hole" | "group";
|
|
794
|
+
units: number;
|
|
795
|
+
}
|
|
796
|
+
const blocks: Block[] = [];
|
|
797
|
+
if (s.destroys.length > 0) blocks.push({ kind: "destroy", units: 0, text: `\n**Destroys and replacements (${s.destroys.length}):**\n\n` });
|
|
798
|
+
for (const d of s.destroys) blocks.push({ kind: "destroy", units: 1, text: `- ${code(destroyText(d))}\n` });
|
|
799
|
+
if (s.forgets.length > 0) blocks.push({ kind: "forget", units: 0, text: `\n**Forgets (${s.forgets.length}), no longer tracked, not destroyed:**\n\n` });
|
|
800
|
+
for (const f of s.forgets) blocks.push({ kind: "forget", units: 1, text: `- ${code(forgetText(f))}\n` });
|
|
801
|
+
if (s.imports.length > 0) blocks.push({ kind: "import", units: 0, text: `\n**Imports (${s.imports.length}):**\n\n` });
|
|
802
|
+
for (const i of s.imports) blocks.push({ kind: "import", units: 1, text: `- ${code(importText(i))}\n` });
|
|
803
|
+
if (s.sideEffects.length > 0) blocks.push({ kind: "effect", units: 0, text: `\n**Triggered actions (${s.sideEffects.length}), side effects of apply:**\n\n` });
|
|
804
|
+
for (const e of s.sideEffects) blocks.push({ kind: "effect", units: 1, text: `- ${code(sideEffectText(e))}\n` });
|
|
805
|
+
if (s.failed.length > 0) blocks.push({ kind: "failed", units: 0, text: `\n**Failed (${s.failed.length}), not grouped:**\n\n` });
|
|
806
|
+
for (const f of s.failed) blocks.push({ kind: "failed", units: 1, text: `- ${code(failureLabel(f))}: ${oneLine(f.reason)}\n` });
|
|
807
|
+
if (s.holes.length > 0) blocks.push({ kind: "hole", units: 0, text: `\n**Holes (${s.holes.length}), addresses the plan says nothing about:**\n\n` });
|
|
808
|
+
for (const h of s.holes) blocks.push({ kind: "hole", units: 1, text: `- ${code(`${h.member}: ${h.address}`)}: ${oneLine(h.reason)}\n` });
|
|
809
|
+
for (const g of s.groups) {
|
|
810
|
+
let t = `\n#### ${groupTitle(s, g)}\n\n`;
|
|
811
|
+
const lines = shownChanges(g);
|
|
812
|
+
if (lines.length > 0) t += "```\n" + lines.map(changeText).join("\n") + "\n```\n\n";
|
|
813
|
+
t += `${s.unit === "instance" ? "Instances" : "Members"}: ${g.units.map(code).join(", ")}\n`;
|
|
814
|
+
if (g.provisional) t += `\n${PROVISIONAL_NOTE}\n`;
|
|
815
|
+
if (g.destroys.length > 0) t += `\nDestroys and replacements: ${g.destroys.length}, listed above.\n`;
|
|
816
|
+
if (g.sideEffects.length > 0) t += `\nTriggered actions: ${g.sideEffects.length}, listed above.\n`;
|
|
817
|
+
blocks.push({ kind: "group", units: g.units.length, text: t });
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
const lengths = blocks.map((b) => codePoints(b.text));
|
|
821
|
+
const total = codePoints(header) + lengths.reduce((a, b) => a + b, 0);
|
|
822
|
+
if (total <= limit) return header + blocks.map((b) => b.text).join("");
|
|
823
|
+
|
|
824
|
+
const notice = (kept: number): string => {
|
|
825
|
+
const cut = blocks.slice(kept);
|
|
826
|
+
const count = (k: Block["kind"]) => cut.filter((b) => b.kind === k && (k === "group" || b.units > 0)).length;
|
|
827
|
+
const parts: string[] = [];
|
|
828
|
+
if (count("destroy") > 0) parts.push(plural(count("destroy"), "destroy or replacement", "destroys or replacements"));
|
|
829
|
+
if (count("forget") > 0) parts.push(plural(count("forget"), "forget", "forgets"));
|
|
830
|
+
if (count("import") > 0) parts.push(plural(count("import"), "import", "imports"));
|
|
831
|
+
if (count("effect") > 0) parts.push(plural(count("effect"), "triggered action", "triggered actions"));
|
|
832
|
+
if (count("failed") > 0) parts.push(plural(count("failed"), "failed member", "failed members"));
|
|
833
|
+
if (count("hole") > 0) parts.push(plural(count("hole"), "hole", "holes"));
|
|
834
|
+
const groups = cut.filter((b) => b.kind === "group");
|
|
835
|
+
if (groups.length > 0) parts.push(`${plural(groups.length, "group", "groups")} (${unitWord(s, groups.reduce((n, b) => n + b.units, 0))})`);
|
|
836
|
+
return `\n**Truncated:** this note leaves out ${parts.join(", ")} to stay within ${limit} characters. \`${command}\` prints all of it.\n`;
|
|
837
|
+
};
|
|
838
|
+
let kept = blocks.length;
|
|
839
|
+
let used = total;
|
|
840
|
+
while (kept > 0 && used + codePoints(notice(kept)) > limit) {
|
|
841
|
+
kept--;
|
|
842
|
+
used -= lengths[kept];
|
|
843
|
+
}
|
|
844
|
+
return header + blocks.slice(0, kept).map((b) => b.text).join("") + notice(kept);
|
|
845
|
+
}
|