@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,514 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// The frame's update: brings what the frame writes in a project to the version of the package
|
|
3
|
+
// installed, as `npx @turbo/codemod migrate` brings a repository to the version of Turborepo.
|
|
4
|
+
//
|
|
5
|
+
// npx grillhouse maj update (npm run grillhouse:maj), after `npm install` of the version wanted
|
|
6
|
+
//
|
|
7
|
+
// - The versions pass by npm: the project installs the version of @roomi-fields/grillhouse it wants
|
|
8
|
+
// (`npm install -D @roomi-fields/grillhouse@<version>`), then the update brings the project to it.
|
|
9
|
+
// What the update reads of the frame is the package's own (scripts/cadre.mjs): the template of a
|
|
10
|
+
// project (`templates/projet/package.json` for the frame's scripts and tools,
|
|
11
|
+
// `templates/turbo/*.json` for a turbo.json). Any argument is refused: the usage is shown (the
|
|
12
|
+
// exit code is non-zero) and nothing is updated, `--help` included.
|
|
13
|
+
// - The migrations first (scripts/migrations.mjs): each migration of the package newer than the
|
|
14
|
+
// version the project recorded (`grillhouse.version` of its package.json; none recorded, every
|
|
15
|
+
// migration), in the order of their versions; each is replayable without effect, and the report
|
|
16
|
+
// names each one that changed something, with what it changed.
|
|
17
|
+
// - The frame copies no file into a project: its scripts are the package's, its skills, agents,
|
|
18
|
+
// hooks of Claude Code, common charter and meta-frame the plugin's, which the package carries
|
|
19
|
+
// (scripts/cadre.mjs): the frame arrives in one piece, at the version npm installs.
|
|
20
|
+
// - The frame's plugin is a setting of the project: in a project whose `.claude/settings.json`
|
|
21
|
+
// exists, `extraKnownMarketplaces` declares the marketplace `roomi-fields` (GitHub,
|
|
22
|
+
// `roomi-fields/claude-plugins`), which names the package npm as the plugin's source, and
|
|
23
|
+
// `enabledPlugins` enables its plugin, `grillhouse@roomi-fields`, each added after the keys the
|
|
24
|
+
// settings hold, which keep their order. What is a project's own (its charter, its settings, its
|
|
25
|
+
// version of the package) lives in the project, never in what is published: the machine knows a
|
|
26
|
+
// marketplace by its name, once for every project. A migration takes off the marketplace of the
|
|
27
|
+
// project's own an earlier update wrote (scripts/migrations/0.23.0-marche-du-projet.mjs).
|
|
28
|
+
// - The status line is a setting of the project, as Husky writes the project's own configuration:
|
|
29
|
+
// in a project whose `.claude/settings.json` exists, `statusLine` becomes the frame's
|
|
30
|
+
// (`"$CLAUDE_PROJECT_DIR/node_modules/.bin/grillhouse" ligne-etat`) when it is absent or names
|
|
31
|
+
// the frame's status line (`ligne-etat`, whatever its former form); a status line of the
|
|
32
|
+
// project's own stays. The settings keep their other keys, in their order.
|
|
33
|
+
// - `package.json`, when there is one: the version of the frame it is brought to
|
|
34
|
+
// (`grillhouse.version`, the package's version), the frame's npm scripts (`tableau`,
|
|
35
|
+
// `grillhouse:maj`), its `prepare` beginning with `grillhouse hooks` (the frame's git hooks at
|
|
36
|
+
// each `npm install`, as Husky's `prepare`; a `prepare` of the project's own follows it,
|
|
37
|
+
// `grillhouse hooks && …`), and `grillhouse:gardes` (scripts/gardes-du-cadre.mjs, the frame's controls), `grillhouse:dette:inscrire` and
|
|
38
|
+
// `grillhouse:dette:elaguer` (the known debt of the guards, scripts/dette.mjs), and `api` (`npm run api`, which writes
|
|
39
|
+
// the API reports and the generated part of each `docs/INTERFACE.md`), each read as it is in the
|
|
40
|
+
// template's `package.json` (a template without one writes none). Turborepo plays `grillhouse:gardes`
|
|
41
|
+
// and the project's own guards, the root script `gardes`, after the build, beside the tests
|
|
42
|
+
// (`turbo.json`, `npm run verifier`): the frame's former wiring goes, a `pretest` that is the
|
|
43
|
+
// frame's call (`npm run --silent grillhouse:gardes`) removed or taken off the head of a longer
|
|
44
|
+
// one (`… && <its pretest>`, the rest kept; a `pretest` of the project's own stays). An
|
|
45
|
+
// `integration:gardes`, the script the former integration played, loses each head that is a
|
|
46
|
+
// former frame command (that call, `npm run --silent pretest`,
|
|
47
|
+
// `node scripts/integration/gardes.mjs`); what remains is the project's own guards, which move to
|
|
48
|
+
// `gardes` when the project has none (the report says so), else stay where nothing plays them
|
|
49
|
+
// (the report names both); nothing remaining, the script goes.
|
|
50
|
+
// - `turbo.json` is the project's own file, as every repository run by Turborepo writes its own. A
|
|
51
|
+
// project with a `package.json` and no `turbo.json` receives the frame's template of its form:
|
|
52
|
+
// `templates/turbo/paquets.json` when its `package.json` declares
|
|
53
|
+
// `workspaces` (the tasks of each package, and the root's guards, `//#grillhouse:gardes` and
|
|
54
|
+
// `//#gardes`; the tests live in the packages, the root has no `test:vitest`; the `build` of each
|
|
55
|
+
// package is set by its own generated `turbo.json`, below), else
|
|
56
|
+
// `templates/turbo/sans-paquets.json` (`build`, then the guards and the tests beside each other).
|
|
57
|
+
// A `turbo.json` present is never written again by the update; only the generator of the
|
|
58
|
+
// packages' turbo.json (below) writes in it the `dependsOn` of the guard tasks, and adds one it
|
|
59
|
+
// lacks.
|
|
60
|
+
// - The tools the frame's checks run as commands (API Extractor, whose TSDoc base the project's
|
|
61
|
+
// `tsdoc.json` extends, dependency-cruiser, knip, Turborepo) are added to the project's
|
|
62
|
+
// `devDependencies` at the version the template's `devDependencies` give; a tool the project
|
|
63
|
+
// already declares in `dependencies`, `devDependencies` or `optionalDependencies` keeps its
|
|
64
|
+
// declaration, one a previous version added stays, and `devDependencies` are written sorted by
|
|
65
|
+
// name. The report names each tool added and `npm install`, which installs them. What the
|
|
66
|
+
// package's scripts import, it declares itself (its `dependencies`, and Vitest and TypeScript as
|
|
67
|
+
// peers). A project without `packageManager` receives the template's (Turborepo requires it), and
|
|
68
|
+
// a `.gitignore` that does not ignore `.turbo/` (Turborepo's runs and cache) receives the line.
|
|
69
|
+
// - A project that runs Vitest (its `test` script names it, or `vitest` is in the `dependencies` or
|
|
70
|
+
// `devDependencies` of its root `package.json` or of one of its packages, the workspaces as npm
|
|
71
|
+
// maps them, read by `paquets` of scripts/paquets.mjs, the frame's one list of the
|
|
72
|
+
// packages: a package a negation takes out is none, and a package out of the frame
|
|
73
|
+
// (`docs/agents/hors-cadre.txt`), which Turborepo never tests, counts for nothing) has its root scripts
|
|
74
|
+
// `test`, `test:vitest`, `test:forcer` and `verifier` become the template's (`npm test`, the tests
|
|
75
|
+
// as Turborepo's tasks with its cache; `npm run test:forcer`, the same played for real;
|
|
76
|
+
// `npm run verifier`, scripts/verifier.mjs, the guards and the tests beside each other, which the
|
|
77
|
+
// integration, the queue, the continuous integration and the night play); the report says so, when the
|
|
78
|
+
// Vitest of the project's root (`npx --no-install vitest`, the one `test:vitest` calls) satisfies
|
|
79
|
+
// the range the template's package.json declares for `vitest`. Where the root resolves no Vitest or
|
|
80
|
+
// another version, the project's scripts stay (the frame's included) and the report names the
|
|
81
|
+
// version found and the range required. A `test` that runs anything else stays. The scripts of
|
|
82
|
+
// the packages of a monorepo are not written.
|
|
83
|
+
// - The frame's base configuration of Vitest is the package's (`@roomi-fields/grillhouse/vitest-config`,
|
|
84
|
+
// scripts/vitest-config/), which each Vitest configuration imports by its name
|
|
85
|
+
// (`import base from '@roomi-fields/grillhouse/vitest-config'`), as the examples of Turborepo
|
|
86
|
+
// reach @repo/eslint-config: a package is reached by its name, never by a path into it. In a
|
|
87
|
+
// project with `workspaces`, each package of the frame that holds a `test:vitest` script
|
|
88
|
+
// declares the frame's package in its `devDependencies`, at the range the root declares (else
|
|
89
|
+
// `^<version>` of the package), a declaration it holds kept; a package out of the frame is not
|
|
90
|
+
// touched. The report names the packages that received it, `npm install`, which links it, and the
|
|
91
|
+
// import; the update writes no Vitest configuration.
|
|
92
|
+
// - The git hooks: both of the frame (commit-msg « a closed ticket refuses a second commit, a reopened ticket takes its corrections », pre-push night branch
|
|
93
|
+
// guard) are placed by scripts/crochets.mjs, the mechanism `grillhouse hooks` (the project's
|
|
94
|
+
// `prepare`) also runs, in the hooks folder git reads for the project (`core.hooksPath`, else the
|
|
95
|
+
// default one; the configuration stays). A hook of the project keeps its lines, which still run;
|
|
96
|
+
// the frame's lines of a former version give way to the current ones.
|
|
97
|
+
// - The plugins: after the files, the command line installs the plugins the project's settings
|
|
98
|
+
// declare, for the project, by scripts/greffons.mjs (the mechanism `grillhouse init` also calls;
|
|
99
|
+
// `mettreAJour` itself never calls claude). A plugin that cannot be installed has its line naming
|
|
100
|
+
// the command to type; the update has finished its files, and the exit code is non-zero.
|
|
101
|
+
// - The generated files: after the package.json, each generator of the frame is run in the project
|
|
102
|
+
// by the project's command (`node_modules/.bin/grillhouse`, its installed package) for the file
|
|
103
|
+
// it writes when the project holds that file (`grillhouse frontieres --ecrire` for
|
|
104
|
+
// `.dependency-cruiser.cjs`, `grillhouse index-interfaces` for
|
|
105
|
+
// `docs/agents/index-des-interfaces.md`; every one of them for a project `grillhouse init`
|
|
106
|
+
// creates), and `grillhouse turbo-des-paquets --ecrire`
|
|
107
|
+
// in a repository with `workspaces` (the `turbo.json` of each of its packages, whose build waits
|
|
108
|
+
// for its runtime dependencies only, and of which a package out of the frame plays no task; the
|
|
109
|
+
// `dependsOn` of the guard tasks of the root's turbo.json, which wait for the builds), so that
|
|
110
|
+
// the frame's checks accept them; a generator
|
|
111
|
+
// that fails, or a project without its command, has its line, which says to run it after
|
|
112
|
+
// `npm install` (the frame's package and tools), and the exit code is non-zero. `interfaces-contre-code.mjs` is not
|
|
113
|
+
// run: it writes after a build.
|
|
114
|
+
// - The report names the version of the frame, « Cadre 0.23.0. », the package's.
|
|
115
|
+
import { spawnSync } from 'node:child_process';
|
|
116
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
117
|
+
import path from 'node:path';
|
|
118
|
+
import { pathToFileURL } from 'node:url';
|
|
119
|
+
import semver from 'semver';
|
|
120
|
+
import { CADRE, VERSION } from './cadre.mjs';
|
|
121
|
+
import { poserCrochets } from './crochets.mjs';
|
|
122
|
+
import { installerGreffons } from './greffons.mjs';
|
|
123
|
+
import { migrer } from './migrations.mjs';
|
|
124
|
+
import { paquets } from './paquets.mjs';
|
|
125
|
+
|
|
126
|
+
const SCRIPTS = {
|
|
127
|
+
tableau: 'grillhouse tableau --texte',
|
|
128
|
+
'grillhouse:maj': 'grillhouse maj',
|
|
129
|
+
};
|
|
130
|
+
// The command of the project's `prepare` that places the frame's git hooks (Husky's model).
|
|
131
|
+
const PREPARE = 'grillhouse hooks';
|
|
132
|
+
// The template's package.json, where the frame's scripts and tools are read.
|
|
133
|
+
const GABARIT = 'templates/projet/package.json';
|
|
134
|
+
// The frame's package, as a project declares it, and its base configuration of Vitest.
|
|
135
|
+
const PAQUET_DU_CADRE = '@roomi-fields/grillhouse';
|
|
136
|
+
const CONFIG_VITEST = `${PAQUET_DU_CADRE}/vitest-config`;
|
|
137
|
+
|
|
138
|
+
/** The frame's status line, as the settings of a project run it. */
|
|
139
|
+
export const LIGNE_ETAT = '"$CLAUDE_PROJECT_DIR/node_modules/.bin/grillhouse" ligne-etat';
|
|
140
|
+
|
|
141
|
+
// The frame's tools a project runs as commands: their names are the frame's, their versions are
|
|
142
|
+
// read in the template.
|
|
143
|
+
const OUTILS_DU_CADRE = ['@microsoft/api-extractor', 'dependency-cruiser', 'knip', 'turbo'];
|
|
144
|
+
// The root scripts of the tests, written together from the template.
|
|
145
|
+
const SCRIPTS_DES_TESTS = ['test', 'test:vitest', 'test:forcer', 'verifier'];
|
|
146
|
+
const CHAMPS_INSTALLES = ['dependencies', 'devDependencies', 'optionalDependencies'];
|
|
147
|
+
|
|
148
|
+
// The frame's former wiring of the guards: the call its `pretest` held, and the commands its
|
|
149
|
+
// `integration:gardes` held.
|
|
150
|
+
const APPEL_GARDES = 'npm run --silent grillhouse:gardes';
|
|
151
|
+
const ANCIENS_GARDES_D_INTEGRATION = [
|
|
152
|
+
APPEL_GARDES,
|
|
153
|
+
'npm run --silent pretest',
|
|
154
|
+
'node scripts/integration/gardes.mjs',
|
|
155
|
+
];
|
|
156
|
+
// The root script of the project's own guards, which `npm run verifier` plays (turbo.json), and
|
|
157
|
+
// the one the former integration played.
|
|
158
|
+
const GARDES_DU_PROJET = 'gardes';
|
|
159
|
+
const ANCIEN_GARDES_DU_PROJET = 'integration:gardes';
|
|
160
|
+
// The line of `.gitignore` that keeps Turborepo's folder out of git.
|
|
161
|
+
const TURBO_IGNORE = '.turbo/';
|
|
162
|
+
// The frame's templates of `turbo.json`, one per form of repository, read in the source.
|
|
163
|
+
const TURBO_PAQUETS = 'templates/turbo/paquets.json';
|
|
164
|
+
const TURBO_SANS_PAQUETS = 'templates/turbo/sans-paquets.json';
|
|
165
|
+
|
|
166
|
+
// Whether `vitest` is declared in the dependencies of the package.json <p> (read at <dossier>) or of
|
|
167
|
+
// one of its packages of the frame (`paquets`: the workspaces as npm maps them, a package out of
|
|
168
|
+
// the frame left out, which Turborepo never tests).
|
|
169
|
+
const declareVitest = (dossier, p) => {
|
|
170
|
+
const a = pkg => Boolean(pkg?.dependencies?.vitest ?? pkg?.devDependencies?.vitest);
|
|
171
|
+
return a(p) || paquets(dossier).some(({ pkg, hors }) => !hors && a(pkg));
|
|
172
|
+
};
|
|
173
|
+
// The version of the Vitest the project's root resolves, as `test:vitest` calls it
|
|
174
|
+
// (`npx --no-install vitest --version`); null when it answers none.
|
|
175
|
+
const vitestDeLaRacine = projet => {
|
|
176
|
+
const r = spawnSync('npx', ['--no-install', 'vitest', '--version'], {
|
|
177
|
+
cwd: projet,
|
|
178
|
+
encoding: 'utf8',
|
|
179
|
+
});
|
|
180
|
+
return r.status === 0 ? (/vitest\/(\d+\.\d+\.\d+\S*)/.exec(r.stdout)?.[1] ?? null) : null;
|
|
181
|
+
};
|
|
182
|
+
const lireJson = (f, defaut) => {
|
|
183
|
+
try {
|
|
184
|
+
return JSON.parse(readFileSync(f, 'utf8'));
|
|
185
|
+
} catch {
|
|
186
|
+
return defaut;
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// Adds `.turbo/` to the `.gitignore` of the project at <projet> when no line ignores it.
|
|
191
|
+
function ignorerTurbo(projet) {
|
|
192
|
+
const f = path.join(projet, '.gitignore');
|
|
193
|
+
const texte = existsSync(f) ? readFileSync(f, 'utf8') : '';
|
|
194
|
+
const lignes = texte.split('\n').map(l => l.trim());
|
|
195
|
+
if (lignes.some(l => l === '.turbo' || l === TURBO_IGNORE || l === '/.turbo' || l === '/.turbo/'))
|
|
196
|
+
return;
|
|
197
|
+
const fin = texte === '' || texte.endsWith('\n') ? '' : '\n';
|
|
198
|
+
writeFileSync(f, `${texte}${fin}${TURBO_IGNORE}\n`);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Writes the `turbo.json` of the project at <projet>, when it has a package.json and no turbo.json,
|
|
202
|
+
// from the source's template of its form (`workspaces` declared: a repository of packages).
|
|
203
|
+
// Returns the template written, or null.
|
|
204
|
+
function poserTurbo(projet, source) {
|
|
205
|
+
const f = path.join(projet, 'turbo.json');
|
|
206
|
+
const pkg = path.join(projet, 'package.json');
|
|
207
|
+
if (existsSync(f) || !existsSync(pkg)) return null;
|
|
208
|
+
const gabarit = lireJson(pkg, {}).workspaces !== undefined ? TURBO_PAQUETS : TURBO_SANS_PAQUETS;
|
|
209
|
+
if (!existsSync(path.join(source, gabarit))) return null;
|
|
210
|
+
copyFileSync(path.join(source, gabarit), f);
|
|
211
|
+
return gabarit;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Declares the frame's package, whose base configuration of Vitest each Vitest configuration
|
|
215
|
+
// imports, in the devDependencies of each package of the frame of the project of packages at
|
|
216
|
+
// <projet> (root package.json <p>) whose scripts hold `test:vitest`, at the range the root declares
|
|
217
|
+
// (else `^<version>` of the package), a declaration the package holds kept; a package out of the
|
|
218
|
+
// frame is not touched. Returns { nom, paquets } (paquets: the packages that received the
|
|
219
|
+
// declaration), or null when nothing changes.
|
|
220
|
+
function poserConfigVitest(projet, p) {
|
|
221
|
+
if (p.workspaces === undefined) return null;
|
|
222
|
+
const testes = paquets(projet).filter(
|
|
223
|
+
({ pkg, hors }) => !hors && typeof pkg.scripts?.['test:vitest'] === 'string'
|
|
224
|
+
);
|
|
225
|
+
const racine = CHAMPS_INSTALLES.map(c => p[c]?.[PAQUET_DU_CADRE]).find(v => v !== undefined);
|
|
226
|
+
const declarants = [];
|
|
227
|
+
for (const { dossier, pkg } of testes) {
|
|
228
|
+
if (CHAMPS_INSTALLES.some(c => pkg[c]?.[PAQUET_DU_CADRE] !== undefined)) continue;
|
|
229
|
+
pkg.devDependencies = Object.fromEntries(
|
|
230
|
+
Object.entries({
|
|
231
|
+
...(pkg.devDependencies ?? {}),
|
|
232
|
+
[PAQUET_DU_CADRE]: racine ?? `^${VERSION}`,
|
|
233
|
+
}).sort(([a], [b]) => a.localeCompare(b, 'en'))
|
|
234
|
+
);
|
|
235
|
+
writeFileSync(path.join(projet, dossier, 'package.json'), `${JSON.stringify(pkg, null, 2)}\n`);
|
|
236
|
+
declarants.push(dossier);
|
|
237
|
+
}
|
|
238
|
+
return declarants.length > 0 ? { nom: PAQUET_DU_CADRE, paquets: declarants } : null;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Writes the frame's status line in the settings of the project at <projet>, when they exist and
|
|
242
|
+
// hold none or the frame's (see the header); their other keys keep their order. Returns whether
|
|
243
|
+
// they changed.
|
|
244
|
+
function reglerLigneEtat(projet) {
|
|
245
|
+
const f = path.join(projet, '.claude', 'settings.json');
|
|
246
|
+
if (!existsSync(f)) return false;
|
|
247
|
+
const reglages = JSON.parse(readFileSync(f, 'utf8'));
|
|
248
|
+
const actuelle = reglages.statusLine;
|
|
249
|
+
const commande = typeof actuelle?.command === 'string' ? actuelle.command : '';
|
|
250
|
+
if (actuelle !== undefined && !commande.includes('ligne-etat')) return false;
|
|
251
|
+
const voulue = { type: 'command', command: LIGNE_ETAT };
|
|
252
|
+
if (JSON.stringify(actuelle) === JSON.stringify(voulue)) return false;
|
|
253
|
+
reglages.statusLine = voulue;
|
|
254
|
+
writeFileSync(f, `${JSON.stringify(reglages, null, 2)}\n`);
|
|
255
|
+
return true;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** The marketplace of the frame's plugin, its source, and the plugin's identifier. */
|
|
259
|
+
export const MARCHE_DU_CADRE = 'roomi-fields';
|
|
260
|
+
export const SOURCE_DU_MARCHE = { source: 'github', repo: 'roomi-fields/claude-plugins' };
|
|
261
|
+
export const GREFFON_DU_CADRE = `grillhouse@${MARCHE_DU_CADRE}`;
|
|
262
|
+
|
|
263
|
+
// Declares in the settings of the project at <projet>, when they exist, the marketplace roomi-fields
|
|
264
|
+
// and enables the frame's plugin (see the header); returns whether they changed.
|
|
265
|
+
function declarerGreffon(projet) {
|
|
266
|
+
const f = path.join(projet, '.claude', 'settings.json');
|
|
267
|
+
if (!existsSync(f)) return false;
|
|
268
|
+
const avant = readFileSync(f, 'utf8');
|
|
269
|
+
const reglages = JSON.parse(avant);
|
|
270
|
+
reglages.extraKnownMarketplaces = {
|
|
271
|
+
...(reglages.extraKnownMarketplaces ?? {}),
|
|
272
|
+
[MARCHE_DU_CADRE]: { source: SOURCE_DU_MARCHE },
|
|
273
|
+
};
|
|
274
|
+
reglages.enabledPlugins = { ...(reglages.enabledPlugins ?? {}), [GREFFON_DU_CADRE]: true };
|
|
275
|
+
if (JSON.stringify(reglages) === JSON.stringify(JSON.parse(avant))) return false;
|
|
276
|
+
writeFileSync(f, `${JSON.stringify(reglages, null, 2)}\n`);
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** The version of the frame the project at <projet> recorded (`grillhouse.version`), or null. */
|
|
281
|
+
export function versionDuProjet(projet) {
|
|
282
|
+
const v = lireJson(path.join(projet, 'package.json'), {})?.grillhouse?.version;
|
|
283
|
+
return typeof v === 'string' ? v : null;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Brings the project at `projet` to the frame's files at `source` (CADRE, scripts/cadre.mjs; a
|
|
288
|
+
* trial's own in the tests). Returns what changed.
|
|
289
|
+
*/
|
|
290
|
+
export function mettreAJour(projet, source) {
|
|
291
|
+
const bilan = {
|
|
292
|
+
dependances: [],
|
|
293
|
+
test: null,
|
|
294
|
+
vitest: null,
|
|
295
|
+
turbo: null,
|
|
296
|
+
configVitest: null,
|
|
297
|
+
gardes: null,
|
|
298
|
+
ligneEtat: false,
|
|
299
|
+
greffon: false,
|
|
300
|
+
};
|
|
301
|
+
const pkg = path.join(projet, 'package.json');
|
|
302
|
+
if (existsSync(pkg)) {
|
|
303
|
+
const p = lireJson(pkg, {});
|
|
304
|
+
p.grillhouse = { ...(p.grillhouse ?? {}), version: VERSION };
|
|
305
|
+
const pkgSource = lireJson(path.join(source, GABARIT), {});
|
|
306
|
+
const gardes = pkgSource.scripts?.['grillhouse:gardes'];
|
|
307
|
+
p.scripts = { ...(p.scripts ?? {}), ...SCRIPTS };
|
|
308
|
+
// The project's `prepare`, which npm runs at its install, places the frame's git hooks first.
|
|
309
|
+
const prepare = typeof p.scripts.prepare === 'string' ? p.scripts.prepare.trim() : '';
|
|
310
|
+
if (!prepare.split(/\s*&&\s*/).includes(PREPARE)) {
|
|
311
|
+
p.scripts.prepare = prepare === '' ? PREPARE : `${PREPARE} && ${prepare}`;
|
|
312
|
+
}
|
|
313
|
+
if (typeof pkgSource.scripts?.api === 'string') {
|
|
314
|
+
p.scripts.api = pkgSource.scripts.api;
|
|
315
|
+
}
|
|
316
|
+
// The frame's tests (Turborepo's tasks) replace a project's own test that runs Vitest.
|
|
317
|
+
const testDuCadre = pkgSource.scripts?.test;
|
|
318
|
+
const testDuProjet = p.scripts.test;
|
|
319
|
+
const vitestExige = pkgSource.devDependencies?.vitest ?? pkgSource.dependencies?.vitest;
|
|
320
|
+
const lanceVitest =
|
|
321
|
+
typeof testDuCadre === 'string' &&
|
|
322
|
+
typeof testDuProjet === 'string' &&
|
|
323
|
+
(testDuProjet === testDuCadre || /\bvitest\b/.test(testDuProjet) || declareVitest(projet, p));
|
|
324
|
+
if (lanceVitest) {
|
|
325
|
+
// The root must resolve the Vitest the frame requires (`test:vitest` calls it).
|
|
326
|
+
const trouvee = typeof vitestExige === 'string' ? vitestDeLaRacine(projet) : null;
|
|
327
|
+
const convient =
|
|
328
|
+
typeof vitestExige !== 'string' ||
|
|
329
|
+
(trouvee !== null && semver.satisfies(trouvee, vitestExige));
|
|
330
|
+
if (!convient) {
|
|
331
|
+
bilan.vitest = { trouvee, exigee: vitestExige };
|
|
332
|
+
} else {
|
|
333
|
+
if (testDuProjet !== testDuCadre)
|
|
334
|
+
bilan.test = { ancien: testDuProjet, nouveau: testDuCadre };
|
|
335
|
+
for (const nom of SCRIPTS_DES_TESTS) {
|
|
336
|
+
const script = pkgSource.scripts?.[nom];
|
|
337
|
+
if (typeof script !== 'string') continue;
|
|
338
|
+
// A repository of packages keeps its tests in its packages, as Turborepo does: its root
|
|
339
|
+
// gets no `test:vitest`, and the frame's own leaves.
|
|
340
|
+
if (p.workspaces !== undefined && nom === 'test:vitest') {
|
|
341
|
+
if (p.scripts[nom] === script) delete p.scripts[nom];
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
p.scripts[nom] = script;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
for (const nom of OUTILS_DU_CADRE) {
|
|
349
|
+
const version = pkgSource.devDependencies?.[nom];
|
|
350
|
+
if (typeof version === 'string' && !CHAMPS_INSTALLES.some(c => p[c]?.[nom] !== undefined)) {
|
|
351
|
+
p.devDependencies = { ...(p.devDependencies ?? {}), [nom]: version };
|
|
352
|
+
bilan.dependances.push(nom);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
if (p.devDependencies) {
|
|
356
|
+
p.devDependencies = Object.fromEntries(
|
|
357
|
+
Object.entries(p.devDependencies).sort(([a], [b]) => a.localeCompare(b, 'en'))
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
if (typeof pkgSource.packageManager === 'string' && typeof p.packageManager !== 'string') {
|
|
361
|
+
p.packageManager = pkgSource.packageManager;
|
|
362
|
+
}
|
|
363
|
+
if (typeof gardes === 'string') p.scripts['grillhouse:gardes'] = gardes;
|
|
364
|
+
// The commands of the known debt of the guards (scripts/dette.mjs) are the frame's.
|
|
365
|
+
for (const nom of ['grillhouse:dette:inscrire', 'grillhouse:dette:elaguer']) {
|
|
366
|
+
const script = pkgSource.scripts?.[nom];
|
|
367
|
+
if (typeof script === 'string') p.scripts[nom] = script;
|
|
368
|
+
}
|
|
369
|
+
// The frame's former wiring of the guards goes: Turborepo plays them (`npm run verifier`).
|
|
370
|
+
const pretest = typeof p.scripts.pretest === 'string' ? p.scripts.pretest.trim() : null;
|
|
371
|
+
if (pretest === APPEL_GARDES) {
|
|
372
|
+
delete p.scripts.pretest;
|
|
373
|
+
} else if (pretest?.startsWith(`${APPEL_GARDES} && `)) {
|
|
374
|
+
p.scripts.pretest = pretest.slice(APPEL_GARDES.length + 4);
|
|
375
|
+
}
|
|
376
|
+
// The project's own guards, which the former integration played, move to `gardes`.
|
|
377
|
+
if (typeof p.scripts[ANCIEN_GARDES_DU_PROJET] === 'string') {
|
|
378
|
+
let reste = p.scripts[ANCIEN_GARDES_DU_PROJET].trim();
|
|
379
|
+
for (
|
|
380
|
+
let tete;
|
|
381
|
+
(tete = ANCIENS_GARDES_D_INTEGRATION.find(
|
|
382
|
+
c => reste === c || reste.startsWith(`${c} && `)
|
|
383
|
+
));
|
|
384
|
+
) {
|
|
385
|
+
reste = reste.slice(tete.length).replace(/^ && /, '');
|
|
386
|
+
}
|
|
387
|
+
if (reste === '') {
|
|
388
|
+
delete p.scripts[ANCIEN_GARDES_DU_PROJET];
|
|
389
|
+
} else if (typeof p.scripts[GARDES_DU_PROJET] !== 'string') {
|
|
390
|
+
delete p.scripts[ANCIEN_GARDES_DU_PROJET];
|
|
391
|
+
p.scripts[GARDES_DU_PROJET] = reste;
|
|
392
|
+
bilan.gardes = { deplace: true };
|
|
393
|
+
} else {
|
|
394
|
+
bilan.gardes = { deplace: false };
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
bilan.configVitest = poserConfigVitest(projet, p);
|
|
398
|
+
writeFileSync(pkg, `${JSON.stringify(p, null, 2)}\n`);
|
|
399
|
+
}
|
|
400
|
+
bilan.turbo = poserTurbo(projet, source);
|
|
401
|
+
bilan.ligneEtat = reglerLigneEtat(projet);
|
|
402
|
+
bilan.greffon = declarerGreffon(projet);
|
|
403
|
+
ignorerTurbo(projet);
|
|
404
|
+
poserCrochets(projet);
|
|
405
|
+
return bilan;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// The frame's generators, subcommands of the project's command, and the file each writes: run in
|
|
409
|
+
// the project after its package.json. A generator without `sortie` (the `turbo.json` of each
|
|
410
|
+
// package) runs in every project whose package.json declares `workspaces`.
|
|
411
|
+
const GENERATEURS = [
|
|
412
|
+
{ commande: 'frontieres', args: ['--ecrire'], sortie: '.dependency-cruiser.cjs' },
|
|
413
|
+
{ commande: 'index-interfaces', args: [], sortie: 'docs/agents/index-des-interfaces.md' },
|
|
414
|
+
{ commande: 'turbo-des-paquets', args: ['--ecrire'], sortie: null },
|
|
415
|
+
];
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Runs each generator whose output the project holds (every one with `tous`, for a project
|
|
419
|
+
* `grillhouse init` creates); returns the lines of failure.
|
|
420
|
+
*/
|
|
421
|
+
export function regenerer(projet, { tous = false } = {}) {
|
|
422
|
+
const lignes = [];
|
|
423
|
+
const grillhouse = path.join(projet, 'node_modules', '.bin', 'grillhouse');
|
|
424
|
+
const aPaquets = lireJson(path.join(projet, 'package.json'), {})?.workspaces !== undefined;
|
|
425
|
+
for (const g of GENERATEURS) {
|
|
426
|
+
const absente = g.sortie !== null && !tous && !existsSync(path.join(projet, g.sortie));
|
|
427
|
+
if (g.sortie === null ? !aPaquets : absente) {
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
if (g.sortie !== null)
|
|
431
|
+
mkdirSync(path.dirname(path.join(projet, g.sortie)), { recursive: true });
|
|
432
|
+
const r = spawnSync(grillhouse, [g.commande, ...g.args], { cwd: projet, encoding: 'utf8' });
|
|
433
|
+
if (r.status !== 0) {
|
|
434
|
+
lignes.push(
|
|
435
|
+
`${g.sortie ?? g.commande} : \`npx grillhouse ${[g.commande, ...g.args].join(' ')}\` échoue ; lance-le après \`npm install\`, qui installe les outils du cadre. ${(r.stderr ?? '').trim()}`.trim()
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return lignes;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/** The report of an update, from the bilan <b> of `mettreAJour`: one line per change. */
|
|
443
|
+
export function rapport(b) {
|
|
444
|
+
const lignes = [];
|
|
445
|
+
if (b.dependances.length > 0) {
|
|
446
|
+
lignes.push(
|
|
447
|
+
`Dépendances de développement ajoutées : ${b.dependances.join(', ')}. \`npm install\` les installe.`
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
if (b.configVitest) {
|
|
451
|
+
const { nom, paquets: declarants } = b.configVitest;
|
|
452
|
+
lignes.push(
|
|
453
|
+
`Configuration de base de Vitest : ${nom} déclaré dans les devDependencies de ${declarants.join(', ')}. \`npm install\` relie le paquet. La configuration Vitest de chaque paquet l'importe par son nom : \`import base from '${CONFIG_VITEST}'\`.`
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
if (b.turbo) {
|
|
457
|
+
lignes.push(
|
|
458
|
+
`turbo.json écrit depuis le gabarit du cadre ${b.turbo} ; il est au projet, la mise à jour n'y touche plus.`
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
if (b.ligneEtat) {
|
|
462
|
+
lignes.push(`Ligne d'état : .claude/settings.json lance \`grillhouse ligne-etat\`.`);
|
|
463
|
+
}
|
|
464
|
+
if (b.greffon) {
|
|
465
|
+
lignes.push(
|
|
466
|
+
`Greffon du cadre : .claude/settings.json déclare la place de marché ${MARCHE_DU_CADRE} (${SOURCE_DU_MARCHE.repo}) et active ${GREFFON_DU_CADRE}.`
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
if (b.gardes?.deplace) {
|
|
470
|
+
lignes.push(
|
|
471
|
+
`Gardes du projet : le script integration:gardes devient gardes, que \`npm run verifier\` joue après la construction (turbo.json).`
|
|
472
|
+
);
|
|
473
|
+
} else if (b.gardes) {
|
|
474
|
+
lignes.push(
|
|
475
|
+
`Gardes du projet : integration:gardes et gardes coexistent ; rien ne joue plus integration:gardes, \`npm run verifier\` joue gardes. Verse l'un dans l'autre.`
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
if (b.vitest) {
|
|
479
|
+
lignes.push(
|
|
480
|
+
`Vitest : la racine résout ${b.vitest.trouvee ?? 'aucune version'}, le cadre exige ${b.vitest.exigee} ; le script test du projet reste tel quel.`
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
if (b.test) {
|
|
484
|
+
lignes.push(
|
|
485
|
+
`Script test : \`${b.test.ancien}\` remplacé par le test du cadre \`${b.test.nouveau}\` (Turborepo : construction, puis tests ; une tâche dont rien n'a bougé est relue de son cache ; \`npm run verifier\` joue les gardes et les tests).`
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
return lignes.map(l => `${l}\n`).join('');
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const USAGE =
|
|
492
|
+
'Usage : npx grillhouse maj met le projet à jour du paquet installé (npm install -D @roomi-fields/grillhouse@<version> d’abord)\n';
|
|
493
|
+
|
|
494
|
+
if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {
|
|
495
|
+
if (process.argv.length > 2) {
|
|
496
|
+
process.stderr.write(USAGE);
|
|
497
|
+
process.exitCode = 2;
|
|
498
|
+
} else {
|
|
499
|
+
const projet = process.cwd();
|
|
500
|
+
const migrations = migrer(projet, versionDuProjet(projet));
|
|
501
|
+
const b = mettreAJour(projet, CADRE);
|
|
502
|
+
process.stdout.write(`Cadre ${VERSION}.\n`);
|
|
503
|
+
for (const { version, nom, lignes } of migrations) {
|
|
504
|
+
process.stdout.write(
|
|
505
|
+
`Migration ${version} (${nom}) :\n${lignes.map(l => `- ${l}\n`).join('')}`
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
process.stdout.write(rapport(b));
|
|
509
|
+
for (const l of [...regenerer(projet), ...installerGreffons(projet)]) {
|
|
510
|
+
process.stderr.write(`${l}\n`);
|
|
511
|
+
process.exitCode = 1;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|