repopo 0.2.2

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 (47) hide show
  1. package/CHANGELOG.md +89 -0
  2. package/LICENSE +21 -0
  3. package/README.md +103 -0
  4. package/THIRD-PARTY-LICENSES.txt +198 -0
  5. package/bin/dev.cmd +3 -0
  6. package/bin/dev.js +5 -0
  7. package/bin/run.cmd +3 -0
  8. package/bin/run.js +5 -0
  9. package/esm/commands/check.d.ts +46 -0
  10. package/esm/commands/check.d.ts.map +1 -0
  11. package/esm/commands/check.js +330 -0
  12. package/esm/commands/check.js.map +1 -0
  13. package/esm/config.d.ts +35 -0
  14. package/esm/config.d.ts.map +1 -0
  15. package/esm/config.js +6 -0
  16. package/esm/config.js.map +1 -0
  17. package/esm/index.d.ts +6 -0
  18. package/esm/index.d.ts.map +1 -0
  19. package/esm/index.js +3 -0
  20. package/esm/index.js.map +1 -0
  21. package/esm/policies/NoJsFileExtensions.d.ts +10 -0
  22. package/esm/policies/NoJsFileExtensions.d.ts.map +1 -0
  23. package/esm/policies/NoJsFileExtensions.js +23 -0
  24. package/esm/policies/NoJsFileExtensions.js.map +1 -0
  25. package/esm/policies/PackageJsonProperties.d.ts +21 -0
  26. package/esm/policies/PackageJsonProperties.d.ts.map +1 -0
  27. package/esm/policies/PackageJsonProperties.js +45 -0
  28. package/esm/policies/PackageJsonProperties.js.map +1 -0
  29. package/esm/policies/PackageJsonRepoDirectoryProperty.d.ts +7 -0
  30. package/esm/policies/PackageJsonRepoDirectoryProperty.d.ts.map +1 -0
  31. package/esm/policies/PackageJsonRepoDirectoryProperty.js +48 -0
  32. package/esm/policies/PackageJsonRepoDirectoryProperty.js.map +1 -0
  33. package/esm/policies/PackageJsonSortedPolicy.d.ts +6 -0
  34. package/esm/policies/PackageJsonSortedPolicy.d.ts.map +1 -0
  35. package/esm/policies/PackageJsonSortedPolicy.js +48 -0
  36. package/esm/policies/PackageJsonSortedPolicy.js.map +1 -0
  37. package/esm/policies/SortTsconfigs.d.ts +10 -0
  38. package/esm/policies/SortTsconfigs.d.ts.map +1 -0
  39. package/esm/policies/SortTsconfigs.js +33 -0
  40. package/esm/policies/SortTsconfigs.js.map +1 -0
  41. package/esm/policy.d.ts +104 -0
  42. package/esm/policy.d.ts.map +1 -0
  43. package/esm/policy.js +53 -0
  44. package/esm/policy.js.map +1 -0
  45. package/esm/tsdoc-metadata.json +11 -0
  46. package/oclif.manifest.json +104 -0
  47. package/package.json +100 -0
