@vernikr/size-report 2.4.0 → 2.6.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.
Files changed (48) hide show
  1. package/README.md +989 -1081
  2. package/bin/postinstall.js +17 -18
  3. package/bin/size.js +2 -2
  4. package/package.json +3 -4
  5. package/src/args.js +72 -72
  6. package/src/artifact.js +14 -14
  7. package/src/check.js +41 -42
  8. package/src/cli.js +26 -29
  9. package/src/config.js +87 -91
  10. package/src/css.js +14 -14
  11. package/src/data.js +26 -50
  12. package/src/derived.js +31 -35
  13. package/src/doctor.js +95 -99
  14. package/src/explain.js +46 -47
  15. package/src/git.js +66 -71
  16. package/src/history.js +74 -83
  17. package/src/hook.js +130 -149
  18. package/src/init.js +37 -37
  19. package/src/journal.js +17 -15
  20. package/src/locales.js +31 -22
  21. package/src/metrics.js +72 -89
  22. package/src/minify.js +28 -27
  23. package/src/modes.js +57 -60
  24. package/src/optional.js +13 -11
  25. package/src/page/app.css +76 -94
  26. package/src/page/app.js +124 -80
  27. package/src/page/build.js +193 -50
  28. package/src/page/dom.js +8 -9
  29. package/src/page/panel.js +157 -69
  30. package/src/page/payload.js +168 -0
  31. package/src/page/state.js +144 -104
  32. package/src/page/table.js +270 -86
  33. package/src/parse-worker.js +10 -10
  34. package/src/parse.js +43 -45
  35. package/src/project.js +100 -104
  36. package/src/refusal.js +75 -76
  37. package/src/size-table.js +41 -76
  38. package/src/strip/forms.js +5 -5
  39. package/src/strip/guard.js +28 -28
  40. package/src/strip/js.js +27 -27
  41. package/src/strip.js +17 -21
  42. package/src/table.css +54 -19
  43. package/src/tokens.js +27 -27
  44. package/src/tool.js +10 -11
  45. package/templates/README.md +71 -77
  46. package/templates/ci.yml +33 -33
  47. package/templates/size-report.config.json +3 -3
  48. package/CHANGELOG.md +0 -690
package/src/refusal.js CHANGED
@@ -3,17 +3,17 @@ import path from 'path';
3
3
  import { fileURLToPath } from 'url';
4
4
  import { TOOL_PKG } from './tool.js';
5
5
 
6
- /* Отказ и справка: код выхода, сообщение с готовой командой починки и текст
7
- * «--help». Стоит ниже всех в цепочке ни о настройках, ни о git не знает,
8
- * поэтому его может звать любой модуль. */
6
+ /* Refusal and help: an exit code, a message carrying the command that fixes the problem,
7
+ * and the `--help` text. It stands at the bottom of the chain it knows neither the
8
+ * settings nor git which is why any module may call it. */
9
9
 
10
- // Пакет модуль, а подсказка в `loadConfig` цитирует путь самого движка: в ESM
11
- // `__filename` нет, поэтому путь берётся от `import.meta.url`.
10
+ // ESM has no `__filename`, and `invocation()` needs the engine's own location: the path
11
+ // comes from `import.meta.url`.
12
12
  const __filename = fileURLToPath(import.meta.url);
13
13
 
14
- /* Отказ это код выхода и одна строка с готовой командой починки: по коду
15
- * ветвится агент (таблица кодов в `PLAN.md` §4.1), по тексту человек. Стек
16
- * наружу не отдаётся вовсе: подсказки в нём нет, зато есть пути машины. */
14
+ /* A refusal is an exit code and one line with the command that fixes it: an agent branches
15
+ * on the code (the table is in `USAGE` below), a human reads the text. The stack is never
16
+ * handed out it holds no hint, only paths of the machine. */
17
17
  export const EXIT = { OK: 0, VIOLATION: 1, CONFIG: 2, SHALLOW: 3, SENSOR: 4, INTERNAL: 5 };
