@vernikr/size-report 1.3.0 → 2.0.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/CHANGELOG.md +81 -0
- package/README.md +53 -40
- package/bin/postinstall.js +51 -0
- package/package.json +4 -3
- package/src/args.js +4 -4
- package/src/artifact.js +25 -16
- package/src/cli.js +16 -4
- package/src/config.js +5 -4
- package/src/css.js +7 -16
- package/src/hook.js +42 -4
- package/src/locales.js +5 -4
- package/src/modes.js +25 -34
- package/src/page/app.css +30 -0
- package/src/page/build.js +23 -4
- package/src/project.js +4 -1
- package/src/refusal.js +8 -8
- package/src/size-table.js +11 -9
- package/templates/README.md +6 -1
- package/templates/ci.yml +6 -5
- package/templates/size-report.config.json +1 -1
- package/src/artifact.css +0 -8
- package/src/render.js +0 -139
package/src/css.js
CHANGED
|
@@ -5,31 +5,22 @@ import fs from 'node:fs';
|
|
|
5
5
|
* редактор, а не только шаблонная строка. Читаются они с диска относительно
|
|
6
6
|
* своего места, поэтому работают и у того, кто поставил пакет.
|
|
7
7
|
*
|
|
8
|
-
* Наборов
|
|
8
|
+
* Наборов два, и у каждого своя роль:
|
|
9
9
|
*
|
|
10
|
-
* 1. `table.css` —
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* текст этой части, без шапки: он попадает в артефакт побайтово, а артефакт
|
|
14
|
-
* заморожен эталоном паритета, и любая добавленная строка меняла бы
|
|
15
|
-
* зафиксированный вывод проекта-потребителя.
|
|
16
|
-
* 2. `artifact.css` — оформление статического артефакта **сверх таблицы**: холст,
|
|
17
|
-
* заголовок, примечание.
|
|
18
|
-
* 3. `page/app.css` — оформление страницы **сверх таблицы**: холст, панель
|
|
10
|
+
* 1. `table.css` — **таблица**: геометрия клеток, липкие шапка и колонка коммита,
|
|
11
|
+
* подпись коммита, цвета дельт.
|
|
12
|
+
* 2. `page/app.css` — оформление страницы **сверх таблицы**: холст, панель
|
|
19
13
|
* выбора, легенда, состояния пустоты и адаптации под узкое окно.
|
|
20
14
|
*
|
|
21
15
|
* Соглашение о цвете дельт задано один раз — в `table.css`: `.up` зелёный, `.down`
|
|
22
|
-
* красный (рост — «больше логики», а не тревога).
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* разными цветами. Смена соглашения — две строки в `table.css` и пересъёмка
|
|
26
|
-
* эталона артефакта; отдельного места у цвета дельт нет намеренно.
|
|
16
|
+
* красный (рост — «больше логики», а не тревога). Второго места у него нет
|
|
17
|
+
* намеренно: рост не может быть показан разными цветами в двух местах одной
|
|
18
|
+
* страницы. Смена соглашения — две строки в `table.css`.
|
|
27
19
|
*
|
|
28
20
|
* Путь у `readCss` — от каталога `src/`: так его видит движок, где бы он ни лежал.
|
|
29
21
|
*/
|
|
30
22
|
|
|
31
23
|
export const TABLE_CSS = readCss('./table.css');
|
|
32
|
-
export const ARTIFACT_CSS = readCss('./artifact.css');
|
|
33
24
|
export const PAGE_CSS = readCss('./page/app.css');
|
|
34
25
|
|
|
35
26
|
function readCss(name) {
|
package/src/hook.js
CHANGED
|
@@ -15,10 +15,15 @@ import { rebuild } from './artifact.js';
|
|
|
15
15
|
*
|
|
16
16
|
* Что он делает и почему именно так:
|
|
17
17
|
*
|
|
18
|
-
* - **Ставится
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
18
|
+
* - **Ставится сам** — после установки пакета (`bin/postinstall.js`) и при первом
|
|
19
|
+
* запуске в проекте (`autoInstall`, зовётся из входа): от человека не требуется
|
|
20
|
+
* ни ручного шага, ни файла настроек, иначе первого обновления отчёта он не
|
|
21
|
+
* увидел бы вовсе. Ставится там, где это безопасно (обычный `.git/hooks`, нет
|
|
22
|
+
* чужого хука, есть чем звать инструмент, не CI); где небезопасно — молчит.
|
|
23
|
+
* Снимается явной командой (`uninstall-hook`), и проект возвращается к прежнему
|
|
24
|
+
* поведению: и поставленное, и снятое — одно и то же место состояния (`git-dir`).
|
|
25
|
+
* Ручная команда (`install-hook`) остаётся: она называет причину, когда поставить
|
|
26
|
+
* не удалось, а тихая постановка причин не объясняет.
|
|
22
27
|
* - **Сам коммитов не создаёт** — за одним исключением: отчёт, лежащий в git,
|
|
23
28
|
* коммитится отдельно от кода. Раньше это делал человек (отсюда ловушка «правка
|
|
24
29
|
* кода и таблицы в одном коммите»), и хук для того и нужен, чтобы ручного шага не
|
|
@@ -185,6 +190,7 @@ export function installHook(root, cfg) {
|
|
|
185
190
|
return { code: EXIT.OK, lines: [
|
|
186
191
|
'· хук уже установлен: ' + rels.join(', '),
|
|
187
192
|
' автоматика работает после каждого коммита и слияния',
|
|
193
|
+
' выключить, не снимая: «"hooks": {"enabled": false}» в файле настроек',
|
|
188
194
|
' снять: ' + cliCommand('uninstall-hook')
|
|
189
195
|
] };
|
|
190
196
|
}
|
|
@@ -208,6 +214,38 @@ export function installHook(root, cfg) {
|
|
|
208
214
|
return { code: EXIT.OK, lines: lines };
|
|
209
215
|
}
|
|
210
216
|
|
|
217
|
+
/* Постановка без спроса. Отвечает списком путей, если поставила, и `null`, если не
|
|
218
|
+
* тронула ничего, — второй ответ не ошибка, а норма: эта услуга фоновая, и там, где
|
|
219
|
+
* она не к месту, её просто нет. Поэтому всё, что мешает поставить, решается
|
|
220
|
+
* молчанием, а не отказом: отказ от фоновой работы после каждого запуска был бы
|
|
221
|
+
* шумом, а причина уже названа точной командой (`install-hook`).
|
|
222
|
+
*
|
|
223
|
+
* Чужой `core.hooksPath` сюда же: этот каталог версионируется и часто лежит в
|
|
224
|
+
* другом репозитории — вписывать строку в чужой файл по своей воле нельзя, и
|
|
225
|
+
* человек берёт её у `install-hook` (готовую и без метки). */
|
|
226
|
+
export function autoInstall(root, cfg) {
|
|
227
|
+
if (process.env.CI || process.env[NO_HOOK]) return null;
|
|
228
|
+
if (cfg !== null && cfg.hooks.enabled === false) return null;
|
|
229
|
+
try {
|
|
230
|
+
const hooks = hooksDir(root);
|
|
231
|
+
const entry = hookEntry(root);
|
|
232
|
+
if (hooks.custom || entry === null) return null;
|
|
233
|
+
const files = HOOKS.map((name) => path.join(hooks.dir, name));
|
|
234
|
+
if (files.some((f) => fs.existsSync(f) && !isOurs(f))) return null;
|
|
235
|
+
if (files.every(isOurs)) return null;
|
|
236
|
+
fs.mkdirSync(hooks.dir, { recursive: true });
|
|
237
|
+
files.forEach((file) => {
|
|
238
|
+
fs.writeFileSync(file, script(entry));
|
|
239
|
+
fs.chmodSync(file, 0o755);
|
|
240
|
+
});
|
|
241
|
+
return files.map((f) => path.relative(root, f));
|
|
242
|
+
} catch (_e) {
|
|
243
|
+
/* Не git-репозиторий, нет прав на `.git`, чужой формат — всё это значит одно:
|
|
244
|
+
* автоматики здесь не будет, а работа инструмента от неё не зависит. */
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
211
249
|
/* Снятие: убирается только то, что поставили мы. Файл не «похож на наш», а помечен
|
|
212
250
|
* меткой, иначе чужой хук был бы стёрт молча. */
|
|
213
251
|
export function uninstallHook(root) {
|
package/src/locales.js
CHANGED
|
@@ -12,14 +12,15 @@ export const LOCALES = {
|
|
|
12
12
|
total: 'Общий объём',
|
|
13
13
|
now: 'сейчас',
|
|
14
14
|
categories: { code: 'Код', docs: 'Документация', chore: 'Служебные', assets: 'Ресурсы' },
|
|
15
|
-
/* Тексты страницы
|
|
16
|
-
*
|
|
15
|
+
/* Тексты страницы отчёта. Они лежат в самом файле отчёта (отдельным словарём,
|
|
16
|
+
* рядом с данными), поэтому меняются вместе с ним — и правка слова стоит
|
|
17
|
+
* пересборки отчёта, иначе файл разойдётся с историей. */
|
|
17
18
|
page: {
|
|
18
19
|
metrics: 'Метрики',
|
|
19
20
|
files: 'Файлы',
|
|
20
21
|
dir: 'все файлы папки {name} ({n})',
|
|
21
22
|
all: 'все',
|
|
22
|
-
sub: '{tool} {version} ·
|
|
23
|
+
sub: '{tool} {version} · {artifact}',
|
|
23
24
|
legendUp: 'рост',
|
|
24
25
|
legendDown: 'спад',
|
|
25
26
|
legendSame: 'пустая клетка — не менялось',
|
|
@@ -75,7 +76,7 @@ export const LOCALES = {
|
|
|
75
76
|
files: 'Files',
|
|
76
77
|
dir: 'all files in “{name}” ({n})',
|
|
77
78
|
all: 'all',
|
|
78
|
-
sub: '{tool} {version} ·
|
|
79
|
+
sub: '{tool} {version} · {artifact}',
|
|
79
80
|
legendUp: 'growth',
|
|
80
81
|
legendDown: 'fall',
|
|
81
82
|
legendSame: 'an empty cell — no change',
|
package/src/modes.js
CHANGED
|
@@ -9,11 +9,9 @@ import { coverage, coverageText } from './check.js';
|
|
|
9
9
|
import { explainCommit, explainText } from './explain.js';
|
|
10
10
|
import { doctor, doctorText } from './doctor.js';
|
|
11
11
|
import { hookRun, installHook, uninstallHook } from './hook.js';
|
|
12
|
-
import {
|
|
12
|
+
import { artifact, rebuild } from './artifact.js';
|
|
13
13
|
import { sensorGaps } from './metrics.js';
|
|
14
|
-
import { render } from './render.js';
|
|
15
14
|
import { totalsOf } from './derived.js';
|
|
16
|
-
import { pageHtml } from './page/build.js';
|
|
17
15
|
|
|
18
16
|
/* Режимы: что инструмент делает по запросу. Разбор аргументов — в `src/args.js`, а
|
|
19
17
|
* сюда приходит готовый план: какой режим, какой ключ, что печатать. Здесь же их
|
|
@@ -21,10 +19,10 @@ import { pageHtml } from './page/build.js';
|
|
|
21
19
|
* на все режимы, потому что один и тот же счёт и один и тот же знак не должны
|
|
22
20
|
* разойтись между `--write`, `--data`, `--page` и `size check`.
|
|
23
21
|
*
|
|
24
|
-
* Что где: сборка и сверка
|
|
25
|
-
* (`--data
|
|
26
|
-
*
|
|
27
|
-
*
|
|
22
|
+
* Что где: сборка и сверка отчёта (`--write`, проверка), данные контракта
|
|
23
|
+
* (`--data`), полнота покрытия (`size check`), диагностика (`doctor`), хук и
|
|
24
|
+
* объяснение пропущенной строки. Файл знает про все остальные модули сразу — это
|
|
25
|
+
* его работа: связать их в одну команду.
|
|
28
26
|
*/
|
|
29
27
|
|
|
30
28
|
function kmb(bytes) {
|
|
@@ -81,23 +79,31 @@ export function check(cfg, want, root) {
|
|
|
81
79
|
return 1;
|
|
82
80
|
}
|
|
83
81
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
82
|
+
/* Путь, названный ключом (`--write <файл>`), — это настройка `output` этого
|
|
83
|
+
* запуска: отчёт обязан называть себя тем путём, по которому лежит, иначе подпись в
|
|
84
|
+
* нём указывала бы на чужое место. */
|
|
85
|
+
function withOutput(cfg, root, file) {
|
|
86
|
+
if (typeof file !== 'string') return cfg;
|
|
87
|
+
return Object.assign({}, cfg, { output: path.relative(root, path.resolve(file)) });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function writeMode(cfg, root, file) {
|
|
91
|
+
const out = rebuild(withOutput(cfg, root, file), root);
|
|
92
|
+
const { rows, files, now, skipped } = out.data;
|
|
93
|
+
console.log('✓ ' + path.relative(root, out.file) + ': ' + rows.length + ' строк × ' + files.length + ' файлов, '
|
|
94
|
+
+ kmb(byteLen(out.html)) + ' (пропущено без строки: ' + skipped.length + ' — '
|
|
95
|
+
+ skipped.join(', ') + ')');
|
|
96
|
+
console.log(' состояние на HEAD: ' + files.map((f, i) => f.label + ' '
|
|
97
|
+
+ (now[i] === null ? '—' : cfg.metrics.map((m) => now[i][m]).join('/'))).join(', '));
|
|
91
98
|
return sensorNote(cfg);
|
|
92
99
|
}
|
|
93
100
|
|
|
94
101
|
export function checkMode(cfg, root) {
|
|
95
|
-
const
|
|
96
|
-
const
|
|
97
|
-
const code = check(cfg, html, root);
|
|
102
|
+
const out = artifact(cfg, root);
|
|
103
|
+
const code = check(cfg, out.html, root);
|
|
98
104
|
if (code === 0) {
|
|
99
|
-
console.log('✓
|
|
100
|
-
+ 'совпадает с историей (' + cfg.output + ', ' + kmb(byteLen(html)) + ')');
|
|
105
|
+
console.log('✓ отчёт: ' + out.data.rows.length + ' коммитов × ' + out.data.files.length + ' файлов '
|
|
106
|
+
+ 'совпадает с историей (' + cfg.output + ', ' + kmb(byteLen(out.html)) + ')');
|
|
101
107
|
}
|
|
102
108
|
return verdict(code, sensorGaps(cfg));
|
|
103
109
|
}
|
|
@@ -162,21 +168,6 @@ export function dataMode(cfg, root) {
|
|
|
162
168
|
return sensorNote(cfg);
|
|
163
169
|
}
|
|
164
170
|
|
|
165
|
-
/* Страница отчёта: собирается тем же проходом по истории, что и артефакт — иначе
|
|
166
|
-
* два отчёта могли бы показывать разные числа. Файл кладётся рядом с таблицей,
|
|
167
|
-
* потому что он из неё и растёт. */
|
|
168
|
-
const PAGE_NAME = 'size-report.html';
|
|
169
|
-
|
|
170
|
-
export function pageMode(cfg, root, file) {
|
|
171
|
-
const data = reportData(cfg, root);
|
|
172
|
-
const target = file ? path.resolve(file) : path.join(root, path.dirname(cfg.output), PAGE_NAME);
|
|
173
|
-
const html = pageHtml(data, cfg);
|
|
174
|
-
writeFileEnsured(target, html);
|
|
175
|
-
console.log('✓ ' + path.relative(root, target) + ': ' + data.rows.length + ' строк × '
|
|
176
|
-
+ data.files.length + ' файлов, ' + kmb(byteLen(html)));
|
|
177
|
-
return sensorNote(cfg);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
171
|
export function jsonMode(cfg, root) {
|
|
181
172
|
const { rows, dropped } = build(cfg, root);
|
|
182
173
|
process.stdout.write(JSON.stringify({
|
package/src/page/app.css
CHANGED
|
@@ -154,6 +154,36 @@ h1 { margin: 0 0 3px; font-size: 21px; font-weight: 650; letter-spacing: -.012em
|
|
|
154
154
|
* сдвигает разметку. Ссылки журнала и все переключатели доступны с Tab. */
|
|
155
155
|
:focus-visible { outline: 2px solid AccentColor; outline-offset: 2px; border-radius: 3px; }
|
|
156
156
|
|
|
157
|
+
/* Широкая страница: панель выбора (метрики, дерево файлов, легенда) стоит **слева**
|
|
158
|
+
* от таблицы. Это не украшение: на десктопе бокового места много, а вертикального
|
|
159
|
+
* мало — переключатели и числа видны одновременно, и прокрутка страницы не уводит
|
|
160
|
+
* управление за экран. Узкое окно эту же раскладку снимает (ниже): там столбцы
|
|
161
|
+
* снова идут друг под другом, потому что рядом им не хватает ширины.
|
|
162
|
+
*
|
|
163
|
+
* Раскладка — сетка на `body`, а не обёртка в разметке: страница собирается
|
|
164
|
+
* вклейкой глав (`src/page/build.js`), и добавлять ей узлы ради оформления значило
|
|
165
|
+
* бы менять форму страницы в двух местах вместо одного.
|
|
166
|
+
*
|
|
167
|
+
* Порог 900px — тот же, что у адаптаций ниже: одна граница на «широко» и «узко»,
|
|
168
|
+
* иначе между двумя порогами страница осталась бы без ни одного правила. */
|
|
169
|
+
@media (min-width: 900px) {
|
|
170
|
+
body {
|
|
171
|
+
display: grid;
|
|
172
|
+
grid-template-columns: minmax(230px, 300px) minmax(0, 1fr);
|
|
173
|
+
column-gap: var(--gap);
|
|
174
|
+
/* Оба столбца начинаются сверху и не растягиваются под соседа: у панели своя
|
|
175
|
+
* высота, у таблицы своя. */
|
|
176
|
+
align-content: start;
|
|
177
|
+
align-items: start;
|
|
178
|
+
}
|
|
179
|
+
header { grid-column: 1 / -1; }
|
|
180
|
+
#panel { grid-column: 1; }
|
|
181
|
+
#notice, #shell, #state, #note { grid-column: 2; }
|
|
182
|
+
/* В столбце дерево прокручивается выше, чем над таблицей: место занимает
|
|
183
|
+
* боковая колонка, а числа не отодвигает. */
|
|
184
|
+
.panel .files { max-height: min(62vh, 620px); }
|
|
185
|
+
}
|
|
186
|
+
|
|
157
187
|
/* Адаптации: единственное место, где страница правит общую геометрию, — потому что
|
|
158
188
|
* общая часть заморожена байтами артефакта, а не потому что так удобнее. */
|
|
159
189
|
@media (max-width: 900px) {
|
package/src/page/build.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import { fill, LOCALES } from '../locales.js';
|
|
3
|
-
import { esc } from '../render.js';
|
|
4
3
|
import { PAGE_CSS, TABLE_CSS } from '../css.js';
|
|
5
4
|
|
|
5
|
+
/* Экранирование текста в разметке — здесь, потому что единственный, кто собирает
|
|
6
|
+
* разметку из данных, — эта сборка: остальное рисует страница узлами. */
|
|
7
|
+
export function esc(s) {
|
|
8
|
+
return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
9
|
+
}
|
|
10
|
+
|
|
6
11
|
/* Сборка страницы отчёта: данные и программа в одном файле, внешних ссылок нет.
|
|
7
12
|
* Оформление — тоже обычные файлы: общая часть таблицы (`table.css`) и своё
|
|
8
13
|
* оформление страницы (`app.css`).
|
|
@@ -37,8 +42,8 @@ export function pageScript() {
|
|
|
37
42
|
return pageSource('../derived.js') + '\n' + PAGE_PARTS.map((part) => pageSource(part)).join('');
|
|
38
43
|
}
|
|
39
44
|
|
|
40
|
-
/* Подпись под заголовком: чем
|
|
41
|
-
*
|
|
45
|
+
/* Подпись под заголовком: чем собран отчёт и где он лежит. Путь — текстом, а не
|
|
46
|
+
* ссылкой: страница открывается с диска и ни от чего не зависит. */
|
|
42
47
|
function subText(data, page) {
|
|
43
48
|
return fill(page.sub, {
|
|
44
49
|
tool: data.tool.name,
|
|
@@ -81,6 +86,20 @@ function uiText(page, loc) {
|
|
|
81
86
|
};
|
|
82
87
|
}
|
|
83
88
|
|
|
89
|
+
/* Что в файл не идёт. Первое — список пропущенных коммитов: он меняется от
|
|
90
|
+
* коммита самого отчёта (тот, кому нечего сказать, попадает в список), и файл
|
|
91
|
+
* перестал бы быть **неподвижной точкой** — пересборка после его же коммита давала
|
|
92
|
+
* бы другие байты, а хук коммитил бы отчёт бесконечно. Странице этот список не
|
|
93
|
+
* нужен вовсе: она его не показывает. Читателю он по-прежнему доступен — `--data`,
|
|
94
|
+
* `--json` и `explain` отвечают этим же проходом. */
|
|
95
|
+
const NOT_IN_FILE = ['skipped'];
|
|
96
|
+
|
|
97
|
+
export function pagePayload(data) {
|
|
98
|
+
const out = Object.assign({}, data);
|
|
99
|
+
NOT_IN_FILE.forEach((key) => delete out[key]);
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
|
|
84
103
|
/* Страница отчёта — один файл: данные лежат в нём же, скрипт вклеен, внешних
|
|
85
104
|
* ссылок нет. Поэтому она открывается двойным щелчком и работает без сети.
|
|
86
105
|
* `<` в данных экранируется: иначе подпись коммита или путь закрыли бы тег
|
|
@@ -98,7 +117,7 @@ export function pageHtml(data, cfg) {
|
|
|
98
117
|
+ '<div id="shell" class="shell"><table id="grid"></table></div>\n'
|
|
99
118
|
+ '<p id="state" class="state" hidden></p>\n'
|
|
100
119
|
+ '<p id="note" class="note"></p>\n'
|
|
101
|
-
+ '<script type="application/json" id="data">' + jsonInHtml(data) + '</script>\n'
|
|
120
|
+
+ '<script type="application/json" id="data">' + jsonInHtml(pagePayload(data)) + '</script>\n'
|
|
102
121
|
+ '<script type="application/json" id="ui">' + jsonInHtml(uiText(loc.page, loc)) + '</script>\n'
|
|
103
122
|
+ '<script>\n' + pageScript() + '</script>\n</body>\n</html>\n';
|
|
104
123
|
}
|
package/src/project.js
CHANGED
|
@@ -97,8 +97,11 @@ function exists(root, p) {
|
|
|
97
97
|
return fs.existsSync(path.join(root, p));
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
/* Имя отчёта — одно на пакет и на проект: файл это и есть страница отчёта, и
|
|
101
|
+
* называться иначе она не может. Каталог решает только, где ей лежать: рядом с
|
|
102
|
+
* доками, если они в проекте есть, иначе в корне. */
|
|
100
103
|
function outputOf(root) {
|
|
101
|
-
return (exists(root, 'docs') ? 'docs/' : '') + 'size-
|
|
104
|
+
return (exists(root, 'docs') ? 'docs/' : '') + 'size-report.html';
|
|
102
105
|
}
|
|
103
106
|
|
|
104
107
|
/* Менеджер пакетов — по lock-файлу, а не догадкой: команда обязана существовать
|
package/src/refusal.js
CHANGED
|
@@ -91,7 +91,8 @@ export function advicePath(p) {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
export const USAGE = [
|
|
94
|
-
'@vernikr/size-report —
|
|
94
|
+
'@vernikr/size-report — отчёт об объёме файлов по коммитам: один файл,',
|
|
95
|
+
'самодостаточная страница (данные, оформление и программа лежат в ней же).',
|
|
95
96
|
'',
|
|
96
97
|
'Запуск: ' + invocation() + ' [команда] [режим] [ключи]',
|
|
97
98
|
'',
|
|
@@ -101,19 +102,18 @@ export const USAGE = [
|
|
|
101
102
|
' explain <коммит> почему у коммита нет строки (имя ревизии, sha или его начало)',
|
|
102
103
|
' doctor [--json] диагностика одним ответом: окружение, зависимости, настройки,',
|
|
103
104
|
' покрытие (код 0 — делать нечего, иначе — первый по важности)',
|
|
104
|
-
' install-hook поставить хуки post-commit и post-merge
|
|
105
|
-
'
|
|
106
|
-
' отдельным коммитом',
|
|
105
|
+
' install-hook поставить хуки post-commit и post-merge (они ставятся сами при',
|
|
106
|
+
' первом запуске в проекте): отчёт пересобирается после каждого',
|
|
107
|
+
' коммита и слияния, а если он в git — ложится отдельным коммитом',
|
|
107
108
|
' uninstall-hook убрать хук и его состояние (проект возвращается к прежнему)',
|
|
108
109
|
' hook-run то, что зовёт хук: пересборка и коммит отчёта (вручную не нужно)',
|
|
109
110
|
'',
|
|
110
111
|
'Режимы:',
|
|
111
112
|
' --init [файл] закрепить настройки файлом (--force — перезаписать существующий)',
|
|
112
|
-
' --write
|
|
113
|
-
' --data данные контракта в stdout — для
|
|
114
|
-
' --page [файл] страница отчёта (по умолчанию рядом с таблицей)',
|
|
113
|
+
' --write [файл] собрать отчёт в файл из настроек (каталог создаётся сам)',
|
|
114
|
+
' --data данные контракта в stdout — для отчёта и для агента',
|
|
115
115
|
' --json прежняя форма данных в stdout',
|
|
116
|
-
' (без режима) проверить, что
|
|
116
|
+
' (без режима) проверить, что отчёт совпадает с историей',
|
|
117
117
|
'',
|
|
118
118
|
'Ключи: --config <файл> — другие настройки; --help — эта справка.',
|
|
119
119
|
'',
|
package/src/size-table.js
CHANGED
|
@@ -37,13 +37,16 @@
|
|
|
37
37
|
* начинает работать и для стратегии «пересобрать и дописать в тот же коммит»:
|
|
38
38
|
* без sha артефакт становится неподвижной точкой сборки.
|
|
39
39
|
*
|
|
40
|
+
* Отчёт один: самодостаточная страница (`size-report.html`), в которой лежат и
|
|
41
|
+
* данные, и оформление, и программа. Второй формы того же отчёта нет намеренно: два
|
|
42
|
+
* вывода одной истории разошлись бы молча, а выбрать, какой верный, было бы нечем.
|
|
43
|
+
*
|
|
40
44
|
* Запуск (из любого места репозитория; `size` — когда пакет установлен, иначе
|
|
41
45
|
* `node bin/size.js`):
|
|
42
|
-
* size проверка:
|
|
43
|
-
* size --write
|
|
46
|
+
* size проверка: отчёт совпадает с историей (CI)
|
|
47
|
+
* size --write [файл] перегенерировать отчёт
|
|
44
48
|
* size --json строки как JSON в stdout
|
|
45
|
-
* size --data данные для
|
|
46
|
-
* size --page [файл] собрать страницу отчёта
|
|
49
|
+
* size --data данные для отчёта и агента в stdout
|
|
47
50
|
* size --init [файл] закрепить настройки файлом (без него они выводятся из проекта)
|
|
48
51
|
* size --config <путь> другой файл настроек
|
|
49
52
|
* size --help справка и коды выхода
|
|
@@ -64,8 +67,8 @@
|
|
|
64
67
|
* config → project, git, refusal, locales, metrics, data — настройки проекта;
|
|
65
68
|
* history → git, metrics, journal, refusal — сборка по истории;
|
|
66
69
|
* data → locales, metrics, journal, history, tool — контракт со страницей;
|
|
67
|
-
*
|
|
68
|
-
*
|
|
70
|
+
* page/build → locales, css — отчёт одним файлом;
|
|
71
|
+
* artifact → data, page/build — запись отчёта;
|
|
69
72
|
* modes → почти все — что делать по запросу;
|
|
70
73
|
* init → config, project, refusal, artifact — закрепление настроек файлом;
|
|
71
74
|
* cli → args, modes, init, config, refusal — вход: разбор и доставка.
|
|
@@ -76,11 +79,10 @@
|
|
|
76
79
|
export { main } from './cli.js';
|
|
77
80
|
export { initMode } from './init.js';
|
|
78
81
|
export { sniffColumns } from './project.js';
|
|
79
|
-
export { check, dataMode,
|
|
82
|
+
export { check, dataMode, writeMode } from './modes.js';
|
|
80
83
|
export { reportData, categoryOf, CATEGORY_EXTS, CATEGORY_ORDER } from './data.js';
|
|
81
84
|
export { measureHistory } from './history.js';
|
|
82
|
-
export {
|
|
83
|
-
export { pageHtml, pageScript, pageSource, stripModules } from './page/build.js';
|
|
85
|
+
export { pageHtml, pageScript, pageSource, stripModules, esc } from './page/build.js';
|
|
84
86
|
export { measureBlob, METRICS } from './metrics.js';
|
|
85
87
|
export { minifyForm, strategyFor, stripCss, stripHtml, stripJs, stripLines, compactJson,
|
|
86
88
|
STRATEGIES } from './strip.js';
|
package/templates/README.md
CHANGED
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
| `size-report.config.json` | `size-table.config.json` в корне проекта | **Поправить колонки** и, если нужно, остальное |
|
|
12
12
|
| `ci.yml` | `.github/workflows/size-report.yml` | Ничего: файл работает как есть |
|
|
13
13
|
|
|
14
|
+
Отчёт в проекте появляется без ручной работы: после установки пакета и первого
|
|
15
|
+
запуска инструмент сам ставит хуки `post-commit`/`post-merge`, и `docs/` с
|
|
16
|
+
`size-report.html` создаётся первым же коммитом. Снять автоматику — `size
|
|
17
|
+
uninstall-hook`, выключить, не снимая, — `"hooks": {"enabled": false}`.
|
|
18
|
+
|
|
14
19
|
## Настройки
|
|
15
20
|
|
|
16
21
|
**Файл настроек заводить не нужно.** Без него инструмент выводит профиль из самого
|
|
@@ -51,7 +56,7 @@ README и журнал колонкой не отслеживаются, — т
|
|
|
51
56
|
именно этот объект выводит `size --init`, когда журнал в проекте есть.
|
|
52
57
|
- `paths` внутри колонки — псевдонимы одного файла: если файл переименовывали,
|
|
53
58
|
перечислите и старое имя, и новое, и колонка не разорвётся.
|
|
54
|
-
- `output: "docs/size-
|
|
59
|
+
- `output: "docs/size-report.html"` — файл отчёта (он один: самодостаточная страница со всеми числами, фильтрами и ссылкой); каталог инструмент создаст сам, а хук — тоже: после установки пакета и первого запуска отчёт обновляется после каждого коммита без ручного шага.
|
|
55
60
|
|
|
56
61
|
## Проверка в CI
|
|
57
62
|
|
package/templates/ci.yml
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
# Положите файл в .github/workflows/size-report.yml — правок он не требует.
|
|
3
3
|
#
|
|
4
4
|
# Что проверяется и почему так:
|
|
5
|
-
# *
|
|
5
|
+
# * отчёт на диске сходится с историей git — это его числа (отчёт один:
|
|
6
|
+
# самодостаточная страница `size-report.html`);
|
|
6
7
|
# * тот же снимок чисел, снятый в среде, где настроек git нет вовсе,
|
|
7
8
|
# совпадает побайтово: вывод инструмента не должен зависеть от того, что
|
|
8
9
|
# настроено на машине (BLOCKERS.md §B1, §B2).
|
|
@@ -23,7 +24,7 @@
|
|
|
23
24
|
# Нужен явный путь — `node node_modules/@vernikr/size-report/bin/size.js`.
|
|
24
25
|
#
|
|
25
26
|
# Отчёт не в git? Такое тоже задумано требованиями (отчёт — выводимый артефакт):
|
|
26
|
-
# тогда вместо шага
|
|
27
|
+
# тогда вместо шага «Отчёт совпадает с историей» поставьте сборку —
|
|
27
28
|
# `pnpm exec size --write` — и этот шаг будет проверять, что отчёт собирается.
|
|
28
29
|
|
|
29
30
|
name: size-report
|
|
@@ -34,7 +35,7 @@ jobs:
|
|
|
34
35
|
size:
|
|
35
36
|
runs-on: ubuntu-latest
|
|
36
37
|
steps:
|
|
37
|
-
# История нужна целиком:
|
|
38
|
+
# История нужна целиком: отчёт строится по коммитам, и на обрезанном
|
|
38
39
|
# клоне инструмент отказывается работать (код 3), а не пишет короткую.
|
|
39
40
|
- uses: actions/checkout@v7
|
|
40
41
|
with:
|
|
@@ -50,9 +51,9 @@ jobs:
|
|
|
50
51
|
- name: Установка
|
|
51
52
|
run: pnpm install --frozen-lockfile
|
|
52
53
|
|
|
53
|
-
# Проверка — та же команда `size` без ключей: она собирает
|
|
54
|
+
# Проверка — та же команда `size` без ключей: она собирает отчёт заново
|
|
54
55
|
# и сверяет с файлом на диске. Своего набора тестов потребителю не нужно.
|
|
55
|
-
- name:
|
|
56
|
+
- name: Отчёт совпадает с историей
|
|
56
57
|
run: pnpm exec size
|
|
57
58
|
|
|
58
59
|
- name: Снимок чисел контракта
|
package/src/artifact.css
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
:root { color-scheme: light dark; }
|
|
2
|
-
/* Фон и цвет текста заданы явно и одной парой (Canvas/CanvasText): без этого
|
|
3
|
-
* страница берёт цвет текста из схемы, а фон — нет, и в тёмной схеме числа
|
|
4
|
-
* оказывались белыми на белом. */
|
|
5
|
-
body { margin: 0; padding: 20px; background: Canvas; color: CanvasText; font: 12.5px/1.4 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; }
|
|
6
|
-
h1 { margin: 0 0 6px; font-size: 16px; }
|
|
7
|
-
.note { margin: 0 0 14px; max-width: 80em; opacity: .75; font-size: 12px; }
|
|
8
|
-
.note code { background: rgba(127, 127, 127, .15); padding: 0 3px; border-radius: 3px; }
|