bussing 0.0.0-stage → 0.0.3

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.
@@ -0,0 +1,45 @@
1
+ // Categorías de problemas y su gravedad.
2
+ /**
3
+ * De la más grave a la menos grave. Primero lo que puede hacer perder trabajo, después lo que hace fallar en otros entornos.
4
+ * Las dependencias desactualizadas van antes de subir la versión porque al corregirlas cambia el package.json.
5
+ */
6
+ export const PROBLEM_CATEGORIES = [
7
+ 'uncommitted',
8
+ 'unpushed',
9
+ 'altered-node-modules',
10
+ 'waiting-dependencies',
11
+ 'outdated-dependencies',
12
+ 'version-not-bumped',
13
+ 'unpublished',
14
+ ];
15
+ /** Categoría de cada tipo de problema. `waiting-dependencies` no corresponde a un tipo: la determina `mainCategory`. */
16
+ export const PROBLEM_CATEGORY = {
17
+ 'uncommitted': 'uncommitted',
18
+ 'unpushed': 'unpushed',
19
+ 'no-upstream': 'unpushed',
20
+ 'linked-dependency': 'altered-node-modules',
21
+ 'copied-dependency': 'altered-node-modules',
22
+ 'outdated-dependency': 'outdated-dependencies',
23
+ 'version-not-bumped': 'version-not-bumped',
24
+ 'unpublished-version': 'unpublished',
25
+ 'not-published': 'unpublished',
26
+ };
27
+ /**
28
+ * Categoría más grave entre los problemas; `null` si no hay problemas.
29
+ * Si la más grave es `outdated-dependencies` y hay que esperar a alguna de esas dependencias (`mustWait`),
30
+ * la categoría es `waiting-dependencies`.
31
+ */
32
+ export function mainCategory(problems, mustWait) {
33
+ for (const category of PROBLEM_CATEGORIES) {
34
+ if (problems.some(function (problem) { return PROBLEM_CATEGORY[problem.kind] === category; })) {
35
+ if (category === 'outdated-dependencies' && problems.some(function (problem) {
36
+ return problem.kind === 'outdated-dependency' && mustWait(problem.dependency);
37
+ })) {
38
+ return 'waiting-dependencies';
39
+ }
40
+ return category;
41
+ }
42
+ }
43
+ return null;
44
+ }
45
+ //# sourceMappingURL=categories.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"categories.js","sourceRoot":"","sources":["../src/categories.ts"],"names":[],"mappings":"AAAA,yCAAyC;AAIzC;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAA+B;IAC1D,aAAa;IACb,UAAU;IACV,sBAAsB;IACtB,sBAAsB;IACtB,uBAAuB;IACvB,oBAAoB;IACpB,aAAa;CAChB,CAAC;AAEF,wHAAwH;AACxH,MAAM,CAAC,MAAM,gBAAgB,GAAyC;IAClE,aAAa,EAAE,aAAa;IAC5B,UAAU,EAAE,UAAU;IACtB,aAAa,EAAE,UAAU;IACzB,mBAAmB,EAAE,sBAAsB;IAC3C,mBAAmB,EAAE,sBAAsB;IAC3C,qBAAqB,EAAE,uBAAuB;IAC9C,oBAAoB,EAAE,oBAAoB;IAC1C,qBAAqB,EAAE,aAAa;IACpC,eAAe,EAAE,aAAa;CACjC,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,QAAmB,EAAE,QAAmC;IACjF,KAAK,MAAM,QAAQ,IAAI,kBAAkB,EAAE,CAAC;QACxC,IAAI,QAAQ,CAAC,IAAI,CAAC,UAAU,OAAO,IAAI,OAAO,gBAAgB,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5F,IAAI,QAAQ,KAAK,uBAAuB,IAAI,QAAQ,CAAC,IAAI,CAAC,UAAU,OAAO;gBACvE,OAAO,OAAO,CAAC,IAAI,KAAK,qBAAqB,IAAI,QAAQ,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;YAClF,CAAC,CAAC,EAAE,CAAC;gBACD,OAAO,sBAAsB,CAAC;YAClC,CAAC;YACD,OAAO,QAAQ,CAAC;QACpB,CAAC;IACL,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC"}
@@ -0,0 +1,29 @@
1
+ import * as git from './git.js';
2
+ import { type PackageJson } from './repos.js';
3
+ import type { CopyDifference, Problem, Registry, Warning } from './types.js';
4
+ export interface CheckResult {
5
+ problems: Problem[];
6
+ warnings: Warning[];
7
+ }
8
+ export interface GitCheckResult extends CheckResult {
9
+ /** `null` si la carpeta no es un repositorio git. */
10
+ state: git.GitState | null;
11
+ }
12
+ export declare function checkGit(dir: string): Promise<GitCheckResult>;
13
+ export declare function checkPublication(dir: string, pkg: PackageJson, state: git.GitState | null, registry: Registry): Promise<CheckResult>;
14
+ /** Versión local de cada paquete revisado (solo los nombres que corresponden a un único repositorio). */
15
+ export type LocalVersions = Map<string, string>;
16
+ export declare function checkCrossDependencies(pkg: PackageJson, localVersions: LocalVersions): CheckResult;
17
+ /** Archivos declarados en `bin` del package.json publicado. */
18
+ export declare function binFiles(tarball: Map<string, Uint8Array>): Set<string>;
19
+ /**
20
+ * Al instalar, npm (bin-links) cambia el fin de línea CRLF de la primera línea (el shebang) de los ejecutables por LF.
21
+ * Devuelve el contenido publicado con ese mismo cambio aplicado.
22
+ */
23
+ export declare function installedBinContent(content: Uint8Array): Uint8Array;
24
+ export declare function compareWithTarball(installedDir: string, tarball: Map<string, Uint8Array>): Promise<CopyDifference[]>;
25
+ /**
26
+ * Busca en `node_modules` enlaces simbólicos (todas las dependencias) y copias que no coinciden con lo publicado
27
+ * (solo las dependencias que son repositorios revisados, `localNames`).
28
+ */
29
+ export declare function checkInstallation(dir: string, localNames: Set<string>, registry: Registry): Promise<CheckResult>;
package/dist/checks.js ADDED
@@ -0,0 +1,255 @@
1
+ // Controles por repositorio: git, publicación, dependencias cruzadas e instalación.
2
+ import { lstat, readdir, readFile, readlink, stat } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { guarantee, is } from 'guarantee-type';
5
+ import semver from 'semver';
6
+ import * as git from './git.js';
7
+ import { declaredDependencies, readPackageJson } from './repos.js';
8
+ function emptyResult() {
9
+ return { problems: [], warnings: [] };
10
+ }
11
+ function isNotFound(err) {
12
+ return err instanceof Error && 'code' in err && err.code === 'ENOENT';
13
+ }
14
+ async function exists(path) {
15
+ try {
16
+ await lstat(path);
17
+ return true;
18
+ }
19
+ catch (err) {
20
+ if (isNotFound(err))
21
+ return false;
22
+ throw err;
23
+ }
24
+ }
25
+ export async function checkGit(dir) {
26
+ if (!await exists(join(dir, '.git'))) {
27
+ return { state: null, problems: [], warnings: [{ check: 'git', message: 'no es un repositorio git' }] };
28
+ }
29
+ const state = await git.getState(dir);
30
+ const result = { state, problems: [], warnings: [] };
31
+ if (state.changed.length > 0)
32
+ result.problems.push({ kind: 'uncommitted', files: state.changed });
33
+ if (state.branch === null) {
34
+ result.warnings.push({ check: 'git', message: 'HEAD desacoplado (no está en ninguna rama)' });
35
+ }
36
+ else if (state.upstream === null) {
37
+ result.problems.push({ kind: 'no-upstream', branch: state.branch });
38
+ }
39
+ if (state.ahead > 0)
40
+ result.problems.push({ kind: 'unpushed', commits: state.ahead });
41
+ return result;
42
+ }
43
+ // ---------------------------------------------------------------- publicación
44
+ export async function checkPublication(dir, pkg, state, registry) {
45
+ const result = emptyResult();
46
+ if (pkg.private === true)
47
+ return result;
48
+ if (pkg.name === undefined || pkg.version === undefined) {
49
+ result.warnings.push({ check: 'publication', message: 'el package.json no tiene name o version' });
50
+ return result;
51
+ }
52
+ const packument = await registry.getPackument(pkg.name);
53
+ if (packument === null) {
54
+ result.problems.push({ kind: 'not-published', version: pkg.version });
55
+ return result;
56
+ }
57
+ const published = packument.versions[pkg.version];
58
+ if (published === undefined) {
59
+ result.problems.push({ kind: 'unpublished-version', version: pkg.version, latest: packument.distTags['latest'] ?? null });
60
+ return result;
61
+ }
62
+ const gitHead = published.gitHead;
63
+ if (gitHead === undefined) {
64
+ result.warnings.push({ check: 'publication', message: `npm no registró el commit (gitHead) de la versión ${pkg.version}; no se puede saber si hay cambios posteriores` });
65
+ }
66
+ else if (state === null || state.head === null) {
67
+ result.warnings.push({ check: 'publication', message: 'no hay commit local para comparar con el publicado' });
68
+ }
69
+ else if (gitHead !== state.head) {
70
+ if (!await git.commitExists(dir, gitHead)) {
71
+ result.warnings.push({ check: 'publication', message: `el commit publicado (${gitHead}) no está en el repositorio local` });
72
+ }
73
+ else if (await git.isAncestor(dir, gitHead, state.head)) {
74
+ result.problems.push({ kind: 'version-not-bumped', version: pkg.version, commitsAfterPublish: await git.countCommits(dir, gitHead, state.head) });
75
+ }
76
+ else {
77
+ result.warnings.push({ check: 'publication', message: `el HEAD local no desciende del commit publicado (${gitHead})` });
78
+ }
79
+ }
80
+ return result;
81
+ }
82
+ export function checkCrossDependencies(pkg, localVersions) {
83
+ const result = emptyResult();
84
+ for (const dep of declaredDependencies(pkg)) {
85
+ const local = localVersions.get(dep.name);
86
+ if (local === undefined || dep.name === pkg.name)
87
+ continue;
88
+ const minimum = semver.validRange(dep.spec) === null ? null : semver.minVersion(dep.spec);
89
+ if (minimum === null) {
90
+ result.warnings.push({ check: 'cross-dependencies', message: `${dep.name}: la versión declarada "${dep.spec}" (${dep.field}) no es un rango semver` });
91
+ }
92
+ else if (semver.valid(local) === null) {
93
+ result.warnings.push({ check: 'cross-dependencies', message: `${dep.name}: la versión local "${local}" no es semver` });
94
+ }
95
+ else if (semver.lt(minimum, local)) {
96
+ result.problems.push({ kind: 'outdated-dependency', dependency: dep.name, field: dep.field, declared: dep.spec, local });
97
+ }
98
+ }
99
+ return result;
100
+ }
101
+ // ---------------------------------------------------------------- instalación
102
+ const lockDescription = is.object({
103
+ lockfileVersion: is.number,
104
+ packages: is.optional.recordString.object({
105
+ version: is.optional.string,
106
+ resolved: is.optional.string,
107
+ integrity: is.optional.string,
108
+ link: is.optional.boolean,
109
+ }),
110
+ });
111
+ /** Nombres de los paquetes instalados en `node_modules` (incluye los que tienen scope). */
112
+ async function installedNames(nodeModules) {
113
+ const names = [];
114
+ for (const entry of await readdir(nodeModules, { withFileTypes: true })) {
115
+ if (entry.name.startsWith('.'))
116
+ continue;
117
+ if (entry.name.startsWith('@') && entry.isDirectory()) {
118
+ for (const scoped of await readdir(join(nodeModules, entry.name)))
119
+ names.push(`${entry.name}/${scoped}`);
120
+ }
121
+ else {
122
+ names.push(entry.name);
123
+ }
124
+ }
125
+ return names;
126
+ }
127
+ /** Rutas relativas (con `/`) de los archivos de un paquete instalado, sin su `node_modules` interno. */
128
+ async function installedFiles(root, relative = '') {
129
+ const files = [];
130
+ for (const entry of await readdir(join(root, relative), { withFileTypes: true })) {
131
+ const path = relative === '' ? entry.name : `${relative}/${entry.name}`;
132
+ if (entry.isDirectory()) {
133
+ if (path !== 'node_modules')
134
+ files.push(...await installedFiles(root, path));
135
+ }
136
+ else {
137
+ files.push(path);
138
+ }
139
+ }
140
+ return files;
141
+ }
142
+ const binDescription = is.object({
143
+ bin: { optional: is.union([is.string, is.recordString.string]) },
144
+ });
145
+ function normalizePath(path) {
146
+ return path.replace(/\\/g, '/').replace(/^(\.\/)+/, '');
147
+ }
148
+ /** Archivos declarados en `bin` del package.json publicado. */
149
+ export function binFiles(tarball) {
150
+ const packageJson = tarball.get('package.json');
151
+ if (packageJson === undefined)
152
+ return new Set();
153
+ const bin = guarantee(binDescription, JSON.parse(Buffer.from(packageJson).toString('utf8'))).bin;
154
+ const paths = bin === undefined ? [] : typeof bin === 'string' ? [bin] : Object.values(bin);
155
+ return new Set(paths.map(normalizePath));
156
+ }
157
+ /**
158
+ * Al instalar, npm (bin-links) cambia el fin de línea CRLF de la primera línea (el shebang) de los ejecutables por LF.
159
+ * Devuelve el contenido publicado con ese mismo cambio aplicado.
160
+ */
161
+ export function installedBinContent(content) {
162
+ const buffer = Buffer.from(content);
163
+ const firstLineEnd = buffer.indexOf(0x0a);
164
+ if (buffer.subarray(0, 2).toString('latin1') === '#!' && firstLineEnd > 0 && buffer[firstLineEnd - 1] === 0x0d) {
165
+ return Buffer.concat([buffer.subarray(0, firstLineEnd - 1), buffer.subarray(firstLineEnd)]);
166
+ }
167
+ return content;
168
+ }
169
+ export async function compareWithTarball(installedDir, tarball) {
170
+ const differences = [];
171
+ const installed = new Set(await installedFiles(installedDir));
172
+ const bins = binFiles(tarball);
173
+ for (const [path, published] of tarball) {
174
+ const expected = bins.has(path) ? installedBinContent(published) : published;
175
+ if (!installed.has(path)) {
176
+ differences.push({ path, kind: 'missing' });
177
+ }
178
+ else if (Buffer.compare(await readFile(join(installedDir, path)), expected) !== 0) {
179
+ differences.push({ path, kind: 'modified' });
180
+ }
181
+ }
182
+ for (const path of installed) {
183
+ if (!tarball.has(path))
184
+ differences.push({ path, kind: 'extra' });
185
+ }
186
+ return differences.sort(function (a, b) { return a.path < b.path ? -1 : a.path > b.path ? 1 : 0; });
187
+ }
188
+ /**
189
+ * Busca en `node_modules` enlaces simbólicos (todas las dependencias) y copias que no coinciden con lo publicado
190
+ * (solo las dependencias que son repositorios revisados, `localNames`).
191
+ */
192
+ export async function checkInstallation(dir, localNames, registry) {
193
+ const result = emptyResult();
194
+ const nodeModules = join(dir, 'node_modules');
195
+ if (!await exists(nodeModules))
196
+ return result;
197
+ const toCompare = [];
198
+ for (const name of await installedNames(nodeModules)) {
199
+ const path = join(nodeModules, name);
200
+ if ((await lstat(path)).isSymbolicLink()) {
201
+ result.problems.push({ kind: 'linked-dependency', dependency: name, target: await readlink(path) });
202
+ }
203
+ else if (localNames.has(name) && (await stat(path)).isDirectory()) {
204
+ toCompare.push(name);
205
+ }
206
+ }
207
+ if (toCompare.length === 0)
208
+ return result;
209
+ let lockText;
210
+ try {
211
+ lockText = await readFile(join(dir, 'package-lock.json'), 'utf8');
212
+ }
213
+ catch (err) {
214
+ if (!isNotFound(err))
215
+ throw err;
216
+ result.warnings.push({ check: 'installation', message: `no hay package-lock.json; no se puede verificar ${toCompare.join(', ')}` });
217
+ return result;
218
+ }
219
+ const lock = guarantee(lockDescription, JSON.parse(lockText));
220
+ if (lock.packages === undefined) {
221
+ result.warnings.push({ check: 'installation', message: `package-lock.json versión ${lock.lockfileVersion} sin "packages"; no se puede verificar ${toCompare.join(', ')}` });
222
+ return result;
223
+ }
224
+ for (const name of toCompare) {
225
+ const entry = lock.packages[`node_modules/${name}`];
226
+ if (entry === undefined || entry.version === undefined) {
227
+ result.warnings.push({ check: 'installation', message: `${name} no figura en package-lock.json` });
228
+ continue;
229
+ }
230
+ let tarballUrl = entry.resolved;
231
+ if (tarballUrl === undefined) {
232
+ const packument = await registry.getPackument(name);
233
+ tarballUrl = packument?.versions[entry.version]?.tarball;
234
+ }
235
+ if (tarballUrl === undefined || !/^https?:\/\//.test(tarballUrl)) {
236
+ result.warnings.push({ check: 'installation', message: `${name}@${entry.version}: no se encontró el tarball publicado (${tarballUrl ?? 'sin resolved'})` });
237
+ continue;
238
+ }
239
+ const installedDir = join(nodeModules, name);
240
+ const differences = await compareWithTarball(installedDir, await registry.getTarballFiles(tarballUrl, entry.integrity));
241
+ if (differences.length > 0) {
242
+ let installedVersion;
243
+ try {
244
+ installedVersion = (await readPackageJson(installedDir)).version ?? null;
245
+ }
246
+ catch (err) {
247
+ installedVersion = null;
248
+ result.warnings.push({ check: 'installation', message: `no se pudo leer node_modules/${name}/package.json: ${err instanceof Error ? err.message : String(err)}` });
249
+ }
250
+ result.problems.push({ kind: 'copied-dependency', dependency: name, version: entry.version, installedVersion, differences });
251
+ }
252
+ }
253
+ return result;
254
+ }
255
+ //# sourceMappingURL=checks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checks.js","sourceRoot":"","sources":["../src/checks.ts"],"names":[],"mappings":"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"}
package/dist/git.d.ts ADDED
@@ -0,0 +1,20 @@
1
+ export interface GitState {
2
+ /** Rama actual; `null` si el HEAD está desacoplado. */
3
+ branch: string | null;
4
+ /** Commit actual; `null` si el repositorio todavía no tiene commits. */
5
+ head: string | null;
6
+ upstream: string | null;
7
+ ahead: number;
8
+ behind: number;
9
+ /** Archivos modificados, en conflicto o no versionados (los ignorados no se incluyen). */
10
+ changed: string[];
11
+ }
12
+ /** Interpreta la salida de `git status --porcelain=v2 --branch -z`. */
13
+ export declare function parseStatus(output: string): GitState;
14
+ export declare function getState(dir: string): Promise<GitState>;
15
+ /** `true` si el commit existe en el repositorio local. */
16
+ export declare function commitExists(dir: string, commit: string): Promise<boolean>;
17
+ /** `true` si `ancestor` es ancestro de `descendant` (o es el mismo commit). */
18
+ export declare function isAncestor(dir: string, ancestor: string, descendant: string): Promise<boolean>;
19
+ /** Cantidad de commits alcanzables desde `to` que no lo son desde `from`. */
20
+ export declare function countCommits(dir: string, from: string, to: string): Promise<number>;
package/dist/git.js ADDED
@@ -0,0 +1,88 @@
1
+ // Consultas a git. Se invoca el ejecutable `git` sin shell.
2
+ import { execFile } from 'node:child_process';
3
+ function runGit(dir, args) {
4
+ return new Promise(function (resolve, reject) {
5
+ execFile('git', ['-C', dir, ...args], { maxBuffer: 64 * 1024 * 1024, encoding: 'utf8' }, function (err, stdout, stderr) {
6
+ if (err === null) {
7
+ resolve({ code: 0, stdout, stderr });
8
+ }
9
+ else if (typeof err.code === 'number') {
10
+ resolve({ code: err.code, stdout, stderr });
11
+ }
12
+ else {
13
+ reject(err);
14
+ }
15
+ });
16
+ });
17
+ }
18
+ async function git(dir, args) {
19
+ const result = await runGit(dir, args);
20
+ if (result.code !== 0)
21
+ throw new Error(`git ${args.join(' ')} (en ${dir}) terminó con código ${result.code}: ${result.stderr.trim()}`);
22
+ return result.stdout;
23
+ }
24
+ /** Interpreta la salida de `git status --porcelain=v2 --branch -z`. */
25
+ export function parseStatus(output) {
26
+ const state = { branch: null, head: null, upstream: null, ahead: 0, behind: 0, changed: [] };
27
+ const items = output.split('\0');
28
+ for (let i = 0; i < items.length; i++) {
29
+ const item = items[i] ?? '';
30
+ if (item.startsWith('# branch.oid ')) {
31
+ const oid = item.slice('# branch.oid '.length);
32
+ state.head = oid === '(initial)' ? null : oid;
33
+ }
34
+ else if (item.startsWith('# branch.head ')) {
35
+ const branch = item.slice('# branch.head '.length);
36
+ state.branch = branch === '(detached)' ? null : branch;
37
+ }
38
+ else if (item.startsWith('# branch.upstream ')) {
39
+ state.upstream = item.slice('# branch.upstream '.length);
40
+ }
41
+ else if (item.startsWith('# branch.ab ')) {
42
+ const match = /^# branch\.ab \+(\d+) -(\d+)$/.exec(item);
43
+ if (match === null)
44
+ throw new Error(`Línea de git status no reconocida: ${item}`);
45
+ state.ahead = Number(match[1]);
46
+ state.behind = Number(match[2]);
47
+ }
48
+ else if (item.startsWith('1 ')) {
49
+ state.changed.push(item.split(' ').slice(8).join(' '));
50
+ }
51
+ else if (item.startsWith('2 ')) {
52
+ state.changed.push(item.split(' ').slice(9).join(' '));
53
+ i++; // el siguiente elemento es la ruta original del renombre
54
+ }
55
+ else if (item.startsWith('u ')) {
56
+ state.changed.push(item.split(' ').slice(10).join(' '));
57
+ }
58
+ else if (item.startsWith('? ')) {
59
+ state.changed.push(item.slice(2));
60
+ }
61
+ else if (item !== '' && !item.startsWith('# ')) {
62
+ throw new Error(`Línea de git status no reconocida: ${item}`);
63
+ }
64
+ }
65
+ return state;
66
+ }
67
+ export async function getState(dir) {
68
+ return parseStatus(await git(dir, ['status', '--porcelain=v2', '--branch', '-z', '--untracked-files=normal']));
69
+ }
70
+ /** `true` si el commit existe en el repositorio local. */
71
+ export async function commitExists(dir, commit) {
72
+ const result = await runGit(dir, ['cat-file', '-e', `${commit}^{commit}`]);
73
+ return result.code === 0;
74
+ }
75
+ /** `true` si `ancestor` es ancestro de `descendant` (o es el mismo commit). */
76
+ export async function isAncestor(dir, ancestor, descendant) {
77
+ const result = await runGit(dir, ['merge-base', '--is-ancestor', ancestor, descendant]);
78
+ if (result.code === 0)
79
+ return true;
80
+ if (result.code === 1)
81
+ return false;
82
+ throw new Error(`git merge-base --is-ancestor (en ${dir}) terminó con código ${result.code}: ${result.stderr.trim()}`);
83
+ }
84
+ /** Cantidad de commits alcanzables desde `to` que no lo son desde `from`. */
85
+ export async function countCommits(dir, from, to) {
86
+ return Number((await git(dir, ['rev-list', '--count', `${from}..${to}`])).trim());
87
+ }
88
+ //# sourceMappingURL=git.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { CleanupStep, ProblemCategory } from './types.js';
2
+ export interface OrderNode {
3
+ /** Identifica al nodo (la carpeta del repositorio, porque dos repositorios pueden tener el mismo nombre). */
4
+ dir: string;
5
+ name: string;
6
+ /** Categoría del problema propio más grave; `null` si no tiene problemas propios. */
7
+ mainProblem: ProblemCategory | null;
8
+ /** Carpetas de los repositorios revisados de los que depende (dependencias fuertes: siempre se respetan en el orden). */
9
+ dependsOn: string[];
10
+ /** Carpetas de los repositorios revisados de los que depende como devDependency (débiles: se respetan salvo que cierren un ciclo). */
11
+ weakDependsOn: string[];
12
+ }
13
+ /** `priority`: prioridad de cada paquete como dependencia débil (por omisión 0); ver `BussingOptions.dependencyPriority`. */
14
+ export declare function computeCleanupOrder(nodes: OrderNode[], priority?: ReadonlyMap<string, number>): CleanupStep[];
package/dist/order.js ADDED
@@ -0,0 +1,154 @@
1
+ // Orden de limpieza: repositorios con problemas y los que dependen de ellos, en orden topológico.
2
+ // Las dependencias fuertes (dependencies, optionalDependencies, peerDependencies) definen quién entra en la lista y se
3
+ // respetan siempre; las débiles (devDependencies) solo ordenan, y se descartan si cierran un ciclo.
4
+ import { defined, mustGet } from './util.js';
5
+ function byName(a, b) {
6
+ return a.name < b.name ? -1 : a.name > b.name ? 1 : a.dir < b.dir ? -1 : a.dir > b.dir ? 1 : 0;
7
+ }
8
+ /** Componentes fuertemente conexas (Tarjan), para tratar cada ciclo de dependencias como un solo paso. */
9
+ function stronglyConnectedComponents(dirs, edges) {
10
+ const component = new Map();
11
+ const index = new Map();
12
+ const low = new Map();
13
+ const stack = [];
14
+ const onStack = new Set();
15
+ let counter = 0;
16
+ let components = 0;
17
+ function visit(dir) {
18
+ index.set(dir, counter);
19
+ low.set(dir, counter);
20
+ counter++;
21
+ stack.push(dir);
22
+ onStack.add(dir);
23
+ for (const next of edges.get(dir) ?? []) {
24
+ if (!index.has(next)) {
25
+ visit(next);
26
+ low.set(dir, Math.min(mustGet(low, dir), mustGet(low, next)));
27
+ }
28
+ else if (onStack.has(next)) {
29
+ low.set(dir, Math.min(mustGet(low, dir), mustGet(index, next)));
30
+ }
31
+ }
32
+ if (low.get(dir) === index.get(dir)) {
33
+ let member;
34
+ do {
35
+ member = defined(stack.pop(), 'elemento de la pila');
36
+ onStack.delete(member);
37
+ component.set(member, components);
38
+ } while (member !== dir);
39
+ components++;
40
+ }
41
+ }
42
+ for (const dir of dirs)
43
+ if (!index.has(dir))
44
+ visit(dir);
45
+ return component;
46
+ }
47
+ /** `true` si desde `from` se llega a `to` siguiendo las aristas. */
48
+ function reaches(edges, from, to) {
49
+ const visited = new Set();
50
+ const pending = [from];
51
+ while (pending.length > 0) {
52
+ const current = defined(pending.pop(), 'pendiente');
53
+ if (current === to)
54
+ return true;
55
+ if (visited.has(current))
56
+ continue;
57
+ visited.add(current);
58
+ pending.push(...(edges.get(current) ?? []));
59
+ }
60
+ return false;
61
+ }
62
+ /** `priority`: prioridad de cada paquete como dependencia débil (por omisión 0); ver `BussingOptions.dependencyPriority`. */
63
+ export function computeCleanupOrder(nodes, priority = new Map()) {
64
+ const byDir = new Map(nodes.map(function (node) { return [node.dir, node]; }));
65
+ const dependents = new Map();
66
+ for (const node of nodes) {
67
+ for (const dep of node.dependsOn)
68
+ dependents.set(dep, [...(dependents.get(dep) ?? []), node.dir]);
69
+ }
70
+ // Los repositorios con problemas propios y, transitivamente por las dependencias fuertes, los que dependen de ellos
71
+ const included = new Set();
72
+ const pending = nodes.filter(function (node) { return node.mainProblem !== null; }).map(function (node) { return node.dir; });
73
+ while (pending.length > 0) {
74
+ const dir = defined(pending.pop(), 'pendiente');
75
+ if (included.has(dir))
76
+ continue;
77
+ included.add(dir);
78
+ pending.push(...(dependents.get(dir) ?? []));
79
+ }
80
+ const dirs = [...included].sort();
81
+ const strongEdges = new Map(dirs.map(function (dir) {
82
+ return [dir, mustGet(byDir, dir).dependsOn.filter(function (dep) { return included.has(dep); })];
83
+ }));
84
+ // Los ciclos de dependencias fuertes forman un solo paso
85
+ const component = stronglyConnectedComponents(dirs, strongEdges);
86
+ const componentEdges = new Map();
87
+ function addComponentEdge(from, to) {
88
+ const set = componentEdges.get(from) ?? new Set();
89
+ set.add(to);
90
+ componentEdges.set(from, set);
91
+ }
92
+ for (const dir of dirs) {
93
+ for (const dep of mustGet(strongEdges, dir)) {
94
+ if (mustGet(component, dep) !== mustGet(component, dir))
95
+ addComponentEdge(mustGet(component, dir), mustGet(component, dep));
96
+ }
97
+ }
98
+ // Las dependencias débiles se respetan salvo que cierren un ciclo. Se agregan primero las de mayor prioridad (según la
99
+ // dependencia) y, a igual prioridad, por nombre, para que el resultado sea reproducible. Las que cerrarían un ciclo se postergan.
100
+ const acceptedWeak = new Map(dirs.map(function (dir) { return [dir, []]; }));
101
+ const deferredWeak = new Map(dirs.map(function (dir) { return [dir, []]; }));
102
+ const priorityOf = function (dir) { return priority.get(mustGet(byDir, dir).name) ?? 0; };
103
+ const weakEdges = [];
104
+ for (const dir of dirs) {
105
+ for (const dep of mustGet(byDir, dir).weakDependsOn)
106
+ if (included.has(dep))
107
+ weakEdges.push([dir, dep]);
108
+ }
109
+ weakEdges.sort(function ([dirA, depA], [dirB, depB]) {
110
+ return priorityOf(depB) - priorityOf(depA) || byName(mustGet(byDir, dirA), mustGet(byDir, dirB)) || byName(mustGet(byDir, depA), mustGet(byDir, depB));
111
+ });
112
+ for (const [dir, dep] of weakEdges) {
113
+ const from = mustGet(component, dir);
114
+ const to = mustGet(component, dep);
115
+ if (from === to)
116
+ continue;
117
+ if (reaches(componentEdges, to, from)) {
118
+ mustGet(deferredWeak, dir).push(dep);
119
+ }
120
+ else {
121
+ addComponentEdge(from, to);
122
+ mustGet(acceptedWeak, dir).push(dep);
123
+ }
124
+ }
125
+ // Nivel de cada componente: 0 si no depende de otra componente; si no, uno más que la de mayor nivel de la que depende
126
+ const levels = new Map();
127
+ function levelOf(id) {
128
+ const known = levels.get(id);
129
+ if (known !== undefined)
130
+ return known;
131
+ let level = 0;
132
+ for (const dep of componentEdges.get(id) ?? [])
133
+ level = Math.max(level, levelOf(dep) + 1);
134
+ levels.set(id, level);
135
+ return level;
136
+ }
137
+ const steps = dirs.map(function (dir) {
138
+ const node = mustGet(byDir, dir);
139
+ const id = mustGet(component, dir);
140
+ const names = function (list) { return list.map(function (d) { return mustGet(byDir, d).name; }).sort(); };
141
+ const deps = [...mustGet(strongEdges, dir).filter(function (dep) { return mustGet(component, dep) !== id; }), ...mustGet(acceptedWeak, dir)];
142
+ return {
143
+ name: node.name,
144
+ dir,
145
+ level: levelOf(id),
146
+ mainProblem: node.mainProblem,
147
+ dependsOn: names([...new Set(deps)]),
148
+ deferred: names(mustGet(deferredWeak, dir)),
149
+ inCycleWith: names(dirs.filter(function (other) { return other !== dir && mustGet(component, other) === id; })),
150
+ };
151
+ });
152
+ return steps.sort(function (a, b) { return a.level - b.level || byName(a, b); });
153
+ }
154
+ //# sourceMappingURL=order.js.map