repopo 0.9.0 → 0.12.0

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 (271) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/README.md +41 -2
  3. package/THIRD-PARTY-LICENSES.txt +71 -11
  4. package/bin/dev.js +5 -1
  5. package/esm/baseCommand.d.ts +1 -1
  6. package/esm/baseCommand.d.ts.map +1 -1
  7. package/esm/baseCommand.js +6 -9
  8. package/esm/baseCommand.js.map +1 -1
  9. package/esm/commands/check-native.d.ts +25 -0
  10. package/esm/commands/check-native.d.ts.map +1 -0
  11. package/esm/commands/check-native.js +137 -0
  12. package/esm/commands/check-native.js.map +1 -0
  13. package/esm/commands/check.d.ts +8 -19
  14. package/esm/commands/check.d.ts.map +1 -1
  15. package/esm/commands/check.js +97 -169
  16. package/esm/commands/check.js.map +1 -1
  17. package/esm/commands/list.d.ts.map +1 -1
  18. package/esm/commands/list.js +5 -6
  19. package/esm/commands/list.js.map +1 -1
  20. package/esm/config.d.ts +19 -4
  21. package/esm/config.d.ts.map +1 -1
  22. package/esm/config.js +2 -3
  23. package/esm/config.js.map +1 -1
  24. package/esm/context.d.ts +13 -3
  25. package/esm/context.d.ts.map +1 -1
  26. package/esm/context.js +11 -1
  27. package/esm/context.js.map +1 -1
  28. package/esm/index.d.ts +7 -2
  29. package/esm/index.d.ts.map +1 -1
  30. package/esm/index.js +9 -1
  31. package/esm/index.js.map +1 -1
  32. package/esm/makePolicy.d.ts +77 -8
  33. package/esm/makePolicy.d.ts.map +1 -1
  34. package/esm/makePolicy.js +108 -4
  35. package/esm/makePolicy.js.map +1 -1
  36. package/esm/perf.d.ts +7 -2
  37. package/esm/perf.d.ts.map +1 -1
  38. package/esm/perf.js.map +1 -1
  39. package/esm/policies/CargoLicenceValidated.d.ts +41 -0
  40. package/esm/policies/CargoLicenceValidated.d.ts.map +1 -0
  41. package/esm/policies/CargoLicenceValidated.js +45 -0
  42. package/esm/policies/CargoLicenceValidated.js.map +1 -0
  43. package/esm/policies/CargoLintsConfigured.d.ts +30 -0
  44. package/esm/policies/CargoLintsConfigured.d.ts.map +1 -0
  45. package/esm/policies/CargoLintsConfigured.js +61 -0
  46. package/esm/policies/CargoLintsConfigured.js.map +1 -0
  47. package/esm/policies/CargoLockPolicy.d.ts +34 -0
  48. package/esm/policies/CargoLockPolicy.d.ts.map +1 -0
  49. package/esm/policies/CargoLockPolicy.js +57 -0
  50. package/esm/policies/CargoLockPolicy.js.map +1 -0
  51. package/esm/policies/CargoTomlRequired.d.ts +34 -0
  52. package/esm/policies/CargoTomlRequired.d.ts.map +1 -0
  53. package/esm/policies/CargoTomlRequired.js +77 -0
  54. package/esm/policies/CargoTomlRequired.js.map +1 -0
  55. package/esm/policies/CargoTomlSorted.d.ts +20 -0
  56. package/esm/policies/CargoTomlSorted.d.ts.map +1 -0
  57. package/esm/policies/CargoTomlSorted.js +164 -0
  58. package/esm/policies/CargoTomlSorted.js.map +1 -0
  59. package/esm/policies/ConditionalScripts.d.ts +94 -0
  60. package/esm/policies/ConditionalScripts.d.ts.map +1 -0
  61. package/esm/policies/ConditionalScripts.js +145 -0
  62. package/esm/policies/ConditionalScripts.js.map +1 -0
  63. package/esm/policies/ExactScripts.d.ts +51 -0
  64. package/esm/policies/ExactScripts.d.ts.map +1 -0
  65. package/esm/policies/ExactScripts.js +108 -0
  66. package/esm/policies/ExactScripts.js.map +1 -0
  67. package/esm/policies/GleamDepVersions.d.ts +25 -0
  68. package/esm/policies/GleamDepVersions.d.ts.map +1 -0
  69. package/esm/policies/GleamDepVersions.js +67 -0
  70. package/esm/policies/GleamDepVersions.js.map +1 -0
  71. package/esm/policies/GleamDocExists.d.ts +24 -0
  72. package/esm/policies/GleamDocExists.d.ts.map +1 -0
  73. package/esm/policies/GleamDocExists.js +39 -0
  74. package/esm/policies/GleamDocExists.js.map +1 -0
  75. package/esm/policies/GleamInternalModulesConfigured.d.ts +21 -0
  76. package/esm/policies/GleamInternalModulesConfigured.d.ts.map +1 -0
  77. package/esm/policies/GleamInternalModulesConfigured.js +30 -0
  78. package/esm/policies/GleamInternalModulesConfigured.js.map +1 -0
  79. package/esm/policies/GleamLicenceConfigured.d.ts +29 -0
  80. package/esm/policies/GleamLicenceConfigured.d.ts.map +1 -0
  81. package/esm/policies/GleamLicenceConfigured.js +34 -0
  82. package/esm/policies/GleamLicenceConfigured.js.map +1 -0
  83. package/esm/policies/GleamManifestCommitted.d.ts +9 -0
  84. package/esm/policies/GleamManifestCommitted.d.ts.map +1 -0
  85. package/esm/policies/GleamManifestCommitted.js +26 -0
  86. package/esm/policies/GleamManifestCommitted.js.map +1 -0
  87. package/esm/policies/GleamMonorepoConsistency.d.ts +30 -0
  88. package/esm/policies/GleamMonorepoConsistency.d.ts.map +1 -0
  89. package/esm/policies/GleamMonorepoConsistency.js +74 -0
  90. package/esm/policies/GleamMonorepoConsistency.js.map +1 -0
  91. package/esm/policies/GleamNameRestrictions.d.ts +25 -0
  92. package/esm/policies/GleamNameRestrictions.d.ts.map +1 -0
  93. package/esm/policies/GleamNameRestrictions.js +44 -0
  94. package/esm/policies/GleamNameRestrictions.js.map +1 -0
  95. package/esm/policies/GleamNoPathDepsInPublished.d.ts +22 -0
  96. package/esm/policies/GleamNoPathDepsInPublished.d.ts.map +1 -0
  97. package/esm/policies/GleamNoPathDepsInPublished.js +43 -0
  98. package/esm/policies/GleamNoPathDepsInPublished.js.map +1 -0
  99. package/esm/policies/GleamPathDepsValid.d.ts +9 -0
  100. package/esm/policies/GleamPathDepsValid.d.ts.map +1 -0
  101. package/esm/policies/GleamPathDepsValid.js +59 -0
  102. package/esm/policies/GleamPathDepsValid.js.map +1 -0
  103. package/esm/policies/GleamProjectStructure.d.ts +25 -0
  104. package/esm/policies/GleamProjectStructure.d.ts.map +1 -0
  105. package/esm/policies/GleamProjectStructure.js +33 -0
  106. package/esm/policies/GleamProjectStructure.js.map +1 -0
  107. package/esm/policies/GleamSharedDependencyVersions.d.ts +29 -0
  108. package/esm/policies/GleamSharedDependencyVersions.d.ts.map +1 -0
  109. package/esm/policies/GleamSharedDependencyVersions.js +101 -0
  110. package/esm/policies/GleamSharedDependencyVersions.js.map +1 -0
  111. package/esm/policies/GleamTomlRequired.d.ts +30 -0
  112. package/esm/policies/GleamTomlRequired.d.ts.map +1 -0
  113. package/esm/policies/GleamTomlRequired.js +37 -0
  114. package/esm/policies/GleamTomlRequired.js.map +1 -0
  115. package/esm/policies/GleamTomlSorted.d.ts +20 -0
  116. package/esm/policies/GleamTomlSorted.d.ts.map +1 -0
  117. package/esm/policies/GleamTomlSorted.js +153 -0
  118. package/esm/policies/GleamTomlSorted.js.map +1 -0
  119. package/esm/policies/GleamVersionConstraint.d.ts +20 -0
  120. package/esm/policies/GleamVersionConstraint.d.ts.map +1 -0
  121. package/esm/policies/GleamVersionConstraint.js +64 -0
  122. package/esm/policies/GleamVersionConstraint.js.map +1 -0
  123. package/esm/policies/HtmlFileHeaders.d.ts +1 -1
  124. package/esm/policies/HtmlFileHeaders.d.ts.map +1 -1
  125. package/esm/policies/HtmlFileHeaders.js +1 -1
  126. package/esm/policies/HtmlFileHeaders.js.map +1 -1
  127. package/esm/policies/JsTsFileHeaders.d.ts +1 -1
  128. package/esm/policies/JsTsFileHeaders.d.ts.map +1 -1
  129. package/esm/policies/MutuallyExclusiveScripts.d.ts +46 -0
  130. package/esm/policies/MutuallyExclusiveScripts.d.ts.map +1 -0
  131. package/esm/policies/MutuallyExclusiveScripts.js +52 -0
  132. package/esm/policies/MutuallyExclusiveScripts.js.map +1 -0
  133. package/esm/policies/NoBuildDirectory.d.ts +9 -0
  134. package/esm/policies/NoBuildDirectory.d.ts.map +1 -0
  135. package/esm/policies/NoBuildDirectory.js +29 -0
  136. package/esm/policies/NoBuildDirectory.js.map +1 -0
  137. package/esm/policies/NoPrivateWorkspaceDependencies.d.ts +1 -1
  138. package/esm/policies/NoPrivateWorkspaceDependencies.d.ts.map +1 -1
  139. package/esm/policies/NoTargetArtifacts.d.ts +8 -0
  140. package/esm/policies/NoTargetArtifacts.d.ts.map +1 -0
  141. package/esm/policies/NoTargetArtifacts.js +42 -0
  142. package/esm/policies/NoTargetArtifacts.js.map +1 -0
  143. package/esm/policies/NoUnsafeWithoutJustification.d.ts +24 -0
  144. package/esm/policies/NoUnsafeWithoutJustification.d.ts.map +1 -0
  145. package/esm/policies/NoUnsafeWithoutJustification.js +80 -0
  146. package/esm/policies/NoUnsafeWithoutJustification.js.map +1 -0
  147. package/esm/policies/NoWildcardDependencies.d.ts +30 -0
  148. package/esm/policies/NoWildcardDependencies.d.ts.map +1 -0
  149. package/esm/policies/NoWildcardDependencies.js +83 -0
  150. package/esm/policies/NoWildcardDependencies.js.map +1 -0
  151. package/esm/policies/PackageAllowedScopes.d.ts +1 -1
  152. package/esm/policies/PackageAllowedScopes.d.ts.map +1 -1
  153. package/esm/policies/PackageEsmType.d.ts +1 -1
  154. package/esm/policies/PackageEsmType.d.ts.map +1 -1
  155. package/esm/policies/PackageEsmType.js +7 -5
  156. package/esm/policies/PackageEsmType.js.map +1 -1
  157. package/esm/policies/PackageFolderName.d.ts +1 -1
  158. package/esm/policies/PackageFolderName.d.ts.map +1 -1
  159. package/esm/policies/PackageJsonProperties.d.ts +1 -1
  160. package/esm/policies/PackageJsonProperties.d.ts.map +1 -1
  161. package/esm/policies/PackageJsonProperties.js +12 -4
  162. package/esm/policies/PackageJsonProperties.js.map +1 -1
  163. package/esm/policies/PackageJsonRepoDirectoryProperty.d.ts +1 -1
  164. package/esm/policies/PackageJsonRepoDirectoryProperty.d.ts.map +1 -1
  165. package/esm/policies/PackageJsonRepoDirectoryProperty.js +3 -2
  166. package/esm/policies/PackageJsonRepoDirectoryProperty.js.map +1 -1
  167. package/esm/policies/PackageJsonSorted.d.ts +1 -1
  168. package/esm/policies/PackageJsonSorted.d.ts.map +1 -1
  169. package/esm/policies/PackageJsonSorted.js +42 -7
  170. package/esm/policies/PackageJsonSorted.js.map +1 -1
  171. package/esm/policies/PackageLicense.d.ts +1 -1
  172. package/esm/policies/PackageLicense.d.ts.map +1 -1
  173. package/esm/policies/PackageLicense.js +1 -2
  174. package/esm/policies/PackageLicense.js.map +1 -1
  175. package/esm/policies/PackagePrivateField.d.ts +1 -1
  176. package/esm/policies/PackagePrivateField.d.ts.map +1 -1
  177. package/esm/policies/PackagePrivateField.js +3 -2
  178. package/esm/policies/PackagePrivateField.js.map +1 -1
  179. package/esm/policies/PackageReadme.d.ts +1 -1
  180. package/esm/policies/PackageReadme.d.ts.map +1 -1
  181. package/esm/policies/PackageReadme.js +2 -2
  182. package/esm/policies/PackageReadme.js.map +1 -1
  183. package/esm/policies/PackageScripts.d.ts +1 -1
  184. package/esm/policies/PackageScripts.d.ts.map +1 -1
  185. package/esm/policies/PackageScripts.js +3 -2
  186. package/esm/policies/PackageScripts.js.map +1 -1
  187. package/esm/policies/PackageTestScripts.d.ts +1 -1
  188. package/esm/policies/PackageTestScripts.d.ts.map +1 -1
  189. package/esm/policies/PackageTestScripts.js +3 -3
  190. package/esm/policies/PackageTestScripts.js.map +1 -1
  191. package/esm/policies/PublicApiDocumented.d.ts +21 -0
  192. package/esm/policies/PublicApiDocumented.d.ts.map +1 -0
  193. package/esm/policies/PublicApiDocumented.js +36 -0
  194. package/esm/policies/PublicApiDocumented.js.map +1 -0
  195. package/esm/policies/RequiredScripts.d.ts +71 -0
  196. package/esm/policies/RequiredScripts.d.ts.map +1 -0
  197. package/esm/policies/RequiredScripts.js +121 -0
  198. package/esm/policies/RequiredScripts.js.map +1 -0
  199. package/esm/policies/RustDocExists.d.ts +24 -0
  200. package/esm/policies/RustDocExists.d.ts.map +1 -0
  201. package/esm/policies/RustDocExists.js +49 -0
  202. package/esm/policies/RustDocExists.js.map +1 -0
  203. package/esm/policies/RustToolchainExists.d.ts +20 -0
  204. package/esm/policies/RustToolchainExists.d.ts.map +1 -0
  205. package/esm/policies/RustToolchainExists.js +27 -0
  206. package/esm/policies/RustToolchainExists.js.map +1 -0
  207. package/esm/policies/RustfmtConfigExists.d.ts +20 -0
  208. package/esm/policies/RustfmtConfigExists.d.ts.map +1 -0
  209. package/esm/policies/RustfmtConfigExists.js +27 -0
  210. package/esm/policies/RustfmtConfigExists.js.map +1 -0
  211. package/esm/policies/ScriptContains.d.ts +64 -0
  212. package/esm/policies/ScriptContains.d.ts.map +1 -0
  213. package/esm/policies/ScriptContains.js +62 -0
  214. package/esm/policies/ScriptContains.js.map +1 -0
  215. package/esm/policies/SharedDependencyVersions.d.ts +24 -0
  216. package/esm/policies/SharedDependencyVersions.d.ts.map +1 -0
  217. package/esm/policies/SharedDependencyVersions.js +135 -0
  218. package/esm/policies/SharedDependencyVersions.js.map +1 -0
  219. package/esm/policies/WorkspaceInheritance.d.ts +24 -0
  220. package/esm/policies/WorkspaceInheritance.d.ts.map +1 -0
  221. package/esm/policies/WorkspaceInheritance.js +94 -0
  222. package/esm/policies/WorkspaceInheritance.js.map +1 -0
  223. package/esm/policies/WorkspaceMembersValid.d.ts +20 -0
  224. package/esm/policies/WorkspaceMembersValid.d.ts.map +1 -0
  225. package/esm/policies/WorkspaceMembersValid.js +88 -0
  226. package/esm/policies/WorkspaceMembersValid.js.map +1 -0
  227. package/esm/policies/constants.d.ts +8 -0
  228. package/esm/policies/constants.d.ts.map +1 -1
  229. package/esm/policies/constants.js +8 -0
  230. package/esm/policies/constants.js.map +1 -1
  231. package/esm/policies.d.ts +36 -1
  232. package/esm/policies.d.ts.map +1 -1
  233. package/esm/policies.js +68 -0
  234. package/esm/policies.js.map +1 -1
  235. package/esm/policy.d.ts +202 -84
  236. package/esm/policy.d.ts.map +1 -1
  237. package/esm/policy.js +103 -21
  238. package/esm/policy.js.map +1 -1
  239. package/esm/policyDefiners/defineCargoPolicy.d.ts +66 -0
  240. package/esm/policyDefiners/defineCargoPolicy.d.ts.map +1 -0
  241. package/esm/policyDefiners/defineCargoPolicy.js +60 -0
  242. package/esm/policyDefiners/defineCargoPolicy.js.map +1 -0
  243. package/esm/policyDefiners/defineFileHeaderPolicy.d.ts +7 -2
  244. package/esm/policyDefiners/defineFileHeaderPolicy.d.ts.map +1 -1
  245. package/esm/policyDefiners/defineFileHeaderPolicy.js +11 -7
  246. package/esm/policyDefiners/defineFileHeaderPolicy.js.map +1 -1
  247. package/esm/policyDefiners/defineGleamPolicy.d.ts +65 -0
  248. package/esm/policyDefiners/defineGleamPolicy.d.ts.map +1 -0
  249. package/esm/policyDefiners/defineGleamPolicy.js +59 -0
  250. package/esm/policyDefiners/defineGleamPolicy.js.map +1 -0
  251. package/esm/policyDefiners/definePackagePolicy.d.ts +18 -5
  252. package/esm/policyDefiners/definePackagePolicy.d.ts.map +1 -1
  253. package/esm/policyDefiners/definePackagePolicy.js +14 -4
  254. package/esm/policyDefiners/definePackagePolicy.js.map +1 -1
  255. package/esm/policyDefiners/spdxValidator.d.ts +74 -0
  256. package/esm/policyDefiners/spdxValidator.d.ts.map +1 -0
  257. package/esm/policyDefiners/spdxValidator.js +127 -0
  258. package/esm/policyDefiners/spdxValidator.js.map +1 -0
  259. package/esm/policyDefiners/tomlParser.d.ts +8 -0
  260. package/esm/policyDefiners/tomlParser.d.ts.map +1 -0
  261. package/esm/policyDefiners/tomlParser.js +17 -0
  262. package/esm/policyDefiners/tomlParser.js.map +1 -0
  263. package/esm/runner.d.ts +60 -0
  264. package/esm/runner.d.ts.map +1 -0
  265. package/esm/runner.js +174 -0
  266. package/esm/runner.js.map +1 -0
  267. package/esm/tsdoc-metadata.json +1 -1
  268. package/oclif.manifest.json +100 -3
  269. package/package.json +46 -31
  270. package/scripts/postinstall.mjs +154 -0
  271. package/sidecar/sidecar.mjs +468 -0
