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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PackageJsonSorted.d.ts","sourceRoot":"","sources":["../../src/policies/PackageJsonSorted.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"PackageJsonSorted.d.ts","sourceRoot":"","sources":["../../src/policies/PackageJsonSorted.ts"],"names":[],"mappings":"AAwCA;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,+CA0D5B,CAAC"}
|
|
@@ -1,8 +1,33 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { call } from "effection";
|
|
1
|
+
import jsonfile from "jsonfile";
|
|
3
2
|
import { resolve as resolvePath } from "pathe";
|
|
4
|
-
import { sortPackageJson } from "sort-package-json";
|
|
5
3
|
import { definePackagePolicy } from "../policyDefiners/definePackagePolicy.js";
|
|
4
|
+
import { detectIndentation } from "../utils/indentation.js";
|
|
5
|
+
const { writeFile: writeJson } = jsonfile;
|
|
6
|
+
const missingDependencyMessage = "PackageJsonSorted requires the optional peer dependency sort-package-json. Install it to enable this policy.";
|
|
7
|
+
function isMissingSortPackageJsonError(error) {
|
|
8
|
+
if (error instanceof Error &&
|
|
9
|
+
"code" in error &&
|
|
10
|
+
error.code === "ERR_MODULE_NOT_FOUND" &&
|
|
11
|
+
error.message.includes("sort-package-json")) {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
return (typeof error === "object" &&
|
|
15
|
+
error !== null &&
|
|
16
|
+
"cause" in error &&
|
|
17
|
+
isMissingSortPackageJsonError(error.cause));
|
|
18
|
+
}
|
|
19
|
+
async function loadSortPackageJson() {
|
|
20
|
+
try {
|
|
21
|
+
const { sortPackageJson } = await import("sort-package-json");
|
|
22
|
+
return sortPackageJson;
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
if (isMissingSortPackageJsonError(error)) {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
6
31
|
/**
|
|
7
32
|
* A repo policy that checks if package.json files in the repo are sorted using sort-package-json.
|
|
8
33
|
*
|
|
@@ -11,7 +36,17 @@ import { definePackagePolicy } from "../policyDefiners/definePackagePolicy.js";
|
|
|
11
36
|
export const PackageJsonSorted = definePackagePolicy({
|
|
12
37
|
name: "PackageJsonSorted",
|
|
13
38
|
description: "Ensures package.json files are sorted consistently using sort-package-json.",
|
|
14
|
-
handler:
|
|
39
|
+
handler: async (json, { file, root, resolve }) => {
|
|
40
|
+
const sortPackageJson = await loadSortPackageJson();
|
|
41
|
+
if (sortPackageJson === undefined) {
|
|
42
|
+
const result = {
|
|
43
|
+
name: PackageJsonSorted.name,
|
|
44
|
+
file,
|
|
45
|
+
autoFixable: false,
|
|
46
|
+
errorMessages: [missingDependencyMessage],
|
|
47
|
+
};
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
15
50
|
const sortedJson = sortPackageJson(json);
|
|
16
51
|
const isSorted = JSON.stringify(sortedJson) === JSON.stringify(json);
|
|
17
52
|
if (isSorted) {
|
|
@@ -19,9 +54,9 @@ export const PackageJsonSorted = definePackagePolicy({
|
|
|
19
54
|
}
|
|
20
55
|
if (resolve) {
|
|
21
56
|
try {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
})
|
|
57
|
+
const packageJsonPath = resolvePath(root, file);
|
|
58
|
+
const indent = await detectIndentation(packageJsonPath);
|
|
59
|
+
await writeJson(packageJsonPath, sortedJson, { spaces: indent });
|
|
25
60
|
const result = {
|
|
26
61
|
name: PackageJsonSorted.name,
|
|
27
62
|
file,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PackageJsonSorted.js","sourceRoot":"","sources":["../../src/policies/PackageJsonSorted.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"PackageJsonSorted.js","sourceRoot":"","sources":["../../src/policies/PackageJsonSorted.ts"],"names":[],"mappings":"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","sourcesContent":["import jsonfile from \"jsonfile\";\nimport { resolve as resolvePath } from \"pathe\";\nimport type { PolicyFailure, PolicyFixResult } from \"../policy.js\";\nimport { definePackagePolicy } from \"../policyDefiners/definePackagePolicy.js\";\nimport { detectIndentation } from \"../utils/indentation.js\";\n\nconst { writeFile: writeJson } = jsonfile;\nconst missingDependencyMessage =\n\t\"PackageJsonSorted requires the optional peer dependency sort-package-json. Install it to enable this policy.\";\n\nfunction isMissingSortPackageJsonError(error: unknown): boolean {\n\tif (\n\t\terror instanceof Error &&\n\t\t\"code\" in error &&\n\t\terror.code === \"ERR_MODULE_NOT_FOUND\" &&\n\t\terror.message.includes(\"sort-package-json\")\n\t) {\n\t\treturn true;\n\t}\n\n\treturn (\n\t\ttypeof error === \"object\" &&\n\t\terror !== null &&\n\t\t\"cause\" in error &&\n\t\tisMissingSortPackageJsonError(error.cause)\n\t);\n}\n\nasync function loadSortPackageJson() {\n\ttry {\n\t\tconst { sortPackageJson } = await import(\"sort-package-json\");\n\t\treturn sortPackageJson;\n\t} catch (error: unknown) {\n\t\tif (isMissingSortPackageJsonError(error)) {\n\t\t\treturn undefined;\n\t\t}\n\t\tthrow error;\n\t}\n}\n\n/**\n * A repo policy that checks if package.json files in the repo are sorted using sort-package-json.\n *\n * @alpha\n */\nexport const PackageJsonSorted = definePackagePolicy({\n\tname: \"PackageJsonSorted\",\n\tdescription:\n\t\t\"Ensures package.json files are sorted consistently using sort-package-json.\",\n\thandler: async (json, { file, root, resolve }) => {\n\t\tconst sortPackageJson = await loadSortPackageJson();\n\t\tif (sortPackageJson === undefined) {\n\t\t\tconst result: PolicyFailure = {\n\t\t\t\tname: PackageJsonSorted.name,\n\t\t\t\tfile,\n\t\t\t\tautoFixable: false,\n\t\t\t\terrorMessages: [missingDependencyMessage],\n\t\t\t};\n\t\t\treturn result;\n\t\t}\n\n\t\tconst sortedJson = sortPackageJson(json);\n\t\tconst isSorted = JSON.stringify(sortedJson) === JSON.stringify(json);\n\n\t\tif (isSorted) {\n\t\t\treturn true;\n\t\t}\n\n\t\tif (resolve) {\n\t\t\ttry {\n\t\t\t\tconst packageJsonPath = resolvePath(root, file);\n\t\t\t\tconst indent = await detectIndentation(packageJsonPath);\n\t\t\t\tawait writeJson(packageJsonPath, sortedJson, { spaces: indent });\n\t\t\t\tconst result: PolicyFixResult = {\n\t\t\t\t\tname: PackageJsonSorted.name,\n\t\t\t\t\tfile,\n\t\t\t\t\tresolved: true,\n\t\t\t\t\terrorMessages: [],\n\t\t\t\t};\n\t\t\t\treturn result;\n\t\t\t} catch (error: unknown) {\n\t\t\t\tconst result: PolicyFixResult = {\n\t\t\t\t\tname: PackageJsonSorted.name,\n\t\t\t\t\tfile,\n\t\t\t\t\tresolved: false,\n\t\t\t\t\tautoFixable: true,\n\t\t\t\t\terrorMessages: [\n\t\t\t\t\t\t(error as Error).message,\n\t\t\t\t\t\t(error as Error).stack ?? \"\",\n\t\t\t\t\t],\n\t\t\t\t};\n\t\t\t\treturn result;\n\t\t\t}\n\t\t} else {\n\t\t\tconst result: PolicyFailure = {\n\t\t\t\tname: PackageJsonSorted.name,\n\t\t\t\tfile,\n\t\t\t\tautoFixable: true,\n\t\t\t\terrorMessages: [],\n\t\t\t};\n\t\t\treturn result;\n\t\t}\n\t},\n});\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PackageLicense.d.ts","sourceRoot":"","sources":["../../src/policies/PackageLicense.ts"],"names":[],"mappings":"AAOA;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACtC;;;;;;;;;;OAUG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CACzB;AA8CD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,eAAO,MAAM,cAAc,
|
|
1
|
+
{"version":3,"file":"PackageLicense.d.ts","sourceRoot":"","sources":["../../src/policies/PackageLicense.ts"],"names":[],"mappings":"AAOA;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACtC;;;;;;;;;;OAUG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CACzB;AA8CD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,eAAO,MAAM,cAAc,wEAyFzB,CAAC"}
|
|
@@ -82,8 +82,7 @@ export const PackageLicense = definePackagePolicy({
|
|
|
82
82
|
return true;
|
|
83
83
|
}
|
|
84
84
|
const packageName = json.name ?? "unknown";
|
|
85
|
-
|
|
86
|
-
const packageDir = path.dirname(file);
|
|
85
|
+
const packageDir = path.dirname(path.resolve(root, file));
|
|
87
86
|
const packageLicensePath = path.join(packageDir, licenseFileName);
|
|
88
87
|
const rootLicensePath = path.join(root, licenseFileName);
|
|
89
88
|
// Check if root LICENSE exists
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PackageLicense.js","sourceRoot":"","sources":["../../src/policies/PackageLicense.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,IAAI,MAAM,OAAO,CAAC;AAGzB,OAAO,EAAE,mBAAmB,EAAE,MAAM,0CAA0C,CAAC;AA6B/E,MAAM,yBAAyB,GAAG,SAAS,CAAC;AAC5C,MAAM,WAAW,GAAG,gBAAgB,CAAC;AAErC;;;GAGG;AACH,KAAK,UAAU,eAAe,CAC7B,eAAuB,EACvB,kBAA0B,EAC1B,UAAkB,EAClB,IAAY,EACZ,WAAmB,EACnB,eAAuB,EACvB,QAAiB;IAEjB,IAAI,CAAC;QACJ,MAAM,QAAQ,CAAC,eAAe,EAAE,kBAAkB,CAAC,CAAC;QACpD,OAAO;YACN,IAAI,EAAE,UAAU;YAChB,IAAI;YACJ,QAAQ,EAAE,IAAI;YACd,WAAW,EAAE,IAAI;YACjB,aAAa,EAAE;gBACd,QAAQ;oBACP,CAAC,CAAC,WAAW,eAAe,sBAAsB,WAAW,GAAG;oBAChE,CAAC,CAAC,WAAW,eAAe,sBAAsB,WAAW,iBAAiB;aAC/E;SACD,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACR,OAAO;YACN,IAAI,EAAE,UAAU;YAChB,IAAI;YACJ,QAAQ,EAAE,KAAK;YACf,WAAW,EAAE,IAAI;YACjB,aAAa,EAAE;gBACd,QAAQ;oBACP,CAAC,CAAC,kBAAkB,eAAe,gBAAgB,WAAW,GAAG;oBACjE,CAAC,CAAC,oBAAoB,eAAe,iBAAiB,WAAW,GAAG;aACrE;SACD,CAAC;IACH,CAAC;AACF,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,mBAAmB,CAG/C;IACD,IAAI,EAAE,gBAAgB;IACtB,WAAW,EACV,mFAAmF;IACpF,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE;QACxD,MAAM,WAAW,GAAG,MAAM,EAAE,WAAW,IAAI,IAAI,CAAC;QAChD,MAAM,eAAe,GACpB,MAAM,EAAE,eAAe,IAAI,yBAAyB,CAAC;QAEtD,+CAA+C;QAC/C,IAAI,WAAW,IAAI,IAAI,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;YAC1C,OAAO,IAAI,CAAC;QACb,CAAC;QAED,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,IAAI,SAAS,CAAC;QAC3C,
|
|
1
|
+
{"version":3,"file":"PackageLicense.js","sourceRoot":"","sources":["../../src/policies/PackageLicense.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync } from \"node:fs\";\nimport { copyFile, readFile } from \"node:fs/promises\";\nimport path from \"pathe\";\nimport type { PackageJson } from \"type-fest\";\nimport type { PolicyFixResult } from \"../policy.js\";\nimport { definePackagePolicy } from \"../policyDefiners/definePackagePolicy.js\";\n\n/**\n * Policy settings for the PackageLicense repo policy.\n *\n * @alpha\n */\nexport interface PackageLicenseSettings {\n\t/**\n\t * Whether to skip validation for private packages.\n\t *\n\t * @defaultValue true\n\t *\n\t * @remarks\n\t *\n\t * Private packages (those with `private: true` in package.json) are typically\n\t * not published and may not need a LICENSE file. Set to `false` to require\n\t * LICENSE files for all packages.\n\t */\n\tskipPrivate?: boolean;\n\n\t/**\n\t * The name of the LICENSE file to look for and sync.\n\t *\n\t * @defaultValue \"LICENSE\"\n\t */\n\tlicenseFileName?: string;\n}\n\nconst DEFAULT_LICENSE_FILE_NAME = \"LICENSE\";\nconst POLICY_NAME = \"PackageLicense\";\n\n/**\n * Copies the root LICENSE file to the package directory.\n * @returns A PolicyFixResult indicating success or failure.\n */\nasync function copyLicenseFile(\n\trootLicensePath: string,\n\tpackageLicensePath: string,\n\tpolicyName: string,\n\tfile: string,\n\tpackageName: string,\n\tlicenseFileName: string,\n\tisCreate: boolean,\n): Promise<PolicyFixResult> {\n\ttry {\n\t\tawait copyFile(rootLicensePath, packageLicensePath);\n\t\treturn {\n\t\t\tname: policyName,\n\t\t\tfile,\n\t\t\tresolved: true,\n\t\t\tautoFixable: true,\n\t\t\terrorMessages: [\n\t\t\t\tisCreate\n\t\t\t\t\t? `Created ${licenseFileName} file for package \"${packageName}\"`\n\t\t\t\t\t: `Updated ${licenseFileName} file for package \"${packageName}\" to match root`,\n\t\t\t],\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tname: policyName,\n\t\t\tfile,\n\t\t\tresolved: false,\n\t\t\tautoFixable: true,\n\t\t\terrorMessages: [\n\t\t\t\tisCreate\n\t\t\t\t\t? `Failed to copy ${licenseFileName} to package \"${packageName}\"`\n\t\t\t\t\t: `Failed to update ${licenseFileName} for package \"${packageName}\"`,\n\t\t\t],\n\t\t};\n\t}\n}\n\n/**\n * A repo policy that ensures each package has a LICENSE file that matches the root LICENSE.\n *\n * @remarks\n *\n * This policy is essential for monorepos where individual packages are published\n * to npm. Each published package should include a LICENSE file, and it should\n * match the root repository's LICENSE to ensure consistent licensing.\n *\n * The policy:\n * 1. Checks if a LICENSE file exists in the package directory\n * 2. Compares it with the root LICENSE file\n * 3. Can auto-fix by copying the root LICENSE to the package directory\n *\n * By default, private packages are skipped since they're not published.\n *\n * @example\n * ```typescript\n * import { makePolicy } from \"repopo\";\n * import { PackageLicense } from \"repopo/policies\";\n *\n * const config: RepopoConfig = {\n * policies: [\n * // Use defaults (skip private, look for LICENSE)\n * makePolicy(PackageLicense),\n *\n * // Or require LICENSE for all packages including private\n * makePolicy(PackageLicense, { skipPrivate: false }),\n * ],\n * };\n * ```\n *\n * @alpha\n */\nexport const PackageLicense = definePackagePolicy<\n\tPackageJson,\n\tPackageLicenseSettings | undefined\n>({\n\tname: \"PackageLicense\",\n\tdescription:\n\t\t\"Ensures each package has a LICENSE file that matches the root repository LICENSE.\",\n\thandler: async (json, { file, root, resolve, config }) => {\n\t\tconst skipPrivate = config?.skipPrivate ?? true;\n\t\tconst licenseFileName =\n\t\t\tconfig?.licenseFileName ?? DEFAULT_LICENSE_FILE_NAME;\n\n\t\t// Skip private packages if configured to do so\n\t\tif (skipPrivate && json.private === true) {\n\t\t\treturn true;\n\t\t}\n\n\t\tconst packageName = json.name ?? \"unknown\";\n\t\tconst packageDir = path.dirname(path.resolve(root, file));\n\t\tconst packageLicensePath = path.join(packageDir, licenseFileName);\n\t\tconst rootLicensePath = path.join(root, licenseFileName);\n\n\t\t// Check if root LICENSE exists\n\t\tif (!existsSync(rootLicensePath)) {\n\t\t\treturn {\n\t\t\t\tname: POLICY_NAME,\n\t\t\t\tfile,\n\t\t\t\tautoFixable: false,\n\t\t\t\terrorMessages: [\n\t\t\t\t\t`Cannot validate package LICENSE: root ${licenseFileName} file not found`,\n\t\t\t\t],\n\t\t\t};\n\t\t}\n\n\t\t// Check if package LICENSE exists\n\t\tif (!existsSync(packageLicensePath)) {\n\t\t\tif (resolve) {\n\t\t\t\treturn await copyLicenseFile(\n\t\t\t\t\trootLicensePath,\n\t\t\t\t\tpackageLicensePath,\n\t\t\t\t\tPOLICY_NAME,\n\t\t\t\t\tfile,\n\t\t\t\t\tpackageName,\n\t\t\t\t\tlicenseFileName,\n\t\t\t\t\ttrue,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tname: POLICY_NAME,\n\t\t\t\tfile,\n\t\t\t\tautoFixable: true,\n\t\t\t\terrorMessages: [\n\t\t\t\t\t`${licenseFileName} file missing for package \"${packageName}\"`,\n\t\t\t\t],\n\t\t\t};\n\t\t}\n\n\t\t// Compare package LICENSE with root LICENSE\n\t\tconst [packageLicenseContent, rootLicenseContent] = await Promise.all([\n\t\t\treadFile(packageLicensePath, \"utf-8\"),\n\t\t\treadFile(rootLicensePath, \"utf-8\"),\n\t\t]);\n\n\t\tif (packageLicenseContent !== rootLicenseContent) {\n\t\t\tif (resolve) {\n\t\t\t\treturn await copyLicenseFile(\n\t\t\t\t\trootLicensePath,\n\t\t\t\t\tpackageLicensePath,\n\t\t\t\t\tPOLICY_NAME,\n\t\t\t\t\tfile,\n\t\t\t\t\tpackageName,\n\t\t\t\t\tlicenseFileName,\n\t\t\t\t\tfalse,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tname: POLICY_NAME,\n\t\t\t\tfile,\n\t\t\t\tautoFixable: true,\n\t\t\t\terrorMessages: [\n\t\t\t\t\t`${licenseFileName} file in package \"${packageName}\" doesn't match root ${licenseFileName}`,\n\t\t\t\t],\n\t\t\t};\n\t\t}\n\n\t\treturn true;\n\t},\n});\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PackagePrivateField.d.ts","sourceRoot":"","sources":["../../src/policies/PackagePrivateField.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"PackagePrivateField.d.ts","sourceRoot":"","sources":["../../src/policies/PackagePrivateField.ts"],"names":[],"mappings":"AAuBA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,WAAW,yBAAyB;IACzC;;;;;;;;;;;;;;;OAeG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IAEvB;;;;;;;;;;;;;;OAcG;IACH,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IAEzB;;;;;;;;;;;;;;;OAeG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IAEtB;;;;;;;;OAQG;IACH,iBAAiB,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAC;CACpD;AA+DD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,eAAO,MAAM,mBAAmB,2EA+E9B,CAAC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import jsonfile from "jsonfile";
|
|
2
|
+
import { resolve as resolvePath } from "pathe";
|
|
2
3
|
import picomatch from "picomatch";
|
|
3
4
|
import { definePackagePolicy } from "../policyDefiners/definePackagePolicy.js";
|
|
4
5
|
const { writeFile: writeJson } = jsonfile;
|
|
@@ -96,7 +97,7 @@ function getRequiredPrivateState(packageName, config) {
|
|
|
96
97
|
export const PackagePrivateField = definePackagePolicy({
|
|
97
98
|
name: "PackagePrivateField",
|
|
98
99
|
description: "Enforces the private field in package.json based on package scope or name patterns.",
|
|
99
|
-
handler: async (json, { file, config, resolve }) => {
|
|
100
|
+
handler: async (json, { file, root, config, resolve }) => {
|
|
100
101
|
// If no config provided, skip validation
|
|
101
102
|
if (config === undefined) {
|
|
102
103
|
return true;
|
|
@@ -144,7 +145,7 @@ export const PackagePrivateField = definePackagePolicy({
|
|
|
144
145
|
// Remove private field
|
|
145
146
|
delete json.private;
|
|
146
147
|
}
|
|
147
|
-
await writeJson(file, json, { spaces: "\t" });
|
|
148
|
+
await writeJson(resolvePath(root, file), json, { spaces: "\t" });
|
|
148
149
|
fixResult.resolved = true;
|
|
149
150
|
}
|
|
150
151
|
catch {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PackagePrivateField.js","sourceRoot":"","sources":["../../src/policies/PackagePrivateField.ts"],"names":[],"mappings":"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","sourcesContent":["import jsonfile from \"jsonfile\";\nimport picomatch from \"picomatch\";\nimport type { PackageJson } from \"type-fest\";\nimport type { PolicyFailure, PolicyFixResult } from \"../policy.js\";\nimport { definePackagePolicy } from \"../policyDefiners/definePackagePolicy.js\";\n\nconst { writeFile: writeJson } = jsonfile;\n\n/**\n * Sentinel value indicating that the package's private status should not be enforced.\n * This is more explicit than using `undefined` to represent \"ignore\".\n */\nconst IGNORE_PRIVATE_STATE = \"ignore\" as const;\n\n/**\n * The required private state for a package.\n * - `true`: Package must have `private: true`\n * - `false`: Package must NOT have `private: true`\n * - `\"ignore\"`: No enforcement - package can be either private or public\n */\ntype RequiredPrivateState = boolean | typeof IGNORE_PRIVATE_STATE;\n\n/**\n * Configuration for the PackagePrivateField policy.\n *\n * This policy enforces that packages are correctly marked as private or public based on\n * their scope or name. This is essential for monorepos where some packages should be\n * published to npm while others should remain internal.\n *\n * All pattern matching uses glob patterns via picomatch.\n *\n * @example\n * ```typescript\n * const config: PackagePrivateFieldConfig = {\n * // Packages in myorg scope must be published (private: false or absent)\n * mustPublish: [\"@myorg/*\"],\n * // Packages in internal scope must be private\n * mustBePrivate: [\"@internal/*\"],\n * // Packages in experimental scope can choose\n * mayPublish: [\"@experimental/*\"],\n * };\n * ```\n *\n * @alpha\n */\nexport interface PackagePrivateFieldConfig {\n\t/**\n\t * A list of glob patterns for packages that must be published.\n\t *\n\t * Packages matching these patterns must NOT have `private: true` in their package.json.\n\t * If they do, the policy will fail and can auto-fix by removing the private field.\n\t *\n\t * @remarks\n\t *\n\t * Uses glob patterns via picomatch. Use \"@scope/*\" to match all packages in a scope,\n\t * or use exact package names.\n\t *\n\t * @example\n\t * ```typescript\n\t * mustPublish: [\"@fluidframework/*\", \"@fluid-tools/*\", \"tinylicious\"]\n\t * ```\n\t */\n\tmustPublish?: string[];\n\n\t/**\n\t * A list of glob patterns for packages that must be private.\n\t *\n\t * Packages matching these patterns must have `private: true` in their package.json.\n\t * If they don't, the policy will fail and can auto-fix by adding `private: true`.\n\t *\n\t * @remarks\n\t *\n\t * Uses glob patterns via picomatch (see mustPublish for pattern examples).\n\t *\n\t * @example\n\t * ```typescript\n\t * mustBePrivate: [\"@fluid-internal/*\", \"@fluid-private/*\"]\n\t * ```\n\t */\n\tmustBePrivate?: string[];\n\n\t/**\n\t * A list of glob patterns for packages that may optionally be published.\n\t *\n\t * Packages matching these patterns can choose whether to be private or public.\n\t * The policy will not enforce either state for these packages.\n\t *\n\t * @remarks\n\t *\n\t * Uses glob patterns via picomatch (see mustPublish for pattern examples).\n\t * Packages not matching any list default to requiring `private: true`.\n\t *\n\t * @example\n\t * ```typescript\n\t * mayPublish: [\"@fluid-experimental/*\"]\n\t * ```\n\t */\n\tmayPublish?: string[];\n\n\t/**\n\t * Default behavior for packages that don't match any of the above lists.\n\t *\n\t * - `\"private\"` (default): Unmatched packages must have `private: true`\n\t * - `\"public\"`: Unmatched packages must NOT have `private: true`\n\t * - `\"ignore\"`: No enforcement for unmatched packages\n\t *\n\t * @defaultValue \"private\"\n\t */\n\tunmatchedPackages?: \"private\" | \"public\" | \"ignore\";\n}\n\n/**\n * Check if a package name matches any pattern in the list.\n *\n * Supports glob patterns via picomatch. Use patterns like:\n * - \"@myorg/\\*\" - matches all packages in the myorg scope\n * - \"my-package\" - exact match\n * - \"my-*\" - matches my-foo, my-bar, etc.\n *\n * @param packageName - The package name to check (e.g., `@myorg/foo`)\n * @param patterns - List of glob patterns or exact package names\n * @returns `true` if the package matches any pattern\n */\nfunction matchesAnyPattern(\n\tpackageName: string,\n\tpatterns: string[] | undefined,\n): boolean {\n\tif (patterns === undefined || patterns.length === 0) {\n\t\treturn false;\n\t}\n\n\treturn picomatch.isMatch(packageName, patterns);\n}\n\n/**\n * Determine what the private field should be for a package.\n *\n * @returns `true` if package must be private, `false` if must be public, `\"ignore\"` if either is ok\n */\nfunction getRequiredPrivateState(\n\tpackageName: string,\n\tconfig: PackagePrivateFieldConfig,\n): RequiredPrivateState {\n\t// Check mustPublish first - these packages must NOT be private\n\tif (matchesAnyPattern(packageName, config.mustPublish)) {\n\t\treturn false;\n\t}\n\n\t// Check mustBePrivate - these packages MUST be private\n\tif (matchesAnyPattern(packageName, config.mustBePrivate)) {\n\t\treturn true;\n\t}\n\n\t// Check mayPublish - these packages can be either\n\tif (matchesAnyPattern(packageName, config.mayPublish)) {\n\t\treturn IGNORE_PRIVATE_STATE;\n\t}\n\n\t// Default behavior for unmatched packages\n\tconst unmatchedBehavior = config.unmatchedPackages ?? \"private\";\n\tswitch (unmatchedBehavior) {\n\t\tcase \"private\":\n\t\t\treturn true;\n\t\tcase \"public\":\n\t\t\treturn false;\n\t\tcase \"ignore\":\n\t\t\treturn IGNORE_PRIVATE_STATE;\n\t\tdefault:\n\t\t\tthrow new Error(`Unknown unmatchedPackages value: ${unmatchedBehavior}`);\n\t}\n}\n\n/**\n * A policy that enforces the `private` field in package.json based on package scope or name.\n *\n * This policy is essential for monorepos where publishing control is important:\n *\n * - Prevent accidental publishing of internal packages\n * - Ensure public packages don't have `private: true` blocking publishing\n * - Allow experimental packages to choose their own publishing status\n *\n * @remarks\n *\n * The policy evaluates packages in the following order:\n *\n * 1. If the package matches `mustPublish`, it must NOT have `private: true`\n * 2. If the package matches `mustBePrivate`, it MUST have `private: true`\n * 3. If the package matches `mayPublish`, no enforcement is applied\n * 4. Otherwise, the `unmatchedPackages` setting determines behavior (defaults to requiring private)\n *\n * @example\n * ```typescript\n * import { makePolicy } from \"repopo\";\n * import { PackagePrivateField } from \"repopo/policies\";\n *\n * const config: RepopoConfig = {\n * policies: [\n * makePolicy(PackagePrivateField, {\n * mustPublish: [\"@myorg/*\"],\n * mustBePrivate: [\"@internal/*\"],\n * mayPublish: [\"@experimental/*\"],\n * unmatchedPackages: \"private\",\n * }),\n * ],\n * };\n * ```\n *\n * @alpha\n */\nexport const PackagePrivateField = definePackagePolicy<\n\tPackageJson,\n\tPackagePrivateFieldConfig | undefined\n>({\n\tname: \"PackagePrivateField\",\n\tdescription:\n\t\t\"Enforces the private field in package.json based on package scope or name patterns.\",\n\thandler: async (json, { file, config, resolve }) => {\n\t\t// If no config provided, skip validation\n\t\tif (config === undefined) {\n\t\t\treturn true;\n\t\t}\n\n\t\tconst packageName = json.name;\n\n\t\t// Skip packages without a name (shouldn't happen in practice)\n\t\tif (packageName === undefined) {\n\t\t\treturn true;\n\t\t}\n\n\t\t// Skip the root package (typically named \"root\" in monorepos)\n\t\tif (packageName === \"root\") {\n\t\t\treturn true;\n\t\t}\n\n\t\tconst requiredState = getRequiredPrivateState(packageName, config);\n\n\t\t// If \"ignore\", the package can be either private or public\n\t\tif (requiredState === IGNORE_PRIVATE_STATE) {\n\t\t\treturn true;\n\t\t}\n\n\t\tconst currentlyPrivate = json.private === true;\n\n\t\t// Check if the current state matches the required state\n\t\tif (requiredState === currentlyPrivate) {\n\t\t\treturn true;\n\t\t}\n\n\t\t// State mismatch - build error message\n\t\tconst errorMessage =\n\t\t\trequiredState === true\n\t\t\t\t? `Package \"${packageName}\" must be marked private. Add \"private\": true to package.json.`\n\t\t\t\t: `Package \"${packageName}\" must not be marked private. Remove \"private\": true from package.json to allow publishing.`;\n\n\t\tconst failResult: PolicyFailure = {\n\t\t\tname: PackagePrivateField.name,\n\t\t\tfile,\n\t\t\tautoFixable: true,\n\t\t\terrorMessages: [errorMessage],\n\t\t};\n\n\t\tif (resolve) {\n\t\t\tconst fixResult: PolicyFixResult = {\n\t\t\t\t...failResult,\n\t\t\t\tresolved: false,\n\t\t\t};\n\n\t\t\ttry {\n\t\t\t\tif (requiredState === true) {\n\t\t\t\t\t// Add private: true\n\t\t\t\t\tjson.private = true;\n\t\t\t\t} else {\n\t\t\t\t\t// Remove private field\n\t\t\t\t\tdelete json.private;\n\t\t\t\t}\n\n\t\t\t\tawait writeJson(file, json, { spaces: \"\\t\" });\n\t\t\t\tfixResult.resolved = true;\n\t\t\t} catch {\n\t\t\t\tfixResult.resolved = false;\n\t\t\t\tfixResult.errorMessages = [`Failed to update ${file}`];\n\t\t\t}\n\n\t\t\treturn fixResult;\n\t\t}\n\n\t\treturn failResult;\n\t},\n});\n"]}
|
|
1
|
+
{"version":3,"file":"PackagePrivateField.js","sourceRoot":"","sources":["../../src/policies/PackagePrivateField.ts"],"names":[],"mappings":"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","sourcesContent":["import jsonfile from \"jsonfile\";\nimport { resolve as resolvePath } from \"pathe\";\nimport picomatch from \"picomatch\";\nimport type { PackageJson } from \"type-fest\";\nimport type { PolicyFailure, PolicyFixResult } from \"../policy.js\";\nimport { definePackagePolicy } from \"../policyDefiners/definePackagePolicy.js\";\n\nconst { writeFile: writeJson } = jsonfile;\n\n/**\n * Sentinel value indicating that the package's private status should not be enforced.\n * This is more explicit than using `undefined` to represent \"ignore\".\n */\nconst IGNORE_PRIVATE_STATE = \"ignore\" as const;\n\n/**\n * The required private state for a package.\n * - `true`: Package must have `private: true`\n * - `false`: Package must NOT have `private: true`\n * - `\"ignore\"`: No enforcement - package can be either private or public\n */\ntype RequiredPrivateState = boolean | typeof IGNORE_PRIVATE_STATE;\n\n/**\n * Configuration for the PackagePrivateField policy.\n *\n * This policy enforces that packages are correctly marked as private or public based on\n * their scope or name. This is essential for monorepos where some packages should be\n * published to npm while others should remain internal.\n *\n * All pattern matching uses glob patterns via picomatch.\n *\n * @example\n * ```typescript\n * const config: PackagePrivateFieldConfig = {\n * // Packages in myorg scope must be published (private: false or absent)\n * mustPublish: [\"@myorg/*\"],\n * // Packages in internal scope must be private\n * mustBePrivate: [\"@internal/*\"],\n * // Packages in experimental scope can choose\n * mayPublish: [\"@experimental/*\"],\n * };\n * ```\n *\n * @alpha\n */\nexport interface PackagePrivateFieldConfig {\n\t/**\n\t * A list of glob patterns for packages that must be published.\n\t *\n\t * Packages matching these patterns must NOT have `private: true` in their package.json.\n\t * If they do, the policy will fail and can auto-fix by removing the private field.\n\t *\n\t * @remarks\n\t *\n\t * Uses glob patterns via picomatch. Use \"@scope/*\" to match all packages in a scope,\n\t * or use exact package names.\n\t *\n\t * @example\n\t * ```typescript\n\t * mustPublish: [\"@fluidframework/*\", \"@fluid-tools/*\", \"tinylicious\"]\n\t * ```\n\t */\n\tmustPublish?: string[];\n\n\t/**\n\t * A list of glob patterns for packages that must be private.\n\t *\n\t * Packages matching these patterns must have `private: true` in their package.json.\n\t * If they don't, the policy will fail and can auto-fix by adding `private: true`.\n\t *\n\t * @remarks\n\t *\n\t * Uses glob patterns via picomatch (see mustPublish for pattern examples).\n\t *\n\t * @example\n\t * ```typescript\n\t * mustBePrivate: [\"@fluid-internal/*\", \"@fluid-private/*\"]\n\t * ```\n\t */\n\tmustBePrivate?: string[];\n\n\t/**\n\t * A list of glob patterns for packages that may optionally be published.\n\t *\n\t * Packages matching these patterns can choose whether to be private or public.\n\t * The policy will not enforce either state for these packages.\n\t *\n\t * @remarks\n\t *\n\t * Uses glob patterns via picomatch (see mustPublish for pattern examples).\n\t * Packages not matching any list default to requiring `private: true`.\n\t *\n\t * @example\n\t * ```typescript\n\t * mayPublish: [\"@fluid-experimental/*\"]\n\t * ```\n\t */\n\tmayPublish?: string[];\n\n\t/**\n\t * Default behavior for packages that don't match any of the above lists.\n\t *\n\t * - `\"private\"` (default): Unmatched packages must have `private: true`\n\t * - `\"public\"`: Unmatched packages must NOT have `private: true`\n\t * - `\"ignore\"`: No enforcement for unmatched packages\n\t *\n\t * @defaultValue \"private\"\n\t */\n\tunmatchedPackages?: \"private\" | \"public\" | \"ignore\";\n}\n\n/**\n * Check if a package name matches any pattern in the list.\n *\n * Supports glob patterns via picomatch. Use patterns like:\n * - \"@myorg/\\*\" - matches all packages in the myorg scope\n * - \"my-package\" - exact match\n * - \"my-*\" - matches my-foo, my-bar, etc.\n *\n * @param packageName - The package name to check (e.g., `@myorg/foo`)\n * @param patterns - List of glob patterns or exact package names\n * @returns `true` if the package matches any pattern\n */\nfunction matchesAnyPattern(\n\tpackageName: string,\n\tpatterns: string[] | undefined,\n): boolean {\n\tif (patterns === undefined || patterns.length === 0) {\n\t\treturn false;\n\t}\n\n\treturn picomatch.isMatch(packageName, patterns);\n}\n\n/**\n * Determine what the private field should be for a package.\n *\n * @returns `true` if package must be private, `false` if must be public, `\"ignore\"` if either is ok\n */\nfunction getRequiredPrivateState(\n\tpackageName: string,\n\tconfig: PackagePrivateFieldConfig,\n): RequiredPrivateState {\n\t// Check mustPublish first - these packages must NOT be private\n\tif (matchesAnyPattern(packageName, config.mustPublish)) {\n\t\treturn false;\n\t}\n\n\t// Check mustBePrivate - these packages MUST be private\n\tif (matchesAnyPattern(packageName, config.mustBePrivate)) {\n\t\treturn true;\n\t}\n\n\t// Check mayPublish - these packages can be either\n\tif (matchesAnyPattern(packageName, config.mayPublish)) {\n\t\treturn IGNORE_PRIVATE_STATE;\n\t}\n\n\t// Default behavior for unmatched packages\n\tconst unmatchedBehavior = config.unmatchedPackages ?? \"private\";\n\tswitch (unmatchedBehavior) {\n\t\tcase \"private\":\n\t\t\treturn true;\n\t\tcase \"public\":\n\t\t\treturn false;\n\t\tcase \"ignore\":\n\t\t\treturn IGNORE_PRIVATE_STATE;\n\t\tdefault:\n\t\t\tthrow new Error(`Unknown unmatchedPackages value: ${unmatchedBehavior}`);\n\t}\n}\n\n/**\n * A policy that enforces the `private` field in package.json based on package scope or name.\n *\n * This policy is essential for monorepos where publishing control is important:\n *\n * - Prevent accidental publishing of internal packages\n * - Ensure public packages don't have `private: true` blocking publishing\n * - Allow experimental packages to choose their own publishing status\n *\n * @remarks\n *\n * The policy evaluates packages in the following order:\n *\n * 1. If the package matches `mustPublish`, it must NOT have `private: true`\n * 2. If the package matches `mustBePrivate`, it MUST have `private: true`\n * 3. If the package matches `mayPublish`, no enforcement is applied\n * 4. Otherwise, the `unmatchedPackages` setting determines behavior (defaults to requiring private)\n *\n * @example\n * ```typescript\n * import { makePolicy } from \"repopo\";\n * import { PackagePrivateField } from \"repopo/policies\";\n *\n * const config: RepopoConfig = {\n * policies: [\n * makePolicy(PackagePrivateField, {\n * mustPublish: [\"@myorg/*\"],\n * mustBePrivate: [\"@internal/*\"],\n * mayPublish: [\"@experimental/*\"],\n * unmatchedPackages: \"private\",\n * }),\n * ],\n * };\n * ```\n *\n * @alpha\n */\nexport const PackagePrivateField = definePackagePolicy<\n\tPackageJson,\n\tPackagePrivateFieldConfig | undefined\n>({\n\tname: \"PackagePrivateField\",\n\tdescription:\n\t\t\"Enforces the private field in package.json based on package scope or name patterns.\",\n\thandler: async (json, { file, root, config, resolve }) => {\n\t\t// If no config provided, skip validation\n\t\tif (config === undefined) {\n\t\t\treturn true;\n\t\t}\n\n\t\tconst packageName = json.name;\n\n\t\t// Skip packages without a name (shouldn't happen in practice)\n\t\tif (packageName === undefined) {\n\t\t\treturn true;\n\t\t}\n\n\t\t// Skip the root package (typically named \"root\" in monorepos)\n\t\tif (packageName === \"root\") {\n\t\t\treturn true;\n\t\t}\n\n\t\tconst requiredState = getRequiredPrivateState(packageName, config);\n\n\t\t// If \"ignore\", the package can be either private or public\n\t\tif (requiredState === IGNORE_PRIVATE_STATE) {\n\t\t\treturn true;\n\t\t}\n\n\t\tconst currentlyPrivate = json.private === true;\n\n\t\t// Check if the current state matches the required state\n\t\tif (requiredState === currentlyPrivate) {\n\t\t\treturn true;\n\t\t}\n\n\t\t// State mismatch - build error message\n\t\tconst errorMessage =\n\t\t\trequiredState === true\n\t\t\t\t? `Package \"${packageName}\" must be marked private. Add \"private\": true to package.json.`\n\t\t\t\t: `Package \"${packageName}\" must not be marked private. Remove \"private\": true from package.json to allow publishing.`;\n\n\t\tconst failResult: PolicyFailure = {\n\t\t\tname: PackagePrivateField.name,\n\t\t\tfile,\n\t\t\tautoFixable: true,\n\t\t\terrorMessages: [errorMessage],\n\t\t};\n\n\t\tif (resolve) {\n\t\t\tconst fixResult: PolicyFixResult = {\n\t\t\t\t...failResult,\n\t\t\t\tresolved: false,\n\t\t\t};\n\n\t\t\ttry {\n\t\t\t\tif (requiredState === true) {\n\t\t\t\t\t// Add private: true\n\t\t\t\t\tjson.private = true;\n\t\t\t\t} else {\n\t\t\t\t\t// Remove private field\n\t\t\t\t\tdelete json.private;\n\t\t\t\t}\n\n\t\t\t\tawait writeJson(resolvePath(root, file), json, { spaces: \"\\t\" });\n\t\t\t\tfixResult.resolved = true;\n\t\t\t} catch {\n\t\t\t\tfixResult.resolved = false;\n\t\t\t\tfixResult.errorMessages = [`Failed to update ${file}`];\n\t\t\t}\n\n\t\t\treturn fixResult;\n\t\t}\n\n\t\treturn failResult;\n\t},\n});\n"]}
|
|
@@ -182,7 +182,7 @@ async function validateExistingReadme(ctx) {
|
|
|
182
182
|
export const PackageReadme = definePackagePolicy({
|
|
183
183
|
name: "PackageReadme",
|
|
184
184
|
description: "Ensures each package has a README.md file with proper title and required content.",
|
|
185
|
-
handler: async (json, { file, resolve, config }) => {
|
|
185
|
+
handler: async (json, { file, root, resolve, config }) => {
|
|
186
186
|
const skipPrivate = config?.skipPrivate ?? true;
|
|
187
187
|
const requireMatchingTitle = config?.requireMatchingTitle ?? true;
|
|
188
188
|
const requiredContent = config?.requiredContent;
|
|
@@ -191,7 +191,7 @@ export const PackageReadme = definePackagePolicy({
|
|
|
191
191
|
return true;
|
|
192
192
|
}
|
|
193
193
|
const packageName = json.name ?? "unknown";
|
|
194
|
-
const packageDir = path.dirname(file);
|
|
194
|
+
const packageDir = path.dirname(path.resolve(root, file));
|
|
195
195
|
const readmePath = path.join(packageDir, "README.md");
|
|
196
196
|
// Check if README exists
|
|
197
197
|
if (!existsSync(readmePath)) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PackageReadme.js","sourceRoot":"","sources":["../../src/policies/PackageReadme.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync } from \"node:fs\";\nimport { readFile, writeFile } from \"node:fs/promises\";\nimport path from \"pathe\";\nimport type { PackageJson } from \"type-fest\";\nimport type { PolicyFailure, PolicyFixResult } from \"../policy.js\";\nimport { definePackagePolicy } from \"../policyDefiners/definePackagePolicy.js\";\n\n// Pre-compiled regex for performance\nconst LINE_SPLIT_REGEX = /\\r?\\n/;\nconst HEADING_REGEX = /^#\\s+(.+)$/;\n\n/**\n * Policy settings for the PackageReadme repo policy.\n *\n * @alpha\n */\nexport interface PackageReadmeSettings {\n\t/**\n\t * Whether to skip validation for private packages.\n\t *\n\t * @defaultValue true\n\t *\n\t * @remarks\n\t *\n\t * Private packages are typically not published and may not need a README file.\n\t * Set to `false` to require README files for all packages.\n\t */\n\tskipPrivate?: boolean;\n\n\t/**\n\t * Whether to require the README title to match the package name.\n\t *\n\t * @defaultValue true\n\t *\n\t * @remarks\n\t *\n\t * When enabled, the first line of the README must be `# package-name`.\n\t * This ensures consistency and helps users identify the package.\n\t */\n\trequireMatchingTitle?: boolean;\n\n\t/**\n\t * A string that must appear in the README content.\n\t *\n\t * @remarks\n\t *\n\t * Useful for ensuring all READMEs include required content like trademark\n\t * notices, disclaimers, or standard documentation sections.\n\t *\n\t * @example\n\t * ```typescript\n\t * requiredContent: \"## Trademark\"\n\t * ```\n\t */\n\trequiredContent?: string;\n}\n\n/**\n * Extract the title from a README file (first markdown H1 heading).\n *\n * @param content - The README content\n * @returns The title text (without the `# ` prefix) or `undefined` if not found\n */\nfunction extractReadmeTitle(content: string): string | undefined {\n\tconst lines = content.split(LINE_SPLIT_REGEX);\n\tfor (const line of lines) {\n\t\tconst match = HEADING_REGEX.exec(line);\n\t\tif (match?.[1]) {\n\t\t\treturn match[1].trim();\n\t\t}\n\t}\n\treturn undefined;\n}\n\n/**\n * Create a basic README content with package name as title.\n */\nfunction createReadmeContent(\n\tpackageName: string,\n\trequiredContent?: string,\n): string {\n\tlet content = `# ${packageName}\\n`;\n\tif (requiredContent) {\n\t\tcontent += `\\n${requiredContent}\\n`;\n\t}\n\treturn content;\n}\n\n/**\n * Handle case when README is missing.\n */\nasync function handleMissingReadme(\n\treadmePath: string,\n\tpackageName: string,\n\tfile: string,\n\tresolve: boolean,\n\trequiredContent?: string,\n): Promise<PolicyFailure | PolicyFixResult> {\n\tif (resolve) {\n\t\ttry {\n\t\t\tawait writeFile(\n\t\t\t\treadmePath,\n\t\t\t\tcreateReadmeContent(packageName, requiredContent),\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tname: \"PackageReadme\",\n\t\t\t\tfile,\n\t\t\t\tresolved: true,\n\t\t\t\tautoFixable: true,\n\t\t\t\terrorMessages: [`Created README.md for package \"${packageName}\"`],\n\t\t\t};\n\t\t} catch {\n\t\t\treturn {\n\t\t\t\tname: \"PackageReadme\",\n\t\t\t\tfile,\n\t\t\t\tresolved: false,\n\t\t\t\tautoFixable: true,\n\t\t\t\terrorMessages: [\n\t\t\t\t\t`Failed to create README.md for package \"${packageName}\"`,\n\t\t\t\t],\n\t\t\t};\n\t\t}\n\t}\n\n\treturn {\n\t\tname: \"PackageReadme\",\n\t\tfile,\n\t\tautoFixable: true,\n\t\terrorMessages: [`README.md missing for package \"${packageName}\"`],\n\t};\n}\n\n/**\n * Validate README title against package name.\n *\n * @returns `true` if valid, or an error message string if invalid\n */\nfunction validateTitle(\n\treadmeContent: string,\n\tpackageName: string,\n): true | string {\n\tconst title = extractReadmeTitle(readmeContent);\n\tif (title !== packageName) {\n\t\treturn `README title \"${title ?? \"(none)\"}\" doesn't match package name \"${packageName}\"`;\n\t}\n\treturn true;\n}\n\n/**\n * Handle required content validation and auto-fix.\n */\nasync function handleRequiredContent(\n\treadmePath: string,\n\treadmeContent: string,\n\trequiredContent: string,\n\tresolve: boolean,\n): Promise<{ error?: string; fixed: boolean }> {\n\tif (readmeContent.includes(requiredContent)) {\n\t\treturn { fixed: false };\n\t}\n\n\tif (resolve) {\n\t\ttry {\n\t\t\tconst newContent = readmeContent.endsWith(\"\\n\")\n\t\t\t\t? `${readmeContent}${requiredContent}\\n`\n\t\t\t\t: `${readmeContent}\\n${requiredContent}\\n`;\n\t\t\tawait writeFile(readmePath, newContent);\n\t\t\treturn {\n\t\t\t\terror: `README missing required content: \"${requiredContent}\"`,\n\t\t\t\tfixed: true,\n\t\t\t};\n\t\t} catch {\n\t\t\t// Fall through to report error\n\t\t}\n\t}\n\n\treturn {\n\t\terror: `README missing required content: \"${requiredContent}\"`,\n\t\tfixed: false,\n\t};\n}\n\ninterface ValidationContext {\n\treadmePath: string;\n\treadmeContent: string;\n\tpackageName: string;\n\tfile: string;\n\tresolve: boolean;\n\trequireMatchingTitle: boolean;\n\trequiredContent: string | undefined;\n}\n\n/**\n * Validate existing README content.\n */\nasync function validateExistingReadme(\n\tctx: ValidationContext,\n): Promise<true | PolicyFailure | PolicyFixResult> {\n\tconst errors: string[] = [];\n\tlet wasFixed = false;\n\n\t// Check title matches package name\n\tif (ctx.requireMatchingTitle) {\n\t\tconst titleResult = validateTitle(ctx.readmeContent, ctx.packageName);\n\t\tif (titleResult !== true) {\n\t\t\terrors.push(titleResult);\n\t\t}\n\t}\n\n\t// Check required content\n\tif (ctx.requiredContent) {\n\t\tconst contentResult = await handleRequiredContent(\n\t\t\tctx.readmePath,\n\t\t\tctx.readmeContent,\n\t\t\tctx.requiredContent,\n\t\t\tctx.resolve,\n\t\t);\n\t\tif (contentResult.error) {\n\t\t\terrors.push(contentResult.error);\n\t\t}\n\t\tif (contentResult.fixed) {\n\t\t\twasFixed = true;\n\t\t}\n\t}\n\n\tif (errors.length === 0) {\n\t\treturn true;\n\t}\n\n\tif (wasFixed) {\n\t\treturn {\n\t\t\tname: \"PackageReadme\",\n\t\t\tfile: ctx.file,\n\t\t\tresolved: true,\n\t\t\tautoFixable: true,\n\t\t\terrorMessages: [errors.join(\"; \")],\n\t\t};\n\t}\n\n\treturn {\n\t\tname: \"PackageReadme\",\n\t\tfile: ctx.file,\n\t\tautoFixable:\n\t\t\tctx.requiredContent !== undefined &&\n\t\t\t!ctx.readmeContent.includes(ctx.requiredContent),\n\t\terrorMessages: [errors.join(\"; \")],\n\t};\n}\n\n/**\n * A repo policy that ensures each package has a README.md file with proper content.\n *\n * @remarks\n *\n * This policy validates that packages have documentation and can enforce:\n * 1. README.md file exists\n * 2. README title matches the package name\n * 3. README contains required content (e.g., trademark notices)\n *\n * The policy supports auto-fixing by creating a basic README with the package name as title.\n *\n * @example\n * ```typescript\n * import { makePolicy } from \"repopo\";\n * import { PackageReadme } from \"repopo/policies\";\n *\n * const config: RepopoConfig = {\n * policies: [\n * // Basic README validation\n * makePolicy(PackageReadme),\n *\n * // Require trademark notice in all READMEs\n * makePolicy(PackageReadme, {\n * requiredContent: \"## Trademark\",\n * }),\n * ],\n * };\n * ```\n *\n * @alpha\n */\nexport const PackageReadme = definePackagePolicy<\n\tPackageJson,\n\tPackageReadmeSettings | undefined\n>({\n\tname: \"PackageReadme\",\n\tdescription:\n\t\t\"Ensures each package has a README.md file with proper title and required content.\",\n\thandler: async (json, { file, resolve, config }) => {\n\t\tconst skipPrivate = config?.skipPrivate ?? true;\n\t\tconst requireMatchingTitle = config?.requireMatchingTitle ?? true;\n\t\tconst requiredContent = config?.requiredContent;\n\n\t\t// Skip private packages if configured to do so\n\t\tif (skipPrivate && json.private === true) {\n\t\t\treturn true;\n\t\t}\n\n\t\tconst packageName = json.name ?? \"unknown\";\n\t\tconst packageDir = path.dirname(file);\n\t\tconst readmePath = path.join(packageDir, \"README.md\");\n\n\t\t// Check if README exists\n\t\tif (!existsSync(readmePath)) {\n\t\t\treturn await handleMissingReadme(\n\t\t\t\treadmePath,\n\t\t\t\tpackageName,\n\t\t\t\tfile,\n\t\t\t\tresolve,\n\t\t\t\trequiredContent,\n\t\t\t);\n\t\t}\n\n\t\t// README exists, validate content\n\t\tconst readmeContent = await readFile(readmePath, \"utf-8\");\n\t\treturn await validateExistingReadme({\n\t\t\treadmePath,\n\t\t\treadmeContent,\n\t\t\tpackageName,\n\t\t\tfile,\n\t\t\tresolve,\n\t\t\trequireMatchingTitle,\n\t\t\trequiredContent,\n\t\t});\n\t},\n});\n"]}
|
|
1
|
+
{"version":3,"file":"PackageReadme.js","sourceRoot":"","sources":["../../src/policies/PackageReadme.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync } from \"node:fs\";\nimport { readFile, writeFile } from \"node:fs/promises\";\nimport path from \"pathe\";\nimport type { PackageJson } from \"type-fest\";\nimport type { PolicyFailure, PolicyFixResult } from \"../policy.js\";\nimport { definePackagePolicy } from \"../policyDefiners/definePackagePolicy.js\";\n\n// Pre-compiled regex for performance\nconst LINE_SPLIT_REGEX = /\\r?\\n/;\nconst HEADING_REGEX = /^#\\s+(.+)$/;\n\n/**\n * Policy settings for the PackageReadme repo policy.\n *\n * @alpha\n */\nexport interface PackageReadmeSettings {\n\t/**\n\t * Whether to skip validation for private packages.\n\t *\n\t * @defaultValue true\n\t *\n\t * @remarks\n\t *\n\t * Private packages are typically not published and may not need a README file.\n\t * Set to `false` to require README files for all packages.\n\t */\n\tskipPrivate?: boolean;\n\n\t/**\n\t * Whether to require the README title to match the package name.\n\t *\n\t * @defaultValue true\n\t *\n\t * @remarks\n\t *\n\t * When enabled, the first line of the README must be `# package-name`.\n\t * This ensures consistency and helps users identify the package.\n\t */\n\trequireMatchingTitle?: boolean;\n\n\t/**\n\t * A string that must appear in the README content.\n\t *\n\t * @remarks\n\t *\n\t * Useful for ensuring all READMEs include required content like trademark\n\t * notices, disclaimers, or standard documentation sections.\n\t *\n\t * @example\n\t * ```typescript\n\t * requiredContent: \"## Trademark\"\n\t * ```\n\t */\n\trequiredContent?: string;\n}\n\n/**\n * Extract the title from a README file (first markdown H1 heading).\n *\n * @param content - The README content\n * @returns The title text (without the `# ` prefix) or `undefined` if not found\n */\nfunction extractReadmeTitle(content: string): string | undefined {\n\tconst lines = content.split(LINE_SPLIT_REGEX);\n\tfor (const line of lines) {\n\t\tconst match = HEADING_REGEX.exec(line);\n\t\tif (match?.[1]) {\n\t\t\treturn match[1].trim();\n\t\t}\n\t}\n\treturn undefined;\n}\n\n/**\n * Create a basic README content with package name as title.\n */\nfunction createReadmeContent(\n\tpackageName: string,\n\trequiredContent?: string,\n): string {\n\tlet content = `# ${packageName}\\n`;\n\tif (requiredContent) {\n\t\tcontent += `\\n${requiredContent}\\n`;\n\t}\n\treturn content;\n}\n\n/**\n * Handle case when README is missing.\n */\nasync function handleMissingReadme(\n\treadmePath: string,\n\tpackageName: string,\n\tfile: string,\n\tresolve: boolean,\n\trequiredContent?: string,\n): Promise<PolicyFailure | PolicyFixResult> {\n\tif (resolve) {\n\t\ttry {\n\t\t\tawait writeFile(\n\t\t\t\treadmePath,\n\t\t\t\tcreateReadmeContent(packageName, requiredContent),\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tname: \"PackageReadme\",\n\t\t\t\tfile,\n\t\t\t\tresolved: true,\n\t\t\t\tautoFixable: true,\n\t\t\t\terrorMessages: [`Created README.md for package \"${packageName}\"`],\n\t\t\t};\n\t\t} catch {\n\t\t\treturn {\n\t\t\t\tname: \"PackageReadme\",\n\t\t\t\tfile,\n\t\t\t\tresolved: false,\n\t\t\t\tautoFixable: true,\n\t\t\t\terrorMessages: [\n\t\t\t\t\t`Failed to create README.md for package \"${packageName}\"`,\n\t\t\t\t],\n\t\t\t};\n\t\t}\n\t}\n\n\treturn {\n\t\tname: \"PackageReadme\",\n\t\tfile,\n\t\tautoFixable: true,\n\t\terrorMessages: [`README.md missing for package \"${packageName}\"`],\n\t};\n}\n\n/**\n * Validate README title against package name.\n *\n * @returns `true` if valid, or an error message string if invalid\n */\nfunction validateTitle(\n\treadmeContent: string,\n\tpackageName: string,\n): true | string {\n\tconst title = extractReadmeTitle(readmeContent);\n\tif (title !== packageName) {\n\t\treturn `README title \"${title ?? \"(none)\"}\" doesn't match package name \"${packageName}\"`;\n\t}\n\treturn true;\n}\n\n/**\n * Handle required content validation and auto-fix.\n */\nasync function handleRequiredContent(\n\treadmePath: string,\n\treadmeContent: string,\n\trequiredContent: string,\n\tresolve: boolean,\n): Promise<{ error?: string; fixed: boolean }> {\n\tif (readmeContent.includes(requiredContent)) {\n\t\treturn { fixed: false };\n\t}\n\n\tif (resolve) {\n\t\ttry {\n\t\t\tconst newContent = readmeContent.endsWith(\"\\n\")\n\t\t\t\t? `${readmeContent}${requiredContent}\\n`\n\t\t\t\t: `${readmeContent}\\n${requiredContent}\\n`;\n\t\t\tawait writeFile(readmePath, newContent);\n\t\t\treturn {\n\t\t\t\terror: `README missing required content: \"${requiredContent}\"`,\n\t\t\t\tfixed: true,\n\t\t\t};\n\t\t} catch {\n\t\t\t// Fall through to report error\n\t\t}\n\t}\n\n\treturn {\n\t\terror: `README missing required content: \"${requiredContent}\"`,\n\t\tfixed: false,\n\t};\n}\n\ninterface ValidationContext {\n\treadmePath: string;\n\treadmeContent: string;\n\tpackageName: string;\n\tfile: string;\n\tresolve: boolean;\n\trequireMatchingTitle: boolean;\n\trequiredContent: string | undefined;\n}\n\n/**\n * Validate existing README content.\n */\nasync function validateExistingReadme(\n\tctx: ValidationContext,\n): Promise<true | PolicyFailure | PolicyFixResult> {\n\tconst errors: string[] = [];\n\tlet wasFixed = false;\n\n\t// Check title matches package name\n\tif (ctx.requireMatchingTitle) {\n\t\tconst titleResult = validateTitle(ctx.readmeContent, ctx.packageName);\n\t\tif (titleResult !== true) {\n\t\t\terrors.push(titleResult);\n\t\t}\n\t}\n\n\t// Check required content\n\tif (ctx.requiredContent) {\n\t\tconst contentResult = await handleRequiredContent(\n\t\t\tctx.readmePath,\n\t\t\tctx.readmeContent,\n\t\t\tctx.requiredContent,\n\t\t\tctx.resolve,\n\t\t);\n\t\tif (contentResult.error) {\n\t\t\terrors.push(contentResult.error);\n\t\t}\n\t\tif (contentResult.fixed) {\n\t\t\twasFixed = true;\n\t\t}\n\t}\n\n\tif (errors.length === 0) {\n\t\treturn true;\n\t}\n\n\tif (wasFixed) {\n\t\treturn {\n\t\t\tname: \"PackageReadme\",\n\t\t\tfile: ctx.file,\n\t\t\tresolved: true,\n\t\t\tautoFixable: true,\n\t\t\terrorMessages: [errors.join(\"; \")],\n\t\t};\n\t}\n\n\treturn {\n\t\tname: \"PackageReadme\",\n\t\tfile: ctx.file,\n\t\tautoFixable:\n\t\t\tctx.requiredContent !== undefined &&\n\t\t\t!ctx.readmeContent.includes(ctx.requiredContent),\n\t\terrorMessages: [errors.join(\"; \")],\n\t};\n}\n\n/**\n * A repo policy that ensures each package has a README.md file with proper content.\n *\n * @remarks\n *\n * This policy validates that packages have documentation and can enforce:\n * 1. README.md file exists\n * 2. README title matches the package name\n * 3. README contains required content (e.g., trademark notices)\n *\n * The policy supports auto-fixing by creating a basic README with the package name as title.\n *\n * @example\n * ```typescript\n * import { makePolicy } from \"repopo\";\n * import { PackageReadme } from \"repopo/policies\";\n *\n * const config: RepopoConfig = {\n * policies: [\n * // Basic README validation\n * makePolicy(PackageReadme),\n *\n * // Require trademark notice in all READMEs\n * makePolicy(PackageReadme, {\n * requiredContent: \"## Trademark\",\n * }),\n * ],\n * };\n * ```\n *\n * @alpha\n */\nexport const PackageReadme = definePackagePolicy<\n\tPackageJson,\n\tPackageReadmeSettings | undefined\n>({\n\tname: \"PackageReadme\",\n\tdescription:\n\t\t\"Ensures each package has a README.md file with proper title and required content.\",\n\thandler: async (json, { file, root, resolve, config }) => {\n\t\tconst skipPrivate = config?.skipPrivate ?? true;\n\t\tconst requireMatchingTitle = config?.requireMatchingTitle ?? true;\n\t\tconst requiredContent = config?.requiredContent;\n\n\t\t// Skip private packages if configured to do so\n\t\tif (skipPrivate && json.private === true) {\n\t\t\treturn true;\n\t\t}\n\n\t\tconst packageName = json.name ?? \"unknown\";\n\t\tconst packageDir = path.dirname(path.resolve(root, file));\n\t\tconst readmePath = path.join(packageDir, \"README.md\");\n\n\t\t// Check if README exists\n\t\tif (!existsSync(readmePath)) {\n\t\t\treturn await handleMissingReadme(\n\t\t\t\treadmePath,\n\t\t\t\tpackageName,\n\t\t\t\tfile,\n\t\t\t\tresolve,\n\t\t\t\trequiredContent,\n\t\t\t);\n\t\t}\n\n\t\t// README exists, validate content\n\t\tconst readmeContent = await readFile(readmePath, \"utf-8\");\n\t\treturn await validateExistingReadme({\n\t\t\treadmePath,\n\t\t\treadmeContent,\n\t\t\tpackageName,\n\t\t\tfile,\n\t\t\tresolve,\n\t\t\trequireMatchingTitle,\n\t\t\trequiredContent,\n\t\t});\n\t},\n});\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PackageScripts.d.ts","sourceRoot":"","sources":["../../src/policies/PackageScripts.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"PackageScripts.d.ts","sourceRoot":"","sources":["../../src/policies/PackageScripts.ts"],"names":[],"mappings":"AAKA;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACrC;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,WAAW,EAAE,MAAM,EAAE,CAAC;CACtB;AAED;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACrC;;;OAGG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;;;;;;;;;;;;;OAcG;IACH,QAAQ,EAAE,mBAAmB,EAAE,CAAC;CAChC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAElE;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACtC;;;;;;;;;;;;;;;;;;OAkBG;IACH,IAAI,CAAC,EAAE,mBAAmB,EAAE,CAAC;IAE7B;;;;;;;;;;;;;;;;OAgBG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAE/B;;;;;;;;;;;;OAYG;IACH,iBAAiB,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC;IAE/B;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,mBAAmB,CAAC,EAAE,qBAAqB,EAAE,CAAC;IAE9C;;;;;;;;;;;;;;;;;;OAkBG;IACH,iBAAiB,CAAC,EAAE,qBAAqB,EAAE,CAAC;CAC5C;AAseD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,eAAO,MAAM,cAAc,wEAgEzB,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 = "PackageScripts";
|
|
4
5
|
/**
|
|
@@ -344,7 +345,7 @@ function runAllValidations(config, scripts, defaultScripts) {
|
|
|
344
345
|
export const PackageScripts = definePackagePolicy({
|
|
345
346
|
name: "PackageScripts",
|
|
346
347
|
description: "Validates package.json scripts based on configurable rules including required scripts, exact content, and conditional requirements.",
|
|
347
|
-
handler: async (json, { file, resolve, config }) => {
|
|
348
|
+
handler: async (json, { file, root, resolve, config }) => {
|
|
348
349
|
if (config === undefined) {
|
|
349
350
|
return true;
|
|
350
351
|
}
|
|
@@ -358,7 +359,7 @@ export const PackageScripts = definePackagePolicy({
|
|
|
358
359
|
if (resolve && hasAutoFixableErrors) {
|
|
359
360
|
const { updatedScripts, fixedScripts } = applyScriptFixes(scripts, missingWithDefaults, mismatchedScripts, defaultScripts);
|
|
360
361
|
try {
|
|
361
|
-
await jsonfile.writeFile(file, { ...json, scripts: updatedScripts }, { spaces: 2 });
|
|
362
|
+
await jsonfile.writeFile(resolvePath(root, file), { ...json, scripts: updatedScripts }, { spaces: 2 });
|
|
362
363
|
const postFixErrors = revalidateAfterFix(config, updatedScripts);
|
|
363
364
|
return {
|
|
364
365
|
name: POLICY_NAME,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PackageScripts.js","sourceRoot":"","sources":["../../src/policies/PackageScripts.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,UAAU,CAAC;AAEhC,OAAO,EAAE,mBAAmB,EAAE,MAAM,0CAA0C,CAAC;AA8K/E,MAAM,WAAW,GAAG,gBAAgB,CAAC;AAErC;;GAEG;AACH,SAAS,kBAAkB,CAAC,KAA0B;IAIrD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC/B,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IACxB,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAChC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CACd,sEAAsE,IAAI,CAAC,MAAM,OAAO,CACxF,CAAC;IACH,CAAC;IACD,sEAAsE;IACtE,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAE,CAAC;IACtB,MAAM,YAAY,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;IACjC,yEAAyE;IACzE,OAAO,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;AACvE,CAAC;AAED;;GAEG;AACH,SAAS,0BAA0B,CAClC,OAA8B,EAC9B,QAAgC;IAEhC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;QACzD,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;YAChC,QAAQ,CAAC,IAAI,CAAC,GAAG,YAAY,CAAC;QAC/B,CAAC;IACF,CAAC;AACF,CAAC;AAED;;GAEG;AACH,SAAS,gBAAgB,CACxB,MAA8B;IAE9B,MAAM,QAAQ,GAA2B,EAAE,CAAC;IAE5C,iCAAiC;IACjC,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;QACjB,0BAA0B,CAAC,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IACnD,CAAC;IAED,gDAAgD;IAChD,IAAI,MAAM,CAAC,mBAAmB,EAAE,CAAC;QAChC,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,mBAAmB,EAAE,CAAC;YAC/C,0BAA0B,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QACrD,CAAC;IACF,CAAC;IAED,sFAAsF;IACtF,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QAClB,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;IACvC,CAAC;IAED,OAAO,QAAQ,CAAC;AACjB,CAAC;AAED;;GAEG;AACH,SAAS,gCAAgC,CACxC,MAAkB,EAClB,OAA+B;IAE/B,MAAM,MAAM,GAAa,EAAE,CAAC;IAE5B,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC5B,MAAM,cAAc,GAAG,KAAK,CAAC,MAAM,CAClC,CAAC,UAAU,EAAE,EAAE,CAAC,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,CAC7D,CAAC;QAEF,8DAA8D;QAC9D,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC/B,MAAM,CAAC,IAAI,CACV,6CAA6C,cAAc,CAAC,MAAM,KAAK,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAClG,CAAC;QACH,CAAC;IACF,CAAC;IAED,OAAO,MAAM,CAAC;AACf,CAAC;AAED;;GAEG;AACH,SAAS,+BAA+B,CACvC,KAA8B,EAC9B,OAA+B;IAE/B,MAAM,MAAM,GAAa,EAAE,CAAC;IAE5B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QAC1B,wCAAwC;QACxC,IAAI,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;YACvD,gCAAgC;YAChC,MAAM,eAAe,GAAa,EAAE,CAAC;YACrC,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACnC,MAAM,EAAE,IAAI,EAAE,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;gBAC3C,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC;oBACnC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC5B,CAAC;YACF,CAAC;YAED,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAChC,MAAM,CAAC,IAAI,CACV,WAAW,IAAI,CAAC,SAAS,2DAA2D,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAChH,CAAC;YACH,CAAC;QACF,CAAC;IACF,CAAC;IAED,OAAO,MAAM,CAAC;AACf,CAAC;AAED;;GAEG;AACH,SAAS,sBAAsB,CAC9B,KAA8B,EAC9B,OAA+B;IAE/B,MAAM,MAAM,GAAa,EAAE,CAAC;IAE5B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QAC1B,qCAAqC;QACrC,IAAI,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YACpD,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACxC,IAAI,OAAO,UAAU,KAAK,QAAQ,EAAE,CAAC;gBACpC,MAAM,iBAAiB,GAAG,IAAI,CAAC,WAAW,CAAC,MAAM,CAChD,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,SAAS,CAAC,CAC9C,CAAC;gBAEF,IAAI,iBAAiB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBAClC,MAAM,CAAC,IAAI,CACV,WAAW,IAAI,CAAC,MAAM,mBAAmB,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACvE,CAAC;gBACH,CAAC;YACF,CAAC;QACF,CAAC;IACF,CAAC;IAED,OAAO,MAAM,CAAC;AACf,CAAC;AAUD;;GAEG;AACH,SAAS,mBAAmB,CAC3B,IAA2B,EAC3B,OAA+B;IAE/B,MAAM,cAAc,GAAa,EAAE,CAAC;IACpC,MAAM,mBAAmB,GAAa,EAAE,CAAC;IAEzC,KAAK,MAAM,KAAK,IAAI,IAAI,EAAE,CAAC;QAC1B,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;QACzD,IAAI,CAAC,CAAC,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC;YAChD,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC1B,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;gBAChC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAChC,CAAC;QACF,CAAC;IACF,CAAC;IAED,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC/B,MAAM,CAAC,IAAI,CAAC,gCAAgC,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC5E,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC;AACxC,CAAC;AAWD;;GAEG;AACH,SAAS,oBAAoB,CAC5B,KAA6B,EAC7B,OAA+B;IAE/B,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,mBAAmB,GAAa,EAAE,CAAC;IACzC,MAAM,iBAAiB,GAAa,EAAE,CAAC;IAEvC,KAAK,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACjE,IAAI,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,CAAC;YACnD,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,mBAAmB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YACrC,MAAM,CAAC,IAAI,CAAC,4BAA4B,UAAU,EAAE,CAAC,CAAC;QACvD,CAAC;IACF,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,CAAC;AAC3D,CAAC;AAUD;;GAEG;AACH,SAAS,mBAAmB,CAC3B,IAA2B,EAC3B,OAA+B;IAE/B,OAAO,IAAI,CAAC,QAAQ;SAClB,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC;SAC9C,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;AAChE,CAAC;AAED;;GAEG;AACH,SAAS,+BAA+B,CACvC,KAA8B,EAC9B,OAA+B,EAC/B,cAAsC,EACtC,2BAAqC;IAErC,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,mBAAmB,GAAa,EAAE,CAAC;IACzC,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,2BAA2B,CAAC,CAAC;IAExD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QAC1B,IAAI,CAAC,CAAC,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC;YAC1D,SAAS;QACV,CAAC;QAED,MAAM,eAAe,GAAG,mBAAmB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAE3D,KAAK,MAAM,UAAU,IAAI,eAAe,EAAE,CAAC;YAC1C,IAAI,UAAU,IAAI,cAAc,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;gBACjE,mBAAmB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;gBACrC,UAAU,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YAC5B,CAAC;QACF,CAAC;QAED,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAChC,MAAM,CAAC,IAAI,CACV,WAAW,IAAI,CAAC,SAAS,2DAA2D,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAChH,CAAC;QACH,CAAC;IACF,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC;AACxC,CAAC;AAED;;GAEG;AACH,SAAS,gBAAgB,CACxB,OAA+B,EAC/B,0BAAoC,EACpC,iBAA2B,EAC3B,cAAsC;IAEtC,MAAM,cAAc,GAA2B,EAAE,CAAC;IAElD,wDAAwD;IACxD,IAAI,OAAO,EAAE,CAAC;QACb,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;YACpD,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBACzB,cAAc,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;YAC7B,CAAC;QACF,CAAC;IACF,CAAC;IAED,MAAM,YAAY,GAAa,EAAE,CAAC;IAElC,KAAK,MAAM,UAAU,IAAI,0BAA0B,EAAE,CAAC;QACrD,MAAM,YAAY,GAAG,cAAc,CAAC,UAAU,CAAC,CAAC;QAChD,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;YAChC,cAAc,CAAC,UAAU,CAAC,GAAG,YAAY,CAAC;YAC1C,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC/B,CAAC;IACF,CAAC;IAED,KAAK,MAAM,UAAU,IAAI,iBAAiB,EAAE,CAAC;QAC5C,MAAM,YAAY,GAAG,cAAc,CAAC,UAAU,CAAC,CAAC;QAChD,IAAI,YAAY,KAAK,SAAS,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;YACtE,cAAc,CAAC,UAAU,CAAC,GAAG,YAAY,CAAC;YAC1C,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC/B,CAAC;IACF,CAAC;IAED,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,CAAC;AACzC,CAAC;AAED;;GAEG;AACH,SAAS,4BAA4B,CACpC,IAA2B,EAC3B,OAA+B;IAE/B,MAAM,YAAY,GAAG,IAAI;SACvB,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC;SAC9C,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;IAElD,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7B,OAAO,CAAC,gCAAgC,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACtE,CAAC;IACD,OAAO,EAAE,CAAC;AACX,CAAC;AAED;;GAEG;AACH,SAAS,6BAA6B,CACrC,KAA6B,EAC7B,OAA+B;IAE/B,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,KAAK,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACjE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,CAAC;YACzC,MAAM,CAAC,IAAI,CAAC,4BAA4B,UAAU,EAAE,CAAC,CAAC;QACvD,CAAC;aAAM,IAAI,OAAO,CAAC,UAAU,CAAC,KAAK,aAAa,EAAE,CAAC;YAClD,MAAM,CAAC,IAAI,CACV,WAAW,UAAU,cAAc,aAAa,iBAAiB,OAAO,CAAC,UAAU,CAAC,GAAG,CACvF,CAAC;QACH,CAAC;IACF,CAAC;IACD,OAAO,MAAM,CAAC;AACf,CAAC;AAED;;GAEG;AACH,SAAS,kBAAkB,CAC1B,MAA8B,EAC9B,cAAsC;IAEtC,MAAM,MAAM,GAAa,EAAE,CAAC;IAE5B,IACC,MAAM,CAAC,iBAAiB,KAAK,SAAS;QACtC,MAAM,CAAC,iBAAiB,CAAC,MAAM,GAAG,CAAC,EAClC,CAAC;QACF,MAAM,CAAC,IAAI,CACV,GAAG,gCAAgC,CAClC,MAAM,CAAC,iBAAiB,EACxB,cAAc,CACd,CACD,CAAC;IACH,CAAC;IAED,IACC,MAAM,CAAC,iBAAiB,KAAK,SAAS;QACtC,MAAM,CAAC,iBAAiB,CAAC,MAAM,GAAG,CAAC,EAClC,CAAC;QACF,MAAM,CAAC,IAAI,CACV,GAAG,sBAAsB,CAAC,MAAM,CAAC,iBAAiB,EAAE,cAAc,CAAC,CACnE,CAAC;IACH,CAAC;IAED,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzD,MAAM,CAAC,IAAI,CAAC,GAAG,4BAA4B,CAAC,MAAM,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC;IAC3E,CAAC;IAED,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxE,MAAM,CAAC,IAAI,CAAC,GAAG,6BAA6B,CAAC,MAAM,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC;IAC7E,CAAC;IAED,IACC,MAAM,CAAC,mBAAmB,KAAK,SAAS;QACxC,MAAM,CAAC,mBAAmB,CAAC,MAAM,GAAG,CAAC,EACpC,CAAC;QACF,MAAM,CAAC,IAAI,CACV,GAAG,+BAA+B,CACjC,MAAM,CAAC,mBAAmB,EAC1B,cAAc,CACd,CACD,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AACf,CAAC;AAWD;;GAEG;AACH,SAAS,iBAAiB,CACzB,MAA8B,EAC9B,OAA+B,EAC/B,cAAsC;IAEtC,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,mBAAmB,GAAa,EAAE,CAAC;IACzC,IAAI,iBAAiB,GAAa,EAAE,CAAC;IAErC,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzD,MAAM,MAAM,GAAG,mBAAmB,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACzD,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;QAC9B,mBAAmB,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,mBAAmB,CAAC,CAAC;IACzD,CAAC;IAED,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxE,MAAM,MAAM,GAAG,oBAAoB,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QAC3D,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;QAC9B,mBAAmB,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,mBAAmB,CAAC,CAAC;QACxD,iBAAiB,GAAG,MAAM,CAAC,iBAAiB,CAAC;IAC9C,CAAC;IAED,IACC,MAAM,CAAC,iBAAiB,KAAK,SAAS;QACtC,MAAM,CAAC,iBAAiB,CAAC,MAAM,GAAG,CAAC,EAClC,CAAC;QACF,MAAM,CAAC,IAAI,CACV,GAAG,gCAAgC,CAAC,MAAM,CAAC,iBAAiB,EAAE,OAAO,CAAC,CACtE,CAAC;IACH,CAAC;IAED,IACC,MAAM,CAAC,mBAAmB,KAAK,SAAS;QACxC,MAAM,CAAC,mBAAmB,CAAC,MAAM,GAAG,CAAC,EACpC,CAAC;QACF,MAAM,MAAM,GAAG,+BAA+B,CAC7C,MAAM,CAAC,mBAAmB,EAC1B,OAAO,EACP,cAAc,EACd,mBAAmB,CACnB,CAAC;QACF,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;QAC9B,mBAAmB,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,mBAAmB,CAAC,CAAC;IACzD,CAAC;IAED,IACC,MAAM,CAAC,iBAAiB,KAAK,SAAS;QACtC,MAAM,CAAC,iBAAiB,CAAC,MAAM,GAAG,CAAC,EAClC,CAAC;QACF,MAAM,CAAC,IAAI,CAAC,GAAG,sBAAsB,CAAC,MAAM,CAAC,iBAAiB,EAAE,OAAO,CAAC,CAAC,CAAC;IAC3E,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,CAAC;AAC3D,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,mBAAmB,CAG/C;IACD,IAAI,EAAE,gBAAgB;IACtB,WAAW,EACV,qIAAqI;IACtI,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE;QAClD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YAC1B,OAAO,IAAI,CAAC;QACb,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;QACnE,MAAM,cAAc,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;QAEhD,MAAM,EAAE,MAAM,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,GACvD,iBAAiB,CAAC,MAAM,EAAE,OAAO,EAAE,cAAc,CAAC,CAAC;QAEpD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzB,OAAO,IAAI,CAAC;QACb,CAAC;QAED,MAAM,oBAAoB,GACzB,mBAAmB,CAAC,MAAM,GAAG,CAAC,IAAI,iBAAiB,CAAC,MAAM,GAAG,CAAC,CAAC;QAEhE,IAAI,OAAO,IAAI,oBAAoB,EAAE,CAAC;YACrC,MAAM,EAAE,cAAc,EAAE,YAAY,EAAE,GAAG,gBAAgB,CACxD,OAAO,EACP,mBAAmB,EACnB,iBAAiB,EACjB,cAAc,CACd,CAAC;YAEF,IAAI,CAAC;gBACJ,MAAM,QAAQ,CAAC,SAAS,CACvB,IAAI,EACJ,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,cAAc,EAAE,EACpC,EAAE,MAAM,EAAE,CAAC,EAAE,CACb,CAAC;gBACF,MAAM,aAAa,GAAG,kBAAkB,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;gBAEjE,OAAO;oBACN,IAAI,EAAE,WAAW;oBACjB,IAAI;oBACJ,QAAQ,EAAE,aAAa,CAAC,MAAM,KAAK,CAAC;oBACpC,aAAa,EACZ,aAAa,CAAC,MAAM,GAAG,CAAC;wBACvB,CAAC,CAAC;4BACA,kBAAkB,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,wBAAwB,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE;yBAC7F;wBACF,CAAC,CAAC,CAAC,kBAAkB,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;iBACjD,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,MAAM;SACrB,CAAC;IACH,CAAC;CACD,CAAC,CAAC","sourcesContent":["import jsonfile from \"jsonfile\";\nimport 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 ScriptMustContainRule {\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 conditional script requirements.\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 *\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: RequiredScriptEntry[];\n}\n\n/**\n * A required script entry. 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)\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 * { lint: \"biome lint .\" }\n * ```\n *\n * @alpha\n */\nexport type RequiredScriptEntry = string | Record<string, string>;\n\n/**\n * Policy settings for the PackageScripts repo policy.\n *\n * @alpha\n */\nexport interface PackageScriptsSettings {\n\t/**\n\t * Scripts that must be present in package.json.\n\t *\n\t * @remarks\n\t *\n\t * Each entry can be:\n\t * - A string: Script must exist, no auto-fix available, content not validated\n\t * - An object `{ scriptName: \"default value\" }`: Script must exist, auto-fix adds the\n\t * default value if missing, but existing scripts with different content still pass\n\t *\n\t * @example\n\t * ```typescript\n\t * must: [\n\t * \"test\", // Must exist, no auto-fix\n\t * { lint: \"biome lint .\" }, // Must exist, auto-fix available\n\t * { clean: \"rimraf dist esm\" }, // Must exist, auto-fix available\n\t * ]\n\t * ```\n\t */\n\tmust?: RequiredScriptEntry[];\n\n\t/**\n\t * Scripts that must exist AND have exact content.\n\t *\n\t * @remarks\n\t *\n\t * Unlike {@link PackageScriptsSettings.must}, 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 * exact: {\n\t * clean: \"rimraf dist esm\",\n\t * format: \"biome format --write .\",\n\t * }\n\t * ```\n\t */\n\texact?: Record<string, string>;\n\n\t/**\n\t * List of mutually exclusive script groups. Each group is an array of script names\n\t * where at most one script from the group can be present.\n\t *\n\t * Packages can have zero scripts from the group (all optional), but cannot have\n\t * multiple scripts from the same group.\n\t *\n\t * @example\n\t * ```typescript\n\t * // Allow \"test:unit\" OR \"test:vitest\" OR neither, but not both\n\t * mutuallyExclusive: [[\"test:unit\", \"test:vitest\"]]\n\t * ```\n\t */\n\tmutuallyExclusive?: string[][];\n\n\t/**\n\t * Conditional script requirements. If a script is present, other scripts must also exist.\n\t *\n\t * @remarks\n\t *\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 * The `requires` field accepts the same format as {@link PackageScriptsSettings.must}:\n\t * - Strings: Script must exist, no auto-fix available\n\t * - Objects `{ scriptName: \"default\" }`: Script must exist, auto-fix adds the default if missing\n\t *\n\t * @example\n\t * ```typescript\n\t * // If \"build\" exists, \"clean\" must also exist (with auto-fix default)\n\t * conditionalRequired: [{ ifPresent: \"build\", requires: [{ clean: \"rimraf dist\" }] }]\n\t *\n\t * // Mixed: \"test\" required without default, \"clean\" with default\n\t * conditionalRequired: [{ ifPresent: \"build\", requires: [\"test\", { clean: \"rimraf dist\" }] }]\n\t * ```\n\t */\n\tconditionalRequired?: ConditionalScriptRule[];\n\n\t/**\n\t * Rules for validating script body content.\n\t *\n\t * @remarks\n\t *\n\t * This allows you to ensure that specific scripts contain required commands or patterns\n\t * without requiring an exact match. For example, requiring that the \"clean\" script uses\n\t * \"rimraf\" or that \"build\" includes \"tsc\".\n\t *\n\t * For exact content matching, use {@link PackageScriptsSettings.exact} instead.\n\t *\n\t * @example\n\t * ```typescript\n\t * scriptMustContain: [\n\t * { script: \"clean\", mustContain: [\"rimraf\"] },\n\t * { script: \"build\", mustContain: [\"tsc\"] },\n\t * ]\n\t * ```\n\t */\n\tscriptMustContain?: ScriptMustContainRule[];\n}\n\nconst POLICY_NAME = \"PackageScripts\";\n\n/**\n * Extracts script name and optional default value from a RequiredScriptEntry.\n */\nfunction parseRequiredEntry(entry: RequiredScriptEntry): {\n\tname: string;\n\tdefaultValue?: string;\n} {\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 required 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\t// Only include defaultValue if it's defined (exactOptionalPropertyTypes)\n\treturn defaultValue !== undefined ? { name, defaultValue } : { name };\n}\n\n/**\n * Extract defaults from RequiredScriptEntry array.\n */\nfunction extractDefaultsFromEntries(\n\tentries: RequiredScriptEntry[],\n\tdefaults: Record<string, string>,\n): void {\n\tfor (const entry of entries) {\n\t\tconst { name, defaultValue } = parseRequiredEntry(entry);\n\t\tif (defaultValue !== undefined) {\n\t\t\tdefaults[name] = defaultValue;\n\t\t}\n\t}\n}\n\n/**\n * Builds a map of script names to their default values from must, conditionalRequired, and exact configs.\n */\nfunction buildDefaultsMap(\n\tconfig: PackageScriptsSettings,\n): Record<string, string> {\n\tconst defaults: Record<string, string> = {};\n\n\t// Add defaults from must entries\n\tif (config.must) {\n\t\textractDefaultsFromEntries(config.must, defaults);\n\t}\n\n\t// Add defaults from conditionalRequired entries\n\tif (config.conditionalRequired) {\n\t\tfor (const rule of config.conditionalRequired) {\n\t\t\textractDefaultsFromEntries(rule.requires, defaults);\n\t\t}\n\t}\n\n\t// Add defaults from exact (these override must/conditionalRequired if both specified)\n\tif (config.exact) {\n\t\tObject.assign(defaults, config.exact);\n\t}\n\n\treturn defaults;\n}\n\n/**\n * Validates mutually exclusive script groups and returns error messages for violations.\n */\nfunction validateMutuallyExclusiveScripts(\n\tgroups: string[][],\n\tscripts: PackageJson[\"scripts\"],\n): string[] {\n\tconst errors: string[] = [];\n\n\tfor (const group of groups) {\n\t\tconst presentScripts = group.filter(\n\t\t\t(scriptName) => scripts && Object.hasOwn(scripts, scriptName),\n\t\t);\n\n\t\t// Only fail if MORE than one script from the group is present\n\t\tif (presentScripts.length > 1) {\n\t\t\terrors.push(\n\t\t\t\t`Scripts are mutually exclusive, but found ${presentScripts.length}: ${presentScripts.join(\", \")}`,\n\t\t\t);\n\t\t}\n\t}\n\n\treturn errors;\n}\n\n/**\n * Validates conditional script requirements (if X exists, Y must also exist).\n */\nfunction validateConditionalRequirements(\n\trules: ConditionalScriptRule[],\n\tscripts: PackageJson[\"scripts\"],\n): string[] {\n\tconst errors: string[] = [];\n\n\tfor (const rule of rules) {\n\t\t// Check if the triggering script exists\n\t\tif (scripts && Object.hasOwn(scripts, rule.ifPresent)) {\n\t\t\t// Find missing required scripts\n\t\t\tconst missingRequired: string[] = [];\n\t\t\tfor (const entry of rule.requires) {\n\t\t\t\tconst { name } = parseRequiredEntry(entry);\n\t\t\t\tif (!Object.hasOwn(scripts, name)) {\n\t\t\t\t\tmissingRequired.push(name);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (missingRequired.length > 0) {\n\t\t\t\terrors.push(\n\t\t\t\t\t`Script \"${rule.ifPresent}\" is present, but required companion script(s) missing: ${missingRequired.join(\", \")}`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn errors;\n}\n\n/**\n * Validates that script bodies contain required substrings.\n */\nfunction validateScriptContents(\n\trules: ScriptMustContainRule[],\n\tscripts: PackageJson[\"scripts\"],\n): string[] {\n\tconst errors: string[] = [];\n\n\tfor (const rule of rules) {\n\t\t// Only validate if the script exists\n\t\tif (scripts && Object.hasOwn(scripts, rule.script)) {\n\t\t\tconst scriptBody = scripts[rule.script];\n\t\t\tif (typeof scriptBody === \"string\") {\n\t\t\t\tconst missingSubstrings = rule.mustContain.filter(\n\t\t\t\t\t(substring) => !scriptBody.includes(substring),\n\t\t\t\t);\n\n\t\t\t\tif (missingSubstrings.length > 0) {\n\t\t\t\t\terrors.push(\n\t\t\t\t\t\t`Script \"${rule.script}\" must contain: ${missingSubstrings.join(\", \")}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn errors;\n}\n\n/**\n * Result of validating required scripts.\n */\ninterface MustScriptsResult {\n\terrors: string[];\n\tmissingWithDefaults: string[];\n}\n\n/**\n * Validates required scripts from 'must' config.\n */\nfunction validateMustScripts(\n\tmust: RequiredScriptEntry[],\n\tscripts: PackageJson[\"scripts\"],\n): MustScriptsResult {\n\tconst missingScripts: string[] = [];\n\tconst missingWithDefaults: string[] = [];\n\n\tfor (const entry of must) {\n\t\tconst { name, defaultValue } = parseRequiredEntry(entry);\n\t\tif (!(scripts && Object.hasOwn(scripts, name))) {\n\t\t\tmissingScripts.push(name);\n\t\t\tif (defaultValue !== undefined) {\n\t\t\t\tmissingWithDefaults.push(name);\n\t\t\t}\n\t\t}\n\t}\n\n\tconst errors: string[] = [];\n\tif (missingScripts.length > 0) {\n\t\terrors.push(`Missing required scripts:\\n\\t${missingScripts.join(\"\\n\\t\")}`);\n\t}\n\n\treturn { errors, missingWithDefaults };\n}\n\n/**\n * Result of validating exact scripts.\n */\ninterface ExactScriptsResult {\n\terrors: string[];\n\tmissingWithDefaults: string[];\n\tmismatchedScripts: string[];\n}\n\n/**\n * Validates scripts from 'exact' config - must exist AND match exactly.\n */\nfunction validateExactScripts(\n\texact: Record<string, string>,\n\tscripts: PackageJson[\"scripts\"],\n): ExactScriptsResult {\n\tconst errors: string[] = [];\n\tconst missingWithDefaults: string[] = [];\n\tconst mismatchedScripts: string[] = [];\n\n\tfor (const [scriptName, expectedValue] of Object.entries(exact)) {\n\t\tif (scripts && 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\tmissingWithDefaults.push(scriptName);\n\t\t\terrors.push(`Missing required script: ${scriptName}`);\n\t\t}\n\t}\n\n\treturn { errors, missingWithDefaults, mismatchedScripts };\n}\n\n/**\n * Result of validating conditional requirements.\n */\ninterface ConditionalResult {\n\terrors: string[];\n\tmissingWithDefaults: string[];\n}\n\n/**\n * Find missing scripts from a conditional rule's requirements.\n */\nfunction findMissingFromRule(\n\trule: ConditionalScriptRule,\n\tscripts: PackageJson[\"scripts\"],\n): string[] {\n\treturn rule.requires\n\t\t.map((entry) => parseRequiredEntry(entry).name)\n\t\t.filter((name) => !(scripts && Object.hasOwn(scripts, name)));\n}\n\n/**\n * Validates conditional requirements and tracks missing scripts with defaults.\n */\nfunction validateConditionalWithDefaults(\n\trules: ConditionalScriptRule[],\n\tscripts: PackageJson[\"scripts\"],\n\tdefaultScripts: Record<string, string>,\n\texistingMissingWithDefaults: string[],\n): ConditionalResult {\n\tconst errors: string[] = [];\n\tconst missingWithDefaults: string[] = [];\n\tconst allTracked = new Set(existingMissingWithDefaults);\n\n\tfor (const rule of rules) {\n\t\tif (!(scripts && Object.hasOwn(scripts, rule.ifPresent))) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst missingRequired = findMissingFromRule(rule, scripts);\n\n\t\tfor (const scriptName of missingRequired) {\n\t\t\tif (scriptName in defaultScripts && !allTracked.has(scriptName)) {\n\t\t\t\tmissingWithDefaults.push(scriptName);\n\t\t\t\tallTracked.add(scriptName);\n\t\t\t}\n\t\t}\n\n\t\tif (missingRequired.length > 0) {\n\t\t\terrors.push(\n\t\t\t\t`Script \"${rule.ifPresent}\" is present, but required companion script(s) missing: ${missingRequired.join(\", \")}`,\n\t\t\t);\n\t\t}\n\t}\n\n\treturn { errors, missingWithDefaults };\n}\n\n/**\n * Apply fixes to scripts and return the updated scripts object.\n */\nfunction applyScriptFixes(\n\tscripts: PackageJson[\"scripts\"],\n\tmissingScriptsWithDefaults: string[],\n\tmismatchedScripts: string[],\n\tdefaultScripts: Record<string, string>,\n): { updatedScripts: Record<string, string>; fixedScripts: string[] } {\n\tconst updatedScripts: Record<string, string> = {};\n\n\t// Copy existing scripts, filtering out undefined values\n\tif (scripts) {\n\t\tfor (const [key, value] of Object.entries(scripts)) {\n\t\t\tif (value !== undefined) {\n\t\t\t\tupdatedScripts[key] = value;\n\t\t\t}\n\t\t}\n\t}\n\n\tconst fixedScripts: string[] = [];\n\n\tfor (const scriptName of missingScriptsWithDefaults) {\n\t\tconst defaultValue = defaultScripts[scriptName];\n\t\tif (defaultValue !== undefined) {\n\t\t\tupdatedScripts[scriptName] = defaultValue;\n\t\t\tfixedScripts.push(scriptName);\n\t\t}\n\t}\n\n\tfor (const scriptName of mismatchedScripts) {\n\t\tconst defaultValue = defaultScripts[scriptName];\n\t\tif (defaultValue !== undefined && !fixedScripts.includes(scriptName)) {\n\t\t\tupdatedScripts[scriptName] = defaultValue;\n\t\t\tfixedScripts.push(scriptName);\n\t\t}\n\t}\n\n\treturn { updatedScripts, fixedScripts };\n}\n\n/**\n * Validate 'must' scripts and return any remaining missing scripts.\n */\nfunction validateRemainingMustScripts(\n\tmust: RequiredScriptEntry[],\n\tscripts: Record<string, string>,\n): string[] {\n\tconst stillMissing = must\n\t\t.map((entry) => parseRequiredEntry(entry).name)\n\t\t.filter((name) => !Object.hasOwn(scripts, name));\n\n\tif (stillMissing.length > 0) {\n\t\treturn [`Missing required scripts:\\n\\t${stillMissing.join(\"\\n\\t\")}`];\n\t}\n\treturn [];\n}\n\n/**\n * Validate 'exact' scripts and return any errors.\n */\nfunction validateRemainingExactScripts(\n\texact: Record<string, string>,\n\tscripts: Record<string, string>,\n): string[] {\n\tconst errors: string[] = [];\n\tfor (const [scriptName, expectedValue] of Object.entries(exact)) {\n\t\tif (!Object.hasOwn(scripts, scriptName)) {\n\t\t\terrors.push(`Missing required script: ${scriptName}`);\n\t\t} else if (scripts[scriptName] !== expectedValue) {\n\t\t\terrors.push(\n\t\t\t\t`Script \"${scriptName}\" must be \"${expectedValue}\", but found \"${scripts[scriptName]}\"`,\n\t\t\t);\n\t\t}\n\t}\n\treturn errors;\n}\n\n/**\n * Re-validate all rules after applying fixes.\n */\nfunction revalidateAfterFix(\n\tconfig: PackageScriptsSettings,\n\tupdatedScripts: Record<string, string>,\n): string[] {\n\tconst errors: string[] = [];\n\n\tif (\n\t\tconfig.mutuallyExclusive !== undefined &&\n\t\tconfig.mutuallyExclusive.length > 0\n\t) {\n\t\terrors.push(\n\t\t\t...validateMutuallyExclusiveScripts(\n\t\t\t\tconfig.mutuallyExclusive,\n\t\t\t\tupdatedScripts,\n\t\t\t),\n\t\t);\n\t}\n\n\tif (\n\t\tconfig.scriptMustContain !== undefined &&\n\t\tconfig.scriptMustContain.length > 0\n\t) {\n\t\terrors.push(\n\t\t\t...validateScriptContents(config.scriptMustContain, updatedScripts),\n\t\t);\n\t}\n\n\tif (config.must !== undefined && config.must.length > 0) {\n\t\terrors.push(...validateRemainingMustScripts(config.must, updatedScripts));\n\t}\n\n\tif (config.exact !== undefined && Object.keys(config.exact).length > 0) {\n\t\terrors.push(...validateRemainingExactScripts(config.exact, updatedScripts));\n\t}\n\n\tif (\n\t\tconfig.conditionalRequired !== undefined &&\n\t\tconfig.conditionalRequired.length > 0\n\t) {\n\t\terrors.push(\n\t\t\t...validateConditionalRequirements(\n\t\t\t\tconfig.conditionalRequired,\n\t\t\t\tupdatedScripts,\n\t\t\t),\n\t\t);\n\t}\n\n\treturn errors;\n}\n\n/**\n * Result of running all script validations.\n */\ninterface AllValidationsResult {\n\terrors: string[];\n\tmissingWithDefaults: string[];\n\tmismatchedScripts: string[];\n}\n\n/**\n * Run all script validations and aggregate results.\n */\nfunction runAllValidations(\n\tconfig: PackageScriptsSettings,\n\tscripts: PackageJson[\"scripts\"],\n\tdefaultScripts: Record<string, string>,\n): AllValidationsResult {\n\tconst errors: string[] = [];\n\tconst missingWithDefaults: string[] = [];\n\tlet mismatchedScripts: string[] = [];\n\n\tif (config.must !== undefined && config.must.length > 0) {\n\t\tconst result = validateMustScripts(config.must, scripts);\n\t\terrors.push(...result.errors);\n\t\tmissingWithDefaults.push(...result.missingWithDefaults);\n\t}\n\n\tif (config.exact !== undefined && Object.keys(config.exact).length > 0) {\n\t\tconst result = validateExactScripts(config.exact, scripts);\n\t\terrors.push(...result.errors);\n\t\tmissingWithDefaults.push(...result.missingWithDefaults);\n\t\tmismatchedScripts = result.mismatchedScripts;\n\t}\n\n\tif (\n\t\tconfig.mutuallyExclusive !== undefined &&\n\t\tconfig.mutuallyExclusive.length > 0\n\t) {\n\t\terrors.push(\n\t\t\t...validateMutuallyExclusiveScripts(config.mutuallyExclusive, scripts),\n\t\t);\n\t}\n\n\tif (\n\t\tconfig.conditionalRequired !== undefined &&\n\t\tconfig.conditionalRequired.length > 0\n\t) {\n\t\tconst result = validateConditionalWithDefaults(\n\t\t\tconfig.conditionalRequired,\n\t\t\tscripts,\n\t\t\tdefaultScripts,\n\t\t\tmissingWithDefaults,\n\t\t);\n\t\terrors.push(...result.errors);\n\t\tmissingWithDefaults.push(...result.missingWithDefaults);\n\t}\n\n\tif (\n\t\tconfig.scriptMustContain !== undefined &&\n\t\tconfig.scriptMustContain.length > 0\n\t) {\n\t\terrors.push(...validateScriptContents(config.scriptMustContain, scripts));\n\t}\n\n\treturn { errors, missingWithDefaults, mismatchedScripts };\n}\n\n/**\n * A RepoPolicy that validates package.json scripts based on configurable rules.\n *\n * @remarks\n *\n * This policy provides comprehensive script validation including:\n * - Required scripts that must be present (with optional defaults for auto-fix)\n * - Exact script content enforcement\n * - Mutually exclusive script groups (at most one can exist)\n * - Conditional requirements (if X exists, Y must also exist)\n * - Script body content validation (script must contain specific substrings)\n *\n * @example\n * ```typescript\n * import { makePolicy } from \"repopo\";\n * import { PackageScripts } from \"repopo/policies\";\n *\n * const config: RepopoConfig = {\n * policies: [\n * makePolicy(PackageScripts, {\n * // Scripts that must exist\n * must: [\n * \"test\", // Must exist, no auto-fix\n * { build: \"tsc\" }, // Must exist, auto-fix available\n * { lint: \"biome lint .\" }, // Must exist, auto-fix available\n * ],\n * // Scripts that must exist AND match exactly\n * exact: {\n * clean: \"rimraf dist esm\",\n * format: \"biome format --write .\",\n * },\n * // If \"build\" exists, \"clean\" must also exist (with inline auto-fix default)\n * conditionalRequired: [{ ifPresent: \"build\", requires: [{ clean: \"rimraf dist\" }] }],\n * // Script body must contain these substrings\n * scriptMustContain: [{ script: \"build\", mustContain: [\"tsc\"] }],\n * // Only one of these can exist\n * mutuallyExclusive: [[\"lint:eslint\", \"lint:biome\"]],\n * }),\n * ],\n * };\n * ```\n *\n * @alpha\n */\nexport const PackageScripts = definePackagePolicy<\n\tPackageJson,\n\tPackageScriptsSettings | undefined\n>({\n\tname: \"PackageScripts\",\n\tdescription:\n\t\t\"Validates package.json scripts based on configurable rules including required scripts, exact content, and conditional requirements.\",\n\thandler: async (json, { file, resolve, config }) => {\n\t\tif (config === undefined) {\n\t\t\treturn true;\n\t\t}\n\n\t\tconst scripts = Object.hasOwn(json, \"scripts\") ? json.scripts : {};\n\t\tconst defaultScripts = buildDefaultsMap(config);\n\n\t\tconst { errors, missingWithDefaults, mismatchedScripts } =\n\t\t\trunAllValidations(config, scripts, defaultScripts);\n\n\t\tif (errors.length === 0) {\n\t\t\treturn true;\n\t\t}\n\n\t\tconst hasAutoFixableErrors =\n\t\t\tmissingWithDefaults.length > 0 || mismatchedScripts.length > 0;\n\n\t\tif (resolve && hasAutoFixableErrors) {\n\t\t\tconst { updatedScripts, fixedScripts } = applyScriptFixes(\n\t\t\t\tscripts,\n\t\t\t\tmissingWithDefaults,\n\t\t\t\tmismatchedScripts,\n\t\t\t\tdefaultScripts,\n\t\t\t);\n\n\t\t\ttry {\n\t\t\t\tawait jsonfile.writeFile(\n\t\t\t\t\tfile,\n\t\t\t\t\t{ ...json, scripts: updatedScripts },\n\t\t\t\t\t{ spaces: 2 },\n\t\t\t\t);\n\t\t\t\tconst postFixErrors = revalidateAfterFix(config, updatedScripts);\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: postFixErrors.length === 0,\n\t\t\t\t\terrorMessages:\n\t\t\t\t\t\tpostFixErrors.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${postFixErrors.join(\"\\n\\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":"PackageScripts.js","sourceRoot":"","sources":["../../src/policies/PackageScripts.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 * Configuration for validating that a script's body contains required substrings.\n *\n * @alpha\n */\nexport interface ScriptMustContainRule {\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 conditional script requirements.\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 *\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: RequiredScriptEntry[];\n}\n\n/**\n * A required script entry. 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)\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 * { lint: \"biome lint .\" }\n * ```\n *\n * @alpha\n */\nexport type RequiredScriptEntry = string | Record<string, string>;\n\n/**\n * Policy settings for the PackageScripts repo policy.\n *\n * @alpha\n */\nexport interface PackageScriptsSettings {\n\t/**\n\t * Scripts that must be present in package.json.\n\t *\n\t * @remarks\n\t *\n\t * Each entry can be:\n\t * - A string: Script must exist, no auto-fix available, content not validated\n\t * - An object `{ scriptName: \"default value\" }`: Script must exist, auto-fix adds the\n\t * default value if missing, but existing scripts with different content still pass\n\t *\n\t * @example\n\t * ```typescript\n\t * must: [\n\t * \"test\", // Must exist, no auto-fix\n\t * { lint: \"biome lint .\" }, // Must exist, auto-fix available\n\t * { clean: \"rimraf dist esm\" }, // Must exist, auto-fix available\n\t * ]\n\t * ```\n\t */\n\tmust?: RequiredScriptEntry[];\n\n\t/**\n\t * Scripts that must exist AND have exact content.\n\t *\n\t * @remarks\n\t *\n\t * Unlike {@link PackageScriptsSettings.must}, 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 * exact: {\n\t * clean: \"rimraf dist esm\",\n\t * format: \"biome format --write .\",\n\t * }\n\t * ```\n\t */\n\texact?: Record<string, string>;\n\n\t/**\n\t * List of mutually exclusive script groups. Each group is an array of script names\n\t * where at most one script from the group can be present.\n\t *\n\t * Packages can have zero scripts from the group (all optional), but cannot have\n\t * multiple scripts from the same group.\n\t *\n\t * @example\n\t * ```typescript\n\t * // Allow \"test:unit\" OR \"test:vitest\" OR neither, but not both\n\t * mutuallyExclusive: [[\"test:unit\", \"test:vitest\"]]\n\t * ```\n\t */\n\tmutuallyExclusive?: string[][];\n\n\t/**\n\t * Conditional script requirements. If a script is present, other scripts must also exist.\n\t *\n\t * @remarks\n\t *\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 * The `requires` field accepts the same format as {@link PackageScriptsSettings.must}:\n\t * - Strings: Script must exist, no auto-fix available\n\t * - Objects `{ scriptName: \"default\" }`: Script must exist, auto-fix adds the default if missing\n\t *\n\t * @example\n\t * ```typescript\n\t * // If \"build\" exists, \"clean\" must also exist (with auto-fix default)\n\t * conditionalRequired: [{ ifPresent: \"build\", requires: [{ clean: \"rimraf dist\" }] }]\n\t *\n\t * // Mixed: \"test\" required without default, \"clean\" with default\n\t * conditionalRequired: [{ ifPresent: \"build\", requires: [\"test\", { clean: \"rimraf dist\" }] }]\n\t * ```\n\t */\n\tconditionalRequired?: ConditionalScriptRule[];\n\n\t/**\n\t * Rules for validating script body content.\n\t *\n\t * @remarks\n\t *\n\t * This allows you to ensure that specific scripts contain required commands or patterns\n\t * without requiring an exact match. For example, requiring that the \"clean\" script uses\n\t * \"rimraf\" or that \"build\" includes \"tsc\".\n\t *\n\t * For exact content matching, use {@link PackageScriptsSettings.exact} instead.\n\t *\n\t * @example\n\t * ```typescript\n\t * scriptMustContain: [\n\t * { script: \"clean\", mustContain: [\"rimraf\"] },\n\t * { script: \"build\", mustContain: [\"tsc\"] },\n\t * ]\n\t * ```\n\t */\n\tscriptMustContain?: ScriptMustContainRule[];\n}\n\nconst POLICY_NAME = \"PackageScripts\";\n\n/**\n * Extracts script name and optional default value from a RequiredScriptEntry.\n */\nfunction parseRequiredEntry(entry: RequiredScriptEntry): {\n\tname: string;\n\tdefaultValue?: string;\n} {\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 required 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\t// Only include defaultValue if it's defined (exactOptionalPropertyTypes)\n\treturn defaultValue !== undefined ? { name, defaultValue } : { name };\n}\n\n/**\n * Extract defaults from RequiredScriptEntry array.\n */\nfunction extractDefaultsFromEntries(\n\tentries: RequiredScriptEntry[],\n\tdefaults: Record<string, string>,\n): void {\n\tfor (const entry of entries) {\n\t\tconst { name, defaultValue } = parseRequiredEntry(entry);\n\t\tif (defaultValue !== undefined) {\n\t\t\tdefaults[name] = defaultValue;\n\t\t}\n\t}\n}\n\n/**\n * Builds a map of script names to their default values from must, conditionalRequired, and exact configs.\n */\nfunction buildDefaultsMap(\n\tconfig: PackageScriptsSettings,\n): Record<string, string> {\n\tconst defaults: Record<string, string> = {};\n\n\t// Add defaults from must entries\n\tif (config.must) {\n\t\textractDefaultsFromEntries(config.must, defaults);\n\t}\n\n\t// Add defaults from conditionalRequired entries\n\tif (config.conditionalRequired) {\n\t\tfor (const rule of config.conditionalRequired) {\n\t\t\textractDefaultsFromEntries(rule.requires, defaults);\n\t\t}\n\t}\n\n\t// Add defaults from exact (these override must/conditionalRequired if both specified)\n\tif (config.exact) {\n\t\tObject.assign(defaults, config.exact);\n\t}\n\n\treturn defaults;\n}\n\n/**\n * Validates mutually exclusive script groups and returns error messages for violations.\n */\nfunction validateMutuallyExclusiveScripts(\n\tgroups: string[][],\n\tscripts: PackageJson[\"scripts\"],\n): string[] {\n\tconst errors: string[] = [];\n\n\tfor (const group of groups) {\n\t\tconst presentScripts = group.filter(\n\t\t\t(scriptName) => scripts && Object.hasOwn(scripts, scriptName),\n\t\t);\n\n\t\t// Only fail if MORE than one script from the group is present\n\t\tif (presentScripts.length > 1) {\n\t\t\terrors.push(\n\t\t\t\t`Scripts are mutually exclusive, but found ${presentScripts.length}: ${presentScripts.join(\", \")}`,\n\t\t\t);\n\t\t}\n\t}\n\n\treturn errors;\n}\n\n/**\n * Validates conditional script requirements (if X exists, Y must also exist).\n */\nfunction validateConditionalRequirements(\n\trules: ConditionalScriptRule[],\n\tscripts: PackageJson[\"scripts\"],\n): string[] {\n\tconst errors: string[] = [];\n\n\tfor (const rule of rules) {\n\t\t// Check if the triggering script exists\n\t\tif (scripts && Object.hasOwn(scripts, rule.ifPresent)) {\n\t\t\t// Find missing required scripts\n\t\t\tconst missingRequired: string[] = [];\n\t\t\tfor (const entry of rule.requires) {\n\t\t\t\tconst { name } = parseRequiredEntry(entry);\n\t\t\t\tif (!Object.hasOwn(scripts, name)) {\n\t\t\t\t\tmissingRequired.push(name);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (missingRequired.length > 0) {\n\t\t\t\terrors.push(\n\t\t\t\t\t`Script \"${rule.ifPresent}\" is present, but required companion script(s) missing: ${missingRequired.join(\", \")}`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn errors;\n}\n\n/**\n * Validates that script bodies contain required substrings.\n */\nfunction validateScriptContents(\n\trules: ScriptMustContainRule[],\n\tscripts: PackageJson[\"scripts\"],\n): string[] {\n\tconst errors: string[] = [];\n\n\tfor (const rule of rules) {\n\t\t// Only validate if the script exists\n\t\tif (scripts && Object.hasOwn(scripts, rule.script)) {\n\t\t\tconst scriptBody = scripts[rule.script];\n\t\t\tif (typeof scriptBody === \"string\") {\n\t\t\t\tconst missingSubstrings = rule.mustContain.filter(\n\t\t\t\t\t(substring) => !scriptBody.includes(substring),\n\t\t\t\t);\n\n\t\t\t\tif (missingSubstrings.length > 0) {\n\t\t\t\t\terrors.push(\n\t\t\t\t\t\t`Script \"${rule.script}\" must contain: ${missingSubstrings.join(\", \")}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn errors;\n}\n\n/**\n * Result of validating required scripts.\n */\ninterface MustScriptsResult {\n\terrors: string[];\n\tmissingWithDefaults: string[];\n}\n\n/**\n * Validates required scripts from 'must' config.\n */\nfunction validateMustScripts(\n\tmust: RequiredScriptEntry[],\n\tscripts: PackageJson[\"scripts\"],\n): MustScriptsResult {\n\tconst missingScripts: string[] = [];\n\tconst missingWithDefaults: string[] = [];\n\n\tfor (const entry of must) {\n\t\tconst { name, defaultValue } = parseRequiredEntry(entry);\n\t\tif (!(scripts && Object.hasOwn(scripts, name))) {\n\t\t\tmissingScripts.push(name);\n\t\t\tif (defaultValue !== undefined) {\n\t\t\t\tmissingWithDefaults.push(name);\n\t\t\t}\n\t\t}\n\t}\n\n\tconst errors: string[] = [];\n\tif (missingScripts.length > 0) {\n\t\terrors.push(`Missing required scripts:\\n\\t${missingScripts.join(\"\\n\\t\")}`);\n\t}\n\n\treturn { errors, missingWithDefaults };\n}\n\n/**\n * Result of validating exact scripts.\n */\ninterface ExactScriptsResult {\n\terrors: string[];\n\tmissingWithDefaults: string[];\n\tmismatchedScripts: string[];\n}\n\n/**\n * Validates scripts from 'exact' config - must exist AND match exactly.\n */\nfunction validateExactScripts(\n\texact: Record<string, string>,\n\tscripts: PackageJson[\"scripts\"],\n): ExactScriptsResult {\n\tconst errors: string[] = [];\n\tconst missingWithDefaults: string[] = [];\n\tconst mismatchedScripts: string[] = [];\n\n\tfor (const [scriptName, expectedValue] of Object.entries(exact)) {\n\t\tif (scripts && 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\tmissingWithDefaults.push(scriptName);\n\t\t\terrors.push(`Missing required script: ${scriptName}`);\n\t\t}\n\t}\n\n\treturn { errors, missingWithDefaults, mismatchedScripts };\n}\n\n/**\n * Result of validating conditional requirements.\n */\ninterface ConditionalResult {\n\terrors: string[];\n\tmissingWithDefaults: string[];\n}\n\n/**\n * Find missing scripts from a conditional rule's requirements.\n */\nfunction findMissingFromRule(\n\trule: ConditionalScriptRule,\n\tscripts: PackageJson[\"scripts\"],\n): string[] {\n\treturn rule.requires\n\t\t.map((entry) => parseRequiredEntry(entry).name)\n\t\t.filter((name) => !(scripts && Object.hasOwn(scripts, name)));\n}\n\n/**\n * Validates conditional requirements and tracks missing scripts with defaults.\n */\nfunction validateConditionalWithDefaults(\n\trules: ConditionalScriptRule[],\n\tscripts: PackageJson[\"scripts\"],\n\tdefaultScripts: Record<string, string>,\n\texistingMissingWithDefaults: string[],\n): ConditionalResult {\n\tconst errors: string[] = [];\n\tconst missingWithDefaults: string[] = [];\n\tconst allTracked = new Set(existingMissingWithDefaults);\n\n\tfor (const rule of rules) {\n\t\tif (!(scripts && Object.hasOwn(scripts, rule.ifPresent))) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst missingRequired = findMissingFromRule(rule, scripts);\n\n\t\tfor (const scriptName of missingRequired) {\n\t\t\tif (scriptName in defaultScripts && !allTracked.has(scriptName)) {\n\t\t\t\tmissingWithDefaults.push(scriptName);\n\t\t\t\tallTracked.add(scriptName);\n\t\t\t}\n\t\t}\n\n\t\tif (missingRequired.length > 0) {\n\t\t\terrors.push(\n\t\t\t\t`Script \"${rule.ifPresent}\" is present, but required companion script(s) missing: ${missingRequired.join(\", \")}`,\n\t\t\t);\n\t\t}\n\t}\n\n\treturn { errors, missingWithDefaults };\n}\n\n/**\n * Apply fixes to scripts and return the updated scripts object.\n */\nfunction applyScriptFixes(\n\tscripts: PackageJson[\"scripts\"],\n\tmissingScriptsWithDefaults: string[],\n\tmismatchedScripts: string[],\n\tdefaultScripts: Record<string, string>,\n): { updatedScripts: Record<string, string>; fixedScripts: string[] } {\n\tconst updatedScripts: Record<string, string> = {};\n\n\t// Copy existing scripts, filtering out undefined values\n\tif (scripts) {\n\t\tfor (const [key, value] of Object.entries(scripts)) {\n\t\t\tif (value !== undefined) {\n\t\t\t\tupdatedScripts[key] = value;\n\t\t\t}\n\t\t}\n\t}\n\n\tconst fixedScripts: string[] = [];\n\n\tfor (const scriptName of missingScriptsWithDefaults) {\n\t\tconst defaultValue = defaultScripts[scriptName];\n\t\tif (defaultValue !== undefined) {\n\t\t\tupdatedScripts[scriptName] = defaultValue;\n\t\t\tfixedScripts.push(scriptName);\n\t\t}\n\t}\n\n\tfor (const scriptName of mismatchedScripts) {\n\t\tconst defaultValue = defaultScripts[scriptName];\n\t\tif (defaultValue !== undefined && !fixedScripts.includes(scriptName)) {\n\t\t\tupdatedScripts[scriptName] = defaultValue;\n\t\t\tfixedScripts.push(scriptName);\n\t\t}\n\t}\n\n\treturn { updatedScripts, fixedScripts };\n}\n\n/**\n * Validate 'must' scripts and return any remaining missing scripts.\n */\nfunction validateRemainingMustScripts(\n\tmust: RequiredScriptEntry[],\n\tscripts: Record<string, string>,\n): string[] {\n\tconst stillMissing = must\n\t\t.map((entry) => parseRequiredEntry(entry).name)\n\t\t.filter((name) => !Object.hasOwn(scripts, name));\n\n\tif (stillMissing.length > 0) {\n\t\treturn [`Missing required scripts:\\n\\t${stillMissing.join(\"\\n\\t\")}`];\n\t}\n\treturn [];\n}\n\n/**\n * Validate 'exact' scripts and return any errors.\n */\nfunction validateRemainingExactScripts(\n\texact: Record<string, string>,\n\tscripts: Record<string, string>,\n): string[] {\n\tconst errors: string[] = [];\n\tfor (const [scriptName, expectedValue] of Object.entries(exact)) {\n\t\tif (!Object.hasOwn(scripts, scriptName)) {\n\t\t\terrors.push(`Missing required script: ${scriptName}`);\n\t\t} else if (scripts[scriptName] !== expectedValue) {\n\t\t\terrors.push(\n\t\t\t\t`Script \"${scriptName}\" must be \"${expectedValue}\", but found \"${scripts[scriptName]}\"`,\n\t\t\t);\n\t\t}\n\t}\n\treturn errors;\n}\n\n/**\n * Re-validate all rules after applying fixes.\n */\nfunction revalidateAfterFix(\n\tconfig: PackageScriptsSettings,\n\tupdatedScripts: Record<string, string>,\n): string[] {\n\tconst errors: string[] = [];\n\n\tif (\n\t\tconfig.mutuallyExclusive !== undefined &&\n\t\tconfig.mutuallyExclusive.length > 0\n\t) {\n\t\terrors.push(\n\t\t\t...validateMutuallyExclusiveScripts(\n\t\t\t\tconfig.mutuallyExclusive,\n\t\t\t\tupdatedScripts,\n\t\t\t),\n\t\t);\n\t}\n\n\tif (\n\t\tconfig.scriptMustContain !== undefined &&\n\t\tconfig.scriptMustContain.length > 0\n\t) {\n\t\terrors.push(\n\t\t\t...validateScriptContents(config.scriptMustContain, updatedScripts),\n\t\t);\n\t}\n\n\tif (config.must !== undefined && config.must.length > 0) {\n\t\terrors.push(...validateRemainingMustScripts(config.must, updatedScripts));\n\t}\n\n\tif (config.exact !== undefined && Object.keys(config.exact).length > 0) {\n\t\terrors.push(...validateRemainingExactScripts(config.exact, updatedScripts));\n\t}\n\n\tif (\n\t\tconfig.conditionalRequired !== undefined &&\n\t\tconfig.conditionalRequired.length > 0\n\t) {\n\t\terrors.push(\n\t\t\t...validateConditionalRequirements(\n\t\t\t\tconfig.conditionalRequired,\n\t\t\t\tupdatedScripts,\n\t\t\t),\n\t\t);\n\t}\n\n\treturn errors;\n}\n\n/**\n * Result of running all script validations.\n */\ninterface AllValidationsResult {\n\terrors: string[];\n\tmissingWithDefaults: string[];\n\tmismatchedScripts: string[];\n}\n\n/**\n * Run all script validations and aggregate results.\n */\nfunction runAllValidations(\n\tconfig: PackageScriptsSettings,\n\tscripts: PackageJson[\"scripts\"],\n\tdefaultScripts: Record<string, string>,\n): AllValidationsResult {\n\tconst errors: string[] = [];\n\tconst missingWithDefaults: string[] = [];\n\tlet mismatchedScripts: string[] = [];\n\n\tif (config.must !== undefined && config.must.length > 0) {\n\t\tconst result = validateMustScripts(config.must, scripts);\n\t\terrors.push(...result.errors);\n\t\tmissingWithDefaults.push(...result.missingWithDefaults);\n\t}\n\n\tif (config.exact !== undefined && Object.keys(config.exact).length > 0) {\n\t\tconst result = validateExactScripts(config.exact, scripts);\n\t\terrors.push(...result.errors);\n\t\tmissingWithDefaults.push(...result.missingWithDefaults);\n\t\tmismatchedScripts = result.mismatchedScripts;\n\t}\n\n\tif (\n\t\tconfig.mutuallyExclusive !== undefined &&\n\t\tconfig.mutuallyExclusive.length > 0\n\t) {\n\t\terrors.push(\n\t\t\t...validateMutuallyExclusiveScripts(config.mutuallyExclusive, scripts),\n\t\t);\n\t}\n\n\tif (\n\t\tconfig.conditionalRequired !== undefined &&\n\t\tconfig.conditionalRequired.length > 0\n\t) {\n\t\tconst result = validateConditionalWithDefaults(\n\t\t\tconfig.conditionalRequired,\n\t\t\tscripts,\n\t\t\tdefaultScripts,\n\t\t\tmissingWithDefaults,\n\t\t);\n\t\terrors.push(...result.errors);\n\t\tmissingWithDefaults.push(...result.missingWithDefaults);\n\t}\n\n\tif (\n\t\tconfig.scriptMustContain !== undefined &&\n\t\tconfig.scriptMustContain.length > 0\n\t) {\n\t\terrors.push(...validateScriptContents(config.scriptMustContain, scripts));\n\t}\n\n\treturn { errors, missingWithDefaults, mismatchedScripts };\n}\n\n/**\n * A RepoPolicy that validates package.json scripts based on configurable rules.\n *\n * @remarks\n *\n * This policy provides comprehensive script validation including:\n * - Required scripts that must be present (with optional defaults for auto-fix)\n * - Exact script content enforcement\n * - Mutually exclusive script groups (at most one can exist)\n * - Conditional requirements (if X exists, Y must also exist)\n * - Script body content validation (script must contain specific substrings)\n *\n * @example\n * ```typescript\n * import { makePolicy } from \"repopo\";\n * import { PackageScripts } from \"repopo/policies\";\n *\n * const config: RepopoConfig = {\n * policies: [\n * makePolicy(PackageScripts, {\n * // Scripts that must exist\n * must: [\n * \"test\", // Must exist, no auto-fix\n * { build: \"tsc\" }, // Must exist, auto-fix available\n * { lint: \"biome lint .\" }, // Must exist, auto-fix available\n * ],\n * // Scripts that must exist AND match exactly\n * exact: {\n * clean: \"rimraf dist esm\",\n * format: \"biome format --write .\",\n * },\n * // If \"build\" exists, \"clean\" must also exist (with inline auto-fix default)\n * conditionalRequired: [{ ifPresent: \"build\", requires: [{ clean: \"rimraf dist\" }] }],\n * // Script body must contain these substrings\n * scriptMustContain: [{ script: \"build\", mustContain: [\"tsc\"] }],\n * // Only one of these can exist\n * mutuallyExclusive: [[\"lint:eslint\", \"lint:biome\"]],\n * }),\n * ],\n * };\n * ```\n *\n * @alpha\n */\nexport const PackageScripts = definePackagePolicy<\n\tPackageJson,\n\tPackageScriptsSettings | undefined\n>({\n\tname: \"PackageScripts\",\n\tdescription:\n\t\t\"Validates package.json scripts based on configurable rules including required scripts, exact content, and conditional requirements.\",\n\thandler: async (json, { file, root, resolve, config }) => {\n\t\tif (config === undefined) {\n\t\t\treturn true;\n\t\t}\n\n\t\tconst scripts = Object.hasOwn(json, \"scripts\") ? json.scripts : {};\n\t\tconst defaultScripts = buildDefaultsMap(config);\n\n\t\tconst { errors, missingWithDefaults, mismatchedScripts } =\n\t\t\trunAllValidations(config, scripts, defaultScripts);\n\n\t\tif (errors.length === 0) {\n\t\t\treturn true;\n\t\t}\n\n\t\tconst hasAutoFixableErrors =\n\t\t\tmissingWithDefaults.length > 0 || mismatchedScripts.length > 0;\n\n\t\tif (resolve && hasAutoFixableErrors) {\n\t\t\tconst { updatedScripts, fixedScripts } = applyScriptFixes(\n\t\t\t\tscripts,\n\t\t\t\tmissingWithDefaults,\n\t\t\t\tmismatchedScripts,\n\t\t\t\tdefaultScripts,\n\t\t\t);\n\n\t\t\ttry {\n\t\t\t\tawait jsonfile.writeFile(\n\t\t\t\t\tresolvePath(root, file),\n\t\t\t\t\t{ ...json, scripts: updatedScripts },\n\t\t\t\t\t{ spaces: 2 },\n\t\t\t\t);\n\t\t\t\tconst postFixErrors = revalidateAfterFix(config, updatedScripts);\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: postFixErrors.length === 0,\n\t\t\t\t\terrorMessages:\n\t\t\t\t\t\tpostFixErrors.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${postFixErrors.join(\"\\n\\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,5 +1,5 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
-
import { dirname, join } from "pathe";
|
|
2
|
+
import { dirname, join, resolve as resolvePath } from "pathe";
|
|
3
3
|
import { definePackagePolicy } from "../policyDefiners/definePackagePolicy.js";
|
|
4
4
|
/**
|
|
5
5
|
* Check if a package should be excluded from validation.
|
|
@@ -108,7 +108,7 @@ function shouldSkipValidation(json, config) {
|
|
|
108
108
|
export const PackageTestScripts = definePackagePolicy({
|
|
109
109
|
name: "PackageTestScripts",
|
|
110
110
|
description: "Ensures packages have test scripts when test directories or test framework dependencies exist.",
|
|
111
|
-
handler: async (json, { file, config }) => {
|
|
111
|
+
handler: async (json, { file, root, config }) => {
|
|
112
112
|
// If no config provided, skip validation
|
|
113
113
|
if (config === undefined) {
|
|
114
114
|
return true;
|
|
@@ -121,7 +121,7 @@ export const PackageTestScripts = definePackagePolicy({
|
|
|
121
121
|
const testDirectories = config.testDirectories ?? [];
|
|
122
122
|
const testDependencies = config.testDependencies ?? [];
|
|
123
123
|
const requiredScripts = config.requiredScripts ?? ["test"];
|
|
124
|
-
const packageDir = dirname(file);
|
|
124
|
+
const packageDir = dirname(resolvePath(root, file));
|
|
125
125
|
const devDeps = json.devDependencies;
|
|
126
126
|
// Check if tests are expected based on what's configured
|
|
127
127
|
const hasTestDir = testDirectories.length > 0 &&
|