repopo 0.10.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.
- package/CHANGELOG.md +60 -0
- package/README.md +41 -2
- package/THIRD-PARTY-LICENSES.txt +71 -11
- package/bin/dev.js +5 -1
- package/esm/baseCommand.d.ts +1 -1
- package/esm/baseCommand.d.ts.map +1 -1
- package/esm/baseCommand.js +6 -9
- package/esm/baseCommand.js.map +1 -1
- package/esm/commands/check-native.d.ts +25 -0
- package/esm/commands/check-native.d.ts.map +1 -0
- package/esm/commands/check-native.js +137 -0
- package/esm/commands/check-native.js.map +1 -0
- package/esm/commands/check.d.ts +8 -20
- package/esm/commands/check.d.ts.map +1 -1
- package/esm/commands/check.js +77 -186
- package/esm/commands/check.js.map +1 -1
- package/esm/commands/list.d.ts.map +1 -1
- package/esm/commands/list.js +5 -6
- package/esm/commands/list.js.map +1 -1
- package/esm/context.d.ts +13 -3
- package/esm/context.d.ts.map +1 -1
- package/esm/context.js +11 -1
- package/esm/context.js.map +1 -1
- package/esm/index.d.ts +6 -1
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +3 -0
- package/esm/index.js.map +1 -1
- package/esm/makePolicy.d.ts +29 -2
- package/esm/makePolicy.d.ts.map +1 -1
- package/esm/makePolicy.js +46 -2
- package/esm/makePolicy.js.map +1 -1
- package/esm/perf.d.ts +7 -2
- package/esm/perf.d.ts.map +1 -1
- package/esm/perf.js.map +1 -1
- package/esm/policies/CargoLicenceValidated.d.ts +41 -0
- package/esm/policies/CargoLicenceValidated.d.ts.map +1 -0
- package/esm/policies/CargoLicenceValidated.js +45 -0
- package/esm/policies/CargoLicenceValidated.js.map +1 -0
- package/esm/policies/CargoLintsConfigured.d.ts +30 -0
- package/esm/policies/CargoLintsConfigured.d.ts.map +1 -0
- package/esm/policies/CargoLintsConfigured.js +61 -0
- package/esm/policies/CargoLintsConfigured.js.map +1 -0
- package/esm/policies/CargoLockPolicy.d.ts +34 -0
- package/esm/policies/CargoLockPolicy.d.ts.map +1 -0
- package/esm/policies/CargoLockPolicy.js +57 -0
- package/esm/policies/CargoLockPolicy.js.map +1 -0
- package/esm/policies/CargoTomlRequired.d.ts +34 -0
- package/esm/policies/CargoTomlRequired.d.ts.map +1 -0
- package/esm/policies/CargoTomlRequired.js +77 -0
- package/esm/policies/CargoTomlRequired.js.map +1 -0
- package/esm/policies/CargoTomlSorted.d.ts +20 -0
- package/esm/policies/CargoTomlSorted.d.ts.map +1 -0
- package/esm/policies/CargoTomlSorted.js +164 -0
- package/esm/policies/CargoTomlSorted.js.map +1 -0
- package/esm/policies/ConditionalScripts.d.ts.map +1 -1
- package/esm/policies/ConditionalScripts.js +3 -2
- package/esm/policies/ConditionalScripts.js.map +1 -1
- package/esm/policies/ExactScripts.d.ts.map +1 -1
- package/esm/policies/ExactScripts.js +3 -2
- package/esm/policies/ExactScripts.js.map +1 -1
- package/esm/policies/GleamDepVersions.d.ts +25 -0
- package/esm/policies/GleamDepVersions.d.ts.map +1 -0
- package/esm/policies/GleamDepVersions.js +67 -0
- package/esm/policies/GleamDepVersions.js.map +1 -0
- package/esm/policies/GleamDocExists.d.ts +24 -0
- package/esm/policies/GleamDocExists.d.ts.map +1 -0
- package/esm/policies/GleamDocExists.js +39 -0
- package/esm/policies/GleamDocExists.js.map +1 -0
- package/esm/policies/GleamInternalModulesConfigured.d.ts +21 -0
- package/esm/policies/GleamInternalModulesConfigured.d.ts.map +1 -0
- package/esm/policies/GleamInternalModulesConfigured.js +30 -0
- package/esm/policies/GleamInternalModulesConfigured.js.map +1 -0
- package/esm/policies/GleamLicenceConfigured.d.ts +29 -0
- package/esm/policies/GleamLicenceConfigured.d.ts.map +1 -0
- package/esm/policies/GleamLicenceConfigured.js +34 -0
- package/esm/policies/GleamLicenceConfigured.js.map +1 -0
- package/esm/policies/GleamManifestCommitted.d.ts +9 -0
- package/esm/policies/GleamManifestCommitted.d.ts.map +1 -0
- package/esm/policies/GleamManifestCommitted.js +26 -0
- package/esm/policies/GleamManifestCommitted.js.map +1 -0
- package/esm/policies/GleamMonorepoConsistency.d.ts +30 -0
- package/esm/policies/GleamMonorepoConsistency.d.ts.map +1 -0
- package/esm/policies/GleamMonorepoConsistency.js +74 -0
- package/esm/policies/GleamMonorepoConsistency.js.map +1 -0
- package/esm/policies/GleamNameRestrictions.d.ts +25 -0
- package/esm/policies/GleamNameRestrictions.d.ts.map +1 -0
- package/esm/policies/GleamNameRestrictions.js +44 -0
- package/esm/policies/GleamNameRestrictions.js.map +1 -0
- package/esm/policies/GleamNoPathDepsInPublished.d.ts +22 -0
- package/esm/policies/GleamNoPathDepsInPublished.d.ts.map +1 -0
- package/esm/policies/GleamNoPathDepsInPublished.js +43 -0
- package/esm/policies/GleamNoPathDepsInPublished.js.map +1 -0
- package/esm/policies/GleamPathDepsValid.d.ts +9 -0
- package/esm/policies/GleamPathDepsValid.d.ts.map +1 -0
- package/esm/policies/GleamPathDepsValid.js +59 -0
- package/esm/policies/GleamPathDepsValid.js.map +1 -0
- package/esm/policies/GleamProjectStructure.d.ts +25 -0
- package/esm/policies/GleamProjectStructure.d.ts.map +1 -0
- package/esm/policies/GleamProjectStructure.js +33 -0
- package/esm/policies/GleamProjectStructure.js.map +1 -0
- package/esm/policies/GleamSharedDependencyVersions.d.ts +29 -0
- package/esm/policies/GleamSharedDependencyVersions.d.ts.map +1 -0
- package/esm/policies/GleamSharedDependencyVersions.js +101 -0
- package/esm/policies/GleamSharedDependencyVersions.js.map +1 -0
- package/esm/policies/GleamTomlRequired.d.ts +30 -0
- package/esm/policies/GleamTomlRequired.d.ts.map +1 -0
- package/esm/policies/GleamTomlRequired.js +37 -0
- package/esm/policies/GleamTomlRequired.js.map +1 -0
- package/esm/policies/GleamTomlSorted.d.ts +20 -0
- package/esm/policies/GleamTomlSorted.d.ts.map +1 -0
- package/esm/policies/GleamTomlSorted.js +153 -0
- package/esm/policies/GleamTomlSorted.js.map +1 -0
- package/esm/policies/GleamVersionConstraint.d.ts +20 -0
- package/esm/policies/GleamVersionConstraint.d.ts.map +1 -0
- package/esm/policies/GleamVersionConstraint.js +64 -0
- package/esm/policies/GleamVersionConstraint.js.map +1 -0
- package/esm/policies/HtmlFileHeaders.js +1 -1
- package/esm/policies/HtmlFileHeaders.js.map +1 -1
- package/esm/policies/NoBuildDirectory.d.ts +9 -0
- package/esm/policies/NoBuildDirectory.d.ts.map +1 -0
- package/esm/policies/NoBuildDirectory.js +29 -0
- package/esm/policies/NoBuildDirectory.js.map +1 -0
- package/esm/policies/NoTargetArtifacts.d.ts +8 -0
- package/esm/policies/NoTargetArtifacts.d.ts.map +1 -0
- package/esm/policies/NoTargetArtifacts.js +42 -0
- package/esm/policies/NoTargetArtifacts.js.map +1 -0
- package/esm/policies/NoUnsafeWithoutJustification.d.ts +24 -0
- package/esm/policies/NoUnsafeWithoutJustification.d.ts.map +1 -0
- package/esm/policies/NoUnsafeWithoutJustification.js +80 -0
- package/esm/policies/NoUnsafeWithoutJustification.js.map +1 -0
- package/esm/policies/NoWildcardDependencies.d.ts +30 -0
- package/esm/policies/NoWildcardDependencies.d.ts.map +1 -0
- package/esm/policies/NoWildcardDependencies.js +83 -0
- package/esm/policies/NoWildcardDependencies.js.map +1 -0
- package/esm/policies/PackageEsmType.d.ts.map +1 -1
- package/esm/policies/PackageEsmType.js +7 -5
- package/esm/policies/PackageEsmType.js.map +1 -1
- package/esm/policies/PackageJsonProperties.d.ts.map +1 -1
- package/esm/policies/PackageJsonProperties.js +12 -4
- package/esm/policies/PackageJsonProperties.js.map +1 -1
- package/esm/policies/PackageJsonRepoDirectoryProperty.d.ts.map +1 -1
- package/esm/policies/PackageJsonRepoDirectoryProperty.js +3 -2
- package/esm/policies/PackageJsonRepoDirectoryProperty.js.map +1 -1
- package/esm/policies/PackageJsonSorted.d.ts.map +1 -1
- package/esm/policies/PackageJsonSorted.js +42 -7
- package/esm/policies/PackageJsonSorted.js.map +1 -1
- package/esm/policies/PackageLicense.d.ts.map +1 -1
- package/esm/policies/PackageLicense.js +1 -2
- package/esm/policies/PackageLicense.js.map +1 -1
- package/esm/policies/PackagePrivateField.d.ts.map +1 -1
- package/esm/policies/PackagePrivateField.js +3 -2
- package/esm/policies/PackagePrivateField.js.map +1 -1
- package/esm/policies/PackageReadme.js +2 -2
- package/esm/policies/PackageReadme.js.map +1 -1
- package/esm/policies/PackageScripts.d.ts.map +1 -1
- package/esm/policies/PackageScripts.js +3 -2
- package/esm/policies/PackageScripts.js.map +1 -1
- package/esm/policies/PackageTestScripts.js +3 -3
- package/esm/policies/PackageTestScripts.js.map +1 -1
- package/esm/policies/PublicApiDocumented.d.ts +21 -0
- package/esm/policies/PublicApiDocumented.d.ts.map +1 -0
- package/esm/policies/PublicApiDocumented.js +36 -0
- package/esm/policies/PublicApiDocumented.js.map +1 -0
- package/esm/policies/RequiredScripts.d.ts.map +1 -1
- package/esm/policies/RequiredScripts.js +3 -2
- package/esm/policies/RequiredScripts.js.map +1 -1
- package/esm/policies/RustDocExists.d.ts +24 -0
- package/esm/policies/RustDocExists.d.ts.map +1 -0
- package/esm/policies/RustDocExists.js +49 -0
- package/esm/policies/RustDocExists.js.map +1 -0
- package/esm/policies/RustToolchainExists.d.ts +20 -0
- package/esm/policies/RustToolchainExists.d.ts.map +1 -0
- package/esm/policies/RustToolchainExists.js +27 -0
- package/esm/policies/RustToolchainExists.js.map +1 -0
- package/esm/policies/RustfmtConfigExists.d.ts +20 -0
- package/esm/policies/RustfmtConfigExists.d.ts.map +1 -0
- package/esm/policies/RustfmtConfigExists.js +27 -0
- package/esm/policies/RustfmtConfigExists.js.map +1 -0
- package/esm/policies/SharedDependencyVersions.d.ts +24 -0
- package/esm/policies/SharedDependencyVersions.d.ts.map +1 -0
- package/esm/policies/SharedDependencyVersions.js +135 -0
- package/esm/policies/SharedDependencyVersions.js.map +1 -0
- package/esm/policies/WorkspaceInheritance.d.ts +24 -0
- package/esm/policies/WorkspaceInheritance.d.ts.map +1 -0
- package/esm/policies/WorkspaceInheritance.js +94 -0
- package/esm/policies/WorkspaceInheritance.js.map +1 -0
- package/esm/policies/WorkspaceMembersValid.d.ts +20 -0
- package/esm/policies/WorkspaceMembersValid.d.ts.map +1 -0
- package/esm/policies/WorkspaceMembersValid.js +88 -0
- package/esm/policies/WorkspaceMembersValid.js.map +1 -0
- package/esm/policies/constants.d.ts +8 -0
- package/esm/policies/constants.d.ts.map +1 -1
- package/esm/policies/constants.js +8 -0
- package/esm/policies/constants.js.map +1 -1
- package/esm/policies.d.ts +30 -0
- package/esm/policies.d.ts.map +1 -1
- package/esm/policies.js +51 -1
- package/esm/policies.js.map +1 -1
- package/esm/policy.d.ts +24 -0
- package/esm/policy.d.ts.map +1 -1
- package/esm/policy.js.map +1 -1
- package/esm/policyDefiners/defineCargoPolicy.d.ts +66 -0
- package/esm/policyDefiners/defineCargoPolicy.d.ts.map +1 -0
- package/esm/policyDefiners/defineCargoPolicy.js +60 -0
- package/esm/policyDefiners/defineCargoPolicy.js.map +1 -0
- package/esm/policyDefiners/defineFileHeaderPolicy.d.ts.map +1 -1
- package/esm/policyDefiners/defineFileHeaderPolicy.js +6 -7
- package/esm/policyDefiners/defineFileHeaderPolicy.js.map +1 -1
- package/esm/policyDefiners/defineGleamPolicy.d.ts +65 -0
- package/esm/policyDefiners/defineGleamPolicy.d.ts.map +1 -0
- package/esm/policyDefiners/defineGleamPolicy.js +59 -0
- package/esm/policyDefiners/defineGleamPolicy.js.map +1 -0
- package/esm/policyDefiners/spdxValidator.d.ts +74 -0
- package/esm/policyDefiners/spdxValidator.d.ts.map +1 -0
- package/esm/policyDefiners/spdxValidator.js +127 -0
- package/esm/policyDefiners/spdxValidator.js.map +1 -0
- package/esm/policyDefiners/tomlParser.d.ts +8 -0
- package/esm/policyDefiners/tomlParser.d.ts.map +1 -0
- package/esm/policyDefiners/tomlParser.js +17 -0
- package/esm/policyDefiners/tomlParser.js.map +1 -0
- package/esm/runner.d.ts +60 -0
- package/esm/runner.d.ts.map +1 -0
- package/esm/runner.js +174 -0
- package/esm/runner.js.map +1 -0
- package/esm/tsdoc-metadata.json +1 -1
- package/oclif.manifest.json +100 -3
- package/package.json +46 -31
- package/scripts/postinstall.mjs +154 -0
- package/sidecar/sidecar.mjs +468 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { defineCargoPolicy, } from "../policyDefiners/defineCargoPolicy.js";
|
|
2
|
+
const DEFAULT_REQUIRED_FIELDS = ["license", "description"];
|
|
3
|
+
const VALID_EDITIONS = ["2015", "2018", "2021", "2024"];
|
|
4
|
+
function checkRequiredFields(pkg, config, errors) {
|
|
5
|
+
const requiredFields = config?.required ?? DEFAULT_REQUIRED_FIELDS;
|
|
6
|
+
for (const field of requiredFields) {
|
|
7
|
+
if (pkg[field] === undefined) {
|
|
8
|
+
errors.push(`Missing required field: package.${field}`);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function checkExpectedValues(pkg, config, errors) {
|
|
13
|
+
if (!config?.expectedValues) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
for (const [field, expected] of Object.entries(config.expectedValues)) {
|
|
17
|
+
const actual = pkg[field];
|
|
18
|
+
if (actual === undefined) {
|
|
19
|
+
errors.push(`Missing required field: package.${field}`);
|
|
20
|
+
}
|
|
21
|
+
else if (String(actual) !== expected) {
|
|
22
|
+
errors.push(`Expected package.${field} = "${expected}", got "${String(actual)}"`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function checkRustVersion(pkg, config, errors) {
|
|
27
|
+
if (config?.requireRustVersion && pkg["rust-version"] === undefined) {
|
|
28
|
+
errors.push("Missing package.rust-version (MSRV). Set the minimum supported Rust version.");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function checkMinimumEdition(pkg, config, errors) {
|
|
32
|
+
if (!config?.minimumEdition) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const edition = pkg.edition;
|
|
36
|
+
if (edition === undefined) {
|
|
37
|
+
errors.push("Missing package.edition field.");
|
|
38
|
+
}
|
|
39
|
+
else if (!VALID_EDITIONS.includes(edition)) {
|
|
40
|
+
errors.push(`Invalid edition "${edition}". Must be one of: ${VALID_EDITIONS.join(", ")}`);
|
|
41
|
+
}
|
|
42
|
+
else if (edition < config.minimumEdition) {
|
|
43
|
+
errors.push(`Edition "${edition}" is below minimum "${config.minimumEdition}".`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* A policy that enforces required fields exist in Cargo.toml [package] section.
|
|
48
|
+
*
|
|
49
|
+
* @alpha
|
|
50
|
+
*/
|
|
51
|
+
export const CargoTomlRequired = defineCargoPolicy({
|
|
52
|
+
name: "CargoTomlRequired",
|
|
53
|
+
description: "Ensures Cargo.toml files contain required fields in the [package] section.",
|
|
54
|
+
handler: async (toml, { config }) => {
|
|
55
|
+
const pkg = toml.package;
|
|
56
|
+
if (!pkg) {
|
|
57
|
+
// Workspace root Cargo.toml may not have [package]
|
|
58
|
+
if (toml.workspace !== undefined) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
return { error: "Missing [package] section in Cargo.toml" };
|
|
62
|
+
}
|
|
63
|
+
const errors = [];
|
|
64
|
+
checkRequiredFields(pkg, config, errors);
|
|
65
|
+
checkExpectedValues(pkg, config, errors);
|
|
66
|
+
checkRustVersion(pkg, config, errors);
|
|
67
|
+
checkMinimumEdition(pkg, config, errors);
|
|
68
|
+
if (errors.length > 0) {
|
|
69
|
+
return {
|
|
70
|
+
error: errors.join("; "),
|
|
71
|
+
manualFix: "Add the missing fields to your Cargo.toml [package] section.",
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return true;
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
//# sourceMappingURL=CargoTomlRequired.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CargoTomlRequired.js","sourceRoot":"","sources":["../../src/policies/CargoTomlRequired.ts"],"names":[],"mappings":"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","sourcesContent":["import type { PolicyShape } from \"../policy.js\";\nimport {\n\ttype CargoToml,\n\tdefineCargoPolicy,\n} from \"../policyDefiners/defineCargoPolicy.js\";\n\n/**\n * Configuration for the CargoTomlRequired policy.\n *\n * @alpha\n */\nexport interface CargoTomlRequiredConfig {\n\t/**\n\t * Fields that must exist in the [package] section.\n\t * @defaultValue [\"license\", \"description\"]\n\t */\n\trequired?: string[];\n\n\t/**\n\t * Minimum Rust edition to require (e.g., \"2021\").\n\t * If set, the `edition` field must exist and be >= this value.\n\t */\n\tminimumEdition?: string;\n\n\t/**\n\t * If true, require the `rust-version` field (MSRV) to be set.\n\t * @defaultValue false\n\t */\n\trequireRustVersion?: boolean;\n\n\t/**\n\t * Specific field values to enforce. Keys are field names, values are expected values.\n\t */\n\texpectedValues?: Record<string, string>;\n}\n\nconst DEFAULT_REQUIRED_FIELDS = [\"license\", \"description\"];\nconst VALID_EDITIONS = [\"2015\", \"2018\", \"2021\", \"2024\"];\n\nfunction checkRequiredFields(\n\tpkg: Record<string, unknown>,\n\tconfig: CargoTomlRequiredConfig | undefined,\n\terrors: string[],\n): void {\n\tconst requiredFields = config?.required ?? DEFAULT_REQUIRED_FIELDS;\n\tfor (const field of requiredFields) {\n\t\tif (pkg[field] === undefined) {\n\t\t\terrors.push(`Missing required field: package.${field}`);\n\t\t}\n\t}\n}\n\nfunction checkExpectedValues(\n\tpkg: Record<string, unknown>,\n\tconfig: CargoTomlRequiredConfig | undefined,\n\terrors: string[],\n): void {\n\tif (!config?.expectedValues) {\n\t\treturn;\n\t}\n\tfor (const [field, expected] of Object.entries(config.expectedValues)) {\n\t\tconst actual = pkg[field];\n\t\tif (actual === undefined) {\n\t\t\terrors.push(`Missing required field: package.${field}`);\n\t\t} else if (String(actual) !== expected) {\n\t\t\terrors.push(\n\t\t\t\t`Expected package.${field} = \"${expected}\", got \"${String(actual)}\"`,\n\t\t\t);\n\t\t}\n\t}\n}\n\nfunction checkRustVersion(\n\tpkg: Record<string, unknown>,\n\tconfig: CargoTomlRequiredConfig | undefined,\n\terrors: string[],\n): void {\n\tif (config?.requireRustVersion && pkg[\"rust-version\"] === undefined) {\n\t\terrors.push(\n\t\t\t\"Missing package.rust-version (MSRV). Set the minimum supported Rust version.\",\n\t\t);\n\t}\n}\n\nfunction checkMinimumEdition(\n\tpkg: Record<string, unknown>,\n\tconfig: CargoTomlRequiredConfig | undefined,\n\terrors: string[],\n): void {\n\tif (!config?.minimumEdition) {\n\t\treturn;\n\t}\n\tconst edition = pkg.edition as string | undefined;\n\tif (edition === undefined) {\n\t\terrors.push(\"Missing package.edition field.\");\n\t} else if (!VALID_EDITIONS.includes(edition)) {\n\t\terrors.push(\n\t\t\t`Invalid edition \"${edition}\". Must be one of: ${VALID_EDITIONS.join(\", \")}`,\n\t\t);\n\t} else if (edition < config.minimumEdition) {\n\t\terrors.push(\n\t\t\t`Edition \"${edition}\" is below minimum \"${config.minimumEdition}\".`,\n\t\t);\n\t}\n}\n\n/**\n * A policy that enforces required fields exist in Cargo.toml [package] section.\n *\n * @alpha\n */\nexport const CargoTomlRequired: PolicyShape<CargoTomlRequiredConfig> =\n\tdefineCargoPolicy({\n\t\tname: \"CargoTomlRequired\",\n\t\tdescription:\n\t\t\t\"Ensures Cargo.toml files contain required fields in the [package] section.\",\n\t\thandler: async (toml: CargoToml, { config }) => {\n\t\t\tconst pkg = toml.package as Record<string, unknown> | undefined;\n\t\t\tif (!pkg) {\n\t\t\t\t// Workspace root Cargo.toml may not have [package]\n\t\t\t\tif (toml.workspace !== undefined) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\treturn { error: \"Missing [package] section in Cargo.toml\" };\n\t\t\t}\n\n\t\t\tconst errors: string[] = [];\n\t\t\tcheckRequiredFields(pkg, config, errors);\n\t\t\tcheckExpectedValues(pkg, config, errors);\n\t\t\tcheckRustVersion(pkg, config, errors);\n\t\t\tcheckMinimumEdition(pkg, config, errors);\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\"Add the missing fields to your Cargo.toml [package] section.\",\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 { PolicyShape } from "../policy.js";
|
|
2
|
+
/**
|
|
3
|
+
* Configuration for the CargoTomlSorted policy.
|
|
4
|
+
*
|
|
5
|
+
* @alpha
|
|
6
|
+
*/
|
|
7
|
+
export interface CargoTomlSortedConfig {
|
|
8
|
+
/**
|
|
9
|
+
* Sections whose keys should be sorted alphabetically.
|
|
10
|
+
* @defaultValue ["dependencies", "dev-dependencies", "build-dependencies"]
|
|
11
|
+
*/
|
|
12
|
+
sections?: string[];
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* A policy that ensures dependency sections in Cargo.toml are sorted alphabetically.
|
|
16
|
+
*
|
|
17
|
+
* @alpha
|
|
18
|
+
*/
|
|
19
|
+
export declare const CargoTomlSorted: PolicyShape<CargoTomlSortedConfig>;
|
|
20
|
+
//# sourceMappingURL=CargoTomlSorted.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CargoTomlSorted.d.ts","sourceRoot":"","sources":["../../src/policies/CargoTomlSorted.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAMhD;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACrC;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAmID;;;;GAIG;AACH,eAAO,MAAM,eAAe,EAAE,WAAW,CAAC,qBAAqB,CAwD5D,CAAC"}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { writeFile } from "node:fs/promises";
|
|
2
|
+
import { resolve } from "pathe";
|
|
3
|
+
import { defineCargoPolicy, } from "../policyDefiners/defineCargoPolicy.js";
|
|
4
|
+
const DEFAULT_SORTED_SECTIONS = [
|
|
5
|
+
"dependencies",
|
|
6
|
+
"dev-dependencies",
|
|
7
|
+
"build-dependencies",
|
|
8
|
+
];
|
|
9
|
+
function isSorted(keys) {
|
|
10
|
+
for (let i = 1; i < keys.length; i++) {
|
|
11
|
+
const current = keys[i];
|
|
12
|
+
const previous = keys[i - 1];
|
|
13
|
+
if (current !== undefined && previous !== undefined && current < previous) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
const SECTION_HEADER_REGEX = /^\[([^\]]+)\]\s*$/;
|
|
20
|
+
const NEXT_SECTION_REGEX = /^\[/;
|
|
21
|
+
const KEY_VALUE_REGEX = /^(\S+)\s*=/;
|
|
22
|
+
/**
|
|
23
|
+
* Process a single line in a dependency section, updating entries and current state.
|
|
24
|
+
*/
|
|
25
|
+
function processEntryLine(entryLine, entries, currentEntry) {
|
|
26
|
+
// Comment line - attach to next entry
|
|
27
|
+
if (entryLine.trim().startsWith("#")) {
|
|
28
|
+
if (currentEntry) {
|
|
29
|
+
currentEntry.lines.push(entryLine);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
return { key: "", lines: [entryLine] };
|
|
33
|
+
}
|
|
34
|
+
return currentEntry;
|
|
35
|
+
}
|
|
36
|
+
// Key = value line
|
|
37
|
+
const keyMatch = entryLine.match(KEY_VALUE_REGEX);
|
|
38
|
+
if (keyMatch) {
|
|
39
|
+
if (currentEntry) {
|
|
40
|
+
entries.push(currentEntry);
|
|
41
|
+
}
|
|
42
|
+
const key = keyMatch[1] ?? "";
|
|
43
|
+
return { key, lines: [entryLine] };
|
|
44
|
+
}
|
|
45
|
+
if (currentEntry) {
|
|
46
|
+
// Continuation line (multiline value)
|
|
47
|
+
currentEntry.lines.push(entryLine);
|
|
48
|
+
}
|
|
49
|
+
return currentEntry;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Collect sorted entries from a TOML dependency section.
|
|
53
|
+
*/
|
|
54
|
+
function collectAndSortEntries(lines, startIndex) {
|
|
55
|
+
const entries = [];
|
|
56
|
+
let currentEntry;
|
|
57
|
+
let i = startIndex;
|
|
58
|
+
while (i < lines.length) {
|
|
59
|
+
const entryLine = lines[i];
|
|
60
|
+
if (entryLine === undefined) {
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
// Stop at next section
|
|
64
|
+
if (NEXT_SECTION_REGEX.test(entryLine)) {
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
// Skip pure blank lines between entries
|
|
68
|
+
if (entryLine.trim() === "") {
|
|
69
|
+
i++;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
currentEntry = processEntryLine(entryLine, entries, currentEntry);
|
|
73
|
+
i++;
|
|
74
|
+
}
|
|
75
|
+
if (currentEntry) {
|
|
76
|
+
entries.push(currentEntry);
|
|
77
|
+
}
|
|
78
|
+
entries.sort((a, b) => a.key.localeCompare(b.key));
|
|
79
|
+
return { entries, nextIndex: i };
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Rebuild TOML content with sorted dependency sections.
|
|
83
|
+
* Uses line-based manipulation to preserve formatting and comments.
|
|
84
|
+
*/
|
|
85
|
+
function sortTomlSections(content, sectionsToSort) {
|
|
86
|
+
const lines = content.split("\n");
|
|
87
|
+
const result = [];
|
|
88
|
+
let i = 0;
|
|
89
|
+
while (i < lines.length) {
|
|
90
|
+
const line = lines[i];
|
|
91
|
+
if (line === undefined) {
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
const sectionMatch = line.match(SECTION_HEADER_REGEX);
|
|
95
|
+
if (sectionMatch) {
|
|
96
|
+
const sectionName = sectionMatch[1] ?? "";
|
|
97
|
+
if (sectionsToSort.includes(sectionName)) {
|
|
98
|
+
result.push(line);
|
|
99
|
+
i++;
|
|
100
|
+
const { entries, nextIndex } = collectAndSortEntries(lines, i);
|
|
101
|
+
for (const entry of entries) {
|
|
102
|
+
result.push(...entry.lines);
|
|
103
|
+
}
|
|
104
|
+
i = nextIndex;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
result.push(line);
|
|
109
|
+
i++;
|
|
110
|
+
}
|
|
111
|
+
return result.join("\n");
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* A policy that ensures dependency sections in Cargo.toml are sorted alphabetically.
|
|
115
|
+
*
|
|
116
|
+
* @alpha
|
|
117
|
+
*/
|
|
118
|
+
export const CargoTomlSorted = defineCargoPolicy({
|
|
119
|
+
name: "CargoTomlSorted",
|
|
120
|
+
description: "Ensures dependency sections in Cargo.toml are sorted alphabetically.",
|
|
121
|
+
handler: async (toml, { file, root, resolve: shouldResolve, config }) => {
|
|
122
|
+
const sections = config?.sections ?? DEFAULT_SORTED_SECTIONS;
|
|
123
|
+
const unsortedSections = [];
|
|
124
|
+
for (const section of sections) {
|
|
125
|
+
const sectionData = toml[section];
|
|
126
|
+
if (sectionData !== undefined) {
|
|
127
|
+
const keys = Object.keys(sectionData);
|
|
128
|
+
if (!isSorted(keys)) {
|
|
129
|
+
unsortedSections.push(section);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (unsortedSections.length === 0) {
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
if (shouldResolve) {
|
|
137
|
+
try {
|
|
138
|
+
const { readFile } = await import("node:fs/promises");
|
|
139
|
+
const filePath = resolve(root, file);
|
|
140
|
+
const content = await readFile(filePath, "utf-8");
|
|
141
|
+
const sorted = sortTomlSections(content, unsortedSections);
|
|
142
|
+
await writeFile(filePath, sorted);
|
|
143
|
+
return {
|
|
144
|
+
error: `Unsorted sections: [${unsortedSections.join("], [")}]`,
|
|
145
|
+
fixable: true,
|
|
146
|
+
fixed: true,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return {
|
|
151
|
+
error: `Unsorted sections: [${unsortedSections.join("], [")}]. Auto-fix failed.`,
|
|
152
|
+
fixable: true,
|
|
153
|
+
fixed: false,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
error: `Unsorted sections: [${unsortedSections.join("], [")}]`,
|
|
159
|
+
fixable: true,
|
|
160
|
+
manualFix: "Sort the dependency keys alphabetically in the listed sections.",
|
|
161
|
+
};
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
//# sourceMappingURL=CargoTomlSorted.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CargoTomlSorted.js","sourceRoot":"","sources":["../../src/policies/CargoTomlSorted.ts"],"names":[],"mappings":"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","sourcesContent":["import { writeFile } from \"node:fs/promises\";\nimport { resolve } from \"pathe\";\nimport type { PolicyShape } from \"../policy.js\";\nimport {\n\ttype CargoToml,\n\tdefineCargoPolicy,\n} from \"../policyDefiners/defineCargoPolicy.js\";\n\n/**\n * Configuration for the CargoTomlSorted policy.\n *\n * @alpha\n */\nexport interface CargoTomlSortedConfig {\n\t/**\n\t * Sections whose keys should be sorted alphabetically.\n\t * @defaultValue [\"dependencies\", \"dev-dependencies\", \"build-dependencies\"]\n\t */\n\tsections?: string[];\n}\n\nconst DEFAULT_SORTED_SECTIONS = [\n\t\"dependencies\",\n\t\"dev-dependencies\",\n\t\"build-dependencies\",\n];\n\nfunction isSorted(keys: string[]): boolean {\n\tfor (let i = 1; i < keys.length; i++) {\n\t\tconst current = keys[i];\n\t\tconst previous = keys[i - 1];\n\t\tif (current !== undefined && previous !== undefined && current < previous) {\n\t\t\treturn false;\n\t\t}\n\t}\n\treturn true;\n}\n\nconst SECTION_HEADER_REGEX = /^\\[([^\\]]+)\\]\\s*$/;\nconst NEXT_SECTION_REGEX = /^\\[/;\nconst KEY_VALUE_REGEX = /^(\\S+)\\s*=/;\n\n/**\n * Process a single line in a dependency section, updating entries and current state.\n */\nfunction processEntryLine(\n\tentryLine: string,\n\tentries: { key: string; lines: string[] }[],\n\tcurrentEntry: { key: string; lines: string[] } | undefined,\n): { key: string; lines: string[] } | undefined {\n\t// Comment line - attach to next entry\n\tif (entryLine.trim().startsWith(\"#\")) {\n\t\tif (currentEntry) {\n\t\t\tcurrentEntry.lines.push(entryLine);\n\t\t} else {\n\t\t\treturn { key: \"\", lines: [entryLine] };\n\t\t}\n\t\treturn currentEntry;\n\t}\n\t// Key = value line\n\tconst keyMatch = entryLine.match(KEY_VALUE_REGEX);\n\tif (keyMatch) {\n\t\tif (currentEntry) {\n\t\t\tentries.push(currentEntry);\n\t\t}\n\t\tconst key = keyMatch[1] ?? \"\";\n\t\treturn { key, lines: [entryLine] };\n\t}\n\tif (currentEntry) {\n\t\t// Continuation line (multiline value)\n\t\tcurrentEntry.lines.push(entryLine);\n\t}\n\treturn currentEntry;\n}\n\n/**\n * Collect sorted entries from a TOML dependency section.\n */\nfunction collectAndSortEntries(\n\tlines: string[],\n\tstartIndex: number,\n): { entries: { key: string; lines: string[] }[]; nextIndex: number } {\n\tconst entries: { key: string; lines: string[] }[] = [];\n\tlet currentEntry: { key: string; lines: string[] } | undefined;\n\tlet i = startIndex;\n\n\twhile (i < lines.length) {\n\t\tconst entryLine = lines[i];\n\t\tif (entryLine === undefined) {\n\t\t\tbreak;\n\t\t}\n\t\t// Stop at next section\n\t\tif (NEXT_SECTION_REGEX.test(entryLine)) {\n\t\t\tbreak;\n\t\t}\n\t\t// Skip pure blank lines between entries\n\t\tif (entryLine.trim() === \"\") {\n\t\t\ti++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tcurrentEntry = processEntryLine(entryLine, entries, currentEntry);\n\t\ti++;\n\t}\n\tif (currentEntry) {\n\t\tentries.push(currentEntry);\n\t}\n\n\tentries.sort((a, b) => a.key.localeCompare(b.key));\n\treturn { entries, nextIndex: i };\n}\n\n/**\n * Rebuild TOML content with sorted dependency sections.\n * Uses line-based manipulation to preserve formatting and comments.\n */\nfunction sortTomlSections(content: string, sectionsToSort: string[]): string {\n\tconst lines = content.split(\"\\n\");\n\tconst result: string[] = [];\n\tlet i = 0;\n\n\twhile (i < lines.length) {\n\t\tconst line = lines[i];\n\t\tif (line === undefined) {\n\t\t\tbreak;\n\t\t}\n\t\tconst sectionMatch = line.match(SECTION_HEADER_REGEX);\n\n\t\tif (sectionMatch) {\n\t\t\tconst sectionName = sectionMatch[1] ?? \"\";\n\t\t\tif (sectionsToSort.includes(sectionName)) {\n\t\t\t\tresult.push(line);\n\t\t\t\ti++;\n\n\t\t\t\tconst { entries, nextIndex } = collectAndSortEntries(lines, i);\n\t\t\t\tfor (const entry of entries) {\n\t\t\t\t\tresult.push(...entry.lines);\n\t\t\t\t}\n\t\t\t\ti = nextIndex;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\tresult.push(line);\n\t\ti++;\n\t}\n\n\treturn result.join(\"\\n\");\n}\n\n/**\n * A policy that ensures dependency sections in Cargo.toml are sorted alphabetically.\n *\n * @alpha\n */\nexport const CargoTomlSorted: PolicyShape<CargoTomlSortedConfig> =\n\tdefineCargoPolicy({\n\t\tname: \"CargoTomlSorted\",\n\t\tdescription:\n\t\t\t\"Ensures dependency sections in Cargo.toml are sorted alphabetically.\",\n\t\thandler: async (\n\t\t\ttoml: CargoToml,\n\t\t\t{ file, root, resolve: shouldResolve, config },\n\t\t) => {\n\t\t\tconst sections = config?.sections ?? DEFAULT_SORTED_SECTIONS;\n\t\t\tconst unsortedSections: string[] = [];\n\n\t\t\tfor (const section of sections) {\n\t\t\t\tconst sectionData = toml[section] as\n\t\t\t\t\t| Record<string, unknown>\n\t\t\t\t\t| undefined;\n\t\t\t\tif (sectionData !== undefined) {\n\t\t\t\t\tconst keys = Object.keys(sectionData);\n\t\t\t\t\tif (!isSorted(keys)) {\n\t\t\t\t\t\tunsortedSections.push(section);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (unsortedSections.length === 0) {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tif (shouldResolve) {\n\t\t\t\ttry {\n\t\t\t\t\tconst { readFile } = await import(\"node:fs/promises\");\n\t\t\t\t\tconst filePath = resolve(root, file);\n\t\t\t\t\tconst content = await readFile(filePath, \"utf-8\");\n\t\t\t\t\tconst sorted = sortTomlSections(content, unsortedSections);\n\t\t\t\t\tawait writeFile(filePath, sorted);\n\t\t\t\t\treturn {\n\t\t\t\t\t\terror: `Unsorted sections: [${unsortedSections.join(\"], [\")}]`,\n\t\t\t\t\t\tfixable: true,\n\t\t\t\t\t\tfixed: true,\n\t\t\t\t\t};\n\t\t\t\t} catch {\n\t\t\t\t\treturn {\n\t\t\t\t\t\terror: `Unsorted sections: [${unsortedSections.join(\"], [\")}]. Auto-fix failed.`,\n\t\t\t\t\t\tfixable: true,\n\t\t\t\t\t\tfixed: false,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\terror: `Unsorted sections: [${unsortedSections.join(\"], [\")}]`,\n\t\t\t\tfixable: true,\n\t\t\t\tmanualFix:\n\t\t\t\t\t\"Sort the dependency keys alphabetically in the listed sections.\",\n\t\t\t};\n\t\t},\n\t});\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConditionalScripts.d.ts","sourceRoot":"","sources":["../../src/policies/ConditionalScripts.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ConditionalScripts.d.ts","sourceRoot":"","sources":["../../src/policies/ConditionalScripts.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAErE;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACrC;;;OAGG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;;;;;;;;;;;;OAaG;IACH,QAAQ,EAAE,sBAAsB,EAAE,CAAC;CACnC;AAED;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACxC;;;;;;;;;;;;;;OAcG;IACH,KAAK,EAAE,qBAAqB,EAAE,CAAC;CAC/B;AAiHD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,kBAAkB,0EAuE7B,CAAC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import jsonfile from "jsonfile";
|
|
2
|
+
import { resolve as resolvePath } from "pathe";
|
|
2
3
|
import { definePackagePolicy } from "../policyDefiners/definePackagePolicy.js";
|
|
3
4
|
const POLICY_NAME = "ConditionalScripts";
|
|
4
5
|
/**
|
|
@@ -94,7 +95,7 @@ async function fixMissingScripts(json, file, missingWithDefaults, existingScript
|
|
|
94
95
|
export const ConditionalScripts = definePackagePolicy({
|
|
95
96
|
name: POLICY_NAME,
|
|
96
97
|
description: "Validates that if certain scripts exist, other required scripts must also exist.",
|
|
97
|
-
handler: async (json, { file, resolve, config }) => {
|
|
98
|
+
handler: async (json, { file, root, resolve, config }) => {
|
|
98
99
|
if (config === undefined || config.rules.length === 0) {
|
|
99
100
|
return true;
|
|
100
101
|
}
|
|
@@ -107,7 +108,7 @@ export const ConditionalScripts = definePackagePolicy({
|
|
|
107
108
|
const hasAutoFixableErrors = validation.missingWithDefaults.length > 0;
|
|
108
109
|
if (resolve && hasAutoFixableErrors) {
|
|
109
110
|
try {
|
|
110
|
-
const fixedScripts = await fixMissingScripts(json, file, validation.missingWithDefaults, scripts);
|
|
111
|
+
const fixedScripts = await fixMissingScripts(json, resolvePath(root, file), validation.missingWithDefaults, scripts);
|
|
111
112
|
// Re-validate after fix to check for remaining errors
|
|
112
113
|
const updatedScripts = { ...scripts };
|
|
113
114
|
for (const name of fixedScripts) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConditionalScripts.js","sourceRoot":"","sources":["../../src/policies/ConditionalScripts.ts"],"names":[],"mappings":"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","sourcesContent":["import jsonfile from \"jsonfile\";\nimport type { PackageJson } from \"type-fest\";\nimport { definePackagePolicy } from \"../policyDefiners/definePackagePolicy.js\";\n\n/**\n * A required script entry for conditional requirements. Can be either:\n * - A string (script name only, no default value)\n * - An object with a single key-value pair (script name → default value for auto-fix)\n *\n * @example\n * ```typescript\n * // Script must exist, no auto-fix available\n * \"test\"\n *\n * // Script must exist, auto-fix will add this value if missing\n * { clean: \"rimraf dist\" }\n * ```\n *\n * @alpha\n */\nexport type ConditionalScriptEntry = string | Record<string, string>;\n\n/**\n * Configuration for a conditional script requirement.\n *\n * @alpha\n */\nexport interface ConditionalScriptRule {\n\t/**\n\t * The script that triggers the requirement.\n\t * If this script exists, then `requires` must also exist.\n\t */\n\tifPresent: string;\n\n\t/**\n\t * Scripts that must be present if `ifPresent` script exists.\n\t *\n\t * @remarks\n\t * Each entry can be:\n\t * - A string: Script must exist, no auto-fix available\n\t * - An object `{ scriptName: \"default value\" }`: Script must exist, auto-fix adds the\n\t * default value if missing\n\t *\n\t * @example\n\t * ```typescript\n\t * requires: [\"test\", { clean: \"rimraf dist\" }]\n\t * ```\n\t */\n\trequires: ConditionalScriptEntry[];\n}\n\n/**\n * Configuration for the ConditionalScripts policy.\n *\n * @alpha\n */\nexport interface ConditionalScriptsConfig {\n\t/**\n\t * Conditional script requirements. If a script is present, other scripts must also exist.\n\t *\n\t * @remarks\n\t * This is useful for enforcing that packages with a \"build\" script also have a \"clean\" script,\n\t * or packages with \"test\" also have \"test:coverage\".\n\t *\n\t * @example\n\t * ```typescript\n\t * rules: [\n\t * { ifPresent: \"build\", requires: [{ clean: \"rimraf dist\" }] },\n\t * { ifPresent: \"test\", requires: [\"test:coverage\"] },\n\t * ]\n\t * ```\n\t */\n\trules: ConditionalScriptRule[];\n}\n\nconst POLICY_NAME = \"ConditionalScripts\";\n\ninterface ParsedEntry {\n\tname: string;\n\tdefaultValue?: string;\n}\n\n/**\n * Extracts script name and optional default value from a ConditionalScriptEntry.\n */\nfunction parseEntry(entry: ConditionalScriptEntry): ParsedEntry {\n\tif (typeof entry === \"string\") {\n\t\treturn { name: entry };\n\t}\n\tconst keys = Object.keys(entry);\n\tif (keys.length !== 1) {\n\t\tthrow new Error(\n\t\t\t`Invalid conditional script entry: expected single key-value pair, got ${keys.length} keys`,\n\t\t);\n\t}\n\t// biome-ignore lint/style/noNonNullAssertion: we verified length is 1\n\tconst name = keys[0]!;\n\tconst defaultValue = entry[name];\n\treturn defaultValue !== undefined ? { name, defaultValue } : { name };\n}\n\ninterface RuleViolation {\n\ttriggerScript: string;\n\tmissingScripts: string[];\n}\n\ninterface ValidationResult {\n\tviolations: RuleViolation[];\n\tmissingWithDefaults: Array<{ name: string; defaultValue: string }>;\n}\n\nfunction validateConditionalRules(\n\tscripts: Record<string, string | undefined>,\n\trules: ConditionalScriptRule[],\n): ValidationResult {\n\tconst violations: RuleViolation[] = [];\n\tconst missingWithDefaults: Array<{ name: string; defaultValue: string }> = [];\n\tconst seenMissing = new Set<string>();\n\n\tfor (const rule of rules) {\n\t\t// Skip if the triggering script doesn't exist\n\t\tif (!Object.hasOwn(scripts, rule.ifPresent)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst missingScripts: string[] = [];\n\t\tfor (const entry of rule.requires) {\n\t\t\tconst { name, defaultValue } = parseEntry(entry);\n\t\t\tif (!Object.hasOwn(scripts, name)) {\n\t\t\t\tmissingScripts.push(name);\n\t\t\t\tif (defaultValue !== undefined && !seenMissing.has(name)) {\n\t\t\t\t\tmissingWithDefaults.push({ name, defaultValue });\n\t\t\t\t\tseenMissing.add(name);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (missingScripts.length > 0) {\n\t\t\tviolations.push({\n\t\t\t\ttriggerScript: rule.ifPresent,\n\t\t\t\tmissingScripts,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn { violations, missingWithDefaults };\n}\n\nfunction formatViolationErrors(violations: RuleViolation[]): string[] {\n\treturn violations.map(\n\t\t(v) =>\n\t\t\t`Script \"${v.triggerScript}\" is present, but required companion script(s) missing: ${v.missingScripts.join(\", \")}`,\n\t);\n}\n\nasync function fixMissingScripts(\n\tjson: PackageJson,\n\tfile: string,\n\tmissingWithDefaults: Array<{ name: string; defaultValue: string }>,\n\texistingScripts: Record<string, string | undefined>,\n): Promise<string[]> {\n\tconst updatedScripts: Record<string, string> = {};\n\n\t// Copy existing scripts\n\tfor (const [key, value] of Object.entries(existingScripts)) {\n\t\tif (value !== undefined) {\n\t\t\tupdatedScripts[key] = value;\n\t\t}\n\t}\n\n\t// Add missing scripts with defaults\n\tconst fixedScripts: string[] = [];\n\tfor (const { name, defaultValue } of missingWithDefaults) {\n\t\tupdatedScripts[name] = defaultValue;\n\t\tfixedScripts.push(name);\n\t}\n\n\tawait jsonfile.writeFile(\n\t\tfile,\n\t\t{ ...json, scripts: updatedScripts },\n\t\t{ spaces: 2 },\n\t);\n\n\treturn fixedScripts;\n}\n\n/**\n * A policy that validates conditional script requirements in package.json.\n *\n * @remarks\n * Use this policy when you want to enforce that the presence of one script\n * requires other scripts to also exist. For example, if \"build\" exists,\n * then \"clean\" must also exist.\n *\n * @example\n * ```typescript\n * import { policy } from \"repopo\";\n * import { ConditionalScripts } from \"repopo/policies\";\n *\n * policy(ConditionalScripts, {\n * rules: [\n * // If \"build\" exists, \"clean\" must also exist (with auto-fix default)\n * { ifPresent: \"build\", requires: [{ clean: \"rimraf dist\" }] },\n * // If \"test\" exists, \"test:coverage\" must also exist (no auto-fix)\n * { ifPresent: \"test\", requires: [\"test:coverage\"] },\n * ],\n * })\n * ```\n *\n * @alpha\n */\nexport const ConditionalScripts = definePackagePolicy<\n\tPackageJson,\n\tConditionalScriptsConfig | undefined\n>({\n\tname: POLICY_NAME,\n\tdescription:\n\t\t\"Validates that if certain scripts exist, other required scripts must also exist.\",\n\thandler: async (json, { file, resolve, 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 validation = validateConditionalRules(scripts, config.rules);\n\n\t\tif (validation.violations.length === 0) {\n\t\t\treturn true;\n\t\t}\n\n\t\tconst errors = formatViolationErrors(validation.violations);\n\t\tconst hasAutoFixableErrors = validation.missingWithDefaults.length > 0;\n\n\t\tif (resolve && hasAutoFixableErrors) {\n\t\t\ttry {\n\t\t\t\tconst fixedScripts = await fixMissingScripts(\n\t\t\t\t\tjson,\n\t\t\t\t\tfile,\n\t\t\t\t\tvalidation.missingWithDefaults,\n\t\t\t\t\tscripts,\n\t\t\t\t);\n\n\t\t\t\t// Re-validate after fix to check for remaining errors\n\t\t\t\tconst updatedScripts = { ...scripts };\n\t\t\t\tfor (const name of fixedScripts) {\n\t\t\t\t\tconst entry = validation.missingWithDefaults.find(\n\t\t\t\t\t\t(m) => m.name === name,\n\t\t\t\t\t);\n\t\t\t\t\tif (entry) {\n\t\t\t\t\t\tupdatedScripts[name] = entry.defaultValue;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst revalidation = validateConditionalRules(\n\t\t\t\t\tupdatedScripts,\n\t\t\t\t\tconfig.rules,\n\t\t\t\t);\n\t\t\t\tconst remainingErrors = formatViolationErrors(revalidation.violations);\n\n\t\t\t\treturn {\n\t\t\t\t\tname: POLICY_NAME,\n\t\t\t\t\tfile,\n\t\t\t\t\tresolved: remainingErrors.length === 0,\n\t\t\t\t\terrorMessages:\n\t\t\t\t\t\tremainingErrors.length > 0\n\t\t\t\t\t\t\t? [\n\t\t\t\t\t\t\t\t\t`Fixed scripts: ${fixedScripts.join(\", \")}. Remaining errors:\\n${remainingErrors.join(\"\\n\")}`,\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t: [`Fixed scripts: ${fixedScripts.join(\", \")}`],\n\t\t\t\t};\n\t\t\t} catch {\n\t\t\t\t// Fall through to return regular failure\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tname: POLICY_NAME,\n\t\t\tfile,\n\t\t\tautoFixable: hasAutoFixableErrors,\n\t\t\terrorMessages: errors,\n\t\t};\n\t},\n});\n"]}
|
|
1
|
+
{"version":3,"file":"ConditionalScripts.js","sourceRoot":"","sources":["../../src/policies/ConditionalScripts.ts"],"names":[],"mappings":"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","sourcesContent":["import jsonfile from \"jsonfile\";\nimport { resolve as resolvePath } from \"pathe\";\nimport type { PackageJson } from \"type-fest\";\nimport { definePackagePolicy } from \"../policyDefiners/definePackagePolicy.js\";\n\n/**\n * A required script entry for conditional requirements. Can be either:\n * - A string (script name only, no default value)\n * - An object with a single key-value pair (script name → default value for auto-fix)\n *\n * @example\n * ```typescript\n * // Script must exist, no auto-fix available\n * \"test\"\n *\n * // Script must exist, auto-fix will add this value if missing\n * { clean: \"rimraf dist\" }\n * ```\n *\n * @alpha\n */\nexport type ConditionalScriptEntry = string | Record<string, string>;\n\n/**\n * Configuration for a conditional script requirement.\n *\n * @alpha\n */\nexport interface ConditionalScriptRule {\n\t/**\n\t * The script that triggers the requirement.\n\t * If this script exists, then `requires` must also exist.\n\t */\n\tifPresent: string;\n\n\t/**\n\t * Scripts that must be present if `ifPresent` script exists.\n\t *\n\t * @remarks\n\t * Each entry can be:\n\t * - A string: Script must exist, no auto-fix available\n\t * - An object `{ scriptName: \"default value\" }`: Script must exist, auto-fix adds the\n\t * default value if missing\n\t *\n\t * @example\n\t * ```typescript\n\t * requires: [\"test\", { clean: \"rimraf dist\" }]\n\t * ```\n\t */\n\trequires: ConditionalScriptEntry[];\n}\n\n/**\n * Configuration for the ConditionalScripts policy.\n *\n * @alpha\n */\nexport interface ConditionalScriptsConfig {\n\t/**\n\t * Conditional script requirements. If a script is present, other scripts must also exist.\n\t *\n\t * @remarks\n\t * This is useful for enforcing that packages with a \"build\" script also have a \"clean\" script,\n\t * or packages with \"test\" also have \"test:coverage\".\n\t *\n\t * @example\n\t * ```typescript\n\t * rules: [\n\t * { ifPresent: \"build\", requires: [{ clean: \"rimraf dist\" }] },\n\t * { ifPresent: \"test\", requires: [\"test:coverage\"] },\n\t * ]\n\t * ```\n\t */\n\trules: ConditionalScriptRule[];\n}\n\nconst POLICY_NAME = \"ConditionalScripts\";\n\ninterface ParsedEntry {\n\tname: string;\n\tdefaultValue?: string;\n}\n\n/**\n * Extracts script name and optional default value from a ConditionalScriptEntry.\n */\nfunction parseEntry(entry: ConditionalScriptEntry): ParsedEntry {\n\tif (typeof entry === \"string\") {\n\t\treturn { name: entry };\n\t}\n\tconst keys = Object.keys(entry);\n\tif (keys.length !== 1) {\n\t\tthrow new Error(\n\t\t\t`Invalid conditional script entry: expected single key-value pair, got ${keys.length} keys`,\n\t\t);\n\t}\n\t// biome-ignore lint/style/noNonNullAssertion: we verified length is 1\n\tconst name = keys[0]!;\n\tconst defaultValue = entry[name];\n\treturn defaultValue !== undefined ? { name, defaultValue } : { name };\n}\n\ninterface RuleViolation {\n\ttriggerScript: string;\n\tmissingScripts: string[];\n}\n\ninterface ValidationResult {\n\tviolations: RuleViolation[];\n\tmissingWithDefaults: Array<{ name: string; defaultValue: string }>;\n}\n\nfunction validateConditionalRules(\n\tscripts: Record<string, string | undefined>,\n\trules: ConditionalScriptRule[],\n): ValidationResult {\n\tconst violations: RuleViolation[] = [];\n\tconst missingWithDefaults: Array<{ name: string; defaultValue: string }> = [];\n\tconst seenMissing = new Set<string>();\n\n\tfor (const rule of rules) {\n\t\t// Skip if the triggering script doesn't exist\n\t\tif (!Object.hasOwn(scripts, rule.ifPresent)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst missingScripts: string[] = [];\n\t\tfor (const entry of rule.requires) {\n\t\t\tconst { name, defaultValue } = parseEntry(entry);\n\t\t\tif (!Object.hasOwn(scripts, name)) {\n\t\t\t\tmissingScripts.push(name);\n\t\t\t\tif (defaultValue !== undefined && !seenMissing.has(name)) {\n\t\t\t\t\tmissingWithDefaults.push({ name, defaultValue });\n\t\t\t\t\tseenMissing.add(name);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (missingScripts.length > 0) {\n\t\t\tviolations.push({\n\t\t\t\ttriggerScript: rule.ifPresent,\n\t\t\t\tmissingScripts,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn { violations, missingWithDefaults };\n}\n\nfunction formatViolationErrors(violations: RuleViolation[]): string[] {\n\treturn violations.map(\n\t\t(v) =>\n\t\t\t`Script \"${v.triggerScript}\" is present, but required companion script(s) missing: ${v.missingScripts.join(\", \")}`,\n\t);\n}\n\nasync function fixMissingScripts(\n\tjson: PackageJson,\n\tfile: string,\n\tmissingWithDefaults: Array<{ name: string; defaultValue: string }>,\n\texistingScripts: Record<string, string | undefined>,\n): Promise<string[]> {\n\tconst updatedScripts: Record<string, string> = {};\n\n\t// Copy existing scripts\n\tfor (const [key, value] of Object.entries(existingScripts)) {\n\t\tif (value !== undefined) {\n\t\t\tupdatedScripts[key] = value;\n\t\t}\n\t}\n\n\t// Add missing scripts with defaults\n\tconst fixedScripts: string[] = [];\n\tfor (const { name, defaultValue } of missingWithDefaults) {\n\t\tupdatedScripts[name] = defaultValue;\n\t\tfixedScripts.push(name);\n\t}\n\n\tawait jsonfile.writeFile(\n\t\tfile,\n\t\t{ ...json, scripts: updatedScripts },\n\t\t{ spaces: 2 },\n\t);\n\n\treturn fixedScripts;\n}\n\n/**\n * A policy that validates conditional script requirements in package.json.\n *\n * @remarks\n * Use this policy when you want to enforce that the presence of one script\n * requires other scripts to also exist. For example, if \"build\" exists,\n * then \"clean\" must also exist.\n *\n * @example\n * ```typescript\n * import { policy } from \"repopo\";\n * import { ConditionalScripts } from \"repopo/policies\";\n *\n * policy(ConditionalScripts, {\n * rules: [\n * // If \"build\" exists, \"clean\" must also exist (with auto-fix default)\n * { ifPresent: \"build\", requires: [{ clean: \"rimraf dist\" }] },\n * // If \"test\" exists, \"test:coverage\" must also exist (no auto-fix)\n * { ifPresent: \"test\", requires: [\"test:coverage\"] },\n * ],\n * })\n * ```\n *\n * @alpha\n */\nexport const ConditionalScripts = definePackagePolicy<\n\tPackageJson,\n\tConditionalScriptsConfig | undefined\n>({\n\tname: POLICY_NAME,\n\tdescription:\n\t\t\"Validates that if certain scripts exist, other required scripts must also exist.\",\n\thandler: async (json, { file, root, resolve, 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 validation = validateConditionalRules(scripts, config.rules);\n\n\t\tif (validation.violations.length === 0) {\n\t\t\treturn true;\n\t\t}\n\n\t\tconst errors = formatViolationErrors(validation.violations);\n\t\tconst hasAutoFixableErrors = validation.missingWithDefaults.length > 0;\n\n\t\tif (resolve && hasAutoFixableErrors) {\n\t\t\ttry {\n\t\t\t\tconst fixedScripts = await fixMissingScripts(\n\t\t\t\t\tjson,\n\t\t\t\t\tresolvePath(root, file),\n\t\t\t\t\tvalidation.missingWithDefaults,\n\t\t\t\t\tscripts,\n\t\t\t\t);\n\n\t\t\t\t// Re-validate after fix to check for remaining errors\n\t\t\t\tconst updatedScripts = { ...scripts };\n\t\t\t\tfor (const name of fixedScripts) {\n\t\t\t\t\tconst entry = validation.missingWithDefaults.find(\n\t\t\t\t\t\t(m) => m.name === name,\n\t\t\t\t\t);\n\t\t\t\t\tif (entry) {\n\t\t\t\t\t\tupdatedScripts[name] = entry.defaultValue;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst revalidation = validateConditionalRules(\n\t\t\t\t\tupdatedScripts,\n\t\t\t\t\tconfig.rules,\n\t\t\t\t);\n\t\t\t\tconst remainingErrors = formatViolationErrors(revalidation.violations);\n\n\t\t\t\treturn {\n\t\t\t\t\tname: POLICY_NAME,\n\t\t\t\t\tfile,\n\t\t\t\t\tresolved: remainingErrors.length === 0,\n\t\t\t\t\terrorMessages:\n\t\t\t\t\t\tremainingErrors.length > 0\n\t\t\t\t\t\t\t? [\n\t\t\t\t\t\t\t\t\t`Fixed scripts: ${fixedScripts.join(\", \")}. Remaining errors:\\n${remainingErrors.join(\"\\n\")}`,\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t: [`Fixed scripts: ${fixedScripts.join(\", \")}`],\n\t\t\t\t};\n\t\t\t} catch {\n\t\t\t\t// Fall through to return regular failure\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tname: POLICY_NAME,\n\t\t\tfile,\n\t\t\tautoFixable: hasAutoFixableErrors,\n\t\t\terrorMessages: errors,\n\t\t};\n\t},\n});\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExactScripts.d.ts","sourceRoot":"","sources":["../../src/policies/ExactScripts.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ExactScripts.d.ts","sourceRoot":"","sources":["../../src/policies/ExactScripts.ts"],"names":[],"mappings":"AAKA;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IAClC;;;;;;;;;;;;;;;OAeG;IACH,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAuED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,YAAY,oEAqDvB,CAAC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import jsonfile from "jsonfile";
|
|
2
|
+
import { resolve as resolvePath } from "pathe";
|
|
2
3
|
import { definePackagePolicy } from "../policyDefiners/definePackagePolicy.js";
|
|
3
4
|
const POLICY_NAME = "ExactScripts";
|
|
4
5
|
function validateScripts(scripts, expectedScripts) {
|
|
@@ -68,7 +69,7 @@ async function fixScripts(json, file, scriptsToFix, expectedScripts, existingScr
|
|
|
68
69
|
export const ExactScripts = definePackagePolicy({
|
|
69
70
|
name: POLICY_NAME,
|
|
70
71
|
description: "Validates that scripts exist and have exact content in package.json.",
|
|
71
|
-
handler: async (json, { file, resolve, config }) => {
|
|
72
|
+
handler: async (json, { file, root, resolve, config }) => {
|
|
72
73
|
if (config === undefined || Object.keys(config.scripts).length === 0) {
|
|
73
74
|
return true;
|
|
74
75
|
}
|
|
@@ -84,7 +85,7 @@ export const ExactScripts = definePackagePolicy({
|
|
|
84
85
|
const hasAutoFixableErrors = scriptsToFix.length > 0;
|
|
85
86
|
if (resolve && hasAutoFixableErrors) {
|
|
86
87
|
try {
|
|
87
|
-
const fixedScripts = await fixScripts(json, file, scriptsToFix, config.scripts, scripts);
|
|
88
|
+
const fixedScripts = await fixScripts(json, resolvePath(root, file), scriptsToFix, config.scripts, scripts);
|
|
88
89
|
return {
|
|
89
90
|
name: POLICY_NAME,
|
|
90
91
|
file,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExactScripts.js","sourceRoot":"","sources":["../../src/policies/ExactScripts.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"ExactScripts.js","sourceRoot":"","sources":["../../src/policies/ExactScripts.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,UAAU,CAAC;AAChC,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,OAAO,CAAC;AAE/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,0CAA0C,CAAC;AA2B/E,MAAM,WAAW,GAAG,cAAc,CAAC;AAQnC,SAAS,eAAe,CACvB,OAA2C,EAC3C,eAAuC;IAEvC,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,cAAc,GAAa,EAAE,CAAC;IACpC,MAAM,iBAAiB,GAAa,EAAE,CAAC;IAEvC,KAAK,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,EAAE,CAAC;QAC3E,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,CAAC;YACxC,MAAM,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;YACxC,IAAI,WAAW,KAAK,aAAa,EAAE,CAAC;gBACnC,iBAAiB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;gBACnC,MAAM,CAAC,IAAI,CACV,WAAW,UAAU,cAAc,aAAa,iBAAiB,WAAW,GAAG,CAC/E,CAAC;YACH,CAAC;QACF,CAAC;aAAM,CAAC;YACP,cAAc,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAChC,MAAM,CAAC,IAAI,CAAC,4BAA4B,UAAU,EAAE,CAAC,CAAC;QACvD,CAAC;IACF,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,iBAAiB,EAAE,CAAC;AACtD,CAAC;AAED,KAAK,UAAU,UAAU,CACxB,IAAiB,EACjB,IAAY,EACZ,YAAsB,EACtB,eAAuC,EACvC,eAAmD;IAEnD,MAAM,cAAc,GAA2B,EAAE,CAAC;IAElD,wBAAwB;IACxB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,EAAE,CAAC;QAC5D,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACzB,cAAc,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QAC7B,CAAC;IACF,CAAC;IAED,qBAAqB;IACrB,MAAM,YAAY,GAAa,EAAE,CAAC;IAClC,KAAK,MAAM,UAAU,IAAI,YAAY,EAAE,CAAC;QACvC,MAAM,aAAa,GAAG,eAAe,CAAC,UAAU,CAAC,CAAC;QAClD,IAAI,aAAa,KAAK,SAAS,EAAE,CAAC;YACjC,cAAc,CAAC,UAAU,CAAC,GAAG,aAAa,CAAC;YAC3C,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC/B,CAAC;IACF,CAAC;IAED,MAAM,QAAQ,CAAC,SAAS,CACvB,IAAI,EACJ,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,cAAc,EAAE,EACpC,EAAE,MAAM,EAAE,CAAC,EAAE,CACb,CAAC;IAEF,OAAO,YAAY,CAAC;AACrB,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,mBAAmB,CAG7C;IACD,IAAI,EAAE,WAAW;IACjB,WAAW,EACV,sEAAsE;IACvE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE;QACxD,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACtE,OAAO,IAAI,CAAC;QACb,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC;QACnC,MAAM,UAAU,GAAG,eAAe,CAAC,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;QAE5D,IAAI,UAAU,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACpC,OAAO,IAAI,CAAC;QACb,CAAC;QAED,MAAM,YAAY,GAAG;YACpB,GAAG,UAAU,CAAC,cAAc;YAC5B,GAAG,UAAU,CAAC,iBAAiB;SAC/B,CAAC;QACF,MAAM,oBAAoB,GAAG,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC;QAErD,IAAI,OAAO,IAAI,oBAAoB,EAAE,CAAC;YACrC,IAAI,CAAC;gBACJ,MAAM,YAAY,GAAG,MAAM,UAAU,CACpC,IAAI,EACJ,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,EACvB,YAAY,EACZ,MAAM,CAAC,OAAO,EACd,OAAO,CACP,CAAC;gBAEF,OAAO;oBACN,IAAI,EAAE,WAAW;oBACjB,IAAI;oBACJ,QAAQ,EAAE,IAAI;oBACd,aAAa,EAAE,CAAC,kBAAkB,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;iBAC5D,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACR,yCAAyC;YAC1C,CAAC;QACF,CAAC;QAED,OAAO;YACN,IAAI,EAAE,WAAW;YACjB,IAAI;YACJ,WAAW,EAAE,oBAAoB;YACjC,aAAa,EAAE,UAAU,CAAC,MAAM;SAChC,CAAC;IACH,CAAC;CACD,CAAC,CAAC","sourcesContent":["import jsonfile from \"jsonfile\";\nimport { resolve as resolvePath } from \"pathe\";\nimport type { PackageJson } from \"type-fest\";\nimport { definePackagePolicy } from \"../policyDefiners/definePackagePolicy.js\";\n\n/**\n * Configuration for the ExactScripts policy.\n *\n * @alpha\n */\nexport interface ExactScriptsConfig {\n\t/**\n\t * Scripts that must exist AND have exact content.\n\t *\n\t * @remarks\n\t * Unlike RequiredScripts, scripts in `exact` are validated against\n\t * the specified value. If a script exists but has different content, it fails validation.\n\t * Both missing and mismatched scripts are auto-fixable.\n\t *\n\t * @example\n\t * ```typescript\n\t * scripts: {\n\t * clean: \"rimraf dist esm\",\n\t * format: \"biome format --write .\",\n\t * }\n\t * ```\n\t */\n\tscripts: Record<string, string>;\n}\n\nconst POLICY_NAME = \"ExactScripts\";\n\ninterface ValidationResult {\n\terrors: string[];\n\tmissingScripts: string[];\n\tmismatchedScripts: string[];\n}\n\nfunction validateScripts(\n\tscripts: Record<string, string | undefined>,\n\texpectedScripts: Record<string, string>,\n): ValidationResult {\n\tconst errors: string[] = [];\n\tconst missingScripts: string[] = [];\n\tconst mismatchedScripts: string[] = [];\n\n\tfor (const [scriptName, expectedValue] of Object.entries(expectedScripts)) {\n\t\tif (Object.hasOwn(scripts, scriptName)) {\n\t\t\tconst actualValue = scripts[scriptName];\n\t\t\tif (actualValue !== expectedValue) {\n\t\t\t\tmismatchedScripts.push(scriptName);\n\t\t\t\terrors.push(\n\t\t\t\t\t`Script \"${scriptName}\" must be \"${expectedValue}\", but found \"${actualValue}\"`,\n\t\t\t\t);\n\t\t\t}\n\t\t} else {\n\t\t\tmissingScripts.push(scriptName);\n\t\t\terrors.push(`Missing required script: ${scriptName}`);\n\t\t}\n\t}\n\n\treturn { errors, missingScripts, mismatchedScripts };\n}\n\nasync function fixScripts(\n\tjson: PackageJson,\n\tfile: string,\n\tscriptsToFix: string[],\n\texpectedScripts: Record<string, string>,\n\texistingScripts: Record<string, string | undefined>,\n): Promise<string[]> {\n\tconst updatedScripts: Record<string, string> = {};\n\n\t// Copy existing scripts\n\tfor (const [key, value] of Object.entries(existingScripts)) {\n\t\tif (value !== undefined) {\n\t\t\tupdatedScripts[key] = value;\n\t\t}\n\t}\n\n\t// Add/update scripts\n\tconst fixedScripts: string[] = [];\n\tfor (const scriptName of scriptsToFix) {\n\t\tconst expectedValue = expectedScripts[scriptName];\n\t\tif (expectedValue !== undefined) {\n\t\t\tupdatedScripts[scriptName] = expectedValue;\n\t\t\tfixedScripts.push(scriptName);\n\t\t}\n\t}\n\n\tawait jsonfile.writeFile(\n\t\tfile,\n\t\t{ ...json, scripts: updatedScripts },\n\t\t{ spaces: 2 },\n\t);\n\n\treturn fixedScripts;\n}\n\n/**\n * A policy that validates scripts exist AND have exact content in package.json.\n *\n * @remarks\n * Use this policy when you want to enforce exact script content, not just presence.\n * This is useful for scripts that must be exactly the same across packages.\n *\n * For scripts where you only care about presence (not content), use RequiredScripts instead.\n *\n * @example\n * ```typescript\n * import { policy } from \"repopo\";\n * import { ExactScripts } from \"repopo/policies\";\n *\n * policy(ExactScripts, {\n * scripts: {\n * clean: \"rimraf dist esm\",\n * format: \"biome format --write .\",\n * lint: \"biome lint .\",\n * },\n * })\n * ```\n *\n * @alpha\n */\nexport const ExactScripts = definePackagePolicy<\n\tPackageJson,\n\tExactScriptsConfig | undefined\n>({\n\tname: POLICY_NAME,\n\tdescription:\n\t\t\"Validates that scripts exist and have exact content in package.json.\",\n\thandler: async (json, { file, root, resolve, config }) => {\n\t\tif (config === undefined || Object.keys(config.scripts).length === 0) {\n\t\t\treturn true;\n\t\t}\n\n\t\tconst scripts = json.scripts ?? {};\n\t\tconst validation = validateScripts(scripts, config.scripts);\n\n\t\tif (validation.errors.length === 0) {\n\t\t\treturn true;\n\t\t}\n\n\t\tconst scriptsToFix = [\n\t\t\t...validation.missingScripts,\n\t\t\t...validation.mismatchedScripts,\n\t\t];\n\t\tconst hasAutoFixableErrors = scriptsToFix.length > 0;\n\n\t\tif (resolve && hasAutoFixableErrors) {\n\t\t\ttry {\n\t\t\t\tconst fixedScripts = await fixScripts(\n\t\t\t\t\tjson,\n\t\t\t\t\tresolvePath(root, file),\n\t\t\t\t\tscriptsToFix,\n\t\t\t\t\tconfig.scripts,\n\t\t\t\t\tscripts,\n\t\t\t\t);\n\n\t\t\t\treturn {\n\t\t\t\t\tname: POLICY_NAME,\n\t\t\t\t\tfile,\n\t\t\t\t\tresolved: true,\n\t\t\t\t\terrorMessages: [`Fixed scripts: ${fixedScripts.join(\", \")}`],\n\t\t\t\t};\n\t\t\t} catch {\n\t\t\t\t// Fall through to return regular failure\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tname: POLICY_NAME,\n\t\t\tfile,\n\t\t\tautoFixable: hasAutoFixableErrors,\n\t\t\terrorMessages: validation.errors,\n\t\t};\n\t},\n});\n"]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { PolicyShape } from "../policy.js";
|
|
2
|
+
/**
|
|
3
|
+
* Configuration for the GleamDepVersions policy.
|
|
4
|
+
*
|
|
5
|
+
* @alpha
|
|
6
|
+
*/
|
|
7
|
+
export interface GleamDepVersionsConfig {
|
|
8
|
+
/**
|
|
9
|
+
* If true, require all dependencies to have version constraints.
|
|
10
|
+
* @defaultValue true
|
|
11
|
+
*/
|
|
12
|
+
requireVersions?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* If true, warn on unbounded version ranges (e.g., ">= 1.0.0" without upper bound).
|
|
15
|
+
* @defaultValue false
|
|
16
|
+
*/
|
|
17
|
+
warnUnbounded?: boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* A policy that prevents missing or overly loose version constraints in Gleam dependencies.
|
|
21
|
+
*
|
|
22
|
+
* @alpha
|
|
23
|
+
*/
|
|
24
|
+
export declare const GleamDepVersions: PolicyShape<GleamDepVersionsConfig>;
|
|
25
|
+
//# sourceMappingURL=GleamDepVersions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GleamDepVersions.d.ts","sourceRoot":"","sources":["../../src/policies/GleamDepVersions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAMhD;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACtC;;;OAGG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAE1B;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACxB;AAkDD;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,EAAE,WAAW,CAAC,sBAAsB,CAsC9D,CAAC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { defineGleamPolicy, } from "../policyDefiners/defineGleamPolicy.js";
|
|
2
|
+
function extractVersion(dep) {
|
|
3
|
+
if (typeof dep === "string") {
|
|
4
|
+
return dep;
|
|
5
|
+
}
|
|
6
|
+
if (typeof dep === "object" && dep !== null) {
|
|
7
|
+
const record = dep;
|
|
8
|
+
if (typeof record.version === "string") {
|
|
9
|
+
return record.version;
|
|
10
|
+
}
|
|
11
|
+
// Path-only dependencies don't need version checks
|
|
12
|
+
if (typeof record.path === "string") {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
function checkSection(sectionName, deps, requireVersions, warnUnbounded) {
|
|
19
|
+
const errors = [];
|
|
20
|
+
for (const [name, value] of Object.entries(deps)) {
|
|
21
|
+
const version = extractVersion(value);
|
|
22
|
+
if (requireVersions && version === undefined) {
|
|
23
|
+
// Skip path dependencies
|
|
24
|
+
if (typeof value === "object" &&
|
|
25
|
+
value !== null &&
|
|
26
|
+
typeof value.path === "string") {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
errors.push(`[${sectionName}] "${name}" has no version constraint`);
|
|
30
|
+
}
|
|
31
|
+
if (warnUnbounded && version !== undefined && version.startsWith(">=")) {
|
|
32
|
+
errors.push(`[${sectionName}] "${name}" = "${version}" uses an unbounded version range`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return errors;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* A policy that prevents missing or overly loose version constraints in Gleam dependencies.
|
|
39
|
+
*
|
|
40
|
+
* @alpha
|
|
41
|
+
*/
|
|
42
|
+
export const GleamDepVersions = defineGleamPolicy({
|
|
43
|
+
name: "GleamDepVersions",
|
|
44
|
+
description: "Prevents missing or overly loose version constraints in gleam.toml dependencies.",
|
|
45
|
+
handler: async (toml, { config }) => {
|
|
46
|
+
const requireVersions = config?.requireVersions ?? true;
|
|
47
|
+
const warnUnbounded = config?.warnUnbounded ?? false;
|
|
48
|
+
const errors = [];
|
|
49
|
+
const sections = [
|
|
50
|
+
{ name: "dependencies", data: toml.dependencies },
|
|
51
|
+
{ name: "dev-dependencies", data: toml["dev-dependencies"] },
|
|
52
|
+
];
|
|
53
|
+
for (const { name, data } of sections) {
|
|
54
|
+
if (data && typeof data === "object") {
|
|
55
|
+
errors.push(...checkSection(name, data, requireVersions, warnUnbounded));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (errors.length > 0) {
|
|
59
|
+
return {
|
|
60
|
+
error: errors.join("; "),
|
|
61
|
+
manualFix: 'Add specific version constraints (e.g., ">= 1.0.0 and < 2.0.0") to all dependencies.',
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return true;
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
//# sourceMappingURL=GleamDepVersions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GleamDepVersions.js","sourceRoot":"","sources":["../../src/policies/GleamDepVersions.ts"],"names":[],"mappings":"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","sourcesContent":["import type { PolicyShape } from \"../policy.js\";\nimport {\n\tdefineGleamPolicy,\n\ttype GleamToml,\n} from \"../policyDefiners/defineGleamPolicy.js\";\n\n/**\n * Configuration for the GleamDepVersions policy.\n *\n * @alpha\n */\nexport interface GleamDepVersionsConfig {\n\t/**\n\t * If true, require all dependencies to have version constraints.\n\t * @defaultValue true\n\t */\n\trequireVersions?: boolean;\n\n\t/**\n\t * If true, warn on unbounded version ranges (e.g., \">= 1.0.0\" without upper bound).\n\t * @defaultValue false\n\t */\n\twarnUnbounded?: 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\t// Path-only dependencies don't need version checks\n\t\tif (typeof record.path === \"string\") {\n\t\t\treturn undefined;\n\t\t}\n\t}\n\treturn undefined;\n}\n\nfunction checkSection(\n\tsectionName: string,\n\tdeps: Record<string, unknown>,\n\trequireVersions: boolean,\n\twarnUnbounded: boolean,\n): string[] {\n\tconst errors: string[] = [];\n\tfor (const [name, value] of Object.entries(deps)) {\n\t\tconst version = extractVersion(value);\n\n\t\tif (requireVersions && version === undefined) {\n\t\t\t// Skip path dependencies\n\t\t\tif (\n\t\t\t\ttypeof value === \"object\" &&\n\t\t\t\tvalue !== null &&\n\t\t\t\ttypeof (value as Record<string, unknown>).path === \"string\"\n\t\t\t) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\terrors.push(`[${sectionName}] \"${name}\" has no version constraint`);\n\t\t}\n\n\t\tif (warnUnbounded && version !== undefined && version.startsWith(\">=\")) {\n\t\t\terrors.push(\n\t\t\t\t`[${sectionName}] \"${name}\" = \"${version}\" uses an unbounded version range`,\n\t\t\t);\n\t\t}\n\t}\n\treturn errors;\n}\n\n/**\n * A policy that prevents missing or overly loose version constraints in Gleam dependencies.\n *\n * @alpha\n */\nexport const GleamDepVersions: PolicyShape<GleamDepVersionsConfig> =\n\tdefineGleamPolicy({\n\t\tname: \"GleamDepVersions\",\n\t\tdescription:\n\t\t\t\"Prevents missing or overly loose version constraints in gleam.toml dependencies.\",\n\t\thandler: async (toml: GleamToml, { config }) => {\n\t\t\tconst requireVersions = config?.requireVersions ?? true;\n\t\t\tconst warnUnbounded = config?.warnUnbounded ?? false;\n\t\t\tconst errors: string[] = [];\n\n\t\t\tconst sections = [\n\t\t\t\t{ name: \"dependencies\", data: toml.dependencies },\n\t\t\t\t{ name: \"dev-dependencies\", data: toml[\"dev-dependencies\"] },\n\t\t\t];\n\n\t\t\tfor (const { name, data } of sections) {\n\t\t\t\tif (data && typeof data === \"object\") {\n\t\t\t\t\terrors.push(\n\t\t\t\t\t\t...checkSection(\n\t\t\t\t\t\t\tname,\n\t\t\t\t\t\t\tdata as Record<string, unknown>,\n\t\t\t\t\t\t\trequireVersions,\n\t\t\t\t\t\t\twarnUnbounded,\n\t\t\t\t\t\t),\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'Add specific version constraints (e.g., \">= 1.0.0 and < 2.0.0\") to all dependencies.',\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 { PolicyShape } from "../policy.js";
|
|
2
|
+
/**
|
|
3
|
+
* Configuration for the GleamDocExists policy.
|
|
4
|
+
*
|
|
5
|
+
* @alpha
|
|
6
|
+
*/
|
|
7
|
+
export interface GleamDocExistsConfig {
|
|
8
|
+
/**
|
|
9
|
+
* Files that must exist alongside the gleam.toml.
|
|
10
|
+
* @defaultValue ["README.md"]
|
|
11
|
+
*/
|
|
12
|
+
required?: string[];
|
|
13
|
+
/**
|
|
14
|
+
* Files that are recommended but not required. Missing ones produce warnings.
|
|
15
|
+
*/
|
|
16
|
+
recommended?: string[];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* A policy that validates documentation files exist for Gleam packages.
|
|
20
|
+
*
|
|
21
|
+
* @alpha
|
|
22
|
+
*/
|
|
23
|
+
export declare const GleamDocExists: PolicyShape<GleamDocExistsConfig>;
|
|
24
|
+
//# sourceMappingURL=GleamDocExists.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GleamDocExists.d.ts","sourceRoot":"","sources":["../../src/policies/GleamDocExists.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAMhD;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACpC;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IAEpB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAID;;;;GAIG;AACH,eAAO,MAAM,cAAc,EAAE,WAAW,CAAC,oBAAoB,CA4C1D,CAAC"}
|