@topogram/cli 0.3.42 → 0.3.44

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topogram/cli",
3
- "version": "0.3.42",
3
+ "version": "0.3.44",
4
4
  "description": "Topogram CLI for checking Topogram workspaces and generating app bundles.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
package/src/cli.js CHANGED
@@ -219,6 +219,7 @@ function printUsage(options = {}) {
219
219
  console.log(" or: topogram generator show <id-or-package> [--json]");
220
220
  console.log(" or: topogram generator check <path-or-package> [--json]");
221
221
  console.log(" or: topogram generator policy init [path] [--json]");
222
+ console.log(" or: topogram generator policy status [path] [--json]");
222
223
  console.log(" or: topogram generator policy check [path] [--json]");
223
224
  console.log(" or: topogram generator policy explain [path] [--json]");
224
225
  console.log(" or: topogram generator policy pin [package@version] [path] [--json]");
@@ -586,6 +587,7 @@ function printGeneratorHelp() {
586
587
  console.log(" or: topogram generator show <id-or-package> [--json]");
587
588
  console.log(" or: topogram generator check <path-or-package> [--json]");
588
589
  console.log(" or: topogram generator policy init [path] [--json]");
590
+ console.log(" or: topogram generator policy status [path] [--json]");
589
591
  console.log(" or: topogram generator policy check [path] [--json]");
590
592
  console.log(" or: topogram generator policy explain [path] [--json]");
591
593
  console.log(" or: topogram generator policy pin [package@version] [path] [--json]");
@@ -607,6 +609,7 @@ function printGeneratorHelp() {
607
609
  console.log(" topogram generator check ./generator-package");
608
610
  console.log(" topogram generator check @scope/topogram-generator-web --json");
609
611
  console.log(" topogram generator policy init");
612
+ console.log(" topogram generator policy status --json");
610
613
  console.log(" topogram generator policy check --json");
611
614
  console.log(" topogram generator policy pin @topogram/generator-react-web@1");
612
615
  }
@@ -1586,9 +1589,270 @@ function effectiveGeneratorPolicy(policyInfo) {
1586
1589
  };
1587
1590
  }
1588
1591
 
1592
+ /**
1593
+ * @param {string} filePath
1594
+ * @returns {any|null}
1595
+ */
1596
+ function readJsonIfPresent(filePath) {
1597
+ if (!fs.existsSync(filePath)) {
1598
+ return null;
1599
+ }
1600
+ try {
1601
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
1602
+ } catch {
1603
+ return null;
1604
+ }
1605
+ }
1606
+
1607
+ /**
1608
+ * @param {Record<string, any>|null} projectPackage
1609
+ * @param {string} packageName
1610
+ * @returns {{ field: string|null, spec: string|null }}
1611
+ */
1612
+ function dependencySpecForPackage(projectPackage, packageName) {
1613
+ const dependencyFields = ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"];
1614
+ for (const field of dependencyFields) {
1615
+ const dependencies = projectPackage?.[field];
1616
+ if (dependencies && typeof dependencies === "object" && typeof dependencies[packageName] === "string") {
1617
+ return {
1618
+ field,
1619
+ spec: dependencies[packageName]
1620
+ };
1621
+ }
1622
+ }
1623
+ return {
1624
+ field: null,
1625
+ spec: null
1626
+ };
1627
+ }
1628
+
1629
+ /**
1630
+ * @param {Record<string, any>|null} lockfile
1631
+ * @param {string} packageName
1632
+ * @returns {{ version: string|null, resolved: string|null, integrity: string|null, entryPath: string|null }}
1633
+ */
1634
+ function npmLockfileInfoForPackage(lockfile, packageName) {
1635
+ const packageEntryPath = `node_modules/${packageName}`;
1636
+ const packageEntry = lockfile?.packages?.[packageEntryPath];
1637
+ if (packageEntry && typeof packageEntry === "object") {
1638
+ return {
1639
+ version: typeof packageEntry.version === "string" ? packageEntry.version : null,
1640
+ resolved: typeof packageEntry.resolved === "string" ? packageEntry.resolved : null,
1641
+ integrity: typeof packageEntry.integrity === "string" ? packageEntry.integrity : null,
1642
+ entryPath: packageEntryPath
1643
+ };
1644
+ }
1645
+ const dependencyEntry = lockfile?.dependencies?.[packageName];
1646
+ if (dependencyEntry && typeof dependencyEntry === "object") {
1647
+ return {
1648
+ version: typeof dependencyEntry.version === "string" ? dependencyEntry.version : null,
1649
+ resolved: typeof dependencyEntry.resolved === "string" ? dependencyEntry.resolved : null,
1650
+ integrity: typeof dependencyEntry.integrity === "string" ? dependencyEntry.integrity : null,
1651
+ entryPath: packageName
1652
+ };
1653
+ }
1654
+ return {
1655
+ version: null,
1656
+ resolved: null,
1657
+ integrity: null,
1658
+ entryPath: null
1659
+ };
1660
+ }
1661
+
1662
+ /**
1663
+ * @param {string} projectRoot
1664
+ * @returns {{ kind: "npm"|"pnpm"|"yarn"|"bun"|null, path: string|null, data: Record<string, any>|null, note: string|null }}
1665
+ */
1666
+ function lockfileMetadataForProject(projectRoot) {
1667
+ const npmLockfilePath = path.join(projectRoot, "package-lock.json");
1668
+ if (fs.existsSync(npmLockfilePath)) {
1669
+ return {
1670
+ kind: "npm",
1671
+ path: npmLockfilePath,
1672
+ data: readJsonIfPresent(npmLockfilePath),
1673
+ note: null
1674
+ };
1675
+ }
1676
+ const candidates = [
1677
+ { kind: "pnpm", file: "pnpm-lock.yaml" },
1678
+ { kind: "yarn", file: "yarn.lock" },
1679
+ { kind: "bun", file: "bun.lock" },
1680
+ { kind: "bun", file: "bun.lockb" }
1681
+ ];
1682
+ for (const candidate of candidates) {
1683
+ const lockfilePath = path.join(projectRoot, candidate.file);
1684
+ if (fs.existsSync(lockfilePath)) {
1685
+ return {
1686
+ kind: /** @type {"pnpm"|"yarn"|"bun"} */ (candidate.kind),
1687
+ path: lockfilePath,
1688
+ data: null,
1689
+ note: `${candidate.file} found; package versions are not inspected by this command.`
1690
+ };
1691
+ }
1692
+ }
1693
+ return {
1694
+ kind: null,
1695
+ path: null,
1696
+ data: null,
1697
+ note: null
1698
+ };
1699
+ }
1700
+
1701
+ /**
1702
+ * @param {string} projectRoot
1703
+ * @param {string} packageName
1704
+ * @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 }}
1705
+ */
1706
+ function packageInfoForGenerator(projectRoot, packageName) {
1707
+ const projectPackage = readJsonIfPresent(path.join(projectRoot, "package.json"));
1708
+ const dependency = dependencySpecForPackage(projectPackage, packageName);
1709
+ const lockfile = lockfileMetadataForProject(projectRoot);
1710
+ const lockfileInfo = lockfile.kind === "npm"
1711
+ ? npmLockfileInfoForPackage(lockfile.data, packageName)
1712
+ : {
1713
+ version: null,
1714
+ resolved: null,
1715
+ integrity: null,
1716
+ entryPath: null
1717
+ };
1718
+ const installedPackageJsonPath = path.join(projectRoot, "node_modules", ...packageName.split("/"), "package.json");
1719
+ const installedPackage = readJsonIfPresent(installedPackageJsonPath);
1720
+ return {
1721
+ dependencyField: dependency.field,
1722
+ dependencySpec: dependency.spec,
1723
+ installedVersion: typeof installedPackage?.version === "string" ? installedPackage.version : null,
1724
+ installedPackageJsonPath: installedPackage ? installedPackageJsonPath : null,
1725
+ lockfileKind: lockfile.kind,
1726
+ lockfilePath: lockfile.path,
1727
+ lockfileVersion: lockfileInfo.version,
1728
+ lockfileResolved: lockfileInfo.resolved,
1729
+ lockfileIntegrity: lockfileInfo.integrity,
1730
+ lockfileEntryPath: lockfileInfo.entryPath,
1731
+ lockfileNote: lockfile.note
1732
+ };
1733
+ }
1734
+
1735
+ /**
1736
+ * @param {ReturnType<typeof packageInfoForGenerator>} packageInfo
1737
+ * @returns {string}
1738
+ */
1739
+ function formatGeneratorPackageLockfile(packageInfo) {
1740
+ if (!packageInfo.lockfileKind || !packageInfo.lockfilePath) {
1741
+ return "(not found)";
1742
+ }
1743
+ const label = path.basename(packageInfo.lockfilePath);
1744
+ if (packageInfo.lockfileVersion) {
1745
+ return `${packageInfo.lockfileKind} ${packageInfo.lockfileVersion}`;
1746
+ }
1747
+ return `${label} (version not inspected)`;
1748
+ }
1749
+
1750
+ /**
1751
+ * @param {string} projectRoot
1752
+ * @param {import("./generator-policy.js").GeneratorPolicy} policy
1753
+ * @param {ReturnType<typeof packageBackedGeneratorBindings>[number]} binding
1754
+ * @returns {ReturnType<typeof packageBackedGeneratorBindings>[number] & { allowed: boolean, packageInfo: ReturnType<typeof packageInfoForGenerator>, pin: { key: string|null, version: string|null, matches: boolean|null } }}
1755
+ */
1756
+ function generatorPolicyBindingStatus(projectRoot, policy, binding) {
1757
+ const packagePin = policy.pinnedVersions[binding.packageName] || null;
1758
+ const generatorPin = policy.pinnedVersions[binding.generatorId] || null;
1759
+ const pinnedVersion = packagePin || generatorPin;
1760
+ return {
1761
+ ...binding,
1762
+ allowed: generatorPackageAllowed(policy, binding.packageName),
1763
+ packageInfo: packageInfoForGenerator(projectRoot, binding.packageName),
1764
+ pin: {
1765
+ key: packagePin ? binding.packageName : generatorPin ? binding.generatorId : null,
1766
+ version: pinnedVersion,
1767
+ matches: pinnedVersion ? pinnedVersion === binding.version : null
1768
+ }
1769
+ };
1770
+ }
1771
+
1772
+ /**
1773
+ * @param {any[]} diagnostics
1774
+ * @param {Array<ReturnType<typeof generatorPolicyBindingStatus>>} bindings
1775
+ * @returns {any[]}
1776
+ */
1777
+ function annotateGeneratorPolicyDiagnostics(diagnostics, bindings) {
1778
+ return diagnostics.map((diagnostic) => {
1779
+ const binding = bindings.find((item) => (
1780
+ item.packageName === diagnostic.packageName &&
1781
+ (!diagnostic.componentId || item.componentId === diagnostic.componentId)
1782
+ ));
1783
+ if (!binding) {
1784
+ return diagnostic;
1785
+ }
1786
+ return {
1787
+ ...diagnostic,
1788
+ packageVersion: binding.packageInfo.installedVersion || binding.packageInfo.lockfileVersion || null,
1789
+ packageDependencyField: binding.packageInfo.dependencyField,
1790
+ packageDependencySpec: binding.packageInfo.dependencySpec,
1791
+ packageLockfileKind: binding.packageInfo.lockfileKind,
1792
+ packageLockfilePath: binding.packageInfo.lockfilePath,
1793
+ packageLockVersion: binding.packageInfo.lockfileVersion
1794
+ };
1795
+ });
1796
+ }
1797
+
1798
+ /**
1799
+ * @param {Array<ReturnType<typeof generatorPolicyBindingStatus>>} bindings
1800
+ * @returns {any[]}
1801
+ */
1802
+ function generatorPolicyPackageMetadataDiagnostics(bindings) {
1803
+ const diagnostics = [];
1804
+ for (const binding of bindings) {
1805
+ if (!binding.packageInfo.dependencySpec) {
1806
+ diagnostics.push({
1807
+ code: "generator_package_dependency_missing",
1808
+ severity: "warning",
1809
+ message: `Component '${binding.componentId}' generator package '${binding.packageName}' is not declared in package.json dependencies.`,
1810
+ path: binding.packageInfo.installedPackageJsonPath,
1811
+ suggestedFix: `Declare '${binding.packageName}' in package.json devDependencies so generator adoption is visible in package review.`,
1812
+ step: "generator-policy",
1813
+ componentId: binding.componentId,
1814
+ generatorId: binding.generatorId,
1815
+ packageName: binding.packageName,
1816
+ version: binding.version,
1817
+ packageVersion: binding.packageInfo.installedVersion || binding.packageInfo.lockfileVersion || null,
1818
+ packageDependencyField: binding.packageInfo.dependencyField,
1819
+ packageDependencySpec: binding.packageInfo.dependencySpec,
1820
+ packageLockfileKind: binding.packageInfo.lockfileKind,
1821
+ packageLockfilePath: binding.packageInfo.lockfilePath,
1822
+ packageLockVersion: binding.packageInfo.lockfileVersion
1823
+ });
1824
+ }
1825
+ if (
1826
+ binding.packageInfo.installedVersion &&
1827
+ binding.packageInfo.lockfileVersion &&
1828
+ binding.packageInfo.installedVersion !== binding.packageInfo.lockfileVersion
1829
+ ) {
1830
+ diagnostics.push({
1831
+ code: "generator_package_version_drift",
1832
+ severity: "warning",
1833
+ message: `Component '${binding.componentId}' generator package '${binding.packageName}' is installed at '${binding.packageInfo.installedVersion}', but package-lock records '${binding.packageInfo.lockfileVersion}'.`,
1834
+ path: binding.packageInfo.lockfilePath,
1835
+ suggestedFix: "Run the package manager install command and review the resulting lockfile before pinning generator policy.",
1836
+ step: "generator-policy",
1837
+ componentId: binding.componentId,
1838
+ generatorId: binding.generatorId,
1839
+ packageName: binding.packageName,
1840
+ version: binding.version,
1841
+ packageVersion: binding.packageInfo.installedVersion,
1842
+ packageDependencyField: binding.packageInfo.dependencyField,
1843
+ packageDependencySpec: binding.packageInfo.dependencySpec,
1844
+ packageLockfileKind: binding.packageInfo.lockfileKind,
1845
+ packageLockfilePath: binding.packageInfo.lockfilePath,
1846
+ packageLockVersion: binding.packageInfo.lockfileVersion
1847
+ });
1848
+ }
1849
+ }
1850
+ return diagnostics;
1851
+ }
1852
+
1589
1853
  /**
1590
1854
  * @param {string} projectPath
1591
- * @returns {{ ok: boolean, path: string, exists: boolean, policy: any, defaulted: boolean, bindings: ReturnType<typeof packageBackedGeneratorBindings>, diagnostics: any[], errors: string[] }}
1855
+ * @returns {{ ok: boolean, path: string, exists: boolean, policy: any, defaulted: boolean, bindings: Array<ReturnType<typeof generatorPolicyBindingStatus>>, diagnostics: any[], errors: string[] }}
1592
1856
  */
1593
1857
  function buildGeneratorPolicyCheckPayload(projectPath) {
1594
1858
  const projectConfigInfo = loadProjectConfig(projectPath);
@@ -1613,7 +1877,9 @@ function buildGeneratorPolicyCheckPayload(projectPath) {
1613
1877
  };
1614
1878
  }
1615
1879
  const policyInfo = loadGeneratorPolicy(projectConfigInfo.configDir);
1616
- const bindings = packageBackedGeneratorBindings(projectConfigInfo.config);
1880
+ const rawBindings = packageBackedGeneratorBindings(projectConfigInfo.config);
1881
+ const policy = policyInfo.policy || effectiveGeneratorPolicy(policyInfo);
1882
+ const bindings = rawBindings.map((binding) => generatorPolicyBindingStatus(projectConfigInfo.configDir, policy, binding));
1617
1883
  const diagnostics = [];
1618
1884
  if (!policyInfo.exists) {
1619
1885
  diagnostics.push({
@@ -1625,16 +1891,18 @@ function buildGeneratorPolicyCheckPayload(projectPath) {
1625
1891
  step: "generator-policy"
1626
1892
  });
1627
1893
  }
1628
- diagnostics.push(...generatorPolicyDiagnosticsForBindings(policyInfo, bindings, "generator-policy"));
1629
- const errors = diagnostics.filter((diagnostic) => diagnostic.severity === "error").map((diagnostic) => diagnostic.message);
1894
+ diagnostics.push(...generatorPolicyDiagnosticsForBindings(policyInfo, rawBindings, "generator-policy"));
1895
+ diagnostics.push(...generatorPolicyPackageMetadataDiagnostics(bindings));
1896
+ const annotatedDiagnostics = annotateGeneratorPolicyDiagnostics(diagnostics, bindings);
1897
+ const errors = annotatedDiagnostics.filter((diagnostic) => diagnostic.severity === "error").map((diagnostic) => diagnostic.message);
1630
1898
  return {
1631
1899
  ok: errors.length === 0,
1632
1900
  path: policyInfo.path,
1633
1901
  exists: policyInfo.exists,
1634
- policy: policyInfo.policy || effectiveGeneratorPolicy(policyInfo),
1902
+ policy,
1635
1903
  defaulted: !policyInfo.exists,
1636
1904
  bindings,
1637
- diagnostics,
1905
+ diagnostics: annotatedDiagnostics,
1638
1906
  errors
1639
1907
  };
1640
1908
  }
@@ -1686,6 +1954,25 @@ function buildGeneratorPolicyExplainPayload(projectPath) {
1686
1954
  };
1687
1955
  }
