synthesisui 0.16.253 → 0.16.254
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/dist/commands/import.js +170 -16
- package/dist/index.js +12 -2
- package/dist/merge-census.js +231 -0
- package/dist/name-claim.js +2 -2
- package/package.json +1 -1
package/dist/commands/import.js
CHANGED
|
@@ -35,6 +35,7 @@ import { definitionSpan, parseClass, readInlineStyle, rootClasses, rootTag, tran
|
|
|
35
35
|
import { transcribeVariants } from "../doctor/variant-read.js";
|
|
36
36
|
import { frontierKind, packageRoot } from "../frontier-kind.js";
|
|
37
37
|
import { withLibraryStructure } from "../library-structure.js";
|
|
38
|
+
import { mergeCensus } from "../merge-census.js";
|
|
38
39
|
import { claimName } from "../name-claim.js";
|
|
39
40
|
import { body, paint, section } from "../output.js";
|
|
40
41
|
import { phase, startProgress } from "../progress.js";
|
|
@@ -1147,13 +1148,39 @@ export async function takeCensus(root, opts) {
|
|
|
1147
1148
|
const edges = new Map();
|
|
1148
1149
|
/** Por raiz: `@/` é uma pasta diferente em cada app - ver `reachabilityOf`. */
|
|
1149
1150
|
const graphRoots = [];
|
|
1151
|
+
/**
|
|
1152
|
+
* A DISTÂNCIA, contada onde os arquivos já estão abertos - ver `Census.adoption`.
|
|
1153
|
+
*
|
|
1154
|
+
* `scopePkg` é o nome do pacote do escopo, que é como um consumidor o importa.
|
|
1155
|
+
* Um arquivo conta quando ele NOMEIA esse especificador, e a régua é o texto do
|
|
1156
|
+
* import: um alias de tsconfig que aponta para a mesma pasta contaria como
|
|
1157
|
+
* "não usa", e essa é a diferença entre um número menor e um número inventado.
|
|
1158
|
+
*/
|
|
1159
|
+
const adoption = new Map();
|
|
1150
1160
|
for (const u of opts?.usage ?? []) {
|
|
1151
1161
|
const uInternal = await internalSpecifiers(u.path);
|
|
1152
1162
|
graphRoots.push({ path: u.path, aliases: await aliasesOf(u.path) });
|
|
1163
|
+
const tally = adoption.get(u.label) ?? { files: 0, using: 0 };
|
|
1164
|
+
adoption.set(u.label, tally);
|
|
1153
1165
|
for await (const file of walk(u.path)) {
|
|
1154
1166
|
const src = await readFile(file, "utf8").catch(() => "");
|
|
1155
1167
|
if (!src)
|
|
1156
1168
|
continue;
|
|
1169
|
+
/**
|
|
1170
|
+
* O DENOMINADOR SÓ CONTA QUEM PINTA.
|
|
1171
|
+
*
|
|
1172
|
+
* A primeira versão contava todo `.ts` - hooks, tipos, clientes http - e o
|
|
1173
|
+
* app do dono saiu como 105 de 1578 (7%), quando 105 de 356 arquivos de
|
|
1174
|
+
* marcação é 29%. Um arquivo de tipos não deixa de usar o design system:
|
|
1175
|
+
* ele nunca teve como. Diluir o denominador com ele faz a migração parecer
|
|
1176
|
+
* mais atrasada do que é, e um número pessimista é tão falso quanto um
|
|
1177
|
+
* otimista.
|
|
1178
|
+
*/
|
|
1179
|
+
if (/\.(tsx|jsx|vue|svelte)$/i.test(file)) {
|
|
1180
|
+
tally.files += 1;
|
|
1181
|
+
if (scopePkg && src.includes(scopePkg))
|
|
1182
|
+
tally.using += 1;
|
|
1183
|
+
}
|
|
1157
1184
|
edges.set(file, edgesIn(src));
|
|
1158
1185
|
const rel = join(u.label, relative(u.path, file));
|
|
1159
1186
|
if (/(\.(spec|test|stories)\.[a-z]+$|__tests__\/|(^|\/)\.storybook\/)/.test(rel)) {
|
|
@@ -1914,6 +1941,18 @@ export async function takeCensus(root, opts) {
|
|
|
1914
1941
|
* só aparece quando há algo a dizer não muda nada para quem já lê o censo.
|
|
1915
1942
|
*/
|
|
1916
1943
|
...(claims.collisions.length > 0 ? { collisions: claims.collisions } : {}),
|
|
1944
|
+
...(scopePkg && adoption.size > 0
|
|
1945
|
+
? {
|
|
1946
|
+
adoption: {
|
|
1947
|
+
specifier: scopePkg,
|
|
1948
|
+
consumers: [...adoption.entries()].map(([label, t]) => ({
|
|
1949
|
+
label,
|
|
1950
|
+
files: t.files,
|
|
1951
|
+
using: t.using,
|
|
1952
|
+
})),
|
|
1953
|
+
},
|
|
1954
|
+
}
|
|
1955
|
+
: {}),
|
|
1917
1956
|
...(conventions.length > 0 ? { conventions } : {}),
|
|
1918
1957
|
classStyle,
|
|
1919
1958
|
...(Object.keys(looks).length > 0 ? { looks } : {}),
|
|
@@ -2029,6 +2068,8 @@ export async function takeCensus(root, opts) {
|
|
|
2029
2068
|
named: d.named,
|
|
2030
2069
|
tokenUses: d.tokenUses,
|
|
2031
2070
|
coverage: d.coverage,
|
|
2071
|
+
ownUses: d.ownUses,
|
|
2072
|
+
phantomUses: d.phantomUses,
|
|
2032
2073
|
},
|
|
2033
2074
|
};
|
|
2034
2075
|
}
|
|
@@ -2089,9 +2130,73 @@ async function summarize(c, root, scope) {
|
|
|
2089
2130
|
}
|
|
2090
2131
|
}
|
|
2091
2132
|
sayConventions(c);
|
|
2133
|
+
sayScopes(c);
|
|
2134
|
+
sayAdoption(c);
|
|
2092
2135
|
sayCollisions(c);
|
|
2093
2136
|
await sayBrokenRefs(c, root, scope);
|
|
2094
2137
|
}
|
|
2138
|
+
/**
|
|
2139
|
+
* OS ESCOPOS QUE VIRARAM UM SISTEMA, e o que cada um perdeu no empate.
|
|
2140
|
+
*
|
|
2141
|
+
* `--scope` aceita mais de um, e a ordem é uma declaração: o primeiro vence. Um
|
|
2142
|
+
* token que os dois declaram diferente não é erro de ninguém - é divergência, e
|
|
2143
|
+
* a lei do dono (§8.6) é que a plataforma não decide sozinha. Ela mostra os
|
|
2144
|
+
* dois, diz quem é a fonte declarada, e propõe alinhar o outro lado.
|
|
2145
|
+
*
|
|
2146
|
+
* Medido no monorepo dele: `packages/ui` e `apps/web-dashboard/src` declaram os
|
|
2147
|
+
* mesmos três `--gradient-gray-*` com valores opostos - claros na biblioteca,
|
|
2148
|
+
* escuros no app. Nenhum dos dois está errado, e um deles ia desaparecer calado.
|
|
2149
|
+
*/
|
|
2150
|
+
function sayScopes(c) {
|
|
2151
|
+
const scopes = c.scopes ?? [];
|
|
2152
|
+
if (scopes.length < 2)
|
|
2153
|
+
return;
|
|
2154
|
+
const conflicts = c.scopeConflicts ?? [];
|
|
2155
|
+
console.log("");
|
|
2156
|
+
console.log(section("Two folders, one system"));
|
|
2157
|
+
console.log(body(`Measured ${scopes.length} scopes and fused them: ${scopes.map((x, i) => (i === 0 ? paint.strong(x) : x)).join(" · ")}. The first one is the authority - it wins a tie, and nothing from the others is dropped without a line.`));
|
|
2158
|
+
if (conflicts.length === 0)
|
|
2159
|
+
return;
|
|
2160
|
+
console.log("");
|
|
2161
|
+
console.log(body(`${conflicts.length} token${conflicts.length === 1 ? "" : "s"} ${conflicts.length === 1 ? "is" : "are"} declared differently in two of them:`));
|
|
2162
|
+
console.log("");
|
|
2163
|
+
for (const x of conflicts.slice(0, 5)) {
|
|
2164
|
+
console.log(body(`${paint.strong(x.name)}\n ${paint.faint("kept")} ${x.kept.value} ${paint.faint(`(${x.kept.scope})`)}\n ${paint.faint("also")} ${x.lost.value} ${paint.faint(`(${x.lost.scope})`)}`));
|
|
2165
|
+
}
|
|
2166
|
+
if (conflicts.length > 5)
|
|
2167
|
+
console.log(body(paint.faint(`(${conflicts.length - 5} more)`)));
|
|
2168
|
+
console.log("");
|
|
2169
|
+
console.log(body(paint.faint("Neither value is wrong - one of them is a different scheme, or a fork that drifted. The system carries the first; the screen can show both.")));
|
|
2170
|
+
}
|
|
2171
|
+
/**
|
|
2172
|
+
* QUANTO DA MIGRAÇÃO JÁ ACONTECEU - o número que faltava.
|
|
2173
|
+
*
|
|
2174
|
+
* O `intent` já dizia que alguém está migrando design para o pacote
|
|
2175
|
+
* compartilhado (`init --intent migrate`, versionado em `_synthesisui/config.json`
|
|
2176
|
+
* com a data). O que nenhuma superfície dizia é QUANTO já chegou. Medido no
|
|
2177
|
+
* monorepo do dono: 103 de 356 arquivos do `web-dashboard` importam
|
|
2178
|
+
* `@frontend-hub/ui`, e o `web-review` não o conhece.
|
|
2179
|
+
*
|
|
2180
|
+
* É a diferença entre medir estado e medir progresso: um relatório de estado é
|
|
2181
|
+
* igual em janeiro e em junho; este muda a cada versão.
|
|
2182
|
+
*/
|
|
2183
|
+
function sayAdoption(c) {
|
|
2184
|
+
const a = c.adoption;
|
|
2185
|
+
if (!a || a.consumers.length === 0)
|
|
2186
|
+
return;
|
|
2187
|
+
const total = a.consumers.reduce((n, x) => n + x.files, 0);
|
|
2188
|
+
const using = a.consumers.reduce((n, x) => n + x.using, 0);
|
|
2189
|
+
console.log("");
|
|
2190
|
+
console.log(section("How far the system already reaches"));
|
|
2191
|
+
console.log(body(`${using} of ${total} source files across the folders you named import ${paint.strong(a.specifier)}. Everything else in them is still painting on its own.`));
|
|
2192
|
+
console.log("");
|
|
2193
|
+
for (const x of a.consumers) {
|
|
2194
|
+
const pct = x.files > 0 ? Math.round((x.using / x.files) * 100) : 0;
|
|
2195
|
+
console.log(body(`${paint.strong(x.label.padEnd(24))} ${String(x.using).padStart(4)} of ${String(x.files).padStart(4)} files ${paint.faint(`· ${pct}%`)}${x.using === 0 ? paint.faint(" - does not know the system yet") : ""}`));
|
|
2196
|
+
}
|
|
2197
|
+
console.log("");
|
|
2198
|
+
console.log(body(paint.faint("This is the number a migration is measured by, and it is the one that moves. Run the import again after a batch and the difference is the progress.")));
|
|
2199
|
+
}
|
|
2095
2200
|
/**
|
|
2096
2201
|
* DOIS ARQUIVOS, UM NOME - dito, com os dois caminhos.
|
|
2097
2202
|
*
|
|
@@ -2107,16 +2212,35 @@ function sayCollisions(c) {
|
|
|
2107
2212
|
const all = c.collisions ?? [];
|
|
2108
2213
|
if (all.length === 0)
|
|
2109
2214
|
return;
|
|
2215
|
+
/**
|
|
2216
|
+
* DUAS REGRAS, DUAS FRASES. Dentro de um escopo vence quem está mais perto da
|
|
2217
|
+
* raiz; entre escopos vence o primeiro `--scope`. Explicar um empate com a
|
|
2218
|
+
* regra do outro é descrever algo que não aconteceu.
|
|
2219
|
+
*/
|
|
2220
|
+
const crossScope = all.filter((x) => x.by === "scope");
|
|
2221
|
+
const inScope = all.filter((x) => x.by !== "scope");
|
|
2110
2222
|
const names = new Set(all.map((x) => x.name));
|
|
2111
2223
|
console.log("");
|
|
2112
|
-
console.log(section("Two
|
|
2113
|
-
console.log(body(`${names.size} name${names.size === 1 ? "" : "s"} ${names.size === 1 ? "is" : "are"} defined more than once in what was measured, so ${all.length} definition${all.length === 1 ? "" : "s"} did not make it into this system
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
console.log(body(`${
|
|
2224
|
+
console.log(section("Two places, one name"));
|
|
2225
|
+
console.log(body(`${names.size} name${names.size === 1 ? "" : "s"} ${names.size === 1 ? "is" : "are"} defined more than once in what was measured, so ${all.length} definition${all.length === 1 ? "" : "s"} did not make it into this system.`));
|
|
2226
|
+
if (crossScope.length > 0) {
|
|
2227
|
+
console.log("");
|
|
2228
|
+
console.log(body(`${crossScope.length} of them in two different scopes - the FIRST --scope kept the name:`));
|
|
2229
|
+
console.log("");
|
|
2230
|
+
for (const x of crossScope.slice(0, 6))
|
|
2231
|
+
console.log(body(`${paint.strong(x.name.padEnd(22))} ${paint.faint("kept in")} ${x.kept} ${paint.faint("· also in")} ${x.dropped}`));
|
|
2232
|
+
if (crossScope.length > 6)
|
|
2233
|
+
console.log(body(paint.faint(`(${crossScope.length - 6} more)`)));
|
|
2234
|
+
}
|
|
2235
|
+
if (inScope.length > 0) {
|
|
2236
|
+
console.log("");
|
|
2237
|
+
console.log(body(`${inScope.length} inside a single scope - the file closest to its root kept the name, never the order the disk happened to return:`));
|
|
2238
|
+
console.log("");
|
|
2239
|
+
for (const x of inScope.slice(0, 6))
|
|
2240
|
+
console.log(body(`${paint.strong(x.name)}\n ${paint.faint("kept")} ${x.kept}\n ${paint.faint("dropped")} ${x.dropped}`));
|
|
2241
|
+
if (inScope.length > 6)
|
|
2242
|
+
console.log(body(paint.faint(`(${inScope.length - 6} more)`)));
|
|
2117
2243
|
}
|
|
2118
|
-
if (all.length > 6)
|
|
2119
|
-
console.log(body(paint.faint(`(${all.length - 6} more)`)));
|
|
2120
2244
|
console.log("");
|
|
2121
2245
|
console.log(body(paint.faint("If the homonyms belong to different projects, a narrower --scope is the answer and nothing is lost. If they belong to the same one, renaming in your code is - two components with one name is one name your agent cannot resolve either.")));
|
|
2122
2246
|
}
|
|
@@ -3158,8 +3282,18 @@ export async function runImport(opts) {
|
|
|
3158
3282
|
.trim()
|
|
3159
3283
|
.replace(/^\.\/+/, "")
|
|
3160
3284
|
.replace(/\/+$/, "");
|
|
3161
|
-
|
|
3162
|
-
|
|
3285
|
+
/**
|
|
3286
|
+
* OS ESCOPOS, na ordem em que a pessoa os escreveu - e `scope` é o primeiro.
|
|
3287
|
+
*
|
|
3288
|
+
* O parser de flags já acumula uma flag repetida (`--usage` usa isso desde
|
|
3289
|
+
* 01/08), então `--scope packages/ui --scope packages/charts` chega aqui como
|
|
3290
|
+
* array sem nenhuma mudança de gramática.
|
|
3291
|
+
*/
|
|
3292
|
+
const scopes = (Array.isArray(opts.scope) ? opts.scope : opts.scope ? [opts.scope] : [])
|
|
3293
|
+
.map(tidy)
|
|
3294
|
+
.filter(Boolean)
|
|
3295
|
+
.filter((v, i, all) => all.indexOf(v) === i);
|
|
3296
|
+
const scope = scopes[0];
|
|
3163
3297
|
/**
|
|
3164
3298
|
* THE SECOND ROLE. `--scope` is the SYSTEM - one place, and the only source of
|
|
3165
3299
|
* the palette, the scales and the convention. `--usage` is the EVIDENCE - as
|
|
@@ -3234,13 +3368,33 @@ export async function runImport(opts) {
|
|
|
3234
3368
|
*/
|
|
3235
3369
|
phase(1, 3, "Measuring the repository");
|
|
3236
3370
|
console.log(section("Reading your project"));
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3371
|
+
/**
|
|
3372
|
+
* UMA MEDIÇÃO POR ESCOPO, E DEPOIS A FUSÃO.
|
|
3373
|
+
*
|
|
3374
|
+
* `takeCensus` recebe UM `root` em cinco pontos estruturais que são por
|
|
3375
|
+
* PACOTE e não por sistema (`harvestOwnCss`, `internalSpecifiers`,
|
|
3376
|
+
* `publicApi`, o `walk`, `aliasesOf`), então ensiná-la a medir N raízes
|
|
3377
|
+
* seria refatorar a função de 1855 linhas que o corpus dourado protege.
|
|
3378
|
+
* Medir N vezes e fundir troca esse risco por uma função pura - e as regras
|
|
3379
|
+
* de empate ficam num arquivo em vez de espalhadas por um laço.
|
|
3380
|
+
*
|
|
3381
|
+
* A EVIDÊNCIA VIAJA COM O PRIMEIRO: `--usage` doa contagem e lei, e somar a
|
|
3382
|
+
* mesma evidência N vezes multiplicaria os números que decidem o que é
|
|
3383
|
+
* hábito. Ela entra uma vez, na medição do escopo que declara.
|
|
3384
|
+
*/
|
|
3385
|
+
const measured = [];
|
|
3386
|
+
for (const [i, one] of (scopes.length > 0 ? scopes : [""]).entries()) {
|
|
3387
|
+
const at = one ? join(root, one) : root;
|
|
3388
|
+
const c = await takeCensus(at, {
|
|
3389
|
+
...(i === 0 && usage.length > 0 ? { usage } : {}),
|
|
3390
|
+
...(one ? { scopeLabel: one } : {}),
|
|
3391
|
+
...(opts.cli ? { cli: opts.cli } : {}),
|
|
3392
|
+
});
|
|
3393
|
+
if (one)
|
|
3394
|
+
c.scope = one;
|
|
3395
|
+
measured.push(c);
|
|
3396
|
+
}
|
|
3397
|
+
census = mergeCensus(measured);
|
|
3244
3398
|
}
|
|
3245
3399
|
/**
|
|
3246
3400
|
* DERIVE THE ANATOMY ON THE MEASURING PATH TOO.
|
package/dist/index.js
CHANGED
|
@@ -89,7 +89,8 @@ Options:
|
|
|
89
89
|
--version <n> install a specific version (default: latest)
|
|
90
90
|
--ds <slug> init: bring this DS in right away · generate: target DS (default: installed)
|
|
91
91
|
--name <name> preferred component name for generate
|
|
92
|
-
--scope <path> import: the SYSTEM - tokens, components, convention.
|
|
92
|
+
--scope <path> import: the SYSTEM - tokens, components, convention. Repeatable;
|
|
93
|
+
the first one wins a tie.
|
|
93
94
|
--usage <path> import: the EVIDENCE - counts, chosen values, laws. Repeatable.
|
|
94
95
|
--scheme <s> dark|light - which end of your ladder is the default
|
|
95
96
|
--target <t> template/init target: next | general (default: next)
|
|
@@ -160,7 +161,16 @@ async function main() {
|
|
|
160
161
|
// Only these two words. Anything else is a typo that would silently
|
|
161
162
|
// invert a system, so it falls through to being measured and asked.
|
|
162
163
|
// WHAT to read. `--dir` stays WHERE the project is, in every command.
|
|
163
|
-
|
|
164
|
+
/**
|
|
165
|
+
* `--scope` REPETÍVEL: um sistema pode viver em dois pacotes, e o parser
|
|
166
|
+
* já acumula flag repetida (`--usage` usa isso). A ordem é a que a pessoa
|
|
167
|
+
* escreveu, e ela decide os empates - ver `merge-census.ts`.
|
|
168
|
+
*/
|
|
169
|
+
scope: Array.isArray(flags.scope)
|
|
170
|
+
? flags.scope
|
|
171
|
+
: typeof flags.scope === "string"
|
|
172
|
+
? flags.scope
|
|
173
|
+
: undefined,
|
|
164
174
|
// WHERE THE EVIDENCE IS. Repeatable, and comma-separated works too - a
|
|
165
175
|
// person typing one path with a comma in it means two paths.
|
|
166
176
|
usage: [flags.usage]
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
const asRecord = (v) => (v ?? {});
|
|
2
|
+
/** Nome do escopo para as mensagens - `.` quando a medição não escopou nada. */
|
|
3
|
+
const nameOf = (c, i) => c.scope ?? (i === 0 ? "." : `#${i + 1}`);
|
|
4
|
+
/**
|
|
5
|
+
* OS TOKENS, com o primeiro escopo mandando - e o conflito registrado.
|
|
6
|
+
*
|
|
7
|
+
* `declared` é o coração: é dele que saem os nomes que viajam para a tela. Um
|
|
8
|
+
* `--brand-500` que vale `#1394dc` na biblioteca e `#0f6fa8` no pacote de
|
|
9
|
+
* gráficos não é um erro de ninguém, é uma divergência - e a lei do dono
|
|
10
|
+
* (§8.6) é que a plataforma não decide sozinha: ela mostra os dois e diz quem é
|
|
11
|
+
* a fonte declarada.
|
|
12
|
+
*/
|
|
13
|
+
function mergeDeclared(into, from, keptScope, fromScope, conflicts) {
|
|
14
|
+
for (const [name, value] of Object.entries(from)) {
|
|
15
|
+
const held = into[name];
|
|
16
|
+
if (held === undefined) {
|
|
17
|
+
into[name] = value;
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (held === value)
|
|
21
|
+
continue;
|
|
22
|
+
conflicts.push({
|
|
23
|
+
kind: "token",
|
|
24
|
+
name,
|
|
25
|
+
kept: { scope: keptScope, value: held },
|
|
26
|
+
lost: { scope: fromScope, value },
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** Os valores medidos, somados por (natureza, valor) - e o nome vem de quem o tem. */
|
|
31
|
+
function mergeObserved(a, b) {
|
|
32
|
+
const out = a.map((v) => ({ ...v }));
|
|
33
|
+
const at = new Map(out.map((v, i) => [`${v.kind}:${v.value}`, i]));
|
|
34
|
+
for (const v of b) {
|
|
35
|
+
const key = `${v.kind}:${v.value}`;
|
|
36
|
+
const i = at.get(key);
|
|
37
|
+
if (i === undefined) {
|
|
38
|
+
at.set(key, out.length);
|
|
39
|
+
out.push({ ...v });
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
out[i].count += v.count;
|
|
43
|
+
out[i].files += v.files;
|
|
44
|
+
/** Um nome não se apaga por o outro escopo não ter um. */
|
|
45
|
+
if (!out[i].token && v.token)
|
|
46
|
+
out[i].token = v.token;
|
|
47
|
+
}
|
|
48
|
+
return out.sort((x, y) => y.count - x.count);
|
|
49
|
+
}
|
|
50
|
+
/** União preservando a ordem de chegada - a do primeiro escopo primeiro. */
|
|
51
|
+
const union = (a, b) => [
|
|
52
|
+
...new Set([...(a ?? []), ...(b ?? [])]),
|
|
53
|
+
];
|
|
54
|
+
const concat = (a, b) => [
|
|
55
|
+
...(a ?? []),
|
|
56
|
+
...(b ?? []),
|
|
57
|
+
];
|
|
58
|
+
/** Soma campo a campo dois objetos de contagem, mantendo o que só um tem. */
|
|
59
|
+
function sumInto(a, b) {
|
|
60
|
+
if (!a)
|
|
61
|
+
return b;
|
|
62
|
+
if (!b)
|
|
63
|
+
return a;
|
|
64
|
+
const out = { ...a };
|
|
65
|
+
for (const [k, v] of Object.entries(b)) {
|
|
66
|
+
const mine = out[k];
|
|
67
|
+
if (typeof v === "number" && typeof mine === "number")
|
|
68
|
+
out[k] = mine + v;
|
|
69
|
+
else if (mine === undefined)
|
|
70
|
+
out[k] = v;
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* OS CENSOS, FUNDIDOS NA ORDEM EM QUE A PESSOA OS PEDIU.
|
|
76
|
+
*
|
|
77
|
+
* Devolve o próprio objeto quando há um só: o caminho comum - um escopo - não
|
|
78
|
+
* paga nada por esta função existir.
|
|
79
|
+
*/
|
|
80
|
+
export function mergeCensus(list) {
|
|
81
|
+
if (list.length === 0)
|
|
82
|
+
throw new Error("mergeCensus needs at least one census");
|
|
83
|
+
if (list.length === 1)
|
|
84
|
+
return list[0];
|
|
85
|
+
const first = list[0];
|
|
86
|
+
const conflicts = [];
|
|
87
|
+
const collisions = [
|
|
88
|
+
...(first.collisions ?? []),
|
|
89
|
+
];
|
|
90
|
+
const declared = { ...asRecord(first.declared) };
|
|
91
|
+
const declaredAlt = { ...asRecord(first.declaredAlt) };
|
|
92
|
+
const keyframes = { ...(first.keyframes ?? {}) };
|
|
93
|
+
const looks = { ...(first.looks ?? {}) };
|
|
94
|
+
let observed = first.observed.map((v) => ({ ...v }));
|
|
95
|
+
let totals = { ...first.totals };
|
|
96
|
+
let coverage = first.coverage;
|
|
97
|
+
let gate = first.gate;
|
|
98
|
+
let reachability = first.reachability;
|
|
99
|
+
let usage = first.usage ?? undefined;
|
|
100
|
+
let animations = first.animations ?? undefined;
|
|
101
|
+
let skipped = first.skipped ?? undefined;
|
|
102
|
+
let brokenRefs = first.brokenRefs ?? undefined;
|
|
103
|
+
let conventions = first.conventions ?? undefined;
|
|
104
|
+
let architectures = first.architectures ?? undefined;
|
|
105
|
+
let components = first.components ?? undefined;
|
|
106
|
+
let defined = first.defined ?? undefined;
|
|
107
|
+
const stack = [...(first.project.stack ?? [])];
|
|
108
|
+
for (let i = 1; i < list.length; i++) {
|
|
109
|
+
const c = list[i];
|
|
110
|
+
const scopeName = nameOf(c, i);
|
|
111
|
+
const firstName = nameOf(first, 0);
|
|
112
|
+
mergeDeclared(declared, asRecord(c.declared), firstName, scopeName, conflicts);
|
|
113
|
+
mergeDeclared(declaredAlt, asRecord(c.declaredAlt), firstName, scopeName, conflicts);
|
|
114
|
+
/**
|
|
115
|
+
* O COMPONENTE QUE OS DOIS DEFINEM fica com a receita do primeiro escopo, e
|
|
116
|
+
* o descarte entra em `collisions` - a mesma lista que `name-claim.ts`
|
|
117
|
+
* alimenta dentro de UM escopo. Uma lista, dois níveis: o arquivo e o
|
|
118
|
+
* pacote, e a tela não precisa saber a diferença para dizer a verdade.
|
|
119
|
+
*/
|
|
120
|
+
for (const [name, look] of Object.entries(c.looks ?? {})) {
|
|
121
|
+
if (looks[name] === undefined) {
|
|
122
|
+
looks[name] = look;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
/** `by` diz que este empate foi resolvido pela ORDEM DOS ESCOPOS. */
|
|
126
|
+
collisions.push({
|
|
127
|
+
name,
|
|
128
|
+
kept: firstName,
|
|
129
|
+
dropped: scopeName,
|
|
130
|
+
by: "scope",
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
for (const [name, frames] of Object.entries(c.keyframes ?? {}))
|
|
134
|
+
if (keyframes[name] === undefined)
|
|
135
|
+
keyframes[name] = frames;
|
|
136
|
+
observed = mergeObserved(observed, c.observed);
|
|
137
|
+
totals = {
|
|
138
|
+
scanned: totals.scanned + c.totals.scanned,
|
|
139
|
+
values: totals.values + c.totals.values,
|
|
140
|
+
named: totals.named + c.totals.named,
|
|
141
|
+
tokenUses: totals.tokenUses + c.totals.tokenUses,
|
|
142
|
+
ownUses: (totals.ownUses ?? 0) + (c.totals.ownUses ?? 0),
|
|
143
|
+
phantomUses: (totals.phantomUses ?? 0) + (c.totals.phantomUses ?? 0),
|
|
144
|
+
/** Recalculado abaixo - somar percentuais não significa nada. */
|
|
145
|
+
coverage: 0,
|
|
146
|
+
};
|
|
147
|
+
coverage = sumInto(coverage, c.coverage);
|
|
148
|
+
gate = sumInto(gate, c.gate);
|
|
149
|
+
reachability = sumInto(reachability, c.reachability);
|
|
150
|
+
usage = union(usage, c.usage);
|
|
151
|
+
animations = union(animations, c.animations);
|
|
152
|
+
skipped = concat(skipped, c.skipped);
|
|
153
|
+
brokenRefs = concat(brokenRefs, c.brokenRefs);
|
|
154
|
+
conventions = concat(conventions, c.conventions);
|
|
155
|
+
architectures = concat(architectures, c.architectures);
|
|
156
|
+
components = concat(components, c.components);
|
|
157
|
+
defined = concat(defined, c.defined);
|
|
158
|
+
for (const s of c.project.stack ?? [])
|
|
159
|
+
if (!stack.includes(s))
|
|
160
|
+
stack.push(s);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* A COBERTURA, RECALCULADA COM A FÓRMULA DE VERDADE.
|
|
164
|
+
*
|
|
165
|
+
* `(tokenUses + ownUses) / (tokenUses + ownUses + values + phantomUses)` - a
|
|
166
|
+
* mesma de `doctor/scan.ts:796`. A primeira versão desta função usou
|
|
167
|
+
* `named/values`, que parece razoável e não é a fórmula: fundir 48% e 30%
|
|
168
|
+
* devolvia 10% no monorepo do dono. Um número que não descreve nenhuma das
|
|
169
|
+
* medições é pior que não ter o número.
|
|
170
|
+
*
|
|
171
|
+
* Quando os censos vêm de um CLI que não gravava `ownUses`, a soma perde esses
|
|
172
|
+
* termos e o resultado seria menor que a verdade - então neste caso a
|
|
173
|
+
* cobertura do PRIMEIRO escopo é preservada, que é uma medição real de uma
|
|
174
|
+
* parte real, em vez de uma conta feita com metade dos termos.
|
|
175
|
+
*/
|
|
176
|
+
const named = totals.tokenUses + (totals.ownUses ?? 0);
|
|
177
|
+
const denominator = named + totals.values + (totals.phantomUses ?? 0);
|
|
178
|
+
const complete = list.every((c) => c.totals.ownUses !== undefined);
|
|
179
|
+
totals.coverage = !complete
|
|
180
|
+
? first.totals.coverage
|
|
181
|
+
: denominator > 0
|
|
182
|
+
? Math.round((named / denominator) * 100)
|
|
183
|
+
: 100;
|
|
184
|
+
const out = {
|
|
185
|
+
...first,
|
|
186
|
+
project: { ...first.project, stack },
|
|
187
|
+
/**
|
|
188
|
+
* `scope` SEGUE SENDO O PRIMEIRO, e não uma lista, porque há quem compare por
|
|
189
|
+
* igualdade: `live-census.ts:44` faz `census.scope === LIBRARY_SCOPE`, e um
|
|
190
|
+
* array ali seria um falso silencioso. `scopes` é aditivo - quem não sabe
|
|
191
|
+
* dele continua lendo o censo como antes.
|
|
192
|
+
*/
|
|
193
|
+
scopes: list.map((c, i) => nameOf(c, i)),
|
|
194
|
+
declared,
|
|
195
|
+
observed,
|
|
196
|
+
totals,
|
|
197
|
+
};
|
|
198
|
+
if (Object.keys(declaredAlt).length > 0)
|
|
199
|
+
out.declaredAlt = declaredAlt;
|
|
200
|
+
if (Object.keys(keyframes).length > 0)
|
|
201
|
+
out.keyframes = keyframes;
|
|
202
|
+
if (Object.keys(looks).length > 0)
|
|
203
|
+
out.looks = looks;
|
|
204
|
+
if (coverage)
|
|
205
|
+
out.coverage = coverage;
|
|
206
|
+
if (gate)
|
|
207
|
+
out.gate = gate;
|
|
208
|
+
if (reachability)
|
|
209
|
+
out.reachability = reachability;
|
|
210
|
+
if (usage && usage.length > 0)
|
|
211
|
+
out.usage = usage;
|
|
212
|
+
if (animations && animations.length > 0)
|
|
213
|
+
out.animations = animations;
|
|
214
|
+
if (skipped && skipped.length > 0)
|
|
215
|
+
out.skipped = skipped;
|
|
216
|
+
if (brokenRefs && brokenRefs.length > 0)
|
|
217
|
+
out.brokenRefs = brokenRefs;
|
|
218
|
+
if (conventions && conventions.length > 0)
|
|
219
|
+
out.conventions = conventions;
|
|
220
|
+
if (architectures && architectures.length > 0)
|
|
221
|
+
out.architectures = architectures;
|
|
222
|
+
if (components && components.length > 0)
|
|
223
|
+
out.components = components;
|
|
224
|
+
if (defined && defined.length > 0)
|
|
225
|
+
out.defined = defined;
|
|
226
|
+
if (collisions.length > 0)
|
|
227
|
+
out.collisions = collisions;
|
|
228
|
+
if (conflicts.length > 0)
|
|
229
|
+
out.scopeConflicts = conflicts;
|
|
230
|
+
return out;
|
|
231
|
+
}
|
package/dist/name-claim.js
CHANGED
|
@@ -62,10 +62,10 @@ export function claimName(claims, name, file) {
|
|
|
62
62
|
const incumbentWins = depth(held) < depth(file) ||
|
|
63
63
|
(depth(held) === depth(file) && held.localeCompare(file) <= 0);
|
|
64
64
|
if (incumbentWins) {
|
|
65
|
-
claims.collisions.push({ name, kept: held, dropped: file });
|
|
65
|
+
claims.collisions.push({ name, kept: held, dropped: file, by: "path" });
|
|
66
66
|
return false;
|
|
67
67
|
}
|
|
68
68
|
claims.by.set(name, file);
|
|
69
|
-
claims.collisions.push({ name, kept: file, dropped: held });
|
|
69
|
+
claims.collisions.push({ name, kept: file, dropped: held, by: "path" });
|
|
70
70
|
return true;
|
|
71
71
|
}
|
package/package.json
CHANGED