repopo 0.8.0 → 0.10.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 +67 -0
- package/THIRD-PARTY-LICENSES.txt +4 -3
- package/esm/adapters/fluidFramework.js +8 -3
- package/esm/adapters/fluidFramework.js.map +1 -1
- package/esm/baseCommand.d.ts.map +1 -1
- package/esm/baseCommand.js +2 -1
- package/esm/baseCommand.js.map +1 -1
- package/esm/commands/check.d.ts +1 -0
- package/esm/commands/check.d.ts.map +1 -1
- package/esm/commands/check.js +114 -52
- package/esm/commands/check.js.map +1 -1
- package/esm/commands/list.d.ts.map +1 -1
- package/esm/commands/list.js +3 -1
- package/esm/commands/list.js.map +1 -1
- package/esm/config.d.ts +19 -4
- package/esm/config.d.ts.map +1 -1
- package/esm/config.js +2 -3
- package/esm/config.js.map +1 -1
- package/esm/generators.d.ts +30 -2
- package/esm/generators.d.ts.map +1 -1
- package/esm/generators.js +31 -1
- package/esm/generators.js.map +1 -1
- package/esm/index.d.ts +14 -4
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +17 -2
- package/esm/index.js.map +1 -1
- package/esm/makePolicy.d.ts +74 -3
- package/esm/makePolicy.d.ts.map +1 -1
- package/esm/makePolicy.js +87 -13
- package/esm/makePolicy.js.map +1 -1
- package/esm/perf.d.ts +3 -2
- package/esm/perf.d.ts.map +1 -1
- package/esm/perf.js +2 -2
- package/esm/perf.js.map +1 -1
- package/esm/policies/ConditionalScripts.d.ts +94 -0
- package/esm/policies/ConditionalScripts.d.ts.map +1 -0
- package/esm/policies/ConditionalScripts.js +144 -0
- package/esm/policies/ConditionalScripts.js.map +1 -0
- package/esm/policies/ExactScripts.d.ts +51 -0
- package/esm/policies/ExactScripts.d.ts.map +1 -0
- package/esm/policies/ExactScripts.js +107 -0
- package/esm/policies/ExactScripts.js.map +1 -0
- package/esm/policies/HtmlFileHeaders.d.ts +1 -1
- package/esm/policies/HtmlFileHeaders.d.ts.map +1 -1
- package/esm/policies/HtmlFileHeaders.js +11 -7
- package/esm/policies/HtmlFileHeaders.js.map +1 -1
- package/esm/policies/JsTsFileHeaders.d.ts +1 -1
- package/esm/policies/JsTsFileHeaders.d.ts.map +1 -1
- package/esm/policies/JsTsFileHeaders.js +11 -7
- package/esm/policies/JsTsFileHeaders.js.map +1 -1
- package/esm/policies/LicenseFileExists.d.ts.map +1 -1
- package/esm/policies/LicenseFileExists.js +22 -18
- package/esm/policies/LicenseFileExists.js.map +1 -1
- package/esm/policies/MutuallyExclusiveScripts.d.ts +46 -0
- package/esm/policies/MutuallyExclusiveScripts.d.ts.map +1 -0
- package/esm/policies/MutuallyExclusiveScripts.js +52 -0
- package/esm/policies/MutuallyExclusiveScripts.js.map +1 -0
- package/esm/policies/NoJsFileExtensions.d.ts.map +1 -1
- package/esm/policies/NoJsFileExtensions.js +16 -12
- package/esm/policies/NoJsFileExtensions.js.map +1 -1
- package/esm/policies/NoLargeBinaryFiles.d.ts.map +1 -1
- package/esm/policies/NoLargeBinaryFiles.js +35 -31
- package/esm/policies/NoLargeBinaryFiles.js.map +1 -1
- package/esm/policies/NoPrivateWorkspaceDependencies.d.ts +1 -1
- package/esm/policies/NoPrivateWorkspaceDependencies.d.ts.map +1 -1
- package/esm/policies/NoPrivateWorkspaceDependencies.js +53 -45
- package/esm/policies/NoPrivateWorkspaceDependencies.js.map +1 -1
- package/esm/policies/PackageAllowedScopes.d.ts +1 -1
- package/esm/policies/PackageAllowedScopes.d.ts.map +1 -1
- package/esm/policies/PackageAllowedScopes.js +45 -40
- package/esm/policies/PackageAllowedScopes.js.map +1 -1
- package/esm/policies/PackageEsmType.d.ts +1 -1
- package/esm/policies/PackageEsmType.d.ts.map +1 -1
- package/esm/policies/PackageEsmType.js +48 -44
- package/esm/policies/PackageEsmType.js.map +1 -1
- package/esm/policies/PackageFolderName.d.ts +1 -1
- package/esm/policies/PackageFolderName.d.ts.map +1 -1
- package/esm/policies/PackageFolderName.js +38 -34
- package/esm/policies/PackageFolderName.js.map +1 -1
- package/esm/policies/PackageJsonProperties.d.ts +1 -1
- package/esm/policies/PackageJsonProperties.d.ts.map +1 -1
- package/esm/policies/PackageJsonProperties.js +36 -31
- package/esm/policies/PackageJsonProperties.js.map +1 -1
- package/esm/policies/PackageJsonRepoDirectoryProperty.d.ts +1 -1
- package/esm/policies/PackageJsonRepoDirectoryProperty.d.ts.map +1 -1
- package/esm/policies/PackageJsonRepoDirectoryProperty.js +41 -38
- package/esm/policies/PackageJsonRepoDirectoryProperty.js.map +1 -1
- package/esm/policies/PackageJsonSorted.d.ts +1 -1
- package/esm/policies/PackageJsonSorted.d.ts.map +1 -1
- package/esm/policies/PackageJsonSorted.js +40 -36
- package/esm/policies/PackageJsonSorted.js.map +1 -1
- package/esm/policies/PackageLicense.d.ts +1 -1
- package/esm/policies/PackageLicense.d.ts.map +1 -1
- package/esm/policies/PackageLicense.js +58 -54
- package/esm/policies/PackageLicense.js.map +1 -1
- package/esm/policies/PackagePrivateField.d.ts +1 -1
- package/esm/policies/PackagePrivateField.d.ts.map +1 -1
- package/esm/policies/PackagePrivateField.js +58 -54
- package/esm/policies/PackagePrivateField.js.map +1 -1
- package/esm/policies/PackageReadme.d.ts +1 -1
- package/esm/policies/PackageReadme.d.ts.map +1 -1
- package/esm/policies/PackageReadme.js +30 -26
- package/esm/policies/PackageReadme.js.map +1 -1
- package/esm/policies/PackageScripts.d.ts +1 -1
- package/esm/policies/PackageScripts.d.ts.map +1 -1
- package/esm/policies/PackageScripts.js +39 -35
- package/esm/policies/PackageScripts.js.map +1 -1
- package/esm/policies/PackageTestScripts.d.ts +1 -1
- package/esm/policies/PackageTestScripts.d.ts.map +1 -1
- package/esm/policies/PackageTestScripts.js +46 -40
- package/esm/policies/PackageTestScripts.js.map +1 -1
- package/esm/policies/RequiredGitignorePatterns.d.ts.map +1 -1
- package/esm/policies/RequiredGitignorePatterns.js +20 -15
- package/esm/policies/RequiredGitignorePatterns.js.map +1 -1
- package/esm/policies/RequiredScripts.d.ts +71 -0
- package/esm/policies/RequiredScripts.d.ts.map +1 -0
- package/esm/policies/RequiredScripts.js +120 -0
- package/esm/policies/RequiredScripts.js.map +1 -0
- package/esm/policies/ScriptContains.d.ts +64 -0
- package/esm/policies/ScriptContains.d.ts.map +1 -0
- package/esm/policies/ScriptContains.js +62 -0
- package/esm/policies/ScriptContains.js.map +1 -0
- package/esm/policies.d.ts +6 -1
- package/esm/policies.d.ts.map +1 -1
- package/esm/policies.js +18 -0
- package/esm/policies.js.map +1 -1
- package/esm/policy.d.ts +193 -74
- package/esm/policy.d.ts.map +1 -1
- package/esm/policy.js +110 -19
- package/esm/policy.js.map +1 -1
- package/esm/policyDefiners/defineFileHeaderPolicy.d.ts +49 -2
- package/esm/policyDefiners/defineFileHeaderPolicy.d.ts.map +1 -1
- package/esm/policyDefiners/defineFileHeaderPolicy.js +21 -4
- package/esm/policyDefiners/defineFileHeaderPolicy.js.map +1 -1
- package/esm/policyDefiners/definePackagePolicy.d.ts +57 -3
- package/esm/policyDefiners/definePackagePolicy.d.ts.map +1 -1
- package/esm/policyDefiners/definePackagePolicy.js +50 -5
- package/esm/policyDefiners/definePackagePolicy.js.map +1 -1
- package/esm/tsdoc-metadata.json +1 -1
- package/oclif.manifest.json +1 -1
- package/package.json +6 -4
|
@@ -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>(\"PackageReadme\", async (json, { file, resolve, config }) => {\n\tconst skipPrivate = config?.skipPrivate ?? true;\n\tconst requireMatchingTitle = config?.requireMatchingTitle ?? true;\n\tconst requiredContent = config?.requiredContent;\n\n\t// Skip private packages if configured to do so\n\tif (skipPrivate && json.private === true) {\n\t\treturn true;\n\t}\n\n\tconst packageName = json.name ?? \"unknown\";\n\tconst packageDir = path.dirname(file);\n\tconst readmePath = path.join(packageDir, \"README.md\");\n\n\t// Check if README exists\n\tif (!existsSync(readmePath)) {\n\t\treturn await handleMissingReadme(\n\t\t\treadmePath,\n\t\t\tpackageName,\n\t\t\tfile,\n\t\t\tresolve,\n\t\t\trequiredContent,\n\t\t);\n\t}\n\n\t// README exists, validate content\n\tconst readmeContent = await readFile(readmePath, \"utf-8\");\n\treturn await validateExistingReadme({\n\t\treadmePath,\n\t\treadmeContent,\n\t\tpackageName,\n\t\tfile,\n\t\tresolve,\n\t\trequireMatchingTitle,\n\t\trequiredContent,\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, 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"]}
|
|
@@ -204,5 +204,5 @@ export interface PackageScriptsSettings {
|
|
|
204
204
|
*
|
|
205
205
|
* @alpha
|
|
206
206
|
*/
|
|
207
|
-
export declare const PackageScripts: import("../policy.js").
|
|
207
|
+
export declare const PackageScripts: import("../policy.js").PolicyShape<PackageScriptsSettings | undefined>;
|
|
208
208
|
//# sourceMappingURL=PackageScripts.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PackageScripts.d.ts","sourceRoot":"","sources":["../../src/policies/PackageScripts.ts"],"names":[],"mappings":"AAIA;;;;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,
|
|
1
|
+
{"version":3,"file":"PackageScripts.d.ts","sourceRoot":"","sources":["../../src/policies/PackageScripts.ts"],"names":[],"mappings":"AAIA;;;;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"}
|
|
@@ -341,42 +341,46 @@ function runAllValidations(config, scripts, defaultScripts) {
|
|
|
341
341
|
*
|
|
342
342
|
* @alpha
|
|
343
343
|
*/
|
|
344
|
-
export const PackageScripts = definePackagePolicy(
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
const { errors, missingWithDefaults, mismatchedScripts } = runAllValidations(config, scripts, defaultScripts);
|
|
351
|
-
if (errors.length === 0) {
|
|
352
|
-
return true;
|
|
353
|
-
}
|
|
354
|
-
const hasAutoFixableErrors = missingWithDefaults.length > 0 || mismatchedScripts.length > 0;
|
|
355
|
-
if (resolve && hasAutoFixableErrors) {
|
|
356
|
-
const { updatedScripts, fixedScripts } = applyScriptFixes(scripts, missingWithDefaults, mismatchedScripts, defaultScripts);
|
|
357
|
-
try {
|
|
358
|
-
await jsonfile.writeFile(file, { ...json, scripts: updatedScripts }, { spaces: 2 });
|
|
359
|
-
const postFixErrors = revalidateAfterFix(config, updatedScripts);
|
|
360
|
-
return {
|
|
361
|
-
name: POLICY_NAME,
|
|
362
|
-
file,
|
|
363
|
-
resolved: postFixErrors.length === 0,
|
|
364
|
-
errorMessages: postFixErrors.length > 0
|
|
365
|
-
? [
|
|
366
|
-
`Fixed scripts: ${fixedScripts.join(", ")}. Remaining errors:\n${postFixErrors.join("\n\n")}`,
|
|
367
|
-
]
|
|
368
|
-
: [`Fixed scripts: ${fixedScripts.join(", ")}`],
|
|
369
|
-
};
|
|
344
|
+
export const PackageScripts = definePackagePolicy({
|
|
345
|
+
name: "PackageScripts",
|
|
346
|
+
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
|
+
if (config === undefined) {
|
|
349
|
+
return true;
|
|
370
350
|
}
|
|
371
|
-
|
|
372
|
-
|
|
351
|
+
const scripts = Object.hasOwn(json, "scripts") ? json.scripts : {};
|
|
352
|
+
const defaultScripts = buildDefaultsMap(config);
|
|
353
|
+
const { errors, missingWithDefaults, mismatchedScripts } = runAllValidations(config, scripts, defaultScripts);
|
|
354
|
+
if (errors.length === 0) {
|
|
355
|
+
return true;
|
|
373
356
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
357
|
+
const hasAutoFixableErrors = missingWithDefaults.length > 0 || mismatchedScripts.length > 0;
|
|
358
|
+
if (resolve && hasAutoFixableErrors) {
|
|
359
|
+
const { updatedScripts, fixedScripts } = applyScriptFixes(scripts, missingWithDefaults, mismatchedScripts, defaultScripts);
|
|
360
|
+
try {
|
|
361
|
+
await jsonfile.writeFile(file, { ...json, scripts: updatedScripts }, { spaces: 2 });
|
|
362
|
+
const postFixErrors = revalidateAfterFix(config, updatedScripts);
|
|
363
|
+
return {
|
|
364
|
+
name: POLICY_NAME,
|
|
365
|
+
file,
|
|
366
|
+
resolved: postFixErrors.length === 0,
|
|
367
|
+
errorMessages: postFixErrors.length > 0
|
|
368
|
+
? [
|
|
369
|
+
`Fixed scripts: ${fixedScripts.join(", ")}. Remaining errors:\n${postFixErrors.join("\n\n")}`,
|
|
370
|
+
]
|
|
371
|
+
: [`Fixed scripts: ${fixedScripts.join(", ")}`],
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
catch {
|
|
375
|
+
// Fall through to return regular failure
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return {
|
|
379
|
+
name: POLICY_NAME,
|
|
380
|
+
file,
|
|
381
|
+
autoFixable: hasAutoFixableErrors,
|
|
382
|
+
errorMessages: errors,
|
|
383
|
+
};
|
|
384
|
+
},
|
|
381
385
|
});
|
|
382
386
|
//# sourceMappingURL=PackageScripts.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PackageScripts.js","sourceRoot":"","sources":["../../src/policies/PackageScripts.ts"],"names":[],"mappings":"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","sourcesContent":["import jsonfile from \"jsonfile\";\nimport type { PackageJson } from \"type-fest\";\nimport { definePackagePolicy } from \"../policyDefiners/definePackagePolicy.js\";\n\n/**\n * 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>(\"PackageScripts\", async (json, { file, resolve, config }) => {\n\tif (config === undefined) {\n\t\treturn true;\n\t}\n\n\tconst scripts = Object.hasOwn(json, \"scripts\") ? json.scripts : {};\n\tconst defaultScripts = buildDefaultsMap(config);\n\n\tconst { errors, missingWithDefaults, mismatchedScripts } = runAllValidations(\n\t\tconfig,\n\t\tscripts,\n\t\tdefaultScripts,\n\t);\n\n\tif (errors.length === 0) {\n\t\treturn true;\n\t}\n\n\tconst hasAutoFixableErrors =\n\t\tmissingWithDefaults.length > 0 || mismatchedScripts.length > 0;\n\n\tif (resolve && hasAutoFixableErrors) {\n\t\tconst { updatedScripts, fixedScripts } = applyScriptFixes(\n\t\t\tscripts,\n\t\t\tmissingWithDefaults,\n\t\t\tmismatchedScripts,\n\t\t\tdefaultScripts,\n\t\t);\n\n\t\ttry {\n\t\t\tawait jsonfile.writeFile(\n\t\t\t\tfile,\n\t\t\t\t{ ...json, scripts: updatedScripts },\n\t\t\t\t{ spaces: 2 },\n\t\t\t);\n\t\t\tconst postFixErrors = revalidateAfterFix(config, updatedScripts);\n\n\t\t\treturn {\n\t\t\t\tname: POLICY_NAME,\n\t\t\t\tfile,\n\t\t\t\tresolved: postFixErrors.length === 0,\n\t\t\t\terrorMessages:\n\t\t\t\t\tpostFixErrors.length > 0\n\t\t\t\t\t\t? [\n\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]\n\t\t\t\t\t\t: [`Fixed scripts: ${fixedScripts.join(\", \")}`],\n\t\t\t};\n\t\t} catch {\n\t\t\t// Fall through to return regular failure\n\t\t}\n\t}\n\n\treturn {\n\t\tname: POLICY_NAME,\n\t\tfile,\n\t\tautoFixable: hasAutoFixableErrors,\n\t\terrorMessages: errors,\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 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"]}
|
|
@@ -96,5 +96,5 @@ export interface PackageTestScriptsConfig {
|
|
|
96
96
|
*
|
|
97
97
|
* @alpha
|
|
98
98
|
*/
|
|
99
|
-
export declare const PackageTestScripts: import("../policy.js").
|
|
99
|
+
export declare const PackageTestScripts: import("../policy.js").PolicyShape<PackageTestScriptsConfig | undefined>;
|
|
100
100
|
//# sourceMappingURL=PackageTestScripts.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PackageTestScripts.d.ts","sourceRoot":"","sources":["../../src/policies/PackageTestScripts.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,WAAW,wBAAwB;IACxC;;;;;;;;;;;;OAYG;IACH,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAE3B;;;;;;;;;;;;OAYG;IACH,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAE5B;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAE3B;;OAEG;IACH,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AA0GD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,kBAAkB
|
|
1
|
+
{"version":3,"file":"PackageTestScripts.d.ts","sourceRoot":"","sources":["../../src/policies/PackageTestScripts.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,WAAW,wBAAwB;IACxC;;;;;;;;;;;;OAYG;IACH,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAE3B;;;;;;;;;;;;OAYG;IACH,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAE5B;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAE3B;;OAEG;IACH,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AA0GD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,kBAAkB,0EA4D7B,CAAC"}
|
|
@@ -105,45 +105,51 @@ function shouldSkipValidation(json, config) {
|
|
|
105
105
|
*
|
|
106
106
|
* @alpha
|
|
107
107
|
*/
|
|
108
|
-
export const PackageTestScripts = definePackagePolicy(
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
108
|
+
export const PackageTestScripts = definePackagePolicy({
|
|
109
|
+
name: "PackageTestScripts",
|
|
110
|
+
description: "Ensures packages have test scripts when test directories or test framework dependencies exist.",
|
|
111
|
+
handler: async (json, { file, config }) => {
|
|
112
|
+
// If no config provided, skip validation
|
|
113
|
+
if (config === undefined) {
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
// Skip validation based on package properties and config
|
|
117
|
+
if (shouldSkipValidation(json, config)) {
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
// Get config values
|
|
121
|
+
const testDirectories = config.testDirectories ?? [];
|
|
122
|
+
const testDependencies = config.testDependencies ?? [];
|
|
123
|
+
const requiredScripts = config.requiredScripts ?? ["test"];
|
|
124
|
+
const packageDir = dirname(file);
|
|
125
|
+
const devDeps = json.devDependencies;
|
|
126
|
+
// Check if tests are expected based on what's configured
|
|
127
|
+
const hasTestDir = testDirectories.length > 0 &&
|
|
128
|
+
hasTestDirectory(packageDir, testDirectories);
|
|
129
|
+
const hasTestDep = testDependencies.length > 0 &&
|
|
130
|
+
hasTestDependency(devDeps, testDependencies);
|
|
131
|
+
// If no tests expected based on configured checks, pass
|
|
132
|
+
if (!(hasTestDir || hasTestDep)) {
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
// Tests are expected - check for required scripts
|
|
136
|
+
const missingScripts = findMissingScripts(json.scripts, requiredScripts);
|
|
137
|
+
if (missingScripts.length === 0) {
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
// Build error message
|
|
141
|
+
const reason = hasTestDir
|
|
142
|
+
? "test directory exists"
|
|
143
|
+
: `test dependencies found (${getPresentTestDependencies(devDeps, testDependencies).join(", ")})`;
|
|
144
|
+
const failResult = {
|
|
145
|
+
name: PackageTestScripts.name,
|
|
146
|
+
file,
|
|
147
|
+
autoFixable: false,
|
|
148
|
+
errorMessages: [
|
|
149
|
+
`Package "${json.name}" has ${reason} but is missing test scripts: ${missingScripts.join(", ")}`,
|
|
150
|
+
],
|
|
151
|
+
};
|
|
152
|
+
return failResult;
|
|
153
|
+
},
|
|
148
154
|
});
|
|
149
155
|
//# sourceMappingURL=PackageTestScripts.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PackageTestScripts.js","sourceRoot":"","sources":["../../src/policies/PackageTestScripts.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync } from \"node:fs\";\nimport { dirname, join } from \"pathe\";\nimport type { PackageJson } from \"type-fest\";\nimport type { PolicyFailure } from \"../policy.js\";\nimport { definePackagePolicy } from \"../policyDefiners/definePackagePolicy.js\";\n\n/**\n * Configuration for the PackageTestScripts policy.\n *\n * This policy ensures that packages have appropriate test scripts when they\n * contain test files or have test-related dependencies.\n *\n * The policy automatically determines what to check based on what's configured:\n * - If `testDirectories` is set, checks for test directories\n * - If `testDependencies` is set, checks for test dependencies\n * - If both are set, a package triggers validation if either condition is met\n *\n * @example\n * ```typescript\n * const config: PackageTestScriptsConfig = {\n * // Check for these test directories\n * testDirectories: [\"test\", \"tests\", \"__tests__\"],\n * // Require test script if these devDependencies exist\n * testDependencies: [\"vitest\", \"jest\", \"mocha\"],\n * // Required test script names\n * requiredScripts: [\"test\"],\n * };\n * ```\n *\n * @alpha\n */\nexport interface PackageTestScriptsConfig {\n\t/**\n\t * Directory names that indicate test files are present.\n\t *\n\t * If any of these directories exist relative to the package.json,\n\t * the policy will require test scripts to be present.\n\t *\n\t * If not set or empty, directory checking is skipped.\n\t *\n\t * @example\n\t * ```typescript\n\t * testDirectories: [\"test\", \"tests\", \"__tests__\"]\n\t * ```\n\t */\n\ttestDirectories?: string[];\n\n\t/**\n\t * Test framework dependencies that indicate tests are expected.\n\t *\n\t * If any of these appear in devDependencies, the policy will require\n\t * test scripts to be present.\n\t *\n\t * If not set or empty, dependency checking is skipped.\n\t *\n\t * @example\n\t * ```typescript\n\t * testDependencies: [\"vitest\", \"jest\", \"mocha\", \"@jest/globals\"]\n\t * ```\n\t */\n\ttestDependencies?: string[];\n\n\t/**\n\t * Script names that must exist when tests are detected.\n\t *\n\t * @defaultValue [\"test\"]\n\t */\n\trequiredScripts?: string[];\n\n\t/**\n\t * Package names or scopes to exclude from validation.\n\t */\n\texcludePackages?: string[];\n}\n\n/**\n * Check if a package should be excluded from validation.\n */\nfunction isExcluded(\n\tpackageName: string,\n\texcludePackages: string[] | undefined,\n): boolean {\n\tif (excludePackages === undefined || excludePackages.length === 0) {\n\t\treturn false;\n\t}\n\n\tfor (const pattern of excludePackages) {\n\t\tif (pattern.startsWith(\"@\") && packageName.startsWith(`${pattern}/`)) {\n\t\t\treturn true;\n\t\t}\n\t\tif (packageName === pattern) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\n/**\n * Check if any test directories exist.\n */\nfunction hasTestDirectory(\n\tpackageDir: string,\n\ttestDirectories: string[],\n): boolean {\n\tfor (const testDir of testDirectories) {\n\t\tconst testPath = join(packageDir, testDir);\n\t\tif (existsSync(testPath)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\n/**\n * Check if any test dependencies are present.\n */\nfunction hasTestDependency(\n\tdevDependencies: Record<string, string> | undefined,\n\ttestDependencies: string[],\n): boolean {\n\tif (devDependencies === undefined) {\n\t\treturn false;\n\t}\n\n\treturn testDependencies.some((dep) => devDependencies[dep] !== undefined);\n}\n\n/**\n * Find missing scripts from the required list.\n */\nfunction findMissingScripts(\n\tscripts: PackageJson[\"scripts\"],\n\trequiredScripts: string[],\n): string[] {\n\treturn requiredScripts.filter(\n\t\t(scriptName) => scripts?.[scriptName] === undefined,\n\t);\n}\n\n/**\n * Get the list of test dependencies that are present in devDependencies.\n */\nfunction getPresentTestDependencies(\n\tdevDependencies: Record<string, string> | undefined,\n\ttestDependencies: string[],\n): string[] {\n\tif (devDependencies === undefined) {\n\t\treturn [];\n\t}\n\treturn testDependencies.filter((d) => devDependencies[d] !== undefined);\n}\n\n/**\n * Check if a package should be validated based on config and package properties.\n */\nfunction shouldSkipValidation(\n\tjson: PackageJson,\n\tconfig: PackageTestScriptsConfig,\n): boolean {\n\tconst packageName = json.name;\n\n\t// Skip packages without a name or root package\n\tif (packageName === undefined || packageName === \"root\") {\n\t\treturn true;\n\t}\n\n\t// Check exclusions\n\tif (isExcluded(packageName, config.excludePackages)) {\n\t\treturn true;\n\t}\n\n\t// Skip if neither directories nor dependencies are configured\n\tconst testDirectories = config.testDirectories ?? [];\n\tconst testDependencies = config.testDependencies ?? [];\n\n\treturn testDirectories.length === 0 && testDependencies.length === 0;\n}\n\n/**\n * A policy that ensures packages have test scripts when test files exist.\n *\n * This policy helps maintain testing discipline by requiring test scripts\n * for packages that have test directories or test framework dependencies.\n *\n * @remarks\n *\n * The policy detects that tests are expected based on:\n *\n * 1. **Test directories**: If `test/`, `tests/`, or `__tests__/` exists\n * 2. **Test dependencies**: If vitest, jest, mocha, etc. are in devDependencies\n *\n * When tests are detected, the policy requires specified test scripts to exist.\n *\n * @example\n * ```typescript\n * import { makePolicy } from \"repopo\";\n * import { PackageTestScripts } from \"repopo/policies\";\n *\n * const config: RepopoConfig = {\n * policies: [\n * makePolicy(PackageTestScripts, {\n * testDirectories: [\"test\", \"tests\"],\n * testDependencies: [\"vitest\", \"jest\"],\n * requiredScripts: [\"test\"],\n * }),\n * ],\n * };\n * ```\n *\n * @alpha\n */\nexport const PackageTestScripts = definePackagePolicy<\n\tPackageJson,\n\tPackageTestScriptsConfig | undefined\n>(\"PackageTestScripts\", async (json, { file, config }) => {\n\t// If no config provided, skip validation\n\tif (config === undefined) {\n\t\treturn true;\n\t}\n\n\t// Skip validation based on package properties and config\n\tif (shouldSkipValidation(json, config)) {\n\t\treturn true;\n\t}\n\n\t// Get config values\n\tconst testDirectories = config.testDirectories ?? [];\n\tconst testDependencies = config.testDependencies ?? [];\n\tconst requiredScripts = config.requiredScripts ?? [\"test\"];\n\tconst packageDir = dirname(file);\n\tconst devDeps = json.devDependencies as Record<string, string> | undefined;\n\n\t// Check if tests are expected based on what's configured\n\tconst hasTestDir =\n\t\ttestDirectories.length > 0 && hasTestDirectory(packageDir, testDirectories);\n\tconst hasTestDep =\n\t\ttestDependencies.length > 0 && hasTestDependency(devDeps, testDependencies);\n\n\t// If no tests expected based on configured checks, pass\n\tif (!(hasTestDir || hasTestDep)) {\n\t\treturn true;\n\t}\n\n\t// Tests are expected - check for required scripts\n\tconst missingScripts = findMissingScripts(json.scripts, requiredScripts);\n\n\tif (missingScripts.length === 0) {\n\t\treturn true;\n\t}\n\n\t// Build error message\n\tconst reason = hasTestDir\n\t\t? \"test directory exists\"\n\t\t: `test dependencies found (${getPresentTestDependencies(devDeps, testDependencies).join(\", \")})`;\n\n\tconst failResult: PolicyFailure = {\n\t\tname: PackageTestScripts.name,\n\t\tfile,\n\t\tautoFixable: false,\n\t\terrorMessages: [\n\t\t\t`Package \"${json.name}\" has ${reason} but is missing test scripts: ${missingScripts.join(\", \")}`,\n\t\t],\n\t};\n\treturn failResult;\n});\n"]}
|
|
1
|
+
{"version":3,"file":"PackageTestScripts.js","sourceRoot":"","sources":["../../src/policies/PackageTestScripts.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync } from \"node:fs\";\nimport { dirname, join } from \"pathe\";\nimport type { PackageJson } from \"type-fest\";\nimport type { PolicyFailure } from \"../policy.js\";\nimport { definePackagePolicy } from \"../policyDefiners/definePackagePolicy.js\";\n\n/**\n * Configuration for the PackageTestScripts policy.\n *\n * This policy ensures that packages have appropriate test scripts when they\n * contain test files or have test-related dependencies.\n *\n * The policy automatically determines what to check based on what's configured:\n * - If `testDirectories` is set, checks for test directories\n * - If `testDependencies` is set, checks for test dependencies\n * - If both are set, a package triggers validation if either condition is met\n *\n * @example\n * ```typescript\n * const config: PackageTestScriptsConfig = {\n * // Check for these test directories\n * testDirectories: [\"test\", \"tests\", \"__tests__\"],\n * // Require test script if these devDependencies exist\n * testDependencies: [\"vitest\", \"jest\", \"mocha\"],\n * // Required test script names\n * requiredScripts: [\"test\"],\n * };\n * ```\n *\n * @alpha\n */\nexport interface PackageTestScriptsConfig {\n\t/**\n\t * Directory names that indicate test files are present.\n\t *\n\t * If any of these directories exist relative to the package.json,\n\t * the policy will require test scripts to be present.\n\t *\n\t * If not set or empty, directory checking is skipped.\n\t *\n\t * @example\n\t * ```typescript\n\t * testDirectories: [\"test\", \"tests\", \"__tests__\"]\n\t * ```\n\t */\n\ttestDirectories?: string[];\n\n\t/**\n\t * Test framework dependencies that indicate tests are expected.\n\t *\n\t * If any of these appear in devDependencies, the policy will require\n\t * test scripts to be present.\n\t *\n\t * If not set or empty, dependency checking is skipped.\n\t *\n\t * @example\n\t * ```typescript\n\t * testDependencies: [\"vitest\", \"jest\", \"mocha\", \"@jest/globals\"]\n\t * ```\n\t */\n\ttestDependencies?: string[];\n\n\t/**\n\t * Script names that must exist when tests are detected.\n\t *\n\t * @defaultValue [\"test\"]\n\t */\n\trequiredScripts?: string[];\n\n\t/**\n\t * Package names or scopes to exclude from validation.\n\t */\n\texcludePackages?: string[];\n}\n\n/**\n * Check if a package should be excluded from validation.\n */\nfunction isExcluded(\n\tpackageName: string,\n\texcludePackages: string[] | undefined,\n): boolean {\n\tif (excludePackages === undefined || excludePackages.length === 0) {\n\t\treturn false;\n\t}\n\n\tfor (const pattern of excludePackages) {\n\t\tif (pattern.startsWith(\"@\") && packageName.startsWith(`${pattern}/`)) {\n\t\t\treturn true;\n\t\t}\n\t\tif (packageName === pattern) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\n/**\n * Check if any test directories exist.\n */\nfunction hasTestDirectory(\n\tpackageDir: string,\n\ttestDirectories: string[],\n): boolean {\n\tfor (const testDir of testDirectories) {\n\t\tconst testPath = join(packageDir, testDir);\n\t\tif (existsSync(testPath)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\n/**\n * Check if any test dependencies are present.\n */\nfunction hasTestDependency(\n\tdevDependencies: Record<string, string> | undefined,\n\ttestDependencies: string[],\n): boolean {\n\tif (devDependencies === undefined) {\n\t\treturn false;\n\t}\n\n\treturn testDependencies.some((dep) => devDependencies[dep] !== undefined);\n}\n\n/**\n * Find missing scripts from the required list.\n */\nfunction findMissingScripts(\n\tscripts: PackageJson[\"scripts\"],\n\trequiredScripts: string[],\n): string[] {\n\treturn requiredScripts.filter(\n\t\t(scriptName) => scripts?.[scriptName] === undefined,\n\t);\n}\n\n/**\n * Get the list of test dependencies that are present in devDependencies.\n */\nfunction getPresentTestDependencies(\n\tdevDependencies: Record<string, string> | undefined,\n\ttestDependencies: string[],\n): string[] {\n\tif (devDependencies === undefined) {\n\t\treturn [];\n\t}\n\treturn testDependencies.filter((d) => devDependencies[d] !== undefined);\n}\n\n/**\n * Check if a package should be validated based on config and package properties.\n */\nfunction shouldSkipValidation(\n\tjson: PackageJson,\n\tconfig: PackageTestScriptsConfig,\n): boolean {\n\tconst packageName = json.name;\n\n\t// Skip packages without a name or root package\n\tif (packageName === undefined || packageName === \"root\") {\n\t\treturn true;\n\t}\n\n\t// Check exclusions\n\tif (isExcluded(packageName, config.excludePackages)) {\n\t\treturn true;\n\t}\n\n\t// Skip if neither directories nor dependencies are configured\n\tconst testDirectories = config.testDirectories ?? [];\n\tconst testDependencies = config.testDependencies ?? [];\n\n\treturn testDirectories.length === 0 && testDependencies.length === 0;\n}\n\n/**\n * A policy that ensures packages have test scripts when test files exist.\n *\n * This policy helps maintain testing discipline by requiring test scripts\n * for packages that have test directories or test framework dependencies.\n *\n * @remarks\n *\n * The policy detects that tests are expected based on:\n *\n * 1. **Test directories**: If `test/`, `tests/`, or `__tests__/` exists\n * 2. **Test dependencies**: If vitest, jest, mocha, etc. are in devDependencies\n *\n * When tests are detected, the policy requires specified test scripts to exist.\n *\n * @example\n * ```typescript\n * import { makePolicy } from \"repopo\";\n * import { PackageTestScripts } from \"repopo/policies\";\n *\n * const config: RepopoConfig = {\n * policies: [\n * makePolicy(PackageTestScripts, {\n * testDirectories: [\"test\", \"tests\"],\n * testDependencies: [\"vitest\", \"jest\"],\n * requiredScripts: [\"test\"],\n * }),\n * ],\n * };\n * ```\n *\n * @alpha\n */\nexport const PackageTestScripts = definePackagePolicy<\n\tPackageJson,\n\tPackageTestScriptsConfig | undefined\n>({\n\tname: \"PackageTestScripts\",\n\tdescription:\n\t\t\"Ensures packages have test scripts when test directories or test framework dependencies exist.\",\n\thandler: async (json, { file, config }) => {\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\t// Skip validation based on package properties and config\n\t\tif (shouldSkipValidation(json, config)) {\n\t\t\treturn true;\n\t\t}\n\n\t\t// Get config values\n\t\tconst testDirectories = config.testDirectories ?? [];\n\t\tconst testDependencies = config.testDependencies ?? [];\n\t\tconst requiredScripts = config.requiredScripts ?? [\"test\"];\n\t\tconst packageDir = dirname(file);\n\t\tconst devDeps = json.devDependencies as Record<string, string> | undefined;\n\n\t\t// Check if tests are expected based on what's configured\n\t\tconst hasTestDir =\n\t\t\ttestDirectories.length > 0 &&\n\t\t\thasTestDirectory(packageDir, testDirectories);\n\t\tconst hasTestDep =\n\t\t\ttestDependencies.length > 0 &&\n\t\t\thasTestDependency(devDeps, testDependencies);\n\n\t\t// If no tests expected based on configured checks, pass\n\t\tif (!(hasTestDir || hasTestDep)) {\n\t\t\treturn true;\n\t\t}\n\n\t\t// Tests are expected - check for required scripts\n\t\tconst missingScripts = findMissingScripts(json.scripts, requiredScripts);\n\n\t\tif (missingScripts.length === 0) {\n\t\t\treturn true;\n\t\t}\n\n\t\t// Build error message\n\t\tconst reason = hasTestDir\n\t\t\t? \"test directory exists\"\n\t\t\t: `test dependencies found (${getPresentTestDependencies(devDeps, testDependencies).join(\", \")})`;\n\n\t\tconst failResult: PolicyFailure = {\n\t\t\tname: PackageTestScripts.name,\n\t\t\tfile,\n\t\t\tautoFixable: false,\n\t\t\terrorMessages: [\n\t\t\t\t`Package \"${json.name}\" has ${reason} but is missing test scripts: ${missingScripts.join(\", \")}`,\n\t\t\t],\n\t\t};\n\t\treturn failResult;\n\t},\n});\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RequiredGitignorePatterns.d.ts","sourceRoot":"","sources":["../../src/policies/RequiredGitignorePatterns.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACX,gBAAgB,EAGhB,MAAM,cAAc,CAAC;AAEtB;;;;GAIG;AACH,MAAM,WAAW,iCAAiC;IACjD;;;OAGG;IACH,QAAQ,EAAE,KAAK,CAAC,MAAM,GAAG;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAChE;AAyMD;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,EAAE,gBAAgB,CAAC,iCAAiC,
|
|
1
|
+
{"version":3,"file":"RequiredGitignorePatterns.d.ts","sourceRoot":"","sources":["../../src/policies/RequiredGitignorePatterns.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACX,gBAAgB,EAGhB,MAAM,cAAc,CAAC;AAEtB;;;;GAIG;AACH,MAAM,WAAW,iCAAiC;IACjD;;;OAGG;IACH,QAAQ,EAAE,KAAK,CAAC,MAAM,GAAG;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAChE;AAyMD;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,EAAE,gBAAgB,CAAC,iCAAiC,CAgBvF,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "pathe";
|
|
3
3
|
import { makePolicyDefinition } from "../makePolicy.js";
|
|
4
4
|
/**
|
|
@@ -90,8 +90,8 @@ function createGitignoreContent(patterns) {
|
|
|
90
90
|
/**
|
|
91
91
|
* Handle the case when .gitignore exists.
|
|
92
92
|
*/
|
|
93
|
-
function handleExistingGitignore(filePath, patterns, file, resolve) {
|
|
94
|
-
const gitignoreContent =
|
|
93
|
+
async function handleExistingGitignore(filePath, patterns, file, resolve) {
|
|
94
|
+
const gitignoreContent = await readFile(filePath, "utf-8");
|
|
95
95
|
const lines = gitignoreContent.split("\n");
|
|
96
96
|
const missingPatterns = findMissingPatterns(patterns, lines);
|
|
97
97
|
if (missingPatterns.length === 0) {
|
|
@@ -112,7 +112,7 @@ function handleExistingGitignore(filePath, patterns, file, resolve) {
|
|
|
112
112
|
};
|
|
113
113
|
try {
|
|
114
114
|
const newContent = addPatternsToContent(gitignoreContent, missingPatterns);
|
|
115
|
-
|
|
115
|
+
await writeFile(filePath, newContent);
|
|
116
116
|
fixResult.resolved = true;
|
|
117
117
|
}
|
|
118
118
|
catch (error) {
|
|
@@ -127,7 +127,7 @@ function handleExistingGitignore(filePath, patterns, file, resolve) {
|
|
|
127
127
|
/**
|
|
128
128
|
* Handle the case when .gitignore doesn't exist.
|
|
129
129
|
*/
|
|
130
|
-
function handleMissingGitignore(filePath, patterns, file, resolve) {
|
|
130
|
+
async function handleMissingGitignore(filePath, patterns, file, resolve) {
|
|
131
131
|
const result = {
|
|
132
132
|
name: RequiredGitignorePatterns.name,
|
|
133
133
|
file,
|
|
@@ -141,7 +141,7 @@ function handleMissingGitignore(filePath, patterns, file, resolve) {
|
|
|
141
141
|
};
|
|
142
142
|
try {
|
|
143
143
|
const content = createGitignoreContent(patterns);
|
|
144
|
-
|
|
144
|
+
await writeFile(filePath, content);
|
|
145
145
|
fixResult.resolved = true;
|
|
146
146
|
}
|
|
147
147
|
catch (writeError) {
|
|
@@ -159,14 +159,19 @@ function handleMissingGitignore(filePath, patterns, file, resolve) {
|
|
|
159
159
|
*
|
|
160
160
|
* @alpha
|
|
161
161
|
*/
|
|
162
|
-
export const RequiredGitignorePatterns = makePolicyDefinition(
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
162
|
+
export const RequiredGitignorePatterns = makePolicyDefinition({
|
|
163
|
+
name: "RequiredGitignorePatterns",
|
|
164
|
+
description: "Ensures .gitignore contains required patterns to prevent committing sensitive files, dependencies, and build artifacts.",
|
|
165
|
+
match: /^\.gitignore$/,
|
|
166
|
+
handler: async ({ file, root, resolve, config }) => {
|
|
167
|
+
const patterns = config?.patterns ?? DEFAULT_PATTERNS;
|
|
168
|
+
const filePath = path.join(root, file);
|
|
169
|
+
try {
|
|
170
|
+
return await handleExistingGitignore(filePath, patterns, file, resolve);
|
|
171
|
+
}
|
|
172
|
+
catch (_error) {
|
|
173
|
+
return await handleMissingGitignore(filePath, patterns, file, resolve);
|
|
174
|
+
}
|
|
175
|
+
},
|
|
171
176
|
});
|
|
172
177
|
//# sourceMappingURL=RequiredGitignorePatterns.js.map
|