@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,127 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# The removal of a working copy, called by the three places that end one: the agent copy (by
|
|
3
|
+
# scripts/integration/integrer.mjs once its lot is committed, and by the supervisor for a ticket
|
|
4
|
+
# closed without integration), the integration copy (by the same script) and the night copy
|
|
5
|
+
# (scripts/nuit.sh), the last two when their existing copy does not get ready.
|
|
6
|
+
#
|
|
7
|
+
# npx grillhouse retirer-copie [--jetable] [--garder-travail] <copie>
|
|
8
|
+
#
|
|
9
|
+
# - The copy is a path read from the current directory, as git reads it, and must be a copy that
|
|
10
|
+
# `git worktree list` names for the repository of the current directory: the main working tree,
|
|
11
|
+
# an absent path, a plain folder or a submodule's folder is refused, nothing touched.
|
|
12
|
+
# - A copy that carries a submodule is removed too: `git worktree remove` refuses it, so the
|
|
13
|
+
# script reads the copy first, then removes it with `git worktree remove --force`. Only the named
|
|
14
|
+
# copy goes, with the link to a shared submodule (`grillhouse.partage`); the main repository and
|
|
15
|
+
# its submodules (.git/modules) stay as they are.
|
|
16
|
+
# - Without `--jetable`, the reading stands in for what `--force` would erase: a copy that carries
|
|
17
|
+
# local work (a modified tracked file, an untracked file outside the ignored ones, a submodule
|
|
18
|
+
# modified or at another commit, read by `git status --ignore-submodules=none`, whatever the
|
|
19
|
+
# project's `submodule.<name>.ignore` or `diff.ignoreSubmodules`), or whose state does not read,
|
|
20
|
+
# is refused, and stays in place intact. Local work that has entered the branch `main` loses
|
|
21
|
+
# nothing and does not hold the copy: each file of the work (its deletion, or its submodule at a
|
|
22
|
+
# commit with no work in it) holds the content and the mode that a commit of `main` after the
|
|
23
|
+
# copy's base gave that path; a content `main` held before the base is not work that entered. The
|
|
24
|
+
# refusal names the work that has not entered. The agent copy's removal passes no option.
|
|
25
|
+
# - With `--jetable`, passed only by the integration and the night for their own copy, local work
|
|
26
|
+
# and an unreadable state do not hold the removal.
|
|
27
|
+
# - A locked copy (`git worktree lock`) is refused in every case.
|
|
28
|
+
# - The copy removed, the working directory of its ticket's circuit, `<copie>.travail` beside it
|
|
29
|
+
# (scripts/circuit.mjs), goes with it, as the workspace of a pipeline goes at its end: a ticket
|
|
30
|
+
# closed without integration (a final stop, an abandon) loses both by this one command. With
|
|
31
|
+
# `--garder-travail`, passed only by the integration, it stays: the circuit still runs, and the
|
|
32
|
+
# queue removes it at the ticket's closure (scripts/integration/file.mjs). A copy refused keeps
|
|
33
|
+
# it.
|
|
34
|
+
# - Exit 0 removed, 1 refused (the first line on stderr names the copy and what holds it), 2 a
|
|
35
|
+
# malformed call (the usage on stderr).
|
|
36
|
+
set -uo pipefail
|
|
37
|
+
jetable=0
|
|
38
|
+
garder=0
|
|
39
|
+
while [ $# -gt 0 ]; do
|
|
40
|
+
case $1 in
|
|
41
|
+
--jetable) jetable=1 ;;
|
|
42
|
+
--garder-travail) garder=1 ;;
|
|
43
|
+
*) break ;;
|
|
44
|
+
esac
|
|
45
|
+
shift
|
|
46
|
+
done
|
|
47
|
+
[ $# -eq 1 ] && [ "${1#-}" = "$1" ] || {
|
|
48
|
+
echo "usage: retirer-copie.sh [--jetable] [--garder-travail] <copie>" >&2
|
|
49
|
+
exit 2
|
|
50
|
+
}
|
|
51
|
+
copie=$1
|
|
52
|
+
|
|
53
|
+
refuser() {
|
|
54
|
+
echo "$1" >&2
|
|
55
|
+
shift
|
|
56
|
+
[ $# -eq 0 ] || echo "$*" >&2
|
|
57
|
+
exit 1
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
[ -n "$copie" ] && [ -d "$copie" ] || refuser "la copie $copie n'existe pas : rien n'est retiré."
|
|
61
|
+
chemin=$(cd "$copie" && pwd -P) || refuser "la copie $copie ne se lit pas : rien n'est retiré."
|
|
62
|
+
liste=$(git worktree list --porcelain 2>&1) ||
|
|
63
|
+
refuser "la liste des copies ne se lit pas pour retirer $copie : rien n'est retiré." "$liste"
|
|
64
|
+
|
|
65
|
+
# The list is one block per copy, separated by a blank line: the first block is the main working
|
|
66
|
+
# tree, never removable; a `locked` line marks a locked copy. `trouvee` is the verdict of the block
|
|
67
|
+
# of the copy, printed once (`dit`: `exit` runs the END rule, which must not read the same block
|
|
68
|
+
# again).
|
|
69
|
+
trouvee=$(printf '%s\n' "$liste" | awk -v cible="$chemin" '
|
|
70
|
+
function fin() {
|
|
71
|
+
if (n > 1 && dossier != "") {
|
|
72
|
+
cmd = "cd \"" dossier "\" 2>/dev/null && pwd -P"
|
|
73
|
+
cmd | getline reel; close(cmd)
|
|
74
|
+
if (reel == cible) { print (verrou ? "verrouillee" : "libre"); dit = 1; exit }
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/^worktree / { fin(); n++; dossier = substr($0, 10); verrou = 0; reel = ""; next }
|
|
78
|
+
/^locked/ { verrou = 1 }
|
|
79
|
+
END { if (!dit) fin() }
|
|
80
|
+
')
|
|
81
|
+
[ -n "$trouvee" ] || refuser "$copie n'est pas une copie inscrite de ce dépôt (git worktree list) : rien n'est retiré."
|
|
82
|
+
[ "$trouvee" != verrouillee ] || refuser "la copie $copie est verrouillée (git worktree lock) : elle n'est pas retirée."
|
|
83
|
+
|
|
84
|
+
# Whether the work at path $1 of the copy has entered `main`: what the copy holds there (a file's
|
|
85
|
+
# blob and mode, a submodule's commit once its own state is clean, zeros for a deleted path) is
|
|
86
|
+
# what a commit of `main` after the copy's base (`HEAD..refs/heads/main` in the copy) gave that
|
|
87
|
+
# path, mode included, as git judges a branch already merged.
|
|
88
|
+
est_entre() {
|
|
89
|
+
local p=$1 id mode
|
|
90
|
+
if [ -e "$chemin/$p/.git" ]; then
|
|
91
|
+
[ -z "$(git -C "$chemin/$p" status --porcelain --ignore-submodules=none 2>/dev/null)" ] ||
|
|
92
|
+
return 1
|
|
93
|
+
id=$(git -C "$chemin/$p" rev-parse HEAD 2>/dev/null) || return 1
|
|
94
|
+
mode=160000
|
|
95
|
+
elif [ -L "$chemin/$p" ]; then
|
|
96
|
+
id=$(git -C "$chemin" hash-object --no-filters -- "$p" 2>/dev/null) || return 1
|
|
97
|
+
mode=120000
|
|
98
|
+
elif [ -e "$chemin/$p" ]; then
|
|
99
|
+
id=$(git -C "$chemin" hash-object -- "$p" 2>/dev/null) || return 1
|
|
100
|
+
if [ -x "$chemin/$p" ]; then mode=100755; else mode=100644; fi
|
|
101
|
+
else
|
|
102
|
+
id=0
|
|
103
|
+
mode=000000
|
|
104
|
+
fi
|
|
105
|
+
git -C "$chemin" log HEAD..refs/heads/main --no-renames --format= --raw --no-abbrev -- "$p" \
|
|
106
|
+
2>/dev/null |
|
|
107
|
+
awk -v id="$id" -v mode="$mode" '
|
|
108
|
+
$2 == mode && ($4 == id || (id == 0 && $4 ~ /^0+$/)) { ok = 1 }
|
|
109
|
+
END { exit !ok }'
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if [ "$jetable" -eq 0 ]; then
|
|
113
|
+
etat=$(git -C "$chemin" status --porcelain -z -uall --no-renames --ignore-submodules=none 2>&1 |
|
|
114
|
+
tr '\0' '\n') ||
|
|
115
|
+
refuser "l'état de la copie $copie ne se lit pas : elle n'est pas retirée." "$etat"
|
|
116
|
+
restant=
|
|
117
|
+
while IFS= read -r ligne; do
|
|
118
|
+
[ -n "$ligne" ] || continue
|
|
119
|
+
est_entre "${ligne:3}" || restant+="${restant:+$'\n'}$ligne"
|
|
120
|
+
done <<<"$etat"
|
|
121
|
+
[ -z "$restant" ] ||
|
|
122
|
+
refuser "la copie $copie porte du travail local : elle n'est pas retirée." "$restant"
|
|
123
|
+
fi
|
|
124
|
+
|
|
125
|
+
sortie=$(git worktree remove --force "$chemin" 2>&1) ||
|
|
126
|
+
refuser "la copie $copie ne se retire pas :" "$sortie"
|
|
127
|
+
[ "$garder" -eq 1 ] || rm -rf -- "$chemin.travail"
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# SessionStart hook: tells the session what the project still lacks before work can start.
|
|
3
|
+
# Silent once the project is set up and initialised. It changes nothing, save the lock of the
|
|
4
|
+
# supervisor below.
|
|
5
|
+
#
|
|
6
|
+
# The project's own check: `grillhouse.demarrage` in the package.json of the root names an npm
|
|
7
|
+
# script (`npm pkg set grillhouse.demarrage=<script>`). The session start plays
|
|
8
|
+
# `npm run --silent <script>` at the root; each non-blank line it writes joins the list « Pas encore
|
|
9
|
+
# renseignés », as an item to deal with. The script has 10 s: past them its whole process group is
|
|
10
|
+
# stopped. A failure (non-zero code) or a stop adds a line that says so, after the lines already
|
|
11
|
+
# written; the session start goes on. Without the key (or with null), nothing runs; a value that is
|
|
12
|
+
# not a non-blank string runs nothing and adds a line that says so. An agent's copy does not play it.
|
|
13
|
+
#
|
|
14
|
+
# The frame's plugins: scripts/greffons.mjs --absents reads `.claude/settings.json` and
|
|
15
|
+
# `claude plugin list --json` (each call stopped after 5 s). Each declared plugin not installed for
|
|
16
|
+
# the project joins « Manque » as « le greffon <id> », the missing claude command as « la commande
|
|
17
|
+
# claude » (the line gives `npx grillhouse maj`, which installs them), and a reading that fails
|
|
18
|
+
# or is stopped as « la lecture des greffons … ». The same reading names the frame's mod installed
|
|
19
|
+
# for the project at another version than the package @roomi-fields/grillhouse, both released under
|
|
20
|
+
# one version, with the command that aligns the one behind. An agent's copy does not read them.
|
|
21
|
+
#
|
|
22
|
+
# One supervisor per project (scripts/superviseur.mjs): the session takes the lock of its
|
|
23
|
+
# repository; a second session, while the first one lives, receives first the section « Un autre
|
|
24
|
+
# superviseur tient ce dépôt », which asks it to say so to the owner and to launch no circuit. An
|
|
25
|
+
# agent's copy and a session of a role the circuit launches take nothing and receive nothing.
|
|
26
|
+
#
|
|
27
|
+
# A session of a role agent of the frame (`claude --agent <rôle>`, the circuit's sessions: the
|
|
28
|
+
# hook's input names it, `agent_type`, bare or as `grillhouse:<rôle>`, and the frame's plugin
|
|
29
|
+
# defines it, `$CLAUDE_PLUGIN_ROOT/agents/<rôle>.md`, the plugin whose hook runs this script) receives nothing:
|
|
30
|
+
# what the project lacks is the supervisor's to deal with, and in an envelope the documents of the
|
|
31
|
+
# hidden packages read as absent.
|
|
32
|
+
set -uo pipefail
|
|
33
|
+
|
|
34
|
+
# The project is Claude Code's, else the working directory; the frame's scripts are this one's
|
|
35
|
+
# neighbours.
|
|
36
|
+
root="${CLAUDE_PROJECT_DIR:-$PWD}"
|
|
37
|
+
ici="$(cd "$(dirname "$0")" && pwd)"
|
|
38
|
+
entree=
|
|
39
|
+
[ -t 0 ] || entree="$(cat)"
|
|
40
|
+
role="$(printf '%s' "$entree" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{try{const a=JSON.parse(s).agent_type;if(typeof a==="string"&&/^[A-Za-z0-9_:-]+$/.test(a))process.stdout.write(a)}catch{}})' 2>/dev/null)"
|
|
41
|
+
role="${role#grillhouse:}"
|
|
42
|
+
[ -n "$role" ] && [ -n "${CLAUDE_PLUGIN_ROOT:-}" ] && [ -f "$CLAUDE_PLUGIN_ROOT/agents/$role.md" ] && exit 0
|
|
43
|
+
if [ -f "$root/.claude/template" ]; then
|
|
44
|
+
echo "## Gabarit Roomi's Grillhouse"
|
|
45
|
+
echo "Ce dossier est le gabarit du framework, pas un projet : il ne s'initialise pas. Un projet neuf se crée par \`npx @roomi-fields/grillhouse init <chemin> [préfixe]\` ; sa première séance propose le grill d'initialisation."
|
|
46
|
+
exit 0
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# One supervisor per project: the lock of the repository, or the section that says who holds it.
|
|
50
|
+
superviseur="$(node "$ici/superviseur.mjs" "$root" 2>/dev/null)"
|
|
51
|
+
[ -z "$superviseur" ] || echo "$superviseur"
|
|
52
|
+
|
|
53
|
+
missing=()
|
|
54
|
+
|
|
55
|
+
command -v bd >/dev/null || missing+=("l'outil de tickets bd (Beads : \`npm install -g @beads/bd\`)")
|
|
56
|
+
[ -d "$root/.beads" ] || missing+=("le magasin de tickets (Beads : \`bd init\`)")
|
|
57
|
+
# The index of CodeGraph is for the roles that see the whole repository: an agent's copy (a git
|
|
58
|
+
# worktree under .claude/worktrees/) is asked for neither the tool nor the index.
|
|
59
|
+
copie_agent=
|
|
60
|
+
case "$root" in
|
|
61
|
+
*/.claude/worktrees/*)
|
|
62
|
+
[ "$(git -C "$root" rev-parse --git-dir 2>/dev/null)" != "$(git -C "$root" rev-parse --git-common-dir 2>/dev/null)" ] && copie_agent=1
|
|
63
|
+
;;
|
|
64
|
+
esac
|
|
65
|
+
if [ -z "$copie_agent" ]; then
|
|
66
|
+
command -v codegraph >/dev/null || missing+=("l'outil CodeGraph (\`npm install -g @colbymchenry/codegraph\`)")
|
|
67
|
+
[ -d "$root/.codegraph" ] || missing+=("l'index CodeGraph du code (\`codegraph init .\`)")
|
|
68
|
+
fi
|
|
69
|
+
[ ! -f "$root/package.json" ] || [ -d "$root/node_modules" ] || missing+=("les dépendances npm (\`npm install\`)")
|
|
70
|
+
# The frame's plugins not installed for the project (scripts/greffons.mjs): the plugins are the
|
|
71
|
+
# machine's installation for the project, so an agent's copy does not read them.
|
|
72
|
+
if [ -z "$copie_agent" ]; then
|
|
73
|
+
while IFS= read -r ligne; do
|
|
74
|
+
case "$ligne" in
|
|
75
|
+
claude) missing+=("la commande claude") ;;
|
|
76
|
+
"greffon "*) missing+=("le greffon ${ligne#greffon }") ;;
|
|
77
|
+
"lecture "*) missing+=("la lecture des greffons : ${ligne#lecture }") ;;
|
|
78
|
+
"ecart "*)
|
|
79
|
+
read -r _ e_id e_greffon e_paquet e_commande <<< "$ligne"
|
|
80
|
+
ecart_versions="Le greffon $e_id est en $e_greffon, le paquet @roomi-fields/grillhouse en $e_paquet : les deux sortent ensemble, sous une même version. Aligne-les : \`$e_commande\`."
|
|
81
|
+
;;
|
|
82
|
+
esac
|
|
83
|
+
done < <(timeout -k 1 8 node "$ici/greffons.mjs" --absents "$root" 2>/dev/null)
|
|
84
|
+
fi
|
|
85
|
+
# The frame's git hooks absent from the folder git reads (scripts/crochets.mjs), each named.
|
|
86
|
+
while IFS= read -r crochet; do
|
|
87
|
+
[ -n "$crochet" ] && missing+=("le crochet git $crochet du cadre")
|
|
88
|
+
done < <(node "$ici/crochets.mjs" --absents "$root" 2>/dev/null)
|
|
89
|
+
command -v bwrap >/dev/null || missing+=("bubblewrap (bwrap), l'enveloppe des agents : sudo apt install bubblewrap")
|
|
90
|
+
|
|
91
|
+
# The key elements the skills read; each one still empty is proposed for a grill.
|
|
92
|
+
pending=()
|
|
93
|
+
charter="$root/CLAUDE.md"
|
|
94
|
+
if [ ! -f "$charter" ]; then
|
|
95
|
+
pending+=("la charte (CLAUDE.md)")
|
|
96
|
+
else
|
|
97
|
+
# an unfilled field of the charter template: <…> outside code spans
|
|
98
|
+
node -e 'const t=require("fs").readFileSync(process.argv[1],"utf8").replace(/`[^`]*`/g,"");process.exit(/<[^<>\n]+>/.test(t)?0:1)' "$charter" \
|
|
99
|
+
&& pending+=("les champs <…> encore vides de la charte (CLAUDE.md)")
|
|
100
|
+
# the arbitration criteria: a heading about arbitration in the charter or a reference document
|
|
101
|
+
grep -qiE '^#+ .*(arbitr)' "$charter" "$root"/docs/*.md 2>/dev/null \
|
|
102
|
+
|| pending+=("les critères d'arbitrage (section « Comment on arbitre » de CLAUDE.md : la référence mature du domaine, l'existant à reprendre, les exigences du domaine)")
|
|
103
|
+
fi
|
|
104
|
+
# The packages out of the frame: docs/agents/hors-cadre.txt ("<package> <reason>", e.g. a frozen
|
|
105
|
+
# version), read by scripts/paquets.mjs as every check reads it; Turborepo plays none of
|
|
106
|
+
# its tasks (scripts/turbo-des-paquets.mjs). Line 1: the names listed;
|
|
107
|
+
# line 2: the names that no directory under packages/ or src/ bears.
|
|
108
|
+
{ read -r hors; read -r inconnus; } < <(node --input-type=module -e '
|
|
109
|
+
const { pathToFileURL } = await import("node:url");
|
|
110
|
+
const { horsCadre } = await import(pathToFileURL(process.argv[1]).href);
|
|
111
|
+
const { horsCadreInconnus } = await import(pathToFileURL(process.argv[2]).href);
|
|
112
|
+
process.stdout.write(`${[...horsCadre(process.argv[3])].join(" ")}\n${horsCadreInconnus(process.argv[3]).join(" ")}\n`);
|
|
113
|
+
' "$ici/paquets.mjs" "$ici/consommateurs.mjs" "$root" 2>/dev/null)
|
|
114
|
+
# One package: docs/ at the root. Several: each package under packages/ has its own three documents,
|
|
115
|
+
# unless it is out of the frame.
|
|
116
|
+
if ls -d "$root"/packages/*/ >/dev/null 2>&1; then
|
|
117
|
+
for pkg in "$root"/packages/*/; do
|
|
118
|
+
name="$(basename "$pkg")"
|
|
119
|
+
case " ${hors:-} " in *" $name "*) continue ;; esac
|
|
120
|
+
for doc in ARCHITECTURE CADRE INTERFACE; do
|
|
121
|
+
[ -f "$pkg/docs/$doc.md" ] || pending+=("packages/$name/docs/$doc.md")
|
|
122
|
+
done
|
|
123
|
+
done
|
|
124
|
+
else
|
|
125
|
+
for doc in ARCHITECTURE CADRE INTERFACE; do
|
|
126
|
+
[ -f "$root/docs/$doc.md" ] || pending+=("docs/$doc.md")
|
|
127
|
+
done
|
|
128
|
+
fi
|
|
129
|
+
[ -f "$root/CONTEXT.md" ] || pending+=("le lexique du domaine (CONTEXT.md)")
|
|
130
|
+
|
|
131
|
+
# The project's own check (grillhouse.demarrage, described in the header), outside an agent's copy.
|
|
132
|
+
# The reading gives « script<TAB>name », « invalide<TAB>value as JSON », or nothing.
|
|
133
|
+
lecture=
|
|
134
|
+
[ -n "$copie_agent" ] || lecture="$(node -e '
|
|
135
|
+
const v = require(process.argv[1]).grillhouse?.demarrage;
|
|
136
|
+
if (v == null) process.exit(0);
|
|
137
|
+
if (typeof v === "string" && v.trim()) process.stdout.write("script\t" + v);
|
|
138
|
+
else process.stdout.write("invalide\t" + JSON.stringify(v));
|
|
139
|
+
' "$root/package.json" 2>/dev/null)"
|
|
140
|
+
demarrage=
|
|
141
|
+
case "$lecture" in
|
|
142
|
+
script$'\t'*) demarrage="${lecture#script$'\t'}" ;;
|
|
143
|
+
invalide$'\t'*) pending+=("la clé grillhouse.demarrage vaut ${lecture#invalide$'\t'}, pas un nom de script npm : rien n'est lancé") ;;
|
|
144
|
+
esac
|
|
145
|
+
if [ -n "$demarrage" ]; then
|
|
146
|
+
# timeout signals the script's whole process group, so a child that keeps the output open stops too.
|
|
147
|
+
sortie="$(cd "$root" && timeout -k 2 10 npm run --silent "$demarrage" 2>/dev/null)"
|
|
148
|
+
code=$?
|
|
149
|
+
while IFS= read -r ligne; do
|
|
150
|
+
ligne="${ligne%$'\r'}"
|
|
151
|
+
[ -n "${ligne//[[:space:]]/}" ] && pending+=("$ligne")
|
|
152
|
+
done <<< "$sortie"
|
|
153
|
+
if [ "$code" -eq 124 ] || [ "$code" -eq 137 ]; then
|
|
154
|
+
pending+=("le contrôle d'ouverture \`npm run $demarrage\` (grillhouse.demarrage) a été interrompu après 10 s")
|
|
155
|
+
elif [ "$code" -ne 0 ]; then
|
|
156
|
+
pending+=("le contrôle d'ouverture \`npm run $demarrage\` (grillhouse.demarrage) échoue (code $code)")
|
|
157
|
+
fi
|
|
158
|
+
fi
|
|
159
|
+
|
|
160
|
+
# The agents run the tests by `npm test`; the integration, the queue and the night run the guards
|
|
161
|
+
# and the tests by `npm run verifier` (Turborepo): two npm scripts to wire.
|
|
162
|
+
wiring=()
|
|
163
|
+
if [ -f "$root/package.json" ]; then
|
|
164
|
+
for s in test verifier; do
|
|
165
|
+
node -e 'const p=require(process.argv[1]);process.exit(p.scripts&&p.scripts[process.argv[2]]?0:1)' \
|
|
166
|
+
"$root/package.json" "$s" 2>/dev/null || wiring+=("$s")
|
|
167
|
+
done
|
|
168
|
+
fi
|
|
169
|
+
|
|
170
|
+
# Once the architecture is written, the night (the full pass played for real, the night tests and
|
|
171
|
+
# the time budget benches) needs its line in the crontab.
|
|
172
|
+
if [ -f "$root/docs/ARCHITECTURE.md" ] && ! crontab -l 2>/dev/null | grep -qF "cd $root && node_modules/.bin/grillhouse nuit"; then
|
|
173
|
+
nuit="0 3 * * * cd $root && node_modules/.bin/grillhouse nuit"
|
|
174
|
+
fi
|
|
175
|
+
|
|
176
|
+
# An epic is a chantier, at the root: one with a mother is a mother ticket typed by mistake.
|
|
177
|
+
epopees="$(cd "$root" && bd list --type epic --all --json --limit 0 2>/dev/null | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{try{process.stdout.write(JSON.parse(s).filter(t=>t.parent).map(t=>t.id).join(" "))}catch{}})')"
|
|
178
|
+
|
|
179
|
+
[ ${#missing[@]} -eq 0 ] && [ ${#pending[@]} -eq 0 ] && [ ${#wiring[@]} -eq 0 ] && [ -z "${nuit:-}" ] && [ -z "$epopees" ] && [ -z "${inconnus:-}" ] && [ -z "${ecart_versions:-}" ] && exit 0
|
|
180
|
+
|
|
181
|
+
echo "## Éléments du projet à définir (Roomi's Grillhouse)"
|
|
182
|
+
if [ ${#missing[@]} -gt 0 ]; then
|
|
183
|
+
echo "Manque : $(IFS=';'; echo "${missing[*]}" | sed "s/;/, /g"). Installe-les toi-même avant tout autre geste, sans invite, chacun par la commande qu'il nomme ; \`npx grillhouse maj\` installe les greffons et les crochets git du cadre."
|
|
184
|
+
fi
|
|
185
|
+
if [ ${#pending[@]} -gt 0 ]; then
|
|
186
|
+
echo "Pas encore renseignés :"
|
|
187
|
+
for p in "${pending[@]}"; do echo "- $p"; done
|
|
188
|
+
echo "Dès ta première réponse, propose au responsable de les griller, chacun avec ce que tu recommandes pour ce projet (compétence \`grill\`). Il peut accepter, en choisir un, ou reporter ; tant qu'un élément reste vide, cette proposition revient à chaque séance."
|
|
189
|
+
fi
|
|
190
|
+
if [ ${#wiring[@]} -gt 0 ]; then
|
|
191
|
+
echo "À brancher : $(IFS=' '; echo "${wiring[*]}") dans le package.json de la racine. L'intégration (\`npx grillhouse integrer\`, contrat en tête de son script) et la nuit lancent \`npm run verifier\` (Turborepo, \`turbo.json\` : construction, puis gardes et tests), les agents \`npm test\` ; sans eux, l'intégration refuse chaque lot. Un projet testé par Vitest les reçoit de \`npm run grillhouse:maj\` ; sinon, écris-les pour ses tests."
|
|
192
|
+
fi
|
|
193
|
+
if [ -n "${nuit:-}" ]; then
|
|
194
|
+
echo "La nuit n'est pas programmée : elle rejoue pour de vrai la passe complète, puis les tests de nuit et les bancs de budget, que le jour ne joue pas (\`npx grillhouse nuit\`). Propose au responsable d'ajouter à la table des tâches du poste (\`crontab -e\`) : \`$nuit\`."
|
|
195
|
+
fi
|
|
196
|
+
if [ -n "$epopees" ]; then
|
|
197
|
+
echo "Épopées qui ont une mère : $epopees. Une épopée est un chantier, à la racine ; une mère sous un chantier est une tâche (\`bd update <id> -t task\`, docs/agents/issue-tracker.md, « Numéros et titres »)."
|
|
198
|
+
fi
|
|
199
|
+
if [ -n "${inconnus:-}" ]; then
|
|
200
|
+
echo "docs/agents/hors-cadre.txt nomme un dossier absent de packages/ et de src/ : $inconnus. Corrige la ligne (faute de frappe) ou retire-la : un nom sans dossier n'écarte rien."
|
|
201
|
+
fi
|
|
202
|
+
[ -n "${ecart_versions:-}" ] && echo "$ecart_versions"
|
|
203
|
+
exit 0
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// A release of Grillhouse, its package npm, which carries the frame's plugin: Grillhouse's own
|
|
3
|
+
// script, which no project runs (`grillhouse init` installs the template of a project alone).
|
|
4
|
+
//
|
|
5
|
+
// npx grillhouse sortie <x.y.z> the release's files: the package's package.json, CHANGELOG.md
|
|
6
|
+
// npx grillhouse sortie --etiqueter the annotated tag v<version> on HEAD, the release commit
|
|
7
|
+
// npx grillhouse sortie --verifier-etiquette <v> exits 0 when the tag <v> names the version of the package
|
|
8
|
+
// npx grillhouse sortie --entree-du-marche the entry of the plugin in the marketplace roomi-fields
|
|
9
|
+
//
|
|
10
|
+
// - The version is the `version` of the package's `packages/grillhouse/package.json`, which npm
|
|
11
|
+
// publishes; the plugin the package carries takes it when the package is packed
|
|
12
|
+
// (scripts/cadre.mjs), so the frame has one version. Semantic versioning in 0.x: a change that
|
|
13
|
+
// obliges a project to act raises the minor, a fix the patch.
|
|
14
|
+
// - Preparing writes the version given, above the current one, in the package's package.json, its
|
|
15
|
+
// other fields kept, and dates the changelog: « ## [Unreleased] » becomes « ## [<x.y.z>] - <today> » and an
|
|
16
|
+
// empty « ## [Unreleased] » opens above it. A release with nothing under « [Unreleased] » is
|
|
17
|
+
// refused. Nothing is written when a check fails.
|
|
18
|
+
// - Tagging creates the annotated tag `v<version>` on HEAD, once the release commit is made and
|
|
19
|
+
// the working tree holds no change. Nothing is pushed: the owner validates the push, whose
|
|
20
|
+
// command the script names; `.github/workflows/release.yml` then plays the CI on the tagged
|
|
21
|
+
// commit and, once it is green, checks the tag (`--verifier-etiquette`), publishes the package
|
|
22
|
+
// on the npm registry (the repository's secret NPM_TOKEN) and the release on GitHub.
|
|
23
|
+
// - The marketplace roomi-fields (GitHub, roomi-fields/claude-plugins, a repository of its own)
|
|
24
|
+
// lists the plugin by its source npm, ENTREE_DU_MARCHE: the package, without a subdirectory (a
|
|
25
|
+
// source npm has none: the package's root is the plugin's) and without a version, so the machine
|
|
26
|
+
// installs the version published last. `--entree-du-marche` prints it; the owner sets it in that
|
|
27
|
+
// repository once the package is published, a public gesture this script does not make.
|
|
28
|
+
import { execFileSync } from 'node:child_process';
|
|
29
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
30
|
+
import path from 'node:path';
|
|
31
|
+
import { pathToFileURL } from 'node:url';
|
|
32
|
+
|
|
33
|
+
const PAQUET = 'packages/grillhouse/package.json';
|
|
34
|
+
|
|
35
|
+
/** The entry of the frame's plugin in the marketplace roomi-fields. */
|
|
36
|
+
export const ENTREE_DU_MARCHE = {
|
|
37
|
+
name: 'grillhouse',
|
|
38
|
+
source: { source: 'npm', package: '@roomi-fields/grillhouse' },
|
|
39
|
+
};
|
|
40
|
+
const CHANGELOG = 'CHANGELOG.md';
|
|
41
|
+
const OUVERTE = '## [Unreleased]\n';
|
|
42
|
+
const SEMVER = /^(\d+)\.(\d+)\.(\d+)$/;
|
|
43
|
+
|
|
44
|
+
const lire = (racine, f) => readFileSync(path.join(racine, f), 'utf8');
|
|
45
|
+
|
|
46
|
+
// The version of Grillhouse at <racine>: its package's.
|
|
47
|
+
export function versionDuCadre(racine) {
|
|
48
|
+
return JSON.parse(lire(racine, PAQUET)).version;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Refuses a tag <etiquette> other than v<version> of the package at <racine>.
|
|
52
|
+
export function verifierEtiquette(racine, etiquette) {
|
|
53
|
+
const v = versionDuCadre(racine);
|
|
54
|
+
if (etiquette !== `v${v}`) {
|
|
55
|
+
throw new Error(`L'étiquette ${etiquette} ne nomme pas la version du paquet, ${v}.`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const rang = v => SEMVER.exec(v)?.slice(1).map(Number) ?? null;
|
|
60
|
+
const audessus = (a, b) => {
|
|
61
|
+
for (let i = 0; i < 3; i++) {
|
|
62
|
+
if (a[i] !== b[i]) {
|
|
63
|
+
return a[i] > b[i];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return false;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// Writes the release <version> of Grillhouse at <racine>, dated <date> (YYYY-MM-DD).
|
|
70
|
+
export function preparerSortie(racine, version, date) {
|
|
71
|
+
const actuelle = versionDuCadre(racine);
|
|
72
|
+
const neuve = rang(version);
|
|
73
|
+
if (!neuve) {
|
|
74
|
+
throw new Error(`« ${version} » n'est pas une version x.y.z.`);
|
|
75
|
+
}
|
|
76
|
+
if (!audessus(neuve, rang(actuelle))) {
|
|
77
|
+
throw new Error(`${version} n'est pas au-dessus de la version actuelle, ${actuelle}.`);
|
|
78
|
+
}
|
|
79
|
+
const journal = lire(racine, CHANGELOG);
|
|
80
|
+
const debut = journal.indexOf(`\n${OUVERTE}`);
|
|
81
|
+
if (debut < 0) {
|
|
82
|
+
throw new Error(`${CHANGELOG} n'a pas de section « [Unreleased] ».`);
|
|
83
|
+
}
|
|
84
|
+
const corps = journal.slice(debut + 1 + OUVERTE.length);
|
|
85
|
+
const fin = corps.search(/^## \[/m);
|
|
86
|
+
if ((fin < 0 ? corps : corps.slice(0, fin)).trim() === '') {
|
|
87
|
+
throw new Error(`La section « [Unreleased] » de ${CHANGELOG} est vide : rien à sortir.`);
|
|
88
|
+
}
|
|
89
|
+
const paquet = JSON.parse(lire(racine, PAQUET));
|
|
90
|
+
paquet.version = version;
|
|
91
|
+
writeFileSync(path.join(racine, PAQUET), `${JSON.stringify(paquet, null, 2)}\n`);
|
|
92
|
+
writeFileSync(
|
|
93
|
+
path.join(racine, CHANGELOG),
|
|
94
|
+
`${journal.slice(0, debut + 1)}${OUVERTE}\n## [${version}] - ${date}\n${corps}`
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Creates the annotated tag of the release at <racine> on HEAD, and returns its name.
|
|
99
|
+
export function etiqueter(racine) {
|
|
100
|
+
const git = (...args) => execFileSync('git', ['-C', racine, ...args], { encoding: 'utf8' });
|
|
101
|
+
const etiquette = `v${versionDuCadre(racine)}`;
|
|
102
|
+
if (git('status', '--porcelain').trim() !== '') {
|
|
103
|
+
throw new Error(
|
|
104
|
+
"L'arbre de travail porte des changements : le commit de sortie vient d'abord."
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
if (git('tag', '--list', etiquette).trim() !== '') {
|
|
108
|
+
throw new Error(`L'étiquette ${etiquette} existe déjà.`);
|
|
109
|
+
}
|
|
110
|
+
git('tag', '-a', etiquette, '-m', `Roomi's Grillhouse ${etiquette.slice(1)}`);
|
|
111
|
+
return etiquette;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const USAGE =
|
|
115
|
+
'Usage : npx grillhouse sortie <x.y.z> prépare la sortie (package.json du paquet, CHANGELOG.md)\n' +
|
|
116
|
+
" npx grillhouse sortie --etiqueter pose l'étiquette v<version> sur le commit de sortie\n" +
|
|
117
|
+
" npx grillhouse sortie --verifier-etiquette <v> vérifie que l'étiquette <v> nomme la version du paquet\n" +
|
|
118
|
+
' npx grillhouse sortie --entree-du-marche écrit l’entrée du greffon dans la place de marché roomi-fields\n';
|
|
119
|
+
|
|
120
|
+
// Today, in the local calendar, as YYYY-MM-DD.
|
|
121
|
+
const aujourdhui = () => {
|
|
122
|
+
const d = new Date();
|
|
123
|
+
const n = x => String(x).padStart(2, '0');
|
|
124
|
+
return `${d.getFullYear()}-${n(d.getMonth() + 1)}-${n(d.getDate())}`;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {
|
|
128
|
+
const args = process.argv.slice(2);
|
|
129
|
+
const racine = process.cwd();
|
|
130
|
+
try {
|
|
131
|
+
if (args.length === 1 && args[0] === '--etiqueter') {
|
|
132
|
+
const e = etiqueter(racine);
|
|
133
|
+
process.stdout.write(
|
|
134
|
+
`Étiquette ${e} posée sur HEAD, en local. La poussée, que valide le responsable : git push origin ${e} ` +
|
|
135
|
+
'(la CI joue alors le commit, puis publie le paquet sur npm)\n'
|
|
136
|
+
);
|
|
137
|
+
} else if (args.length === 1 && args[0] === '--entree-du-marche') {
|
|
138
|
+
process.stdout.write(`${JSON.stringify(ENTREE_DU_MARCHE, null, 2)}\n`);
|
|
139
|
+
} else if (args.length === 2 && args[0] === '--verifier-etiquette') {
|
|
140
|
+
verifierEtiquette(racine, args[1]);
|
|
141
|
+
} else if (args.length === 1 && !args[0].startsWith('-')) {
|
|
142
|
+
preparerSortie(racine, args[0], aujourdhui());
|
|
143
|
+
process.stdout.write(
|
|
144
|
+
`Sortie ${args[0]} préparée : ${PAQUET}, ${CHANGELOG}. ` +
|
|
145
|
+
`Le commit de sortie, puis : npx grillhouse sortie --etiqueter\n`
|
|
146
|
+
);
|
|
147
|
+
} else {
|
|
148
|
+
process.stderr.write(USAGE);
|
|
149
|
+
process.exitCode = 2;
|
|
150
|
+
}
|
|
151
|
+
} catch (e) {
|
|
152
|
+
process.stderr.write(`${e.message}\n`);
|
|
153
|
+
process.exitCode = 1;
|
|
154
|
+
}
|
|
155
|
+
}
|