@vernikr/size-report 2.3.0 → 2.5.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/src/hook.js CHANGED
@@ -8,64 +8,53 @@ import { loadConfig } from './config.js';
8
8
  import { TOOL_PKG } from './tool.js';
9
9
  import { rebuild } from './artifact.js';
10
10
 
11
- /* Хуки `post-commit` и `post-merge`: после каждого коммита и слияния отчёт
12
- * пересобирается сам, а если он лежит в git — ложится отдельным коммитом.
13
- * Требования §7.1–§7.3 (автообновление, защита от зацикливания, отключаемость) и
14
- * шаг 5 плана.
11
+ /* The `post-commit` and `post-merge` hooks: after every commit and merge the report rebuilds itself and, when it is tracked
12
+ * by git, lands as a commit of its own.
15
13
  *
16
- * Что он делает и почему именно так:
14
+ * What it does, and why exactly so:
17
15
  *
18
- * - **Ставится сам**после установки пакета (`bin/postinstall.js`) и при первом
19
- * запуске в проекте (`autoInstall`, зовётся из входа): от человека не требуется
20
- * ни ручного шага, ни файла настроек, иначе первого обновления отчёта он не
21
- * увидел бы вовсе. Ставится там, где это безопасно (обычный `.git/hooks`, нет
22
- * чужого хука, есть чем звать инструмент, не CI); где небезопасно молчит.
23
- * Снимается явной командой (`uninstall-hook`), и проект возвращается к прежнему
24
- * поведению: и поставленное, и снятое одно и то же место состояния (`git-dir`).
25
- * Ручная команда (`install-hook`) остаётся: она называет причину, когда поставить
26
- * не удалось, а тихая постановка причин не объясняет.
27
- * - **Сам коммитов не создаёт** за одним исключением: отчёт, лежащий в git,
28
- * коммитится отдельно от кода. Раньше это делал человек (отсюда ловушка «правка
29
- * кода и таблицы в одном коммите»), и хук для того и нужен, чтобы ручного шага не
30
- * было. Правило одно на два состояния проекта: отчёт в git отдельный коммит;
31
- * отчёт вне git только пересборка (ровно то, что описано в требованиях §7.2:
32
- * пересборка коммита не порождает).
33
- * - **Коммитит только путь отчёта**: дерево берётся от HEAD, и в нём подменяется
34
- * ровно путь отчёта, поэтому в коммит физически не может попасть ничего другого —
35
- * ни индекс, ни чужая незакоммиченная работа («не может потерять работу»).
36
- * - **Слияниетакой же случай, как обычный коммит**, но с одной поправкой к тому,
37
- * что зовёт git: `post-commit` при `git merge` не выполняется (git создаёт коммит
38
- * слияния сам), поэтому ставится ещё и `post-merge`. Строка слияния ложится в
39
- * отчёт по общему правилу (`rows.merges`), а поведение при вызове второго файла
40
- * то же, что при повторном запуске: пересобирать нечего коммита нет.
41
- * - **Зацикливание невозможно по двум причинам.** Коммит отчёта собирается
42
- * плумбингом (`commit-tree`), а он хуков не зовёт вовсе, — вложенного запуска не
43
- * бывает по устройству, а не по флагу в окружении. И сам отчёт путь, который
44
- * строки не получает (инвариант «строка про коммит не может лежать внутри самого
45
- * коммита»), поэтому та же пересборка даёт те же байты и второго коммита не будет.
46
- * Плюс замок, чтобы два хука не пошли одновременно.
47
- * - **Отказ инструмента не роняет коммит.** Коммит уже сделан, и блокировать в нём
48
- * нечего (требование §8: инструмент только показывает). Причина печатается одной
49
- * строкой и запоминается — её видно в `size doctor` (`src/doctor.js`).
50
- * - **В окружениях, где обновлять отчёт не нужно** (интеграция, чужая машина,
51
- * зависимостей нет) хук молчит: сам файл хука в git не едет — он лежит в `.git`,
52
- * то есть у каждого клона свой, — а внутри есть проверки «есть ли чем звать
53
- * инструмент».
16
+ * - **It installs itself** after the package is installed (`bin/postinstall.js`) and on the first run in a project
17
+ * (`autoInstall`, called from the entry point): a person needs neither a manual step nor a settings file, or he would
18
+ * never see the first rebuild at all. It installs where that is safe (an ordinary `.git/hooks`, no one else's hook,
19
+ * something to call the tool with, not CI) and stays silent where it is not. It is removed by an explicit command
20
+ * (`uninstall-hook`), which returns the project to its previous behaviour: both the installing and the removing live in
21
+ * one place of state (`git-dir`). The manual command (`install-hook`) stays as well: it names the cause when installing
22
+ * failed, while the silent path explains nothing.
23
+ * - **It creates no commits of its own** — with one exception: a report tracked by git is committed separately from the
24
+ * code. A person used to do that (hence the trap of an edit of code and of the report in one commit), and the hook
25
+ * exists so that the manual step is gone. One rule for the two states of a project: the report in git is a commit of its
26
+ * own, the report outside git is a rebuild alone a rebuild produces no commit.
27
+ * - **It commits the report's path alone**: the tree comes from HEAD with exactly the report's path replaced in it, so
28
+ * nothing else can physically enter the commit neither the index nor someone's uncommitted work.
29
+ * - **A merge is the same case as an ordinary commit**, with one correction to what git calls: a merge does not run
30
+ * `post-commit` (git creates the merge commit itself), hence the second hook file, `post-merge`. The merge's row lands
31
+ * in the report by the usual rule (`rows.merges`), and the second file behaves like a repeated run: there is nothing to
32
+ * rebuild there is no new commit.
33
+ * - **A loop is impossible for two reasons.** The report's commit is assembled with plumbing (`commit-tree`), which calls
34
+ * no hooks at all a nested run cannot happen by construction rather than by a flag in the environment. And the report
35
+ * itself is a path that gets no row (the invariant "a row about a commit cannot lie inside that commit"), so the same
36
+ * rebuild yields the same bytes and there will be no second commit. A lock is there besides, so that two hooks do not
37
+ * run at once.
38
+ * - **A refusal by the tool does not bring the commit down.** The commit has been made already and there is nothing to
39
+ * block in it the tool only shows. The cause is printed as one line and remembered: `size doctor` shows it
40
+ * (`src/doctor.js`).
41
+ * - **In environments where the report needs no updating** (integration, someone else's machine, no dependencies) the hook
42
+ * stays silent: the hook file itself does not travel in git — it lies in `.git`, so every clone has one of its own —
43
+ * while the body checks whether there is anything to call the tool with.
54
44
  *
55
- * Место состояния git-каталог (`<git-dir>/size-report/`), а не рабочее дерево:
56
- * иначе замок и запись о запуске торчали бы в `git status` как неотслеживаемые
57
- * файлы. Состояние локально для клона, как и сам хук. */
45
+ * The state lives in the git directory (`<git-dir>/size-report/`) rather than in the working tree: otherwise the lock and
46
+ * the record of a run would stand out in `git status` as untracked files. The state is local to a clone, as the hook
47
+ * itself is. */
58
48
 
