@topogram/cli 0.3.64 → 0.3.65
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/package.json +1 -1
- package/src/adoption/plan/index.js +703 -0
- package/src/adoption/plan.js +12 -703
- package/src/agent-ops/query-builders/auth.js +375 -0
- package/src/agent-ops/query-builders/change-risk/change-plan.js +123 -0
- package/src/agent-ops/query-builders/change-risk/import-plan.js +49 -0
- package/src/agent-ops/query-builders/change-risk/maintained.js +286 -0
- package/src/agent-ops/query-builders/change-risk/review-packets.js +123 -0
- package/src/agent-ops/query-builders/change-risk/risk.js +189 -0
- package/src/agent-ops/query-builders/change-risk.js +25 -0
- package/src/agent-ops/query-builders/common.js +149 -0
- package/src/agent-ops/query-builders/maintained-risk.js +539 -0
- package/src/agent-ops/query-builders/maintained-shared.js +120 -0
- package/src/agent-ops/query-builders/multi-agent.js +547 -0
- package/src/agent-ops/query-builders/projection-impacts.js +514 -0
- package/src/agent-ops/query-builders/work-packets.js +417 -0
- package/src/agent-ops/query-builders/workflow-context-shared.js +300 -0
- package/src/agent-ops/query-builders/workflow-context.js +398 -0
- package/src/agent-ops/query-builders/workflow-presets-core.js +676 -0
- package/src/agent-ops/query-builders/workflow-presets.js +341 -0
- package/src/agent-ops/query-builders.d.ts +26 -26
- package/src/agent-ops/query-builders.js +42 -5021
- package/src/catalog/constants.js +10 -0
- package/src/catalog/copy.js +60 -0
- package/src/catalog/diagnostics.js +15 -0
- package/src/catalog/entries.js +42 -0
- package/src/catalog/files.js +67 -0
- package/src/catalog/provenance.js +122 -0
- package/src/catalog/source.js +150 -0
- package/src/catalog/validation.js +252 -0
- package/src/catalog.d.ts +2 -0
- package/src/catalog.js +18 -746
- package/src/cli/commands/catalog/check.js +31 -0
- package/src/cli/commands/catalog/copy.js +59 -0
- package/src/cli/commands/catalog/doctor.js +248 -0
- package/src/cli/commands/catalog/help.js +21 -0
- package/src/cli/commands/catalog/list.js +52 -0
- package/src/cli/commands/catalog/runner.js +92 -0
- package/src/cli/commands/catalog/shared.js +17 -0
- package/src/cli/commands/catalog/show.js +134 -0
- package/src/cli/commands/catalog.js +30 -615
- package/src/cli/commands/generator-policy/package-info.js +162 -0
- package/src/cli/commands/generator-policy/payloads.js +372 -0
- package/src/cli/commands/generator-policy/printers.js +159 -0
- package/src/cli/commands/generator-policy/runner.js +81 -0
- package/src/cli/commands/generator-policy/shared.js +39 -0
- package/src/cli/commands/generator-policy.js +15 -783
- package/src/cli/commands/import/adopt.js +170 -0
- package/src/cli/commands/import/check.js +91 -0
- package/src/cli/commands/import/diff.js +84 -0
- package/src/cli/commands/import/help.js +47 -0
- package/src/cli/commands/import/paths.js +277 -0
- package/src/cli/commands/import/plan.js +284 -0
- package/src/cli/commands/import/refresh.js +470 -0
- package/src/cli/commands/import/status-history.js +196 -0
- package/src/cli/commands/import/workspace.js +230 -0
- package/src/cli/commands/import.js +33 -1732
- package/src/cli/commands/package/constants.js +17 -0
- package/src/cli/commands/package/doctor.js +240 -0
- package/src/cli/commands/package/help.js +27 -0
- package/src/cli/commands/package/lockfile.js +135 -0
- package/src/cli/commands/package/npm.js +97 -0
- package/src/cli/commands/package/reporting.js +35 -0
- package/src/cli/commands/package/runner.js +33 -0
- package/src/cli/commands/package/shared.js +9 -0
- package/src/cli/commands/package/update-cli.js +252 -0
- package/src/cli/commands/package/versions.js +35 -0
- package/src/cli/commands/package.js +29 -813
- package/src/cli/commands/query/change-plan.js +68 -0
- package/src/cli/commands/query/definitions.js +202 -0
- package/src/cli/commands/query/import-adopt.js +121 -0
- package/src/cli/commands/query/runner/artifacts.js +102 -0
- package/src/cli/commands/query/runner/boundaries.js +211 -0
- package/src/cli/commands/query/runner/change.js +182 -0
- package/src/cli/commands/query/runner/import-adopt.js +111 -0
- package/src/cli/commands/query/runner/index.js +31 -0
- package/src/cli/commands/query/runner/output.js +12 -0
- package/src/cli/commands/query/runner/workflow.js +241 -0
- package/src/cli/commands/query/runner.js +3 -0
- package/src/cli/commands/query/workflow-context.js +5 -0
- package/src/cli/commands/query/workspace.js +274 -0
- package/src/cli/commands/query.js +9 -1300
- package/src/cli/commands/template/baseline.js +100 -0
- package/src/cli/commands/template/check.js +466 -0
- package/src/cli/commands/template/constants.js +8 -0
- package/src/cli/commands/template/diagnostics.js +26 -0
- package/src/cli/commands/template/help.js +28 -0
- package/src/cli/commands/template/lifecycle.js +404 -0
- package/src/cli/commands/template/list-show.js +287 -0
- package/src/cli/commands/template/policy.js +422 -0
- package/src/cli/commands/template/shared.js +127 -0
- package/src/cli/commands/template/updates.js +352 -0
- package/src/cli/commands/template.js +41 -2143
- package/src/generator/api/contracts.js +497 -0
- package/src/generator/api/metadata.js +221 -0
- package/src/generator/api/openapi.js +559 -0
- package/src/generator/api/schema.js +124 -0
- package/src/generator/api/types.d.ts +98 -0
- package/src/generator/api.js +3 -1195
- package/src/generator/context/shared/domain-sdlc.js +282 -0
- package/src/generator/context/shared/maintained-boundary.js +665 -0
- package/src/generator/context/shared/metrics.js +85 -0
- package/src/generator/context/shared/primitives.js +64 -0
- package/src/generator/context/shared/relationships.js +453 -0
- package/src/generator/context/shared/summaries.js +263 -0
- package/src/generator/context/shared/types.d.ts +207 -0
- package/src/generator/context/shared.d.ts +42 -0
- package/src/generator/context/shared.js +80 -1390
- package/src/generator/context/slice/core.js +397 -0
- package/src/generator/context/slice/sdlc.js +417 -0
- package/src/generator/context/slice/ui-packets.js +183 -0
- package/src/generator/context/slice.js +2 -859
- package/src/generator/registry/index.js +507 -0
- package/src/generator/registry.js +18 -504
- package/src/generator/runtime/environment/index.js +666 -0
- package/src/generator/runtime/environment.js +4 -666
- package/src/generator/runtime/runtime-check/index.js +554 -0
- package/src/generator/runtime/runtime-check.js +4 -554
- package/src/generator/runtime/shared/index.js +572 -0
- package/src/generator/runtime/shared.js +19 -570
- package/src/generator/shared.d.ts +2 -0
- package/src/generator/surfaces/shared.d.ts +3 -0
- package/src/generator/widget-conformance/behavior-report.js +258 -0
- package/src/generator/widget-conformance/checks.js +371 -0
- package/src/generator/widget-conformance/projection-context.js +200 -0
- package/src/generator/widget-conformance/report.js +166 -0
- package/src/generator/widget-conformance/types.d.ts +121 -0
- package/src/generator/widget-conformance.js +3 -824
- package/src/import/core/context.d.ts +3 -0
- package/src/import/core/contracts.d.ts +1 -0
- package/src/import/core/registry.d.ts +4 -0
- package/src/import/core/runner/candidates.js +217 -0
- package/src/import/core/runner/options.js +22 -0
- package/src/import/core/runner/reports.js +50 -0
- package/src/import/core/runner/run.js +79 -0
- package/src/import/core/runner/tracks.js +150 -0
- package/src/import/core/runner/ui-drafts.js +337 -0
- package/src/import/core/runner.js +3 -698
- package/src/import/core/shared/api-routes.js +221 -0
- package/src/import/core/shared/candidates.js +97 -0
- package/src/import/core/shared/files.js +177 -0
- package/src/import/core/shared/next-app.js +389 -0
- package/src/import/core/shared/types.d.ts +51 -0
- package/src/import/core/shared/ui-routes.js +230 -0
- package/src/import/core/shared.js +60 -861
- package/src/new-project/constants.js +128 -0
- package/src/new-project/create.js +83 -0
- package/src/new-project/json.js +28 -0
- package/src/new-project/metadata.js +96 -0
- package/src/new-project/package-spec.js +161 -0
- package/src/new-project/project-files.js +348 -0
- package/src/new-project/template-policy.js +269 -0
- package/src/new-project/template-resolution.js +368 -0
- package/src/new-project/template-snapshots.js +430 -0
- package/src/new-project/template-updates.js +512 -0
- package/src/new-project/types.d.ts +83 -0
- package/src/new-project.js +6 -2277
- package/src/parser.d.ts +87 -1
- package/src/parser.js +118 -0
- package/src/policy/review-boundaries.d.ts +15 -0
- package/src/project-config/index.js +564 -0
- package/src/project-config.js +19 -561
- package/src/resolver/enrich/acceptance-criterion.js +2 -0
- package/src/resolver/enrich/bug.js +2 -0
- package/src/resolver/enrich/pitch.js +2 -0
- package/src/resolver/enrich/requirement.js +2 -0
- package/src/resolver/enrich/task.js +2 -0
- package/src/resolver/index.js +19 -2089
- package/src/resolver/normalize.js +384 -1
- package/src/resolver/plans.js +168 -0
- package/src/resolver/projections-api.js +494 -0
- package/src/resolver/projections-db.js +133 -0
- package/src/resolver/projections-ui.js +317 -0
- package/src/resolver/shapes.js +251 -0
- package/src/resolver/shared.js +278 -0
- package/src/resolver/widgets.js +132 -0
- package/src/template-trust/constants.js +62 -0
- package/src/template-trust/content.js +258 -0
- package/src/template-trust/diff.js +92 -0
- package/src/template-trust/policy.js +61 -0
- package/src/template-trust/record.js +90 -0
- package/src/template-trust/status.js +182 -0
- package/src/template-trust.js +24 -687
- package/src/text-helpers.d.ts +1 -0
- package/src/topogram-types.d.ts +69 -0
- package/src/validator/common.js +488 -0
- package/src/validator/data-model.js +237 -0
- package/src/validator/docs.js +167 -0
- package/src/validator/expressions.js +146 -1
- package/src/validator/index.d.ts +23 -0
- package/src/validator/index.js +32 -3585
- package/src/validator/kinds.d.ts +41 -0
- package/src/validator/kinds.js +2 -0
- package/src/validator/model-helpers.js +46 -0
- package/src/validator/per-kind/acceptance-criterion.js +5 -0
- package/src/validator/per-kind/bug.js +6 -0
- package/src/validator/per-kind/domain.js +15 -2
- package/src/validator/per-kind/pitch.js +7 -0
- package/src/validator/per-kind/requirement.js +5 -0
- package/src/validator/per-kind/task.js +7 -0
- package/src/validator/per-kind/widget.js +14 -0
- package/src/validator/projections/api-http-async.js +410 -0
- package/src/validator/projections/api-http-authz.js +88 -0
- package/src/validator/projections/api-http-core.js +205 -0
- package/src/validator/projections/api-http-policies.js +339 -0
- package/src/validator/projections/api-http-responses.js +233 -0
- package/src/validator/projections/api-http.js +44 -0
- package/src/validator/projections/db.js +353 -0
- package/src/validator/projections/generator-defaults.js +45 -0
- package/src/validator/projections/helpers.js +87 -0
- package/src/validator/projections/ui-helpers.js +214 -0
- package/src/validator/projections/ui-navigation.js +344 -0
- package/src/validator/projections/ui-structure.js +364 -0
- package/src/validator/projections/ui-widgets.js +493 -0
- package/src/validator/projections/ui.js +46 -0
- package/src/validator/registry.js +48 -1
- package/src/validator/utils.d.ts +20 -0
- package/src/validator/utils.js +115 -12
- package/src/widget-behavior.d.ts +1 -0
- package/src/workflows/import-app/api/collect.js +221 -0
- package/src/workflows/import-app/api/openapi.js +257 -0
- package/src/workflows/import-app/api/routes.js +327 -0
- package/src/workflows/import-app/api/sources.js +22 -0
- package/src/workflows/import-app/api.js +2 -797
- package/src/workflows/reconcile/adoption-plan/build.js +208 -0
- package/src/workflows/reconcile/adoption-plan/dependencies.js +75 -0
- package/src/workflows/reconcile/adoption-plan/outputs.js +143 -0
- package/src/workflows/reconcile/adoption-plan/paths.js +58 -0
- package/src/workflows/reconcile/adoption-plan/projection-patches.js +177 -0
- package/src/workflows/reconcile/adoption-plan/reasons.js +107 -0
- package/src/workflows/reconcile/adoption-plan.js +30 -740
- package/src/workflows/reconcile/auth/closures.js +115 -0
- package/src/workflows/reconcile/auth/formatters.js +142 -0
- package/src/workflows/reconcile/auth/inference.js +330 -0
- package/src/workflows/reconcile/auth/roles.js +122 -0
- package/src/workflows/reconcile/auth.js +35 -690
- package/src/workflows/reconcile/bundle-core/index.js +600 -0
- package/src/workflows/reconcile/bundle-core.js +12 -598
- package/src/workflows/reconcile/canonical-surface.js +1 -1
- package/src/workflows/reconcile/impacts/adoption-plan.js +192 -0
- package/src/workflows/reconcile/impacts/indexes.js +101 -0
- package/src/workflows/reconcile/impacts/patches.js +252 -0
- package/src/workflows/reconcile/impacts/reports.js +80 -0
- package/src/workflows/reconcile/impacts.js +14 -623
- package/src/workspace-docs.d.ts +29 -0
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
import { buildTopogramSourceStatus } from "../../../catalog.js";
|
|
7
|
+
import { stableStringify } from "../../../format.js";
|
|
8
|
+
import {
|
|
9
|
+
getTemplateTrustStatus,
|
|
10
|
+
TEMPLATE_TRUST_FILE
|
|
11
|
+
} from "../../../template-trust.js";
|
|
12
|
+
import { TEMPLATE_FILES_MANIFEST, TEMPLATE_POLICY_FILE } from "./constants.js";
|
|
13
|
+
import { buildTemplateOwnedBaselineStatus } from "./baseline.js";
|
|
14
|
+
import { latestTemplateInfo, templateMetadataFromProjectConfig } from "./shared.js";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {{ config: Record<string, any>, configPath: string|null, configDir: string }} projectConfigInfo
|
|
18
|
+
* @param {{ latest?: boolean }} [options]
|
|
19
|
+
* @returns {{ ok: boolean, template: ReturnType<typeof templateMetadataFromProjectConfig>, trust: ReturnType<typeof getTemplateTrustStatus>|null, latest: { checked: boolean, supported?: boolean, packageName?: string|null, version?: string|null, isCurrent?: boolean|null, candidateSpec?: string|null, reason: string|null }, recommendations: string[] }}
|
|
20
|
+
*/
|
|
21
|
+
export function buildTemplateStatusPayload(projectConfigInfo, options = {}) {
|
|
22
|
+
const template = templateMetadataFromProjectConfig(projectConfigInfo.config);
|
|
23
|
+
const recommendations = [];
|
|
24
|
+
/** @type {ReturnType<typeof getTemplateTrustStatus>|null} */
|
|
25
|
+
let trust = null;
|
|
26
|
+
if (projectConfigInfo.config.implementation) {
|
|
27
|
+
trust = getTemplateTrustStatus({
|
|
28
|
+
config: projectConfigInfo.config.implementation,
|
|
29
|
+
configPath: projectConfigInfo.configPath,
|
|
30
|
+
configDir: projectConfigInfo.configDir
|
|
31
|
+
}, projectConfigInfo.config);
|
|
32
|
+
if (!trust.ok) {
|
|
33
|
+
recommendations.push("Run `topogram trust diff` to review implementation changes, then `topogram trust template` to trust the current files.");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (!template.id) {
|
|
37
|
+
recommendations.push("No template metadata found in topogram.project.json.");
|
|
38
|
+
}
|
|
39
|
+
const latest = options.latest
|
|
40
|
+
? latestTemplateInfo(template)
|
|
41
|
+
: {
|
|
42
|
+
checked: false,
|
|
43
|
+
supported: false,
|
|
44
|
+
packageName: null,
|
|
45
|
+
version: null,
|
|
46
|
+
isCurrent: null,
|
|
47
|
+
candidateSpec: null,
|
|
48
|
+
reason: "Registry lookups are not performed by default."
|
|
49
|
+
};
|
|
50
|
+
if (latest.checked && latest.supported && latest.candidateSpec && latest.isCurrent === false) {
|
|
51
|
+
recommendations.push(`Run \`topogram template update --recommend --template ${latest.candidateSpec}\` to review the latest template.`);
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
ok: trust ? trust.ok : true,
|
|
55
|
+
template,
|
|
56
|
+
trust,
|
|
57
|
+
latest,
|
|
58
|
+
recommendations
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @param {ReturnType<typeof buildTemplateStatusPayload>} payload
|
|
64
|
+
* @returns {void}
|
|
65
|
+
*/
|
|
66
|
+
export function printTemplateStatus(payload) {
|
|
67
|
+
if (!payload.template.id) {
|
|
68
|
+
console.log("Template status: detached");
|
|
69
|
+
} else if (payload.trust?.requiresTrust) {
|
|
70
|
+
console.log(`Template status: attached; implementation trust: ${payload.ok ? "trusted" : "review required"}`);
|
|
71
|
+
} else {
|
|
72
|
+
console.log("Template status: attached; implementation trust: not required");
|
|
73
|
+
}
|
|
74
|
+
if (payload.template.id) {
|
|
75
|
+
console.log(`Template: ${payload.template.id}@${payload.template.version || "unknown"}`);
|
|
76
|
+
}
|
|
77
|
+
if (payload.template.source) {
|
|
78
|
+
console.log(`Source: ${payload.template.source}`);
|
|
79
|
+
}
|
|
80
|
+
if (payload.template.sourceSpec) {
|
|
81
|
+
console.log(`Source spec: ${payload.template.sourceSpec}`);
|
|
82
|
+
}
|
|
83
|
+
if (payload.template.requested) {
|
|
84
|
+
console.log(`Requested: ${payload.template.requested}`);
|
|
85
|
+
}
|
|
86
|
+
if (payload.template.catalog) {
|
|
87
|
+
console.log(`Catalog: ${payload.template.catalog.id || "unknown"} from ${payload.template.catalog.source || "unknown"}`);
|
|
88
|
+
}
|
|
89
|
+
if (payload.template.sourceRoot) {
|
|
90
|
+
console.log(`Source root: ${payload.template.sourceRoot}`);
|
|
91
|
+
}
|
|
92
|
+
if (!payload.latest.checked) {
|
|
93
|
+
console.log("Latest version: not checked");
|
|
94
|
+
} else if (!payload.latest.supported) {
|
|
95
|
+
console.log(`Latest version: not checked (${payload.latest.reason})`);
|
|
96
|
+
} else {
|
|
97
|
+
console.log(`Latest version: ${payload.latest.version}`);
|
|
98
|
+
if (payload.latest.packageName) {
|
|
99
|
+
console.log(`Latest package: ${payload.latest.packageName}`);
|
|
100
|
+
}
|
|
101
|
+
if (payload.latest.candidateSpec) {
|
|
102
|
+
console.log(`Latest candidate: ${payload.latest.candidateSpec}`);
|
|
103
|
+
}
|
|
104
|
+
console.log(`Latest status: ${payload.latest.isCurrent ? "current" : "update available"}`);
|
|
105
|
+
}
|
|
106
|
+
if (payload.trust) {
|
|
107
|
+
if (payload.trust.trustRecord?.trustedAt) {
|
|
108
|
+
console.log(`Trusted at: ${payload.trust.trustRecord.trustedAt}`);
|
|
109
|
+
}
|
|
110
|
+
if (payload.trust.implementation.module) {
|
|
111
|
+
console.log(`Implementation: ${payload.trust.implementation.module}`);
|
|
112
|
+
}
|
|
113
|
+
if (payload.trust.content.trustedDigest) {
|
|
114
|
+
console.log(`Trusted digest: ${payload.trust.content.trustedDigest}`);
|
|
115
|
+
}
|
|
116
|
+
if (payload.trust.content.currentDigest) {
|
|
117
|
+
console.log(`Current digest: ${payload.trust.content.currentDigest}`);
|
|
118
|
+
}
|
|
119
|
+
for (const issue of payload.trust.issues) {
|
|
120
|
+
console.log(`Issue: ${issue}`);
|
|
121
|
+
}
|
|
122
|
+
for (const filePath of payload.trust.content.changed) {
|
|
123
|
+
console.log(`Changed: ${filePath}`);
|
|
124
|
+
}
|
|
125
|
+
for (const filePath of payload.trust.content.added) {
|
|
126
|
+
console.log(`Added: ${filePath}`);
|
|
127
|
+
}
|
|
128
|
+
for (const filePath of payload.trust.content.removed) {
|
|
129
|
+
console.log(`Removed: ${filePath}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
for (const recommendation of payload.recommendations) {
|
|
133
|
+
console.log(recommendation);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* @param {{ config: Record<string, any>, configPath: string|null, configDir: string }} projectConfigInfo
|
|
139
|
+
* @returns {{ ok: boolean, projectRoot: string, projectConfigPath: string|null, attached: boolean, ownership: "template-attached"|"project-owned", template: ReturnType<typeof templateMetadataFromProjectConfig>, trust: ReturnType<typeof getTemplateTrustStatus>|null, baseline: ReturnType<typeof buildTemplateOwnedBaselineStatus>, source: ReturnType<typeof buildTopogramSourceStatus>, commands: { status: string, detachDryRun: string|null, detach: string|null, updateCheck: string|null, trustStatus: string|null, trustTemplate: string|null, check: string, generate: string }, summary: string[], diagnostics: any[], errors: string[] }}
|
|
140
|
+
*/
|
|
141
|
+
export function buildTemplateExplainPayload(projectConfigInfo) {
|
|
142
|
+
const template = templateMetadataFromProjectConfig(projectConfigInfo.config);
|
|
143
|
+
const attached = Boolean(template.id);
|
|
144
|
+
const projectRoot = projectConfigInfo.configDir;
|
|
145
|
+
const baseline = buildTemplateOwnedBaselineStatus(projectRoot);
|
|
146
|
+
const source = buildTopogramSourceStatus(projectRoot);
|
|
147
|
+
/** @type {ReturnType<typeof getTemplateTrustStatus>|null} */
|
|
148
|
+
let trust = null;
|
|
149
|
+
if (projectConfigInfo.config.implementation) {
|
|
150
|
+
trust = getTemplateTrustStatus({
|
|
151
|
+
config: projectConfigInfo.config.implementation,
|
|
152
|
+
configPath: projectConfigInfo.configPath,
|
|
153
|
+
configDir: projectConfigInfo.configDir
|
|
154
|
+
}, projectConfigInfo.config);
|
|
155
|
+
}
|
|
156
|
+
const summary = [];
|
|
157
|
+
if (attached) {
|
|
158
|
+
summary.push("This project is still attached to its starter template.");
|
|
159
|
+
summary.push("Local edits are allowed; template update checks are opt-in.");
|
|
160
|
+
} else {
|
|
161
|
+
summary.push("This project is detached from starter-template update tracking.");
|
|
162
|
+
summary.push("The project owns its Topogram files and template updates no longer apply.");
|
|
163
|
+
}
|
|
164
|
+
if (baseline.state === "diverged") {
|
|
165
|
+
summary.push("Template-derived files have local changes; those changes are project-owned.");
|
|
166
|
+
} else if (baseline.state === "matches-template") {
|
|
167
|
+
summary.push("Template-derived files still match the recorded template baseline.");
|
|
168
|
+
}
|
|
169
|
+
if (trust?.requiresTrust && trust.ok) {
|
|
170
|
+
summary.push("Executable implementation trust is retained and currently matches reviewed files.");
|
|
171
|
+
} else if (trust?.requiresTrust && !trust.ok) {
|
|
172
|
+
summary.push("Executable implementation changed since it was trusted and needs review.");
|
|
173
|
+
} else {
|
|
174
|
+
summary.push("No executable implementation trust review is required.");
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
ok: trust ? trust.ok : true,
|
|
178
|
+
projectRoot,
|
|
179
|
+
projectConfigPath: projectConfigInfo.configPath,
|
|
180
|
+
attached,
|
|
181
|
+
ownership: attached ? "template-attached" : "project-owned",
|
|
182
|
+
template,
|
|
183
|
+
trust,
|
|
184
|
+
baseline,
|
|
185
|
+
source,
|
|
186
|
+
commands: {
|
|
187
|
+
status: "topogram source status --local",
|
|
188
|
+
detachDryRun: attached ? "topogram template detach --dry-run" : null,
|
|
189
|
+
detach: attached ? "topogram template detach" : null,
|
|
190
|
+
updateCheck: attached ? "topogram template update --check" : null,
|
|
191
|
+
trustStatus: trust?.requiresTrust ? "topogram trust status" : null,
|
|
192
|
+
trustTemplate: trust?.requiresTrust && !trust.ok ? "topogram trust template" : null,
|
|
193
|
+
check: "topogram check",
|
|
194
|
+
generate: "topogram generate"
|
|
195
|
+
},
|
|
196
|
+
summary,
|
|
197
|
+
diagnostics: source.diagnostics,
|
|
198
|
+
errors: trust && !trust.ok ? trust.issues : []
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* @param {ReturnType<typeof buildTemplateExplainPayload>} payload
|
|
204
|
+
* @returns {void}
|
|
205
|
+
*/
|
|
206
|
+
export function printTemplateExplain(payload) {
|
|
207
|
+
console.log(`Template lifecycle: ${payload.attached ? "attached" : "detached"}`);
|
|
208
|
+
console.log(`Ownership: ${payload.ownership}`);
|
|
209
|
+
console.log(`Project: ${payload.projectRoot}`);
|
|
210
|
+
if (payload.projectConfigPath) {
|
|
211
|
+
console.log(`Project config: ${payload.projectConfigPath}`);
|
|
212
|
+
}
|
|
213
|
+
if (payload.template.id) {
|
|
214
|
+
console.log(`Template: ${payload.template.id}@${payload.template.version || "unknown"}`);
|
|
215
|
+
console.log(`Requested: ${payload.template.requested || "unknown"}`);
|
|
216
|
+
console.log(`Source: ${payload.template.sourceSpec || payload.template.source || "unknown"}`);
|
|
217
|
+
if (payload.template.catalog) {
|
|
218
|
+
console.log(`Catalog: ${payload.template.catalog.id || "unknown"} from ${payload.template.catalog.source || "unknown"}`);
|
|
219
|
+
}
|
|
220
|
+
} else {
|
|
221
|
+
console.log("Template: none");
|
|
222
|
+
}
|
|
223
|
+
console.log(`Template baseline: ${payload.baseline.state}`);
|
|
224
|
+
console.log(`Template baseline meaning: ${payload.baseline.meaning}`);
|
|
225
|
+
if (payload.baseline.content.changed.length > 0) {
|
|
226
|
+
console.log(`Template baseline changed files: ${payload.baseline.content.changed.length}`);
|
|
227
|
+
}
|
|
228
|
+
if (payload.baseline.content.removed.length > 0) {
|
|
229
|
+
console.log(`Template baseline removed files: ${payload.baseline.content.removed.length}`);
|
|
230
|
+
}
|
|
231
|
+
if (payload.trust) {
|
|
232
|
+
console.log(`Implementation trust: ${payload.trust.requiresTrust ? (payload.trust.ok ? "trusted" : "review required") : "not required"}`);
|
|
233
|
+
if (payload.trust.implementation.module) {
|
|
234
|
+
console.log(`Implementation: ${payload.trust.implementation.module}`);
|
|
235
|
+
}
|
|
236
|
+
} else {
|
|
237
|
+
console.log("Implementation trust: not required");
|
|
238
|
+
}
|
|
239
|
+
console.log("");
|
|
240
|
+
console.log("Summary:");
|
|
241
|
+
for (const line of payload.summary) {
|
|
242
|
+
console.log(`- ${line}`);
|
|
243
|
+
}
|
|
244
|
+
console.log("");
|
|
245
|
+
console.log("Useful commands:");
|
|
246
|
+
console.log(` ${payload.commands.status}`);
|
|
247
|
+
if (payload.commands.detachDryRun) {
|
|
248
|
+
console.log(` ${payload.commands.detachDryRun}`);
|
|
249
|
+
}
|
|
250
|
+
if (payload.commands.detach) {
|
|
251
|
+
console.log(` ${payload.commands.detach}`);
|
|
252
|
+
}
|
|
253
|
+
if (payload.commands.updateCheck) {
|
|
254
|
+
console.log(` ${payload.commands.updateCheck}`);
|
|
255
|
+
}
|
|
256
|
+
if (payload.commands.trustStatus) {
|
|
257
|
+
console.log(` ${payload.commands.trustStatus}`);
|
|
258
|
+
}
|
|
259
|
+
if (payload.commands.trustTemplate) {
|
|
260
|
+
console.log(` ${payload.commands.trustTemplate}`);
|
|
261
|
+
}
|
|
262
|
+
console.log(` ${payload.commands.check}`);
|
|
263
|
+
console.log(` ${payload.commands.generate}`);
|
|
264
|
+
for (const diagnostic of payload.diagnostics) {
|
|
265
|
+
const label = diagnostic.severity === "warning" ? "Warning" : "Error";
|
|
266
|
+
console.log(`${label}: ${diagnostic.message}`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* @param {{ config: Record<string, any>, configPath: string|null, configDir: string }} projectConfigInfo
|
|
272
|
+
* @param {{ dryRun?: boolean, removePolicy?: boolean }} [options]
|
|
273
|
+
* @returns {{ ok: boolean, detached: boolean, dryRun: boolean, projectConfigPath: string, removedTemplate: Record<string, any>|null, implementationTrust: { retained: boolean, removed: boolean, path: string, reason: string }, removedFiles: string[], plannedRemovals: string[], preservedFiles: string[], diagnostics: any[], errors: any[] }}
|
|
274
|
+
*/
|
|
275
|
+
export function buildTemplateDetachPayload(projectConfigInfo, options = {}) {
|
|
276
|
+
const dryRun = Boolean(options.dryRun);
|
|
277
|
+
const removePolicy = Boolean(options.removePolicy);
|
|
278
|
+
const projectRoot = projectConfigInfo.configDir;
|
|
279
|
+
const projectConfigPath = projectConfigInfo.configPath || path.join(projectRoot, "topogram.project.json");
|
|
280
|
+
const nextConfig = JSON.parse(JSON.stringify(projectConfigInfo.config || {}));
|
|
281
|
+
const removedTemplate = nextConfig.template && typeof nextConfig.template === "object" && !Array.isArray(nextConfig.template)
|
|
282
|
+
? nextConfig.template
|
|
283
|
+
: null;
|
|
284
|
+
const removedFiles = [];
|
|
285
|
+
const plannedRemovals = [];
|
|
286
|
+
const preservedFiles = [];
|
|
287
|
+
const diagnostics = [];
|
|
288
|
+
|
|
289
|
+
if (removedTemplate) {
|
|
290
|
+
delete nextConfig.template;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const manifestPath = path.join(projectRoot, TEMPLATE_FILES_MANIFEST);
|
|
294
|
+
const policyPath = path.join(projectRoot, TEMPLATE_POLICY_FILE);
|
|
295
|
+
const trustPath = path.join(projectRoot, TEMPLATE_TRUST_FILE);
|
|
296
|
+
const implementationRemains = Boolean(projectConfigInfo.config?.implementation);
|
|
297
|
+
|
|
298
|
+
/** @param {string} filePath */
|
|
299
|
+
const maybeRemove = (filePath) => {
|
|
300
|
+
if (!fs.existsSync(filePath)) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
plannedRemovals.push(filePath);
|
|
304
|
+
if (!dryRun) {
|
|
305
|
+
fs.rmSync(filePath);
|
|
306
|
+
removedFiles.push(filePath);
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
maybeRemove(manifestPath);
|
|
311
|
+
if (removePolicy) {
|
|
312
|
+
maybeRemove(policyPath);
|
|
313
|
+
} else if (fs.existsSync(policyPath)) {
|
|
314
|
+
preservedFiles.push(policyPath);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const implementationTrust = {
|
|
318
|
+
retained: false,
|
|
319
|
+
removed: false,
|
|
320
|
+
path: trustPath,
|
|
321
|
+
reason: "not-present"
|
|
322
|
+
};
|
|
323
|
+
if (fs.existsSync(trustPath)) {
|
|
324
|
+
if (implementationRemains) {
|
|
325
|
+
implementationTrust.retained = true;
|
|
326
|
+
implementationTrust.reason = "implementation-remains";
|
|
327
|
+
preservedFiles.push(trustPath);
|
|
328
|
+
} else {
|
|
329
|
+
implementationTrust.removed = !dryRun;
|
|
330
|
+
implementationTrust.reason = "no-implementation-config";
|
|
331
|
+
plannedRemovals.push(trustPath);
|
|
332
|
+
if (!dryRun) {
|
|
333
|
+
fs.rmSync(trustPath);
|
|
334
|
+
removedFiles.push(trustPath);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (!removedTemplate) {
|
|
340
|
+
diagnostics.push({
|
|
341
|
+
code: "template_already_detached",
|
|
342
|
+
severity: "warning",
|
|
343
|
+
message: "topogram.project.json has no template metadata.",
|
|
344
|
+
path: projectConfigPath,
|
|
345
|
+
suggestedFix: "No detach action is required."
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (!dryRun && removedTemplate) {
|
|
350
|
+
fs.writeFileSync(projectConfigPath, `${stableStringify(nextConfig)}\n`, "utf8");
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return {
|
|
354
|
+
ok: true,
|
|
355
|
+
detached: Boolean(removedTemplate),
|
|
356
|
+
dryRun,
|
|
357
|
+
projectConfigPath,
|
|
358
|
+
removedTemplate,
|
|
359
|
+
implementationTrust,
|
|
360
|
+
removedFiles,
|
|
361
|
+
plannedRemovals,
|
|
362
|
+
preservedFiles,
|
|
363
|
+
diagnostics,
|
|
364
|
+
errors: []
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* @param {ReturnType<typeof buildTemplateDetachPayload>} payload
|
|
370
|
+
* @returns {void}
|
|
371
|
+
*/
|
|
372
|
+
export function printTemplateDetachPayload(payload) {
|
|
373
|
+
if (payload.dryRun) {
|
|
374
|
+
console.log(payload.detached ? "Template detach plan ready." : "Template detach plan: already detached.");
|
|
375
|
+
} else {
|
|
376
|
+
console.log(payload.detached ? "Template detached." : "Template already detached.");
|
|
377
|
+
}
|
|
378
|
+
console.log(`Project config: ${payload.projectConfigPath}`);
|
|
379
|
+
if (payload.removedTemplate?.id) {
|
|
380
|
+
console.log(`Removed template metadata: ${payload.removedTemplate.id}@${payload.removedTemplate.version || "unknown"}`);
|
|
381
|
+
}
|
|
382
|
+
if (payload.plannedRemovals.length > 0) {
|
|
383
|
+
console.log(payload.dryRun ? "Would remove:" : "Removed:");
|
|
384
|
+
for (const filePath of (payload.dryRun ? payload.plannedRemovals : payload.removedFiles)) {
|
|
385
|
+
console.log(`- ${filePath}`);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
if (payload.preservedFiles.length > 0) {
|
|
389
|
+
console.log("Preserved:");
|
|
390
|
+
for (const filePath of payload.preservedFiles) {
|
|
391
|
+
console.log(`- ${filePath}`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
if (payload.implementationTrust.retained) {
|
|
395
|
+
console.log("Implementation trust retained because implementation config remains.");
|
|
396
|
+
} else if (payload.implementationTrust.removed) {
|
|
397
|
+
console.log("Implementation trust removed because no implementation config remains.");
|
|
398
|
+
}
|
|
399
|
+
for (const diagnostic of payload.diagnostics) {
|
|
400
|
+
const label = diagnostic.severity === "warning" ? "Warning" : "Error";
|
|
401
|
+
console.log(`${label}: ${diagnostic.message}`);
|
|
402
|
+
}
|
|
403
|
+
console.log("Next: run `topogram source status --local`, then `topogram check`.");
|
|
404
|
+
}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
catalogSourceOrDefault,
|
|
5
|
+
catalogTemplateListItem,
|
|
6
|
+
isCatalogSourceDisabled,
|
|
7
|
+
loadCatalog
|
|
8
|
+
} from "../../../catalog.js";
|
|
9
|
+
import {
|
|
10
|
+
buildCatalogShowPayload,
|
|
11
|
+
catalogShowCommands,
|
|
12
|
+
shellCommandArg
|
|
13
|
+
} from "../catalog.js";
|
|
14
|
+
import { messageFromError, packageNameFromPackageSpec } from "./shared.js";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {{ catalogSource?: string|null }} [options]
|
|
18
|
+
* @returns {{ ok: boolean, catalog: { source: string|null, loaded: boolean }, templates: Array<Record<string, any>>, diagnostics: Array<Record<string, any>>, errors: string[] }}
|
|
19
|
+
*/
|
|
20
|
+
export function buildTemplateListPayload(options = {}) {
|
|
21
|
+
const catalogSource = catalogSourceOrDefault(options.catalogSource || null);
|
|
22
|
+
/** @type {Array<Record<string, any>>} */
|
|
23
|
+
const templates = [];
|
|
24
|
+
/** @type {Array<Record<string, any>>} */
|
|
25
|
+
const diagnostics = [];
|
|
26
|
+
let catalogLoaded = false;
|
|
27
|
+
if (!isCatalogSourceDisabled(catalogSource)) {
|
|
28
|
+
try {
|
|
29
|
+
const loaded = loadCatalog(catalogSource);
|
|
30
|
+
catalogLoaded = true;
|
|
31
|
+
const entries = /** @type {any[]} */ (loaded.catalog.entries || []);
|
|
32
|
+
templates.push(
|
|
33
|
+
...entries
|
|
34
|
+
.filter((entry) => entry.kind === "template")
|
|
35
|
+
.map((entry) => templateListItemFromCatalogEntry(entry, loaded.source))
|
|
36
|
+
);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
diagnostics.push({
|
|
39
|
+
code: "catalog_unavailable",
|
|
40
|
+
severity: "warning",
|
|
41
|
+
message: messageFromError(error),
|
|
42
|
+
path: catalogSource,
|
|
43
|
+
suggestedFix: "Run `topogram catalog list` after authenticating, or pass a local template path/package spec directly."
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
ok: true,
|
|
49
|
+
catalog: {
|
|
50
|
+
source: isCatalogSourceDisabled(catalogSource) ? null : catalogSource,
|
|
51
|
+
loaded: catalogLoaded
|
|
52
|
+
},
|
|
53
|
+
templates,
|
|
54
|
+
diagnostics,
|
|
55
|
+
errors: []
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @param {any} entry
|
|
61
|
+
* @param {string} source
|
|
62
|
+
* @returns {Record<string, any>}
|
|
63
|
+
*/
|
|
64
|
+
function templateListItemFromCatalogEntry(entry, source) {
|
|
65
|
+
const item = catalogTemplateListItem(entry);
|
|
66
|
+
const commands = catalogShowCommands(entry, source);
|
|
67
|
+
return {
|
|
68
|
+
...item,
|
|
69
|
+
surfaces: Array.isArray(item.surfaces) ? item.surfaces : [],
|
|
70
|
+
generators: Array.isArray(item.generators) ? item.generators : [],
|
|
71
|
+
stack: typeof item.stack === "string" ? item.stack : null,
|
|
72
|
+
isDefault: item.id === "hello-web",
|
|
73
|
+
recommendedCommand: commands.primary,
|
|
74
|
+
commands
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @param {ReturnType<typeof buildTemplateListPayload>} payload
|
|
80
|
+
* @returns {void}
|
|
81
|
+
*/
|
|
82
|
+
export function printTemplateList(payload) {
|
|
83
|
+
console.log("Template starters:");
|
|
84
|
+
console.log("Catalog aliases resolve to versioned package installs. Local paths and full package specs can also be used with `topogram new`.");
|
|
85
|
+
if (payload.catalog.source) {
|
|
86
|
+
console.log(`Catalog: ${payload.catalog.source} (${payload.catalog.loaded ? "loaded" : "unavailable"})`);
|
|
87
|
+
} else {
|
|
88
|
+
console.log("Catalog: disabled");
|
|
89
|
+
}
|
|
90
|
+
for (const template of payload.templates) {
|
|
91
|
+
const defaultLabel = template.isDefault ? " (default)" : "";
|
|
92
|
+
const stack = template.stack || "not declared";
|
|
93
|
+
const surfaces = Array.isArray(template.surfaces) && template.surfaces.length > 0
|
|
94
|
+
? template.surfaces.join(", ")
|
|
95
|
+
: "not declared";
|
|
96
|
+
const command = template.recommendedCommand || `topogram new ./my-app --template ${shellCommandArg(template.id)}`;
|
|
97
|
+
console.log(`- ${template.id}@${template.version}${defaultLabel}`);
|
|
98
|
+
console.log(` Source: ${template.source} | Surfaces: ${surfaces} | Stack: ${stack} | Executable implementation: ${template.includesExecutableImplementation ? "yes" : "no"}`);
|
|
99
|
+
console.log(` New: ${command}`);
|
|
100
|
+
}
|
|
101
|
+
for (const diagnostic of payload.diagnostics) {
|
|
102
|
+
console.warn(`Warning: ${diagnostic.message}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @param {Record<string, any>} template
|
|
108
|
+
* @param {"catalog"} sourceKind
|
|
109
|
+
* @param {string|null} packageSpec
|
|
110
|
+
* @param {{ primary: string|null, followUp: string[] }} commands
|
|
111
|
+
* @returns {{ surfaces: string[], generators: string[], stack: string|null, packageSpec: string|null, packageName: string|null, version: string|null, executableImplementation: boolean, policyImpact: string, recommendedCommand: string|null, followUp: string[], notes: string[] }}
|
|
112
|
+
*/
|
|
113
|
+
function templateDecisionSummary(template, sourceKind, packageSpec, commands) {
|
|
114
|
+
const trust = template.trust && typeof template.trust === "object" ? template.trust : null;
|
|
115
|
+
const executable = trust
|
|
116
|
+
? Boolean(trust.includesExecutableImplementation)
|
|
117
|
+
: Boolean(template.includesExecutableImplementation);
|
|
118
|
+
const surfaces = Array.isArray(template.surfaces) ? template.surfaces : [];
|
|
119
|
+
const generators = Array.isArray(template.generators) ? template.generators : [];
|
|
120
|
+
const stack = typeof template.stack === "string" && template.stack ? template.stack : null;
|
|
121
|
+
const notes = [];
|
|
122
|
+
if (sourceKind === "catalog") {
|
|
123
|
+
notes.push("Catalog templates resolve to versioned package installs; the catalog is an index, not the template payload.");
|
|
124
|
+
}
|
|
125
|
+
if (surfaces.length === 0) {
|
|
126
|
+
notes.push("Surface metadata is not declared in this catalog entry.");
|
|
127
|
+
}
|
|
128
|
+
if (generators.length === 0) {
|
|
129
|
+
notes.push("Generator metadata is not declared in this catalog entry.");
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
surfaces,
|
|
133
|
+
generators,
|
|
134
|
+
stack,
|
|
135
|
+
packageSpec,
|
|
136
|
+
packageName: template.package || (packageSpec ? packageNameFromPackageSpec(packageSpec) : null),
|
|
137
|
+
version: template.defaultVersion || template.version || null,
|
|
138
|
+
executableImplementation: executable,
|
|
139
|
+
policyImpact: executable
|
|
140
|
+
? "Copies implementation/ code into the project; topogram new does not execute it, but topogram generate may load it after local trust is recorded."
|
|
141
|
+
: "No executable implementation trust is required for this template.",
|
|
142
|
+
recommendedCommand: commands.primary,
|
|
143
|
+
followUp: commands.followUp,
|
|
144
|
+
notes
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* @param {string} id
|
|
150
|
+
* @param {string|null} source
|
|
151
|
+
* @returns {{ ok: boolean, source: "catalog"|null, catalog: { source: string|null, version: string|null }, template: Record<string, any>|null, packageSpec: string|null, decision: ReturnType<typeof templateDecisionSummary>|null, commands: { primary: string|null, followUp: string[] }, diagnostics: any[], errors: string[] }}
|
|
152
|
+
*/
|
|
153
|
+
export function buildTemplateShowPayload(id, source) {
|
|
154
|
+
if (!id || id.startsWith("-")) {
|
|
155
|
+
throw new Error("topogram template show requires <id>.");
|
|
156
|
+
}
|
|
157
|
+
const catalogPayload = buildCatalogShowPayload(id, source);
|
|
158
|
+
if (!catalogPayload.ok || !catalogPayload.entry) {
|
|
159
|
+
return {
|
|
160
|
+
ok: false,
|
|
161
|
+
source: "catalog",
|
|
162
|
+
catalog: {
|
|
163
|
+
source: catalogPayload.source,
|
|
164
|
+
version: catalogPayload.catalog.version
|
|
165
|
+
},
|
|
166
|
+
template: null,
|
|
167
|
+
packageSpec: null,
|
|
168
|
+
decision: null,
|
|
169
|
+
commands: { primary: null, followUp: [] },
|
|
170
|
+
diagnostics: catalogPayload.diagnostics,
|
|
171
|
+
errors: catalogPayload.errors
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
if (catalogPayload.entry.kind !== "template") {
|
|
175
|
+
const diagnostic = {
|
|
176
|
+
code: "catalog_entry_not_template",
|
|
177
|
+
severity: "error",
|
|
178
|
+
message: `Catalog entry '${id}' is a ${catalogPayload.entry.kind}, not a template.`,
|
|
179
|
+
path: catalogPayload.source,
|
|
180
|
+
suggestedFix: "Use `topogram catalog show` for non-template catalog entries."
|
|
181
|
+
};
|
|
182
|
+
return {
|
|
183
|
+
ok: false,
|
|
184
|
+
source: "catalog",
|
|
185
|
+
catalog: {
|
|
186
|
+
source: catalogPayload.source,
|
|
187
|
+
version: catalogPayload.catalog.version
|
|
188
|
+
},
|
|
189
|
+
template: catalogPayload.entry,
|
|
190
|
+
packageSpec: catalogPayload.packageSpec,
|
|
191
|
+
decision: null,
|
|
192
|
+
commands: catalogPayload.commands,
|
|
193
|
+
diagnostics: [...catalogPayload.diagnostics, diagnostic],
|
|
194
|
+
errors: [diagnostic.message]
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
ok: true,
|
|
199
|
+
source: "catalog",
|
|
200
|
+
catalog: {
|
|
201
|
+
source: catalogPayload.source,
|
|
202
|
+
version: catalogPayload.catalog.version
|
|
203
|
+
},
|
|
204
|
+
template: catalogPayload.entry,
|
|
205
|
+
packageSpec: catalogPayload.packageSpec,
|
|
206
|
+
decision: templateDecisionSummary(catalogPayload.entry, "catalog", catalogPayload.packageSpec, catalogPayload.commands),
|
|
207
|
+
commands: catalogPayload.commands,
|
|
208
|
+
diagnostics: catalogPayload.diagnostics,
|
|
209
|
+
errors: []
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* @param {ReturnType<typeof buildTemplateShowPayload>} payload
|
|
215
|
+
* @returns {void}
|
|
216
|
+
*/
|
|
217
|
+
export function printTemplateShow(payload) {
|
|
218
|
+
if (!payload.ok || !payload.template) {
|
|
219
|
+
console.log("Template not found.");
|
|
220
|
+
if (payload.catalog.source) {
|
|
221
|
+
console.log(`Catalog: ${payload.catalog.source}`);
|
|
222
|
+
}
|
|
223
|
+
for (const diagnostic of payload.diagnostics) {
|
|
224
|
+
const label = diagnostic.severity === "warning" ? "Warning" : "Error";
|
|
225
|
+
console.log(`${label}: ${diagnostic.message}`);
|
|
226
|
+
}
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
const template = payload.template;
|
|
230
|
+
console.log(`Template: ${template.id}`);
|
|
231
|
+
console.log(`Source: ${payload.source}`);
|
|
232
|
+
if (template.name) {
|
|
233
|
+
const defaultLabel = template.isDefault ? " (default)" : "";
|
|
234
|
+
console.log(`Name: ${template.name}${defaultLabel}`);
|
|
235
|
+
}
|
|
236
|
+
if (payload.catalog.source) {
|
|
237
|
+
console.log(`Catalog: ${payload.catalog.source}`);
|
|
238
|
+
}
|
|
239
|
+
if (payload.packageSpec) {
|
|
240
|
+
console.log(`Package: ${payload.packageSpec}`);
|
|
241
|
+
}
|
|
242
|
+
if (template.description) {
|
|
243
|
+
console.log(`Description: ${template.description}`);
|
|
244
|
+
}
|
|
245
|
+
if (payload.decision) {
|
|
246
|
+
console.log("");
|
|
247
|
+
console.log("What it creates:");
|
|
248
|
+
console.log(` Surfaces: ${payload.decision.surfaces.join(", ") || "not declared"}`);
|
|
249
|
+
console.log(` Stack: ${payload.decision.stack || "not declared"}`);
|
|
250
|
+
console.log(` Generators: ${payload.decision.generators.join(", ") || "not declared"}`);
|
|
251
|
+
console.log(` Package: ${payload.decision.packageSpec || "not declared"}`);
|
|
252
|
+
console.log(` Executable implementation: ${payload.decision.executableImplementation ? "yes" : "no"}`);
|
|
253
|
+
console.log(` Policy impact: ${payload.decision.policyImpact}`);
|
|
254
|
+
for (const note of payload.decision.notes) {
|
|
255
|
+
console.log(` Note: ${note}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
console.log("");
|
|
259
|
+
console.log("Details:");
|
|
260
|
+
if (Array.isArray(template.tags) && template.tags.length > 0) {
|
|
261
|
+
console.log(`Tags: ${template.tags.join(", ")}`);
|
|
262
|
+
}
|
|
263
|
+
if (template.trust?.scope) {
|
|
264
|
+
console.log(`Trust scope: ${template.trust.scope}`);
|
|
265
|
+
}
|
|
266
|
+
const executable = template.trust
|
|
267
|
+
? template.trust.includesExecutableImplementation
|
|
268
|
+
: template.includesExecutableImplementation;
|
|
269
|
+
console.log(`Executable implementation: ${executable ? "yes" : "no"}`);
|
|
270
|
+
if (template.trust?.notes) {
|
|
271
|
+
console.log(`Trust notes: ${template.trust.notes}`);
|
|
272
|
+
}
|
|
273
|
+
console.log("");
|
|
274
|
+
console.log("Recommended command:");
|
|
275
|
+
console.log(` ${payload.commands.primary}`);
|
|
276
|
+
if (payload.commands.followUp.length > 0) {
|
|
277
|
+
console.log("Follow-up:");
|
|
278
|
+
for (const command of payload.commands.followUp) {
|
|
279
|
+
console.log(` ${command}`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
for (const diagnostic of payload.diagnostics) {
|
|
283
|
+
if (diagnostic.severity === "warning") {
|
|
284
|
+
console.warn(`Warning: ${diagnostic.message}`);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|