@limenet-ch/baseline 2.9.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/LICENSE.md +21 -0
- package/README.md +272 -0
- package/js/dist/bin.d.ts +2 -0
- package/js/dist/bin.js +59 -0
- package/js/dist/bin.js.map +1 -0
- package/js/dist/checks/allows-tooling-in-claude-settings-check.d.ts +11 -0
- package/js/dist/checks/allows-tooling-in-claude-settings-check.js +14 -0
- package/js/dist/checks/allows-tooling-in-claude-settings-check.js.map +1 -0
- package/js/dist/checks/biome-uses-local-schema-check.d.ts +21 -0
- package/js/dist/checks/biome-uses-local-schema-check.js +78 -0
- package/js/dist/checks/biome-uses-local-schema-check.js.map +1 -0
- package/js/dist/checks/calls-baseline-check.d.ts +11 -0
- package/js/dist/checks/calls-baseline-check.js +34 -0
- package/js/dist/checks/calls-baseline-check.js.map +1 -0
- package/js/dist/checks/check.d.ts +37 -0
- package/js/dist/checks/check.js +54 -0
- package/js/dist/checks/check.js.map +1 -0
- package/js/dist/checks/ci-sets-node-version-check.d.ts +10 -0
- package/js/dist/checks/ci-sets-node-version-check.js +43 -0
- package/js/dist/checks/ci-sets-node-version-check.js.map +1 -0
- package/js/dist/checks/claude-settings-check.d.ts +30 -0
- package/js/dist/checks/claude-settings-check.js +43 -0
- package/js/dist/checks/claude-settings-check.js.map +1 -0
- package/js/dist/checks/denies-env-reads-in-claude-settings-check.d.ts +13 -0
- package/js/dist/checks/denies-env-reads-in-claude-settings-check.js +25 -0
- package/js/dist/checks/denies-env-reads-in-claude-settings-check.js.map +1 -0
- package/js/dist/checks/does-not-have-copilot-or-junie-agent-files-check.d.ts +5 -0
- package/js/dist/checks/does-not-have-copilot-or-junie-agent-files-check.js +25 -0
- package/js/dist/checks/does-not-have-copilot-or-junie-agent-files-check.js.map +1 -0
- package/js/dist/checks/does-not-use-both-baseline-runners-check.d.ts +13 -0
- package/js/dist/checks/does-not-use-both-baseline-runners-check.js +30 -0
- package/js/dist/checks/does-not-use-both-baseline-runners-check.js.map +1 -0
- package/js/dist/checks/hardens-npm-supply-chain-check.d.ts +5 -0
- package/js/dist/checks/hardens-npm-supply-chain-check.js +68 -0
- package/js/dist/checks/hardens-npm-supply-chain-check.js.map +1 -0
- package/js/dist/checks/has-ci-jobs-check.d.ts +9 -0
- package/js/dist/checks/has-ci-jobs-check.js +14 -0
- package/js/dist/checks/has-ci-jobs-check.js.map +1 -0
- package/js/dist/checks/has-editorconfig-check.d.ts +6 -0
- package/js/dist/checks/has-editorconfig-check.js +42 -0
- package/js/dist/checks/has-editorconfig-check.js.map +1 -0
- package/js/dist/checks/has-npm-scripts-check.d.ts +5 -0
- package/js/dist/checks/has-npm-scripts-check.js +20 -0
- package/js/dist/checks/has-npm-scripts-check.js.map +1 -0
- package/js/dist/checks/has-trivy-config-check.d.ts +26 -0
- package/js/dist/checks/has-trivy-config-check.js +179 -0
- package/js/dist/checks/has-trivy-config-check.js.map +1 -0
- package/js/dist/checks/is-ci-lint-complete-check.d.ts +10 -0
- package/js/dist/checks/is-ci-lint-complete-check.js +25 -0
- package/js/dist/checks/is-ci-lint-complete-check.js.map +1 -0
- package/js/dist/checks/node-version-check.d.ts +5 -0
- package/js/dist/checks/node-version-check.js +77 -0
- package/js/dist/checks/node-version-check.js.map +1 -0
- package/js/dist/checks/periodic-check.d.ts +16 -0
- package/js/dist/checks/periodic-check.js +34 -0
- package/js/dist/checks/periodic-check.js.map +1 -0
- package/js/dist/checks/registry.d.ts +10 -0
- package/js/dist/checks/registry.js +56 -0
- package/js/dist/checks/registry.js.map +1 -0
- package/js/dist/checks/runs-ci-lint-hook-in-claude-settings-check.d.ts +6 -0
- package/js/dist/checks/runs-ci-lint-hook-in-claude-settings-check.js +26 -0
- package/js/dist/checks/runs-ci-lint-hook-in-claude-settings-check.js.map +1 -0
- package/js/dist/checks/updates-dependencies-check.d.ts +5 -0
- package/js/dist/checks/updates-dependencies-check.js +8 -0
- package/js/dist/checks/updates-dependencies-check.js.map +1 -0
- package/js/dist/checks/uses-release-it-check.d.ts +12 -0
- package/js/dist/checks/uses-release-it-check.js +40 -0
- package/js/dist/checks/uses-release-it-check.js.map +1 -0
- package/js/dist/commands/check.d.ts +7 -0
- package/js/dist/commands/check.js +59 -0
- package/js/dist/commands/check.js.map +1 -0
- package/js/dist/commands/install-skills.d.ts +16 -0
- package/js/dist/commands/install-skills.js +47 -0
- package/js/dist/commands/install-skills.js.map +1 -0
- package/js/dist/commands/periodic.d.ts +6 -0
- package/js/dist/commands/periodic.js +40 -0
- package/js/dist/commands/periodic.js.map +1 -0
- package/js/dist/index.d.ts +8 -0
- package/js/dist/index.js +9 -0
- package/js/dist/index.js.map +1 -0
- package/js/dist/policy.d.ts +45 -0
- package/js/dist/policy.js +145 -0
- package/js/dist/policy.js.map +1 -0
- package/js/dist/project.d.ts +26 -0
- package/js/dist/project.js +64 -0
- package/js/dist/project.js.map +1 -0
- package/js/dist/state.d.ts +21 -0
- package/js/dist/state.js +36 -0
- package/js/dist/state.js.map +1 -0
- package/js/dist/support/ci-jobs.d.ts +13 -0
- package/js/dist/support/ci-jobs.js +42 -0
- package/js/dist/support/ci-jobs.js.map +1 -0
- package/js/dist/support/npmrc.d.ts +10 -0
- package/js/dist/support/npmrc.js +36 -0
- package/js/dist/support/npmrc.js.map +1 -0
- package/js/dist/support/versions.d.ts +10 -0
- package/js/dist/support/versions.js +34 -0
- package/js/dist/support/versions.js.map +1 -0
- package/js/skills/creating-a-release/SKILL.md +45 -0
- package/js/skills/updating-dependencies/SKILL.md +104 -0
- package/package.json +67 -0
- package/policy/policy.json +131 -0
- package/policy/policy.schema.json +284 -0
- package/policy/templates/editorconfig +18 -0
- package/policy/templates/trivy.yaml +19 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { CommentCollector, icon, isError, isFixable } from '../checks/check.js';
|
|
2
|
+
import { isPeriodic } from '../checks/periodic-check.js';
|
|
3
|
+
import { createChecks } from '../checks/registry.js';
|
|
4
|
+
import { excludedChecks } from '../state.js';
|
|
5
|
+
/** Runs every registered check and returns the process exit code. */
|
|
6
|
+
export function runCheck(project, options) {
|
|
7
|
+
const comments = new CommentCollector();
|
|
8
|
+
const excluded = excludedChecks(project);
|
|
9
|
+
let errors = 0;
|
|
10
|
+
for (const check of createChecks(project, comments)) {
|
|
11
|
+
// A periodic check that does not apply yields no result at all, matching
|
|
12
|
+
// the PHP runner rather than counting as a pass.
|
|
13
|
+
if (isPeriodic(check) && !check.isApplicable()) {
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
if (excluded.includes(check.name)) {
|
|
17
|
+
console.log(`⚪ ${displayName(check.name)} (excluded)`);
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
comments.reset();
|
|
21
|
+
let fixed = false;
|
|
22
|
+
let result;
|
|
23
|
+
if (options.fix && isFixable(check)) {
|
|
24
|
+
result = check.check();
|
|
25
|
+
if (isError(result)) {
|
|
26
|
+
comments.reset();
|
|
27
|
+
result = check.fix();
|
|
28
|
+
fixed = !isError(result);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
result = check.check();
|
|
33
|
+
}
|
|
34
|
+
if (isError(result)) {
|
|
35
|
+
errors += 1;
|
|
36
|
+
}
|
|
37
|
+
const show = isError(result) || options.verbose || fixed;
|
|
38
|
+
if (show) {
|
|
39
|
+
console.log(`${fixed ? '🔧' : icon(result)} ${displayName(check.name)}${fixed ? ' (fixed)' : ''}`);
|
|
40
|
+
for (const comment of comments.all()) {
|
|
41
|
+
console.log(` ${comment}`);
|
|
42
|
+
}
|
|
43
|
+
if (isError(result)) {
|
|
44
|
+
console.log(` 💡 To exclude, add "${check.name}" to excludes in .baseline.json`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (errors !== 0) {
|
|
49
|
+
console.error(`\nBaseline check failed with ${errors} error(s).`);
|
|
50
|
+
return 1;
|
|
51
|
+
}
|
|
52
|
+
console.log('\nBaseline check passed!');
|
|
53
|
+
return 0;
|
|
54
|
+
}
|
|
55
|
+
/** nodeVersion => Node Version, matching the PHP runner's ucsplit display. */
|
|
56
|
+
function displayName(name) {
|
|
57
|
+
return name.replace(/(?<!^)([A-Z])/g, ' $1').replace(/^./, (first) => first.toUpperCase());
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check.js","sourceRoot":"","sources":["../../src/commands/check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,gBAAgB,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AACjG,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAA;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAEpD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAO5C,qEAAqE;AACrE,MAAM,UAAU,QAAQ,CAAC,OAAgB,EAAE,OAAqB;IAC5D,MAAM,QAAQ,GAAG,IAAI,gBAAgB,EAAE,CAAA;IACvC,MAAM,QAAQ,GAAG,cAAc,CAAC,OAAO,CAAC,CAAA;IAExC,IAAI,MAAM,GAAG,CAAC,CAAA;IAEd,KAAK,MAAM,KAAK,IAAI,YAAY,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE,CAAC;QAClD,yEAAyE;QACzE,iDAAiD;QACjD,IAAI,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,EAAE,EAAE,CAAC;YAC7C,SAAQ;QACZ,CAAC;QAED,IAAI,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YAChC,OAAO,CAAC,GAAG,CAAC,KAAK,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;YAEtD,SAAQ;QACZ,CAAC;QAED,QAAQ,CAAC,KAAK,EAAE,CAAA;QAEhB,IAAI,KAAK,GAAG,KAAK,CAAA;QACjB,IAAI,MAAmB,CAAA;QAEvB,IAAI,OAAO,CAAC,GAAG,IAAI,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC;YAClC,MAAM,GAAG,KAAK,CAAC,KAAK,EAAE,CAAA;YAEtB,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;gBAClB,QAAQ,CAAC,KAAK,EAAE,CAAA;gBAChB,MAAM,GAAG,KAAK,CAAC,GAAG,EAAE,CAAA;gBACpB,KAAK,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;YAC5B,CAAC;QACL,CAAC;aAAM,CAAC;YACJ,MAAM,GAAG,KAAK,CAAC,KAAK,EAAE,CAAA;QAC1B,CAAC;QAED,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAClB,MAAM,IAAI,CAAC,CAAA;QACf,CAAC;QAED,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,OAAO,CAAC,OAAO,IAAI,KAAK,CAAA;QAExD,IAAI,IAAI,EAAE,CAAC;YACP,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;YAElG,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,GAAG,EAAE,EAAE,CAAC;gBACnC,OAAO,CAAC,GAAG,CAAC,MAAM,OAAO,EAAE,CAAC,CAAA;YAChC,CAAC;YAED,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;gBAClB,OAAO,CAAC,GAAG,CAAC,0BAA0B,KAAK,CAAC,IAAI,iCAAiC,CAAC,CAAA;YACtF,CAAC;QACL,CAAC;IACL,CAAC;IAED,IAAI,MAAM,KAAK,CAAC,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,gCAAgC,MAAM,YAAY,CAAC,CAAA;QAEjE,OAAO,CAAC,CAAA;IACZ,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAA;IAEvC,OAAO,CAAC,CAAA;AACZ,CAAC;AAED,8EAA8E;AAC9E,SAAS,WAAW,CAAC,IAAY;IAC7B,OAAO,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAA;AAC9F,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Project } from '../project.js';
|
|
2
|
+
/**
|
|
3
|
+
* Copies the packaged skills into the project's .claude/skills/.
|
|
4
|
+
*
|
|
5
|
+
* The PHP runner does not need this: Laravel Boost discovers
|
|
6
|
+
* resources/boost/skills/ by scanning installed composer packages. A JS project
|
|
7
|
+
* has no vendor autoload and no Boost, so the runner installs them itself.
|
|
8
|
+
*
|
|
9
|
+
* js/src and js/dist sit at the same depth, so this resolves identically under
|
|
10
|
+
* vitest and in the published package.
|
|
11
|
+
*/
|
|
12
|
+
export declare function skillsDirectory(): string;
|
|
13
|
+
export interface InstallSkillsOptions {
|
|
14
|
+
force: boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare function runInstallSkills(project: Project, options: InstallSkillsOptions): number;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
/**
|
|
5
|
+
* Copies the packaged skills into the project's .claude/skills/.
|
|
6
|
+
*
|
|
7
|
+
* The PHP runner does not need this: Laravel Boost discovers
|
|
8
|
+
* resources/boost/skills/ by scanning installed composer packages. A JS project
|
|
9
|
+
* has no vendor autoload and no Boost, so the runner installs them itself.
|
|
10
|
+
*
|
|
11
|
+
* js/src and js/dist sit at the same depth, so this resolves identically under
|
|
12
|
+
* vitest and in the published package.
|
|
13
|
+
*/
|
|
14
|
+
export function skillsDirectory() {
|
|
15
|
+
return join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'skills');
|
|
16
|
+
}
|
|
17
|
+
export function runInstallSkills(project, options) {
|
|
18
|
+
const source = skillsDirectory();
|
|
19
|
+
if (!existsSync(source)) {
|
|
20
|
+
console.error(`No packaged skills found at ${source}.`);
|
|
21
|
+
return 1;
|
|
22
|
+
}
|
|
23
|
+
let installed = 0;
|
|
24
|
+
let skipped = 0;
|
|
25
|
+
for (const skill of readdirSync(source).sort()) {
|
|
26
|
+
const file = join(source, skill, 'SKILL.md');
|
|
27
|
+
if (!existsSync(file)) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
const target = `.claude/skills/${skill}/SKILL.md`;
|
|
31
|
+
if (project.exists(target) && !options.force) {
|
|
32
|
+
console.log(`⏭ ${skill} (already installed — pass --force to overwrite)`);
|
|
33
|
+
skipped += 1;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
project.write(target, readFileSync(file, 'utf8'));
|
|
37
|
+
console.log(`✅ ${skill} → ${target}`);
|
|
38
|
+
installed += 1;
|
|
39
|
+
}
|
|
40
|
+
if (installed === 0 && skipped === 0) {
|
|
41
|
+
console.error('No skills were installed.');
|
|
42
|
+
return 1;
|
|
43
|
+
}
|
|
44
|
+
console.log(`\n${installed} skill(s) installed, ${skipped} left untouched.`);
|
|
45
|
+
return 0;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=install-skills.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install-skills.js","sourceRoot":"","sources":["../../src/commands/install-skills.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAC/D,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAGxC;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe;IAC3B,OAAO,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAA;AAC9E,CAAC;AAMD,MAAM,UAAU,gBAAgB,CAAC,OAAgB,EAAE,OAA6B;IAC5E,MAAM,MAAM,GAAG,eAAe,EAAE,CAAA;IAEhC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACtB,OAAO,CAAC,KAAK,CAAC,+BAA+B,MAAM,GAAG,CAAC,CAAA;QAEvD,OAAO,CAAC,CAAA;IACZ,CAAC;IAED,IAAI,SAAS,GAAG,CAAC,CAAA;IACjB,IAAI,OAAO,GAAG,CAAC,CAAA;IAEf,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QAC7C,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,UAAU,CAAC,CAAA;QAE5C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACpB,SAAQ;QACZ,CAAC;QAED,MAAM,MAAM,GAAG,kBAAkB,KAAK,WAAW,CAAA;QAEjD,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YAC3C,OAAO,CAAC,GAAG,CAAC,MAAM,KAAK,kDAAkD,CAAC,CAAA;YAC1E,OAAO,IAAI,CAAC,CAAA;YAEZ,SAAQ;QACZ,CAAC;QAED,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAA;QACjD,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,MAAM,MAAM,EAAE,CAAC,CAAA;QACrC,SAAS,IAAI,CAAC,CAAA;IAClB,CAAC;IAED,IAAI,SAAS,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,EAAE,CAAC;QACnC,OAAO,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAA;QAE1C,OAAO,CAAC,CAAA;IACZ,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,KAAK,SAAS,wBAAwB,OAAO,kBAAkB,CAAC,CAAA;IAE5E,OAAO,CAAC,CAAA;AACZ,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Project } from '../project.js';
|
|
2
|
+
/**
|
|
3
|
+
* Walks the developer through every expired periodic check and records the ones
|
|
4
|
+
* they confirm. Mirrors the artisan limenet:laravel-baseline:periodic command.
|
|
5
|
+
*/
|
|
6
|
+
export declare function runPeriodic(project: Project): Promise<number>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline/promises';
|
|
2
|
+
import { CommentCollector } from '../checks/check.js';
|
|
3
|
+
import { isPeriodic } from '../checks/periodic-check.js';
|
|
4
|
+
import { createChecks } from '../checks/registry.js';
|
|
5
|
+
import { recordRun } from '../state.js';
|
|
6
|
+
/**
|
|
7
|
+
* Walks the developer through every expired periodic check and records the ones
|
|
8
|
+
* they confirm. Mirrors the artisan limenet:laravel-baseline:periodic command.
|
|
9
|
+
*/
|
|
10
|
+
export async function runPeriodic(project) {
|
|
11
|
+
const comments = new CommentCollector();
|
|
12
|
+
const expired = createChecks(project, comments)
|
|
13
|
+
.filter(isPeriodic)
|
|
14
|
+
.filter((check) => check.isApplicable())
|
|
15
|
+
.filter((check) => check.check() === 'fail');
|
|
16
|
+
if (expired.length === 0) {
|
|
17
|
+
console.log('All periodic checks are up to date!');
|
|
18
|
+
return 0;
|
|
19
|
+
}
|
|
20
|
+
const readline = createInterface({ input: process.stdin, output: process.stdout });
|
|
21
|
+
try {
|
|
22
|
+
for (const check of expired) {
|
|
23
|
+
console.log(`\n${check.name}`);
|
|
24
|
+
console.log(check.promptDescription());
|
|
25
|
+
const answer = await readline.question('\nHave you completed this task? [y/N] ');
|
|
26
|
+
if (answer.trim().toLowerCase().startsWith('y')) {
|
|
27
|
+
recordRun(project, check.name);
|
|
28
|
+
console.log('✅ Marked as done.');
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
console.log('⏭ Skipped.');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
readline.close();
|
|
37
|
+
}
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=periodic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"periodic.js","sourceRoot":"","sources":["../../src/commands/periodic.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAA;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAEpD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAEvC;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAAgB;IAC9C,MAAM,QAAQ,GAAG,IAAI,gBAAgB,EAAE,CAAA;IAEvC,MAAM,OAAO,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,CAAC;SAC1C,MAAM,CAAC,UAAU,CAAC;SAClB,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,YAAY,EAAE,CAAC;SACvC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,MAAM,CAAC,CAAA;IAEhD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,CAAC,GAAG,CAAC,qCAAqC,CAAC,CAAA;QAElD,OAAO,CAAC,CAAA;IACZ,CAAC;IAED,MAAM,QAAQ,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;IAElF,IAAI,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC1B,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,IAAI,EAAE,CAAC,CAAA;YAC9B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,iBAAiB,EAAE,CAAC,CAAA;YAEtC,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,QAAQ,CAAC,wCAAwC,CAAC,CAAA;YAEhF,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC9C,SAAS,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,CAAA;gBAC9B,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAA;YACpC,CAAC;iBAAM,CAAC;gBACJ,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;YAC7B,CAAC;QACL,CAAC;IACL,CAAC;YAAS,CAAC;QACP,QAAQ,CAAC,KAAK,EAAE,CAAA;IACpB,CAAC;IAED,OAAO,CAAC,CAAA;AACZ,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { Check, type CheckResult, CommentCollector, FixableCheck, icon, isError, isFixable, } from './checks/check.js';
|
|
2
|
+
export { isPeriodic, PeriodicCheck } from './checks/periodic-check.js';
|
|
3
|
+
export { allChecks, checkNames, createChecks, findCheck } from './checks/registry.js';
|
|
4
|
+
export { runCheck } from './commands/check.js';
|
|
5
|
+
export { runPeriodic } from './commands/periodic.js';
|
|
6
|
+
export { Policy, PolicyError, policy, setPolicy } from './policy.js';
|
|
7
|
+
export { Project } from './project.js';
|
|
8
|
+
export { type BaselineState, excludedChecks, lastRun, readState, recordRun, STATE_FILE, writeState, } from './state.js';
|
package/js/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { Check, CommentCollector, FixableCheck, icon, isError, isFixable, } from './checks/check.js';
|
|
2
|
+
export { isPeriodic, PeriodicCheck } from './checks/periodic-check.js';
|
|
3
|
+
export { allChecks, checkNames, createChecks, findCheck } from './checks/registry.js';
|
|
4
|
+
export { runCheck } from './commands/check.js';
|
|
5
|
+
export { runPeriodic } from './commands/periodic.js';
|
|
6
|
+
export { Policy, PolicyError, policy, setPolicy } from './policy.js';
|
|
7
|
+
export { Project } from './project.js';
|
|
8
|
+
export { excludedChecks, lastRun, readState, recordRun, STATE_FILE, writeState, } from './state.js';
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,KAAK,EAEL,gBAAgB,EAChB,YAAY,EACZ,IAAI,EACJ,OAAO,EACP,SAAS,GACZ,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AACtE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AACrF,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AACpD,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACpE,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,OAAO,EAEH,cAAc,EACd,OAAO,EACP,SAAS,EACT,SAAS,EACT,UAAU,EACV,UAAU,GACb,MAAM,YAAY,CAAA"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The policy values shared verbatim with the PHP runner (limenet/laravel-baseline).
|
|
3
|
+
* Both read policy/policy.json, so a version floor or a required key is bumped in
|
|
4
|
+
* exactly one place.
|
|
5
|
+
*
|
|
6
|
+
* Accessors narrow to concrete types and throw otherwise, mirroring
|
|
7
|
+
* src/Policy/Policy.php so the two runners stay legible side by side.
|
|
8
|
+
*/
|
|
9
|
+
export declare class PolicyError extends Error {
|
|
10
|
+
static wrongType(key: string, expected: string): PolicyError;
|
|
11
|
+
}
|
|
12
|
+
export declare class Policy {
|
|
13
|
+
private readonly data;
|
|
14
|
+
private readonly baseDirectory;
|
|
15
|
+
constructor(data: unknown, baseDirectory: string);
|
|
16
|
+
/**
|
|
17
|
+
* The shipped policy directory. `js/src` and `js/dist` sit at the same depth,
|
|
18
|
+
* so this resolves identically under vitest and in the published package.
|
|
19
|
+
*/
|
|
20
|
+
static defaultDirectory(): string;
|
|
21
|
+
static fromDirectory(directory?: string): Policy;
|
|
22
|
+
static fromObject(data: unknown, baseDirectory?: string): Policy;
|
|
23
|
+
number(key: string): number;
|
|
24
|
+
string(key: string): string;
|
|
25
|
+
boolean(key: string): boolean;
|
|
26
|
+
strings(key: string): string[];
|
|
27
|
+
stringMap(key: string): Record<string, string>;
|
|
28
|
+
/**
|
|
29
|
+
* A map whose values are either a single string or a list of them, always
|
|
30
|
+
* returned as lists — for keys where one accepted value and several are
|
|
31
|
+
* equally valid, such as the CI templates a job may extend.
|
|
32
|
+
*/
|
|
33
|
+
stringListMap(key: string): Record<string, string[]>;
|
|
34
|
+
/**
|
|
35
|
+
* The verbatim body of a canonical file under policy/templates/. Multi-line
|
|
36
|
+
* bodies live there rather than JSON-escaped into policy.json, so their diffs
|
|
37
|
+
* stay readable and their newlines survive both loaders unchanged.
|
|
38
|
+
*/
|
|
39
|
+
template(name: string): string;
|
|
40
|
+
private lookup;
|
|
41
|
+
}
|
|
42
|
+
/** The shipped policy, read once per process. */
|
|
43
|
+
export declare function policy(): Policy;
|
|
44
|
+
/** Test seam: swap the process-wide policy, or reset it by passing nothing. */
|
|
45
|
+
export declare function setPolicy(replacement?: Policy): void;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
/**
|
|
5
|
+
* The policy values shared verbatim with the PHP runner (limenet/laravel-baseline).
|
|
6
|
+
* Both read policy/policy.json, so a version floor or a required key is bumped in
|
|
7
|
+
* exactly one place.
|
|
8
|
+
*
|
|
9
|
+
* Accessors narrow to concrete types and throw otherwise, mirroring
|
|
10
|
+
* src/Policy/Policy.php so the two runners stay legible side by side.
|
|
11
|
+
*/
|
|
12
|
+
export class PolicyError extends Error {
|
|
13
|
+
static wrongType(key, expected) {
|
|
14
|
+
return new PolicyError(`Policy key "${key}" is not a ${expected}`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export class Policy {
|
|
18
|
+
data;
|
|
19
|
+
baseDirectory;
|
|
20
|
+
constructor(data, baseDirectory) {
|
|
21
|
+
this.data = data;
|
|
22
|
+
this.baseDirectory = baseDirectory;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The shipped policy directory. `js/src` and `js/dist` sit at the same depth,
|
|
26
|
+
* so this resolves identically under vitest and in the published package.
|
|
27
|
+
*/
|
|
28
|
+
static defaultDirectory() {
|
|
29
|
+
return join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'policy');
|
|
30
|
+
}
|
|
31
|
+
static fromDirectory(directory = Policy.defaultDirectory()) {
|
|
32
|
+
const file = join(directory, 'policy.json');
|
|
33
|
+
if (!existsSync(file)) {
|
|
34
|
+
throw new PolicyError(`Policy file "${file}" is missing or unreadable. It ships with the package — check the "files" whitelist in package.json.`);
|
|
35
|
+
}
|
|
36
|
+
return new Policy(JSON.parse(readFileSync(file, 'utf8')), directory);
|
|
37
|
+
}
|
|
38
|
+
static fromObject(data, baseDirectory = '') {
|
|
39
|
+
return new Policy(data, baseDirectory);
|
|
40
|
+
}
|
|
41
|
+
number(key) {
|
|
42
|
+
const value = this.lookup(key);
|
|
43
|
+
if (typeof value !== 'number' || !Number.isInteger(value)) {
|
|
44
|
+
throw PolicyError.wrongType(key, 'integer');
|
|
45
|
+
}
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
string(key) {
|
|
49
|
+
const value = this.lookup(key);
|
|
50
|
+
if (typeof value !== 'string') {
|
|
51
|
+
throw PolicyError.wrongType(key, 'string');
|
|
52
|
+
}
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
boolean(key) {
|
|
56
|
+
const value = this.lookup(key);
|
|
57
|
+
if (typeof value !== 'boolean') {
|
|
58
|
+
throw PolicyError.wrongType(key, 'boolean');
|
|
59
|
+
}
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
strings(key) {
|
|
63
|
+
const value = this.lookup(key);
|
|
64
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== 'string')) {
|
|
65
|
+
throw PolicyError.wrongType(key, 'list of strings');
|
|
66
|
+
}
|
|
67
|
+
// A copy, as Policy::strings() returns one: the policy is cached for the
|
|
68
|
+
// whole process, so handing out the live array would let a caller that
|
|
69
|
+
// appends to the result (DeniesEnvReadsInClaudeSettingsCheck does)
|
|
70
|
+
// permanently extend the policy for every later check.
|
|
71
|
+
return [...value];
|
|
72
|
+
}
|
|
73
|
+
stringMap(key) {
|
|
74
|
+
const value = this.lookup(key);
|
|
75
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
76
|
+
throw PolicyError.wrongType(key, 'map of strings');
|
|
77
|
+
}
|
|
78
|
+
for (const item of Object.values(value)) {
|
|
79
|
+
if (typeof item !== 'string') {
|
|
80
|
+
throw PolicyError.wrongType(key, 'map of strings');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return { ...value };
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* A map whose values are either a single string or a list of them, always
|
|
87
|
+
* returned as lists — for keys where one accepted value and several are
|
|
88
|
+
* equally valid, such as the CI templates a job may extend.
|
|
89
|
+
*/
|
|
90
|
+
stringListMap(key) {
|
|
91
|
+
const value = this.lookup(key);
|
|
92
|
+
const expected = 'map of strings or lists of strings';
|
|
93
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
94
|
+
throw PolicyError.wrongType(key, expected);
|
|
95
|
+
}
|
|
96
|
+
const map = {};
|
|
97
|
+
for (const [mapKey, item] of Object.entries(value)) {
|
|
98
|
+
if (typeof item === 'string') {
|
|
99
|
+
map[mapKey] = [item];
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (!Array.isArray(item) ||
|
|
103
|
+
item.length === 0 ||
|
|
104
|
+
item.some((entry) => typeof entry !== 'string')) {
|
|
105
|
+
throw PolicyError.wrongType(key, expected);
|
|
106
|
+
}
|
|
107
|
+
// Copied for the same reason strings() copies — see above.
|
|
108
|
+
map[mapKey] = [...item];
|
|
109
|
+
}
|
|
110
|
+
return map;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* The verbatim body of a canonical file under policy/templates/. Multi-line
|
|
114
|
+
* bodies live there rather than JSON-escaped into policy.json, so their diffs
|
|
115
|
+
* stay readable and their newlines survive both loaders unchanged.
|
|
116
|
+
*/
|
|
117
|
+
template(name) {
|
|
118
|
+
const file = join(this.baseDirectory, 'templates', name);
|
|
119
|
+
if (!existsSync(file)) {
|
|
120
|
+
throw new PolicyError(`Policy template "${file}" is missing or unreadable.`);
|
|
121
|
+
}
|
|
122
|
+
return readFileSync(file, 'utf8');
|
|
123
|
+
}
|
|
124
|
+
lookup(key) {
|
|
125
|
+
let value = this.data;
|
|
126
|
+
for (const segment of key.split('.')) {
|
|
127
|
+
if (typeof value !== 'object' || value === null || !(segment in value)) {
|
|
128
|
+
throw new PolicyError(`Policy key "${key}" is not defined in policy/policy.json`);
|
|
129
|
+
}
|
|
130
|
+
value = value[segment];
|
|
131
|
+
}
|
|
132
|
+
return value;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
let cached;
|
|
136
|
+
/** The shipped policy, read once per process. */
|
|
137
|
+
export function policy() {
|
|
138
|
+
cached ??= Policy.fromDirectory();
|
|
139
|
+
return cached;
|
|
140
|
+
}
|
|
141
|
+
/** Test seam: swap the process-wide policy, or reset it by passing nothing. */
|
|
142
|
+
export function setPolicy(replacement) {
|
|
143
|
+
cached = replacement;
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy.js","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The project under inspection. Every file access a check makes goes through
|
|
3
|
+
* here, so checks never touch absolute paths and the whole runner can be pointed
|
|
4
|
+
* at a temporary directory in tests.
|
|
5
|
+
*
|
|
6
|
+
* The PHP runner's equivalent is base_path() plus the file helpers on
|
|
7
|
+
* AbstractCheck.
|
|
8
|
+
*/
|
|
9
|
+
export declare class Project {
|
|
10
|
+
readonly basePath: string;
|
|
11
|
+
constructor(basePath: string);
|
|
12
|
+
static at(basePath?: string): Project;
|
|
13
|
+
path(relative: string): string;
|
|
14
|
+
exists(relative: string): boolean;
|
|
15
|
+
isDirectory(relative: string): boolean;
|
|
16
|
+
read(relative: string): string | null;
|
|
17
|
+
readJson<T = Record<string, unknown>>(relative: string): T | null;
|
|
18
|
+
write(relative: string, contents: string): void;
|
|
19
|
+
/**
|
|
20
|
+
* Four-space indent plus a trailing newline, matching the PHP runner's
|
|
21
|
+
* writePackageJson() so the two never fight over formatting in a project
|
|
22
|
+
* that runs both.
|
|
23
|
+
*/
|
|
24
|
+
writeJson(relative: string, data: unknown): void;
|
|
25
|
+
remove(relative: string): void;
|
|
26
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join, resolve } from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* The project under inspection. Every file access a check makes goes through
|
|
5
|
+
* here, so checks never touch absolute paths and the whole runner can be pointed
|
|
6
|
+
* at a temporary directory in tests.
|
|
7
|
+
*
|
|
8
|
+
* The PHP runner's equivalent is base_path() plus the file helpers on
|
|
9
|
+
* AbstractCheck.
|
|
10
|
+
*/
|
|
11
|
+
export class Project {
|
|
12
|
+
basePath;
|
|
13
|
+
constructor(basePath) {
|
|
14
|
+
this.basePath = basePath;
|
|
15
|
+
}
|
|
16
|
+
static at(basePath = process.cwd()) {
|
|
17
|
+
return new Project(resolve(basePath));
|
|
18
|
+
}
|
|
19
|
+
path(relative) {
|
|
20
|
+
return join(this.basePath, relative);
|
|
21
|
+
}
|
|
22
|
+
exists(relative) {
|
|
23
|
+
return existsSync(this.path(relative));
|
|
24
|
+
}
|
|
25
|
+
isDirectory(relative) {
|
|
26
|
+
const path = this.path(relative);
|
|
27
|
+
return existsSync(path) && statSync(path).isDirectory();
|
|
28
|
+
}
|
|
29
|
+
read(relative) {
|
|
30
|
+
return this.exists(relative) ? readFileSync(this.path(relative), 'utf8') : null;
|
|
31
|
+
}
|
|
32
|
+
readJson(relative) {
|
|
33
|
+
const contents = this.read(relative);
|
|
34
|
+
if (contents === null) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
// Throws rather than yielding null, as the PHP runner's JSON_THROW_ON_ERROR
|
|
38
|
+
// does: null is how callers spell "the file isn't there", so swallowing a
|
|
39
|
+
// syntax error would let --fix overwrite a file it never managed to read.
|
|
40
|
+
try {
|
|
41
|
+
return JSON.parse(contents);
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
throw new Error(`${relative} is not valid JSON: ${error instanceof Error ? error.message : error}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
write(relative, contents) {
|
|
48
|
+
const path = this.path(relative);
|
|
49
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
50
|
+
writeFileSync(path, contents);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Four-space indent plus a trailing newline, matching the PHP runner's
|
|
54
|
+
* writePackageJson() so the two never fight over formatting in a project
|
|
55
|
+
* that runs both.
|
|
56
|
+
*/
|
|
57
|
+
writeJson(relative, data) {
|
|
58
|
+
this.write(relative, `${JSON.stringify(data, null, 4)}\n`);
|
|
59
|
+
}
|
|
60
|
+
remove(relative) {
|
|
61
|
+
rmSync(this.path(relative), { recursive: true, force: true });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=project.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project.js","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAC9F,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAElD;;;;;;;GAOG;AACH,MAAM,OAAO,OAAO;IACK,QAAQ;IAA7B,YAAqB,QAAgB;wBAAhB,QAAQ;IAAW,CAAC;IAEzC,MAAM,CAAC,EAAE,CAAC,QAAQ,GAAW,OAAO,CAAC,GAAG,EAAE;QACtC,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAA;IACzC,CAAC;IAED,IAAI,CAAC,QAAgB;QACjB,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAA;IACxC,CAAC;IAED,MAAM,CAAC,QAAgB;QACnB,OAAO,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAA;IAC1C,CAAC;IAED,WAAW,CAAC,QAAgB;QACxB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QAEhC,OAAO,UAAU,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAA;IAC3D,CAAC;IAED,IAAI,CAAC,QAAgB;QACjB,OAAO,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IACnF,CAAC;IAED,QAAQ,CAA8B,QAAgB;QAClD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QAEpC,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;YACpB,OAAO,IAAI,CAAA;QACf,CAAC;QAED,4EAA4E;QAC5E,0EAA0E;QAC1E,0EAA0E;QAC1E,IAAI,CAAC;YACD,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAM,CAAA;QACpC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CACX,GAAG,QAAQ,uBAAuB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,EAAE,CACrF,CAAA;QACL,CAAC;IACL,CAAC;IAED,KAAK,CAAC,QAAgB,EAAE,QAAgB;QACpC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QAEhC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;QAC7C,aAAa,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;IACjC,CAAC;IAED;;;;OAIG;IACH,SAAS,CAAC,QAAgB,EAAE,IAAa;QACrC,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAA;IAC9D,CAAC;IAED,MAAM,CAAC,QAAgB;QACnB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;IACjE,CAAC;CACJ"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Project } from './project.js';
|
|
2
|
+
/**
|
|
3
|
+
* Per-project runner state, in `.baseline.json` at the project root.
|
|
4
|
+
*
|
|
5
|
+
* The PHP runner keeps the same two things in `config/baseline.php` — a JS
|
|
6
|
+
* project has no `config/` directory to write a PHP file into, so the shape is
|
|
7
|
+
* mirrored in JSON instead:
|
|
8
|
+
*
|
|
9
|
+
* { "excludes": ["hasEditorconfig"], "periodic": { "updatesDependencies": "…" } }
|
|
10
|
+
*/
|
|
11
|
+
export declare const STATE_FILE = ".baseline.json";
|
|
12
|
+
export interface BaselineState {
|
|
13
|
+
excludes?: string[];
|
|
14
|
+
periodic?: Record<string, string>;
|
|
15
|
+
}
|
|
16
|
+
export declare function readState(project: Project): BaselineState;
|
|
17
|
+
export declare function excludedChecks(project: Project): string[];
|
|
18
|
+
export declare function writeState(project: Project, state: BaselineState): void;
|
|
19
|
+
/** When the developer last confirmed a periodic check, or null if never. */
|
|
20
|
+
export declare function lastRun(project: Project, checkName: string): Date | null;
|
|
21
|
+
export declare function recordRun(project: Project, checkName: string, when?: Date): void;
|
package/js/dist/state.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-project runner state, in `.baseline.json` at the project root.
|
|
3
|
+
*
|
|
4
|
+
* The PHP runner keeps the same two things in `config/baseline.php` — a JS
|
|
5
|
+
* project has no `config/` directory to write a PHP file into, so the shape is
|
|
6
|
+
* mirrored in JSON instead:
|
|
7
|
+
*
|
|
8
|
+
* { "excludes": ["hasEditorconfig"], "periodic": { "updatesDependencies": "…" } }
|
|
9
|
+
*/
|
|
10
|
+
export const STATE_FILE = '.baseline.json';
|
|
11
|
+
export function readState(project) {
|
|
12
|
+
return project.readJson(STATE_FILE) ?? {};
|
|
13
|
+
}
|
|
14
|
+
export function excludedChecks(project) {
|
|
15
|
+
return readState(project).excludes ?? [];
|
|
16
|
+
}
|
|
17
|
+
export function writeState(project, state) {
|
|
18
|
+
project.writeJson(STATE_FILE, state);
|
|
19
|
+
}
|
|
20
|
+
/** When the developer last confirmed a periodic check, or null if never. */
|
|
21
|
+
export function lastRun(project, checkName) {
|
|
22
|
+
const recorded = readState(project).periodic?.[checkName];
|
|
23
|
+
if (recorded === undefined) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
const parsed = new Date(recorded);
|
|
27
|
+
return Number.isNaN(parsed.getTime()) ? null : parsed;
|
|
28
|
+
}
|
|
29
|
+
export function recordRun(project, checkName, when = new Date()) {
|
|
30
|
+
const state = readState(project);
|
|
31
|
+
writeState(project, {
|
|
32
|
+
...state,
|
|
33
|
+
periodic: { ...state.periodic, [checkName]: when.toISOString() },
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.js","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,gBAAgB,CAAA;AAO1C,MAAM,UAAU,SAAS,CAAC,OAAgB;IACtC,OAAO,OAAO,CAAC,QAAQ,CAAgB,UAAU,CAAC,IAAI,EAAE,CAAA;AAC5D,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,OAAgB;IAC3C,OAAO,SAAS,CAAC,OAAO,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAA;AAC5C,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,OAAgB,EAAE,KAAoB;IAC7D,OAAO,CAAC,SAAS,CAAC,UAAU,EAAE,KAAK,CAAC,CAAA;AACxC,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,OAAO,CAAC,OAAgB,EAAE,SAAiB;IACvD,MAAM,QAAQ,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC,SAAS,CAAC,CAAA;IAEzD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QACzB,OAAO,IAAI,CAAA;IACf,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,QAAQ,CAAC,CAAA;IAEjC,OAAO,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAA;AACzD,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,OAAgB,EAAE,SAAiB,EAAE,IAAI,GAAS,IAAI,IAAI,EAAE;IAClF,MAAM,KAAK,GAAG,SAAS,CAAC,OAAO,CAAC,CAAA;IAEhC,UAAU,CAAC,OAAO,EAAE;QAChB,GAAG,KAAK;QACR,QAAQ,EAAE,EAAE,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC,SAAS,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,EAAE;KACnE,CAAC,CAAA;AACN,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CheckResult } from '../checks/check.js';
|
|
2
|
+
import type { Project } from '../project.js';
|
|
3
|
+
export declare const CI_FILE = ".gitlab-ci.yml";
|
|
4
|
+
/**
|
|
5
|
+
* The TS counterpart of AbstractCiJobCheck. A function rather than a base class:
|
|
6
|
+
* hasCiJobs is a plain Check and hasTrivyConfig a FixableCheck, and TS has no
|
|
7
|
+
* way to sit one shared base under both.
|
|
8
|
+
*/
|
|
9
|
+
export declare function checkRequiredCiJobs(project: Project, required: Record<string, string[]>, comment: (message: string) => void): CheckResult;
|
|
10
|
+
/** The parsed pipeline, or null with the reason already commented. */
|
|
11
|
+
export declare function readCiJobs(project: Project, comment: (message: string) => void): Record<string, {
|
|
12
|
+
extends?: unknown;
|
|
13
|
+
} | undefined> | null;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { parseDocument } from 'yaml';
|
|
2
|
+
export const CI_FILE = '.gitlab-ci.yml';
|
|
3
|
+
/**
|
|
4
|
+
* The TS counterpart of AbstractCiJobCheck. A function rather than a base class:
|
|
5
|
+
* hasCiJobs is a plain Check and hasTrivyConfig a FixableCheck, and TS has no
|
|
6
|
+
* way to sit one shared base under both.
|
|
7
|
+
*/
|
|
8
|
+
export function checkRequiredCiJobs(project, required, comment) {
|
|
9
|
+
const jobs = readCiJobs(project, comment);
|
|
10
|
+
if (jobs === null) {
|
|
11
|
+
return 'fail';
|
|
12
|
+
}
|
|
13
|
+
for (const [job, accepted] of Object.entries(required)) {
|
|
14
|
+
const declared = jobs[job]?.extends;
|
|
15
|
+
if (!Array.isArray(declared) || declared.length !== 1 || !accepted.includes(declared[0])) {
|
|
16
|
+
const rendered = accepted.map((template) => `[${template}]`).join(' or ');
|
|
17
|
+
comment(`Missing or misconfigured CI job in ${CI_FILE}: Add job '${job}' with 'extends: ${rendered}'`);
|
|
18
|
+
return 'fail';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return 'pass';
|
|
22
|
+
}
|
|
23
|
+
/** The parsed pipeline, or null with the reason already commented. */
|
|
24
|
+
export function readCiJobs(project, comment) {
|
|
25
|
+
const contents = project.read(CI_FILE);
|
|
26
|
+
if (contents === null) {
|
|
27
|
+
comment(`${CI_FILE} not found`);
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
const document = parseDocument(contents);
|
|
31
|
+
if (document.errors.length > 0) {
|
|
32
|
+
comment(`${CI_FILE} could not be parsed: ${document.errors[0]?.message ?? 'invalid YAML'}`);
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const config = document.toJS();
|
|
36
|
+
if (typeof config !== 'object' || config === null) {
|
|
37
|
+
comment(`${CI_FILE} is empty or invalid`);
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
return config;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=ci-jobs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ci-jobs.js","sourceRoot":"","sources":["../../src/support/ci-jobs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,MAAM,CAAA;AAIpC,MAAM,CAAC,MAAM,OAAO,GAAG,gBAAgB,CAAA;AAEvC;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAC/B,OAAgB,EAChB,QAAkC,EAClC,OAAkC;IAElC,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;IAEzC,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAChB,OAAO,MAAM,CAAA;IACjB,CAAC;IAED,KAAK,MAAM,CAAC,GAAG,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QACrD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,OAAO,CAAA;QAEnC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAW,CAAC,EAAE,CAAC;YACjG,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAI,QAAQ,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;YACzE,OAAO,CACH,sCAAsC,OAAO,cAAc,GAAG,oBAAoB,QAAQ,GAAG,CAChG,CAAA;YAED,OAAO,MAAM,CAAA;QACjB,CAAC;IACL,CAAC;IAED,OAAO,MAAM,CAAA;AACjB,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,UAAU,CACtB,OAAgB,EAChB,OAAkC;IAElC,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IAEtC,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QACpB,OAAO,CAAC,GAAG,OAAO,YAAY,CAAC,CAAA;QAE/B,OAAO,IAAI,CAAA;IACf,CAAC;IAED,MAAM,QAAQ,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAA;IAExC,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7B,OAAO,CAAC,GAAG,OAAO,yBAAyB,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO,IAAI,cAAc,EAAE,CAAC,CAAA;QAE3F,OAAO,IAAI,CAAA;IACf,CAAC;IAED,MAAM,MAAM,GAAY,QAAQ,CAAC,IAAI,EAAE,CAAA;IAEvC,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QAChD,OAAO,CAAC,GAAG,OAAO,sBAAsB,CAAC,CAAA;QAEzC,OAAO,IAAI,CAAA;IACf,CAAC;IAED,OAAO,MAA2D,CAAA;AACtE,CAAC"}
|