@@ -0,0 +1,330 @@
1
+ import fs from "node:fs";
2
+ import { EOL as newline } from "node:os";
3
+ import path from "node:path";
4
+ import { Flags } from "@oclif/core";
5
+ import { StringBuilder } from "@rushstack/node-core-library";
6
+ import { GitCommand, RegExpFlag, findGitRoot } from "@tylerbu/cli-api";
7
+ import chalk from "chalk";
8
+ import { cosmiconfig } from "cosmiconfig";
9
+ import { DefaultPolicyConfig } from "../config.js";
10
+ import { DefaultPolicies, isPolicyFixResult, } from "../policy.js";
11
+ /**
12
+ * Stores performance data for each handler. Used to collect and display performance stats.
13
+ */
14
+ const handlerPerformanceData = new Map();
15
+ /**
16
+ * This tool enforces policies across the code base via a series of handler functions. The handler functions are
17
+ * associated with a regular expression, and all files matching that expression are passed to the handler function.
18
+ *
19
+ * By default, the list of files to check comes from `git ls-files. You can also pipe in file names from stdin. For
20
+ * example:
21
+ *
22
+ * `git ls-files -co --exclude-standard --full-name | repopo check --stdin --verbose`
23
+ */
24
+ export class CheckPolicy extends GitCommand {
25
+ static summary = "Checks and applies policies to the files in the repository.";
26
+ static flags = {
27
+ fix: Flags.boolean({
28
+ aliases: ["resolve"],
29
+ description: "Fix errors if possible.",
30
+ required: false,
31
+ char: "f",
32
+ }),
33
+ policy: RegExpFlag({
34
+ description: "Filter policies to apply by <regex>. Only policies with a name matching the regex will be applied.",
35
+ required: false,
36
+ char: "d",
37
+ }),
38
+ excludePolicy: Flags.string({
39
+ char: "D",
40
+ description: "Exclude policies by name. Can be specified multiple times to exclude multiple policies.",
41
+ exclusive: ["policy"],
42
+ multiple: true,
43
+ }),
44
+ path: RegExpFlag({
45
+ description: "Filter file paths by <regex>.",
46
+ required: false,
47
+ char: "p",
48
+ }),
49
+ stdin: Flags.boolean({
50
+ description: "Read list of files from stdin.",
51
+ required: false,
52
+ }),
53
+ ...GitCommand.flags,
54
+ };
55
+ processed = 0;
56
+ count = 0;
57
+ // private policies: RepoPolicy[] | undefined;
58
+ commandContext;
59
+ get defaultConfig() {
60
+ return DefaultPolicyConfig;
61
+ }
62
+ async loadConfig() {
63
+ const gitRoot = await findGitRoot();
64
+ // this.configPath ??= this.config.configDir; // path.join(this.config.configDir, "config.ts");
65
+ const explorer = cosmiconfig(this.config.bin, {
66
+ searchStrategy: "global",
67
+ });
68
+ this.verbose(`Looking for '${this.config.bin}' config at '${gitRoot}'`);
69
+ const config = await explorer.search(gitRoot);
70
+ if (config?.config !== undefined) {
71
+ this.verbose(`Found config at ${config.filepath}`);
72
+ }
73
+ if (config?.config === undefined) {
74
+ this.warning("No config found; using defaults.");
75
+ }
76
+ const finalConfig = config?.config ?? this.defaultConfig;
77
+ if (finalConfig.includeDefaultPolicies === true) {
78
+ finalConfig.policies ??= [];
79
+ finalConfig.policies.push(...DefaultPolicies);
80
+ }
81
+ return finalConfig;
82
+ }
83
+ async init() {
84
+ await super.init();
85
+ let policies = this.commandConfig?.policies?.filter((h) => {
86
+ if (this.flags.excludePolicy === undefined ||
87
+ this.flags.excludePolicy.length === 0) {
88
+ return true;
89
+ }
90
+ const shouldRun = this.flags.excludePolicy?.includes(h.name) === false;
91
+ if (!shouldRun) {
92
+ this.info(`Disabled policy: ${h.name}`);
93
+ }
94
+ return shouldRun;
95
+ }) ?? [];
96
+ // const pathRegex: RegExp =
97
+ // this.flags.path === undefined ? /.?/ : new RegExp(this.flags.path, "i");
98
+ if (this.flags.policy !== undefined) {
99
+ this.info(`Filtering handlers by regex: ${this.flags.policy}`);
100
+ policies = policies?.filter((h) => this.flags.policy?.test(h.name));
101
+ }
102
+ const pathRegex = this.flags.path ?? /.?/;
103
+ if (this.flags.path !== undefined) {
104
+ this.info(`Filtering file paths by regex: ${pathRegex}`);
105
+ }
106
+ if (this.flags.fix) {
107
+ this.info("Resolving errors if possible.");
108
+ }
109
+ const excludeFiles = this.commandConfig?.excludeFiles?.map((e) => toRegExp(e)) ?? [];
110
+ const excludePoliciesForFilesRaw = this.commandConfig?.excludePoliciesForFiles;
111
+ const excludePoliciesForFiles = new Map();
112
+ if (excludePoliciesForFilesRaw) {
113
+ for (const [policyName, filters] of Object.entries(excludePoliciesForFilesRaw)) {
114
+ const regexes = filters.map((e) => toRegExp(e));
115
+ excludePoliciesForFiles.set(policyName, regexes);
116
+ }
117
+ }
118
+ const gitRoot = await findGitRoot();
119
+ this.commandContext = {
120
+ pathRegex,
121
+ excludeFiles,
122
+ policies,
123
+ excludePoliciesForFiles,
124
+ gitRoot,
125
+ };
126
+ }
127
+ async run() {
128
+ // list the handlers then exit
129
+ // if (this.flags.listHandlers) {
130
+ // for (const h of handlersToRun) {
131
+ // this.log(`${h.name}\nresolver: ${h.resolver !== undefined}\n`);
132
+ // }
133
+ // this.log(`${handlersToRun.length} TOTAL POLICY HANDLERS`);
134
+ // this.exit(0);
135
+ // }
136
+ const policies = this.commandConfig?.policies ?? [];
137
+ this.verbose(`${policies.length} policies loaded.`);
138
+ for (const h of policies) {
139
+ this.verbose(h.name);
140
+ }
141
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
142
+ // const rawExclusions: string[] =
143
+ // this.flags.exclusions === undefined
144
+ // ? manifest.policy?.exclusions
145
+ // : await readJson(this.flags.exclusions);
146
+ // const exclusions: RegExp[] = rawExclusions.map((e) => new RegExp(e, "i"));
147
+ // const rawHandlerExclusions = manifest?.policy?.handlerExclusions;
148
+ // const handlerExclusions: ExcludedPolicyFileMap = {};
149
+ // if (rawHandlerExclusions) {
150
+ // for (const rule of Object.keys(rawHandlerExclusions)) {
151
+ // handlerExclusions[rule] = rawHandlerExclusions[rule].map(
152
+ // (e) => new RegExp(e, "i"),
153
+ // );
154
+ // }
155
+ // }
156
+ const filePathsToCheck = [];
157
+ // const context = await this.getContext();
158
+ // const gitRoot = context.repo.resolvedRoot;
159
+ if (this.flags.stdin) {
160
+ const stdInput = await readStdin();
161
+ if (stdInput !== undefined) {
162
+ filePathsToCheck.push(...stdInput.split("\n"));
163
+ }
164
+ }
165
+ else {
166
+ // const repo = new Repository({ baseDir: gitRoot });
167
+ const gitFiles = (await this.git.raw("ls-files", "-co", "--exclude-standard", "--full-name")) ?? "";
168
+ filePathsToCheck.push(...gitFiles.split("\n"));
169
+ }
170
+ if (this.commandContext === undefined) {
171
+ this.error("Command context was undefined - fatal error.", {
172
+ exit: 100,
173
+ });
174
+ }
175
+ await this.executePolicy(filePathsToCheck, this.commandContext);
176
+ }
177
+ async executePolicy(pathsToCheck, commandContext) {
178
+ try {
179
+ for (const pathToCheck of pathsToCheck) {
180
+ // eslint-disable-next-line no-await-in-loop
181
+ await this.checkOrExcludeFile(pathToCheck, commandContext);
182
+ }
183
+ }
184
+ finally {
185
+ this.logStats();
186
+ }
187
+ }
188
+ /**
189
+ * Routes files to their handlers and resolvers by regex testing their full paths. If a file fails a policy that has a
190
+ * resolver, the resolver will be invoked as well. Synchronizes the output, exit codes, and resolve
191
+ * decision for all handlers.
192
+ */
193
+ async routeToHandlers(file, commandContext) {
194
+ const {
195
+ // context,
196
+ policies, excludePoliciesForFiles, gitRoot, } = commandContext;
197
+ // Use the repo-relative path so that regexes that specify string start (^) will match repo paths.
198
+ // Replace \ in result with / in case OS is Windows.
199
+ const relPath = path.relative(gitRoot, file).replace(/\\/g, "/");
200
+ await Promise.all(policies
201
+ .filter((handler) => handler.match.test(relPath))
202
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: <explanation>
203
+ .map(async (policy) => {
204
+ // doing exclusion per handler
205
+ const exclusions = excludePoliciesForFiles.get(policy.name);
206
+ if (exclusions !== undefined &&
207
+ !exclusions.every((regex) => !regex.test(relPath))) {
208
+ this.verbose(`Excluded from '${policy.name}' policy: ${relPath}`);
209
+ return;
210
+ }
211
+ const result = await runWithPerf(policy.name, "handle", async () => policy.handler({
212
+ file: relPath,
213
+ root: gitRoot,
214
+ resolve: this.flags.fix,
215
+ config: this.commandConfig?.policySettings?.[policy.name],
216
+ }));
217
+ if (result === true) {
218
+ return;
219
+ }
220
+ const messages = new StringBuilder();
221
+ if (isPolicyFixResult(result)) {
222
+ if (result.resolved) {
223
+ messages.append(`Resolved ${policy.name} policy failure for file: ${result.file}`);
224
+ }
225
+ else {
226
+ messages.append(`Error when trying to fix ${policy.name} policy failure in ${result.file}`);
227
+ process.exitCode = 1;
228
+ }
229
+ }
230
+ else {
231
+ // result must be a PolicyFailureResult; check if there is a standalone resolver.
232
+ const { resolver } = policy;
233
+ if (this.flags.fix && resolver !== undefined) {
234
+ // Resolve the failure
235
+ messages.append(`${newline}attempting to resolve: ${relPath}`);
236
+ const resolveResult = await runWithPerf(policy.name, "resolve", async () => resolver({ file: relPath, root: gitRoot }));
237
+ if (resolveResult.errorMessage !== undefined &&
238
+ resolveResult.errorMessage !== "") {
239
+ messages.append(newline + resolveResult.errorMessage);
240
+ }
241
+ if (!resolveResult.resolved) {
242
+ process.exitCode = 1;
243
+ }
244
+ }
245
+ else {
246
+ // No resolver, or fix is false, so we're in the full failure case.
247
+ const autoFixable = result.autoFixable
248
+ ? chalk.green(" (autofixable)")
249
+ : "";
250
+ messages.append(`'${policy.name}' policy failure${autoFixable}: ${result.file}`);
251
+ messages.append(result.errorMessage === undefined
252
+ ? ""
253
+ : `${newline}${result.errorMessage}`);
254
+ process.exitCode = 1;
255
+ }
256
+ }
257
+ if ((process.exitCode ?? 0) === 0) {
258
+ this.info(messages.toString());
259
+ }
260
+ else {
261
+ this.warning(messages.toString());
262
+ }
263
+ }));
264
+ }
265
+ logStats() {
266
+ this.log(`Statistics: ${this.processed} processed, ${this.count - this.processed} excluded, ${this.count} total`);
267
+ for (const [action, handlerPerf] of handlerPerformanceData.entries()) {
268
+ this.log(`Performance for "${action}":`);
269
+ for (const [handler, dur] of handlerPerf.entries()) {
270
+ this.log(`\t${handler}: ${dur}ms`);
271
+ }
272
+ }
273
+ }
274
+ /**
275
+ * Given a string that represents a path to a file in the repo, determines if the file should be checked, and if so,
276
+ * routes the file to the appropriate handlers.
277
+ */
278
+ async checkOrExcludeFile(inputPath, commandContext) {
279
+ const { excludeFiles: exclusions, gitRoot, pathRegex } = commandContext;
280
+ const filePath = path.join(gitRoot, inputPath).trim().replace(/\\/g, "/");
281
+ if (!(pathRegex.test(inputPath) && fs.existsSync(filePath))) {
282
+ return;
283
+ }
284
+ this.count++;
285
+ if (!exclusions.every((value) => !value.test(inputPath))) {
286
+ this.verbose(`Excluded all handlers: ${inputPath}`);
287
+ return;
288
+ }
289
+ try {
290
+ await this.routeToHandlers(filePath, commandContext);
291
+ }
292
+ catch (error) {
293
+ throw new Error(`Error routing ${filePath} to handler: ${error}`);
294
+ }
295
+ this.processed++;
296
+ }
297
+ }
298
+ async function runWithPerf(name, action, run) {
299
+ const actionMap = handlerPerformanceData.get(action) ?? new Map();
300
+ let dur = actionMap.get(name) ?? 0;
301
+ const start = Date.now();
302
+ const result = await run();
303
+ dur += Date.now() - start;
304
+ actionMap.set(name, dur);
305
+ handlerPerformanceData.set(action, actionMap);
306
+ return result;
307
+ }
308
+ async function readStdin() {
309
+ return new Promise((resolve) => {
310
+ const stdin = process.stdin;
311
+ stdin.setEncoding("utf8");
312
+ let data = "";
313
+ stdin.on("data", (chunk) => {
314
+ data += chunk;
315
+ });
316
+ stdin.on("end", () => {
317
+ resolve(data);
318
+ });
319
+ if (stdin.isTTY) {
320
+ resolve("");
321
+ }
322
+ });
323
+ }
324
+ function toRegExp(input) {
325
+ if (typeof input === "string") {
326
+ return new RegExp(input, "i");
327
+ }
328
+ return input;
329
+ }
330
+ //# sourceMappingURL=check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check.js","sourceRoot":"","sources":["../../src/commands/check.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from \"node:fs\";\nimport { EOL as newline } from \"node:os\";\nimport path from \"node:path\";\nimport { Flags } from \"@oclif/core\";\nimport { StringBuilder } from \"@rushstack/node-core-library\";\nimport { GitCommand, RegExpFlag, findGitRoot } from \"@tylerbu/cli-api\";\nimport chalk from \"chalk\";\nimport { type CosmiconfigResult, cosmiconfig } from \"cosmiconfig\";\nimport { DefaultPolicyConfig, type PolicyConfig } from \"../config.js\";\nimport {\n\tDefaultPolicies,\n\ttype PolicyName,\n\ttype RepoPolicy,\n\tisPolicyFixResult,\n} from \"../policy.js\";\n\ntype PolicyAction = \"handle\" | \"resolve\";\n\ntype ExcludedPolicyFileMap = Map<PolicyName, RegExp[]>; // Record<PolicyName, RegExp[]>\n\n/**\n * A convenience interface used to pass commonly used parameters to functions in this file.\n */\ninterface CheckPolicyCommandContext {\n\t/**\n\t * A regular expression used to filter selected files.\n\t */\n\tpathRegex: RegExp;\n\n\t/**\n\t * A list of regular expressions used to exclude files from all handlers.\n\t */\n\texcludeFiles: RegExp[];\n\n\t/**\n\t * A list of handlers to apply to selected files.\n\t */\n\tpolicies: RepoPolicy[];\n\n\t/**\n\t * A per-handler list of regular expressions used to exclude files from specific handlers.\n\t */\n\texcludePoliciesForFiles: ExcludedPolicyFileMap;\n\n\t/**\n\t * Path to the root of the git repo.\n\t */\n\tgitRoot: string;\n\n\t/**\n\t * The repo context.\n\t */\n\t// context: Context;\n}\n\n/**\n * Stores performance data for each handler. Used to collect and display performance stats.\n */\nconst handlerPerformanceData = new Map<PolicyAction, Map<string, number>>();\n\n/**\n * This tool enforces policies across the code base via a series of handler functions. The handler functions are\n * associated with a regular expression, and all files matching that expression are passed to the handler function.\n *\n * By default, the list of files to check comes from `git ls-files. You can also pipe in file names from stdin. For\n * example:\n *\n * `git ls-files -co --exclude-standard --full-name | repopo check --stdin --verbose`\n */\nexport class CheckPolicy extends GitCommand<\n\ttypeof CheckPolicy & {\n\t\targs: typeof GitCommand.args;\n\t\tflags: typeof GitCommand.flags;\n\t},\n\tPolicyConfig\n> {\n\tstatic override readonly summary =\n\t\t\"Checks and applies policies to the files in the repository.\";\n\n\tstatic override readonly flags = {\n\t\tfix: Flags.boolean({\n\t\t\taliases: [\"resolve\"],\n\t\t\tdescription: \"Fix errors if possible.\",\n\t\t\trequired: false,\n\t\t\tchar: \"f\",\n\t\t}),\n\t\tpolicy: RegExpFlag({\n\t\t\tdescription:\n\t\t\t\t\"Filter policies to apply by <regex>. Only policies with a name matching the regex will be applied.\",\n\t\t\trequired: false,\n\t\t\tchar: \"d\",\n\t\t}),\n\t\texcludePolicy: Flags.string({\n\t\t\tchar: \"D\",\n\t\t\tdescription:\n\t\t\t\t\"Exclude policies by name. Can be specified multiple times to exclude multiple policies.\",\n\t\t\texclusive: [\"policy\"],\n\t\t\tmultiple: true,\n\t\t}),\n\t\tpath: RegExpFlag({\n\t\t\tdescription: \"Filter file paths by <regex>.\",\n\t\t\trequired: false,\n\t\t\tchar: \"p\",\n\t\t}),\n\t\tstdin: Flags.boolean({\n\t\t\tdescription: \"Read list of files from stdin.\",\n\t\t\trequired: false,\n\t\t}),\n\t\t...GitCommand.flags,\n\t} as const;\n\n\tprivate processed = 0;\n\tprivate count = 0;\n\t// private policies: RepoPolicy[] | undefined;\n\tprivate commandContext: CheckPolicyCommandContext | undefined;\n\n\tpublic override get defaultConfig() {\n\t\treturn DefaultPolicyConfig;\n\t}\n\n\tprotected override async loadConfig(): Promise<PolicyConfig> {\n\t\tconst gitRoot = await findGitRoot();\n\t\t// this.configPath ??= this.config.configDir; // path.join(this.config.configDir, \"config.ts\");\n\t\tconst explorer = cosmiconfig(this.config.bin, {\n\t\t\tsearchStrategy: \"global\",\n\t\t});\n\t\tthis.verbose(`Looking for '${this.config.bin}' config at '${gitRoot}'`);\n\t\tconst config: CosmiconfigResult = await explorer.search(gitRoot);\n\t\tif (config?.config !== undefined) {\n\t\t\tthis.verbose(`Found config at ${config.filepath}`);\n\t\t}\n\t\tif (config?.config === undefined) {\n\t\t\tthis.warning(\"No config found; using defaults.\");\n\t\t}\n\t\tconst finalConfig: PolicyConfig = config?.config ?? this.defaultConfig;\n\t\tif (finalConfig.includeDefaultPolicies === true) {\n\t\t\tfinalConfig.policies ??= [];\n\t\t\tfinalConfig.policies.push(...DefaultPolicies);\n\t\t}\n\n\t\treturn finalConfig;\n\t}\n\n\tpublic override async init(): Promise<void> {\n\t\tawait super.init();\n\n\t\tlet policies =\n\t\t\tthis.commandConfig?.policies?.filter((h) => {\n\t\t\t\tif (\n\t\t\t\t\tthis.flags.excludePolicy === undefined ||\n\t\t\t\t\tthis.flags.excludePolicy.length === 0\n\t\t\t\t) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\tconst shouldRun = this.flags.excludePolicy?.includes(h.name) === false;\n\t\t\t\tif (!shouldRun) {\n\t\t\t\t\tthis.info(`Disabled policy: ${h.name}`);\n\t\t\t\t}\n\t\t\t\treturn shouldRun;\n\t\t\t}) ?? [];\n\n\t\t// const pathRegex: RegExp =\n\t\t// \tthis.flags.path === undefined ? /.?/ : new RegExp(this.flags.path, \"i\");\n\n\t\tif (this.flags.policy !== undefined) {\n\t\t\tthis.info(`Filtering handlers by regex: ${this.flags.policy}`);\n\t\t\tpolicies = policies?.filter((h) => this.flags.policy?.test(h.name));\n\t\t}\n\n\t\tconst pathRegex = this.flags.path ?? /.?/;\n\t\tif (this.flags.path !== undefined) {\n\t\t\tthis.info(`Filtering file paths by regex: ${pathRegex}`);\n\t\t}\n\n\t\tif (this.flags.fix) {\n\t\t\tthis.info(\"Resolving errors if possible.\");\n\t\t}\n\n\t\tconst excludeFiles: RegExp[] =\n\t\t\tthis.commandConfig?.excludeFiles?.map((e) => toRegExp(e)) ?? [];\n\n\t\tconst excludePoliciesForFilesRaw =\n\t\t\tthis.commandConfig?.excludePoliciesForFiles;\n\n\t\tconst excludePoliciesForFiles: ExcludedPolicyFileMap = new Map();\n\t\tif (excludePoliciesForFilesRaw) {\n\t\t\tfor (const [policyName, filters] of Object.entries(\n\t\t\t\texcludePoliciesForFilesRaw,\n\t\t\t)) {\n\t\t\t\tconst regexes = filters.map((e) => toRegExp(e));\n\t\t\t\texcludePoliciesForFiles.set(policyName, regexes);\n\t\t\t}\n\t\t}\n\n\t\tconst gitRoot = await findGitRoot();\n\t\tthis.commandContext = {\n\t\t\tpathRegex,\n\t\t\texcludeFiles,\n\t\t\tpolicies,\n\t\t\texcludePoliciesForFiles,\n\t\t\tgitRoot,\n\t\t};\n\t}\n\n\tpublic override async run(): Promise<void> {\n\t\t// list the handlers then exit\n\t\t// if (this.flags.listHandlers) {\n\t\t// \tfor (const h of handlersToRun) {\n\t\t// \t\tthis.log(`${h.name}\\nresolver: ${h.resolver !== undefined}\\n`);\n\t\t// \t}\n\t\t// \tthis.log(`${handlersToRun.length} TOTAL POLICY HANDLERS`);\n\t\t// \tthis.exit(0);\n\t\t// }\n\n\t\tconst policies = this.commandConfig?.policies ?? [];\n\t\tthis.verbose(`${policies.length} policies loaded.`);\n\t\tfor (const h of policies) {\n\t\t\tthis.verbose(h.name);\n\t\t}\n\n\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n\t\t// const rawExclusions: string[] =\n\t\t// \tthis.flags.exclusions === undefined\n\t\t// \t\t? manifest.policy?.exclusions\n\t\t// \t\t: await readJson(this.flags.exclusions);\n\n\t\t// const exclusions: RegExp[] = rawExclusions.map((e) => new RegExp(e, \"i\"));\n\n\t\t// const rawHandlerExclusions = manifest?.policy?.handlerExclusions;\n\n\t\t// const handlerExclusions: ExcludedPolicyFileMap = {};\n\t\t// if (rawHandlerExclusions) {\n\t\t// \tfor (const rule of Object.keys(rawHandlerExclusions)) {\n\t\t// \t\thandlerExclusions[rule] = rawHandlerExclusions[rule].map(\n\t\t// \t\t\t(e) => new RegExp(e, \"i\"),\n\t\t// \t\t);\n\t\t// \t}\n\t\t// }\n\n\t\tconst filePathsToCheck: string[] = [];\n\t\t// const context = await this.getContext();\n\t\t// const gitRoot = context.repo.resolvedRoot;\n\n\t\tif (this.flags.stdin) {\n\t\t\tconst stdInput = await readStdin();\n\n\t\t\tif (stdInput !== undefined) {\n\t\t\t\tfilePathsToCheck.push(...stdInput.split(\"\\n\"));\n\t\t\t}\n\t\t} else {\n\t\t\t// const repo = new Repository({ baseDir: gitRoot });\n\t\t\tconst gitFiles =\n\t\t\t\t(await this.git.raw(\n\t\t\t\t\t\"ls-files\",\n\t\t\t\t\t\"-co\",\n\t\t\t\t\t\"--exclude-standard\",\n\t\t\t\t\t\"--full-name\",\n\t\t\t\t)) ?? \"\";\n\n\t\t\tfilePathsToCheck.push(...gitFiles.split(\"\\n\"));\n\t\t}\n\n\t\tif (this.commandContext === undefined) {\n\t\t\tthis.error(\"Command context was undefined - fatal error.\", {\n\t\t\t\texit: 100,\n\t\t\t});\n\t\t}\n\n\t\tawait this.executePolicy(filePathsToCheck, this.commandContext);\n\t}\n\n\tprivate async executePolicy(\n\t\tpathsToCheck: string[],\n\t\tcommandContext: CheckPolicyCommandContext,\n\t): Promise<void> {\n\t\ttry {\n\t\t\tfor (const pathToCheck of pathsToCheck) {\n\t\t\t\t// eslint-disable-next-line no-await-in-loop\n\t\t\t\tawait this.checkOrExcludeFile(pathToCheck, commandContext);\n\t\t\t}\n\t\t} finally {\n\t\t\tthis.logStats();\n\t\t}\n\t}\n\n\t/**\n\t * Routes files to their handlers and resolvers by regex testing their full paths. If a file fails a policy that has a\n\t * resolver, the resolver will be invoked as well. Synchronizes the output, exit codes, and resolve\n\t * decision for all handlers.\n\t */\n\tprivate async routeToHandlers(\n\t\tfile: string,\n\t\tcommandContext: CheckPolicyCommandContext,\n\t): Promise<void> {\n\t\tconst {\n\t\t\t// context,\n\t\t\tpolicies,\n\t\t\texcludePoliciesForFiles,\n\t\t\tgitRoot,\n\t\t} = commandContext;\n\n\t\t// Use the repo-relative path so that regexes that specify string start (^) will match repo paths.\n\t\t// Replace \\ in result with / in case OS is Windows.\n\t\tconst relPath = path.relative(gitRoot, file).replace(/\\\\/g, \"/\");\n\n\t\tawait Promise.all(\n\t\t\tpolicies\n\t\t\t\t.filter((handler) => handler.match.test(relPath))\n\t\t\t\t// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: <explanation>\n\t\t\t\t.map(async (policy): Promise<void> => {\n\t\t\t\t\t// doing exclusion per handler\n\t\t\t\t\tconst exclusions = excludePoliciesForFiles.get(policy.name);\n\t\t\t\t\tif (\n\t\t\t\t\t\texclusions !== undefined &&\n\t\t\t\t\t\t!exclusions.every((regex) => !regex.test(relPath))\n\t\t\t\t\t) {\n\t\t\t\t\t\tthis.verbose(`Excluded from '${policy.name}' policy: ${relPath}`);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst result = await runWithPerf(policy.name, \"handle\", async () =>\n\t\t\t\t\t\tpolicy.handler({\n\t\t\t\t\t\t\tfile: relPath,\n\t\t\t\t\t\t\troot: gitRoot,\n\t\t\t\t\t\t\tresolve: this.flags.fix,\n\t\t\t\t\t\t\tconfig: this.commandConfig?.policySettings?.[policy.name],\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\n\t\t\t\t\tif (result === true) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst messages = new StringBuilder();\n\t\t\t\t\tif (isPolicyFixResult(result)) {\n\t\t\t\t\t\tif (result.resolved) {\n\t\t\t\t\t\t\tmessages.append(\n\t\t\t\t\t\t\t\t`Resolved ${policy.name} policy failure for file: ${result.file}`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tmessages.append(\n\t\t\t\t\t\t\t\t`Error when trying to fix ${policy.name} policy failure in ${result.file}`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tprocess.exitCode = 1;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// result must be a PolicyFailureResult; check if there is a standalone resolver.\n\t\t\t\t\t\tconst { resolver } = policy;\n\n\t\t\t\t\t\tif (this.flags.fix && resolver !== undefined) {\n\t\t\t\t\t\t\t// Resolve the failure\n\t\t\t\t\t\t\tmessages.append(`${newline}attempting to resolve: ${relPath}`);\n\t\t\t\t\t\t\tconst resolveResult = await runWithPerf(\n\t\t\t\t\t\t\t\tpolicy.name,\n\t\t\t\t\t\t\t\t\"resolve\",\n\t\t\t\t\t\t\t\tasync () => resolver({ file: relPath, root: gitRoot }),\n\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\tresolveResult.errorMessage !== undefined &&\n\t\t\t\t\t\t\t\tresolveResult.errorMessage !== \"\"\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tmessages.append(newline + resolveResult.errorMessage);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (!resolveResult.resolved) {\n\t\t\t\t\t\t\t\tprocess.exitCode = 1;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// No resolver, or fix is false, so we're in the full failure case.\n\t\t\t\t\t\t\tconst autoFixable = result.autoFixable\n\t\t\t\t\t\t\t\t? chalk.green(\" (autofixable)\")\n\t\t\t\t\t\t\t\t: \"\";\n\t\t\t\t\t\t\tmessages.append(\n\t\t\t\t\t\t\t\t`'${policy.name}' policy failure${autoFixable}: ${result.file}`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tmessages.append(\n\t\t\t\t\t\t\t\tresult.errorMessage === undefined\n\t\t\t\t\t\t\t\t\t? \"\"\n\t\t\t\t\t\t\t\t\t: `${newline}${result.errorMessage}`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tprocess.exitCode = 1;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif ((process.exitCode ?? 0) === 0) {\n\t\t\t\t\t\tthis.info(messages.toString());\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.warning(messages.toString());\n\t\t\t\t\t}\n\t\t\t\t}),\n\t\t);\n\t}\n\n\tprivate logStats(): void {\n\t\tthis.log(\n\t\t\t`Statistics: ${this.processed} processed, ${\n\t\t\t\tthis.count - this.processed\n\t\t\t} excluded, ${this.count} total`,\n\t\t);\n\t\tfor (const [action, handlerPerf] of handlerPerformanceData.entries()) {\n\t\t\tthis.log(`Performance for \"${action}\":`);\n\t\t\tfor (const [handler, dur] of handlerPerf.entries()) {\n\t\t\t\tthis.log(`\\t${handler}: ${dur}ms`);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Given a string that represents a path to a file in the repo, determines if the file should be checked, and if so,\n\t * routes the file to the appropriate handlers.\n\t */\n\tprivate async checkOrExcludeFile(\n\t\tinputPath: string,\n\t\tcommandContext: CheckPolicyCommandContext,\n\t): Promise<void> {\n\t\tconst { excludeFiles: exclusions, gitRoot, pathRegex } = commandContext;\n\n\t\tconst filePath = path.join(gitRoot, inputPath).trim().replace(/\\\\/g, \"/\");\n\n\t\tif (!(pathRegex.test(inputPath) && fs.existsSync(filePath))) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.count++;\n\t\tif (!exclusions.every((value) => !value.test(inputPath))) {\n\t\t\tthis.verbose(`Excluded all handlers: ${inputPath}`);\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tawait this.routeToHandlers(filePath, commandContext);\n\t\t} catch (error: unknown) {\n\t\t\tthrow new Error(`Error routing ${filePath} to handler: ${error}`);\n\t\t}\n\n\t\tthis.processed++;\n\t}\n}\n\nasync function runWithPerf<T>(\n\tname: string,\n\taction: PolicyAction,\n\trun: () => Promise<T>,\n): Promise<T> {\n\tconst actionMap =\n\t\thandlerPerformanceData.get(action) ?? new Map<string, number>();\n\tlet dur = actionMap.get(name) ?? 0;\n\n\tconst start = Date.now();\n\tconst result = await run();\n\tdur += Date.now() - start;\n\n\tactionMap.set(name, dur);\n\thandlerPerformanceData.set(action, actionMap);\n\treturn result;\n}\n\nasync function readStdin(): Promise<string> {\n\treturn new Promise((resolve) => {\n\t\tconst stdin = process.stdin;\n\t\tstdin.setEncoding(\"utf8\");\n\n\t\tlet data = \"\";\n\t\tstdin.on(\"data\", (chunk) => {\n\t\t\tdata += chunk;\n\t\t});\n\n\t\tstdin.on(\"end\", () => {\n\t\t\tresolve(data);\n\t\t});\n\n\t\tif (stdin.isTTY) {\n\t\t\tresolve(\"\");\n\t\t}\n\t});\n}\n\nfunction toRegExp(input: string | RegExp): RegExp {\n\tif (typeof input === \"string\") {\n\t\treturn new RegExp(input, \"i\");\n\t}\n\treturn input;\n}\n"]}
@@ -0,0 +1,35 @@
1
+ import type { RequireAtLeastOne, SetOptional } from "type-fest";
2
+ import type { PackageJsonPropertiesSettings } from "./policies/PackageJsonProperties.js";
3
+ import type { PolicyName, RepoPolicy } from "./policy.js";
4
+ /**
5
+ * @alpha
6
+ */
7
+ export type PerPolicySettings = ({
8
+ PackageJsonProperties: PackageJsonPropertiesSettings;
9
+ } & Record<PolicyName, unknown>) | undefined;
10
+ /**
11
+ * @alpha
12
+ */
13
+ export interface OptionalPolicyConfig {
14
+ policies?: RepoPolicy[];
15
+ /**
16
+ * An array of strings/regular expressions. Paths that match any of these expressions will be completely excluded from
17
+ * policy.
18
+ */
19
+ excludeFiles?: (string | RegExp)[];
20
+ /**
21
+ * An object with a policy name as keys that map to an array of strings/regular expressions to
22
+ * exclude that rule from being checked.
23
+ */
24
+ excludePoliciesForFiles?: Record<PolicyName, (string | RegExp)[]>;
25
+ includeDefaultPolicies?: boolean;
26
+ policySettings?: PerPolicySettings | undefined;
27
+ }
28
+ /**
29
+ * A type representing policy configuration.
30
+ *
31
+ * @alpha
32
+ */
33
+ export type PolicyConfig = RequireAtLeastOne<OptionalPolicyConfig, "policies" | "includeDefaultPolicies">;
34
+ export declare const DefaultPolicyConfig: SetOptional<Required<PolicyConfig>, "policies" | "policySettings">;
35
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAChE,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,qCAAqC,CAAC;AACzF,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE1D;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAC1B,CAAC;IAED,qBAAqB,EAAE,6BAA6B,CAAC;CACpD,GAAG,MAAM,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,GAChC,SAAS,CAAC;AAEb;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,EAAE,UAAU,EAAE,CAAC;IAExB;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;IAEnC;;;OAGG;IACH,uBAAuB,CAAC,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC;IAClE,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAEjC,cAAc,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;CAC/C;AAED;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,iBAAiB,CAC3C,oBAAoB,EACpB,UAAU,GAAG,wBAAwB,CACrC,CAAC;AAEF,eAAO,MAAM,mBAAmB,EAChC,WAAW,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,UAAU,GAAG,gBAAgB,CAIhE,CAAC"}
package/esm/config.js ADDED
@@ -0,0 +1,6 @@
1
+ export const DefaultPolicyConfig = {
2
+ excludeFiles: [],
3
+ excludePoliciesForFiles: {},
4
+ includeDefaultPolicies: true,
5
+ };
6
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AA8CA,MAAM,CAAC,MAAM,mBAAmB,GACqC;IACpE,YAAY,EAAE,EAAE;IAChB,uBAAuB,EAAE,EAAE;IAC3B,sBAAsB,EAAE,IAAI;CAC5B,CAAC","sourcesContent":["import type { RequireAtLeastOne, SetOptional } from \"type-fest\";\nimport type { PackageJsonPropertiesSettings } from \"./policies/PackageJsonProperties.js\";\nimport type { PolicyName, RepoPolicy } from \"./policy.js\";\n\n/**\n * @alpha\n */\nexport type PerPolicySettings =\n\t| ({\n\t\t\t// biome-ignore lint/style/useNamingConvention: key needs to match policy name\n\t\t\tPackageJsonProperties: PackageJsonPropertiesSettings;\n\t } & Record<PolicyName, unknown>)\n\t| undefined;\n\n/**\n * @alpha\n */\nexport interface OptionalPolicyConfig {\n\tpolicies?: RepoPolicy[];\n\n\t/**\n\t * An array of strings/regular expressions. Paths that match any of these expressions will be completely excluded from\n\t * policy.\n\t */\n\texcludeFiles?: (string | RegExp)[];\n\n\t/**\n\t * An object with a policy name as keys that map to an array of strings/regular expressions to\n\t * exclude that rule from being checked.\n\t */\n\texcludePoliciesForFiles?: Record<PolicyName, (string | RegExp)[]>;\n\tincludeDefaultPolicies?: boolean;\n\n\tpolicySettings?: PerPolicySettings | undefined;\n}\n\n/**\n * A type representing policy configuration.\n *\n * @alpha\n */\nexport type PolicyConfig = RequireAtLeastOne<\n\tOptionalPolicyConfig,\n\t\"policies\" | \"includeDefaultPolicies\"\n>;\n\nexport const DefaultPolicyConfig: // PolicyConfig\nSetOptional<Required<PolicyConfig>, \"policies\" | \"policySettings\"> = {\n\texcludeFiles: [],\n\texcludePoliciesForFiles: {},\n\tincludeDefaultPolicies: true,\n};\n"]}
package/esm/index.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ export { run } from "@oclif/core";
2
+ export type { PolicyFailure, PolicyFixResult, PolicyFunctionArguments, PolicyHandler, PolicyName, PolicyStandaloneResolver, RepoPolicy, } from "./policy.js";
3
+ export { DefaultPolicies } from "./policy.js";
4
+ export type { PolicyConfig, OptionalPolicyConfig, PerPolicySettings, } from "./config.js";
5
+ export type { PackageJsonPropertiesSettings, PackageJsonProperty, } from "./policies/PackageJsonProperties.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAElC,YAAY,EACX,aAAa,EACb,eAAe,EACf,uBAAuB,EACvB,aAAa,EACb,UAAU,EACV,wBAAwB,EACxB,UAAU,GACV,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,YAAY,EACX,YAAY,EACZ,oBAAoB,EACpB,iBAAiB,GACjB,MAAM,aAAa,CAAC;AACrB,YAAY,EACX,6BAA6B,EAC7B,mBAAmB,GACnB,MAAM,qCAAqC,CAAC"}
package/esm/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { run } from "@oclif/core";
2
+ export { DefaultPolicies } from "./policy.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAWlC,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC","sourcesContent":["export { run } from \"@oclif/core\";\n\nexport type {\n\tPolicyFailure,\n\tPolicyFixResult,\n\tPolicyFunctionArguments,\n\tPolicyHandler,\n\tPolicyName,\n\tPolicyStandaloneResolver,\n\tRepoPolicy,\n} from \"./policy.js\";\nexport { DefaultPolicies } from \"./policy.js\";\nexport type {\n\tPolicyConfig,\n\tOptionalPolicyConfig,\n\tPerPolicySettings,\n} from \"./config.js\";\nexport type {\n\tPackageJsonPropertiesSettings,\n\tPackageJsonProperty,\n} from \"./policies/PackageJsonProperties.js\";\n"]}
@@ -0,0 +1,10 @@
1
+ import type { RepoPolicy } from "../policy.js";
2
+ /**
3
+ * A repo policy that checks for JavaScript source files that just use the .js file extension. Such files may be
4
+ * interpreted by node as either CommonJS or ESM based on the `type` field in the nearest package.json file. This
5
+ * can create unexpected behavior for JS files; changing the package.json nearest to one will change how the JS
6
+ * is processed by node. Using explicit file extensions reduces ambiguity and ensures a CJS file isn't suddenly treated
7
+ * like an ESM file.
8
+ */
9
+ export declare const NoJsFileExtensions: RepoPolicy;
10
+ //# sourceMappingURL=NoJsFileExtensions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NoJsFileExtensions.d.ts","sourceRoot":"","sources":["../../src/policies/NoJsFileExtensions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,UAAU,EAAE,MAAM,cAAc,CAAC;AAE9D;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,EAAE,UAehC,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * A repo policy that checks for JavaScript source files that just use the .js file extension. Such files may be
3
+ * interpreted by node as either CommonJS or ESM based on the `type` field in the nearest package.json file. This
4
+ * can create unexpected behavior for JS files; changing the package.json nearest to one will change how the JS
5
+ * is processed by node. Using explicit file extensions reduces ambiguity and ensures a CJS file isn't suddenly treated
6
+ * like an ESM file.
7
+ */
8
+ export const NoJsFileExtensions = {
9
+ name: "NoJsFileExtensions",
10
+ match: /(^|\/)[^/]+\.js$/i,
11
+ // biome-ignore lint/suspicious/useAwait: <explanation>
12
+ handler: async ({ file }) => {
13
+ // Any match is considered a failure.
14
+ const result = {
15
+ name: NoJsFileExtensions.name,
16
+ file,
17
+ autoFixable: false,
18
+ errorMessage: "JavaScript files should have a .cjs or .mjs file extension based on the module format of the file. Rename the file accordingly.",
19
+ };
20
+ return result;
21
+ },
22
+ };
23
+ //# sourceMappingURL=NoJsFileExtensions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NoJsFileExtensions.js","sourceRoot":"","sources":["../../src/policies/NoJsFileExtensions.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAe;IAC7C,IAAI,EAAE,oBAAoB;IAC1B,KAAK,EAAE,mBAAmB;IAC1B,uDAAuD;IACvD,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;QAC3B,qCAAqC;QACrC,MAAM,MAAM,GAAkB;YAC7B,IAAI,EAAE,kBAAkB,CAAC,IAAI;YAC7B,IAAI;YACJ,WAAW,EAAE,KAAK;YAClB,YAAY,EACX,iIAAiI;SAClI,CAAC;QACF,OAAO,MAAM,CAAC;IACf,CAAC;CACD,CAAC","sourcesContent":["import type { PolicyFailure, RepoPolicy } from \"../policy.js\";\n\n/**\n * A repo policy that checks for JavaScript source files that just use the .js file extension. Such files may be\n * interpreted by node as either CommonJS or ESM based on the `type` field in the nearest package.json file. This\n * can create unexpected behavior for JS files; changing the package.json nearest to one will change how the JS\n * is processed by node. Using explicit file extensions reduces ambiguity and ensures a CJS file isn't suddenly treated\n * like an ESM file.\n */\nexport const NoJsFileExtensions: RepoPolicy = {\n\tname: \"NoJsFileExtensions\",\n\tmatch: /(^|\\/)[^/]+\\.js$/i,\n\t// biome-ignore lint/suspicious/useAwait: <explanation>\n\thandler: async ({ file }) => {\n\t\t// Any match is considered a failure.\n\t\tconst result: PolicyFailure = {\n\t\t\tname: NoJsFileExtensions.name,\n\t\t\tfile,\n\t\t\tautoFixable: false,\n\t\t\terrorMessage:\n\t\t\t\t\"JavaScript files should have a .cjs or .mjs file extension based on the module format of the file. Rename the file accordingly.\",\n\t\t};\n\t\treturn result;\n\t},\n};\n"]}
@@ -0,0 +1,21 @@
1
+ import type { RepoPolicy } from "../policy.js";
2
+ /**
3
+ * @alpha
4
+ */
5
+ export type PackageJsonProperty = string;
6
+ /**
7
+ * Policy settings for the PackageJsonProperties repo policy.
8
+ *
9
+ * @alpha
10
+ */
11
+ export interface PackageJsonPropertiesSettings {
12
+ /**
13
+ * Sets a package.json property to the string value provided. The string value will be used verbatim.
14
+ */
15
+ verbatim: Record<PackageJsonProperty, string>;
16
+ }
17
+ /**
18
+ * A RepoPolicy that checks that package.json properties in packages match expected values.
19
+ */
20
+ export declare const PackageJsonProperties: RepoPolicy<PackageJsonPropertiesSettings>;
21
+ //# sourceMappingURL=PackageJsonProperties.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PackageJsonProperties.d.ts","sourceRoot":"","sources":["../../src/policies/PackageJsonProperties.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAkC,UAAU,EAAE,MAAM,cAAc,CAAC;AAE/E;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC;AAEzC;;;;GAIG;AACH,MAAM,WAAW,6BAA6B;IAC7C;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAAC;CAC9C;AAED;;GAEG;AACH,eAAO,MAAM,qBAAqB,EAAE,UAAU,CAAC,6BAA6B,CAkD1E,CAAC"}
@@ -0,0 +1,45 @@
1
+ import { readJson, writeJson } from "fs-extra/esm";
2
+ /**
3
+ * A RepoPolicy that checks that package.json properties in packages match expected values.
4
+ */
5
+ export const PackageJsonProperties = {
6
+ name: "PackageJsonProperties",
7
+ match: /(^|\/)package\.json/i,
8
+ handler: async ({ file, config, resolve }) => {
9
+ if (config === undefined) {
10
+ return true;
11
+ }
12
+ const failResult = {
13
+ name: PackageJsonProperties.name,
14
+ file,
15
+ autoFixable: true,
16
+ };
17
+ const { verbatim } = config;
18
+ const json = (await readJson(file));
19
+ const messages = [];
20
+ for (const [propName, value] of Object.entries(verbatim)) {
21
+ if (json[propName] !== value) {
22
+ messages.push(`Incorrect package.json field value for '${propName}'. Expected '${value}', got '${json[propName]}'.`);
23
+ }
24
+ }
25
+ if (messages.length > 0) {
26
+ if (resolve) {
27
+ const fixResult = {
28
+ ...failResult,
29
+ resolved: false,
30
+ };
31
+ for (const [propName, value] of Object.entries(verbatim)) {
32
+ json[propName] = value;
33
+ }
34
+ await writeJson(file, json, { spaces: "\t" });
35
+ fixResult.resolved = true;
36
+ return fixResult;
37
+ }
38
+ // There were errors, and we're not resolving them, so return a fail result
39
+ failResult.errorMessage = messages.join("\n");
40
+ return failResult;
41
+ }
42
+ return true;
43
+ },
44
+ };
45
+ //# sourceMappingURL=PackageJsonProperties.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PackageJsonProperties.js","sourceRoot":"","sources":["../../src/policies/PackageJsonProperties.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAqBnD;;GAEG;AACH,MAAM,CAAC,MAAM,qBAAqB,GACjC;IACC,IAAI,EAAE,uBAAuB;IAC7B,KAAK,EAAE,sBAAsB;IAC7B,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE;QAC5C,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YAC1B,OAAO,IAAI,CAAC;QACb,CAAC;QAED,MAAM,UAAU,GAAkB;YACjC,IAAI,EAAE,qBAAqB,CAAC,IAAI;YAChC,IAAI;YACJ,WAAW,EAAE,IAAI;SACjB,CAAC;QAEF,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,CAAC;QAC5B,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAgB,CAAC;QACnD,MAAM,QAAQ,GAAa,EAAE,CAAC;QAE9B,KAAK,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC1D,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,KAAK,EAAE,CAAC;gBAC9B,QAAQ,CAAC,IAAI,CACZ,2CAA2C,QAAQ,gBAAgB,KAAK,WAAW,IAAI,CAAC,QAAQ,CAAC,IAAI,CACrG,CAAC;YACH,CAAC;QACF,CAAC;QAED,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACzB,IAAI,OAAO,EAAE,CAAC;gBACb,MAAM,SAAS,GAAoB;oBAClC,GAAG,UAAU;oBACb,QAAQ,EAAE,KAAK;iBACf,CAAC;gBAEF,KAAK,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;oBAC1D,IAAI,CAAC,QAAQ,CAAC,GAAG,KAAK,CAAC;gBACxB,CAAC;gBACD,MAAM,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;gBAE9C,SAAS,CAAC,QAAQ,GAAG,IAAI,CAAC;gBAC1B,OAAO,SAAS,CAAC;YAClB,CAAC;YAED,2EAA2E;YAC3E,UAAU,CAAC,YAAY,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC9C,OAAO,UAAU,CAAC;QACnB,CAAC;QAED,OAAO,IAAI,CAAC;IACb,CAAC;CACD,CAAC","sourcesContent":["import { readJson, writeJson } from \"fs-extra/esm\";\nimport type { PackageJson } from \"type-fest\";\nimport type { PolicyFailure, PolicyFixResult, RepoPolicy } from \"../policy.js\";\n\n/**\n * @alpha\n */\nexport type PackageJsonProperty = string;\n\n/**\n * Policy settings for the PackageJsonProperties repo policy.\n *\n * @alpha\n */\nexport interface PackageJsonPropertiesSettings {\n\t/**\n\t * Sets a package.json property to the string value provided. The string value will be used verbatim.\n\t */\n\tverbatim: Record<PackageJsonProperty, string>;\n}\n\n/**\n * A RepoPolicy that checks that package.json properties in packages match expected values.\n */\nexport const PackageJsonProperties: RepoPolicy<PackageJsonPropertiesSettings> =\n\t{\n\t\tname: \"PackageJsonProperties\",\n\t\tmatch: /(^|\\/)package\\.json/i,\n\t\thandler: async ({ file, config, resolve }) => {\n\t\t\tif (config === undefined) {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tconst failResult: PolicyFailure = {\n\t\t\t\tname: PackageJsonProperties.name,\n\t\t\t\tfile,\n\t\t\t\tautoFixable: true,\n\t\t\t};\n\n\t\t\tconst { verbatim } = config;\n\t\t\tconst json = (await readJson(file)) as PackageJson;\n\t\t\tconst messages: string[] = [];\n\n\t\t\tfor (const [propName, value] of Object.entries(verbatim)) {\n\t\t\t\tif (json[propName] !== value) {\n\t\t\t\t\tmessages.push(\n\t\t\t\t\t\t`Incorrect package.json field value for '${propName}'. Expected '${value}', got '${json[propName]}'.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (messages.length > 0) {\n\t\t\t\tif (resolve) {\n\t\t\t\t\tconst fixResult: PolicyFixResult = {\n\t\t\t\t\t\t...failResult,\n\t\t\t\t\t\tresolved: false,\n\t\t\t\t\t};\n\n\t\t\t\t\tfor (const [propName, value] of Object.entries(verbatim)) {\n\t\t\t\t\t\tjson[propName] = value;\n\t\t\t\t\t}\n\t\t\t\t\tawait writeJson(file, json, { spaces: \"\\t\" });\n\n\t\t\t\t\tfixResult.resolved = true;\n\t\t\t\t\treturn fixResult;\n\t\t\t\t}\n\n\t\t\t\t// There were errors, and we're not resolving them, so return a fail result\n\t\t\t\tfailResult.errorMessage = messages.join(\"\\n\");\n\t\t\t\treturn failResult;\n\t\t\t}\n\n\t\t\treturn true;\n\t\t},\n\t};\n"]}
@@ -0,0 +1,7 @@
1
+ import type { RepoPolicy } from "../policy.js";
2
+ /**
3
+ * A RepoPolicy that checks that the repository.directory property in package.json is set correctly. If the repository
4
+ * field is a string instead of an object the package will be ignored.
5
+ */
6
+ export declare const PackageJsonRepoDirectoryProperty: RepoPolicy;
7
+ //# sourceMappingURL=PackageJsonRepoDirectoryProperty.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PackageJsonRepoDirectoryProperty.d.ts","sourceRoot":"","sources":["../../src/policies/PackageJsonRepoDirectoryProperty.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAkC,UAAU,EAAE,MAAM,cAAc,CAAC;AAE/E;;;GAGG;AACH,eAAO,MAAM,gCAAgC,EAAE,UA2C9C,CAAC"}