repopo 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/THIRD-PARTY-LICENSES.txt +6 -6
  3. package/esm/baseCommand.d.ts.map +1 -1
  4. package/esm/baseCommand.js +0 -1
  5. package/esm/baseCommand.js.map +1 -1
  6. package/esm/commands/check.d.ts.map +1 -1
  7. package/esm/commands/check.js +33 -21
  8. package/esm/commands/check.js.map +1 -1
  9. package/esm/commands/list.d.ts.map +1 -1
  10. package/esm/commands/list.js +0 -1
  11. package/esm/commands/list.js.map +1 -1
  12. package/esm/index.d.ts +2 -2
  13. package/esm/index.d.ts.map +1 -1
  14. package/esm/index.js +1 -1
  15. package/esm/index.js.map +1 -1
  16. package/esm/policies/HtmlFileHeaders.d.ts.map +1 -1
  17. package/esm/policies/HtmlFileHeaders.js.map +1 -1
  18. package/esm/policies/JsTsFileHeaders.d.ts.map +1 -1
  19. package/esm/policies/JsTsFileHeaders.js.map +1 -1
  20. package/esm/policies/LicenseFileExists.d.ts +20 -0
  21. package/esm/policies/LicenseFileExists.d.ts.map +1 -0
  22. package/esm/policies/LicenseFileExists.js +40 -0
  23. package/esm/policies/LicenseFileExists.js.map +1 -0
  24. package/esm/policies/NoJsFileExtensions.d.ts.map +1 -1
  25. package/esm/policies/NoJsFileExtensions.js +1 -3
  26. package/esm/policies/NoJsFileExtensions.js.map +1 -1
  27. package/esm/policies/NoLargeBinaryFiles.d.ts +30 -0
  28. package/esm/policies/NoLargeBinaryFiles.d.ts.map +1 -0
  29. package/esm/policies/NoLargeBinaryFiles.js +118 -0
  30. package/esm/policies/NoLargeBinaryFiles.js.map +1 -0
  31. package/esm/policies/PackageJsonRepoDirectoryProperty.d.ts.map +1 -1
  32. package/esm/policies/PackageJsonRepoDirectoryProperty.js +22 -23
  33. package/esm/policies/PackageJsonRepoDirectoryProperty.js.map +1 -1
  34. package/esm/policies/PackageJsonSorted.d.ts.map +1 -1
  35. package/esm/policies/PackageJsonSorted.js +1 -0
  36. package/esm/policies/PackageJsonSorted.js.map +1 -1
  37. package/esm/policies/PackageScripts.d.ts +25 -2
  38. package/esm/policies/PackageScripts.d.ts.map +1 -1
  39. package/esm/policies/PackageScripts.js +46 -9
  40. package/esm/policies/PackageScripts.js.map +1 -1
  41. package/esm/policies/RequiredGitignorePatterns.d.ts +24 -0
  42. package/esm/policies/RequiredGitignorePatterns.d.ts.map +1 -0
  43. package/esm/policies/RequiredGitignorePatterns.js +166 -0
  44. package/esm/policies/RequiredGitignorePatterns.js.map +1 -0
  45. package/esm/policies.d.ts +5 -1
  46. package/esm/policies.d.ts.map +1 -1
  47. package/esm/policies.js +5 -1
  48. package/esm/policies.js.map +1 -1
  49. package/esm/tsdoc-metadata.json +1 -1
  50. package/oclif.manifest.json +1 -1
  51. package/package.json +35 -28
