@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.
- package/package.json +1 -1
- package/src/cli.js +134 -11
package/package.json
CHANGED
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
|
|
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 =
|
|
1671
|
-
const lockfileInfo =
|
|
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
|
|
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
|
|
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 = {
|