@topogram/cli 0.3.63 → 0.3.64
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.d.ts +6 -0
- package/src/adoption/reporting.d.ts +10 -0
- package/src/adoption/review-groups.d.ts +6 -0
- package/src/agent-brief.d.ts +3 -0
- package/src/agent-brief.js +495 -0
- package/src/agent-ops/query-builders.d.ts +26 -0
- package/src/archive/archive.d.ts +2 -0
- package/src/archive/compact.d.ts +1 -0
- package/src/archive/unarchive.d.ts +1 -0
- package/src/catalog.d.ts +10 -0
- package/src/catalog.js +62 -66
- package/src/cli/catalog-alias.d.ts +1 -0
- package/src/cli/command-parser.js +38 -0
- package/src/cli/command-parsers/core.js +102 -0
- package/src/cli/command-parsers/generator.js +39 -0
- package/src/cli/command-parsers/import.js +44 -0
- package/src/cli/command-parsers/legacy-workflow.js +21 -0
- package/src/cli/command-parsers/project.js +47 -0
- package/src/cli/command-parsers/sdlc.js +47 -0
- package/src/cli/command-parsers/shared.js +51 -0
- package/src/cli/command-parsers/template.js +48 -0
- package/src/cli/commands/agent.js +47 -0
- package/src/cli/commands/catalog.js +617 -0
- package/src/cli/commands/check.js +268 -0
- package/src/cli/commands/doctor.js +268 -0
- package/src/cli/commands/emit.js +149 -0
- package/src/cli/commands/generate.js +96 -0
- package/src/cli/commands/generator-policy.js +785 -0
- package/src/cli/commands/generator.js +443 -0
- package/src/cli/commands/import-runner.js +157 -0
- package/src/cli/commands/import.js +1734 -0
- package/src/cli/commands/inspect.js +55 -0
- package/src/cli/commands/new.js +94 -0
- package/src/cli/commands/package.js +815 -0
- package/src/cli/commands/query.js +1302 -0
- package/src/cli/commands/release-rollout.js +257 -0
- package/src/cli/commands/release-shared.js +528 -0
- package/src/cli/commands/release-status.js +429 -0
- package/src/cli/commands/release.js +107 -0
- package/src/cli/commands/sdlc.js +168 -0
- package/src/cli/commands/setup.js +76 -0
- package/src/cli/commands/source.js +291 -0
- package/src/cli/commands/template-runner.js +198 -0
- package/src/cli/commands/template.js +2145 -0
- package/src/cli/commands/trust.js +219 -0
- package/src/cli/commands/version.js +40 -0
- package/src/cli/commands/widget.js +168 -0
- package/src/cli/commands/workflow.js +63 -0
- package/src/cli/dispatcher.js +392 -0
- package/src/cli/help-dispatch.js +188 -0
- package/src/cli/help.js +296 -0
- package/src/cli/migration-guidance.js +59 -0
- package/src/cli/options.js +96 -0
- package/src/cli/output-safety.js +107 -0
- package/src/cli/path-normalization.js +29 -0
- package/src/cli.js +47 -11711
- package/src/example-implementation.d.ts +2 -0
- package/src/format.d.ts +1 -0
- package/src/generator/check.d.ts +1 -0
- package/src/generator/context/bundle.d.ts +1 -0
- package/src/generator/context/shared.d.ts +2 -0
- package/src/generator/native/parity-bundle.js +2 -1
- package/src/generator/surfaces/web/html-escape.js +22 -0
- package/src/generator/surfaces/web/react.js +10 -8
- package/src/generator/surfaces/web/sveltekit.js +7 -5
- package/src/generator/surfaces/web/vanilla.js +8 -4
- package/src/generator.d.ts +2 -0
- package/src/github-client.js +520 -0
- package/src/import/core/shared.js +20 -62
- package/src/import/extractors/api/flutter-dio.js +4 -8
- package/src/import/extractors/api/react-native-repository.js +4 -8
- package/src/import/index.d.ts +4 -0
- package/src/import/provenance.d.ts +4 -0
- package/src/new-project.js +100 -11
- package/src/npm-safety.js +79 -0
- package/src/parser.d.ts +1 -0
- package/src/path-helpers.d.ts +1 -0
- package/src/path-helpers.js +20 -0
- package/src/project-config.js +1 -0
- package/src/reconcile/docs.d.ts +8 -0
- package/src/reconcile/journeys.d.ts +1 -0
- package/src/resolver.d.ts +1 -0
- package/src/runtime-support.js +29 -0
- package/src/sdlc/adopt.d.ts +1 -0
- package/src/sdlc/check.d.ts +1 -0
- package/src/sdlc/explain.d.ts +1 -0
- package/src/sdlc/release.d.ts +1 -0
- package/src/sdlc/scaffold.d.ts +1 -0
- package/src/sdlc/transition.d.ts +1 -0
- package/src/text-helpers.d.ts +6 -0
- package/src/text-helpers.js +245 -0
- package/src/topogram-config.js +306 -0
- package/src/validator.d.ts +2 -0
- package/src/workflows/adoption/index.js +26 -0
- package/src/workflows/docs-generate.js +262 -0
- package/src/workflows/docs-scan.js +703 -0
- package/src/workflows/docs.js +15 -0
- package/src/workflows/import-app/api.js +799 -0
- package/src/workflows/import-app/db.js +538 -0
- package/src/workflows/import-app/index.js +30 -0
- package/src/workflows/import-app/shared.js +218 -0
- package/src/workflows/import-app/ui.js +443 -0
- package/src/workflows/import-app/workflow.js +159 -0
- package/src/workflows/reconcile/adoption-plan.js +742 -0
- package/src/workflows/reconcile/auth.js +692 -0
- package/src/workflows/reconcile/bundle-core.js +600 -0
- package/src/workflows/reconcile/bundle-shared.js +75 -0
- package/src/workflows/reconcile/candidate-model.js +477 -0
- package/src/workflows/reconcile/canonical-surface.js +264 -0
- package/src/workflows/reconcile/gap-report.js +333 -0
- package/src/workflows/reconcile/ids.js +6 -0
- package/src/workflows/reconcile/impacts.js +625 -0
- package/src/workflows/reconcile/index.js +7 -0
- package/src/workflows/reconcile/renderers.js +461 -0
- package/src/workflows/reconcile/summary.js +90 -0
- package/src/workflows/reconcile/workflow.js +309 -0
- package/src/workflows/shared.js +189 -0
- package/src/workflows/types.d.ts +93 -0
- package/src/workflows.d.ts +1 -0
- package/src/workflows.js +10 -7652
|
@@ -0,0 +1,785 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
import { stableStringify } from "../../format.js";
|
|
7
|
+
import {
|
|
8
|
+
defaultGeneratorPolicy,
|
|
9
|
+
GENERATOR_POLICY_FILE,
|
|
10
|
+
generatorPackageAllowed,
|
|
11
|
+
generatorPolicyDiagnosticsForBindings,
|
|
12
|
+
loadGeneratorPolicy,
|
|
13
|
+
packageBackedGeneratorBindings,
|
|
14
|
+
packageScopeFromName,
|
|
15
|
+
parseGeneratorPolicyPin,
|
|
16
|
+
writeGeneratorPolicy
|
|
17
|
+
} from "../../generator-policy.js";
|
|
18
|
+
import { loadProjectConfig } from "../../project-config.js";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {string} name
|
|
22
|
+
* @param {boolean} ok
|
|
23
|
+
* @param {string} actual
|
|
24
|
+
* @param {string} expected
|
|
25
|
+
* @param {string} message
|
|
26
|
+
* @param {string|null} fix
|
|
27
|
+
* @returns {{ name: string, ok: boolean, actual: string, expected: string, message: string, fix: string|null }}
|
|
28
|
+
*/
|
|
29
|
+
function generatorPolicyRule(name, ok, actual, expected, message, fix = null) {
|
|
30
|
+
return { name, ok, actual, expected, message, fix };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param {string} name
|
|
35
|
+
* @returns {string}
|
|
36
|
+
*/
|
|
37
|
+
function generatorPolicyRuleLabel(name) {
|
|
38
|
+
return ({
|
|
39
|
+
"policy-file": "Policy file",
|
|
40
|
+
"allowed-package": "Allowed package",
|
|
41
|
+
"pinned-version": "Pinned version"
|
|
42
|
+
})[name] || name;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @param {any} policyInfo
|
|
47
|
+
* @returns {any}
|
|
48
|
+
*/
|
|
49
|
+
function effectiveGeneratorPolicy(policyInfo) {
|
|
50
|
+
return policyInfo.policy || {
|
|
51
|
+
version: "0.1",
|
|
52
|
+
allowedPackageScopes: ["@topogram"],
|
|
53
|
+
allowedPackages: [],
|
|
54
|
+
pinnedVersions: {}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @param {string} filePath
|
|
60
|
+
* @returns {any|null}
|
|
61
|
+
*/
|
|
62
|
+
function readJsonIfPresent(filePath) {
|
|
63
|
+
if (!fs.existsSync(filePath)) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
68
|
+
} catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @param {Record<string, any>|null} projectPackage
|
|
75
|
+
* @param {string} packageName
|
|
76
|
+
* @returns {{ field: string|null, spec: string|null }}
|
|
77
|
+
*/
|
|
78
|
+
function dependencySpecForPackage(projectPackage, packageName) {
|
|
79
|
+
const dependencyFields = ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"];
|
|
80
|
+
for (const field of dependencyFields) {
|
|
81
|
+
const dependencies = projectPackage?.[field];
|
|
82
|
+
if (dependencies && typeof dependencies === "object" && typeof dependencies[packageName] === "string") {
|
|
83
|
+
return {
|
|
84
|
+
field,
|
|
85
|
+
spec: dependencies[packageName]
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
field: null,
|
|
91
|
+
spec: null
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @param {Record<string, any>|null} lockfile
|
|
97
|
+
* @param {string} packageName
|
|
98
|
+
* @returns {{ version: string|null, resolved: string|null, integrity: string|null, entryPath: string|null }}
|
|
99
|
+
*/
|
|
100
|
+
function npmLockfileInfoForPackage(lockfile, packageName) {
|
|
101
|
+
const packageEntryPath = `node_modules/${packageName}`;
|
|
102
|
+
const packageEntry = lockfile?.packages?.[packageEntryPath];
|
|
103
|
+
if (packageEntry && typeof packageEntry === "object") {
|
|
104
|
+
return {
|
|
105
|
+
version: typeof packageEntry.version === "string" ? packageEntry.version : null,
|
|
106
|
+
resolved: typeof packageEntry.resolved === "string" ? packageEntry.resolved : null,
|
|
107
|
+
integrity: typeof packageEntry.integrity === "string" ? packageEntry.integrity : null,
|
|
108
|
+
entryPath: packageEntryPath
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const dependencyEntry = lockfile?.dependencies?.[packageName];
|
|
112
|
+
if (dependencyEntry && typeof dependencyEntry === "object") {
|
|
113
|
+
return {
|
|
114
|
+
version: typeof dependencyEntry.version === "string" ? dependencyEntry.version : null,
|
|
115
|
+
resolved: typeof dependencyEntry.resolved === "string" ? dependencyEntry.resolved : null,
|
|
116
|
+
integrity: typeof dependencyEntry.integrity === "string" ? dependencyEntry.integrity : null,
|
|
117
|
+
entryPath: packageName
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
version: null,
|
|
122
|
+
resolved: null,
|
|
123
|
+
integrity: null,
|
|
124
|
+
entryPath: null
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* @param {string} projectRoot
|
|
130
|
+
* @returns {{ kind: "npm"|"pnpm"|"yarn"|"bun"|null, path: string|null, data: Record<string, any>|null, note: string|null }}
|
|
131
|
+
*/
|
|
132
|
+
function lockfileMetadataForProject(projectRoot) {
|
|
133
|
+
const npmLockfilePath = path.join(projectRoot, "package-lock.json");
|
|
134
|
+
if (fs.existsSync(npmLockfilePath)) {
|
|
135
|
+
return {
|
|
136
|
+
kind: "npm",
|
|
137
|
+
path: npmLockfilePath,
|
|
138
|
+
data: readJsonIfPresent(npmLockfilePath),
|
|
139
|
+
note: null
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
const candidates = [
|
|
143
|
+
{ kind: "pnpm", file: "pnpm-lock.yaml" },
|
|
144
|
+
{ kind: "yarn", file: "yarn.lock" },
|
|
145
|
+
{ kind: "bun", file: "bun.lock" },
|
|
146
|
+
{ kind: "bun", file: "bun.lockb" }
|
|
147
|
+
];
|
|
148
|
+
for (const candidate of candidates) {
|
|
149
|
+
const lockfilePath = path.join(projectRoot, candidate.file);
|
|
150
|
+
if (fs.existsSync(lockfilePath)) {
|
|
151
|
+
return {
|
|
152
|
+
kind: /** @type {"pnpm"|"yarn"|"bun"} */ (candidate.kind),
|
|
153
|
+
path: lockfilePath,
|
|
154
|
+
data: null,
|
|
155
|
+
note: `${candidate.file} found; package versions are not inspected by this command.`
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
kind: null,
|
|
161
|
+
path: null,
|
|
162
|
+
data: null,
|
|
163
|
+
note: null
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* @param {string} projectRoot
|
|
169
|
+
* @param {string} packageName
|
|
170
|
+
* @returns {{ dependencyField: string|null, dependencySpec: string|null, installedVersion: string|null, installedPackageJsonPath: string|null, lockfileKind: "npm"|"pnpm"|"yarn"|"bun"|null, lockfilePath: string|null, lockfileVersion: string|null, lockfileResolved: string|null, lockfileIntegrity: string|null, lockfileEntryPath: string|null, lockfileNote: string|null }}
|
|
171
|
+
*/
|
|
172
|
+
function packageInfoForGenerator(projectRoot, packageName) {
|
|
173
|
+
const projectPackage = readJsonIfPresent(path.join(projectRoot, "package.json"));
|
|
174
|
+
const dependency = dependencySpecForPackage(projectPackage, packageName);
|
|
175
|
+
const lockfile = lockfileMetadataForProject(projectRoot);
|
|
176
|
+
const lockfileInfo = lockfile.kind === "npm"
|
|
177
|
+
? npmLockfileInfoForPackage(lockfile.data, packageName)
|
|
178
|
+
: {
|
|
179
|
+
version: null,
|
|
180
|
+
resolved: null,
|
|
181
|
+
integrity: null,
|
|
182
|
+
entryPath: null
|
|
183
|
+
};
|
|
184
|
+
const installedPackageJsonPath = path.join(projectRoot, "node_modules", ...packageName.split("/"), "package.json");
|
|
185
|
+
const installedPackage = readJsonIfPresent(installedPackageJsonPath);
|
|
186
|
+
return {
|
|
187
|
+
dependencyField: dependency.field,
|
|
188
|
+
dependencySpec: dependency.spec,
|
|
189
|
+
installedVersion: typeof installedPackage?.version === "string" ? installedPackage.version : null,
|
|
190
|
+
installedPackageJsonPath: installedPackage ? installedPackageJsonPath : null,
|
|
191
|
+
lockfileKind: lockfile.kind,
|
|
192
|
+
lockfilePath: lockfile.path,
|
|
193
|
+
lockfileVersion: lockfileInfo.version,
|
|
194
|
+
lockfileResolved: lockfileInfo.resolved,
|
|
195
|
+
lockfileIntegrity: lockfileInfo.integrity,
|
|
196
|
+
lockfileEntryPath: lockfileInfo.entryPath,
|
|
197
|
+
lockfileNote: lockfile.note
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* @param {ReturnType<typeof packageInfoForGenerator>} packageInfo
|
|
203
|
+
* @returns {string}
|
|
204
|
+
*/
|
|
205
|
+
function formatGeneratorPackageLockfile(packageInfo) {
|
|
206
|
+
if (!packageInfo.lockfileKind || !packageInfo.lockfilePath) {
|
|
207
|
+
return "(not found)";
|
|
208
|
+
}
|
|
209
|
+
const label = path.basename(packageInfo.lockfilePath);
|
|
210
|
+
if (packageInfo.lockfileVersion) {
|
|
211
|
+
return `${packageInfo.lockfileKind} ${packageInfo.lockfileVersion}`;
|
|
212
|
+
}
|
|
213
|
+
return `${label} (version not inspected)`;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* @param {string} projectRoot
|
|
218
|
+
* @param {any} policy
|
|
219
|
+
* @param {ReturnType<typeof packageBackedGeneratorBindings>[number]} binding
|
|
220
|
+
* @returns {ReturnType<typeof packageBackedGeneratorBindings>[number] & { allowed: boolean, packageInfo: ReturnType<typeof packageInfoForGenerator>, pin: { key: string|null, version: string|null, matches: boolean|null } }}
|
|
221
|
+
*/
|
|
222
|
+
function generatorPolicyBindingStatus(projectRoot, policy, binding) {
|
|
223
|
+
const packagePin = policy.pinnedVersions[binding.packageName] || null;
|
|
224
|
+
const generatorPin = policy.pinnedVersions[binding.generatorId] || null;
|
|
225
|
+
const pinnedVersion = packagePin || generatorPin;
|
|
226
|
+
return {
|
|
227
|
+
...binding,
|
|
228
|
+
allowed: generatorPackageAllowed(policy, binding.packageName),
|
|
229
|
+
packageInfo: packageInfoForGenerator(projectRoot, binding.packageName),
|
|
230
|
+
pin: {
|
|
231
|
+
key: packagePin ? binding.packageName : generatorPin ? binding.generatorId : null,
|
|
232
|
+
version: pinnedVersion,
|
|
233
|
+
matches: pinnedVersion ? pinnedVersion === binding.version : null
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* @param {any[]} diagnostics
|
|
240
|
+
* @param {Array<ReturnType<typeof generatorPolicyBindingStatus>>} bindings
|
|
241
|
+
* @returns {any[]}
|
|
242
|
+
*/
|
|
243
|
+
function annotateGeneratorPolicyDiagnostics(diagnostics, bindings) {
|
|
244
|
+
return diagnostics.map((diagnostic) => {
|
|
245
|
+
const binding = bindings.find((item) => (
|
|
246
|
+
item.packageName === diagnostic.packageName &&
|
|
247
|
+
(!diagnostic.runtimeId || item.runtimeId === diagnostic.runtimeId)
|
|
248
|
+
));
|
|
249
|
+
if (!binding) {
|
|
250
|
+
return diagnostic;
|
|
251
|
+
}
|
|
252
|
+
return {
|
|
253
|
+
...diagnostic,
|
|
254
|
+
packageVersion: binding.packageInfo.installedVersion || binding.packageInfo.lockfileVersion || null,
|
|
255
|
+
packageDependencyField: binding.packageInfo.dependencyField,
|
|
256
|
+
packageDependencySpec: binding.packageInfo.dependencySpec,
|
|
257
|
+
packageLockfileKind: binding.packageInfo.lockfileKind,
|
|
258
|
+
packageLockfilePath: binding.packageInfo.lockfilePath,
|
|
259
|
+
packageLockVersion: binding.packageInfo.lockfileVersion
|
|
260
|
+
};
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* @param {Array<ReturnType<typeof generatorPolicyBindingStatus>>} bindings
|
|
266
|
+
* @returns {any[]}
|
|
267
|
+
*/
|
|
268
|
+
function generatorPolicyPackageMetadataDiagnostics(bindings) {
|
|
269
|
+
const diagnostics = [];
|
|
270
|
+
for (const binding of bindings) {
|
|
271
|
+
if (!binding.packageInfo.dependencySpec) {
|
|
272
|
+
diagnostics.push({
|
|
273
|
+
code: "generator_package_dependency_missing",
|
|
274
|
+
severity: "warning",
|
|
275
|
+
message: `Runtime '${binding.runtimeId}' generator package '${binding.packageName}' is not declared in package.json dependencies.`,
|
|
276
|
+
path: binding.packageInfo.installedPackageJsonPath,
|
|
277
|
+
suggestedFix: `Declare '${binding.packageName}' in package.json devDependencies so generator adoption is visible in package review.`,
|
|
278
|
+
step: "generator-policy",
|
|
279
|
+
runtimeId: binding.runtimeId,
|
|
280
|
+
generatorId: binding.generatorId,
|
|
281
|
+
packageName: binding.packageName,
|
|
282
|
+
version: binding.version,
|
|
283
|
+
packageVersion: binding.packageInfo.installedVersion || binding.packageInfo.lockfileVersion || null,
|
|
284
|
+
packageDependencyField: binding.packageInfo.dependencyField,
|
|
285
|
+
packageDependencySpec: binding.packageInfo.dependencySpec,
|
|
286
|
+
packageLockfileKind: binding.packageInfo.lockfileKind,
|
|
287
|
+
packageLockfilePath: binding.packageInfo.lockfilePath,
|
|
288
|
+
packageLockVersion: binding.packageInfo.lockfileVersion
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
if (
|
|
292
|
+
binding.packageInfo.installedVersion &&
|
|
293
|
+
binding.packageInfo.lockfileVersion &&
|
|
294
|
+
binding.packageInfo.installedVersion !== binding.packageInfo.lockfileVersion
|
|
295
|
+
) {
|
|
296
|
+
diagnostics.push({
|
|
297
|
+
code: "generator_package_version_drift",
|
|
298
|
+
severity: "warning",
|
|
299
|
+
message: `Runtime '${binding.runtimeId}' generator package '${binding.packageName}' is installed at '${binding.packageInfo.installedVersion}', but package-lock records '${binding.packageInfo.lockfileVersion}'.`,
|
|
300
|
+
path: binding.packageInfo.lockfilePath,
|
|
301
|
+
suggestedFix: "Run the package manager install command and review the resulting lockfile before pinning generator policy.",
|
|
302
|
+
step: "generator-policy",
|
|
303
|
+
runtimeId: binding.runtimeId,
|
|
304
|
+
generatorId: binding.generatorId,
|
|
305
|
+
packageName: binding.packageName,
|
|
306
|
+
version: binding.version,
|
|
307
|
+
packageVersion: binding.packageInfo.installedVersion,
|
|
308
|
+
packageDependencyField: binding.packageInfo.dependencyField,
|
|
309
|
+
packageDependencySpec: binding.packageInfo.dependencySpec,
|
|
310
|
+
packageLockfileKind: binding.packageInfo.lockfileKind,
|
|
311
|
+
packageLockfilePath: binding.packageInfo.lockfilePath,
|
|
312
|
+
packageLockVersion: binding.packageInfo.lockfileVersion
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return diagnostics;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* @param {string} projectPath
|
|
321
|
+
* @returns {{ ok: boolean, path: string, exists: boolean, policy: any, defaulted: boolean, bindings: Array<ReturnType<typeof generatorPolicyBindingStatus>>, diagnostics: any[], errors: string[] }}
|
|
322
|
+
*/
|
|
323
|
+
export function buildGeneratorPolicyCheckPayload(projectPath) {
|
|
324
|
+
const projectConfigInfo = loadProjectConfig(projectPath);
|
|
325
|
+
if (!projectConfigInfo) {
|
|
326
|
+
const diagnostic = {
|
|
327
|
+
code: "generator_policy_project_missing",
|
|
328
|
+
severity: "error",
|
|
329
|
+
message: "Cannot check generator policy without topogram.project.json.",
|
|
330
|
+
path: path.resolve(projectPath),
|
|
331
|
+
suggestedFix: "Run this command in a Topogram project.",
|
|
332
|
+
step: "generator-policy"
|
|
333
|
+
};
|
|
334
|
+
return {
|
|
335
|
+
ok: false,
|
|
336
|
+
path: path.join(path.resolve(projectPath), GENERATOR_POLICY_FILE),
|
|
337
|
+
exists: false,
|
|
338
|
+
policy: null,
|
|
339
|
+
defaulted: false,
|
|
340
|
+
bindings: [],
|
|
341
|
+
diagnostics: [diagnostic],
|
|
342
|
+
errors: [diagnostic.message]
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
const policyInfo = loadGeneratorPolicy(projectConfigInfo.configDir);
|
|
346
|
+
const rawBindings = packageBackedGeneratorBindings(projectConfigInfo.config);
|
|
347
|
+
const policy = policyInfo.policy || effectiveGeneratorPolicy(policyInfo);
|
|
348
|
+
const bindings = rawBindings.map((binding) => generatorPolicyBindingStatus(projectConfigInfo.configDir, policy, binding));
|
|
349
|
+
const diagnostics = [];
|
|
350
|
+
if (!policyInfo.exists) {
|
|
351
|
+
diagnostics.push({
|
|
352
|
+
code: "generator_policy_missing",
|
|
353
|
+
severity: "warning",
|
|
354
|
+
message: `No ${GENERATOR_POLICY_FILE} found. Default generator policy allows @topogram/* package-backed generators and blocks other package scopes.`,
|
|
355
|
+
path: policyInfo.path,
|
|
356
|
+
suggestedFix: "Run `topogram generator policy init` to write an explicit project generator policy after review.",
|
|
357
|
+
step: "generator-policy"
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
diagnostics.push(...generatorPolicyDiagnosticsForBindings(policyInfo, rawBindings, "generator-policy"));
|
|
361
|
+
diagnostics.push(...generatorPolicyPackageMetadataDiagnostics(bindings));
|
|
362
|
+
const annotatedDiagnostics = annotateGeneratorPolicyDiagnostics(diagnostics, bindings);
|
|
363
|
+
const errors = annotatedDiagnostics.filter((diagnostic) => diagnostic.severity === "error").map((diagnostic) => diagnostic.message);
|
|
364
|
+
return {
|
|
365
|
+
ok: errors.length === 0,
|
|
366
|
+
path: policyInfo.path,
|
|
367
|
+
exists: policyInfo.exists,
|
|
368
|
+
policy,
|
|
369
|
+
defaulted: !policyInfo.exists,
|
|
370
|
+
bindings,
|
|
371
|
+
diagnostics: annotatedDiagnostics,
|
|
372
|
+
errors
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* @param {string} projectPath
|
|
378
|
+
* @returns {ReturnType<typeof buildGeneratorPolicyCheckPayload> & { rules: Array<{ name: string, ok: boolean, actual: string, expected: string, message: string, fix: string|null }> }}
|
|
379
|
+
*/
|
|
380
|
+
export function buildGeneratorPolicyExplainPayload(projectPath) {
|
|
381
|
+
const check = buildGeneratorPolicyCheckPayload(projectPath);
|
|
382
|
+
const policy = check.policy || effectiveGeneratorPolicy({ path: check.path, exists: false, policy: null, diagnostics: [] });
|
|
383
|
+
const rules = [];
|
|
384
|
+
rules.push(generatorPolicyRule(
|
|
385
|
+
"policy-file",
|
|
386
|
+
check.exists,
|
|
387
|
+
check.exists ? "present" : "missing",
|
|
388
|
+
"present",
|
|
389
|
+
check.exists
|
|
390
|
+
? "Project has a generator policy file."
|
|
391
|
+
: "Project is using the default generator policy.",
|
|
392
|
+
check.exists ? null : "Run `topogram generator policy init` after review."
|
|
393
|
+
));
|
|
394
|
+
for (const binding of check.bindings) {
|
|
395
|
+
const scope = packageScopeFromName(binding.packageName);
|
|
396
|
+
rules.push(generatorPolicyRule(
|
|
397
|
+
"allowed-package",
|
|
398
|
+
generatorPackageAllowed(policy, binding.packageName),
|
|
399
|
+
`${binding.packageName}${scope ? ` (${scope})` : ""}`,
|
|
400
|
+
[
|
|
401
|
+
`scopes=${policy.allowedPackageScopes.join(", ") || "(none)"}`,
|
|
402
|
+
`packages=${policy.allowedPackages.join(", ") || "(none)"}`
|
|
403
|
+
].join("; "),
|
|
404
|
+
`Runtime '${binding.runtimeId}' package-backed generator must be from an allowed package or scope.`,
|
|
405
|
+
`Run \`topogram generator policy pin ${binding.packageName}@${binding.version}\` after reviewing the generator package.`
|
|
406
|
+
));
|
|
407
|
+
const pinnedVersion = policy.pinnedVersions[binding.packageName] || policy.pinnedVersions[binding.generatorId] || null;
|
|
408
|
+
rules.push(generatorPolicyRule(
|
|
409
|
+
"pinned-version",
|
|
410
|
+
!pinnedVersion || pinnedVersion === binding.version,
|
|
411
|
+
binding.version,
|
|
412
|
+
pinnedVersion || "(unpinned)",
|
|
413
|
+
`Runtime '${binding.runtimeId}' generator version must match its policy pin when one exists.`,
|
|
414
|
+
`Run \`topogram generator policy pin ${binding.packageName}@${binding.version}\` after review.`
|
|
415
|
+
));
|
|
416
|
+
}
|
|
417
|
+
return {
|
|
418
|
+
...check,
|
|
419
|
+
rules
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* @param {string} projectPath
|
|
425
|
+
* @returns {ReturnType<typeof buildGeneratorPolicyExplainPayload> & { summary: { packageBackedGenerators: number, allowed: number, denied: number, pinned: number, unpinned: number, pinMismatches: number } }}
|
|
426
|
+
*/
|
|
427
|
+
export function buildGeneratorPolicyStatusPayload(projectPath) {
|
|
428
|
+
const explain = buildGeneratorPolicyExplainPayload(projectPath);
|
|
429
|
+
return {
|
|
430
|
+
...explain,
|
|
431
|
+
summary: {
|
|
432
|
+
packageBackedGenerators: explain.bindings.length,
|
|
433
|
+
allowed: explain.bindings.filter((binding) => binding.allowed).length,
|
|
434
|
+
denied: explain.bindings.filter((binding) => !binding.allowed).length,
|
|
435
|
+
pinned: explain.bindings.filter((binding) => Boolean(binding.pin.version)).length,
|
|
436
|
+
unpinned: explain.bindings.filter((binding) => !binding.pin.version).length,
|
|
437
|
+
pinMismatches: explain.bindings.filter((binding) => binding.pin.matches === false).length
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* @param {string} projectPath
|
|
444
|
+
* @returns {{ ok: boolean, path: string, policy: any, diagnostics: any[], errors: string[] }}
|
|
445
|
+
*/
|
|
446
|
+
export function buildGeneratorPolicyInitPayload(projectPath) {
|
|
447
|
+
const projectConfigInfo = loadProjectConfig(projectPath);
|
|
448
|
+
if (!projectConfigInfo) {
|
|
449
|
+
throw new Error("Cannot initialize generator policy without topogram.project.json.");
|
|
450
|
+
}
|
|
451
|
+
const policy = writeGeneratorPolicy(projectConfigInfo.configDir, defaultGeneratorPolicy());
|
|
452
|
+
return {
|
|
453
|
+
ok: true,
|
|
454
|
+
path: path.join(projectConfigInfo.configDir, GENERATOR_POLICY_FILE),
|
|
455
|
+
policy,
|
|
456
|
+
diagnostics: [],
|
|
457
|
+
errors: []
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* @param {ReturnType<typeof buildGeneratorPolicyInitPayload>} payload
|
|
463
|
+
* @returns {void}
|
|
464
|
+
*/
|
|
465
|
+
export function printGeneratorPolicyInitPayload(payload) {
|
|
466
|
+
console.log(`Wrote generator policy: ${payload.path}`);
|
|
467
|
+
console.log(`Allowed package scopes: ${payload.policy.allowedPackageScopes.join(", ") || "(none)"}`);
|
|
468
|
+
console.log(`Allowed packages: ${payload.policy.allowedPackages.join(", ") || "(none)"}`);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* @param {ReturnType<typeof buildGeneratorPolicyCheckPayload>} payload
|
|
473
|
+
* @returns {void}
|
|
474
|
+
*/
|
|
475
|
+
export function printGeneratorPolicyCheckPayload(payload) {
|
|
476
|
+
console.log(payload.ok ? "Generator policy check passed" : "Generator policy check failed");
|
|
477
|
+
console.log(`Policy: ${payload.path}`);
|
|
478
|
+
console.log(`Exists: ${payload.exists ? "yes" : "no"}`);
|
|
479
|
+
console.log(`Defaulted: ${payload.defaulted ? "yes" : "no"}`);
|
|
480
|
+
console.log(`Package-backed generators: ${payload.bindings.length}`);
|
|
481
|
+
for (const binding of payload.bindings) {
|
|
482
|
+
console.log(`- ${binding.runtimeId}: ${binding.generatorId}@${binding.version} via ${binding.packageName}`);
|
|
483
|
+
console.log(` npm package: ${binding.packageInfo.installedVersion || "(not installed)"}`);
|
|
484
|
+
if (binding.packageInfo.dependencySpec) {
|
|
485
|
+
console.log(` dependency: ${binding.packageInfo.dependencyField} ${binding.packageInfo.dependencySpec}`);
|
|
486
|
+
}
|
|
487
|
+
if (binding.packageInfo.lockfileVersion) {
|
|
488
|
+
console.log(` lockfile: ${formatGeneratorPackageLockfile(binding.packageInfo)}`);
|
|
489
|
+
} else if (binding.packageInfo.lockfileKind) {
|
|
490
|
+
console.log(` lockfile: ${formatGeneratorPackageLockfile(binding.packageInfo)}`);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
for (const diagnostic of payload.diagnostics) {
|
|
494
|
+
console.log(`[${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`);
|
|
495
|
+
if (diagnostic.path) {
|
|
496
|
+
console.log(` path: ${diagnostic.path}`);
|
|
497
|
+
}
|
|
498
|
+
if (diagnostic.suggestedFix) {
|
|
499
|
+
console.log(` fix: ${diagnostic.suggestedFix}`);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* @param {ReturnType<typeof buildGeneratorPolicyStatusPayload>} payload
|
|
506
|
+
* @returns {void}
|
|
507
|
+
*/
|
|
508
|
+
export function printGeneratorPolicyStatusPayload(payload) {
|
|
509
|
+
console.log(payload.ok ? "Generator policy status: allowed" : "Generator policy status: denied");
|
|
510
|
+
console.log(`Policy file: ${payload.path}`);
|
|
511
|
+
console.log(`Policy file exists: ${payload.exists ? "yes" : "no"}`);
|
|
512
|
+
console.log(`Default policy active: ${payload.defaulted ? "yes" : "no"}`);
|
|
513
|
+
console.log(`Package-backed generators: ${payload.summary.packageBackedGenerators}`);
|
|
514
|
+
console.log(`Allowed packages: ${payload.summary.allowed}`);
|
|
515
|
+
console.log(`Denied packages: ${payload.summary.denied}`);
|
|
516
|
+
console.log(`Pinned generators: ${payload.summary.pinned}`);
|
|
517
|
+
console.log(`Unpinned generators: ${payload.summary.unpinned}`);
|
|
518
|
+
console.log(`Pin mismatches: ${payload.summary.pinMismatches}`);
|
|
519
|
+
if (payload.bindings.length > 0) {
|
|
520
|
+
console.log("");
|
|
521
|
+
console.log("Generator packages:");
|
|
522
|
+
}
|
|
523
|
+
for (const binding of payload.bindings) {
|
|
524
|
+
console.log(`- ${binding.runtimeId}: ${binding.generatorId}@${binding.version} via ${binding.packageName}`);
|
|
525
|
+
console.log(` allowed: ${binding.allowed ? "yes" : "no"}`);
|
|
526
|
+
console.log(` npm package: ${binding.packageInfo.installedVersion || "(not installed)"}`);
|
|
527
|
+
console.log(` dependency: ${binding.packageInfo.dependencyField && binding.packageInfo.dependencySpec ? `${binding.packageInfo.dependencyField} ${binding.packageInfo.dependencySpec}` : "(not declared)"}`);
|
|
528
|
+
console.log(` lockfile: ${formatGeneratorPackageLockfile(binding.packageInfo)}`);
|
|
529
|
+
console.log(` policy pin: ${binding.pin.version ? `${binding.pin.key}@${binding.pin.version}` : "(none)"}`);
|
|
530
|
+
}
|
|
531
|
+
for (const diagnostic of payload.diagnostics) {
|
|
532
|
+
const label = diagnostic.severity === "warning" ? "Warning" : "Error";
|
|
533
|
+
console.log(`${label}: ${diagnostic.code}: ${diagnostic.message}`);
|
|
534
|
+
if (diagnostic.packageVersion) {
|
|
535
|
+
console.log(` package version: ${diagnostic.packageVersion}`);
|
|
536
|
+
}
|
|
537
|
+
if (diagnostic.packageDependencySpec) {
|
|
538
|
+
console.log(` dependency: ${diagnostic.packageDependencyField} ${diagnostic.packageDependencySpec}`);
|
|
539
|
+
}
|
|
540
|
+
if (diagnostic.packageLockfilePath) {
|
|
541
|
+
console.log(` lockfile: ${path.basename(diagnostic.packageLockfilePath)}${diagnostic.packageLockVersion ? ` ${diagnostic.packageLockVersion}` : ""}`);
|
|
542
|
+
}
|
|
543
|
+
if (diagnostic.suggestedFix) {
|
|
544
|
+
console.log(` fix: ${diagnostic.suggestedFix}`);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* @param {ReturnType<typeof buildGeneratorPolicyExplainPayload>} payload
|
|
551
|
+
* @returns {void}
|
|
552
|
+
*/
|
|
553
|
+
export function printGeneratorPolicyExplainPayload(payload) {
|
|
554
|
+
console.log(payload.ok ? "Generator policy: allowed" : "Generator policy: denied");
|
|
555
|
+
console.log(payload.ok
|
|
556
|
+
? "Decision: package-backed generators are allowed by this project's generator policy."
|
|
557
|
+
: "Decision: one or more package-backed generators are blocked by this project's generator policy.");
|
|
558
|
+
console.log(`Policy file: ${payload.path}`);
|
|
559
|
+
console.log(`Policy file exists: ${payload.exists ? "yes" : "no"}`);
|
|
560
|
+
console.log(`Default policy active: ${payload.defaulted ? "yes" : "no"}`);
|
|
561
|
+
if (payload.bindings.length > 0) {
|
|
562
|
+
console.log("");
|
|
563
|
+
console.log("Package-backed generators:");
|
|
564
|
+
for (const binding of payload.bindings) {
|
|
565
|
+
console.log(`- ${binding.runtimeId}: ${binding.generatorId}@${binding.version} via ${binding.packageName}`);
|
|
566
|
+
console.log(` npm package: ${binding.packageInfo.installedVersion || "(not installed)"}`);
|
|
567
|
+
if (binding.packageInfo.dependencySpec) {
|
|
568
|
+
console.log(` dependency: ${binding.packageInfo.dependencyField} ${binding.packageInfo.dependencySpec}`);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
if (payload.rules.length > 0) {
|
|
573
|
+
console.log("");
|
|
574
|
+
console.log("Policy checks:");
|
|
575
|
+
}
|
|
576
|
+
for (const rule of payload.rules) {
|
|
577
|
+
console.log(`${rule.ok ? "PASS" : "FAIL"} ${generatorPolicyRuleLabel(rule.name)}: ${rule.message}`);
|
|
578
|
+
console.log(` actual: ${rule.actual}`);
|
|
579
|
+
console.log(` expected: ${rule.expected}`);
|
|
580
|
+
if (!rule.ok && rule.fix) {
|
|
581
|
+
console.log(` fix: ${rule.fix}`);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
for (const diagnostic of payload.diagnostics) {
|
|
585
|
+
const label = diagnostic.severity === "warning" ? "Warning" : "Error";
|
|
586
|
+
console.log(`${label}: ${diagnostic.code}: ${diagnostic.message}`);
|
|
587
|
+
if (diagnostic.suggestedFix) {
|
|
588
|
+
console.log(` fix: ${diagnostic.suggestedFix}`);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* @param {string} projectPath
|
|
595
|
+
* @param {string|null|undefined} spec
|
|
596
|
+
* @returns {{ ok: boolean, path: string, policy: any, pinned: Array<{ packageName: string, version: string }>, diagnostics: any[], errors: string[] }}
|
|
597
|
+
*/
|
|
598
|
+
export function buildGeneratorPolicyPinPayload(projectPath, spec) {
|
|
599
|
+
const projectConfigInfo = loadProjectConfig(projectPath);
|
|
600
|
+
if (!projectConfigInfo) {
|
|
601
|
+
const diagnostic = {
|
|
602
|
+
code: "generator_policy_project_missing",
|
|
603
|
+
severity: "error",
|
|
604
|
+
message: "Cannot pin generator policy without topogram.project.json.",
|
|
605
|
+
path: path.resolve(projectPath),
|
|
606
|
+
suggestedFix: "Run this command in a Topogram project.",
|
|
607
|
+
step: "generator-policy"
|
|
608
|
+
};
|
|
609
|
+
return {
|
|
610
|
+
ok: false,
|
|
611
|
+
path: path.join(path.resolve(projectPath), GENERATOR_POLICY_FILE),
|
|
612
|
+
policy: null,
|
|
613
|
+
pinned: [],
|
|
614
|
+
diagnostics: [diagnostic],
|
|
615
|
+
errors: [diagnostic.message]
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
const policyInfo = loadGeneratorPolicy(projectConfigInfo.configDir);
|
|
619
|
+
const policyDiagnostics = /** @type {any[]} */ (policyInfo.diagnostics || []);
|
|
620
|
+
if (policyDiagnostics.some((diagnostic) => diagnostic.severity === "error")) {
|
|
621
|
+
const errors = policyDiagnostics.filter((diagnostic) => diagnostic.severity === "error").map((diagnostic) => diagnostic.message);
|
|
622
|
+
return {
|
|
623
|
+
ok: false,
|
|
624
|
+
path: policyInfo.path,
|
|
625
|
+
policy: policyInfo.policy,
|
|
626
|
+
pinned: [],
|
|
627
|
+
diagnostics: policyDiagnostics,
|
|
628
|
+
errors
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
let pins = [];
|
|
632
|
+
try {
|
|
633
|
+
pins = spec
|
|
634
|
+
? [parseGeneratorPolicyPin(spec)]
|
|
635
|
+
: packageBackedGeneratorBindings(projectConfigInfo.config).map((binding) => ({
|
|
636
|
+
packageName: binding.packageName,
|
|
637
|
+
version: binding.version
|
|
638
|
+
}));
|
|
639
|
+
} catch (error) {
|
|
640
|
+
const diagnostic = {
|
|
641
|
+
code: "generator_policy_pin_invalid",
|
|
642
|
+
severity: "error",
|
|
643
|
+
message: error instanceof Error ? error.message : String(error),
|
|
644
|
+
path: policyInfo.path,
|
|
645
|
+
suggestedFix: "Pass a pin such as @topogram/generator-react-web@1.",
|
|
646
|
+
step: "generator-policy"
|
|
647
|
+
};
|
|
648
|
+
return {
|
|
649
|
+
ok: false,
|
|
650
|
+
path: policyInfo.path,
|
|
651
|
+
policy: policyInfo.policy,
|
|
652
|
+
pinned: [],
|
|
653
|
+
diagnostics: [diagnostic],
|
|
654
|
+
errors: [diagnostic.message]
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
if (pins.length === 0) {
|
|
658
|
+
const diagnostic = {
|
|
659
|
+
code: "generator_policy_pin_no_generators",
|
|
660
|
+
severity: "error",
|
|
661
|
+
message: "No package-backed topology generator bindings are available to pin.",
|
|
662
|
+
path: projectConfigInfo.configPath,
|
|
663
|
+
suggestedFix: "Pass an explicit pin such as @topogram/generator-react-web@1, or use bundled generators.",
|
|
664
|
+
step: "generator-policy"
|
|
665
|
+
};
|
|
666
|
+
return {
|
|
667
|
+
ok: false,
|
|
668
|
+
path: policyInfo.path,
|
|
669
|
+
policy: policyInfo.policy,
|
|
670
|
+
pinned: [],
|
|
671
|
+
diagnostics: [diagnostic],
|
|
672
|
+
errors: [diagnostic.message]
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
const policy = policyInfo.policy || defaultGeneratorPolicy();
|
|
676
|
+
const allowedPackages = [...policy.allowedPackages];
|
|
677
|
+
const allowedPackageScopes = [...policy.allowedPackageScopes];
|
|
678
|
+
const pinnedVersions = { ...policy.pinnedVersions };
|
|
679
|
+
for (const pin of pins) {
|
|
680
|
+
if (!allowedPackages.includes(pin.packageName)) {
|
|
681
|
+
allowedPackages.push(pin.packageName);
|
|
682
|
+
}
|
|
683
|
+
pinnedVersions[pin.packageName] = pin.version;
|
|
684
|
+
}
|
|
685
|
+
const nextPolicy = {
|
|
686
|
+
...policy,
|
|
687
|
+
allowedPackageScopes,
|
|
688
|
+
allowedPackages,
|
|
689
|
+
pinnedVersions
|
|
690
|
+
};
|
|
691
|
+
writeGeneratorPolicy(projectConfigInfo.configDir, nextPolicy);
|
|
692
|
+
return {
|
|
693
|
+
ok: true,
|
|
694
|
+
path: path.join(projectConfigInfo.configDir, GENERATOR_POLICY_FILE),
|
|
695
|
+
policy: nextPolicy,
|
|
696
|
+
pinned: pins,
|
|
697
|
+
diagnostics: [],
|
|
698
|
+
errors: []
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
/**
|
|
703
|
+
* @param {{ ok: boolean, path: string, pinned: Array<{ packageName: string, version: string }>, diagnostics: any[] }} payload
|
|
704
|
+
* @returns {void}
|
|
705
|
+
*/
|
|
706
|
+
export function printGeneratorPolicyPinPayload(payload) {
|
|
707
|
+
console.log(payload.ok ? "Generator policy pin updated" : "Generator policy pin failed");
|
|
708
|
+
console.log(`Policy: ${payload.path}`);
|
|
709
|
+
for (const pin of payload.pinned) {
|
|
710
|
+
console.log(`Pinned: ${pin.packageName}@${pin.version}`);
|
|
711
|
+
}
|
|
712
|
+
for (const diagnostic of payload.diagnostics) {
|
|
713
|
+
console.log(`[${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`);
|
|
714
|
+
if (diagnostic.path) {
|
|
715
|
+
console.log(` path: ${diagnostic.path}`);
|
|
716
|
+
}
|
|
717
|
+
if (diagnostic.suggestedFix) {
|
|
718
|
+
console.log(` fix: ${diagnostic.suggestedFix}`);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/**
|
|
724
|
+
* @param {{
|
|
725
|
+
* commandArgs: Record<string, any>,
|
|
726
|
+
* inputPath: string|null|undefined,
|
|
727
|
+
* json: boolean
|
|
728
|
+
* }} context
|
|
729
|
+
* @returns {number}
|
|
730
|
+
*/
|
|
731
|
+
export function runGeneratorPolicyCommand(context) {
|
|
732
|
+
const { commandArgs, inputPath, json } = context;
|
|
733
|
+
const projectPath = inputPath || "./topogram";
|
|
734
|
+
if (commandArgs.generatorPolicyCommand === "init") {
|
|
735
|
+
const payload = buildGeneratorPolicyInitPayload(projectPath);
|
|
736
|
+
if (json) {
|
|
737
|
+
console.log(stableStringify(payload));
|
|
738
|
+
} else {
|
|
739
|
+
printGeneratorPolicyInitPayload(payload);
|
|
740
|
+
}
|
|
741
|
+
return 0;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
if (commandArgs.generatorPolicyCommand === "status") {
|
|
745
|
+
const payload = buildGeneratorPolicyStatusPayload(projectPath);
|
|
746
|
+
if (json) {
|
|
747
|
+
console.log(stableStringify(payload));
|
|
748
|
+
} else {
|
|
749
|
+
printGeneratorPolicyStatusPayload(payload);
|
|
750
|
+
}
|
|
751
|
+
return payload.ok ? 0 : 1;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
if (commandArgs.generatorPolicyCommand === "check") {
|
|
755
|
+
const payload = buildGeneratorPolicyCheckPayload(projectPath);
|
|
756
|
+
if (json) {
|
|
757
|
+
console.log(stableStringify(payload));
|
|
758
|
+
} else {
|
|
759
|
+
printGeneratorPolicyCheckPayload(payload);
|
|
760
|
+
}
|
|
761
|
+
return payload.ok ? 0 : 1;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
if (commandArgs.generatorPolicyCommand === "explain") {
|
|
765
|
+
const payload = buildGeneratorPolicyExplainPayload(projectPath);
|
|
766
|
+
if (json) {
|
|
767
|
+
console.log(stableStringify(payload));
|
|
768
|
+
} else {
|
|
769
|
+
printGeneratorPolicyExplainPayload(payload);
|
|
770
|
+
}
|
|
771
|
+
return payload.ok ? 0 : 1;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
if (commandArgs.generatorPolicyCommand === "pin") {
|
|
775
|
+
const payload = buildGeneratorPolicyPinPayload(projectPath, commandArgs.generatorPolicyPinSpec);
|
|
776
|
+
if (json) {
|
|
777
|
+
console.log(stableStringify(payload));
|
|
778
|
+
} else {
|
|
779
|
+
printGeneratorPolicyPinPayload(payload);
|
|
780
|
+
}
|
|
781
|
+
return payload.ok ? 0 : 1;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
throw new Error(`Unknown generator policy command '${commandArgs.generatorPolicyCommand}'`);
|
|
785
|
+
}
|