@roomi-fields/grillhouse 0.0.0-stage → 0.23.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/.claude-plugin/plugin.json +9 -0
- package/METACADRE.md +41 -0
- package/agents/developpeur.md +10 -0
- package/agents/explorateur.md +9 -0
- package/agents/questionneur.md +11 -0
- package/agents/referent-technique.md +9 -0
- package/agents/regulateur.md +11 -0
- package/agents/relecteur.md +9 -0
- package/agents/testwriter.md +10 -0
- package/bin/grillhouse.mjs +125 -0
- package/cadre/templates/projet/.claude/settings.json +30 -0
- package/cadre/templates/projet/.editorconfig +47 -0
- package/cadre/templates/projet/.env.example +57 -0
- package/cadre/templates/projet/.eslintrc.cjs +56 -0
- package/cadre/templates/projet/.github/workflows/ci.yml +66 -0
- package/cadre/templates/projet/.mcp.json +13 -0
- package/cadre/templates/projet/.prettierrc +11 -0
- package/cadre/templates/projet/CHANGELOG.md +61 -0
- package/cadre/templates/projet/CLAUDE.md +46 -0
- package/cadre/templates/projet/CONTRIBUTING.md +243 -0
- package/cadre/templates/projet/CREDITS.md +41 -0
- package/cadre/templates/projet/README.md +152 -0
- package/cadre/templates/projet/_gitignore +91 -0
- package/cadre/templates/projet/_npmignore +46 -0
- package/cadre/templates/projet/deployment/README.md +98 -0
- package/cadre/templates/projet/deployment/docs/01-INSTALL.md +145 -0
- package/cadre/templates/projet/deployment/docs/02-CONFIGURATION.md +117 -0
- package/cadre/templates/projet/deployment/docs/03-API.md +264 -0
- package/cadre/templates/projet/deployment/docs/04-TROUBLESHOOTING.md +262 -0
- package/cadre/templates/projet/deployment/scripts/install.ps1 +100 -0
- package/cadre/templates/projet/deployment/scripts/start.ps1 +61 -0
- package/cadre/templates/projet/deployment/scripts/stop.ps1 +37 -0
- package/cadre/templates/projet/deployment/scripts/test.ps1 +118 -0
- package/cadre/templates/projet/docs/agents/issue-tracker.md +44 -0
- package/cadre/templates/projet/ecosystem.config.cjs +73 -0
- package/cadre/templates/projet/package.json +90 -0
- package/cadre/templates/projet/pitmaster/SUIVI.md +22 -0
- package/cadre/templates/projet/src/config.ts +68 -0
- package/cadre/templates/projet/src/errors.ts +102 -0
- package/cadre/templates/projet/src/index.ts +46 -0
- package/cadre/templates/projet/src/services/.gitkeep +8 -0
- package/cadre/templates/projet/src/types/index.ts +24 -0
- package/cadre/templates/projet/src/utils/logger.ts +106 -0
- package/cadre/templates/projet/tests/integration/.gitkeep +7 -0
- package/cadre/templates/projet/tests/unit/config.spec.ts +68 -0
- package/cadre/templates/projet/tests/unit/errors.spec.ts +132 -0
- package/cadre/templates/projet/tests/unit/types.spec.ts +73 -0
- package/cadre/templates/projet/tsconfig.eslint.json +9 -0
- package/cadre/templates/projet/tsconfig.json +41 -0
- package/cadre/templates/projet/tsdoc.json +6 -0
- package/cadre/templates/projet/vitest.config.ts +44 -0
- package/cadre/templates/turbo/paquets.json +29 -0
- package/cadre/templates/turbo/sans-paquets.json +28 -0
- package/charte.md +107 -0
- package/docs/ARCHITECTURE.md +90 -0
- package/hooks/crochet.mjs +33 -0
- package/hooks/hooks.json +58 -0
- package/hooks/register.tsx +558 -0
- package/package.json +75 -4
- package/scripts/avancement.mjs +244 -0
- package/scripts/bilan.mjs +523 -0
- package/scripts/branche-de-nuit.mjs +59 -0
- package/scripts/cadre.mjs +109 -0
- package/scripts/circuit.mjs +4375 -0
- package/scripts/code-mort.mjs +371 -0
- package/scripts/composants-affectes.mjs +598 -0
- package/scripts/consommateurs-arbitres.mjs +192 -0
- package/scripts/consommateurs.mjs +310 -0
- package/scripts/crochet.mjs +24 -0
- package/scripts/crochets.mjs +135 -0
- package/scripts/dernier-vert.mjs +208 -0
- package/scripts/dette.mjs +181 -0
- package/scripts/droits.mjs +354 -0
- package/scripts/enveloppe/codes.mjs +17 -0
- package/scripts/enveloppe/composant.mjs +11 -0
- package/scripts/enveloppe/enveloppe.mjs +628 -0
- package/scripts/enveloppe/lancer.sh +311 -0
- package/scripts/etapes.mjs +323 -0
- package/scripts/etat-du-circuit.mjs +104 -0
- package/scripts/frontieres.mjs +456 -0
- package/scripts/gardes-du-cadre.mjs +72 -0
- package/scripts/greffons.mjs +338 -0
- package/scripts/grillhouse-maj.mjs +514 -0
- package/scripts/index-interfaces.mjs +293 -0
- package/scripts/init.mjs +117 -0
- package/scripts/integration/file-attente.mjs +61 -0
- package/scripts/integration/file.mjs +494 -0
- package/scripts/integration/integrer.mjs +381 -0
- package/scripts/integration/rejeu.mjs +139 -0
- package/scripts/integration/verdict.mjs +13 -0
- package/scripts/interfaces-contre-code.mjs +547 -0
- package/scripts/ligne-etat/defaut.sh +110 -0
- package/scripts/ligne-etat/ligne-etat.mjs +93 -0
- package/scripts/migrations/0.23.0-charte-importee.mjs +47 -0
- package/scripts/migrations/0.23.0-config-vitest.mjs +105 -0
- package/scripts/migrations/0.23.0-crochets-de-seance.mjs +55 -0
- package/scripts/migrations/0.23.0-marche-du-projet.mjs +82 -0
- package/scripts/migrations.mjs +43 -0
- package/scripts/nuit.sh +269 -0
- package/scripts/paquets.mjs +79 -0
- package/scripts/poussee-cles.mjs +46 -0
- package/scripts/preparation.mjs +84 -0
- package/scripts/references-par-les-types.mjs +420 -0
- package/scripts/registre.mjs +336 -0
- package/scripts/resultats.mjs +580 -0
- package/scripts/resumes.mjs +57 -0
- package/scripts/retirer-copie.sh +127 -0
- package/scripts/session-start.sh +203 -0
- package/scripts/sortie.mjs +155 -0
- package/scripts/sous-modules.sh +316 -0
- package/scripts/structure-guard.sh +27 -0
- package/scripts/superviseur.mjs +102 -0
- package/scripts/tableau.mjs +1886 -0
- package/scripts/tests.mjs +236 -0
- package/scripts/ticket.sh +23 -0
- package/scripts/turbo-des-paquets.mjs +899 -0
- package/scripts/un-commit-par-ticket.mjs +212 -0
- package/scripts/verifier.mjs +469 -0
- package/scripts/verrou-de-fichier.mjs +130 -0
- package/scripts/verrou-de-machine.mjs +211 -0
- package/scripts/verrous/commande.mjs +180 -0
- package/scripts/verrous/ticket-du-sous-agent.mjs +39 -0
- package/scripts/verrous/verrou.mjs +165 -0
- package/scripts/vitest-config/avancement.mjs +39 -0
- package/scripts/vitest-config/avancement.reporter.mjs +56 -0
- package/scripts/vitest-config/base-de-passe.mjs +128 -0
- package/scripts/vitest-config/base.d.ts +6 -0
- package/scripts/vitest-config/base.mjs +87 -0
- package/scripts/vitest-config/changements.mjs +173 -0
- package/scripts/vitest-config/cibles.mjs +207 -0
- package/scripts/vitest-config/duree.reporter.mjs +98 -0
- package/scripts/vitest-config/machine.setup.mjs +14 -0
- package/scripts/vitest-config/mesure.d.ts +6 -0
- package/scripts/vitest-config/mesure.mjs +13 -0
- package/scripts/vitest-config/mesure.setup.mjs +12 -0
- package/scripts/vitest-config/pression.mjs +90 -0
- package/scripts/vitest-config/resultats.mjs +23 -0
- package/scripts/vitest-config/resultats.setup.mjs +19 -0
- package/scripts/vitest.nuit.config.mjs +26 -0
- package/skills/developpeur/SKILL.md +175 -0
- package/skills/explorateur/SKILL.md +84 -0
- package/skills/grill/SKILL.md +238 -0
- package/skills/grill/references/releve-structurel.md +38 -0
- package/skills/mesure/SKILL.md +168 -0
- package/skills/pitmaster/SKILL.md +382 -0
- package/skills/pitmaster/references/cadre.md +119 -0
- package/skills/pitmaster/references/consigne-agent.md +308 -0
- package/skills/pitmaster/references/erreurs.md +25 -0
- package/skills/pitmaster/references/tour.md +124 -0
- package/skills/questionneur/SKILL.md +64 -0
- package/skills/redacteur/SKILL.md +84 -0
- package/skills/redacteur/references/documents-d-un-composant.md +105 -0
- package/skills/referent-technique/SKILL.md +145 -0
- package/skills/regulateur/SKILL.md +220 -0
- package/skills/release/SKILL.md +134 -0
- package/skills/release/scripts/update-version.cjs +92 -0
- package/skills/relecteur/SKILL.md +152 -0
- package/skills/relecteur/references/LICENSE-MIT +30 -0
- package/skills/relecteur/references/SOURCE.md +8 -0
- package/skills/relecteur/references/claims-check.md +14 -0
- package/skills/relecteur/references/deletion-check.md +14 -0
- package/skills/relecteur/references/edge-case-hunter.md +80 -0
- package/skills/relecteur/references/verification-gap.md +113 -0
- package/skills/testwriter/SKILL.md +187 -0
- package/types/index.d.ts +56 -0
- package/README.md +0 -3
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// The dead-code guard: refuses a file, an export, a type or a dependency that nothing living
|
|
3
|
+
// uses, measured by knip (configured in the project's `knip.json`). Run by `grillhouse:gardes`.
|
|
4
|
+
//
|
|
5
|
+
// - Each finding is named with its file; exit code 1 when there is one. A finding inscribed in the
|
|
6
|
+
// project's debt (`grillhouse-dette.json`, see `dette.mjs`) passes, keyed by its file and knip's
|
|
7
|
+
// kind of issue (`files`, `exports`, `types`, `dependencies`…).
|
|
8
|
+
// - A knip that measured nothing (it failed, or its output is not its report) refuses too: an
|
|
9
|
+
// empty green is the worst green.
|
|
10
|
+
// - A finding that is not dead (a format's full table, a public name a consumer outside the
|
|
11
|
+
// repository reads) leaves by its name in `knip.json`, with its reason, never by a wide pattern.
|
|
12
|
+
// - The packages listed out of the frame (`docs/agents/hors-cadre.txt`, read by `horsCadre` of
|
|
13
|
+
// `paquets.mjs`) are skipped: knip neither reads nor reports them, so a use they make
|
|
14
|
+
// keeps nothing alive. knip then runs on the project's `knip.json` (knip's defaults without
|
|
15
|
+
// one) plus these exclusions and the frame's entries, written to a temporary file outside the
|
|
16
|
+
// repository; the project's `knip.json` is never written.
|
|
17
|
+
// - The tests and benches of a component count for the use of its dependencies only. knip runs a
|
|
18
|
+
// first time on the settings above and gives every finding. It runs a second time, restricted
|
|
19
|
+
// to the dependency findings, with the test and bench files of the repository as its only
|
|
20
|
+
// entries, each place receiving its own files: the root (its `.` key, which knip reads as soon
|
|
21
|
+
// as there are `workspaces`) those that no package holds, each package of the repository those
|
|
22
|
+
// under its directory, the deepest package holding a file owning it. The packages are the
|
|
23
|
+
// workspaces knip makes, with its own glob matcher (`picomatch`, loaded from the repository's
|
|
24
|
+
// knip): the directories holding a `package.json` that the `workspaces` of the root
|
|
25
|
+
// `package.json` select or a key of the `knip.json` names, exact or by pattern. Each place
|
|
26
|
+
// receives the settings knip gives it (`getWorkspaceConfig`): those of the key knip chooses
|
|
27
|
+
// (`getConfigKeyForWorkspace`: the keys sorted by knip's `byPathDepth`, the last one that
|
|
28
|
+
// matches), the whole `knip.json` for the root without `workspaces`, empty settings when no key
|
|
29
|
+
// matches; kept to those knip's schema admits in a workspace key, the top-level-only ones
|
|
30
|
+
// (`$schema`, `rules`…) staying at the top level. So a pattern key (`packages/*`) holds for
|
|
31
|
+
// each package it names and a package no key names is covered too. A dependency is unused when
|
|
32
|
+
// both runs report it, so the entries knip takes by itself stay in the first run. The files,
|
|
33
|
+
// exports and types findings are those of the first run.
|
|
34
|
+
// - The test and bench files are those git does not ignore (`.gitignore`, as knip reads it): a
|
|
35
|
+
// ticket copy or a build that git ignores counts for nothing. A test file is known by the
|
|
36
|
+
// repository's rule (`isTestFile`, `verrous/verrou.mjs`), plus the benches `*.budget.*` and
|
|
37
|
+
// `*.nuit.*`. A file the top-level or workspace `ignore` targets is not an entry: these patterns
|
|
38
|
+
// become negated entry patterns, which knip matches as its own.
|
|
39
|
+
import { spawnSync } from 'node:child_process';
|
|
40
|
+
import { createRequire } from 'node:module';
|
|
41
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
42
|
+
import { tmpdir } from 'node:os';
|
|
43
|
+
import path from 'node:path';
|
|
44
|
+
import { pathToFileURL } from 'node:url';
|
|
45
|
+
import { horsCadre } from './paquets.mjs';
|
|
46
|
+
import { trancher } from './dette.mjs';
|
|
47
|
+
import { isTestFile } from './verrous/verrou.mjs';
|
|
48
|
+
|
|
49
|
+
const GENRES = {
|
|
50
|
+
files: 'fichier sans appelant',
|
|
51
|
+
exports: 'export sans appelant',
|
|
52
|
+
types: 'type exporté sans appelant',
|
|
53
|
+
enumMembers: "membre d'énumération sans appelant",
|
|
54
|
+
dependencies: 'dépendance sans usage',
|
|
55
|
+
devDependencies: 'dépendance de développement sans usage',
|
|
56
|
+
unlisted: 'dépendance non déclarée',
|
|
57
|
+
unresolved: 'import introuvable',
|
|
58
|
+
binaries: 'commande non déclarée',
|
|
59
|
+
duplicates: 'export en double',
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// The findings of knip's JSON report, of the given kinds (all by default), each
|
|
63
|
+
// `{ fichier, regle, message }`: its file, knip's kind of issue (`exports`, `dependencies`…) and
|
|
64
|
+
// its line; null when the report is not knip's.
|
|
65
|
+
export function constats(texte, genres = Object.keys(GENRES)) {
|
|
66
|
+
let r;
|
|
67
|
+
try {
|
|
68
|
+
r = JSON.parse(texte);
|
|
69
|
+
} catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
if (!Array.isArray(r?.issues)) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const out = [];
|
|
76
|
+
for (const i of r.issues) {
|
|
77
|
+
for (const genre of genres) {
|
|
78
|
+
const nom = GENRES[genre];
|
|
79
|
+
for (const x of i[genre] ?? []) {
|
|
80
|
+
const n = Array.isArray(x) ? x.map(y => y.name).join(', ') : x.name;
|
|
81
|
+
out.push({
|
|
82
|
+
fichier: i.file,
|
|
83
|
+
...(Array.isArray(x) || !x.line ? {} : { ligne: x.line }),
|
|
84
|
+
regle: genre,
|
|
85
|
+
message: genre === 'files' ? `${i.file} : ${nom}` : `${i.file} : ${nom} — ${n}`,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const liste = v => (v === undefined ? [] : [v].flat());
|
|
94
|
+
|
|
95
|
+
// The knip settings of the repository at <racine> with its packages out of the frame skipped:
|
|
96
|
+
// its `knip.json` (empty without one) plus `ignoreWorkspaces` for each listed directory under
|
|
97
|
+
// `packages/` and `ignore` for each listed directory under `packages/` and `src/`; null when the
|
|
98
|
+
// list names nothing.
|
|
99
|
+
function reglages(racine) {
|
|
100
|
+
const hors = [...horsCadre(racine)].sort();
|
|
101
|
+
if (!hors.length) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
const fichier = path.join(racine, 'knip.json');
|
|
105
|
+
const r = existsSync(fichier) ? JSON.parse(readFileSync(fichier, 'utf8')) : {};
|
|
106
|
+
const espaces = [];
|
|
107
|
+
const motifs = [];
|
|
108
|
+
for (const nom of hors) {
|
|
109
|
+
for (const parent of ['packages', 'src']) {
|
|
110
|
+
if (!existsSync(path.join(racine, parent, nom))) {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (parent === 'packages') {
|
|
114
|
+
espaces.push(`packages/${nom}`);
|
|
115
|
+
}
|
|
116
|
+
motifs.push(`${parent}/${nom}/**`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
...r,
|
|
121
|
+
ignore: [...liste(r.ignore), ...motifs],
|
|
122
|
+
ignoreWorkspaces: [...liste(r.ignoreWorkspaces), ...espaces],
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const CODE = /\.[cm]?[jt]sx?$/;
|
|
127
|
+
const BANC = /\.(budget|nuit)\.[cm]?[jt]sx?$/;
|
|
128
|
+
|
|
129
|
+
// The test and bench files of the repository at <racine>, relative to it with `/` separators,
|
|
130
|
+
// as `fichiers`, and the directories holding a `package.json` as `dossiers` (`.` for the root);
|
|
131
|
+
// `erreur` is git's message when git fails. git lists every file its ignore rules
|
|
132
|
+
// (`.gitignore`) leave in against a fresh empty index, so a file counts whether tracked or not,
|
|
133
|
+
// and the user's global ignore file stays out, as knip reads them; a file under `node_modules`
|
|
134
|
+
// stays out, as knip skips it.
|
|
135
|
+
function fichiersTests(racine) {
|
|
136
|
+
const index = mkdtempSync(path.join(tmpdir(), 'code-mort-git-'));
|
|
137
|
+
try {
|
|
138
|
+
const init = spawnSync('git', ['init', '-q', '--bare', '--template=', index], {
|
|
139
|
+
encoding: 'utf8',
|
|
140
|
+
});
|
|
141
|
+
const r =
|
|
142
|
+
init.status === 0 &&
|
|
143
|
+
spawnSync(
|
|
144
|
+
'git',
|
|
145
|
+
[
|
|
146
|
+
'--git-dir',
|
|
147
|
+
index,
|
|
148
|
+
'--work-tree',
|
|
149
|
+
'.',
|
|
150
|
+
'-c',
|
|
151
|
+
'core.excludesFile=',
|
|
152
|
+
'ls-files',
|
|
153
|
+
'-o',
|
|
154
|
+
'--exclude-standard',
|
|
155
|
+
'-z',
|
|
156
|
+
],
|
|
157
|
+
{ cwd: racine, encoding: 'utf8', maxBuffer: 1 << 28 }
|
|
158
|
+
);
|
|
159
|
+
if (!r || r.status !== 0) {
|
|
160
|
+
return { erreur: (r || init).stderr || `git : code ${(r || init).status}` };
|
|
161
|
+
}
|
|
162
|
+
const tous = r.stdout.split('\0').filter(f => f && !f.split('/').includes('node_modules'));
|
|
163
|
+
const fichiers = tous.filter(
|
|
164
|
+
f => CODE.test(f) && (isTestFile(f) || BANC.test(path.posix.basename(f)))
|
|
165
|
+
);
|
|
166
|
+
const dossiers = tous
|
|
167
|
+
.filter(f => path.posix.basename(f) === 'package.json')
|
|
168
|
+
.map(f => path.posix.dirname(f));
|
|
169
|
+
return { fichiers, dossiers };
|
|
170
|
+
} finally {
|
|
171
|
+
rmSync(index, { recursive: true, force: true });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// The parts of the repository's `knip` the guard follows, loaded from its own files:
|
|
176
|
+
// `picomatch`, its glob matcher; `byPathDepth` (util/workspace.js), its order of workspace keys;
|
|
177
|
+
// `reglagesEspace`, the settings its schema admits in a workspace key
|
|
178
|
+
// (`workspaceConfigurationSchema`, schema/configuration.js). null when knip is not installed there
|
|
179
|
+
// or lacks one of them (the guard then refuses).
|
|
180
|
+
async function knipDuDepot(racine) {
|
|
181
|
+
try {
|
|
182
|
+
const entree = createRequire(path.join(racine, 'noeud.js')).resolve('knip');
|
|
183
|
+
const dist = path.dirname(entree);
|
|
184
|
+
const module = f => import(pathToFileURL(path.join(dist, f)).href);
|
|
185
|
+
const { byPathDepth } = await module('util/workspace.js');
|
|
186
|
+
const { workspaceConfigurationSchema } = await module('schema/configuration.js');
|
|
187
|
+
const reglagesEspace = new Set(Object.keys(workspaceConfigurationSchema.shape));
|
|
188
|
+
const picomatch = createRequire(entree)('picomatch');
|
|
189
|
+
return typeof byPathDepth === 'function' && reglagesEspace.has('entry')
|
|
190
|
+
? { picomatch, byPathDepth, reglagesEspace }
|
|
191
|
+
: null;
|
|
192
|
+
} catch {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// The workspace keys knip reads in <config>: those of its `workspaces` (`.` without them) less the
|
|
198
|
+
// ones `ignoreWorkspaces` names (`getConfiguredWorkspaceKeys`).
|
|
199
|
+
const clesConfigurees = config =>
|
|
200
|
+
(config?.workspaces ? Object.keys(config.workspaces) : ['.']).filter(
|
|
201
|
+
k => !liste(config?.ignoreWorkspaces).includes(k)
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
// The key of <cles> knip chooses for the workspace <nom> (`getConfigKeyForWorkspace`): the keys
|
|
205
|
+
// sorted by knip's `byPathDepth`, the deepest one that matches; undefined when none does.
|
|
206
|
+
const cleDe = ({ picomatch, byPathDepth }, cles, nom) =>
|
|
207
|
+
[...cles]
|
|
208
|
+
.sort(byPathDepth)
|
|
209
|
+
.reverse()
|
|
210
|
+
.find(c => picomatch.isMatch(nom, c));
|
|
211
|
+
|
|
212
|
+
// The settings knip gives the workspace <nom> in <config> (`getWorkspaceConfig`): those of the key
|
|
213
|
+
// it chooses, the whole of <config> when that key is `.` without `workspaces`, empty settings when
|
|
214
|
+
// no key matches; kept to the settings a workspace key admits (<knipLu>.reglagesEspace), so they
|
|
215
|
+
// can be written under a key of `workspaces`.
|
|
216
|
+
function reglagesDeEspace(knipLu, config, nom) {
|
|
217
|
+
const cle = cleDe(knipLu, clesConfigurees(config), nom);
|
|
218
|
+
const brut =
|
|
219
|
+
cle === undefined
|
|
220
|
+
? {}
|
|
221
|
+
: cle === '.' && !('.' in (config?.workspaces ?? {}))
|
|
222
|
+
? (config ?? {})
|
|
223
|
+
: (config.workspaces[cle] ?? {});
|
|
224
|
+
return Object.fromEntries(Object.entries(brut).filter(([k]) => knipLu.reglagesEspace.has(k)));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// The workspaces of the repository at <racine> among <dossiers> (the directories holding a
|
|
228
|
+
// `package.json`), as knip makes them (`getWorkspaces`): those the `workspaces` of the root
|
|
229
|
+
// `package.json` select (negated globs exclude), plus those the keys of <config> name, exact or
|
|
230
|
+
// by pattern, even when the root `package.json` lists them not; the ones the negated globs and
|
|
231
|
+
// `ignoreWorkspaces` of <config> target are left out. The root is not one. Sorted.
|
|
232
|
+
function paquets({ picomatch }, racine, dossiers, config) {
|
|
233
|
+
const fichier = path.join(racine, 'package.json');
|
|
234
|
+
const w = existsSync(fichier) ? JSON.parse(readFileSync(fichier, 'utf8')).workspaces : undefined;
|
|
235
|
+
const globs = liste(Array.isArray(w) ? w : w?.packages).map(g => g.replace(/^\.\//, ''));
|
|
236
|
+
const nies = globs.filter(g => g.startsWith('!')).map(g => g.slice(1));
|
|
237
|
+
const cles = clesConfigurees(config).filter(k => k !== '.');
|
|
238
|
+
const retenus = [...globs.filter(g => !g.startsWith('!')), ...cles];
|
|
239
|
+
const ignores = [...nies, ...liste(config?.ignoreWorkspaces).filter(g => !g.endsWith('!'))];
|
|
240
|
+
const exclus = ignores.length ? picomatch(ignores) : () => false;
|
|
241
|
+
return dossiers
|
|
242
|
+
.filter(d => d !== '.' && retenus.some(g => picomatch.isMatch(d, g)) && !exclus(d))
|
|
243
|
+
.sort();
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// The settings of the dependency pass: <config> whose `workspaces` are replaced by the places
|
|
247
|
+
// of the run, each receiving the settings knip gives it in <config> (`reglagesDeEspace`, <knipLu>
|
|
248
|
+
// the parts of knip it follows) and as entries its own test and bench files of the repository at
|
|
249
|
+
// <racine>, by absolute path: the `.` key the files no package holds, and one exact key per
|
|
250
|
+
// package <paquets> the files under its directory (the deepest package owns a file). The keys of
|
|
251
|
+
// <config> leave: an exact key per package is the only key knip finds for it. The `ignore`
|
|
252
|
+
// patterns follow as negated entry patterns rooted at their directory: the top-level ones in
|
|
253
|
+
// every place, those of a key in the place it serves; knip matches them as it matches its own
|
|
254
|
+
// patterns, and a file they target is not an entry.
|
|
255
|
+
function reglagesDependances(knipLu, racine, config, fichiers, paquets) {
|
|
256
|
+
const r = config ?? {};
|
|
257
|
+
const absolu = (dossier, m) => path.posix.join(racine, dossier === '.' ? '' : dossier, m);
|
|
258
|
+
const sauf = (dossier, motifs) => liste(motifs).map(m => `!${absolu(dossier, m)}`);
|
|
259
|
+
const proprio = f =>
|
|
260
|
+
paquets.filter(p => f.startsWith(`${p}/`)).sort((a, b) => b.length - a.length)[0] ?? '.';
|
|
261
|
+
const parPlace = new Map([['.', []], ...paquets.map(p => [p, []])]);
|
|
262
|
+
for (const f of fichiers) {
|
|
263
|
+
parPlace.get(proprio(f))?.push(path.posix.join(racine, f));
|
|
264
|
+
}
|
|
265
|
+
const racineIgnore = sauf('.', r.ignore);
|
|
266
|
+
const reglagesRacine = reglagesDeEspace(knipLu, r, '.');
|
|
267
|
+
const entryRacine = [...parPlace.get('.'), ...racineIgnore, ...sauf('.', reglagesRacine.ignore)];
|
|
268
|
+
if (!paquets.length && !r.workspaces) {
|
|
269
|
+
return { ...r, entry: entryRacine };
|
|
270
|
+
}
|
|
271
|
+
const espaces = {};
|
|
272
|
+
espaces['.'] = { ...reglagesRacine, entry: entryRacine };
|
|
273
|
+
for (const p of paquets) {
|
|
274
|
+
const reglages = reglagesDeEspace(knipLu, r, p);
|
|
275
|
+
espaces[p] = {
|
|
276
|
+
...reglages,
|
|
277
|
+
entry: [...parPlace.get(p), ...racineIgnore, ...sauf(p, reglages.ignore)],
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
return { ...r, entry: entryRacine, workspaces: espaces };
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Runs knip on <config> (knip's defaults when null), restricted to <include> when given; the
|
|
284
|
+
// spawn result.
|
|
285
|
+
function knip(config, include) {
|
|
286
|
+
const args = ['--no-install', 'knip', '--reporter', 'json', '--no-progress'];
|
|
287
|
+
if (include) {
|
|
288
|
+
args.push('--include', include);
|
|
289
|
+
}
|
|
290
|
+
let tmp = null;
|
|
291
|
+
try {
|
|
292
|
+
if (config) {
|
|
293
|
+
tmp = mkdtempSync(path.join(tmpdir(), 'code-mort-'));
|
|
294
|
+
const f = path.join(tmp, 'knip.json');
|
|
295
|
+
writeFileSync(f, JSON.stringify(config));
|
|
296
|
+
args.push('--config', f);
|
|
297
|
+
}
|
|
298
|
+
return spawnSync('npx', args, { encoding: 'utf8' });
|
|
299
|
+
} finally {
|
|
300
|
+
if (tmp) {
|
|
301
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// The command on the repository at <racine>: its exit code.
|
|
307
|
+
async function codeMort(racine) {
|
|
308
|
+
const hors = reglages(racine);
|
|
309
|
+
const fichierKnip = path.join(racine, 'knip.json');
|
|
310
|
+
const config =
|
|
311
|
+
hors ?? (existsSync(fichierKnip) ? JSON.parse(readFileSync(fichierKnip, 'utf8')) : null);
|
|
312
|
+
const DEPS = ['dependencies', 'devDependencies'];
|
|
313
|
+
const { fichiers, dossiers, erreur } = fichiersTests(racine);
|
|
314
|
+
if (erreur) {
|
|
315
|
+
process.stderr.write(`⛔ CODE MORT — git n'a pas listé les fichiers de test :\n${erreur}\n`);
|
|
316
|
+
return 1;
|
|
317
|
+
}
|
|
318
|
+
const knipLu = await knipDuDepot(racine);
|
|
319
|
+
if (!knipLu) {
|
|
320
|
+
process.stderr.write(
|
|
321
|
+
'⛔ CODE MORT — knip du dépôt illisible : son matcher (picomatch), son ordre des clés (byPathDepth) ou son schéma des espaces manque.\n'
|
|
322
|
+
);
|
|
323
|
+
return 1;
|
|
324
|
+
}
|
|
325
|
+
const premier = knip(hors ?? config);
|
|
326
|
+
const c1 = constats(premier.stdout ?? '');
|
|
327
|
+
const second = fichiers.length
|
|
328
|
+
? knip(
|
|
329
|
+
reglagesDependances(
|
|
330
|
+
knipLu,
|
|
331
|
+
racine,
|
|
332
|
+
config,
|
|
333
|
+
fichiers,
|
|
334
|
+
paquets(knipLu, racine, dossiers, config)
|
|
335
|
+
),
|
|
336
|
+
DEPS.join(',')
|
|
337
|
+
)
|
|
338
|
+
: null;
|
|
339
|
+
const c2 = second ? constats(second.stdout ?? '', DEPS) : null;
|
|
340
|
+
let c = null;
|
|
341
|
+
if (c1 && (c2 || !second)) {
|
|
342
|
+
const messages = liste => new Set(liste.map(x => x.message));
|
|
343
|
+
const deps = messages(constats(premier.stdout, DEPS));
|
|
344
|
+
const lus = c2 ? messages(c2) : deps;
|
|
345
|
+
c = c1.filter(x => !deps.has(x.message) || lus.has(x.message));
|
|
346
|
+
}
|
|
347
|
+
if (c === null) {
|
|
348
|
+
const r = c1 ? second : premier;
|
|
349
|
+
process.stderr.write(
|
|
350
|
+
`⛔ CODE MORT — knip n'a rien mesuré :\n${(r.stderr || r.stdout || '').slice(0, 1000)}\n`
|
|
351
|
+
);
|
|
352
|
+
return 1;
|
|
353
|
+
}
|
|
354
|
+
const { refus, lignes } = trancher(racine, 'code-mort', c);
|
|
355
|
+
if (refus.length) {
|
|
356
|
+
process.stderr.write(
|
|
357
|
+
`⛔ CODE MORT — ${refus.length} constat(s) : chacun se supprime, ou sort par son nom dans knip.json avec sa raison.\n`
|
|
358
|
+
);
|
|
359
|
+
for (const x of refus) {
|
|
360
|
+
process.stderr.write(` ${x.message}\n`);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
for (const l of lignes) {
|
|
364
|
+
process.stderr.write(`${l}\n`);
|
|
365
|
+
}
|
|
366
|
+
return refus.length ? 1 : 0;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {
|
|
370
|
+
process.exitCode = await codeMort(process.cwd());
|
|
371
|
+
}
|