@@ -0,0 +1,62 @@
1
+ import { definePackagePolicy } from "../policyDefiners/definePackagePolicy.js";
2
+ const POLICY_NAME = "ScriptContains";
3
+ /**
4
+ * A policy that validates script bodies contain required substrings.
5
+ *
6
+ * @remarks
7
+ * Use this policy to ensure specific scripts contain required commands or patterns
8
+ * without requiring an exact match. For example, requiring that the "clean" script
9
+ * uses "rimraf" or that "build" includes "tsc".
10
+ *
11
+ * The policy only validates scripts that exist. If a script doesn't exist,
12
+ * it's not an error (use RequiredScripts for that).
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * import { policy } from "repopo";
17
+ * import { ScriptContains } from "repopo/policies";
18
+ *
19
+ * policy(ScriptContains, {
20
+ * rules: [
21
+ * { script: "clean", mustContain: ["rimraf"] },
22
+ * { script: "build", mustContain: ["tsc"] },
23
+ * { script: "test", mustContain: ["vitest", "run"] },
24
+ * ],
25
+ * })
26
+ * ```
27
+ *
28
+ * @alpha
29
+ */
30
+ export const ScriptContains = definePackagePolicy({
31
+ name: POLICY_NAME,
32
+ description: "Validates that script bodies contain required substrings.",
33
+ handler: async (json, { file, config }) => {
34
+ if (config === undefined || config.rules.length === 0) {
35
+ return true;
36
+ }
37
+ const scripts = json.scripts ?? {};
38
+ const errors = [];
39
+ for (const rule of config.rules) {
40
+ // Only validate if the script exists
41
+ if (Object.hasOwn(scripts, rule.script)) {
42
+ const scriptBody = scripts[rule.script];
43
+ if (typeof scriptBody === "string") {
44
+ const missingSubstrings = rule.mustContain.filter((substring) => !scriptBody.includes(substring));
45
+ if (missingSubstrings.length > 0) {
46
+ errors.push(`Script "${rule.script}" must contain: ${missingSubstrings.join(", ")}`);
47
+ }
48
+ }
49
+ }
50
+ }
51
+ if (errors.length === 0) {
52
+ return true;
53
+ }
54
+ return {
55
+ name: POLICY_NAME,
56
+ file,
57
+ autoFixable: false,
58
+ errorMessages: errors,
59
+ };
60
+ },
61
+ });
62
+ //# sourceMappingURL=ScriptContains.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ScriptContains.js","sourceRoot":"","sources":["../../src/policies/ScriptContains.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,0CAA0C,CAAC;AAwC/E,MAAM,WAAW,GAAG,gBAAgB,CAAC;AAErC;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,mBAAmB,CAG/C;IACD,IAAI,EAAE,WAAW;IACjB,WAAW,EAAE,2DAA2D;IACxE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE;QACzC,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvD,OAAO,IAAI,CAAC;QACb,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC;QACnC,MAAM,MAAM,GAAa,EAAE,CAAC;QAE5B,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;YACjC,qCAAqC;YACrC,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;gBACzC,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;gBACxC,IAAI,OAAO,UAAU,KAAK,QAAQ,EAAE,CAAC;oBACpC,MAAM,iBAAiB,GAAG,IAAI,CAAC,WAAW,CAAC,MAAM,CAChD,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,SAAS,CAAC,CAC9C,CAAC;oBAEF,IAAI,iBAAiB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;wBAClC,MAAM,CAAC,IAAI,CACV,WAAW,IAAI,CAAC,MAAM,mBAAmB,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACvE,CAAC;oBACH,CAAC;gBACF,CAAC;YACF,CAAC;QACF,CAAC;QAED,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzB,OAAO,IAAI,CAAC;QACb,CAAC;QAED,OAAO;YACN,IAAI,EAAE,WAAW;YACjB,IAAI;YACJ,WAAW,EAAE,KAAK;YAClB,aAAa,EAAE,MAAM;SACrB,CAAC;IACH,CAAC;CACD,CAAC,CAAC","sourcesContent":["import type { PackageJson } from \"type-fest\";\nimport { definePackagePolicy } from \"../policyDefiners/definePackagePolicy.js\";\n\n/**\n * Configuration for validating that a script's body contains required substrings.\n *\n * @alpha\n */\nexport interface ScriptContainsRule {\n\t/**\n\t * The name of the script to validate.\n\t */\n\tscript: string;\n\n\t/**\n\t * Substrings that must appear in the script body.\n\t * All substrings must be present for the script to pass validation.\n\t */\n\tmustContain: string[];\n}\n\n/**\n * Configuration for the ScriptContains policy.\n *\n * @alpha\n */\nexport interface ScriptContainsConfig {\n\t/**\n\t * Rules for validating script body content.\n\t *\n\t * @example\n\t * ```typescript\n\t * rules: [\n\t * { script: \"clean\", mustContain: [\"rimraf\"] },\n\t * { script: \"build\", mustContain: [\"tsc\"] },\n\t * ]\n\t * ```\n\t */\n\trules: ScriptContainsRule[];\n}\n\nconst POLICY_NAME = \"ScriptContains\";\n\n/**\n * A policy that validates script bodies contain required substrings.\n *\n * @remarks\n * Use this policy to ensure specific scripts contain required commands or patterns\n * without requiring an exact match. For example, requiring that the \"clean\" script\n * uses \"rimraf\" or that \"build\" includes \"tsc\".\n *\n * The policy only validates scripts that exist. If a script doesn't exist,\n * it's not an error (use RequiredScripts for that).\n *\n * @example\n * ```typescript\n * import { policy } from \"repopo\";\n * import { ScriptContains } from \"repopo/policies\";\n *\n * policy(ScriptContains, {\n * rules: [\n * { script: \"clean\", mustContain: [\"rimraf\"] },\n * { script: \"build\", mustContain: [\"tsc\"] },\n * { script: \"test\", mustContain: [\"vitest\", \"run\"] },\n * ],\n * })\n * ```\n *\n * @alpha\n */\nexport const ScriptContains = definePackagePolicy<\n\tPackageJson,\n\tScriptContainsConfig | undefined\n>({\n\tname: POLICY_NAME,\n\tdescription: \"Validates that script bodies contain required substrings.\",\n\thandler: async (json, { file, config }) => {\n\t\tif (config === undefined || config.rules.length === 0) {\n\t\t\treturn true;\n\t\t}\n\n\t\tconst scripts = json.scripts ?? {};\n\t\tconst errors: string[] = [];\n\n\t\tfor (const rule of config.rules) {\n\t\t\t// Only validate if the script exists\n\t\t\tif (Object.hasOwn(scripts, rule.script)) {\n\t\t\t\tconst scriptBody = scripts[rule.script];\n\t\t\t\tif (typeof scriptBody === \"string\") {\n\t\t\t\t\tconst missingSubstrings = rule.mustContain.filter(\n\t\t\t\t\t\t(substring) => !scriptBody.includes(substring),\n\t\t\t\t\t);\n\n\t\t\t\t\tif (missingSubstrings.length > 0) {\n\t\t\t\t\t\terrors.push(\n\t\t\t\t\t\t\t`Script \"${rule.script}\" must contain: ${missingSubstrings.join(\", \")}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (errors.length === 0) {\n\t\t\treturn true;\n\t\t}\n\n\t\treturn {\n\t\t\tname: POLICY_NAME,\n\t\t\tfile,\n\t\t\tautoFixable: false,\n\t\t\terrorMessages: errors,\n\t\t};\n\t},\n});\n"]}
@@ -0,0 +1,24 @@
1
+ import type { PolicyDefinition } from "../policy.js";
2
+ /**
3
+ * Configuration for the SharedDependencyVersions policy.
4
+ *
5
+ * @alpha
6
+ */
7
+ export interface SharedDependencyVersionsConfig {
8
+ /**
9
+ * Dependencies that should use workspace-level versions.
10
+ */
11
+ requireWorkspaceDeps?: string[];
12
+ /**
13
+ * If true, detect version mismatches across workspace members.
14
+ * @defaultValue true
15
+ */
16
+ detectMismatches?: boolean;
17
+ }
18
+ /**
19
+ * A policy that detects dependency version mismatches across Cargo workspace members.
20
+ *
21
+ * @alpha
22
+ */
23
+ export declare const SharedDependencyVersions: PolicyDefinition<SharedDependencyVersionsConfig>;
24
+ //# sourceMappingURL=SharedDependencyVersions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SharedDependencyVersions.d.ts","sourceRoot":"","sources":["../../src/policies/SharedDependencyVersions.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGrD;;;;GAIG;AACH,MAAM,WAAW,8BAA8B;IAC9C;;OAEG;IACH,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;IAEhC;;;OAGG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC3B;AAkHD;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,EAAE,gBAAgB,CAAC,8BAA8B,CAyDnF,CAAC"}
@@ -0,0 +1,135 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "pathe";
3
+ import { glob } from "tinyglobby";
4
+ import { makePolicyDefinition } from "../makePolicy.js";
5
+ import { parseToml } from "../policyDefiners/tomlParser.js";
6
+ function extractVersion(dep) {
7
+ if (typeof dep === "string") {
8
+ return dep;
9
+ }
10
+ if (typeof dep === "object" && dep !== null) {
11
+ const record = dep;
12
+ if (typeof record.version === "string") {
13
+ return record.version;
14
+ }
15
+ }
16
+ return undefined;
17
+ }
18
+ /**
19
+ * Collect dependencies from a TOML object.
20
+ */
21
+ function collectDeps(toml) {
22
+ const deps = new Map();
23
+ const sections = ["dependencies", "dev-dependencies", "build-dependencies"];
24
+ for (const section of sections) {
25
+ const sectionDeps = toml[section];
26
+ if (!sectionDeps) {
27
+ continue;
28
+ }
29
+ for (const [name, value] of Object.entries(sectionDeps)) {
30
+ const version = extractVersion(value);
31
+ if (version && !version.startsWith("workspace")) {
32
+ deps.set(name, version);
33
+ }
34
+ }
35
+ }
36
+ return deps;
37
+ }
38
+ /**
39
+ * Check that required dependencies are defined at the workspace level.
40
+ */
41
+ function checkRequiredWorkspaceDeps(config, workspaceDeps, errors) {
42
+ if (!config?.requireWorkspaceDeps) {
43
+ return;
44
+ }
45
+ for (const dep of config.requireWorkspaceDeps) {
46
+ if (!(dep in workspaceDeps)) {
47
+ errors.push(`Dependency "${dep}" should be defined in [workspace.dependencies]`);
48
+ }
49
+ }
50
+ }
51
+ /**
52
+ * Add a member's dependency version to the tracking map.
53
+ */
54
+ function recordDependency(allDeps, name, version, member) {
55
+ if (!allDeps.has(name)) {
56
+ allDeps.set(name, new Map());
57
+ }
58
+ const versionMap = allDeps.get(name);
59
+ if (versionMap !== undefined) {
60
+ if (!versionMap.has(version)) {
61
+ versionMap.set(version, []);
62
+ }
63
+ const memberList = versionMap.get(version);
64
+ if (memberList !== undefined) {
65
+ memberList.push(member);
66
+ }
67
+ }
68
+ }
69
+ /**
70
+ * Collect dependency versions from all workspace members and record which members use them.
71
+ */
72
+ async function collectMemberDeps(members, root, exclude) {
73
+ const allDeps = new Map();
74
+ const memberCargoPaths = await glob(members.map((member) => path.join(member, "Cargo.toml")), {
75
+ cwd: root,
76
+ ignore: exclude.map((member) => path.join(member, "Cargo.toml")),
77
+ });
78
+ for (const memberCargoPath of memberCargoPaths) {
79
+ const memberContent = await readFile(path.resolve(root, memberCargoPath), "utf-8");
80
+ const memberToml = await parseToml(memberContent);
81
+ const deps = collectDeps(memberToml);
82
+ for (const [name, version] of deps) {
83
+ recordDependency(allDeps, name, version, path.dirname(memberCargoPath));
84
+ }
85
+ }
86
+ return allDeps;
87
+ }
88
+ /**
89
+ * A policy that detects dependency version mismatches across Cargo workspace members.
90
+ *
91
+ * @alpha
92
+ */
93
+ export const SharedDependencyVersions = makePolicyDefinition({
94
+ name: "SharedDependencyVersions",
95
+ description: "Detects dependency version mismatches across Cargo workspace members.",
96
+ match: /^Cargo\.toml$/,
97
+ handler: async ({ file, root, config }) => {
98
+ const detectMismatches = config?.detectMismatches ?? true;
99
+ const content = await readFile(path.resolve(root, file), "utf-8");
100
+ const toml = await parseToml(content);
101
+ const workspace = toml.workspace;
102
+ if (!workspace) {
103
+ return true;
104
+ }
105
+ const members = workspace.members;
106
+ if (!members || members.length === 0) {
107
+ return true;
108
+ }
109
+ const errors = [];
110
+ // Check required workspace deps
111
+ const workspaceDeps = (workspace.dependencies ?? {});
112
+ checkRequiredWorkspaceDeps(config, workspaceDeps, errors);
113
+ // Detect mismatches across members
114
+ if (detectMismatches) {
115
+ const exclude = (workspace.exclude ?? []);
116
+ const allDeps = await collectMemberDeps(members, root, exclude);
117
+ for (const [dep, versionMap] of allDeps) {
118
+ if (versionMap.size > 1) {
119
+ const details = [...versionMap.entries()]
120
+ .map(([ver, memberList]) => `"${ver}" in ${memberList.join(", ")}`)
121
+ .join("; ");
122
+ errors.push(`Version mismatch for "${dep}": ${details}`);
123
+ }
124
+ }
125
+ }
126
+ if (errors.length > 0) {
127
+ return {
128
+ error: errors.join("; "),
129
+ manualFix: "Use [workspace.dependencies] with `dep.workspace = true` in member Cargo.toml files to share versions.",
130
+ };
131
+ }
132
+ return true;
133
+ },
134
+ });
135
+ //# sourceMappingURL=SharedDependencyVersions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SharedDependencyVersions.js","sourceRoot":"","sources":["../../src/policies/SharedDependencyVersions.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport path from \"pathe\";\nimport { glob } from \"tinyglobby\";\nimport { makePolicyDefinition } from \"../makePolicy.js\";\nimport type { PolicyDefinition } from \"../policy.js\";\nimport { parseToml } from \"../policyDefiners/tomlParser.js\";\n\n/**\n * Configuration for the SharedDependencyVersions policy.\n *\n * @alpha\n */\nexport interface SharedDependencyVersionsConfig {\n\t/**\n\t * Dependencies that should use workspace-level versions.\n\t */\n\trequireWorkspaceDeps?: string[];\n\n\t/**\n\t * If true, detect version mismatches across workspace members.\n\t * @defaultValue true\n\t */\n\tdetectMismatches?: boolean;\n}\n\nfunction extractVersion(dep: unknown): string | undefined {\n\tif (typeof dep === \"string\") {\n\t\treturn dep;\n\t}\n\tif (typeof dep === \"object\" && dep !== null) {\n\t\tconst record = dep as Record<string, unknown>;\n\t\tif (typeof record.version === \"string\") {\n\t\t\treturn record.version;\n\t\t}\n\t}\n\treturn undefined;\n}\n\n/**\n * Collect dependencies from a TOML object.\n */\nfunction collectDeps(toml: Record<string, unknown>): Map<string, string> {\n\tconst deps = new Map<string, string>();\n\tconst sections = [\"dependencies\", \"dev-dependencies\", \"build-dependencies\"];\n\n\tfor (const section of sections) {\n\t\tconst sectionDeps = toml[section] as Record<string, unknown> | undefined;\n\t\tif (!sectionDeps) {\n\t\t\tcontinue;\n\t\t}\n\t\tfor (const [name, value] of Object.entries(sectionDeps)) {\n\t\t\tconst version = extractVersion(value);\n\t\t\tif (version && !version.startsWith(\"workspace\")) {\n\t\t\t\tdeps.set(name, version);\n\t\t\t}\n\t\t}\n\t}\n\treturn deps;\n}\n\n/**\n * Check that required dependencies are defined at the workspace level.\n */\nfunction checkRequiredWorkspaceDeps(\n\tconfig: SharedDependencyVersionsConfig | undefined,\n\tworkspaceDeps: Record<string, unknown>,\n\terrors: string[],\n): void {\n\tif (!config?.requireWorkspaceDeps) {\n\t\treturn;\n\t}\n\tfor (const dep of config.requireWorkspaceDeps) {\n\t\tif (!(dep in workspaceDeps)) {\n\t\t\terrors.push(\n\t\t\t\t`Dependency \"${dep}\" should be defined in [workspace.dependencies]`,\n\t\t\t);\n\t\t}\n\t}\n}\n\n/**\n * Add a member's dependency version to the tracking map.\n */\nfunction recordDependency(\n\tallDeps: Map<string, Map<string, string[]>>,\n\tname: string,\n\tversion: string,\n\tmember: string,\n): void {\n\tif (!allDeps.has(name)) {\n\t\tallDeps.set(name, new Map());\n\t}\n\tconst versionMap = allDeps.get(name);\n\tif (versionMap !== undefined) {\n\t\tif (!versionMap.has(version)) {\n\t\t\tversionMap.set(version, []);\n\t\t}\n\t\tconst memberList = versionMap.get(version);\n\t\tif (memberList !== undefined) {\n\t\t\tmemberList.push(member);\n\t\t}\n\t}\n}\n\n/**\n * Collect dependency versions from all workspace members and record which members use them.\n */\nasync function collectMemberDeps(\n\tmembers: string[],\n\troot: string,\n\texclude: string[],\n): Promise<Map<string, Map<string, string[]>>> {\n\tconst allDeps = new Map<string, Map<string, string[]>>();\n\tconst memberCargoPaths = await glob(\n\t\tmembers.map((member) => path.join(member, \"Cargo.toml\")),\n\t\t{\n\t\t\tcwd: root,\n\t\t\tignore: exclude.map((member) => path.join(member, \"Cargo.toml\")),\n\t\t},\n\t);\n\n\tfor (const memberCargoPath of memberCargoPaths) {\n\t\tconst memberContent = await readFile(\n\t\t\tpath.resolve(root, memberCargoPath),\n\t\t\t\"utf-8\",\n\t\t);\n\t\tconst memberToml = await parseToml(memberContent);\n\t\tconst deps = collectDeps(memberToml);\n\n\t\tfor (const [name, version] of deps) {\n\t\t\trecordDependency(allDeps, name, version, path.dirname(memberCargoPath));\n\t\t}\n\t}\n\n\treturn allDeps;\n}\n\n/**\n * A policy that detects dependency version mismatches across Cargo workspace members.\n *\n * @alpha\n */\nexport const SharedDependencyVersions: PolicyDefinition<SharedDependencyVersionsConfig> =\n\tmakePolicyDefinition({\n\t\tname: \"SharedDependencyVersions\",\n\t\tdescription:\n\t\t\t\"Detects dependency version mismatches across Cargo workspace members.\",\n\t\tmatch: /^Cargo\\.toml$/,\n\t\thandler: async ({ file, root, config }) => {\n\t\t\tconst detectMismatches = config?.detectMismatches ?? true;\n\t\t\tconst content = await readFile(path.resolve(root, file), \"utf-8\");\n\t\t\tconst toml = await parseToml(content);\n\n\t\t\tconst workspace = toml.workspace as Record<string, unknown> | undefined;\n\t\t\tif (!workspace) {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tconst members = workspace.members as string[] | undefined;\n\t\t\tif (!members || members.length === 0) {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tconst errors: string[] = [];\n\n\t\t\t// Check required workspace deps\n\t\t\tconst workspaceDeps = (workspace.dependencies ?? {}) as Record<\n\t\t\t\tstring,\n\t\t\t\tunknown\n\t\t\t>;\n\t\t\tcheckRequiredWorkspaceDeps(config, workspaceDeps, errors);\n\n\t\t\t// Detect mismatches across members\n\t\t\tif (detectMismatches) {\n\t\t\t\tconst exclude = (workspace.exclude ?? []) as string[];\n\t\t\t\tconst allDeps = await collectMemberDeps(members, root, exclude);\n\n\t\t\t\tfor (const [dep, versionMap] of allDeps) {\n\t\t\t\t\tif (versionMap.size > 1) {\n\t\t\t\t\t\tconst details = [...versionMap.entries()]\n\t\t\t\t\t\t\t.map(\n\t\t\t\t\t\t\t\t([ver, memberList]) => `\"${ver}\" in ${memberList.join(\", \")}`,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t.join(\"; \");\n\t\t\t\t\t\terrors.push(`Version mismatch for \"${dep}\": ${details}`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (errors.length > 0) {\n\t\t\t\treturn {\n\t\t\t\t\terror: errors.join(\"; \"),\n\t\t\t\t\tmanualFix:\n\t\t\t\t\t\t\"Use [workspace.dependencies] with `dep.workspace = true` in member Cargo.toml files to share versions.\",\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn true;\n\t\t},\n\t});\n"]}
@@ -0,0 +1,24 @@
1
+ import type { PolicyDefinition } from "../policy.js";
2
+ /**
3
+ * Configuration for the WorkspaceInheritance policy.
4
+ *
5
+ * @alpha
6
+ */
7
+ export interface WorkspaceInheritanceConfig {
8
+ /**
9
+ * Package fields that should use workspace inheritance.
10
+ * @defaultValue ["version", "authors", "license", "repository"]
11
+ */
12
+ inherit?: string[];
13
+ /**
14
+ * Crate paths that are allowed to override inherited fields.
15
+ */
16
+ allowOverrides?: string[];
17
+ }
18
+ /**
19
+ * A policy that ensures Cargo workspace members inherit fields from the workspace config.
20
+ *
21
+ * @alpha
22
+ */
23
+ export declare const WorkspaceInheritance: PolicyDefinition<WorkspaceInheritanceConfig>;
24
+ //# sourceMappingURL=WorkspaceInheritance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WorkspaceInheritance.d.ts","sourceRoot":"","sources":["../../src/policies/WorkspaceInheritance.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGrD;;;;GAIG;AACH,MAAM,WAAW,0BAA0B;IAC1C;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAEnB;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AA0DD;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,EAAE,gBAAgB,CAAC,0BAA0B,CAyD3E,CAAC"}
@@ -0,0 +1,94 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "pathe";
3
+ import { makePolicyDefinition } from "../makePolicy.js";
4
+ import { parseToml } from "../policyDefiners/tomlParser.js";
5
+ const DEFAULT_INHERIT_FIELDS = ["version", "authors", "license", "repository"];
6
+ /**
7
+ * Check if a member path matches any override pattern.
8
+ */
9
+ function isOverrideAllowed(member, allowOverrides) {
10
+ return allowOverrides.some((pattern) => {
11
+ if (pattern.endsWith("*")) {
12
+ return member.startsWith(pattern.slice(0, -1));
13
+ }
14
+ return member === pattern;
15
+ });
16
+ }
17
+ /**
18
+ * Check a single member's fields for workspace inheritance compliance.
19
+ */
20
+ async function checkMemberInheritance(member, root, availableFields, errors) {
21
+ const memberCargoPath = path.join(root, member, "Cargo.toml");
22
+ try {
23
+ const memberContent = await readFile(memberCargoPath, "utf-8");
24
+ const memberToml = await parseToml(memberContent);
25
+ const memberPkg = memberToml.package;
26
+ if (!memberPkg) {
27
+ return;
28
+ }
29
+ for (const field of availableFields) {
30
+ const value = memberPkg[field];
31
+ // Check if the field uses workspace inheritance
32
+ if (typeof value === "object" &&
33
+ value !== null &&
34
+ value.workspace === true) {
35
+ // Using workspace inheritance - good
36
+ continue;
37
+ }
38
+ if (value !== undefined) {
39
+ errors.push(`${member}: package.${field} should use workspace inheritance (${field}.workspace = true)`);
40
+ }
41
+ }
42
+ }
43
+ catch {
44
+ // Member might not exist (handled by WorkspaceMembersValid)
45
+ }
46
+ }
47
+ /**
48
+ * A policy that ensures Cargo workspace members inherit fields from the workspace config.
49
+ *
50
+ * @alpha
51
+ */
52
+ export const WorkspaceInheritance = makePolicyDefinition({
53
+ name: "WorkspaceInheritance",
54
+ description: "Ensures Cargo workspace members inherit shared fields (version, authors, etc.) from workspace config.",
55
+ match: /^Cargo\.toml$/,
56
+ handler: async ({ file, root, config }) => {
57
+ const content = await readFile(path.resolve(root, file), "utf-8");
58
+ const toml = await parseToml(content);
59
+ const workspace = toml.workspace;
60
+ if (!workspace) {
61
+ return true;
62
+ }
63
+ const members = workspace.members;
64
+ if (!members || members.length === 0) {
65
+ return true;
66
+ }
67
+ const inheritFields = config?.inherit ?? DEFAULT_INHERIT_FIELDS;
68
+ const allowOverrides = config?.allowOverrides ?? [];
69
+ // Check which fields are defined at the workspace level
70
+ const workspacePkg = (workspace.package ?? {});
71
+ const availableFields = inheritFields.filter((field) => workspacePkg[field] !== undefined);
72
+ if (availableFields.length === 0) {
73
+ return true;
74
+ }
75
+ const errors = [];
76
+ for (const member of members) {
77
+ if (member.includes("*")) {
78
+ continue;
79
+ }
80
+ if (isOverrideAllowed(member, allowOverrides)) {
81
+ continue;
82
+ }
83
+ await checkMemberInheritance(member, root, availableFields, errors);
84
+ }
85
+ if (errors.length > 0) {
86
+ return {
87
+ error: errors.join("; "),
88
+ manualFix: "Use workspace inheritance in member Cargo.toml files, e.g., `version.workspace = true`.",
89
+ };
90
+ }
91
+ return true;
92
+ },
93
+ });
94
+ //# sourceMappingURL=WorkspaceInheritance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WorkspaceInheritance.js","sourceRoot":"","sources":["../../src/policies/WorkspaceInheritance.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport path from \"pathe\";\nimport { makePolicyDefinition } from \"../makePolicy.js\";\nimport type { PolicyDefinition } from \"../policy.js\";\nimport { parseToml } from \"../policyDefiners/tomlParser.js\";\n\n/**\n * Configuration for the WorkspaceInheritance policy.\n *\n * @alpha\n */\nexport interface WorkspaceInheritanceConfig {\n\t/**\n\t * Package fields that should use workspace inheritance.\n\t * @defaultValue [\"version\", \"authors\", \"license\", \"repository\"]\n\t */\n\tinherit?: string[];\n\n\t/**\n\t * Crate paths that are allowed to override inherited fields.\n\t */\n\tallowOverrides?: string[];\n}\n\nconst DEFAULT_INHERIT_FIELDS = [\"version\", \"authors\", \"license\", \"repository\"];\n\n/**\n * Check if a member path matches any override pattern.\n */\nfunction isOverrideAllowed(member: string, allowOverrides: string[]): boolean {\n\treturn allowOverrides.some((pattern) => {\n\t\tif (pattern.endsWith(\"*\")) {\n\t\t\treturn member.startsWith(pattern.slice(0, -1));\n\t\t}\n\t\treturn member === pattern;\n\t});\n}\n\n/**\n * Check a single member's fields for workspace inheritance compliance.\n */\nasync function checkMemberInheritance(\n\tmember: string,\n\troot: string,\n\tavailableFields: string[],\n\terrors: string[],\n): Promise<void> {\n\tconst memberCargoPath = path.join(root, member, \"Cargo.toml\");\n\n\ttry {\n\t\tconst memberContent = await readFile(memberCargoPath, \"utf-8\");\n\t\tconst memberToml = await parseToml(memberContent);\n\t\tconst memberPkg = memberToml.package as Record<string, unknown> | undefined;\n\n\t\tif (!memberPkg) {\n\t\t\treturn;\n\t\t}\n\n\t\tfor (const field of availableFields) {\n\t\t\tconst value = memberPkg[field];\n\t\t\t// Check if the field uses workspace inheritance\n\t\t\tif (\n\t\t\t\ttypeof value === \"object\" &&\n\t\t\t\tvalue !== null &&\n\t\t\t\t(value as Record<string, unknown>).workspace === true\n\t\t\t) {\n\t\t\t\t// Using workspace inheritance - good\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (value !== undefined) {\n\t\t\t\terrors.push(\n\t\t\t\t\t`${member}: package.${field} should use workspace inheritance (${field}.workspace = true)`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// Member might not exist (handled by WorkspaceMembersValid)\n\t}\n}\n\n/**\n * A policy that ensures Cargo workspace members inherit fields from the workspace config.\n *\n * @alpha\n */\nexport const WorkspaceInheritance: PolicyDefinition<WorkspaceInheritanceConfig> =\n\tmakePolicyDefinition({\n\t\tname: \"WorkspaceInheritance\",\n\t\tdescription:\n\t\t\t\"Ensures Cargo workspace members inherit shared fields (version, authors, etc.) from workspace config.\",\n\t\tmatch: /^Cargo\\.toml$/,\n\t\thandler: async ({ file, root, config }) => {\n\t\t\tconst content = await readFile(path.resolve(root, file), \"utf-8\");\n\t\t\tconst toml = await parseToml(content);\n\n\t\t\tconst workspace = toml.workspace as Record<string, unknown> | undefined;\n\t\t\tif (!workspace) {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tconst members = workspace.members as string[] | undefined;\n\t\t\tif (!members || members.length === 0) {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tconst inheritFields = config?.inherit ?? DEFAULT_INHERIT_FIELDS;\n\t\t\tconst allowOverrides = config?.allowOverrides ?? [];\n\n\t\t\t// Check which fields are defined at the workspace level\n\t\t\tconst workspacePkg = (workspace.package ?? {}) as Record<string, unknown>;\n\t\t\tconst availableFields = inheritFields.filter(\n\t\t\t\t(field) => workspacePkg[field] !== undefined,\n\t\t\t);\n\n\t\t\tif (availableFields.length === 0) {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tconst errors: string[] = [];\n\n\t\t\tfor (const member of members) {\n\t\t\t\tif (member.includes(\"*\")) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (isOverrideAllowed(member, allowOverrides)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tawait checkMemberInheritance(member, root, availableFields, errors);\n\t\t\t}\n\n\t\t\tif (errors.length > 0) {\n\t\t\t\treturn {\n\t\t\t\t\terror: errors.join(\"; \"),\n\t\t\t\t\tmanualFix:\n\t\t\t\t\t\t\"Use workspace inheritance in member Cargo.toml files, e.g., `version.workspace = true`.\",\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn true;\n\t\t},\n\t});\n"]}
@@ -0,0 +1,20 @@
1
+ import type { PolicyDefinition } from "../policy.js";
2
+ /**
3
+ * Configuration for the WorkspaceMembersValid policy.
4
+ *
5
+ * @alpha
6
+ */
7
+ export interface WorkspaceMembersValidConfig {
8
+ /**
9
+ * If true, validate that all member paths exist on disk.
10
+ * @defaultValue true
11
+ */
12
+ validatePaths?: boolean;
13
+ }
14
+ /**
15
+ * A policy that validates Cargo workspace member paths exist.
16
+ *
17
+ * @alpha
18
+ */
19
+ export declare const WorkspaceMembersValid: PolicyDefinition<WorkspaceMembersValidConfig>;
20
+ //# sourceMappingURL=WorkspaceMembersValid.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WorkspaceMembersValid.d.ts","sourceRoot":"","sources":["../../src/policies/WorkspaceMembersValid.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGrD;;;;GAIG;AACH,MAAM,WAAW,2BAA2B;IAC3C;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACxB;AAoDD;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,EAAE,gBAAgB,CAAC,2BAA2B,CA0C7E,CAAC"}
@@ -0,0 +1,88 @@
1
+ import { existsSync, readdirSync } from "node:fs";
2
+ import { readFile } from "node:fs/promises";
3
+ import path from "pathe";
4
+ import { makePolicyDefinition } from "../makePolicy.js";
5
+ import { parseToml } from "../policyDefiners/tomlParser.js";
6
+ const GLOB_SUFFIX_REGEX = /\/?\*\*?$/;
7
+ /**
8
+ * Expand a simple glob pattern (e.g., "crates/*") into matching directories.
9
+ */
10
+ function expandSimpleGlob(root, pattern) {
11
+ if (!pattern.includes("*")) {
12
+ return [pattern];
13
+ }
14
+ const base = pattern.replace(GLOB_SUFFIX_REGEX, "");
15
+ const baseDir = path.join(root, base);
16
+ if (!existsSync(baseDir)) {
17
+ return [];
18
+ }
19
+ try {
20
+ const entries = readdirSync(baseDir, { withFileTypes: true });
21
+ return entries
22
+ .filter((e) => e.isDirectory())
23
+ .filter((e) => existsSync(path.join(baseDir, e.name, "Cargo.toml")))
24
+ .map((e) => path.join(base, e.name));
25
+ }
26
+ catch {
27
+ return [];
28
+ }
29
+ }
30
+ /**
31
+ * Validate a single workspace member entry.
32
+ */
33
+ function validateMember(member, root, errors) {
34
+ if (member.includes("*")) {
35
+ // Expand glob and check each result
36
+ const expanded = expandSimpleGlob(root, member);
37
+ if (expanded.length === 0) {
38
+ errors.push(`Workspace member glob "${member}" matches no directories with Cargo.toml`);
39
+ }
40
+ }
41
+ else {
42
+ const memberPath = path.join(root, member);
43
+ if (!existsSync(memberPath)) {
44
+ errors.push(`Workspace member "${member}" does not exist`);
45
+ }
46
+ else if (!existsSync(path.join(memberPath, "Cargo.toml"))) {
47
+ errors.push(`Workspace member "${member}" exists but has no Cargo.toml`);
48
+ }
49
+ }
50
+ }
51
+ /**
52
+ * A policy that validates Cargo workspace member paths exist.
53
+ *
54
+ * @alpha
55
+ */
56
+ export const WorkspaceMembersValid = makePolicyDefinition({
57
+ name: "WorkspaceMembersValid",
58
+ description: "Validates that all Cargo workspace member paths exist and contain Cargo.toml files.",
59
+ match: /^Cargo\.toml$/,
60
+ handler: async ({ file, root, config }) => {
61
+ const validatePaths = config?.validatePaths ?? true;
62
+ const content = await readFile(path.resolve(root, file), "utf-8");
63
+ const toml = await parseToml(content);
64
+ const workspace = toml.workspace;
65
+ if (!workspace) {
66
+ return true;
67
+ }
68
+ const members = workspace.members;
69
+ if (!members || members.length === 0) {
70
+ return true;
71
+ }
72
+ if (!validatePaths) {
73
+ return true;
74
+ }
75
+ const errors = [];
76
+ for (const member of members) {
77
+ validateMember(member, root, errors);
78
+ }
79
+ if (errors.length > 0) {
80
+ return {
81
+ error: errors.join("; "),
82
+ manualFix: "Fix the workspace.members paths in root Cargo.toml to point to valid crate directories.",
83
+ };
84
+ }
85
+ return true;
86
+ },
87
+ });
88
+ //# sourceMappingURL=WorkspaceMembersValid.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WorkspaceMembersValid.js","sourceRoot":"","sources":["../../src/policies/WorkspaceMembersValid.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readdirSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport path from \"pathe\";\nimport { makePolicyDefinition } from \"../makePolicy.js\";\nimport type { PolicyDefinition } from \"../policy.js\";\nimport { parseToml } from \"../policyDefiners/tomlParser.js\";\n\n/**\n * Configuration for the WorkspaceMembersValid policy.\n *\n * @alpha\n */\nexport interface WorkspaceMembersValidConfig {\n\t/**\n\t * If true, validate that all member paths exist on disk.\n\t * @defaultValue true\n\t */\n\tvalidatePaths?: boolean;\n}\n\nconst GLOB_SUFFIX_REGEX = /\\/?\\*\\*?$/;\n\n/**\n * Expand a simple glob pattern (e.g., \"crates/*\") into matching directories.\n */\nfunction expandSimpleGlob(root: string, pattern: string): string[] {\n\tif (!pattern.includes(\"*\")) {\n\t\treturn [pattern];\n\t}\n\n\tconst base = pattern.replace(GLOB_SUFFIX_REGEX, \"\");\n\tconst baseDir = path.join(root, base);\n\n\tif (!existsSync(baseDir)) {\n\t\treturn [];\n\t}\n\n\ttry {\n\t\tconst entries = readdirSync(baseDir, { withFileTypes: true });\n\t\treturn entries\n\t\t\t.filter((e) => e.isDirectory())\n\t\t\t.filter((e) => existsSync(path.join(baseDir, e.name, \"Cargo.toml\")))\n\t\t\t.map((e) => path.join(base, e.name));\n\t} catch {\n\t\treturn [];\n\t}\n}\n\n/**\n * Validate a single workspace member entry.\n */\nfunction validateMember(member: string, root: string, errors: string[]): void {\n\tif (member.includes(\"*\")) {\n\t\t// Expand glob and check each result\n\t\tconst expanded = expandSimpleGlob(root, member);\n\t\tif (expanded.length === 0) {\n\t\t\terrors.push(\n\t\t\t\t`Workspace member glob \"${member}\" matches no directories with Cargo.toml`,\n\t\t\t);\n\t\t}\n\t} else {\n\t\tconst memberPath = path.join(root, member);\n\t\tif (!existsSync(memberPath)) {\n\t\t\terrors.push(`Workspace member \"${member}\" does not exist`);\n\t\t} else if (!existsSync(path.join(memberPath, \"Cargo.toml\"))) {\n\t\t\terrors.push(`Workspace member \"${member}\" exists but has no Cargo.toml`);\n\t\t}\n\t}\n}\n\n/**\n * A policy that validates Cargo workspace member paths exist.\n *\n * @alpha\n */\nexport const WorkspaceMembersValid: PolicyDefinition<WorkspaceMembersValidConfig> =\n\tmakePolicyDefinition({\n\t\tname: \"WorkspaceMembersValid\",\n\t\tdescription:\n\t\t\t\"Validates that all Cargo workspace member paths exist and contain Cargo.toml files.\",\n\t\tmatch: /^Cargo\\.toml$/,\n\t\thandler: async ({ file, root, config }) => {\n\t\t\tconst validatePaths = config?.validatePaths ?? true;\n\n\t\t\tconst content = await readFile(path.resolve(root, file), \"utf-8\");\n\t\t\tconst toml = await parseToml(content);\n\n\t\t\tconst workspace = toml.workspace as Record<string, unknown> | undefined;\n\t\t\tif (!workspace) {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tconst members = workspace.members as string[] | undefined;\n\t\t\tif (!members || members.length === 0) {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tif (!validatePaths) {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tconst errors: string[] = [];\n\n\t\t\tfor (const member of members) {\n\t\t\t\tvalidateMember(member, root, errors);\n\t\t\t}\n\n\t\t\tif (errors.length > 0) {\n\t\t\t\treturn {\n\t\t\t\t\terror: errors.join(\"; \"),\n\t\t\t\t\tmanualFix:\n\t\t\t\t\t\t\"Fix the workspace.members paths in root Cargo.toml to point to valid crate directories.\",\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn true;\n\t\t},\n\t});\n"]}
@@ -2,4 +2,12 @@
2
2
  * A regular expression that matches package.json files in path strings.