@@ -0,0 +1,118 @@
1
+ import { statSync } from "node:fs";
2
+ import path from "pathe";
3
+ import { makePolicyDefinition } from "../makePolicy.js";
4
+ /**
5
+ * Default maximum file size (10MB in bytes).
6
+ */
7
+ const DEFAULT_MAX_SIZE = 10 * 1024 * 1024;
8
+ /**
9
+ * Default file extensions that are typically safe to exclude from size limits.
10
+ */
11
+ const DEFAULT_EXCLUDE_EXTENSIONS = [
12
+ ".md",
13
+ ".txt",
14
+ ".json",
15
+ ".js",
16
+ ".ts",
17
+ ".jsx",
18
+ ".tsx",
19
+ ".css",
20
+ ".scss",
21
+ ".sass",
22
+ ".less",
23
+ ".html",
24
+ ".xml",
25
+ ".yml",
26
+ ".yaml",
27
+ ".toml",
28
+ ".ini",
29
+ ".conf",
30
+ ".config",
31
+ ];
32
+ /**
33
+ * Convert a glob pattern to a regex pattern by escaping special characters
34
+ * and converting * to .* for wildcard matching.
35
+ */
36
+ function globToRegex(pattern) {
37
+ // Escape all regex special characters except *
38
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
39
+ // Convert * to .* for wildcard matching
40
+ const regexPattern = escaped.replace(/\*/g, ".*");
41
+ return new RegExp(`^${regexPattern}$`);
42
+ }
43
+ /**
44
+ * Check if a file matches any of the exclude patterns.
45
+ */
46
+ function isExcluded(filePath, excludePatterns, excludeExtensions) {
47
+ const ext = path.extname(filePath).toLowerCase();
48
+ // Check extensions
49
+ if (excludeExtensions.includes(ext)) {
50
+ return true;
51
+ }
52
+ // Check patterns (simple glob-like matching)
53
+ for (const pattern of excludePatterns) {
54
+ if (pattern.includes("*")) {
55
+ const regex = globToRegex(pattern);
56
+ if (regex.test(filePath)) {
57
+ return true;
58
+ }
59
+ }
60
+ else if (filePath.includes(pattern)) {
61
+ return true;
62
+ }
63
+ }
64
+ return false;
65
+ }
66
+ /**
67
+ * Format bytes in a human-readable format.
68
+ */
69
+ function formatBytes(bytes) {
70
+ if (bytes === 0) {
71
+ return "0 B";
72
+ }
73
+ const k = 1024;
74
+ const sizes = ["B", "KB", "MB", "GB", "TB"];
75
+ const i = Math.min(Math.floor(Math.log(bytes) / Math.log(k)), sizes.length - 1);
76
+ const formatted = (bytes / k ** i).toFixed(1);
77
+ return `${formatted} ${sizes[i]}`;
78
+ }
79
+ /**
80
+ * A repo policy that prevents large binary files from being committed to the repository.
81
+ * Large files can bloat the repository and slow down clones. Consider using Git LFS for large assets.
82
+ *
83
+ * @alpha
84
+ */
85
+ export const NoLargeBinaryFiles = makePolicyDefinition("NoLargeBinaryFiles",
86
+ // Match all files
87
+ /.*/, async ({ file, root, config }) => {
88
+ const maxSizeBytes = config?.maxSizeBytes ?? DEFAULT_MAX_SIZE;
89
+ const excludeExtensions = config?.excludeExtensions ?? DEFAULT_EXCLUDE_EXTENSIONS;
90
+ const excludePatterns = config?.excludePatterns ?? [];
91
+ // Skip excluded files
92
+ if (isExcluded(file, excludePatterns, excludeExtensions)) {
93
+ return true;
94
+ }
95
+ try {
96
+ const filePath = path.resolve(root, file);
97
+ const stats = statSync(filePath);
98
+ // Skip directories
99
+ if (stats.isDirectory()) {
100
+ return true;
101
+ }
102
+ if (stats.size > maxSizeBytes) {
103
+ const result = {
104
+ name: NoLargeBinaryFiles.name,
105
+ file,
106
+ autoFixable: false,
107
+ errorMessage: `File is too large: ${formatBytes(stats.size)} (max: ${formatBytes(maxSizeBytes)}). Consider using Git LFS or removing the file.`,
108
+ };
109
+ return result;
110
+ }
111
+ return true;
112
+ }
113
+ catch (_error) {
114
+ // File doesn't exist or can't be accessed, skip it
115
+ return true;
116
+ }
117
+ });
118
+ //# sourceMappingURL=NoLargeBinaryFiles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NoLargeBinaryFiles.js","sourceRoot":"","sources":["../../src/policies/NoLargeBinaryFiles.ts"],"names":[],"mappings":"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","sourcesContent":["import { statSync } from \"node:fs\";\nimport path from \"pathe\";\nimport { makePolicyDefinition } from \"../makePolicy.js\";\nimport type { PolicyDefinition, PolicyFailure } from \"../policy.js\";\n\n/**\n * Policy settings for the NoLargeBinaryFiles repo policy.\n *\n * @alpha\n */\nexport interface NoLargeBinaryFilesSettings {\n\t/**\n\t * Maximum file size in bytes. Defaults to 10MB.\n\t */\n\tmaxSizeBytes?: number;\n\n\t/**\n\t * File extensions to exclude from size checking.\n\t * Useful for known large files that should be allowed.\n\t */\n\texcludeExtensions?: string[];\n\n\t/**\n\t * File patterns to exclude from size checking.\n\t * Uses glob-style patterns.\n\t */\n\texcludePatterns?: string[];\n}\n\n/**\n * Default maximum file size (10MB in bytes).\n */\nconst DEFAULT_MAX_SIZE = 10 * 1024 * 1024;\n\n/**\n * Default file extensions that are typically safe to exclude from size limits.\n */\nconst DEFAULT_EXCLUDE_EXTENSIONS = [\n\t\".md\",\n\t\".txt\",\n\t\".json\",\n\t\".js\",\n\t\".ts\",\n\t\".jsx\",\n\t\".tsx\",\n\t\".css\",\n\t\".scss\",\n\t\".sass\",\n\t\".less\",\n\t\".html\",\n\t\".xml\",\n\t\".yml\",\n\t\".yaml\",\n\t\".toml\",\n\t\".ini\",\n\t\".conf\",\n\t\".config\",\n];\n\n/**\n * Convert a glob pattern to a regex pattern by escaping special characters\n * and converting * to .* for wildcard matching.\n */\nfunction globToRegex(pattern: string): RegExp {\n\t// Escape all regex special characters except *\n\tconst escaped = pattern.replace(/[.+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n\t// Convert * to .* for wildcard matching\n\tconst regexPattern = escaped.replace(/\\*/g, \".*\");\n\treturn new RegExp(`^${regexPattern}$`);\n}\n\n/**\n * Check if a file matches any of the exclude patterns.\n */\nfunction isExcluded(\n\tfilePath: string,\n\texcludePatterns: string[],\n\texcludeExtensions: string[],\n): boolean {\n\tconst ext = path.extname(filePath).toLowerCase();\n\n\t// Check extensions\n\tif (excludeExtensions.includes(ext)) {\n\t\treturn true;\n\t}\n\n\t// Check patterns (simple glob-like matching)\n\tfor (const pattern of excludePatterns) {\n\t\tif (pattern.includes(\"*\")) {\n\t\t\tconst regex = globToRegex(pattern);\n\t\t\tif (regex.test(filePath)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t} else if (filePath.includes(pattern)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\n/**\n * Format bytes in a human-readable format.\n */\nfunction formatBytes(bytes: number): string {\n\tif (bytes === 0) {\n\t\treturn \"0 B\";\n\t}\n\tconst k = 1024;\n\tconst sizes = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\"];\n\tconst i = Math.min(\n\t\tMath.floor(Math.log(bytes) / Math.log(k)),\n\t\tsizes.length - 1,\n\t);\n\tconst formatted = (bytes / k ** i).toFixed(1);\n\treturn `${formatted} ${sizes[i]}`;\n}\n\n/**\n * A repo policy that prevents large binary files from being committed to the repository.\n * Large files can bloat the repository and slow down clones. Consider using Git LFS for large assets.\n *\n * @alpha\n */\nexport const NoLargeBinaryFiles: PolicyDefinition<NoLargeBinaryFilesSettings> =\n\tmakePolicyDefinition(\n\t\t\"NoLargeBinaryFiles\",\n\t\t// Match all files\n\t\t/.*/,\n\t\tasync ({ file, root, config }) => {\n\t\t\tconst maxSizeBytes = config?.maxSizeBytes ?? DEFAULT_MAX_SIZE;\n\t\t\tconst excludeExtensions =\n\t\t\t\tconfig?.excludeExtensions ?? DEFAULT_EXCLUDE_EXTENSIONS;\n\t\t\tconst excludePatterns = config?.excludePatterns ?? [];\n\n\t\t\t// Skip excluded files\n\t\t\tif (isExcluded(file, excludePatterns, excludeExtensions)) {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst filePath = path.resolve(root, file);\n\t\t\t\tconst stats = statSync(filePath);\n\n\t\t\t\t// Skip directories\n\t\t\t\tif (stats.isDirectory()) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\n\t\t\t\tif (stats.size > maxSizeBytes) {\n\t\t\t\t\tconst result: PolicyFailure = {\n\t\t\t\t\t\tname: NoLargeBinaryFiles.name,\n\t\t\t\t\t\tfile,\n\t\t\t\t\t\tautoFixable: false,\n\t\t\t\t\t\terrorMessage: `File is too large: ${formatBytes(stats.size)} (max: ${formatBytes(maxSizeBytes)}). Consider using Git LFS or removing the file.`,\n\t\t\t\t\t};\n\n\t\t\t\t\treturn result;\n\t\t\t\t}\n\n\t\t\t\treturn true;\n\t\t\t} catch (_error: unknown) {\n\t\t\t\t// File doesn't exist or can't be accessed, skip it\n\t\t\t\treturn true;\n\t\t\t}\n\t\t},\n\t);\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"PackageJsonRepoDirectoryProperty.d.ts","sourceRoot":"","sources":["../../src/policies/PackageJsonRepoDirectoryProperty.ts"],"names":[],"mappings":"AAOA;;;;;GAKG;AACH,eAAO,MAAM,gCAAgC,oDA+C3C,CAAC"}
1
+ {"version":3,"file":"PackageJsonRepoDirectoryProperty.d.ts","sourceRoot":"","sources":["../../src/policies/PackageJsonRepoDirectoryProperty.ts"],"names":[],"mappings":"AAOA;;;;;GAKG;AACH,eAAO,MAAM,gCAAgC,oDAgD3C,CAAC"}
@@ -21,31 +21,30 @@ export const PackageJsonRepoDirectoryProperty = definePackagePolicy("PackageJson
21
21
  const pkgDir = path.dirname(file);
22
22
  const maybeDir = path.relative(root, pkgDir);
23
23
  const relativePkgDir = maybeDir === "" ? undefined : maybeDir;
24
- if (typeof json.repository === "object") {
25
- if (json.repository.directory !== relativePkgDir) {
26
- if (resolve) {
27
- try {
28
- updatePackageJsonFile(file, (json) => {
29
- assert(typeof json.repository === "object");
30
- if (relativePkgDir === undefined) {
31
- // biome-ignore lint/performance/noDelete: <explanation>
32
- delete json.repository.directory;
33
- }
34
- else {
35
- json.repository.directory = relativePkgDir;
36
- }
37
- });
38
- fixResult.resolved = true;
39
- }
40
- catch (error) {
41
- fixResult.resolved = false;
42
- fixResult.errorMessage = `${error.message}\n${error.stack}`;
43
- }
44
- return fixResult;
24
+ if (typeof json.repository === "object" &&
25
+ json.repository.directory !== relativePkgDir) {
26
+ if (resolve) {
27
+ try {
28
+ // biome-ignore lint/nursery/noShadow: no need to use shadowed variable
29
+ updatePackageJsonFile(file, (json) => {
30
+ assert(typeof json.repository === "object");
31
+ if (relativePkgDir === undefined) {
32
+ delete json.repository.directory;
33
+ }
34
+ else {
35
+ json.repository.directory = relativePkgDir;
36
+ }
37
+ });
38
+ fixResult.resolved = true;
45
39
  }
46
- failResult.errorMessage = `repository.directory value is wrong. Expected '${relativePkgDir}', got '${json.repository.directory}'`;
47
- return failResult;
40
+ catch (error) {
41
+ fixResult.resolved = false;
42
+ fixResult.errorMessage = `${error.message}\n${error.stack}`;
43
+ }
44
+ return fixResult;
48
45
  }
46
+ failResult.errorMessage = `repository.directory value is wrong. Expected '${relativePkgDir}', got '${json.repository.directory}'`;
47
+ return failResult;
49
48
  }
50
49
  return true;
51
50
  });
@@ -1 +1 @@
1
- {"version":3,"file":"PackageJsonRepoDirectoryProperty.js","sourceRoot":"","sources":["../../src/policies/PackageJsonRepoDirectoryProperty.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,qBAAqB,EAAE,MAAM,mCAAmC,CAAC;AAC1E,OAAO,IAAI,MAAM,OAAO,CAAC;AAGzB,OAAO,EAAE,mBAAmB,EAAE,MAAM,0CAA0C,CAAC;AAE/E;;;;;GAKG;AACH,MAAM,CAAC,MAAM,gCAAgC,GAAG,mBAAmB,CAGjE,kCAAkC,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE;IAC7E,MAAM,UAAU,GAAkB;QACjC,IAAI,EAAE,gCAAgC,CAAC,IAAI;QAC3C,IAAI;QACJ,WAAW,EAAE,IAAI;KACjB,CAAC;IAEF,MAAM,SAAS,GAAoB;QAClC,GAAG,UAAU;QACb,QAAQ,EAAE,KAAK;KACf,CAAC;IAEF,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC7C,MAAM,cAAc,GAAG,QAAQ,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC;IAE9D,IAAI,OAAO,IAAI,CAAC,UAAU,KAAK,QAAQ,EAAE,CAAC;QACzC,IAAI,IAAI,CAAC,UAAU,CAAC,SAAS,KAAK,cAAc,EAAE,CAAC;YAClD,IAAI,OAAO,EAAE,CAAC;gBACb,IAAI,CAAC;oBACJ,qBAAqB,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE;wBACpC,MAAM,CAAC,OAAO,IAAI,CAAC,UAAU,KAAK,QAAQ,CAAC,CAAC;wBAC5C,IAAI,cAAc,KAAK,SAAS,EAAE,CAAC;4BAClC,wDAAwD;4BACxD,OAAO,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;wBAClC,CAAC;6BAAM,CAAC;4BACP,IAAI,CAAC,UAAU,CAAC,SAAS,GAAG,cAAc,CAAC;wBAC5C,CAAC;oBACF,CAAC,CAAC,CAAC;oBACH,SAAS,CAAC,QAAQ,GAAG,IAAI,CAAC;gBAC3B,CAAC;gBAAC,OAAO,KAAc,EAAE,CAAC;oBACzB,SAAS,CAAC,QAAQ,GAAG,KAAK,CAAC;oBAC3B,SAAS,CAAC,YAAY,GAAG,GAAI,KAAe,CAAC,OAAO,KAClD,KAAe,CAAC,KAClB,EAAE,CAAC;gBACJ,CAAC;gBACD,OAAO,SAAS,CAAC;YAClB,CAAC;YACD,UAAU,CAAC,YAAY,GAAG,kDAAkD,cAAc,WAAW,IAAI,CAAC,UAAU,CAAC,SAAS,GAAG,CAAC;YAClI,OAAO,UAAU,CAAC;QACnB,CAAC;IACF,CAAC;IAED,OAAO,IAAI,CAAC;AACb,CAAC,CAAC,CAAC","sourcesContent":["import assert from \"node:assert/strict\";\nimport { updatePackageJsonFile } from \"@fluid-tools/build-infrastructure\";\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/**\n * A RepoPolicy that checks that the repository.directory property in package.json is set correctly. If the repository\n * field is a string instead of an object the package will be ignored.\n *\n * @alpha\n */\nexport const PackageJsonRepoDirectoryProperty = definePackagePolicy<\n\tPackageJson,\n\tundefined\n>(\"PackageJsonRepoDirectoryProperty\", async (json, { file, root, resolve }) => {\n\tconst failResult: PolicyFailure = {\n\t\tname: PackageJsonRepoDirectoryProperty.name,\n\t\tfile,\n\t\tautoFixable: true,\n\t};\n\n\tconst fixResult: PolicyFixResult = {\n\t\t...failResult,\n\t\tresolved: false,\n\t};\n\n\tconst pkgDir = path.dirname(file);\n\tconst maybeDir = path.relative(root, pkgDir);\n\tconst relativePkgDir = maybeDir === \"\" ? undefined : maybeDir;\n\n\tif (typeof json.repository === \"object\") {\n\t\tif (json.repository.directory !== relativePkgDir) {\n\t\t\tif (resolve) {\n\t\t\t\ttry {\n\t\t\t\t\tupdatePackageJsonFile(file, (json) => {\n\t\t\t\t\t\tassert(typeof json.repository === \"object\");\n\t\t\t\t\t\tif (relativePkgDir === undefined) {\n\t\t\t\t\t\t\t// biome-ignore lint/performance/noDelete: <explanation>\n\t\t\t\t\t\t\tdelete json.repository.directory;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tjson.repository.directory = relativePkgDir;\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t\tfixResult.resolved = true;\n\t\t\t\t} catch (error: unknown) {\n\t\t\t\t\tfixResult.resolved = false;\n\t\t\t\t\tfixResult.errorMessage = `${(error as Error).message}\\n${\n\t\t\t\t\t\t(error as Error).stack\n\t\t\t\t\t}`;\n\t\t\t\t}\n\t\t\t\treturn fixResult;\n\t\t\t}\n\t\t\tfailResult.errorMessage = `repository.directory value is wrong. Expected '${relativePkgDir}', got '${json.repository.directory}'`;\n\t\t\treturn failResult;\n\t\t}\n\t}\n\n\treturn true;\n});\n"]}
1
+ {"version":3,"file":"PackageJsonRepoDirectoryProperty.js","sourceRoot":"","sources":["../../src/policies/PackageJsonRepoDirectoryProperty.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,qBAAqB,EAAE,MAAM,mCAAmC,CAAC;AAC1E,OAAO,IAAI,MAAM,OAAO,CAAC;AAGzB,OAAO,EAAE,mBAAmB,EAAE,MAAM,0CAA0C,CAAC;AAE/E;;;;;GAKG;AACH,MAAM,CAAC,MAAM,gCAAgC,GAAG,mBAAmB,CAGjE,kCAAkC,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE;IAC7E,MAAM,UAAU,GAAkB;QACjC,IAAI,EAAE,gCAAgC,CAAC,IAAI;QAC3C,IAAI;QACJ,WAAW,EAAE,IAAI;KACjB,CAAC;IAEF,MAAM,SAAS,GAAoB;QAClC,GAAG,UAAU;QACb,QAAQ,EAAE,KAAK;KACf,CAAC;IAEF,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC7C,MAAM,cAAc,GAAG,QAAQ,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC;IAE9D,IACC,OAAO,IAAI,CAAC,UAAU,KAAK,QAAQ;QACnC,IAAI,CAAC,UAAU,CAAC,SAAS,KAAK,cAAc,EAC3C,CAAC;QACF,IAAI,OAAO,EAAE,CAAC;YACb,IAAI,CAAC;gBACJ,uEAAuE;gBACvE,qBAAqB,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE;oBACpC,MAAM,CAAC,OAAO,IAAI,CAAC,UAAU,KAAK,QAAQ,CAAC,CAAC;oBAC5C,IAAI,cAAc,KAAK,SAAS,EAAE,CAAC;wBAClC,OAAO,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;oBAClC,CAAC;yBAAM,CAAC;wBACP,IAAI,CAAC,UAAU,CAAC,SAAS,GAAG,cAAc,CAAC;oBAC5C,CAAC;gBACF,CAAC,CAAC,CAAC;gBACH,SAAS,CAAC,QAAQ,GAAG,IAAI,CAAC;YAC3B,CAAC;YAAC,OAAO,KAAc,EAAE,CAAC;gBACzB,SAAS,CAAC,QAAQ,GAAG,KAAK,CAAC;gBAC3B,SAAS,CAAC,YAAY,GAAG,GAAI,KAAe,CAAC,OAAO,KAClD,KAAe,CAAC,KAClB,EAAE,CAAC;YACJ,CAAC;YACD,OAAO,SAAS,CAAC;QAClB,CAAC;QACD,UAAU,CAAC,YAAY,GAAG,kDAAkD,cAAc,WAAW,IAAI,CAAC,UAAU,CAAC,SAAS,GAAG,CAAC;QAClI,OAAO,UAAU,CAAC;IACnB,CAAC;IAED,OAAO,IAAI,CAAC;AACb,CAAC,CAAC,CAAC","sourcesContent":["import assert from \"node:assert/strict\";\nimport { updatePackageJsonFile } from \"@fluid-tools/build-infrastructure\";\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/**\n * A RepoPolicy that checks that the repository.directory property in package.json is set correctly. If the repository\n * field is a string instead of an object the package will be ignored.\n *\n * @alpha\n */\nexport const PackageJsonRepoDirectoryProperty = definePackagePolicy<\n\tPackageJson,\n\tundefined\n>(\"PackageJsonRepoDirectoryProperty\", async (json, { file, root, resolve }) => {\n\tconst failResult: PolicyFailure = {\n\t\tname: PackageJsonRepoDirectoryProperty.name,\n\t\tfile,\n\t\tautoFixable: true,\n\t};\n\n\tconst fixResult: PolicyFixResult = {\n\t\t...failResult,\n\t\tresolved: false,\n\t};\n\n\tconst pkgDir = path.dirname(file);\n\tconst maybeDir = path.relative(root, pkgDir);\n\tconst relativePkgDir = maybeDir === \"\" ? undefined : maybeDir;\n\n\tif (\n\t\ttypeof json.repository === \"object\" &&\n\t\tjson.repository.directory !== relativePkgDir\n\t) {\n\t\tif (resolve) {\n\t\t\ttry {\n\t\t\t\t// biome-ignore lint/nursery/noShadow: no need to use shadowed variable\n\t\t\t\tupdatePackageJsonFile(file, (json) => {\n\t\t\t\t\tassert(typeof json.repository === \"object\");\n\t\t\t\t\tif (relativePkgDir === undefined) {\n\t\t\t\t\t\tdelete json.repository.directory;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tjson.repository.directory = relativePkgDir;\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tfixResult.resolved = true;\n\t\t\t} catch (error: unknown) {\n\t\t\t\tfixResult.resolved = false;\n\t\t\t\tfixResult.errorMessage = `${(error as Error).message}\\n${\n\t\t\t\t\t(error as Error).stack\n\t\t\t\t}`;\n\t\t\t}\n\t\t\treturn fixResult;\n\t\t}\n\t\tfailResult.errorMessage = `repository.directory value is wrong. Expected '${relativePkgDir}', got '${json.repository.directory}'`;\n\t\treturn failResult;\n\t}\n\n\treturn true;\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"PackageJsonSorted.d.ts","sourceRoot":"","sources":["../../src/policies/PackageJsonSorted.ts"],"names":[],"mappings":"AAKA;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,oDAsC7B,CAAC"}
1
+ {"version":3,"file":"PackageJsonSorted.d.ts","sourceRoot":"","sources":["../../src/policies/PackageJsonSorted.ts"],"names":[],"mappings":"AAKA;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,oDAuC7B,CAAC"}
@@ -14,6 +14,7 @@ export const PackageJsonSorted = definePackagePolicy("PackageJsonSorted", async
14
14
  }
15
15
  if (resolve) {
16
16
  try {
17
+ // biome-ignore lint/nursery/noShadow: no need to use the shadowed variable
17
18
  await updatePackageJsonFile(file, (json) => json, { sort: true });
18
19
  const result = {
19
20
  name: PackageJsonSorted.name,
@@ -1 +1 @@
1
- {"version":3,"file":"PackageJsonSorted.js","sourceRoot":"","sources":["../../src/policies/PackageJsonSorted.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,0CAA0C,CAAC;AAE/E;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,mBAAmB,CACnD,mBAAmB,EACnB,KAAK,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE;IACjC,MAAM,UAAU,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;IACzC,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IAErE,IAAI,QAAQ,EAAE,CAAC;QACd,OAAO,IAAI,CAAC;IACb,CAAC;IAED,IAAI,OAAO,EAAE,CAAC;QACb,IAAI,CAAC;YACJ,MAAM,qBAAqB,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YAClE,MAAM,MAAM,GAAoB;gBAC/B,IAAI,EAAE,iBAAiB,CAAC,IAAI;gBAC5B,IAAI;gBACJ,QAAQ,EAAE,IAAI;aACd,CAAC;YACF,OAAO,MAAM,CAAC;QACf,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACzB,MAAM,MAAM,GAAoB;gBAC/B,IAAI,EAAE,iBAAiB,CAAC,IAAI;gBAC5B,IAAI;gBACJ,QAAQ,EAAE,KAAK;gBACf,WAAW,EAAE,IAAI;gBACjB,YAAY,EAAG,KAAe,CAAC,OAAO;aACtC,CAAC;YACF,OAAO,MAAM,CAAC;QACf,CAAC;IACF,CAAC;SAAM,CAAC;QACP,MAAM,MAAM,GAAkB;YAC7B,IAAI,EAAE,iBAAiB,CAAC,IAAI;YAC5B,IAAI;YACJ,WAAW,EAAE,IAAI;SACjB,CAAC;QACF,OAAO,MAAM,CAAC;IACf,CAAC;AACF,CAAC,CACD,CAAC","sourcesContent":["import { updatePackageJsonFile } from \"@tylerbu/cli-api\";\nimport { sortPackageJson } from \"sort-package-json\";\nimport type { PolicyFailure, PolicyFixResult } from \"../policy.js\";\nimport { definePackagePolicy } from \"../policyDefiners/definePackagePolicy.js\";\n\n/**\n * A repo policy that checks if package.json files in the repo are sorted using sort-package-json.\n *\n * @alpha\n */\nexport const PackageJsonSorted = definePackagePolicy(\n\t\"PackageJsonSorted\",\n\tasync (json, { file, resolve }) => {\n\t\tconst sortedJson = sortPackageJson(json);\n\t\tconst isSorted = JSON.stringify(sortedJson) === JSON.stringify(json);\n\n\t\tif (isSorted) {\n\t\t\treturn true;\n\t\t}\n\n\t\tif (resolve) {\n\t\t\ttry {\n\t\t\t\tawait updatePackageJsonFile(file, (json) => json, { sort: true });\n\t\t\t\tconst result: PolicyFixResult = {\n\t\t\t\t\tname: PackageJsonSorted.name,\n\t\t\t\t\tfile,\n\t\t\t\t\tresolved: true,\n\t\t\t\t};\n\t\t\t\treturn result;\n\t\t\t} catch (error: unknown) {\n\t\t\t\tconst result: PolicyFixResult = {\n\t\t\t\t\tname: PackageJsonSorted.name,\n\t\t\t\t\tfile,\n\t\t\t\t\tresolved: false,\n\t\t\t\t\tautoFixable: true,\n\t\t\t\t\terrorMessage: (error as Error).message,\n\t\t\t\t};\n\t\t\t\treturn result;\n\t\t\t}\n\t\t} else {\n\t\t\tconst result: PolicyFailure = {\n\t\t\t\tname: PackageJsonSorted.name,\n\t\t\t\tfile,\n\t\t\t\tautoFixable: true,\n\t\t\t};\n\t\t\treturn result;\n\t\t}\n\t},\n);\n"]}
1
+ {"version":3,"file":"PackageJsonSorted.js","sourceRoot":"","sources":["../../src/policies/PackageJsonSorted.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,0CAA0C,CAAC;AAE/E;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,mBAAmB,CACnD,mBAAmB,EACnB,KAAK,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE;IACjC,MAAM,UAAU,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;IACzC,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IAErE,IAAI,QAAQ,EAAE,CAAC;QACd,OAAO,IAAI,CAAC;IACb,CAAC;IAED,IAAI,OAAO,EAAE,CAAC;QACb,IAAI,CAAC;YACJ,2EAA2E;YAC3E,MAAM,qBAAqB,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YAClE,MAAM,MAAM,GAAoB;gBAC/B,IAAI,EAAE,iBAAiB,CAAC,IAAI;gBAC5B,IAAI;gBACJ,QAAQ,EAAE,IAAI;aACd,CAAC;YACF,OAAO,MAAM,CAAC;QACf,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACzB,MAAM,MAAM,GAAoB;gBAC/B,IAAI,EAAE,iBAAiB,CAAC,IAAI;gBAC5B,IAAI;gBACJ,QAAQ,EAAE,KAAK;gBACf,WAAW,EAAE,IAAI;gBACjB,YAAY,EAAG,KAAe,CAAC,OAAO;aACtC,CAAC;YACF,OAAO,MAAM,CAAC;QACf,CAAC;IACF,CAAC;SAAM,CAAC;QACP,MAAM,MAAM,GAAkB;YAC7B,IAAI,EAAE,iBAAiB,CAAC,IAAI;YAC5B,IAAI;YACJ,WAAW,EAAE,IAAI;SACjB,CAAC;QACF,OAAO,MAAM,CAAC;IACf,CAAC;AACF,CAAC,CACD,CAAC","sourcesContent":["import { updatePackageJsonFile } from \"@tylerbu/cli-api\";\nimport { sortPackageJson } from \"sort-package-json\";\nimport type { PolicyFailure, PolicyFixResult } from \"../policy.js\";\nimport { definePackagePolicy } from \"../policyDefiners/definePackagePolicy.js\";\n\n/**\n * A repo policy that checks if package.json files in the repo are sorted using sort-package-json.\n *\n * @alpha\n */\nexport const PackageJsonSorted = definePackagePolicy(\n\t\"PackageJsonSorted\",\n\tasync (json, { file, resolve }) => {\n\t\tconst sortedJson = sortPackageJson(json);\n\t\tconst isSorted = JSON.stringify(sortedJson) === JSON.stringify(json);\n\n\t\tif (isSorted) {\n\t\t\treturn true;\n\t\t}\n\n\t\tif (resolve) {\n\t\t\ttry {\n\t\t\t\t// biome-ignore lint/nursery/noShadow: no need to use the shadowed variable\n\t\t\t\tawait updatePackageJsonFile(file, (json) => json, { sort: true });\n\t\t\t\tconst result: PolicyFixResult = {\n\t\t\t\t\tname: PackageJsonSorted.name,\n\t\t\t\t\tfile,\n\t\t\t\t\tresolved: true,\n\t\t\t\t};\n\t\t\t\treturn result;\n\t\t\t} catch (error: unknown) {\n\t\t\t\tconst result: PolicyFixResult = {\n\t\t\t\t\tname: PackageJsonSorted.name,\n\t\t\t\t\tfile,\n\t\t\t\t\tresolved: false,\n\t\t\t\t\tautoFixable: true,\n\t\t\t\t\terrorMessage: (error as Error).message,\n\t\t\t\t};\n\t\t\t\treturn result;\n\t\t\t}\n\t\t} else {\n\t\t\tconst result: PolicyFailure = {\n\t\t\t\tname: PackageJsonSorted.name,\n\t\t\t\tfile,\n\t\t\t\tautoFixable: true,\n\t\t\t};\n\t\t\treturn result;\n\t\t}\n\t},\n);\n"]}
@@ -1,7 +1,30 @@
1
1
  /**
2
- * A RepoPolicy that checks that package.json properties in packages match expected values.
2
+ * Policy settings for the PackageScripts repo policy.
3
3
  *
4
4
  * @alpha
5
5
  */
6
- export declare const PackageScripts: import("../policy.js").PolicyDefinition<undefined>;
6
+ export interface PackageScriptsSettings {
7
+ /**
8
+ * List of script names that must be present in package.json scripts.
9
+ */
10
+ must?: string[];
11
+ /**
12
+ * List of mutually exclusive script groups. Each group is an array of script names
13
+ * where at most one script from the group can be present.
14
+ *
15
+ * Packages can have zero scripts from the group (all optional), but cannot have
16
+ * multiple scripts from the same group.
17
+ *
18
+ * @example
19
+ * // Allow "test:unit" OR "test:vitest" OR neither, but not both
20
+ * mutuallyExclusive: [["test:unit", "test:vitest"]]
21
+ */
22
+ mutuallyExclusive?: string[][];
23
+ }
24
+ /**
25
+ * A RepoPolicy that validates package.json scripts based on configurable rules.
26
+ *
27
+ * @alpha
28
+ */
29
+ export declare const PackageScripts: import("../policy.js").PolicyDefinition<PackageScriptsSettings | undefined>;
7
30
  //# sourceMappingURL=PackageScripts.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"PackageScripts.d.ts","sourceRoot":"","sources":["../../src/policies/PackageScripts.ts"],"names":[],"mappings":"AAKA;;;;GAIG;AACH,eAAO,MAAM,cAAc,oDA+B1B,CAAC"}
1
+ {"version":3,"file":"PackageScripts.d.ts","sourceRoot":"","sources":["../../src/policies/PackageScripts.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACtC;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAEhB;;;;;;;;;;OAUG;IACH,iBAAiB,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC;CAC/B;AAgDD;;;;GAIG;AACH,eAAO,MAAM,cAAc,6EAoCzB,CAAC"}
@@ -1,23 +1,60 @@
1
1
  import { definePackagePolicy } from "../policyDefiners/definePackagePolicy.js";
2
- const expectedScripts = ["build", "clean"];
3
2
  /**
4
- * A RepoPolicy that checks that package.json properties in packages match expected values.
3
+ * Validates required scripts and returns error messages for missing scripts.
4
+ */
5
+ function validateRequiredScripts(must, scripts) {
6
+ const missingScripts = [];
7
+ for (const scriptName of must) {
8
+ if (!(scripts && Object.hasOwn(scripts, scriptName))) {
9
+ missingScripts.push(scriptName);
10
+ }
11
+ }
12
+ if (missingScripts.length > 0) {
13
+ return [`Missing required scripts:\n\t${missingScripts.join("\n\t")}`];
14
+ }
15
+ return [];
16
+ }
17
+ /**
18
+ * Validates mutually exclusive script groups and returns error messages for violations.
19
+ */
20
+ function validateMutuallyExclusiveScripts(groups, scripts) {
21
+ const errors = [];
22
+ for (const group of groups) {
23
+ const presentScripts = group.filter((scriptName) => scripts && Object.hasOwn(scripts, scriptName));
24
+ // Only fail if MORE than one script from the group is present
25
+ if (presentScripts.length > 1) {
26
+ errors.push(`Scripts are mutually exclusive, but found ${presentScripts.length}: ${presentScripts.join(", ")}`);
27
+ }
28
+ }
29
+ return errors;
30
+ }
31
+ /**
32
+ * A RepoPolicy that validates package.json scripts based on configurable rules.
5
33
  *
6
34
  * @alpha
7
35
  */
8
- export const PackageScripts = definePackagePolicy("PackageScripts", async (json, { file }) => {
36
+ export const PackageScripts = definePackagePolicy("PackageScripts", async (json, { file, config }) => {
9
37
  const failResult = {
10
38
  name: PackageScripts.name,
11
39
  file,
12
40
  autoFixable: false,
13
41
  };
14
- const hasScriptsField = Object.prototype.hasOwnProperty.call(json, "scripts");
15
- const missingScripts = [];
16
- if (hasScriptsField) {
17
- missingScripts.push(...expectedScripts.filter((script) => !Object.prototype.hasOwnProperty.call(json.scripts, script)));
42
+ if (config === undefined) {
43
+ return true;
18
44
  }
19
- if (missingScripts.length > 0) {
20
- failResult.errorMessage = `missing the following scripts: \n\t${missingScripts.join("\n\t")}`;
45
+ const hasScriptsField = Object.hasOwn(json, "scripts");
46
+ const scripts = hasScriptsField ? json.scripts : {};
47
+ const errorMessages = [];
48
+ // Validate required scripts
49
+ if (config.must && config.must.length > 0) {
50
+ errorMessages.push(...validateRequiredScripts(config.must, scripts));
51
+ }
52
+ // Validate mutually exclusive script groups
53
+ if (config.mutuallyExclusive && config.mutuallyExclusive.length > 0) {
54
+ errorMessages.push(...validateMutuallyExclusiveScripts(config.mutuallyExclusive, scripts));
55
+ }
56
+ if (errorMessages.length > 0) {
57
+ failResult.errorMessage = errorMessages.join("\n\n");
21
58
  return failResult;
22
59
  }
23
60
  return true;
@@ -1 +1 @@
1
- {"version":3,"file":"PackageScripts.js","sourceRoot":"","sources":["../../src/policies/PackageScripts.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,0CAA0C,CAAC;AAE/E,MAAM,eAAe,GAAG,CAAC,OAAO,EAAE,OAAO,CAAU,CAAC;AAEpD;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,mBAAmB,CAChD,gBAAgB,EAChB,KAAK,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;IACxB,MAAM,UAAU,GAAkB;QACjC,IAAI,EAAE,cAAc,CAAC,IAAI;QACzB,IAAI;QACJ,WAAW,EAAE,KAAK;KAClB,CAAC;IAEF,MAAM,eAAe,GAAG,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAC3D,IAAI,EACJ,SAAS,CACT,CAAC;IAEF,MAAM,cAAc,GAAa,EAAE,CAAC;IACpC,IAAI,eAAe,EAAE,CAAC;QACrB,cAAc,CAAC,IAAI,CAClB,GAAG,eAAe,CAAC,MAAM,CACxB,CAAC,MAAM,EAAE,EAAE,CACV,CAAC,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAC5D,CACD,CAAC;IACH,CAAC;IAED,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC/B,UAAU,CAAC,YAAY,GAAG,sCAAsC,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QAC9F,OAAO,UAAU,CAAC;IACnB,CAAC;IAED,OAAO,IAAI,CAAC;AACb,CAAC,CACD,CAAC","sourcesContent":["import type { PolicyFailure } from \"../policy.js\";\nimport { definePackagePolicy } from \"../policyDefiners/definePackagePolicy.js\";\n\nconst expectedScripts = [\"build\", \"clean\"] as const;\n\n/**\n * A RepoPolicy that checks that package.json properties in packages match expected values.\n *\n * @alpha\n */\nexport const PackageScripts = definePackagePolicy(\n\t\"PackageScripts\",\n\tasync (json, { file }) => {\n\t\tconst failResult: PolicyFailure = {\n\t\t\tname: PackageScripts.name,\n\t\t\tfile,\n\t\t\tautoFixable: false,\n\t\t};\n\n\t\tconst hasScriptsField = Object.prototype.hasOwnProperty.call(\n\t\t\tjson,\n\t\t\t\"scripts\",\n\t\t);\n\n\t\tconst missingScripts: string[] = [];\n\t\tif (hasScriptsField) {\n\t\t\tmissingScripts.push(\n\t\t\t\t...expectedScripts.filter(\n\t\t\t\t\t(script) =>\n\t\t\t\t\t\t!Object.prototype.hasOwnProperty.call(json.scripts, script),\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\n\t\tif (missingScripts.length > 0) {\n\t\t\tfailResult.errorMessage = `missing the following scripts: \\n\\t${missingScripts.join(\"\\n\\t\")}`;\n\t\t\treturn failResult;\n\t\t}\n\n\t\treturn true;\n\t},\n);\n"]}
1
+ {"version":3,"file":"PackageScripts.js","sourceRoot":"","sources":["../../src/policies/PackageScripts.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,MAAM,0CAA0C,CAAC;AA2B/E;;GAEG;AACH,SAAS,uBAAuB,CAC/B,IAAc,EACd,OAA+B;IAE/B,MAAM,cAAc,GAAa,EAAE,CAAC;IACpC,KAAK,MAAM,UAAU,IAAI,IAAI,EAAE,CAAC;QAC/B,IAAI,CAAC,CAAC,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC;YACtD,cAAc,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACjC,CAAC;IACF,CAAC;IAED,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC/B,OAAO,CAAC,gCAAgC,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACxE,CAAC;IAED,OAAO,EAAE,CAAC;AACX,CAAC;AAED;;GAEG;AACH,SAAS,gCAAgC,CACxC,MAAkB,EAClB,OAA+B;IAE/B,MAAM,MAAM,GAAa,EAAE,CAAC;IAE5B,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC5B,MAAM,cAAc,GAAG,KAAK,CAAC,MAAM,CAClC,CAAC,UAAU,EAAE,EAAE,CAAC,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,CAC7D,CAAC;QAEF,8DAA8D;QAC9D,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC/B,MAAM,CAAC,IAAI,CACV,6CAA6C,cAAc,CAAC,MAAM,KAAK,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAClG,CAAC;QACH,CAAC;IACF,CAAC;IAED,OAAO,MAAM,CAAC;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,mBAAmB,CAG/C,gBAAgB,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE;IACpD,MAAM,UAAU,GAAkB;QACjC,IAAI,EAAE,cAAc,CAAC,IAAI;QACzB,IAAI;QACJ,WAAW,EAAE,KAAK;KAClB,CAAC;IAEF,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QAC1B,OAAO,IAAI,CAAC;IACb,CAAC;IAED,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IACvD,MAAM,OAAO,GAAG,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IACpD,MAAM,aAAa,GAAa,EAAE,CAAC;IAEnC,4BAA4B;IAC5B,IAAI,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3C,aAAa,CAAC,IAAI,CAAC,GAAG,uBAAuB,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;IACtE,CAAC;IAED,4CAA4C;IAC5C,IAAI,MAAM,CAAC,iBAAiB,IAAI,MAAM,CAAC,iBAAiB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrE,aAAa,CAAC,IAAI,CACjB,GAAG,gCAAgC,CAAC,MAAM,CAAC,iBAAiB,EAAE,OAAO,CAAC,CACtE,CAAC;IACH,CAAC;IAED,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9B,UAAU,CAAC,YAAY,GAAG,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACrD,OAAO,UAAU,CAAC;IACnB,CAAC;IAED,OAAO,IAAI,CAAC;AACb,CAAC,CAAC,CAAC","sourcesContent":["import type { PackageJson } from \"type-fest\";\nimport type { PolicyFailure } from \"../policy.js\";\nimport { definePackagePolicy } from \"../policyDefiners/definePackagePolicy.js\";\n\n/**\n * Policy settings for the PackageScripts repo policy.\n *\n * @alpha\n */\nexport interface PackageScriptsSettings {\n\t/**\n\t * List of script names that must be present in package.json scripts.\n\t */\n\tmust?: 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 * // Allow \"test:unit\" OR \"test:vitest\" OR neither, but not both\n\t * mutuallyExclusive: [[\"test:unit\", \"test:vitest\"]]\n\t */\n\tmutuallyExclusive?: string[][];\n}\n\n/**\n * Validates required scripts and returns error messages for missing scripts.\n */\nfunction validateRequiredScripts(\n\tmust: string[],\n\tscripts: PackageJson[\"scripts\"],\n): string[] {\n\tconst missingScripts: string[] = [];\n\tfor (const scriptName of must) {\n\t\tif (!(scripts && Object.hasOwn(scripts, scriptName))) {\n\t\t\tmissingScripts.push(scriptName);\n\t\t}\n\t}\n\n\tif (missingScripts.length > 0) {\n\t\treturn [`Missing required scripts:\\n\\t${missingScripts.join(\"\\n\\t\")}`];\n\t}\n\n\treturn [];\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 * A RepoPolicy that validates package.json scripts based on configurable rules.\n *\n * @alpha\n */\nexport const PackageScripts = definePackagePolicy<\n\tPackageJson,\n\tPackageScriptsSettings | undefined\n>(\"PackageScripts\", async (json, { file, config }) => {\n\tconst failResult: PolicyFailure = {\n\t\tname: PackageScripts.name,\n\t\tfile,\n\t\tautoFixable: false,\n\t};\n\n\tif (config === undefined) {\n\t\treturn true;\n\t}\n\n\tconst hasScriptsField = Object.hasOwn(json, \"scripts\");\n\tconst scripts = hasScriptsField ? json.scripts : {};\n\tconst errorMessages: string[] = [];\n\n\t// Validate required scripts\n\tif (config.must && config.must.length > 0) {\n\t\terrorMessages.push(...validateRequiredScripts(config.must, scripts));\n\t}\n\n\t// Validate mutually exclusive script groups\n\tif (config.mutuallyExclusive && config.mutuallyExclusive.length > 0) {\n\t\terrorMessages.push(\n\t\t\t...validateMutuallyExclusiveScripts(config.mutuallyExclusive, scripts),\n\t\t);\n\t}\n\n\tif (errorMessages.length > 0) {\n\t\tfailResult.errorMessage = errorMessages.join(\"\\n\\n\");\n\t\treturn failResult;\n\t}\n\n\treturn true;\n});\n"]}
@@ -0,0 +1,24 @@
1
+ import type { PolicyDefinition } from "../policy.js";
2
+ /**
3
+ * Policy settings for the RequiredGitignorePatterns repo policy.
4
+ *
5
+ * @alpha
6
+ */
7
+ export interface RequiredGitignorePatternsSettings {
8
+ /**
9
+ * Array of patterns that must be present in .gitignore.
10
+ * Each pattern can be a string or an object with pattern and comment.
11
+ */
12
+ patterns: Array<string | {
13
+ pattern: string;
14
+ comment?: string;
15
+ }>;
16
+ }
17
+ /**
18
+ * A repo policy that ensures .gitignore contains required patterns to prevent
19
+ * committing sensitive files, dependencies, and build artifacts.
20
+ *
21
+ * @alpha
22
+ */
23
+ export declare const RequiredGitignorePatterns: PolicyDefinition<RequiredGitignorePatternsSettings>;
24
+ //# sourceMappingURL=RequiredGitignorePatterns.d.ts.map
@@ -0,0 +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;AAmMD;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,EAAE,gBAAgB,CAAC,iCAAiC,CAcxF,CAAC"}
@@ -0,0 +1,166 @@
1
+ import { readFileSync, writeFileSync } from "node:fs";
2
+ import path from "pathe";
3
+ import { makePolicyDefinition } from "../makePolicy.js";
4
+ /**
5
+ * Default patterns that should typically be in .gitignore files.
6
+ */
7
+ const DEFAULT_PATTERNS = [
8
+ { pattern: "node_modules/", comment: "Dependencies" },
9
+ { pattern: ".env", comment: "Environment files" },
10
+ { pattern: ".env.*", comment: "Environment files" },
11
+ { pattern: "!.env.example", comment: "Allow example env file" },
12
+ { pattern: "dist/", comment: "Build output" },
13
+ { pattern: "build/", comment: "Build output" },
14
+ { pattern: ".DS_Store", comment: "macOS system files" },
15
+ { pattern: "Thumbs.db", comment: "Windows system files" },
16
+ ];
17
+ /**
18
+ * Check if a pattern exists in gitignore content.
19
+ * Handles patterns with inline comments (e.g., "node_modules/ # comment").
20
+ */
21
+ function patternExists(lines, pattern) {
22
+ return lines.some((line) => {
23
+ const trimmed = line.trim();
24
+ // Remove inline comments (everything after # that's not escaped)
25
+ const withoutComment = trimmed.split("#")[0]?.trim() ?? "";
26
+ return (withoutComment === pattern ||
27
+ trimmed === pattern ||
28
+ trimmed.startsWith(`${pattern} `) ||
29
+ trimmed.startsWith(`${pattern}\t`));
30
+ });
31
+ }
32
+ /**
33
+ * Find missing patterns in gitignore content.
34
+ */
35
+ function findMissingPatterns(patterns, lines) {
36
+ const missingPatterns = [];
37
+ for (const patternConfig of patterns) {
38
+ const pattern = typeof patternConfig === "string" ? patternConfig : patternConfig.pattern;
39
+ if (!patternExists(lines, pattern)) {
40
+ missingPatterns.push(patternConfig);
41
+ }
42
+ }
43
+ return missingPatterns;
44
+ }
45
+ /**
46
+ * Add patterns to gitignore content.
47
+ */
48
+ function addPatternsToContent(content, missingPatterns) {
49
+ let newContent = content;
50
+ // Ensure file ends with newline
51
+ if (!newContent.endsWith("\n")) {
52
+ newContent += "\n";
53
+ }
54
+ // Add a section header if we're adding patterns
55
+ if (missingPatterns.length > 0) {
56
+ newContent += "\n# Added by repopo RequiredGitignorePatterns policy\n";
57
+ }
58
+ // Add each missing pattern
59
+ for (const patternConfig of missingPatterns) {
60
+ if (typeof patternConfig === "string") {
61
+ newContent += `${patternConfig}\n`;
62
+ }
63
+ else {
64
+ if (patternConfig.comment) {
65
+ newContent += `# ${patternConfig.comment}\n`;
66
+ }
67
+ newContent += `${patternConfig.pattern}\n`;
68
+ }
69
+ }
70
+ return newContent;
71
+ }
72
+ /**
73
+ * Create gitignore content from scratch.
74
+ */
75
+ function createGitignoreContent(patterns) {
76
+ let content = "# Generated by repopo RequiredGitignorePatterns policy\n\n";
77
+ for (const patternConfig of patterns) {
78
+ if (typeof patternConfig === "string") {
79
+ content += `${patternConfig}\n`;
80
+ }
81
+ else {
82
+ if (patternConfig.comment) {
83
+ content += `# ${patternConfig.comment}\n`;
84
+ }
85
+ content += `${patternConfig.pattern}\n`;
86
+ }
87
+ }
88
+ return content;
89
+ }
90
+ /**
91
+ * Handle the case when .gitignore exists.
92
+ */
93
+ function handleExistingGitignore(filePath, patterns, file, resolve) {
94
+ const gitignoreContent = readFileSync(filePath, "utf-8");
95
+ const lines = gitignoreContent.split("\n");
96
+ const missingPatterns = findMissingPatterns(patterns, lines);
97
+ if (missingPatterns.length === 0) {
98
+ return true;
99
+ }
100
+ const result = {
101
+ name: RequiredGitignorePatterns.name,
102
+ file,
103
+ autoFixable: true,
104
+ errorMessage: `Missing required .gitignore patterns: ${missingPatterns.map((p) => (typeof p === "string" ? p : p.pattern)).join(", ")}`,
105
+ };
106
+ if (resolve) {
107
+ const fixResult = {
108
+ ...result,
109
+ resolved: false,
110
+ };
111
+ try {
112
+ const newContent = addPatternsToContent(gitignoreContent, missingPatterns);
113
+ writeFileSync(filePath, newContent);
114
+ fixResult.resolved = true;
115
+ }
116
+ catch (error) {
117
+ fixResult.errorMessage = `Failed to update .gitignore: ${error.message}`;
118
+ }
119
+ return fixResult;
120
+ }
121
+ return result;
122
+ }
123
+ /**
124
+ * Handle the case when .gitignore doesn't exist.
125
+ */
126
+ function handleMissingGitignore(filePath, patterns, file, resolve) {
127
+ const result = {
128
+ name: RequiredGitignorePatterns.name,
129
+ file,
130
+ autoFixable: true,
131
+ errorMessage: "No .gitignore file found in repository root",
132
+ };
133
+ if (resolve) {
134
+ const fixResult = {
135
+ ...result,
136
+ resolved: false,
137
+ };
138
+ try {
139
+ const content = createGitignoreContent(patterns);
140
+ writeFileSync(filePath, content);
141
+ fixResult.resolved = true;
142
+ }
143
+ catch (writeError) {
144
+ fixResult.errorMessage = `Failed to create .gitignore: ${writeError.message}`;
145
+ }
146
+ return fixResult;
147
+ }
148
+ return result;
149
+ }
150
+ /**
151
+ * A repo policy that ensures .gitignore contains required patterns to prevent
152
+ * committing sensitive files, dependencies, and build artifacts.
153
+ *
154
+ * @alpha
155
+ */
156
+ export const RequiredGitignorePatterns = makePolicyDefinition("RequiredGitignorePatterns", /^\.gitignore$/, async ({ file, root, resolve, config }) => {
157
+ const patterns = config?.patterns ?? DEFAULT_PATTERNS;
158
+ const filePath = path.join(root, file);
159
+ try {
160
+ return handleExistingGitignore(filePath, patterns, file, resolve);
161
+ }
162
+ catch (_error) {
163
+ return handleMissingGitignore(filePath, patterns, file, resolve);
164
+ }
165
+ });
166
+ //# sourceMappingURL=RequiredGitignorePatterns.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RequiredGitignorePatterns.js","sourceRoot":"","sources":["../../src/policies/RequiredGitignorePatterns.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACtD,OAAO,IAAI,MAAM,OAAO,CAAC;AACzB,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAoBxD;;GAEG;AACH,MAAM,gBAAgB,GAAG;IACxB,EAAE,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,cAAc,EAAE;IACrD,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,mBAAmB,EAAE;IACjD,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,mBAAmB,EAAE;IACnD,EAAE,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,wBAAwB,EAAE;IAC/D,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE;IAC7C,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,cAAc,EAAE;IAC9C,EAAE,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,oBAAoB,EAAE;IACvD,EAAE,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,sBAAsB,EAAE;CACzD,CAAC;AAEF;;;GAGG;AACH,SAAS,aAAa,CAAC,KAAe,EAAE,OAAe;IACtD,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE;QAC1B,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,iEAAiE;QACjE,MAAM,cAAc,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QAC3D,OAAO,CACN,cAAc,KAAK,OAAO;YAC1B,OAAO,KAAK,OAAO;YACnB,OAAO,CAAC,UAAU,CAAC,GAAG,OAAO,GAAG,CAAC;YACjC,OAAO,CAAC,UAAU,CAAC,GAAG,OAAO,IAAI,CAAC,CAClC,CAAC;IACH,CAAC,CAAC,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,SAAS,mBAAmB,CAC3B,QAA+D,EAC/D,KAAe;IAEf,MAAM,eAAe,GACpB,EAAE,CAAC;IAEJ,KAAK,MAAM,aAAa,IAAI,QAAQ,EAAE,CAAC;QACtC,MAAM,OAAO,GACZ,OAAO,aAAa,KAAK,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,aAAa,CAAC,OAAO,CAAC;QAE3E,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,CAAC;YACpC,eAAe,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACrC,CAAC;IACF,CAAC;IAED,OAAO,eAAe,CAAC;AACxB,CAAC;AAED;;GAEG;AACH,SAAS,oBAAoB,CAC5B,OAAe,EACf,eAAsE;IAEtE,IAAI,UAAU,GAAG,OAAO,CAAC;IAEzB,gCAAgC;IAChC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAChC,UAAU,IAAI,IAAI,CAAC;IACpB,CAAC;IAED,gDAAgD;IAChD,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAChC,UAAU,IAAI,wDAAwD,CAAC;IACxE,CAAC;IAED,2BAA2B;IAC3B,KAAK,MAAM,aAAa,IAAI,eAAe,EAAE,CAAC;QAC7C,IAAI,OAAO,aAAa,KAAK,QAAQ,EAAE,CAAC;YACvC,UAAU,IAAI,GAAG,aAAa,IAAI,CAAC;QACpC,CAAC;aAAM,CAAC;YACP,IAAI,aAAa,CAAC,OAAO,EAAE,CAAC;gBAC3B,UAAU,IAAI,KAAK,aAAa,CAAC,OAAO,IAAI,CAAC;YAC9C,CAAC;YACD,UAAU,IAAI,GAAG,aAAa,CAAC,OAAO,IAAI,CAAC;QAC5C,CAAC;IACF,CAAC;IAED,OAAO,UAAU,CAAC;AACnB,CAAC;AAED;;GAEG;AACH,SAAS,sBAAsB,CAC9B,QAA+D;IAE/D,IAAI,OAAO,GAAG,4DAA4D,CAAC;IAE3E,KAAK,MAAM,aAAa,IAAI,QAAQ,EAAE,CAAC;QACtC,IAAI,OAAO,aAAa,KAAK,QAAQ,EAAE,CAAC;YACvC,OAAO,IAAI,GAAG,aAAa,IAAI,CAAC;QACjC,CAAC;aAAM,CAAC;YACP,IAAI,aAAa,CAAC,OAAO,EAAE,CAAC;gBAC3B,OAAO,IAAI,KAAK,aAAa,CAAC,OAAO,IAAI,CAAC;YAC3C,CAAC;YACD,OAAO,IAAI,GAAG,aAAa,CAAC,OAAO,IAAI,CAAC;QACzC,CAAC;IACF,CAAC;IAED,OAAO,OAAO,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,SAAS,uBAAuB,CAC/B,QAAgB,EAChB,QAA+D,EAC/D,IAAY,EACZ,OAAgB;IAEhB,MAAM,gBAAgB,GAAG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACzD,MAAM,KAAK,GAAG,gBAAgB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC3C,MAAM,eAAe,GAAG,mBAAmB,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAE7D,IAAI,eAAe,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClC,OAAO,IAAI,CAAC;IACb,CAAC;IAED,MAAM,MAAM,GAAkB;QAC7B,IAAI,EAAE,yBAAyB,CAAC,IAAI;QACpC,IAAI;QACJ,WAAW,EAAE,IAAI;QACjB,YAAY,EAAE,yCAAyC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;KACvI,CAAC;IAEF,IAAI,OAAO,EAAE,CAAC;QACb,MAAM,SAAS,GAAoB;YAClC,GAAG,MAAM;YACT,QAAQ,EAAE,KAAK;SACf,CAAC;QAEF,IAAI,CAAC;YACJ,MAAM,UAAU,GAAG,oBAAoB,CACtC,gBAAgB,EAChB,eAAe,CACf,CAAC;YACF,aAAa,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;YACpC,SAAS,CAAC,QAAQ,GAAG,IAAI,CAAC;QAC3B,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACzB,SAAS,CAAC,YAAY,GAAG,gCAAiC,KAAe,CAAC,OAAO,EAAE,CAAC;QACrF,CAAC;QAED,OAAO,SAAS,CAAC;IAClB,CAAC;IAED,OAAO,MAAM,CAAC;AACf,CAAC;AAED;;GAEG;AACH,SAAS,sBAAsB,CAC9B,QAAgB,EAChB,QAA+D,EAC/D,IAAY,EACZ,OAAgB;IAEhB,MAAM,MAAM,GAAkB;QAC7B,IAAI,EAAE,yBAAyB,CAAC,IAAI;QACpC,IAAI;QACJ,WAAW,EAAE,IAAI;QACjB,YAAY,EAAE,6CAA6C;KAC3D,CAAC;IAEF,IAAI,OAAO,EAAE,CAAC;QACb,MAAM,SAAS,GAAoB;YAClC,GAAG,MAAM;YACT,QAAQ,EAAE,KAAK;SACf,CAAC;QAEF,IAAI,CAAC;YACJ,MAAM,OAAO,GAAG,sBAAsB,CAAC,QAAQ,CAAC,CAAC;YACjD,aAAa,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YACjC,SAAS,CAAC,QAAQ,GAAG,IAAI,CAAC;QAC3B,CAAC;QAAC,OAAO,UAAmB,EAAE,CAAC;YAC9B,SAAS,CAAC,YAAY,GAAG,gCAAiC,UAAoB,CAAC,OAAO,EAAE,CAAC;QAC1F,CAAC;QAED,OAAO,SAAS,CAAC;IAClB,CAAC;IAED,OAAO,MAAM,CAAC;AACf,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,yBAAyB,GACrC,oBAAoB,CACnB,2BAA2B,EAC3B,eAAe,EACf,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE;IACzC,MAAM,QAAQ,GAAG,MAAM,EAAE,QAAQ,IAAI,gBAAgB,CAAC;IACtD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAEvC,IAAI,CAAC;QACJ,OAAO,uBAAuB,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;IACnE,CAAC;IAAC,OAAO,MAAe,EAAE,CAAC;QAC1B,OAAO,sBAAsB,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;IAClE,CAAC;AACF,CAAC,CACD,CAAC","sourcesContent":["import { readFileSync, writeFileSync } from \"node:fs\";\nimport path from \"pathe\";\nimport { makePolicyDefinition } from \"../makePolicy.js\";\nimport type {\n\tPolicyDefinition,\n\tPolicyFailure,\n\tPolicyFixResult,\n} from \"../policy.js\";\n\n/**\n * Policy settings for the RequiredGitignorePatterns repo policy.\n *\n * @alpha\n */\nexport interface RequiredGitignorePatternsSettings {\n\t/**\n\t * Array of patterns that must be present in .gitignore.\n\t * Each pattern can be a string or an object with pattern and comment.\n\t */\n\tpatterns: Array<string | { pattern: string; comment?: string }>;\n}\n\n/**\n * Default patterns that should typically be in .gitignore files.\n */\nconst DEFAULT_PATTERNS = [\n\t{ pattern: \"node_modules/\", comment: \"Dependencies\" },\n\t{ pattern: \".env\", comment: \"Environment files\" },\n\t{ pattern: \".env.*\", comment: \"Environment files\" },\n\t{ pattern: \"!.env.example\", comment: \"Allow example env file\" },\n\t{ pattern: \"dist/\", comment: \"Build output\" },\n\t{ pattern: \"build/\", comment: \"Build output\" },\n\t{ pattern: \".DS_Store\", comment: \"macOS system files\" },\n\t{ pattern: \"Thumbs.db\", comment: \"Windows system files\" },\n];\n\n/**\n * Check if a pattern exists in gitignore content.\n * Handles patterns with inline comments (e.g., \"node_modules/ # comment\").\n */\nfunction patternExists(lines: string[], pattern: string): boolean {\n\treturn lines.some((line) => {\n\t\tconst trimmed = line.trim();\n\t\t// Remove inline comments (everything after # that's not escaped)\n\t\tconst withoutComment = trimmed.split(\"#\")[0]?.trim() ?? \"\";\n\t\treturn (\n\t\t\twithoutComment === pattern ||\n\t\t\ttrimmed === pattern ||\n\t\t\ttrimmed.startsWith(`${pattern} `) ||\n\t\t\ttrimmed.startsWith(`${pattern}\\t`)\n\t\t);\n\t});\n}\n\n/**\n * Find missing patterns in gitignore content.\n */\nfunction findMissingPatterns(\n\tpatterns: Array<string | { pattern: string; comment?: string }>,\n\tlines: string[],\n): Array<string | { pattern: string; comment?: string }> {\n\tconst missingPatterns: Array<string | { pattern: string; comment?: string }> =\n\t\t[];\n\n\tfor (const patternConfig of patterns) {\n\t\tconst pattern =\n\t\t\ttypeof patternConfig === \"string\" ? patternConfig : patternConfig.pattern;\n\n\t\tif (!patternExists(lines, pattern)) {\n\t\t\tmissingPatterns.push(patternConfig);\n\t\t}\n\t}\n\n\treturn missingPatterns;\n}\n\n/**\n * Add patterns to gitignore content.\n */\nfunction addPatternsToContent(\n\tcontent: string,\n\tmissingPatterns: Array<string | { pattern: string; comment?: string }>,\n): string {\n\tlet newContent = content;\n\n\t// Ensure file ends with newline\n\tif (!newContent.endsWith(\"\\n\")) {\n\t\tnewContent += \"\\n\";\n\t}\n\n\t// Add a section header if we're adding patterns\n\tif (missingPatterns.length > 0) {\n\t\tnewContent += \"\\n# Added by repopo RequiredGitignorePatterns policy\\n\";\n\t}\n\n\t// Add each missing pattern\n\tfor (const patternConfig of missingPatterns) {\n\t\tif (typeof patternConfig === \"string\") {\n\t\t\tnewContent += `${patternConfig}\\n`;\n\t\t} else {\n\t\t\tif (patternConfig.comment) {\n\t\t\t\tnewContent += `# ${patternConfig.comment}\\n`;\n\t\t\t}\n\t\t\tnewContent += `${patternConfig.pattern}\\n`;\n\t\t}\n\t}\n\n\treturn newContent;\n}\n\n/**\n * Create gitignore content from scratch.\n */\nfunction createGitignoreContent(\n\tpatterns: Array<string | { pattern: string; comment?: string }>,\n): string {\n\tlet content = \"# Generated by repopo RequiredGitignorePatterns policy\\n\\n\";\n\n\tfor (const patternConfig of patterns) {\n\t\tif (typeof patternConfig === \"string\") {\n\t\t\tcontent += `${patternConfig}\\n`;\n\t\t} else {\n\t\t\tif (patternConfig.comment) {\n\t\t\t\tcontent += `# ${patternConfig.comment}\\n`;\n\t\t\t}\n\t\t\tcontent += `${patternConfig.pattern}\\n`;\n\t\t}\n\t}\n\n\treturn content;\n}\n\n/**\n * Handle the case when .gitignore exists.\n */\nfunction handleExistingGitignore(\n\tfilePath: string,\n\tpatterns: Array<string | { pattern: string; comment?: string }>,\n\tfile: string,\n\tresolve: boolean,\n) {\n\tconst gitignoreContent = readFileSync(filePath, \"utf-8\");\n\tconst lines = gitignoreContent.split(\"\\n\");\n\tconst missingPatterns = findMissingPatterns(patterns, lines);\n\n\tif (missingPatterns.length === 0) {\n\t\treturn true;\n\t}\n\n\tconst result: PolicyFailure = {\n\t\tname: RequiredGitignorePatterns.name,\n\t\tfile,\n\t\tautoFixable: true,\n\t\terrorMessage: `Missing required .gitignore patterns: ${missingPatterns.map((p) => (typeof p === \"string\" ? p : p.pattern)).join(\", \")}`,\n\t};\n\n\tif (resolve) {\n\t\tconst fixResult: PolicyFixResult = {\n\t\t\t...result,\n\t\t\tresolved: false,\n\t\t};\n\n\t\ttry {\n\t\t\tconst newContent = addPatternsToContent(\n\t\t\t\tgitignoreContent,\n\t\t\t\tmissingPatterns,\n\t\t\t);\n\t\t\twriteFileSync(filePath, newContent);\n\t\t\tfixResult.resolved = true;\n\t\t} catch (error: unknown) {\n\t\t\tfixResult.errorMessage = `Failed to update .gitignore: ${(error as Error).message}`;\n\t\t}\n\n\t\treturn fixResult;\n\t}\n\n\treturn result;\n}\n\n/**\n * Handle the case when .gitignore doesn't exist.\n */\nfunction handleMissingGitignore(\n\tfilePath: string,\n\tpatterns: Array<string | { pattern: string; comment?: string }>,\n\tfile: string,\n\tresolve: boolean,\n) {\n\tconst result: PolicyFailure = {\n\t\tname: RequiredGitignorePatterns.name,\n\t\tfile,\n\t\tautoFixable: true,\n\t\terrorMessage: \"No .gitignore file found in repository root\",\n\t};\n\n\tif (resolve) {\n\t\tconst fixResult: PolicyFixResult = {\n\t\t\t...result,\n\t\t\tresolved: false,\n\t\t};\n\n\t\ttry {\n\t\t\tconst content = createGitignoreContent(patterns);\n\t\t\twriteFileSync(filePath, content);\n\t\t\tfixResult.resolved = true;\n\t\t} catch (writeError: unknown) {\n\t\t\tfixResult.errorMessage = `Failed to create .gitignore: ${(writeError as Error).message}`;\n\t\t}\n\n\t\treturn fixResult;\n\t}\n\n\treturn result;\n}\n\n/**\n * A repo policy that ensures .gitignore contains required patterns to prevent\n * committing sensitive files, dependencies, and build artifacts.\n *\n * @alpha\n */\nexport const RequiredGitignorePatterns: PolicyDefinition<RequiredGitignorePatternsSettings> =\n\tmakePolicyDefinition(\n\t\t\"RequiredGitignorePatterns\",\n\t\t/^\\.gitignore$/,\n\t\tasync ({ file, root, resolve, config }) => {\n\t\t\tconst patterns = config?.patterns ?? DEFAULT_PATTERNS;\n\t\t\tconst filePath = path.join(root, file);\n\n\t\t\ttry {\n\t\t\t\treturn handleExistingGitignore(filePath, patterns, file, resolve);\n\t\t\t} catch (_error: unknown) {\n\t\t\t\treturn handleMissingGitignore(filePath, patterns, file, resolve);\n\t\t\t}\n\t\t},\n\t);\n"]}
package/esm/policies.d.ts CHANGED
@@ -1,8 +1,12 @@
1
- export { DefaultPolicies } from "./policy.js";
2
1
  export { HtmlFileHeaders } from "./policies/HtmlFileHeaders.js";
3
2
  export { JsTsFileHeaders } from "./policies/JsTsFileHeaders.js";
3
+ export { LicenseFileExists } from "./policies/LicenseFileExists.js";
4
4
  export { NoJsFileExtensions } from "./policies/NoJsFileExtensions.js";
5
+ export { NoLargeBinaryFiles } from "./policies/NoLargeBinaryFiles.js";
5
6
  export { PackageJsonProperties } from "./policies/PackageJsonProperties.js";
6
7
  export { PackageJsonRepoDirectoryProperty } from "./policies/PackageJsonRepoDirectoryProperty.js";
7
8
  export { PackageJsonSorted } from "./policies/PackageJsonSorted.js";
9
+ export { PackageScripts } from "./policies/PackageScripts.js";
10
+ export { RequiredGitignorePatterns } from "./policies/RequiredGitignorePatterns.js";
11
+ export { DefaultPolicies } from "./policy.js";
8
12
  //# sourceMappingURL=policies.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"policies.d.ts","sourceRoot":"","sources":["../src/policies.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AACtE,OAAO,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AAC5E,OAAO,EAAE,gCAAgC,EAAE,MAAM,gDAAgD,CAAC;AAClG,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC"}
1
+ {"version":3,"file":"policies.d.ts","sourceRoot":"","sources":["../src/policies.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AACtE,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AACtE,OAAO,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AAC5E,OAAO,EAAE,gCAAgC,EAAE,MAAM,gDAAgD,CAAC;AAClG,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,yBAAyB,EAAE,MAAM,yCAAyC,CAAC;AACpF,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC"}