59
- // Второй выключатель: окружение, где автообновление не нужно вовсе (CI, чужая
60
- // машина), и явный рубильник для тех, кто не хочет править настройки.
49
+ // The second switch: an environment where auto-updating is not wanted at all (CI, someone else's machine) — and an explicit
50
+ // lever for those who would rather not edit the settings.
61
51
  const NO_HOOK = 'SIZE_REPORT_NO_HOOK';
62
- /* Два файла, а не один: на обычный коммит git зовёт `post-commit`, а на слияние
63
- * только `post-merge` (`post-commit` при `git merge` не выполняется вовсе). Вход
64
- * у обоих один, поэтому лишнего запуска не бывает: тот, что сработал вторым,
65
- * видит, что менять нечего, и молчит. */
52
+ /* Two files rather than one: git calls `post-commit` for an ordinary commit, while a merge only runs `post-merge`
53
+ * (`post-commit` is not run for `git merge` at all — checked on git 2.50 in `test/hook.test.js`). Both share one entry
54
+ * point, so a spare run does not happen: whichever fires second sees there is nothing to change and stays silent. */
66
55
  const HOOKS = ['post-commit', 'post-merge'];
67
- // Метка «этот файл наш»: по ней хук отличается от чужого, который перезаписывать
68
- // нельзя, и по ней же снятие понимает, что удалять.
56
+ // The mark "this file is ours": the hook differs by it from someone else's, which must not be overwritten, and the removal
57
+ // reads it to know what to delete.
69
58
  const MARK = '# size-report: hook';
