@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,338 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// The frame's plugins, installed for the project by one mechanism: `scripts/preparation.mjs` (the
|
|
3
|
+
// preparation of `grillhouse init`) and `scripts/grillhouse-maj.mjs` (update, its command line) both
|
|
4
|
+
// call it, and the session start
|
|
5
|
+
// asks it which are absent.
|
|
6
|
+
//
|
|
7
|
+
// npx grillhouse greffons [<project>] installs the declared plugins (the current directory by default)
|
|
8
|
+
// npx grillhouse greffons --absents [<project>] prints what the project lacks, one line each (see below)
|
|
9
|
+
//
|
|
10
|
+
// - The list is `enabledPlugins` of the project's `.claude/settings.json` (a plugin at `true`; one
|
|
11
|
+
// at `false` or unlisted is left alone) and `extraKnownMarketplaces` for the marketplaces. A
|
|
12
|
+
// missing file declares nothing (code 0); an unreadable one is a failure.
|
|
13
|
+
// - Installation: each marketplace of `extraKnownMarketplaces` unknown to `claude plugin
|
|
14
|
+
// marketplace list --json` is added by `claude plugin marketplace add <source>` (the user's scope,
|
|
15
|
+
// the project's settings stay untouched; the source is `repo` for github, `url` for git and url,
|
|
16
|
+
// `path` for directory and file, any other type is a failure); then each declared plugin not
|
|
17
|
+
// installed for the project is installed by `claude plugin install <id> --scope project`, run
|
|
18
|
+
// from the project's root. `claude` rewrites `.claude/settings.json` in its own key order; once
|
|
19
|
+
// the installs are done, the file holds the keys in the order it had before them (the keys
|
|
20
|
+
// claude added after), and its exact text when the JSON value is the same.
|
|
21
|
+
// - A plugin is installed for the project when `claude plugin list --json` has an entry for it of
|
|
22
|
+
// scope user, or of scope project or local whose `projectPath` is the project's root (both paths
|
|
23
|
+
// resolved); the entry's state (enabled, errors) is not read.
|
|
24
|
+
// - A failure has its line on the error output, naming the plugin and the command that installs it
|
|
25
|
+
// (a failed add: the marketplace's command, then the plugin's); the others are still tried and the
|
|
26
|
+
// code is 1. A state reading that fails installs nothing. Without `claude`, each plugin has its line.
|
|
27
|
+
// - `--absents` prints `claude` (the command is missing), `greffon <id>` per declared plugin not
|
|
28
|
+
// installed for the project, or `lecture <reason>` when the state cannot be read; each claude
|
|
29
|
+
// call stops after 5 s. It also prints `ecart <id> <mod's version> <package's version>
|
|
30
|
+
// <command>` when the frame's mod (`grillhouse@…`), installed for the project, carries another
|
|
31
|
+
// version than the package `@roomi-fields/grillhouse` this script belongs to: the package carries
|
|
32
|
+
// the plugin, whose manifest receives the package's version when it is packed (scripts/cadre.mjs),
|
|
33
|
+
// and the marketplace roomi-fields names the package, so a project's package and the plugin the
|
|
34
|
+
// machine installed for it can drift apart. The entry read is the project's at scope local, else
|
|
35
|
+
// project, else user; the command aligns the one behind (`claude plugin update <id> --scope <its
|
|
36
|
+
// scope>`, or `npm install -D @roomi-fields/grillhouse@<mod's version>`). A version that is not
|
|
37
|
+
// x.y.z (a plugin whose manifest carries none) and a script without its package's package.json
|
|
38
|
+
// beside it compare nothing.
|
|
39
|
+
import { spawnSync } from 'node:child_process';
|
|
40
|
+
import { existsSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
|
|
41
|
+
import path from 'node:path';
|
|
42
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
43
|
+
import { pathToFileURL } from 'node:url';
|
|
44
|
+
|
|
45
|
+
const DELAI_LECTURE_MS = 5000;
|
|
46
|
+
|
|
47
|
+
// The path resolved through links, else normalised.
|
|
48
|
+
function resolu(p) {
|
|
49
|
+
try {
|
|
50
|
+
return realpathSync(p);
|
|
51
|
+
} catch {
|
|
52
|
+
return path.resolve(p);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Runs claude; `absent` is true when the command does not exist, `delai` when it was stopped.
|
|
57
|
+
function claude(args, options = {}) {
|
|
58
|
+
const r = spawnSync('claude', args, { encoding: 'utf8', ...options });
|
|
59
|
+
return {
|
|
60
|
+
ok: r.status === 0 && !r.error,
|
|
61
|
+
absent: r.error?.code === 'ENOENT',
|
|
62
|
+
delai: r.error?.code === 'ETIMEDOUT',
|
|
63
|
+
stdout: r.stdout ?? '',
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// The declaration of the project: `{ plugins: [id], marches: {name: {source}} }`, or `{ erreur }`.
|
|
68
|
+
function declaration(projet) {
|
|
69
|
+
const f = path.join(projet, '.claude', 'settings.json');
|
|
70
|
+
if (!existsSync(f)) {
|
|
71
|
+
return { plugins: [], marches: {} };
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
const r = JSON.parse(readFileSync(f, 'utf8'));
|
|
75
|
+
return {
|
|
76
|
+
plugins: Object.entries(r.enabledPlugins ?? {})
|
|
77
|
+
.filter(([, actif]) => actif === true)
|
|
78
|
+
.map(([id]) => id),
|
|
79
|
+
marches: r.extraKnownMarketplaces ?? {},
|
|
80
|
+
};
|
|
81
|
+
} catch {
|
|
82
|
+
return { erreur: `.claude/settings.json illisible (${f})` };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// The JSON array `claude <args> --json` answers, or `{ erreur, delai }`.
|
|
87
|
+
function lireListe(args, delai) {
|
|
88
|
+
const r = claude([...args, '--json'], { timeout: delai });
|
|
89
|
+
if (r.absent) {
|
|
90
|
+
return { absent: true };
|
|
91
|
+
}
|
|
92
|
+
if (!r.ok) {
|
|
93
|
+
return {
|
|
94
|
+
erreur: `\`claude ${args.join(' ')} --json\` ${r.delai ? 'interrompu' : 'échoue'}`,
|
|
95
|
+
delai: r.delai,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
const v = JSON.parse(r.stdout);
|
|
100
|
+
if (Array.isArray(v)) {
|
|
101
|
+
return { liste: v };
|
|
102
|
+
}
|
|
103
|
+
} catch {
|
|
104
|
+
// unreadable, below
|
|
105
|
+
}
|
|
106
|
+
return { erreur: `\`claude ${args.join(' ')} --json\` rend un JSON illisible` };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// True when the entries hold the plugin for the project (scope user, or project/local at its root).
|
|
110
|
+
function installePourLeProjet(entrees, id, racine) {
|
|
111
|
+
return entrees.some(
|
|
112
|
+
e =>
|
|
113
|
+
e.id === id &&
|
|
114
|
+
(e.scope === 'user' ||
|
|
115
|
+
((e.scope === 'project' || e.scope === 'local') &&
|
|
116
|
+
typeof e.projectPath === 'string' &&
|
|
117
|
+
resolu(e.projectPath) === racine))
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// The source `marketplace add` receives for an entry of `extraKnownMarketplaces`, or null.
|
|
122
|
+
function sourceDuMarche(entree) {
|
|
123
|
+
const s = entree?.source ?? {};
|
|
124
|
+
const champ = { github: 'repo', git: 'url', url: 'url', directory: 'path', file: 'path' }[
|
|
125
|
+
s.source
|
|
126
|
+
];
|
|
127
|
+
return champ && typeof s[champ] === 'string' ? s[champ] : null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// The value <nouveau> with the object keys of <ancien> first, in its order, the others after;
|
|
131
|
+
// applied at each depth (arrays and scalars are kept as they are).
|
|
132
|
+
function dansLOrdre(nouveau, ancien) {
|
|
133
|
+
const objet = v => v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
134
|
+
if (!objet(nouveau) || !objet(ancien)) {
|
|
135
|
+
return nouveau;
|
|
136
|
+
}
|
|
137
|
+
const cles = [
|
|
138
|
+
...Object.keys(ancien).filter(k => k in nouveau),
|
|
139
|
+
...Object.keys(nouveau).filter(k => !(k in ancien)),
|
|
140
|
+
];
|
|
141
|
+
return Object.fromEntries(cles.map(k => [k, dansLOrdre(nouveau[k], ancien[k])]));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Puts `.claude/settings.json` back in the key order (and text, for an equal value) it had in
|
|
145
|
+
// <avant>, the text read before the installs; a file that is gone or unreadable stays as it is.
|
|
146
|
+
function restaurerReglages(fichier, avant) {
|
|
147
|
+
if (avant === null) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
const apres = readFileSync(fichier, 'utf8');
|
|
152
|
+
if (apres === avant) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const ancien = JSON.parse(avant);
|
|
156
|
+
const range = dansLOrdre(JSON.parse(apres), ancien);
|
|
157
|
+
writeFileSync(
|
|
158
|
+
fichier,
|
|
159
|
+
isDeepStrictEqual(range, ancien) ? avant : `${JSON.stringify(range, null, 2)}\n`
|
|
160
|
+
);
|
|
161
|
+
} catch {
|
|
162
|
+
// unreadable: left as claude wrote it
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const commandeGreffon = id => `claude plugin install ${id} --scope project`;
|
|
167
|
+
const marcheDe = id => id.slice(id.indexOf('@') + 1);
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Installs the plugins the project declares, for the project. Returns the lines of failure
|
|
171
|
+
* (empty when everything stands).
|
|
172
|
+
*/
|
|
173
|
+
export function installerGreffons(projet) {
|
|
174
|
+
const fichier = path.join(resolu(projet), '.claude', 'settings.json');
|
|
175
|
+
const avant = existsSync(fichier) ? readFileSync(fichier, 'utf8') : null;
|
|
176
|
+
try {
|
|
177
|
+
return installer(projet);
|
|
178
|
+
} finally {
|
|
179
|
+
restaurerReglages(fichier, avant);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function installer(projet) {
|
|
184
|
+
const racine = resolu(projet);
|
|
185
|
+
const d = declaration(racine);
|
|
186
|
+
if (d.erreur) {
|
|
187
|
+
return [d.erreur];
|
|
188
|
+
}
|
|
189
|
+
if (d.plugins.length === 0 && Object.keys(d.marches).length === 0) {
|
|
190
|
+
return [];
|
|
191
|
+
}
|
|
192
|
+
const lignes = [];
|
|
193
|
+
const lecturePlugins = lireListe(['plugin', 'list'], undefined);
|
|
194
|
+
if (lecturePlugins.absent) {
|
|
195
|
+
return d.plugins.map(
|
|
196
|
+
id =>
|
|
197
|
+
`${id} : la commande claude manque ; installe-la, puis lance \`${commandeGreffon(id)}\`.`
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
if (lecturePlugins.erreur) {
|
|
201
|
+
return [`${lecturePlugins.erreur} : aucun greffon n'est installé.`];
|
|
202
|
+
}
|
|
203
|
+
const lectureMarches = lireListe(['plugin', 'marketplace', 'list'], undefined);
|
|
204
|
+
if (lectureMarches.erreur) {
|
|
205
|
+
return [`${lectureMarches.erreur} : aucun marketplace n'est ajouté, aucun greffon installé.`];
|
|
206
|
+
}
|
|
207
|
+
const connus = new Set(lectureMarches.liste.map(m => m.name));
|
|
208
|
+
// marketplace name -> the line-part of its failed add (command or reason)
|
|
209
|
+
const echecs = new Map();
|
|
210
|
+
for (const [nom, entree] of Object.entries(d.marches)) {
|
|
211
|
+
if (connus.has(nom)) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
const src = sourceDuMarche(entree);
|
|
215
|
+
if (src === null) {
|
|
216
|
+
echecs.set(
|
|
217
|
+
nom,
|
|
218
|
+
`le marketplace ${nom} a une source (${entree?.source?.source}) que \`claude plugin marketplace add\` n'accepte pas`
|
|
219
|
+
);
|
|
220
|
+
lignes.push(`${echecs.get(nom)}.`);
|
|
221
|
+
} else if (!claude(['plugin', 'marketplace', 'add', src], { cwd: racine }).ok) {
|
|
222
|
+
echecs.set(nom, `\`claude plugin marketplace add ${src}\``);
|
|
223
|
+
if (!d.plugins.some(id => marcheDe(id) === nom)) {
|
|
224
|
+
lignes.push(`le marketplace ${nom} : l'ajout échoue, lance ${echecs.get(nom)}.`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
for (const id of d.plugins) {
|
|
229
|
+
if (installePourLeProjet(lecturePlugins.liste, id, racine)) {
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
const marche = marcheDe(id);
|
|
233
|
+
if (echecs.has(marche)) {
|
|
234
|
+
lignes.push(
|
|
235
|
+
`${id} : le marketplace ${marche} manque (${echecs.get(marche)}), puis \`${commandeGreffon(id)}\`.`
|
|
236
|
+
);
|
|
237
|
+
} else if (!claude(['plugin', 'install', id, '--scope', 'project'], { cwd: racine }).ok) {
|
|
238
|
+
lignes.push(`${id} : l'installation échoue ; lance \`${commandeGreffon(id)}\`.`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return lignes;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* What the project lacks of its declared plugins: `claude`, `greffon <id>` or `lecture <reason>`
|
|
246
|
+
* lines, and the `ecart …` line of a mod and a package of different versions. Reads only; each
|
|
247
|
+
* claude call stops after 5 s.
|
|
248
|
+
*/
|
|
249
|
+
export function greffonsAbsents(projet) {
|
|
250
|
+
const racine = resolu(projet);
|
|
251
|
+
const d = declaration(racine);
|
|
252
|
+
if (d.erreur) {
|
|
253
|
+
return [`lecture ${d.erreur}`];
|
|
254
|
+
}
|
|
255
|
+
if (d.plugins.length === 0) {
|
|
256
|
+
return [];
|
|
257
|
+
}
|
|
258
|
+
const lecture = lireListe(['plugin', 'list'], DELAI_LECTURE_MS);
|
|
259
|
+
if (lecture.absent) {
|
|
260
|
+
return ['claude'];
|
|
261
|
+
}
|
|
262
|
+
if (lecture.erreur) {
|
|
263
|
+
return [`lecture ${lecture.erreur}`];
|
|
264
|
+
}
|
|
265
|
+
const absents = d.plugins.filter(id => !installePourLeProjet(lecture.liste, id, racine));
|
|
266
|
+
const ecart = ecartDeVersions(lecture.liste, d.plugins, racine);
|
|
267
|
+
return [...absents.map(id => `greffon ${id}`), ...(ecart ? [ecart] : [])];
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// The frame's mod and its package: one version, the `version` of the package.json beside this
|
|
271
|
+
// script's directory (the package's, wherever it is installed), which the plugin's manifest
|
|
272
|
+
// receives when the package is packed.
|
|
273
|
+
const GREFFON_DU_CADRE = 'grillhouse';
|
|
274
|
+
const NOM_DU_PAQUET = '@roomi-fields/grillhouse';
|
|
275
|
+
const X_Y_Z = /^\d+\.\d+\.\d+$/;
|
|
276
|
+
function versionDuPaquet() {
|
|
277
|
+
try {
|
|
278
|
+
const p = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
279
|
+
return p.name === NOM_DU_PAQUET && typeof p.version === 'string' ? p.version : null;
|
|
280
|
+
} catch {
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// The line `ecart <id> <mod's version> <package's version> <command>` when the frame's mod,
|
|
286
|
+
// declared and installed for the project (its entry of scope local, else project, else user, as
|
|
287
|
+
// Claude Code applies them), carries another version than the package; null otherwise. The command
|
|
288
|
+
// aligns the one behind: the mod's update at its scope, or the package's install at the mod's
|
|
289
|
+
// version.
|
|
290
|
+
function ecartDeVersions(entrees, declares, racine) {
|
|
291
|
+
const paquet = versionDuPaquet();
|
|
292
|
+
const id = declares.find(x => x.slice(0, x.indexOf('@')) === GREFFON_DU_CADRE);
|
|
293
|
+
if (!paquet || !id) {
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
const pourLeProjet = entrees.filter(e => installePourLeProjet([e], id, racine));
|
|
297
|
+
const entree = ['local', 'project', 'user']
|
|
298
|
+
.map(s => pourLeProjet.find(e => e.scope === s))
|
|
299
|
+
.find(Boolean);
|
|
300
|
+
const greffon = entree?.version;
|
|
301
|
+
if (typeof greffon !== 'string' || !X_Y_Z.test(greffon) || greffon === paquet) {
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
const commande = plusAncienne(greffon, paquet)
|
|
305
|
+
? `claude plugin update ${id} --scope ${entree.scope}`
|
|
306
|
+
: `npm install -D ${NOM_DU_PAQUET}@${greffon}`;
|
|
307
|
+
return `ecart ${id} ${greffon} ${paquet} ${commande}`;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// True when version <a> comes before version <b> (x.y.z, numbers compared in order).
|
|
311
|
+
function plusAncienne(a, b) {
|
|
312
|
+
const x = a.split('.').map(Number);
|
|
313
|
+
const y = b.split('.').map(Number);
|
|
314
|
+
for (let i = 0; i < 3; i++) {
|
|
315
|
+
if ((x[i] ?? 0) !== (y[i] ?? 0)) {
|
|
316
|
+
return (x[i] ?? 0) < (y[i] ?? 0);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(resolu(process.argv[1])).href) {
|
|
323
|
+
const args = process.argv.slice(2);
|
|
324
|
+
const absents = args[0] === '--absents';
|
|
325
|
+
const projet = path.resolve((absents ? args[1] : args[0]) ?? '.');
|
|
326
|
+
if (absents) {
|
|
327
|
+
const lignes = greffonsAbsents(projet);
|
|
328
|
+
if (lignes.length > 0) {
|
|
329
|
+
process.stdout.write(`${lignes.join('\n')}\n`);
|
|
330
|
+
}
|
|
331
|
+
} else {
|
|
332
|
+
const lignes = installerGreffons(projet);
|
|
333
|
+
for (const l of lignes) {
|
|
334
|
+
process.stderr.write(`${l}\n`);
|
|
335
|
+
}
|
|
336
|
+
process.exitCode = lignes.length > 0 ? 1 : 0;
|
|
337
|
+
}
|
|
338
|
+
}
|