18
18
 
19
19
  export class Refusal extends Error {
@@ -27,51 +27,50 @@ export function refuse(code, message) {
27
27
  throw new Refusal(code, message);
28
28
  }
29
29
 
30
- /* Причины отказа кодом 2 одним списком, и он единственное место, где они
31
- * перечислены словами: справка печатает их из него, таблица кодов в `README.md`
32
- * сверяется с ним проверкой (`test/docs-commands.test.js`), а `refuseCause` не
33
- * пропускает отказ, не назвавший причины. Поэтому «в документации сказано
34
- * меньше, чем бывает» здесь не может случиться молча. Группы по тому, откуда
35
- * причина: разбор вызова, настройки и проект, история, хук, измерение. */
30
+ /* The causes of a code-2 refusal in one list, and this is the only place where they are
31
+ * spelled out: the help prints them from it, the code table in `README.md` is checked
32
+ * against it (`test/docs-commands.test.js`), and `refuseCause` lets no refusal through
33
+ * without a named cause. So "the documentation says less than happens" cannot pass here
34
+ * silently. The groups say where a cause comes from: the command line, settings and the
35
+ * project, history, the hook, measurement. */
36
36
  export const CONFIG_CAUSES = [
37
- ['командная строка', [
38
- 'незнакомый ключ', 'ключ без значения', 'повтор ключа', 'два режима сразу',
39
- 'лишнее слово', 'команда и режим', 'неизвестная команда', 'несовместимый ключ',
40
- 'нет ответа в JSON', 'два ответа сразу', 'нет коммита'
37
+ ['command line', [
38
+ 'unknown flag', 'flag without a value', 'repeated flag', 'two modes at once',
39
+ 'extra word', 'command and mode', 'unknown command', 'incompatible flag',
40
+ 'no JSON answer', 'two answers at once', 'no commit'
41
41
  ]],
42
- ['настройки и проект', [
43
- 'нет файла настроек', 'настройки не разобраны', 'настройки неверны',
44
- 'нет git', 'не git-репозиторий', 'конфиг уже есть'
42
+ ['settings and the project', [
43
+ 'no settings file', 'settings not parsed', 'settings invalid',
44
+ 'git missing', 'not a git repository', 'config already exists'
45
45
  ]],
46
- ['история', ['нет такого коммита', 'коммит назван неточно', 'коммит вне истории']],
47
- ['хук', ['чужой хук', 'чужой core.hooksPath', 'нечем звать инструмент']],
48
- ['измерение', ['файл не JavaScript', 'минификатор не разобрал']]
46
+ ['history', ['no such commit', 'ambiguous commit', 'commit outside the history']],
47
+ ['hook', ['foreign hook', 'foreign core.hooksPath', 'no way to invoke the tool']],
48
+ ['measurement', ['file is not JavaScript', 'minifier did not parse']]
49
49
  ];
50
50
 
51
- /* Причина объявленное имя, а не украшение текста: неназванная не доедет до
52
- * пользователя, потому что это дефект инструмента, а не тупик человека. */
51
+ /* A cause is a declared name, not decoration of the text: an undeclared one never reaches
52
+ * the user, because that is a defect of the tool rather than a dead end for a human. */
53
53
  export function refuseCause(cause, message) {
54
54
  if (!CONFIG_CAUSES.some((g) => g[1].indexOf(cause) >= 0)) {
55
- throw new Error('причина отказа не объявлена: ' + cause);
55
+ throw new Error('refusal cause is not declared: ' + cause);
56
56
  }
57
57
  refuse(EXIT.CONFIG, message);
58
58
  }
59
59
 
60
- /* Строки справки про причины из того же списка, поэтому справка не может
61
- * разойтись с проверками. */
60
+ /* The help lines about causes come from the same list, so the help cannot drift from the
61
+ * checks. */
62
62
  const CAUSE_LINES = CONFIG_CAUSES.map((g) => ' ' + g[0] + ': ' + g[1].join(' · '));
63
63
 
64
- /* Как инструмент вызывается там, где его читают. Совет называет то, что лежит
65
- * рядом, и никогда имя пакета: `npx <имя>` запускает установленный пакет, только
66
- * пока тот на месте, а в проекте без него имя уходит в реестр и запускает чужой
67
- * пакет с тем же именем текст, который должен выручать, приводит к чужому коду.
68
- * Поэтому форма одна: путь внутри проекта (`node node_modules/<имя>/bin/size.js`) —
69
- * в проекте с пакетом она работает, без пакета отказывает на месте и в сеть не идёт.
64
+ /* How the tool is called where it is read. The advice names what lies nearby and never the package
65
+ * name: a path inside the project (`node node_modules/<name>/bin/size.js`) works where the package is
66
+ * installed and, where it is not, refuses on the spot; the name from the registry would instead fetch
67
+ * and run a revision the project never pinned (and the unscoped name there belongs to another package
68
+ * altogether). One form, therefore, and no trip to the network.
70
69
  *
71
- * Команда починки цитирует точку входа, а не сам движок: при импорте движок ничего
72
- * не запускает, поэтому `--init` работает только через команду. Путь в репозитории
73
- * пакета считается от места движка, а не от текущего каталога, сообщение обязано
74
- * работать из любого места проекта. */
70
+ * The fix command quotes the entry point rather than the engine itself: importing the
71
+ * engine runs nothing, so `--init` works through the command only. Inside the package's own
72
+ * repository the path is computed from the engine's location rather than from the current
73
+ * directory the message has to work from anywhere in the project. */
75
74
  export function invocation() {
76
75
  const local = path.join('node_modules', TOOL_PKG.name, 'bin', 'size.js');
77
76
  if (fs.existsSync(path.resolve(process.cwd(), local))) return 'node ' + local;
@@ -84,55 +83,55 @@ export function cliCommand(flag) {
84
83
  return invocation() + ' ' + flag;
85
84
  }
86
85
 
87
- /* Путь внутри готовой команды: пробел или кавычка в нём сломали бы копирование,
88
- * поэтому такой путь берётся в кавычки так его и приняла бы оболочка. */
86
+ /* A path inside a ready-made command: a space or a quote in it would break copying, so such
87
+ * a path is quoted the way a shell would accept it. */
89
88
  export function advicePath(p) {
90
89
  return /[\s"'$`\\]/.test(p) ? JSON.stringify(p) : p;
91
90
  }
92
91
 
93
92
  export const USAGE = [
94
- '@vernikr/size-report — отчёт об объёме файлов по коммитам: один файл,',
95
- 'самодостаточная страница (данные, оформление и программа лежат в ней же).',
93
+ '@vernikr/size-report — a report on how the size of files grows commit by commit: one file,',
94
+ 'a self-contained page (the data, the styling and the program live inside it).',
96
95
  '',
97
- 'Запуск: ' + invocation() + ' [команда] [режим] [ключи]',
96
+ 'Usage: ' + invocation() + ' [command] [mode] [flags]',
98
97
  '',
99
- 'Команды:',
100
- ' check [--json] полнота: настройки, история, пути, датчики (код 1 — путь',
101
- ' истории не отслеживается и не объявлен исключением)',
102
- ' explain <коммит> почему у коммита нет строки (имя ревизии, sha или его начало)',
103
- ' doctor [--json] диагностика одним ответом: окружение, зависимости, настройки,',
104
- ' покрытие (код 0 — делать нечего, иначепервый по важности)',
105
- ' install-hook поставить хуки post-commit и post-merge (они ставятся сами при',
106
- ' первом запуске в проекте): отчёт пересобирается после каждого',
107
- ' коммита и слияния, а если он в git ложится отдельным коммитом',
108
- ' uninstall-hook убрать хук и его состояние (проект возвращается к прежнему)',
109
- ' hook-run то, что зовёт хук: пересборка и коммит отчёта (вручную не нужно)',
98
+ 'Commands:',
99
+ ' check [--json] completeness: settings, history, paths, sensors (code 1 — a path',
100
+ ' of the history is not tracked and is not declared an exception)',
101
+ ' explain <commit> why a commit got no row (a revision name, a sha or its beginning)',
102
+ ' doctor [--json] diagnostics in one answer: environment, dependencies, settings,',
103
+ ' coverage (code 0 — nothing to do, otherwise the first by weight)',
104
+ ' install-hook install the post-commit and post-merge hooks (they install themselves',
105
+ ' on the first run in a project): the report is rebuilt after every',
106
+ ' commit and merge and, if it is in git, lands as a commit of its own',
107
+ ' uninstall-hook remove the hook and its state (the project goes back to what it was)',
108
+ ' hook-run what the hook calls: rebuild the report and commit it (never by hand)',
110
109
  '',
111
- 'Режимы:',
112
- ' --init [файл] закрепить настройки файлом (--force — перезаписать существующий)',
113
- ' --write [файл] собрать отчёт в файл из настроек (каталог создаётся сам)',
114
- ' --data данные контракта в stdout — для отчёта и для агента',
115
- ' --json прежняя форма данных в stdout',
116
- ' (без режима) проверить, что отчёт совпадает с историей',
110
+ 'Modes:',
111
+ ' --init [file] pin the settings in a file (--force — overwrite an existing one)',
112
+ ' --write [file] build the report into a file from the settings (the directory is made)',
113
+ ' --data the contract data on stdout — for the report and for an agent',
114
+ ' --json the former shape of the data on stdout',
115
+ ' (no mode) check that the report matches the history',
117
116
  '',
118
- 'Ключи: --config <файл>другие настройки; --help — эта справка.',
117
+ 'Flags: --config <file>other settings; --help — this help.',
119
118
  '',
120
- '--json форма ответа, а не отдельный режим, и правило у него одно: ответ бывает',
121
- 'ровно у четырёх вызовов. Без команды это прежняя форма данных, у check, explain',
122
- 'и doctor — их ответ; у остального ответа нет, и там --json отказ, а не тишина.',
119
+ '--json is a shape of the answer rather than a mode of its own, and it has one rule: exactly',
120
+ 'four calls answer. Without a command it is the former shape of the data, with check, explain',
121
+ 'and doctor — theirs; the rest have no answer, and there --json is a refusal rather than silence.',
123
122
  '',
124
- 'Запуск один: команда и режим не совмещаются, режим тоже один, и лишнее слово',
125
- 'вместе с незнакомым ключом отказ с готовой командой, а не обычный прогон.',
123
+ 'One run at a time: a command and a mode do not combine, a mode is alone too, and an extra word',
124
+ 'or an unknown flag is a refusal with a ready-made command rather than an ordinary run.',
126
125
  '',
127
- 'Настройки заводить не обязательно: без файла они выводятся из самого проекта',
128
- '(колонкипо группам путей из дерева и истории, журнал и куда писать оттуда же),',
129
- 'и об этом сказано в выводе. «--init» закрепляет выведенное файломдальше правят его;',
130
- 'файл, названный ключом «--config», обязан быть, иначе отказ.',
126
+ 'Settings are not obligatory: without a file they are derived from the project itself',
127
+ '(columnsfrom the path groups of the tree and the history, the journal and where to write —',
128
+ 'from there too), and the output says so. `--init` pins what was derived in a file after that',
129
+ 'the file is edited; a file named by `--config` has to exist, and otherwise it is a refusal.',
131
130
  '',
132
- 'Коды выхода: 0 — всё хорошо, 1 — расхождение с историей или неполнота, 2 — вызов,',
133
- 'настройки или окружение, 3 — неполная история, 4 — нет датчика, 5 — внутренняя',
134
- 'ошибка. У doctor свой порядок: 2, 3, 1, 4 — по важности находки, а не по тому,',
135
- 'что нашлось первым.',
131
+ 'Exit codes: 0 — all is well, 1 — a mismatch with the history or an incompleteness, 2 — the',
132
+ 'call, the settings or the environment, 3 — an incomplete history, 4 — no sensor, 5 — an',
133
+ 'internal error. doctor has an order of its own: 2, 3, 1, 4 — by the weight of a finding',
134
+ 'rather than by what came first.',
136
135
  '',
137
- 'Причины отказа кодом 2 (их же называет таблица кодов в README.md):'
136
+ 'Causes of a code-2 refusal (the code table in README.md names them too):'
138
137
  ].concat(CAUSE_LINES, ['']).join('\n');
package/src/size-table.js CHANGED
@@ -1,81 +1,46 @@
1
- /* Таблица объёма файлов по коммитампереносимый генератор.
2
- *
3
- * Зачем. Объём проекта обсуждается числами регулярно (здесь WORKLOG §19–§21),
4
- * и каждый раз это был ручной замер двух ревизий. Генератор делает замер
5
- * непрерывным: строка коммит, колонка файл, в клетке изменение к
6
- * предыдущему коммиту по каждой метрике (`raw` — файл как он есть,
7
- * `min` форма без комментариев и отступов), а абсолютные размеры стоят один
8
- * раз, в верхней строке «сейчас» (иначе крупное число повторялось бы в каждой
9
- * строке, и колонки расползались бы на экраны вширь).
10
- *
11
- * Источник правды — сам git: размеры берутся из блобов коммитов, а не из
12
- * рабочего дерева. Поэтому таблица не зависит от того, что открыто в редакторе,
13
- * и собирается заново по всей истории, а не дописывается инкрементально
14
- * (инкрементальный файл пришлось бы чинить после любой правки старых чисел).
15
- *
16
- * **Проектное в конфиге, механика в пакете.** Движок не знает ни имён файлов
17
- * проекта, ни имени журнала, ни языка подписей: колонки, метрики, журнал,
18
- * локаль, куда писать всё в `size-table.config.json` рядом с корнем
19
- * репозитория (`--config` — другой путь). Поэтому пакет подключается к новому
20
- * проекту как зависимость, а `size --init` подбирает там черновик конфига
21
- * (какие расширения в проекте, где журнал, куда писать), который дальше
22
- * правится глазами.
23
- *
24
- * Строку получает коммит, сдвинувший хотя бы одно число, включая merge: у
25
- * слияния берётся дифф к первому родителю, поэтому его правки видны и в строке,
26
- * и в переносе состояния. Не получают строку коммиты, тронувшие лишь сам файл
27
- * таблицы всё, что перечислено в `skip`) строка про коммит не может лежать
28
- * внутри самого коммита (sha на момент сборки ещё неизвестен), поэтому
29
- * обновление таблицы отдельный коммит, и коммиты, у которых все клетки
30
- * вышли нулевыми (слияние, разрешённое ровно в то, что уже дала ветка): строка
31
- * без единого числа читается как поломка. Отсюда же
32
- * требование к конфигу: колонки обязаны покрывать всё, что коммит может
33
- * изменить. Коммит мимо колонок дал бы строку без единого числа, а пустая
34
- * клетка в таблице означает «файла в этой ревизии ещё нет»читается как
35
- * поломка (это стережёт тест).
36
- * Если в конфиге выключить sha в строках (`rows.sha: false`), тот же инвариант
37
- * начинает работать и для стратегии «пересобрать и дописать в тот же коммит»:
38
- * без sha артефакт становится неподвижной точкой сборки.
39
- *
40
- * Отчёт один: самодостаточная страница (`size-report.html`), в которой лежат и
41
- * данные, и оформление, и программа. Второй формы того же отчёта нет намеренно: два
42
- * вывода одной истории разошлись бы молча, а выбрать, какой верный, было бы нечем.
43
- *
44
- * Запуск (из любого места репозитория; `size` — когда пакет установлен, иначе
45
- * `node bin/size.js`):
46
- * size проверка: отчёт совпадает с историей (CI)
47
- * size --write [файл] перегенерировать отчёт
48
- * size --json строки как JSON в stdout
49
- * size --data данные для отчёта и агента в stdout
50
- * size --init [файл] закрепить настройки файлом (без него они выводятся из проекта)
51
- * size --config <путь> другой файл настроек
52
- * size --help справка и коды выхода
53
- *
54
- * Требуется полная история: на обрезанном клоне (shallow) скрипт отказывается
55
- * работать, а не пишет молча короткую таблицу. В CI — `fetch-depth: 0`.
1
+ /* Size of files by committhe portable generator.
2
+ *
3
+ * A row is a commit, a column is a file, a cell is the change against the previous commit
4
+ * for one metric; absolute sizes appear once, in the top "now" row, or a large number
5
+ * would repeat in every row and the columns would run off the screen.
6
+ *
7
+ * The source of truth is git itself: sizes come from the blobs of the commits, not from
8
+ * the working tree, so the table does not depend on what is open in an editor, and it is
9
+ * rebuilt from the whole history rather than appended to — an incremental file would have
10
+ * to be repaired after any change to an old number.
11
+ *
12
+ * **Project matters in the config, mechanics in the package.** The engine knows neither
13
+ * the file names of the project nor the name of its journal nor the language of the
14
+ * labels: columns, metrics, journal, locale and output path all live in
15
+ * `size-table.config.json` next to the repository root (`--config` names another path).
16
+ * That is why the package can be attached to a new project as a dependency, and why
17
+ * `size --init` can draft a config there (which extensions the project has, where its
18
+ * journal is, where to write), to be edited by eye afterwards.
19
+ *
20
+ * A commit gets a row when it moved at least one number, merges included: a merge is
21
+ * diffed against its first parent, so its edits show both in the row and in the carried
22
+ * state. No row goes to a commit that touched only the report itself (or anything else
23
+ * listed in `skip`) — a row about a commit cannot live inside that commit, whose sha is
24
+ * unknown while it is being built, and that is why a rebuilt report is a commit of its
25
+ * own nor to a commit whose cells all came out zero (a merge resolved into exactly what
26
+ * the branch already gave): a row without a single number reads as a breakdown. Hence the
27
+ * requirement on the config: the columns must cover everything a commit can change,
28
+ * because a commit outside the columns would give such a row, while an empty cell already
29
+ * means "the file is not in that revision yet". With `rows.sha: false` the same invariant
30
+ * serves the other strategy rebuild and amend into the same commit — because without
31
+ * the sha the artifact becomes a fixed point.
32
+ *
33
+ * One report: a single self-contained page holding the data, the styling and the program.
34
+ * A second form of the same report does not exist on purpose two outputs of one history
35
+ * would diverge silently, with nothing to tell which one is right.
36
+ *
37
+ * The full history is required: on a shallow clone it refuses instead of silently writing
38
+ * a short table (`fetch-depth: 0` in CI). Modes and exit codes: `size --help`.
56
39
  */
57
40
 
58
- /* Точка входа пакета и только она: здесь нет ни одного расчёта, только
59
- * реэкспорт. Механика разложена по швам, которые видно по зависимостям:
60
- *
61
- * refusal, locales, journal, tool, css, derived — ни на чём не стоят;
62
- * parse → parse-worker — разбор модуля вне процесса;
63
- * strip → refusal, parse — снятие балласта и гард;
64
- * metrics → strip — реестр метрик;
65
- * git → refusal — всё, что читается у git;
66
- * project → git, refusal — что проект говорит о себе сам;
67
- * config → project, git, refusal, locales, metrics, data — настройки проекта;
68
- * history → git, metrics, journal, refusal — сборка по истории;
69
- * data → locales, metrics, journal, history, project, tool — контракт со страницей;
70
- * page/build → locales, css — отчёт одним файлом;
71
- * artifact → data, page/build — запись отчёта;
72
- * modes → почти все — что делать по запросу;
73
- * init → config, project, refusal, artifact — закрепление настроек файлом;
74
- * cli → args, modes, init, config, refusal — вход: разбор и доставка.
75
- *
76
- * Публичный API — то, чем пользуются `bin/size.js` и `test/`: список ниже не
77
- * сокращается при разбиении (это проверяет `test/api.test.js`).
78
- */
41
+ /* The entry point of the package, and nothing else: no computation here, only re-exports.
42
+ * The mechanics are laid out along the seams visible in the imports. The public API is a
43
+ * frozen list — `test/api.test.js` does not let it shrink. */
79
44
  export { main } from './cli.js';
80
45
  export { initMode } from './init.js';
81
46
  export { sniffColumns } from './project.js';
@@ -1,15 +1,15 @@
1
1
  import { stripJs } from './js.js';
2
2
 
3
- /* Формы текста, у которых снятие балласта своё: разметка, стили, строки файла и
4
- * JSON. Разные формы разные правила, и одно правило на все было бы либо
5
- * трусостью (не снимать ничего), либо порчей чужого синтаксиса. */
3
+ /* Text forms with a stripping rule of their own: markup, styles, the lines of a file and
4
+ * JSON. One rule for all of them would either strip nothing at all or corrupt a syntax it was
5
+ * never taught. */
6
6
 
7
7
  export function stripCss(src) {
8
8
  return src.replace(/\/\*[\s\S]*?\*\//g, ' ');
9
9
  }
10
10
 
11
- /* HTML: комментарии разметки (включая маркеры вклеек `<!--icon …-->` и
12
- * `<!--/icon-->`), комментарии внутри <script> как JS и внутри <style> как CSS. */
11
+ /* HTML: markup comments, and the comments inside <script> as JS and inside <style> as CSS —
12
+ * each part stripped by the rule of its own form rather than by the markup one. */
13
13
  export function stripHtml(src) {
14
14
  return src
15
15
  .replace(/<!--[\s\S]*?-->/g, '')
@@ -3,52 +3,52 @@ import vm from 'vm';
3
3
  import { refuseCause } from '../refusal.js';
4
4
  import { moduleError } from '../parse.js';
5
5
 
6
- /* Гард стриппера: он не имеет права выбросить что-то кроме комментариев и
7
- * отступов, поэтому результат обязан компилироваться. Проверяем только те
8
- * расширения, где содержимое валидный JavaScript (список в конфиге,
9
- * `minify.guard`): TypeScript или JSX хостом не проверяются, и делать вид, что
10
- * проверили, было бы хуже, чем не проверять.
6
+ /* The stripper's guard: it may throw away nothing but comments and indentation, so its result
7
+ * has to compile. Only the extensions whose content is valid JavaScript are checked (the list
8
+ * is `minify.guard` in the settings): TypeScript or JSX are not checked by the host, and
9
+ * pretending they were would be worse than not checking at all.
11
10
  *
12
- * Модуль или скрипт решает текст, а не расширение: проект с бандлером пишет
13
- * `import`/`export` прямо в `.js` (и с `type: module` в манифесте, и без него), а
14
- * `vm.Script` разбирает такой файл как скрипт и падает на самом `export`. Гард
15
- * обязан понимать оба формата, поэтому пробует тот, на который файл похож, и
16
- * принимает результат, если он разбирается хотя бы одним из двух способов.
17
- * От этого он не слабеет: настоящая поломка не разберётся ни скриптом, ни
18
- * модулем, и тогда наружу идёт причина того разбора, которым файл был.
11
+ * The text, not the extension, decides between a module and a script: a project with a
12
+ * bundler writes `import`/`export` straight into `.js` (with `type: module` in its manifest
13
+ * and without it), while `vm.Script` parses such a file as a script and fails on the very
14
+ * `export`. So the guard tries the shape the file looks like and accepts the result when
15
+ * either of the two parses it. That does not weaken it: a real breakage parses neither way,
16
+ * and then the reason reported is the one from the shape the file had.
19
17
  *
20
- * Модуль разбирает отдельный рабочий поток (`parse.js`): без него разбор модуля
21
- * стоил бы запуска Node на каждую клетку. Иначе конфиг вида `eslint.config.mjs`
22
- * остался бы без гарда, а без гарда его правка могла бы испортить «объём» молча.
18
+ * A module is parsed by a separate worker (`parse.js`), or the guard would cost a Node run per
19
+ * cell: without it a config like `eslint.config.mjs` would stay unguarded, and an edit of it
20
+ * could silently spoil the "volume" number.
23
21
  *
24
- * Когда не разбирается даже исходный текст, стриппер тут ни при чём: в этой
25
- * графе измеряется не JavaScript (TypeScript, JSX), и это отказ с командой
26
- * починки правкой настроек. */
22
+ * When even the original text does not parse, the stripper is not to blame: the cell holds
23
+ * something other than JavaScript (TypeScript, JSX), and that is a refusal with a fix —
24
+ * changing the settings. */
27
25
 
28
26
  const MODULE_MARK = /^[ \t]*(?:import|export)\b/m;
29
27
  const MODULE_EXT = ['.mjs'];
30
28
 
31
29
  export function assertCompilable(min, rev, p, src) {
32
- // Скрипт пробуется первым не ради формы, а ради цены: этот разбор идёт
33
- // в процессе, а модуль в рабочем потоке.
30
+ // The script is tried first for its price rather than its shape: it parses in this
31
+ // process, while a module goes to the worker.
34
32
  const asScript = scriptError(min, p);
35
33
  if (asScript === null) return;
36
34
  const asModule = moduleError(min);
37
35
  if (asModule === null) return;
38
36
  const shape = MODULE_EXT.indexOf(path.extname(p).toLowerCase()) >= 0 || MODULE_MARK.test(min);
39
37
  if (src !== undefined && scriptError(src, p) !== null && moduleError(src) !== null) {
40
- refuseCause('файл не JavaScript', 'файл ' + p + ' не JavaScript: его исходный текст не'
41
- + ' разбирается ни как скрипт, ни как модуль, так что дело не в стриптере, а '
42
- + path.extname(p) + ' стоит в minify.guard: ' + (shape ? asModule : asScript) + '\n'
43
- + ' починка: уберите это расширение из minify.guard или задайте для него '
44
- + 'minify.ext — например { "' + path.extname(p).toLowerCase() + '": "strip-lines" }');
38
+ refuseCause('file is not JavaScript', 'the file ' + p + ' is not JavaScript: its source text parses'
39
+ + ' neither as a script nor as a module, so the stripper is not to blame, while '
40
+ + path.extname(p) + ' stands in minify.guard: ' + (shape ? asModule : asScript) + '\n'
41
+ + ' fix: remove this extension from minify.guard or give it '
42
+ + 'minify.ext — for example { "' + path.extname(p).toLowerCase() + '": "strip-lines" }');
45
43
  }
46
- // Причина того разбора, которым файл был: обвинять в чужой форме незачем.
47
- throw new Error('стриппер испортил ' + p + ' на ' + rev.slice(0, 7) + ': '
44
+ /* The reason comes from the parse the file actually was: blaming the other shape would explain nothing.
45
+ * A revision is optional: the assembler of the report's page squeezes a text it built itself, and there is no
46
+ * revision to name it by — then the message says only what broke. */
47
+ throw new Error('the stripper broke ' + p + (rev === '' ? '' : ' at ' + rev.slice(0, 7)) + ': '
48
48
  + (shape ? asModule : asScript));
49
49
  }
50
50
 
51
- // Разбор как скрипт в процессе: дешевле и без временных файлов.
51
+ // Parsing as a script happens in this process: cheaper, and without temporary files.
52
52
  function scriptError(text, p) {
53
53
  try {
54
54
  new vm.Script(text, { filename: p });