@topogram/cli 0.3.43 → 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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/cli.js +134 -11
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topogram/cli",
3
- "version": "0.3.43",
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
@@ -1631,7 +1631,7 @@ function dependencySpecForPackage(projectPackage, packageName) {
1631
1631
  * @param {string} packageName
1632
1632
  * @returns {{ version: string|null, resolved: string|null, integrity: string|null, entryPath: string|null }}
1633
1633
  */
1634
- function lockfileInfoForPackage(lockfile, packageName) {
1634
+ function npmLockfileInfoForPackage(lockfile, packageName) {
1635
1635
  const packageEntryPath = `node_modules/${packageName}`;
1636
1636
  const packageEntry = lockfile?.packages?.[packageEntryPath];
1637
1637
  if (packageEntry && typeof packageEntry === "object") {
@@ -1659,16 +1659,62 @@ function lockfileInfoForPackage(lockfile, packageName) {
1659
1659
  };
1660
1660
  }
1661
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
+
1662
1701
  /**
1663
1702
  * @param {string} projectRoot
1664
1703
  * @param {string} packageName
1665
- * @returns {{ dependencyField: string|null, dependencySpec: string|null, installedVersion: string|null, installedPackageJsonPath: string|null, lockfileVersion: string|null, lockfileResolved: string|null, lockfileIntegrity: string|null, lockfileEntryPath: string|null }}
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 }}
1666
1705
  */
1667
1706
  function packageInfoForGenerator(projectRoot, packageName) {
1668
1707
  const projectPackage = readJsonIfPresent(path.join(projectRoot, "package.json"));
1669
1708
  const dependency = dependencySpecForPackage(projectPackage, packageName);
1670
- const lockfile = readJsonIfPresent(path.join(projectRoot, "package-lock.json"));
1671
- const lockfileInfo = lockfileInfoForPackage(lockfile, 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
+ };
1672
1718
  const installedPackageJsonPath = path.join(projectRoot, "node_modules", ...packageName.split("/"), "package.json");
1673
1719
  const installedPackage = readJsonIfPresent(installedPackageJsonPath);
1674
1720
  return {
@@ -1676,13 +1722,31 @@ function packageInfoForGenerator(projectRoot, packageName) {
1676
1722
  dependencySpec: dependency.spec,
1677
1723
  installedVersion: typeof installedPackage?.version === "string" ? installedPackage.version : null,
1678
1724
  installedPackageJsonPath: installedPackage ? installedPackageJsonPath : null,
1725
+ lockfileKind: lockfile.kind,
1726
+ lockfilePath: lockfile.path,
1679
1727
  lockfileVersion: lockfileInfo.version,
1680
1728
  lockfileResolved: lockfileInfo.resolved,
1681
1729
  lockfileIntegrity: lockfileInfo.integrity,
1682
- lockfileEntryPath: lockfileInfo.entryPath
1730
+ lockfileEntryPath: lockfileInfo.entryPath,
1731
+ lockfileNote: lockfile.note
1683
1732
  };
1684
1733
  }
1685
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
+
1686
1750
  /**
1687
1751
  * @param {string} projectRoot
1688
1752
  * @param {import("./generator-policy.js").GeneratorPolicy} policy
@@ -1724,11 +1788,68 @@ function annotateGeneratorPolicyDiagnostics(diagnostics, bindings) {
1724
1788
  packageVersion: binding.packageInfo.installedVersion || binding.packageInfo.lockfileVersion || null,
1725
1789
  packageDependencyField: binding.packageInfo.dependencyField,
1726
1790
  packageDependencySpec: binding.packageInfo.dependencySpec,
1791
+ packageLockfileKind: binding.packageInfo.lockfileKind,
1792
+ packageLockfilePath: binding.packageInfo.lockfilePath,
1727
1793
  packageLockVersion: binding.packageInfo.lockfileVersion
1728
1794
  };
1729
1795
  });
1730
1796
  }
1731
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
+
1732
1853
  /**
1733
1854
  * @param {string} projectPath
1734
1855
  * @returns {{ ok: boolean, path: string, exists: boolean, policy: any, defaulted: boolean, bindings: Array<ReturnType<typeof generatorPolicyBindingStatus>>, diagnostics: any[], errors: string[] }}
@@ -1771,6 +1892,7 @@ function buildGeneratorPolicyCheckPayload(projectPath) {
1771
1892
  });
1772
1893
  }
1773
1894
  diagnostics.push(...generatorPolicyDiagnosticsForBindings(policyInfo, rawBindings, "generator-policy"));
1895
+ diagnostics.push(...generatorPolicyPackageMetadataDiagnostics(bindings));
1774
1896
  const annotatedDiagnostics = annotateGeneratorPolicyDiagnostics(diagnostics, bindings);
1775
1897
  const errors = annotatedDiagnostics.filter((diagnostic) => diagnostic.severity === "error").map((diagnostic) => diagnostic.message);
1776
1898
  return {
@@ -1868,7 +1990,9 @@ function printGeneratorPolicyCheckPayload(payload) {
1868
1990
  console.log(` dependency: ${binding.packageInfo.dependencyField} ${binding.packageInfo.dependencySpec}`);
1869
1991
  }
1870
1992
  if (binding.packageInfo.lockfileVersion) {
1871
- console.log(` lockfile: ${binding.packageInfo.lockfileVersion}`);
1993
+ console.log(` lockfile: ${formatGeneratorPackageLockfile(binding.packageInfo)}`);
1994
+ } else if (binding.packageInfo.lockfileKind) {
1995
+ console.log(` lockfile: ${formatGeneratorPackageLockfile(binding.packageInfo)}`);
1872
1996
  }
1873
1997
  }
1874
1998
  for (const diagnostic of payload.diagnostics) {
@@ -1906,7 +2030,7 @@ function printGeneratorPolicyStatusPayload(payload) {
1906
2030
  console.log(` allowed: ${binding.allowed ? "yes" : "no"}`);
1907
2031
  console.log(` npm package: ${binding.packageInfo.installedVersion || "(not installed)"}`);
1908
2032
  console.log(` dependency: ${binding.packageInfo.dependencyField && binding.packageInfo.dependencySpec ? `${binding.packageInfo.dependencyField} ${binding.packageInfo.dependencySpec}` : "(not declared)"}`);
1909
- console.log(` lockfile: ${binding.packageInfo.lockfileVersion || "(not locked)"}`);
2033
+ console.log(` lockfile: ${formatGeneratorPackageLockfile(binding.packageInfo)}`);
1910
2034
  console.log(` policy pin: ${binding.pin.version ? `${binding.pin.key}@${binding.pin.version}` : "(none)"}`);
1911
2035
  }
1912
2036
  for (const diagnostic of payload.diagnostics) {
@@ -1918,6 +2042,9 @@ function printGeneratorPolicyStatusPayload(payload) {
1918
2042
  if (diagnostic.packageDependencySpec) {
1919
2043
  console.log(` dependency: ${diagnostic.packageDependencyField} ${diagnostic.packageDependencySpec}`);
1920
2044
  }
2045
+ if (diagnostic.packageLockfilePath) {
2046
+ console.log(` lockfile: ${path.basename(diagnostic.packageLockfilePath)}${diagnostic.packageLockVersion ? ` ${diagnostic.packageLockVersion}` : ""}`);
2047
+ }
1921
2048
  if (diagnostic.suggestedFix) {
1922
2049
  console.log(` fix: ${diagnostic.suggestedFix}`);
1923
2050
  }
@@ -2057,10 +2184,6 @@ function buildGeneratorPolicyPinPayload(projectPath, spec) {
2057
2184
  if (!allowedPackages.includes(pin.packageName)) {
2058
2185
  allowedPackages.push(pin.packageName);
2059
2186
  }
2060
- const scope = packageScopeFromName(pin.packageName);
2061
- if (scope && !allowedPackageScopes.includes(scope)) {
2062
- allowedPackageScopes.push(scope);
2063
- }
2064
2187
  pinnedVersions[pin.packageName] = pin.version;
2065
2188
  }
2066
2189
  const nextPolicy = {