1688
1956
 
1957
+ /**
1958
+ * @param {string} projectPath
1959
+ * @returns {ReturnType<typeof buildGeneratorPolicyExplainPayload> & { summary: { packageBackedGenerators: number, allowed: number, denied: number, pinned: number, unpinned: number, pinMismatches: number } }}
1960
+ */
1961
+ function buildGeneratorPolicyStatusPayload(projectPath) {
1962
+ const explain = buildGeneratorPolicyExplainPayload(projectPath);
1963
+ return {
1964
+ ...explain,
1965
+ summary: {
1966
+ packageBackedGenerators: explain.bindings.length,
1967
+ allowed: explain.bindings.filter((binding) => binding.allowed).length,
1968
+ denied: explain.bindings.filter((binding) => !binding.allowed).length,
1969
+ pinned: explain.bindings.filter((binding) => Boolean(binding.pin.version)).length,
1970
+ unpinned: explain.bindings.filter((binding) => !binding.pin.version).length,
1971
+ pinMismatches: explain.bindings.filter((binding) => binding.pin.matches === false).length
1972
+ }
1973
+ };
1974
+ }
1975
+
1689
1976
  /**
1690
1977
  * @param {ReturnType<typeof buildGeneratorPolicyCheckPayload>} payload
1691
1978
  * @returns {void}
@@ -1698,6 +1985,15 @@ function printGeneratorPolicyCheckPayload(payload) {
1698
1985
  console.log(`Package-backed generators: ${payload.bindings.length}`);
1699
1986
  for (const binding of payload.bindings) {
1700
1987
  console.log(`- ${binding.componentId}: ${binding.generatorId}@${binding.version} via ${binding.packageName}`);
1988
+ console.log(` npm package: ${binding.packageInfo.installedVersion || "(not installed)"}`);
1989
+ if (binding.packageInfo.dependencySpec) {
1990
+ console.log(` dependency: ${binding.packageInfo.dependencyField} ${binding.packageInfo.dependencySpec}`);
1991
+ }
1992
+ if (binding.packageInfo.lockfileVersion) {
1993
+ console.log(` lockfile: ${formatGeneratorPackageLockfile(binding.packageInfo)}`);
1994
+ } else if (binding.packageInfo.lockfileKind) {
1995
+ console.log(` lockfile: ${formatGeneratorPackageLockfile(binding.packageInfo)}`);
1996
+ }
1701
1997
  }
1702
1998
  for (const diagnostic of payload.diagnostics) {
1703
1999
  console.log(`[${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`);
@@ -1710,6 +2006,51 @@ function printGeneratorPolicyCheckPayload(payload) {
1710
2006
  }
1711
2007
  }
1712
2008
 
2009
+ /**
2010
+ * @param {ReturnType<typeof buildGeneratorPolicyStatusPayload>} payload
2011
+ * @returns {void}
2012
+ */
2013
+ function printGeneratorPolicyStatusPayload(payload) {
2014
+ console.log(payload.ok ? "Generator policy status: allowed" : "Generator policy status: denied");
2015
+ console.log(`Policy file: ${payload.path}`);
2016
+ console.log(`Policy file exists: ${payload.exists ? "yes" : "no"}`);
2017
+ console.log(`Default policy active: ${payload.defaulted ? "yes" : "no"}`);
2018
+ console.log(`Package-backed generators: ${payload.summary.packageBackedGenerators}`);
2019
+ console.log(`Allowed packages: ${payload.summary.allowed}`);
2020
+ console.log(`Denied packages: ${payload.summary.denied}`);
2021
+ console.log(`Pinned generators: ${payload.summary.pinned}`);
2022
+ console.log(`Unpinned generators: ${payload.summary.unpinned}`);
2023
+ console.log(`Pin mismatches: ${payload.summary.pinMismatches}`);
2024
+ if (payload.bindings.length > 0) {
2025
+ console.log("");
2026
+ console.log("Generator packages:");
2027
+ }
2028
+ for (const binding of payload.bindings) {
2029
+ console.log(`- ${binding.componentId}: ${binding.generatorId}@${binding.version} via ${binding.packageName}`);
2030
+ console.log(` allowed: ${binding.allowed ? "yes" : "no"}`);
2031
+ console.log(` npm package: ${binding.packageInfo.installedVersion || "(not installed)"}`);
2032
+ console.log(` dependency: ${binding.packageInfo.dependencyField && binding.packageInfo.dependencySpec ? `${binding.packageInfo.dependencyField} ${binding.packageInfo.dependencySpec}` : "(not declared)"}`);
2033
+ console.log(` lockfile: ${formatGeneratorPackageLockfile(binding.packageInfo)}`);
2034
+ console.log(` policy pin: ${binding.pin.version ? `${binding.pin.key}@${binding.pin.version}` : "(none)"}`);
2035
+ }
2036
+ for (const diagnostic of payload.diagnostics) {
2037
+ const label = diagnostic.severity === "warning" ? "Warning" : "Error";
2038
+ console.log(`${label}: ${diagnostic.code}: ${diagnostic.message}`);
2039
+ if (diagnostic.packageVersion) {
2040
+ console.log(` package version: ${diagnostic.packageVersion}`);
2041
+ }
2042
+ if (diagnostic.packageDependencySpec) {
2043
+ console.log(` dependency: ${diagnostic.packageDependencyField} ${diagnostic.packageDependencySpec}`);
2044
+ }
2045
+ if (diagnostic.packageLockfilePath) {
2046
+ console.log(` lockfile: ${path.basename(diagnostic.packageLockfilePath)}${diagnostic.packageLockVersion ? ` ${diagnostic.packageLockVersion}` : ""}`);
2047
+ }
2048
+ if (diagnostic.suggestedFix) {
2049
+ console.log(` fix: ${diagnostic.suggestedFix}`);
2050
+ }
2051
+ }
2052
+ }
2053
+
1713
2054
  /**
1714
2055
  * @param {ReturnType<typeof buildGeneratorPolicyExplainPayload>} payload
1715
2056
  * @returns {void}
@@ -1727,6 +2068,10 @@ function printGeneratorPolicyExplainPayload(payload) {
1727
2068
  console.log("Package-backed generators:");
1728
2069
  for (const binding of payload.bindings) {
1729
2070
  console.log(`- ${binding.componentId}: ${binding.generatorId}@${binding.version} via ${binding.packageName}`);
2071
+ console.log(` npm package: ${binding.packageInfo.installedVersion || "(not installed)"}`);
2072
+ if (binding.packageInfo.dependencySpec) {
2073
+ console.log(` dependency: ${binding.packageInfo.dependencyField} ${binding.packageInfo.dependencySpec}`);
2074
+ }
1730
2075
  }
1731
2076
  }
1732
2077
  if (payload.rules.length > 0) {
@@ -1839,10 +2184,6 @@ function buildGeneratorPolicyPinPayload(projectPath, spec) {
1839
2184
  if (!allowedPackages.includes(pin.packageName)) {
1840
2185
  allowedPackages.push(pin.packageName);
1841
2186
  }
1842
- const scope = packageScopeFromName(pin.packageName);
1843
- if (scope && !allowedPackageScopes.includes(scope)) {
1844
- allowedPackageScopes.push(scope);
1845
- }
1846
2187
  pinnedVersions[pin.packageName] = pin.version;
1847
2188
  }
1848
2189
  const nextPolicy = {
@@ -3715,6 +4056,7 @@ function printNewProjectResult(result, cwd) {
3715
4056
  console.log(" npm run source:status");
3716
4057
  console.log(" npm run template:explain");
3717
4058
  console.log(" npm run check");
4059
+ console.log(" npm run generator:policy:status");
3718
4060
  console.log(" npm run generator:policy:check");
3719
4061
  if (template.includesExecutableImplementation) {
3720
4062
  console.log(" npm run template:policy:explain");
@@ -7219,6 +7561,8 @@ if (args[0] === "version" || args[0] === "--version") {
7219
7561
  commandArgs = { generatorCheck: true, inputPath: args[2] };
7220
7562
  } else if (args[0] === "generator" && args[1] === "policy" && args[2] === "init") {
7221
7563
  commandArgs = { generatorPolicyInit: true, inputPath: commandPath(3) };
7564
+ } else if (args[0] === "generator" && args[1] === "policy" && args[2] === "status") {
7565
+ commandArgs = { generatorPolicyStatus: true, inputPath: commandPath(3) };
7222
7566
  } else if (args[0] === "generator" && args[1] === "policy" && args[2] === "check") {
7223
7567
  commandArgs = { generatorPolicyCheck: true, inputPath: commandPath(3) };
7224
7568
  } else if (args[0] === "generator" && args[1] === "policy" && args[2] === "explain") {
@@ -7422,6 +7766,7 @@ const shouldGeneratorList = Boolean(commandArgs?.generatorList);
7422
7766
  const shouldGeneratorShow = Boolean(commandArgs?.generatorShow);
7423
7767
  const shouldGeneratorCheck = Boolean(commandArgs?.generatorCheck);
7424
7768
  const shouldGeneratorPolicyInit = Boolean(commandArgs?.generatorPolicyInit);
7769
+ const shouldGeneratorPolicyStatus = Boolean(commandArgs?.generatorPolicyStatus);
7425
7770
  const shouldGeneratorPolicyCheck = Boolean(commandArgs?.generatorPolicyCheck);
7426
7771
  const shouldGeneratorPolicyExplain = Boolean(commandArgs?.generatorPolicyExplain);
7427
7772
  const shouldGeneratorPolicyPin = Boolean(commandArgs?.generatorPolicyPin);
@@ -7558,7 +7903,7 @@ const outIndex = args.indexOf("--out");
7558
7903
  const outPath = outIndex >= 0 ? args[outIndex + 1] : null;
7559
7904
  const effectiveOutDir = outDir || outPath || commandArgs?.defaultOutDir || null;
7560
7905
 
7561
- if ((shouldCheck || shouldComponentCheck || shouldComponentBehavior || shouldGeneratorCheck || shouldGeneratorPolicyInit || shouldGeneratorPolicyCheck || shouldGeneratorPolicyExplain || shouldGeneratorPolicyPin || shouldValidate || shouldTrustTemplate || shouldTrustStatus || shouldTrustDiff || shouldSourceStatus || shouldTemplateExplain || shouldTemplateStatus || shouldTemplateDetach || shouldTemplatePolicyInit || shouldTemplatePolicyCheck || shouldTemplatePolicyExplain || shouldTemplatePolicyPin || shouldTemplateCheck || shouldTemplateUpdate || generateTarget === "app-bundle") && !inputPath) {
7906
+ if ((shouldCheck || shouldComponentCheck || shouldComponentBehavior || shouldGeneratorCheck || shouldGeneratorPolicyInit || shouldGeneratorPolicyStatus || shouldGeneratorPolicyCheck || shouldGeneratorPolicyExplain || shouldGeneratorPolicyPin || shouldValidate || shouldTrustTemplate || shouldTrustStatus || shouldTrustDiff || shouldSourceStatus || shouldTemplateExplain || shouldTemplateStatus || shouldTemplateDetach || shouldTemplatePolicyInit || shouldTemplatePolicyCheck || shouldTemplatePolicyExplain || shouldTemplatePolicyPin || shouldTemplateCheck || shouldTemplateUpdate || generateTarget === "app-bundle") && !inputPath) {
7562
7907
  console.error("Missing required <path>.");
7563
7908
  printUsage();
7564
7909
  process.exit(1);
@@ -7612,7 +7957,7 @@ if (shouldQueryShow && !commandArgs?.queryShowName) {
7612
7957
  process.exit(1);
7613
7958
  }
7614
7959
 
7615
- if ((shouldCheck || shouldComponentCheck || shouldComponentBehavior || shouldValidate || shouldGeneratorPolicyInit || shouldGeneratorPolicyCheck || shouldGeneratorPolicyExplain || shouldGeneratorPolicyPin || shouldTrustTemplate || shouldTrustStatus || shouldTrustDiff || shouldTemplateExplain || shouldTemplateStatus || shouldTemplatePolicyInit || shouldTemplatePolicyCheck || shouldTemplatePolicyExplain || shouldTemplatePolicyPin || shouldTemplateUpdate || generateTarget === "app-bundle") && inputPath) {
7960
+ if ((shouldCheck || shouldComponentCheck || shouldComponentBehavior || shouldValidate || shouldGeneratorPolicyInit || shouldGeneratorPolicyStatus || shouldGeneratorPolicyCheck || shouldGeneratorPolicyExplain || shouldGeneratorPolicyPin || shouldTrustTemplate || shouldTrustStatus || shouldTrustDiff || shouldTemplateExplain || shouldTemplateStatus || shouldTemplatePolicyInit || shouldTemplatePolicyCheck || shouldTemplatePolicyExplain || shouldTemplatePolicyPin || shouldTemplateUpdate || generateTarget === "app-bundle") && inputPath) {
7616
7961
  inputPath = normalizeTopogramPath(inputPath);
7617
7962
  }
7618
7963
 
@@ -7762,6 +8107,16 @@ try {
7762
8107
  process.exit(0);
7763
8108
  }
7764
8109
 
8110
+ if (shouldGeneratorPolicyStatus) {
8111
+ const payload = buildGeneratorPolicyStatusPayload(inputPath);
8112
+ if (emitJson) {
8113
+ console.log(stableStringify(payload));
8114
+ } else {
8115
+ printGeneratorPolicyStatusPayload(payload);
8116
+ }
8117
+ process.exit(payload.ok ? 0 : 1);
8118
+ }
8119
+
7765
8120
  if (shouldGeneratorPolicyCheck) {
7766
8121
  const payload = buildGeneratorPolicyCheckPayload(inputPath);
7767
8122
  if (emitJson) {
@@ -1975,6 +1975,7 @@ function writeProjectPackage(projectRoot, engineRoot, template) {
1975
1975
  "template:detach:dry-run": "topogram template detach --dry-run",
1976
1976
  "template:policy:check": "topogram template policy check",
1977
1977
  "template:policy:explain": "topogram template policy explain",
1978
+ "generator:policy:status": "topogram generator policy status",
1978
1979
  "generator:policy:check": "topogram generator policy check",
1979
1980
  "generator:policy:explain": "topogram generator policy explain",
1980
1981
  "template:update:status": "topogram template update --status",
@@ -2052,6 +2053,7 @@ Useful inspection:
2052
2053
  npm run template:detach:dry-run
2053
2054
  npm run template:policy:check
2054
2055
  npm run template:policy:explain
2056
+ npm run generator:policy:status
2055
2057
  npm run generator:policy:check
2056
2058
  npm run generator:policy:explain
2057
2059
  npm run template:update:status
@@ -2084,6 +2086,7 @@ function writeProjectReadme(projectRoot, projectConfig) {
2084
2086
  "npm run template:explain",
2085
2087
  "npm run check",
2086
2088
  "npm run template:policy:check",
2089
+ "npm run generator:policy:status",
2087
2090
  "npm run generator:policy:check",
2088
2091
  ...(template.includesExecutableImplementation ? [
2089
2092
  "npm run template:policy:explain",