@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.
- package/README.md +989 -1081
- package/bin/postinstall.js +17 -18
- package/bin/size.js +2 -2
- package/package.json +3 -4
- package/src/args.js +72 -72
- package/src/artifact.js +14 -14
- package/src/check.js +41 -42
- package/src/cli.js +26 -29
- package/src/config.js +87 -91
- package/src/css.js +14 -14
- package/src/data.js +26 -50
- package/src/derived.js +31 -35
- package/src/doctor.js +95 -99
- package/src/explain.js +46 -47
- package/src/git.js +66 -71
- package/src/history.js +74 -83
- package/src/hook.js +130 -149
- package/src/init.js +37 -37
- package/src/journal.js +17 -15
- package/src/locales.js +31 -22
- package/src/metrics.js +72 -89
- package/src/minify.js +28 -27
- package/src/modes.js +57 -60
- package/src/optional.js +13 -11
- package/src/page/app.css +76 -94
- package/src/page/app.js +124 -80
- package/src/page/build.js +193 -50
- package/src/page/dom.js +8 -9
- package/src/page/panel.js +157 -69
- package/src/page/payload.js +168 -0
- package/src/page/state.js +144 -104
- package/src/page/table.js +270 -86
- package/src/parse-worker.js +10 -10
- package/src/parse.js +43 -45
- package/src/project.js +100 -104
- package/src/refusal.js +75 -76
- package/src/size-table.js +41 -76
- package/src/strip/forms.js +5 -5
- package/src/strip/guard.js +28 -28
- package/src/strip/js.js +27 -27
- package/src/strip.js +17 -21
- package/src/table.css +54 -19
- package/src/tokens.js +27 -27
- package/src/tool.js +10 -11
- package/templates/README.md +71 -77
- package/templates/ci.yml +33 -33
- package/templates/size-report.config.json +3 -3
- package/CHANGELOG.md +0 -690
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
|
-
/*
|
|
12
|
-
*
|
|
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
|
-
* -
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
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
|
-
*
|
|
56
|
-
*
|
|
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
|
-
//
|
|
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
|
-
/*
|
|
63
|
-
*
|
|
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
|
-
/*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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 + ':
|
|
129
|
-
+ '#
|
|
130
|
-
+ '#
|
|
131
|
-
+ '#
|
|
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
|
-
/*
|
|
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
|
-
//
|
|
166
|
-
//
|
|
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
|
-
+ '
|
|
170
|
-
+ '
|
|
171
|
-
+ (spec === null ? '' : ' (
|
|
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('
|
|
177
|
-
+ '
|
|
178
|
-
+ '
|
|
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('
|
|
185
|
-
+ '
|
|
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
|
-
'·
|
|
192
|
-
'
|
|
193
|
-
'
|
|
194
|
-
'
|
|
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
|
-
'✓
|
|
205
|
-
'
|
|
206
|
-
+ '
|
|
207
|
-
'
|
|
208
|
-
'
|
|
209
|
-
'
|
|
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('!
|
|
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
|
-
/*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
*
|
|
203
|
+
/* Installing without being asked. It answers with the list of paths when it installed and with `null` when it touched
|
|
204
|
+
* nothing — the 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
|
-
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
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
|
-
/*
|
|
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: ['·
|
|
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('
|
|
261
|
-
+ '
|
|
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: ['✓
|
|
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
|
-
/*
|
|
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
|
-
/*
|
|
319
|
-
*
|
|
320
|
-
*
|
|
321
|
-
*
|
|
322
|
-
*
|
|
323
|
-
*
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
373
|
+
return record(root, { result: 'skipped', head: sha, report: cfg.output, why: 'switched off by the hooks.enabled setting' });
|
|
391
374
|
}
|
|
392
|
-
/*
|
|
393
|
-
*
|
|
394
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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:
|
|
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: '
|
|
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):
|
|
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:
|
|
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 + '
|
|
428
|
+
'✓ size-report: ' + cfg.output + ' rebuilt and committed (' + commit.sha + ')');
|
|
448
429
|
}
|