3
3
  */
4
4
  export declare const PackageJsonRegexMatch: RegExp;
5
+ /**
6
+ * A regular expression that matches Cargo.toml files in path strings.
7
+ */
8
+ export declare const CargoTomlRegexMatch: RegExp;
9
+ /**
10
+ * A regular expression that matches gleam.toml files in path strings.
11
+ */
12
+ export declare const GleamTomlRegexMatch: RegExp;
5
13
  //# sourceMappingURL=constants.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/policies/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,qBAAqB,QAAyB,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/policies/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,qBAAqB,QAAyB,CAAC;AAE5D;;GAEG;AACH,eAAO,MAAM,mBAAmB,QAAuB,CAAC;AAExD;;GAEG;AACH,eAAO,MAAM,mBAAmB,QAAuB,CAAC"}
@@ -2,4 +2,12 @@
2
2
  * A regular expression that matches package.json files in path strings.
3
3
  */
4
4
  export const PackageJsonRegexMatch = /(^|\/)package\.json/i;
5
+ /**
6
+ * A regular expression that matches Cargo.toml files in path strings.
7
+ */
8
+ export const CargoTomlRegexMatch = /(^|\/)Cargo\.toml$/;
9
+ /**
10
+ * A regular expression that matches gleam.toml files in path strings.
11
+ */
12
+ export const GleamTomlRegexMatch = /(^|\/)gleam\.toml$/;
5
13
  //# sourceMappingURL=constants.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/policies/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,sBAAsB,CAAC","sourcesContent":["/**\n * A regular expression that matches package.json files in path strings.\n */\nexport const PackageJsonRegexMatch = /(^|\\/)package\\.json/i;\n"]}
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/policies/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,sBAAsB,CAAC;AAE5D;;GAEG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,oBAAoB,CAAC;AAExD;;GAEG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,oBAAoB,CAAC","sourcesContent":["/**\n * A regular expression that matches package.json files in path strings.\n */\nexport const PackageJsonRegexMatch = /(^|\\/)package\\.json/i;\n\n/**\n * A regular expression that matches Cargo.toml files in path strings.\n */\nexport const CargoTomlRegexMatch = /(^|\\/)Cargo\\.toml$/;\n\n/**\n * A regular expression that matches gleam.toml files in path strings.\n */\nexport const GleamTomlRegexMatch = /(^|\\/)gleam\\.toml$/;\n"]}
package/esm/policies.d.ts CHANGED
@@ -1,9 +1,35 @@
1
+ export { CargoLicenceValidated, type CargoLicenceValidatedConfig, } from "./policies/CargoLicenceValidated.js";
2
+ export { CargoLintsConfigured, type CargoLintsConfiguredConfig, } from "./policies/CargoLintsConfigured.js";
3
+ export { CargoLockPolicy, type CargoLockPolicyConfig, } from "./policies/CargoLockPolicy.js";
4
+ export { CargoTomlRequired, type CargoTomlRequiredConfig, } from "./policies/CargoTomlRequired.js";
5
+ export { CargoTomlSorted, type CargoTomlSortedConfig, } from "./policies/CargoTomlSorted.js";
6
+ export { type ConditionalScriptEntry, type ConditionalScriptRule, ConditionalScripts, type ConditionalScriptsConfig, } from "./policies/ConditionalScripts.js";
7
+ export { ExactScripts, type ExactScriptsConfig, } from "./policies/ExactScripts.js";
8
+ export { GleamDepVersions, type GleamDepVersionsConfig, } from "./policies/GleamDepVersions.js";
9
+ export { GleamDocExists, type GleamDocExistsConfig, } from "./policies/GleamDocExists.js";
10
+ export { GleamInternalModulesConfigured, type GleamInternalModulesConfiguredConfig, } from "./policies/GleamInternalModulesConfigured.js";
11
+ export { GleamLicenceConfigured, type GleamLicenceConfiguredConfig, } from "./policies/GleamLicenceConfigured.js";
12
+ export { GleamManifestCommitted } from "./policies/GleamManifestCommitted.js";
13
+ export { GleamMonorepoConsistency, type GleamMonorepoConsistencyConfig, } from "./policies/GleamMonorepoConsistency.js";
14
+ export { GleamNameRestrictions, type GleamNameRestrictionsConfig, } from "./policies/GleamNameRestrictions.js";
15
+ export { GleamNoPathDepsInPublished, type GleamNoPathDepsInPublishedConfig, } from "./policies/GleamNoPathDepsInPublished.js";
16
+ export { GleamPathDepsValid } from "./policies/GleamPathDepsValid.js";
17
+ export { GleamProjectStructure, type GleamProjectStructureConfig, } from "./policies/GleamProjectStructure.js";
18
+ export { GleamSharedDependencyVersions, type GleamSharedDependencyVersionsConfig, } from "./policies/GleamSharedDependencyVersions.js";
19
+ export { GleamTomlRequired, type GleamTomlRequiredConfig, } from "./policies/GleamTomlRequired.js";
20
+ export { GleamTomlSorted, type GleamTomlSortedConfig, } from "./policies/GleamTomlSorted.js";
21
+ export { GleamVersionConstraint, type GleamVersionConstraintConfig, } from "./policies/GleamVersionConstraint.js";
1
22
  export { HtmlFileHeaders } from "./policies/HtmlFileHeaders.js";