70
59
  const STATE = 'hook.json';
71
60
  const LOCK = 'hook.lock';
@@ -78,10 +67,9 @@ function stateDir(root) {
78
67
  return path.join(gitDir(root), 'size-report');
79
68
  }
80
69
 
81
- /* Куда git читает хуки. `core.hooksPath` перебивает `.git/hooks`, и это чужая
82
- * настройка: такой каталог часто лежит в другом репозитории и версионируется, а
83
- * вставлять строку в чужой скрипт править чужой файл. Поэтому с чужим путём
84
- * установка отказывает и называет готовую строку. */
70
+ /* Where git reads hooks from. `core.hooksPath` overrides `.git/hooks`, and that is someone else's setting: such a
71
+ * directory often lies in another repository and is versioned, while inserting a line into someone else's script means
72
+ * editing someone else's file. Hence with a custom path the install refuses and names a ready line. */
85
73
  function hooksDir(root) {
86
74
  const custom = gitTry(root, ['config', '--get', 'core.hooksPath']);
87
75
  if (custom.status === 0 && custom.stdout.trim() !== '') {
@@ -94,10 +82,9 @@ function hookFile(root, name) {
94
82
  return path.join(hooksDir(root).dir, name);
95
83
  }
96
84
 
97
- /* Движок, которым хук зовёт инструмент,тот, что нашла бы сама установка. Путь
98
- * записывается в скрипт на время установки: у монорепозитория `node_modules` может
99
- * лежать выше корня проекта, и общий относительный путь там не работает. Путь
100
- * цитируется: в нём может стоять пробел, а хук — это sh, а не список аргументов. */
85
+ /* The engine the hook calls the tool with the one the install itself would have found. The path is written into the
86
+ * script at install time: in a monorepo `node_modules` may lie above the project root, where a common relative path does
87
+ * not work. The path is quoted: it may hold a space, and a hook is sh rather than a list of arguments. */
101
88
  function hookEntry(root) {
102
89
  const candidates = [
103
90
  path.join(root, 'node_modules', TOOL_PKG.name, 'bin', 'size.js'),
@@ -114,28 +101,27 @@ function shQuote(text) {
114
101
  return '"' + text.replace(/(["\\$`])/g, '\\$1') + '"';
115
102
  }
116
103
 
117
- // Готовая строка для чужого хука: её копируют как есть, поэтому она без метки.
104
+ // A ready line for someone else's hook: it is copied as it stands, which is why it carries no mark.
118
105
  function runLine(entry) {
119
106
  return 'node ' + entry.path + ' hook-run';
120
107
  }
121
108
 
122
- /* Тело хука. Проверки перед запуском это ответ «что он НЕ делает там, где
123
- * обновлять нечего»: без `node` (GUI-клиент git с урезанным PATH) и без движка
124
- * (зависимости не поставлены, клон без установки) хук выходит молча шум после
125
- * каждого коммита был бы хуже отсутствия автоматики. */
109
+ /* The hook's body. The checks before the run are the answer to "what it does NOT do where there is nothing to update":
110
+ * without `node` (a git GUI client with a trimmed PATH) and without the engine (dependencies not installed, a clone
111
+ * without an install) the hook exits silently noise after every commit would be worse than no automation. */
126
112
  function script(entry) {
127
113
  return '#!/bin/sh\n'
128
- + MARK + ': обновление отчёта после коммита.\n'
129
- + '# Ставится и снимается командами `size install-hook` / `size uninstall-hook`;\n'
130
- + '# правки в этом файле не сохранятсякоманда перезаписывает его целиком.\n'
131
- + '# Коммитов хук сам не делает: пересборка и коммит отчёта в `size hook-run`.\n'
114
+ + MARK + ': the report is refreshed after a commit.\n'
115
+ + '# Installed and removed by the commands `size install-hook` / `size uninstall-hook`;\n'
116
+ + '# edits in this file do not survive the command overwrites it whole.\n'
117
+ + '# The hook makes no commits of its own: the rebuild and the commit of the report are in `size hook-run`.\n'
132
118
  + 'command -v node >/dev/null 2>&1 || exit 0\n'
133
119
  + '[ -f ' + entry.quoted + ' ] || exit 0\n'
134
120
  + 'exec node ' + entry.quoted + ' hook-run\n';
135
121
  }
136
122
 
137
- /* Состояние хука для `size doctor`: установлен ли и чем кончился последний
138
- * запуск. Ничего не считает и ни к чему не обязывает. */
123
+ /* The hook's state for `size doctor`: whether it is installed and how its last run ended. It counts nothing and commits
124
+ * to nothing. */
139
125
  export function hookStatus(root) {
140
126
  const files = HOOKS.map((name) => hookFile(root, name));
141
127
  return {
@@ -149,8 +135,8 @@ function isOurs(file) {
149
135
  return fs.existsSync(file) && fs.readFileSync(file, 'utf8').indexOf(MARK) >= 0;
150
136
  }
151
137
 
152
- /* Запись о последнем запуске. Отсутствие файла «хук ещё не запускался», а не
153
- * ошибка: до первого коммита её и не должно быть. */
138
+ /* The record of the last run. A missing file means "the hook has not run yet" rather than an error: before a first commit
139
+ * there is nothing to record. */
154
140
  function hookState(root) {
155
141
  try {
156
142
  return JSON.parse(fs.readFileSync(path.join(stateDir(root), STATE), 'utf8'));
@@ -162,36 +148,36 @@ function hookState(root) {
162
148
  export function installHook(root, cfg) {
163
149
  const entry = hookEntry(root);
164
150
  if (entry === null) {
165
- // Совет называет ту установку, которой учит README: имя пакета в реестре занято
166
- // чужим пакетом, и `add -D <имя>` поставил бы его (REFACTOR R-4.21).
151
+ // The advice names the very installation the README teaches the git link pinned to this release, so that the advice
152
+ // cannot drift from the release the documentation describes.
167
153
  const spec = installSpec();
168
- refuseCause('нечем звать инструмент', 'не нашлось чем звать инструмент: хук без него молчал бы'
169
- + ' после каждого коммита.\n'
170
- + ' починка: поставьте пакет зависимостью проекта'
171
- + (spec === null ? '' : ' (так, как он ставится в этот проект: pnpm add -D ' + spec + ')')
172
- + ' и повторите установку');
154
+ refuseCause('no way to invoke the tool', 'nothing to call the tool with: without it the hook would stay silent'
155
+ + ' after every commit.\n'
156
+ + ' fix: install the package as a dependency of the project'
157
+ + (spec === null ? '' : ' (the way it is installed in this project: pnpm add -D ' + spec + ')')
158
+ + ' and repeat the installation');
173
159
  }
174
160
  const hooks = hooksDir(root);
175
161
  if (hooks.custom) {
176
- refuseCause('чужой core.hooksPath', 'в проекте задан core.hooksPath (' + hooks.dir + '): этот каталог может лежать'
177
- + ' в другом репозитории, и править его инструмент не станет.\n'
178
- + ' починка: впишите в свой хук строку «' + runLine(entry) + '»');
162
+ refuseCause('foreign core.hooksPath', 'the project sets core.hooksPath (' + hooks.dir + '): that directory may lie'
163
+ + ' in another repository, and the tool will not edit it.\n'
164
+ + ' fix: write the line "' + runLine(entry) + '" into your own hook');
179
165
  }
180
166
  const files = HOOKS.map((name) => path.join(hooks.dir, name));
181
167
  const rels = files.map((f) => path.relative(root, f));
182
168
  files.forEach((file, i) => {
183
169
  if (fs.existsSync(file) && !isOurs(file)) {
184
- refuseCause('чужой хук', 'хук ' + rels[i] + ' уже есть и поставлен не этим инструментом (в нём нет метки).\n'
185
- + ' починка: позовите инструмент из своего хука строкой «' + runLine(entry) + '»'
186
- + ' — перезаписи чужого файла нет намеренно');
170
+ refuseCause('foreign hook', 'the hook ' + rels[i] + ' is already there and was not put there by this tool (it carries no mark).\n'
171
+ + ' fix: call the tool from your own hook with the line "' + runLine(entry) + '"'
172
+ + ' — the tool deliberately does not rewrite what it did not write');
187
173
  }
188
174
  });
189
175
  if (files.every(isOurs)) {
190
176
  return { code: EXIT.OK, lines: [
191
- хук уже установлен: ' + rels.join(', '),
192
- ' автоматика работает после каждого коммита и слияния',
193
- ' выключить, не снимая: «"hooks": {"enabled": false}» в файле настроек',
194
- ' снять: ' + cliCommand('uninstall-hook')
177
+ the hook is already installed: ' + rels.join(', '),
178
+ ' the automation works after every commit and merge',
179
+ ' to switch it off without removing it: "hooks": {"enabled": false} in the settings file',
180
+ ' to remove it: ' + cliCommand('uninstall-hook')
195
181
  ] };
196
182
  }
197
183
  fs.mkdirSync(hooks.dir, { recursive: true });
@@ -201,28 +187,28 @@ export function installHook(root, cfg) {
201
187
  });
202
188
 
203
189
  const lines = [
204
- '✓ хук: ' + rels.join(', '),
205
- ' после каждого коммита и слияния пересобирает ' + cfg.output
206
- + ' и, если этот файл в git, кладёт его отдельным коммитом',
207
- ' сам он коммитит только отчёт: индекс и ваша незакоммиченная работа не тронуты',
208
- ' выключить, не снимая: «"hooks": {"enabled": false}» в файле настроек',
209
- ' снять: ' + cliCommand('uninstall-hook')
190
+ '✓ hook: ' + rels.join(', '),
191
+ ' after every commit and merge it rebuilds ' + cfg.output
192
+ + ' and, if that file is in git, lands it as a commit of its own',
193
+ ' it commits nothing but the report: the index and your uncommitted work are untouched',
194
+ ' to switch it off without removing it: "hooks": {"enabled": false} in the settings file',
195
+ ' to remove it: ' + cliCommand('uninstall-hook')
210
196
  ];
211
197
  if (cfg.hooks.enabled === false) {
212
- lines.push('! сейчас автоматика выключена настройкой hooks.enabled — хук будет молчать');
198
+ lines.push('! the automation is switched off by the hooks.enabled setting right now the hook will stay silent');
213
199
  }
214
200
  return { code: EXIT.OK, lines: lines };
215
201
  }
216
202
 
217
- /* Постановка без спроса. Отвечает списком путей, если поставила, и `null`, если не
218
- * тронула ничего, второй ответ не ошибка, а норма: эта услуга фоновая, и там, где
219
- * она не к месту, её просто нет. Поэтому всё, что мешает поставить, решается
220
- * молчанием, а не отказом: отказ от фоновой работы после каждого запуска был бы
221
- * шумом, а причина уже названа точной командой (`install-hook`).
203
+ /* Installing without being asked. It answers with the list of paths when it installed and with `null` when it touched
204
+ * nothingthe second answer is the norm rather than an error: this service is a background one, and where it does not
205
+ * belong it simply is not there. Hence everything that prevents installing is settled by silence rather than by a refusal:
206
+ * a refusal of background work after every run would be noise, while the cause is already named by the exact command
207
+ * (`install-hook`).
222
208
  *
223
- * Чужой `core.hooksPath` сюда же: этот каталог версионируется и часто лежит в
224
- * другом репозитории вписывать строку в чужой файл по своей воле нельзя, и
225
- * человек берёт её у `install-hook` (готовую и без метки). */
209
+ * Someone else's `core.hooksPath` belongs here too: that directory is versioned and often lies in another repository —
210
+ * writing a line into someone else's file on one's own initiative is not for the tool, and the person takes the line from
211
+ * `install-hook` (ready and without a mark). */
226
212
  export function autoInstall(root, cfg) {
227
213
  if (process.env.CI || process.env[NO_HOOK]) return null;
228
214
  if (cfg !== null && cfg.hooks.enabled === false) return null;
@@ -240,36 +226,35 @@ export function autoInstall(root, cfg) {
240
226
  });
241
227
  return files.map((f) => path.relative(root, f));
242
228
  } catch (_e) {
243
- /* Не git-репозиторий, нет прав на `.git`, чужой форматвсё это значит одно:
244
- * автоматики здесь не будет, а работа инструмента от неё не зависит. */
229
+ /* Not a git repository, no rights on `.git`, a foreign format all of it means one thing: there will be no automation
230
+ * here, while the tool's work does not depend on it. */
245
231
  return null;
246
232
  }
247
233
  }
248
234
 
249
- /* Снятие: убирается только то, что поставили мы. Файл не «похож на наш», а помечен
250
- * меткой, иначе чужой хук был бы стёрт молча. */
235
+ /* Removing takes away only what we installed. A file is not "like ours" but carries the mark, or someone else's hook would
236
+ * be wiped in silence. */
251
237
  export function uninstallHook(root) {
252
238
  const files = HOOKS.map((name) => hookFile(root, name));
253
239
  const rels = files.map((f) => path.relative(root, f));
254
240
  const present = files.filter((f) => fs.existsSync(f));
255
241
  if (present.length === 0) {
256
- return { code: EXIT.OK, lines: ['· хук не установлен: ' + rels.join(', ')] };
242
+ return { code: EXIT.OK, lines: ['· the hook is not installed: ' + rels.join(', ')] };
257
243
  }
258
244
  present.forEach((file) => {
259
245
  if (!isOurs(file)) {
260
- refuseCause('чужой хук', 'хук ' + path.relative(root, file) + ' поставлен не этим инструментомне трогаю.\n'
261
- + ' починка: уберите из него строку с «hook-run», если она там есть');
246
+ refuseCause('foreign hook', 'the hook ' + path.relative(root, file) + ' was not put there by this tool I leave it alone.\n'
247
+ + ' fix: take the line with "hook-run" out of it, if it is there');
262
248
  }
263
249
  });
264
250
  present.forEach((file) => fs.rmSync(file));
265
251
  fs.rmSync(stateDir(root), { recursive: true, force: true });
266
- return { code: EXIT.OK, lines: ['✓ хук снят: ' + rels.join(', ') + ' (проект ведёт себя как до установки)'] };
252
+ return { code: EXIT.OK, lines: ['✓ hook removed: ' + rels.join(', ') + ' (the project behaves as it did before the install)'] };
267
253
  }
268
254
 
269
- /* Замок: два хука одновременно (например, коммит из двух терминалов) не должны
270
- * пересобирать один файл. Замок с живым владельцем«уже идёт»; замок, оставшийся
271
- * от убитого процесса, забирается: иначе однажды прерванный прогон запретил бы хук
272
- * навсегда. */
255
+ /* The lock: two hooks at once (a commit from two terminals, say) must not rebuild one file. A lock with a live owner means
256
+ * "already running"; a lock left by a killed process is taken over otherwise a run interrupted once would forbid the hook
257
+ * forever. */
273
258
  function acquire(root) {
274
259
  const file = path.join(stateDir(root), LOCK);
275
260
  fs.mkdirSync(stateDir(root), { recursive: true });
@@ -302,28 +287,26 @@ function alive(pid) {
302
287
  }
303
288
  }
304
289
 
305
- /* Запись о запуске для `size doctor`. Её провал (нет прав, нет каталога) не должен
306
- * становиться шумом после коммита: это не то, ради чего хук запускают. */
290
+ /* The record of a run for `size doctor`. Its failure (no rights, no directory) must not become noise after a commit: the
291
+ * record is not what a hook is run for. */
307
292
  function record(root, fields, note) {
308
293
  const state = Object.assign({ schema: 1, at: new Date().toISOString() }, fields);
309
294
  try {
310
295
  fs.mkdirSync(stateDir(root), { recursive: true });
311
296
  fs.writeFileSync(path.join(stateDir(root), STATE), JSON.stringify(state, null, 2) + '\n');
312
297
  } catch (_e) {
313
- // см. выше: запись не цель прогона
298
+ // See above: the record is not the goal of the run.
314
299
  }
315
300
  return { code: EXIT.OK, note: note === undefined ? '' : note };
316
301
  }
317
302
 
318
- /* Коммит отчёта собирается плумбингом, а не `git commit --only`: `--only` отказывает
319
- * на слиянии («cannot do a partial commit during a merge» — MERGE_HEAD жив, пока хук
320
- * `post-merge` работает), а развилка «обычный случай так, слияние иначе» оставила бы
321
- * один из двух путей почти без хода. Дерево берётся от HEAD, и в нём подменяется
322
- * ровно путь отчёта, поэтому в коммит не может попасть ни индекс, ни чужая правка,
323
- * а `commit-tree` хуков не зовёт вложенного запуска не бывает по устройству.
324
- * Подпись коммита не спрашивается: настройка машины не должна останавливать коммит.
325
- * Дешёвый индекс здесь — временный: настоящий трогается один раз, и только в записи
326
- * об отчёте, иначе после коммита дерево было бы грязным. */
303
+ /* The report's commit is assembled with plumbing rather than `git commit --only`: `--only` refuses during a merge ("cannot
304
+ * do a partial commit during a merge" — MERGE_HEAD is alive while the `post-merge` hook runs), while a fork of "the
305
+ * ordinary case this way, a merge that way" would leave one of the two paths almost untested. The tree comes from HEAD with
306
+ * exactly the report's path replaced in it, so neither the index nor someone else's edit can enter the commit, and
307
+ * `commit-tree` calls no hooks a nested run cannot happen by construction. No signature is asked for the commit: a
308
+ * machine's setting must not stop a commit. The index used here is a temporary one: the real index is touched once, and
309
+ * only with the report's own entry, or the tree would be left dirty after the commit. */
327
310
  function commitReport(root, job) {
328
311
  const { rel, file, message, branch, head } = job;
329
312
  const fail = (why) => ({ ok: false, why: why });
@@ -358,7 +341,7 @@ export function hookRun(root, configFile) {
358
341
  if (process.env.CI || process.env[NO_HOOK]) {
359
342
  return record(root, {
360
343
  result: 'skipped',
361
- why: 'окружение без автообновления (' + (process.env.CI ? 'CI' : NO_HOOK) + ')'
344
+ why: 'an environment without auto-updating (' + (process.env.CI ? 'CI' : NO_HOOK) + ')'
362
345
  });
363
346
  }
364
347
  const head = gitTry(root, ['rev-parse', 'HEAD']);
@@ -368,7 +351,7 @@ export function hookRun(root, configFile) {
368
351
  return record(root, {
369
352
  result: 'skipped',
370
353
  head: sha,
371
- why: 'обновление уже идёт (замок ' + path.relative(root, lock.file) + ')'
354
+ why: 'a rebuild is already running (lock ' + path.relative(root, lock.file) + ')'
372
355
  });
373
356
  }
374
357
  try {
@@ -384,40 +367,38 @@ function runLocked(root, configFile, sha) {
384
367
  cfg = loadConfig(configFile, root);
385
368
  } catch (e) {
386
369
  if (!(e instanceof Refusal)) throw e;
387
- return record(root, { result: 'skipped', head: sha, why: 'настройки нечитаемы: ' + e.message });
370
+ return record(root, { result: 'skipped', head: sha, why: 'the settings cannot be read: ' + e.message });
388
371
  }
389
372
  if (cfg.hooks.enabled === false) {
390
- return record(root, { result: 'skipped', head: sha, report: cfg.output, why: 'выключено настройкой hooks.enabled' });
373
+ return record(root, { result: 'skipped', head: sha, report: cfg.output, why: 'switched off by the hooks.enabled setting' });
391
374
  }
392
- /* Отделённый HEAD это rebase, cherry-pick и bisect: коммитить в такое состояние
393
- * нельзя (запись окажется ни на одной ветке), а пересобирать отчёт по промежуточному
394
- * состоянию некому. Слиянию это не мешает: `post-merge` приходит уже после того,
395
- * как коммит слияния создан. */
375
+ /* A detached HEAD means rebase, cherry-pick and bisect: committing into such a state is not allowed (the record would
376
+ * land on no branch), and nobody is there to rebuild the report from an intermediate state. It does not hinder a merge:
377
+ * `post-merge` comes once the merge commit has been created. */
396
378
  const branch = gitTry(root, ['symbolic-ref', '-q', 'HEAD']).stdout.trim();
397
379
  if (branch === '') {
398
- return record(root, { result: 'skipped', head: sha, report: cfg.output, why: 'HEAD отделён (rebase, cherry-pick, bisect)' });
380
+ return record(root, { result: 'skipped', head: sha, report: cfg.output, why: 'detached HEAD (rebase, cherry-pick, bisect)' });
399
381
  }
400
382
 
401
383
  let out;
402
384
  try {
403
385
  out = rebuild(cfg, root);
404
386
  } catch (e) {
405
- /* Отказ инструмента (нет истории, не разобрался файл, нет настроек) это не
406
- * отказ хука: коммит уже сделан, и валить его нечем и незачем. Причина едет
407
- * в `size doctor`, а человеку хватает одной строки. Неожиданная ошибка дефект
408
- * инструмента, и стек печатается: иначе его нечем разбирать. */
387
+ /* A refusal by the tool (no history, an unparsed file, no settings) is not a refusal by the hook: the commit has been
388
+ * made, and there is nothing and no reason to bring it down. The cause goes to `size doctor`, while one line is enough
389
+ * for a person. An unexpected error is a defect of the tool and its stack is printed: otherwise there is nothing to
390
+ * investigate it with. */
409
391
  if (e instanceof Refusal) {
410
392
  return record(root, { result: 'refused', head: sha, report: cfg.output, why: e.message }, '✗ size-report: ' + e.message.split('\n')[0]);
411
393
  }
412
394
  return record(root, { result: 'failed', head: sha, report: cfg.output, why: String(e.message) },
413
- '✗ size-report: внутренняя ошибка: ' + e.stack);
395
+ '✗ size-report: internal error: ' + e.stack);
414
396
  }
415
397
 
416
398
  return storeReport(root, cfg, branch, sha, out.file);
417
399
  }
418
400
 
419
- /* Запись отчёта после пересборки: не отслеживаетсясказать словами, не
420
- * изменился — ничего не делать, изменился — закоммитить. */
401
+ /* Storing the report after a rebuild: not tracked say so in words, unchanged — do nothing, changed — commit it. */
421
402
  function storeReport(root, cfg, branch, head, file) {
422
403
  const rel = path.relative(root, file);
423
404
  if (gitTry(root, ['ls-files', '--error-unmatch', '--', rel]).status !== 0) {
@@ -425,7 +406,7 @@ function storeReport(root, cfg, branch, head, file) {
425
406
  result: 'rebuilt',
426
407
  head: head,
427
408
  report: cfg.output,
428
- why: 'отчёт не отслеживается git: пересобран, коммита нет'
409
+ why: 'the report is not tracked by git: rebuilt, no commit'
429
410
  });
430
411
  }
431
412
  const diff = gitTry(root, ['diff', '--quiet', 'HEAD', '--', rel]);
@@ -437,12 +418,12 @@ function storeReport(root, cfg, branch, head, file) {
437
418
  '✗ size-report: git diff -- ' + rel + ': ' + diff.stderr.trim());
438
419
  }
439
420
 
440
- const message = 'chore(report): отчёт пересобран после ' + head.slice(0, 7);
421
+ const message = 'chore(report): report rebuilt after ' + head.slice(0, 7);
441
422
  const commit = commitReport(root, { rel: rel, file: file, message: message, branch: branch, head: head });
442
423
  if (!commit.ok) {
443
424
  return record(root, { result: 'refused', head: head, report: cfg.output, why: commit.why },
444
- '✗ size-report: коммит отчёта не прошёл: ' + commit.why);
425
+ '✗ size-report: the report commit did not go through: ' + commit.why);
445
426
  }
446
427
  return record(root, { result: 'committed', head: head, report: cfg.output, commit: commit.sha },
447
- '✓ size-report: ' + cfg.output + ' пересобран и закоммичен (' + commit.sha + ')');
428
+ '✓ size-report: ' + cfg.output + ' rebuilt and committed (' + commit.sha + ')');
448
429
  }