@rtorcato/repo-tooling 3.4.0 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/base/checks.js
CHANGED
|
@@ -1,6 +1,29 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import fs from 'fs-extra';
|
|
3
3
|
import { BADGE_START, hasPublicOnlyBadges } from '../cli/generators/badges.js';
|
|
4
|
+
import { detectNestedLanguages } from '../cli/utils/detect-language.js';
|
|
5
|
+
/**
|
|
6
|
+
* Root-only detection is the decision (#317), not an oversight — but it used to
|
|
7
|
+
* be a *silent* one: a Swift repo with a TypeScript docs app audited as Swift
|
|
8
|
+
* and never mentioned the half it skipped. This says so.
|
|
9
|
+
*
|
|
10
|
+
* Always `ok`. Nesting a second language is a legitimate repo shape, so failing
|
|
11
|
+
* on it would break every monorepo's CI to report a limitation of ours.
|
|
12
|
+
*/
|
|
13
|
+
export async function checkNestedLanguages(dir, rootLanguage) {
|
|
14
|
+
const check = 'Monorepo';
|
|
15
|
+
const nested = await detectNestedLanguages(dir, rootLanguage);
|
|
16
|
+
if (nested.length === 0) {
|
|
17
|
+
return { check, status: 'ok', detail: 'single-language repo' };
|
|
18
|
+
}
|
|
19
|
+
const summary = nested.map((n) => `${n.dir} (${n.language})`).join(', ');
|
|
20
|
+
return {
|
|
21
|
+
check,
|
|
22
|
+
status: 'ok',
|
|
23
|
+
detail: `auditing the root only — ${summary} ${nested.length === 1 ? 'is' : 'are'} not audited`,
|
|
24
|
+
hint: 'Run doctor with `--directory <path>` to audit a nested project on its own',
|
|
25
|
+
};
|
|
26
|
+
}
|
|
4
27
|
export async function checkFile(dir, spec) {
|
|
5
28
|
for (const candidate of spec.candidates) {
|
|
6
29
|
const filepath = path.join(dir, candidate);
|
|
@@ -12,7 +12,7 @@ import { checkGitHubSettings } from '../../base/github-settings.js';
|
|
|
12
12
|
import { checkGitIdentity } from '../../base/git-identity.js';
|
|
13
13
|
import { LOCKFILE_VERSION, readLockfile } from '../utils/lockfile.js';
|
|
14
14
|
import { declinedInLock, getFixTargetForCheck } from './fix-targets.js';
|
|
15
|
-
import { checkAiSetup, checkCodeowners, checkCodeQL, checkCommunityHealth, checkCoverageUpload, checkDependabot, checkEditorConfig, checkFile, checkGitHooks, checkGitHubActions, checkGitLabCI, checkPrePushHook, checkReadmeBadges, COMMITLINT_FILE_CHECK, } from '../../base/checks.js';
|
|
15
|
+
import { checkAiSetup, checkCodeowners, checkCodeQL, checkCommunityHealth, checkCoverageUpload, checkDependabot, checkEditorConfig, checkFile, checkGitHooks, checkGitHubActions, checkGitLabCI, checkNestedLanguages, checkPrePushHook, checkReadmeBadges, COMMITLINT_FILE_CHECK, } from '../../base/checks.js';
|
|
16
16
|
import { allDeps, checkAreTheTypesWrong, checkConfigSchemaVersions, checkDocsSite, checkEnginesNode, checkGitDependencies, checkKnip, checkLintStaged, checkNodeVersionConsistency, checkNodeVersionPin, checkPackageJson, checkPublint, checkSemanticRelease, checkSizeLimit, checkTailwind, checkPnpmWorkspace, checkTreeshakeSetup, checkTurborepo, checkTypedoc, checkVerifyScript, checkVscodeExtensions, evaluateNodeVersion, FILE_CHECKS, findDocsAppDir, jsBadgeAudience, jsGitHooksProfile, readPackageJson, } from '../../languages/js/checks.js';
|
|
17
17
|
export { evaluateNodeVersion };
|
|
18
18
|
const PACKAGE = '@rtorcato/repo-tooling';
|
|
@@ -145,6 +145,7 @@ function demoteDeclined(results, lock) {
|
|
|
145
145
|
async function runBaseChecks(dir, lock, opts) {
|
|
146
146
|
const results = [];
|
|
147
147
|
results.push(checkLockfile(lock));
|
|
148
|
+
results.push(await checkNestedLanguages(dir, opts.language));
|
|
148
149
|
results.push(await checkGitIdentity(dir));
|
|
149
150
|
results.push(await checkEditorConfig(dir));
|
|
150
151
|
results.push(await checkFile(dir, COMMITLINT_FILE_CHECK));
|
|
@@ -191,6 +192,7 @@ export async function runDoctor(dir) {
|
|
|
191
192
|
hooks: null,
|
|
192
193
|
badges: { audience: 'public', fixTarget: null },
|
|
193
194
|
presetWorkflow: null,
|
|
195
|
+
language,
|
|
194
196
|
})),
|
|
195
197
|
];
|
|
196
198
|
return demoteDeclined(results, lock);
|
|
@@ -211,6 +213,7 @@ export async function runDoctor(dir) {
|
|
|
211
213
|
// package.json name/repository, which a Swift repo hasn't got.
|
|
212
214
|
badges: { audience: 'public', fixTarget: null },
|
|
213
215
|
presetWorkflow: renderSwiftWorkflow(await readSwiftPackage(targetDir)),
|
|
216
|
+
language,
|
|
214
217
|
})),
|
|
215
218
|
...(await runSwiftChecks(targetDir)),
|
|
216
219
|
];
|
|
@@ -261,6 +264,7 @@ export async function runDoctor(dir) {
|
|
|
261
264
|
hooks: jsGitHooksProfile(pkg),
|
|
262
265
|
badges: { audience: jsBadgeAudience(pkg), fixTarget: 'badges' },
|
|
263
266
|
presetWorkflow: renderGitHubWorkflow(githubJobs(inferProjectConfig(pkg))),
|
|
267
|
+
language,
|
|
264
268
|
})));
|
|
265
269
|
return demoteDeclined(results, lock);
|
|
266
270
|
}
|
|
@@ -2,8 +2,9 @@ import path from 'node:path';
|
|
|
2
2
|
import fs from 'fs-extra';
|
|
3
3
|
/**
|
|
4
4
|
* Marker files that identify a repo's language, checked in order — first match
|
|
5
|
-
* wins. One language per repo root; multi-language monorepos
|
|
6
|
-
*
|
|
5
|
+
* wins. One language per repo root; multi-language monorepos stay out of scope
|
|
6
|
+
* (#139, reaffirmed in #317 — the audit is root-only, but says so via
|
|
7
|
+
* `detectNestedLanguages`). A dir with no marker → 'unknown' (base checks only).
|
|
7
8
|
*/
|
|
8
9
|
const MARKERS = [
|
|
9
10
|
['js', ['package.json']],
|
|
@@ -20,3 +21,44 @@ export async function detectLanguage(dir) {
|
|
|
20
21
|
}
|
|
21
22
|
return 'unknown';
|
|
22
23
|
}
|
|
24
|
+
/** Never worth descending into, and expensive when we do. */
|
|
25
|
+
const SKIP_DIRS = new Set(['node_modules', 'dist', 'build', 'target', 'vendor', '.build']);
|
|
26
|
+
/**
|
|
27
|
+
* Sub-project dirs whose language marker disagrees with the root's, as
|
|
28
|
+
* `{ dir, language }` sorted by dir. Depth-2 scan — enough for the shapes that
|
|
29
|
+
* exist (`apps/docs`, `packages/*`, a top-level `docs/`) and cheap enough to run
|
|
30
|
+
* on every doctor.
|
|
31
|
+
*
|
|
32
|
+
* Deliberately *not* driven by the workspace manifest: the case #317 is about is
|
|
33
|
+
* a Swift or Perl root with a JS docs app, and such a repo has no
|
|
34
|
+
* `pnpm-workspace.yaml` to read. Walking the tree finds it either way, and needs
|
|
35
|
+
* no glob dependency.
|
|
36
|
+
*/
|
|
37
|
+
export async function detectNestedLanguages(dir, rootLanguage) {
|
|
38
|
+
const found = [];
|
|
39
|
+
const scan = async (relative, depth) => {
|
|
40
|
+
let entries;
|
|
41
|
+
try {
|
|
42
|
+
entries = await fs.readdir(path.join(dir, relative));
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return; // unreadable dir is the caller's problem, not the audit's
|
|
46
|
+
}
|
|
47
|
+
for (const entry of entries) {
|
|
48
|
+
if (entry.startsWith('.') || SKIP_DIRS.has(entry))
|
|
49
|
+
continue;
|
|
50
|
+
const child = relative ? `${relative}/${entry}` : entry;
|
|
51
|
+
if (!(await fs.stat(path.join(dir, child)).catch(() => null))?.isDirectory())
|
|
52
|
+
continue;
|
|
53
|
+
const language = await detectLanguage(path.join(dir, child));
|
|
54
|
+
if (language !== 'unknown' && language !== rootLanguage) {
|
|
55
|
+
found.push({ dir: child, language });
|
|
56
|
+
continue; // a package's own subdirs belong to it, not to the root
|
|
57
|
+
}
|
|
58
|
+
if (depth > 1)
|
|
59
|
+
await scan(child, depth - 1);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
await scan('', 2);
|
|
63
|
+
return found.sort((a, b) => a.dir.localeCompare(b.dir));
|
|
64
|
+
}
|
package/package.json
CHANGED