2
23
  export { JsTsFileHeaders } from "./policies/JsTsFileHeaders.js";
3
24
  export { LicenseFileExists } from "./policies/LicenseFileExists.js";
25
+ export { MutuallyExclusiveScripts, type MutuallyExclusiveScriptsConfig, } from "./policies/MutuallyExclusiveScripts.js";
26
+ export { NoBuildDirectory } from "./policies/NoBuildDirectory.js";
4
27
  export { NoJsFileExtensions } from "./policies/NoJsFileExtensions.js";
5
28
  export { NoLargeBinaryFiles } from "./policies/NoLargeBinaryFiles.js";
6
29
  export { NoPrivateWorkspaceDependencies } from "./policies/NoPrivateWorkspaceDependencies.js";
30
+ export { NoTargetArtifacts } from "./policies/NoTargetArtifacts.js";
31
+ export { NoUnsafeWithoutJustification, type NoUnsafeWithoutJustificationConfig, } from "./policies/NoUnsafeWithoutJustification.js";
32
+ export { NoWildcardDependencies, type NoWildcardDependenciesConfig, } from "./policies/NoWildcardDependencies.js";
7
33
  export { PackageAllowedScopes, type PackageAllowedScopesConfig, } from "./policies/PackageAllowedScopes.js";
8
34
  export { PackageEsmType, type PackageEsmTypeConfig, } from "./policies/PackageEsmType.js";
9
35
  export { PackageFolderName, type PackageFolderNameConfig, } from "./policies/PackageFolderName.js";
@@ -13,8 +39,17 @@ export { PackageJsonSorted } from "./policies/PackageJsonSorted.js";
13
39
  export { PackageLicense, type PackageLicenseSettings, } from "./policies/PackageLicense.js";
14
40
  export { PackagePrivateField, type PackagePrivateFieldConfig, } from "./policies/PackagePrivateField.js";
15
41
  export { PackageReadme, type PackageReadmeSettings, } from "./policies/PackageReadme.js";
16
- export { type ConditionalScriptRule, PackageScripts, type PackageScriptsSettings, type RequiredScriptEntry, type ScriptMustContainRule, } from "./policies/PackageScripts.js";
42
+ export { type ConditionalScriptRule as LegacyConditionalScriptRule, PackageScripts, type PackageScriptsSettings, type RequiredScriptEntry as LegacyRequiredScriptEntry, type ScriptMustContainRule, } from "./policies/PackageScripts.js";
17
43
  export { PackageTestScripts, type PackageTestScriptsConfig, } from "./policies/PackageTestScripts.js";
44
+ export { PublicApiDocumented, type PublicApiDocumentedConfig, } from "./policies/PublicApiDocumented.js";
18
45
  export { RequiredGitignorePatterns } from "./policies/RequiredGitignorePatterns.js";
46
+ export { type RequiredScriptEntry, RequiredScripts, type RequiredScriptsConfig, } from "./policies/RequiredScripts.js";
47
+ export { RustDocExists, type RustDocExistsConfig, } from "./policies/RustDocExists.js";
48
+ export { RustfmtConfigExists, type RustfmtConfigExistsConfig, } from "./policies/RustfmtConfigExists.js";
49
+ export { RustToolchainExists, type RustToolchainExistsConfig, } from "./policies/RustToolchainExists.js";
50
+ export { ScriptContains, type ScriptContainsConfig, type ScriptContainsRule, } from "./policies/ScriptContains.js";
51
+ export { SharedDependencyVersions, type SharedDependencyVersionsConfig, } from "./policies/SharedDependencyVersions.js";
52
+ export { WorkspaceInheritance, type WorkspaceInheritanceConfig, } from "./policies/WorkspaceInheritance.js";
53
+ export { WorkspaceMembersValid, type WorkspaceMembersValidConfig, } from "./policies/WorkspaceMembersValid.js";
19
54
  export { DefaultPolicies } from "./policy.js";
20
55
  //# sourceMappingURL=policies.d.ts.map