@konce-pt/ui 0.6.0 → 0.6.1

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.
@@ -3,6 +3,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
3
3
  import { requireConfig, writeConfig } from "../config.js";
4
4
  import { loadIndex, loadBlockMeta, loadBlockFile } from "../registry.js";
5
5
  import { flag, hasFlag, log, UserError } from "../util.js";
6
+ import { satisfies } from "../semver.js";
6
7
  export async function addCommand(args) {
7
8
  const requested = args.positionals;
8
9
  if (requested.length === 0)
@@ -113,6 +114,14 @@ function rewriteImports(content, config) {
113
114
  }
114
115
  return out;
115
116
  }
117
+ /**
118
+ * Weryfikuje zależności bloku względem package.json projektu.
119
+ *
120
+ * Rozróżnienie surowości jest celowe: `npm` to nasze paczki i zła wersja realnie
121
+ * psuje blok, więc przerywamy. `peer` to Angular i rxjs — ich wersję trzyma
122
+ * aplikacja, a twardy błąd na prerelease, którego prosty parser może odczytać
123
+ * inaczej niż npm, byłby gorszy od ostrzeżenia.
124
+ */
116
125
  function checkNpmDependencies(metas, projectRoot) {
117
126
  const pkgPath = path.join(projectRoot, 'package.json');
118
127
  const installed = {};
@@ -120,40 +129,46 @@ function checkNpmDependencies(metas, projectRoot) {
120
129
  const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
121
130
  Object.assign(installed, pkg.dependencies ?? {}, pkg.devDependencies ?? {});
122
131
  }
123
- const missing = new Set();
132
+ const missing = new Map();
133
+ // Set, bo kilka bloków deklaruje zwykle ten sam peer (@angular/core) — bez tego
134
+ // to samo ostrzeżenie poszłoby raz na blok.
135
+ const peerNotes = new Set();
124
136
  for (const meta of metas.values()) {
125
- for (const [name, range] of Object.entries(meta.dependencies?.npm ?? {})) {
126
- const have = installed[name];
127
- if (!have) {
128
- missing.add(`${name}@${range}`);
129
- continue;
130
- }
131
- // Specyfikatory bez konkretnej wersji (workspace:*, *, latest, git/file:) — nie oceniamy.
132
- if (!/\d/.test(have))
133
- continue;
134
- if (belowMinimum(have, range)) {
135
- throw new UserError(`Zależność ${name} jest w wersji ${have}, blok wymaga ${range}. ` +
136
- `Zaktualizuj ją samodzielnie i uruchom ponownie.`);
137
+ for (const [kind, deps] of [
138
+ ['npm', meta.dependencies?.npm ?? {}],
139
+ ['peer', meta.dependencies?.peer ?? {}],
140
+ ]) {
141
+ for (const [name, range] of Object.entries(deps)) {
142
+ const have = installed[name];
143
+ if (!have) {
144
+ if (kind === 'npm')
145
+ missing.set(name, range);
146
+ else
147
+ peerNotes.add(`${name} (peer) — brak w projekcie, blok wymaga ${range}`);
148
+ continue;
149
+ }
150
+ // Specyfikatory bez konkretnej wersji (workspace:*, *, latest, git/file:) — nie oceniamy.
151
+ if (!/\d/.test(have))
152
+ continue;
153
+ if (satisfies(have, range))
154
+ continue;
155
+ if (kind === 'npm') {
156
+ throw new UserError(`Zależność ${name} jest w wersji ${have}, blok wymaga ${range}. ` +
157
+ `Zaktualizuj ją samodzielnie i uruchom ponownie.`);
158
+ }
159
+ peerNotes.add(`${name} (peer) jest w wersji ${have}, blok wymaga ${range}`);
137
160
  }
138
161
  }
139
162
  }
140
163
  if (missing.size > 0) {
141
- log.warn(`Brakujące zależności npm — zainstaluj je: ${[...missing].join(' ')}`);
164
+ // Do instalacji podajemy same nazwy — zakres bywa złożony (">=0.6.0 <1.0.0")
165
+ // i wklejony do shella by się rozpadł. Wymagania pokazujemy osobno.
166
+ log.warn(`Brakujące zależności npm — zainstaluj je: ${[...missing.keys()].join(' ')}`);
167
+ for (const [name, range] of missing)
168
+ log.step(`${name} wymaga ${range}`);
142
169
  }
143
- }
144
- function toTuple(version) {
145
- const [maj = 0, min = 0, patch = 0] = version.replace(/^[^\d]*/, '').split('.').map((n) => parseInt(n, 10) || 0);
146
- return [maj, min, patch];
147
- }
148
- /** Prosty test: czy `have` jest poniżej minimum z zakresu (`^`, `~`, `>=`). */
149
- function belowMinimum(have, range) {
150
- const [hM, hm, hp] = toTuple(have);
151
- const [rM, rm, rp] = toTuple(range);
152
- if (hM !== rM)
153
- return hM < rM;
154
- if (hm !== rm)
155
- return hm < rm;
156
- return hp < rp;
170
+ for (const note of peerNotes)
171
+ log.warn(note);
157
172
  }
158
173
  function printSummary(installed, metas, dryRun) {
159
174
  log.info(`\n${dryRun ? 'Zainstalowałbym' : 'Zainstalowano'}: ${installed.join(', ')}`);
package/dist/semver.js ADDED
@@ -0,0 +1,73 @@
1
+ // Minimalna obsługa zakresów semver. CLI ma zero zależności runtime
2
+ // (package.json → "dependencies": {}), więc zamiast paczki `semver` niesiemy tyle
3
+ // kodu, ile pokrywa realne kształty zakresów z registry i z package.json aplikacji:
4
+ // ">=0.6.0 <1.0.0", "^22.0.0", "~1.2.3", "1.2.3", oraz alternatywy przez "||".
5
+ //
6
+ // Zasada bezpieczeństwa: czego nie rozumiemy, tego nie oceniamy. Zablokowanie
7
+ // instalacji z powodu składni nieznanej naszemu parserowi byłoby gorsze niż
8
+ // pominięcie kontroli — stąd `satisfies` zwraca `true` dla zakresów, których nie
9
+ // potrafi rozłożyć.
10
+ /** Wyciąga trójkę z wersji; toleruje prefiksy (`v1.2.3`) i sufiksy (`1.2.3-rc.1`). */
11
+ export function parseVersion(input) {
12
+ const m = /^[^\d]*(\d+)(?:\.(\d+))?(?:\.(\d+))?/.exec(input.trim());
13
+ if (!m)
14
+ return null;
15
+ return [Number(m[1]), Number(m[2] ?? 0), Number(m[3] ?? 0)];
16
+ }
17
+ export function compare(a, b) {
18
+ for (let i = 0; i < 3; i++) {
19
+ if (a[i] !== b[i])
20
+ return a[i] < b[i] ? -1 : 1;
21
+ }
22
+ return 0;
23
+ }
24
+ /** Górna granica `^`: dla 0.x zawęża się do minora (0.6.x), a od 1.0 do majora. */
25
+ function caretUpper([maj, min, patch]) {
26
+ if (maj > 0)
27
+ return [maj + 1, 0, 0];
28
+ if (min > 0)
29
+ return [0, min + 1, 0];
30
+ return [0, 0, patch + 1];
31
+ }
32
+ /** Pojedynczy komparator. `null` = nie rozumiem tego kształtu. */
33
+ function satisfiesOne(version, comparator) {
34
+ const m = /^(>=|<=|>|<|=|\^|~|)\s*(.+)$/.exec(comparator.trim());
35
+ if (!m)
36
+ return null;
37
+ const [, op, rest] = m;
38
+ if (/[x*]/i.test(rest))
39
+ return null; // 1.x / 1.* — poza zakresem tego parsera
40
+ const target = parseVersion(rest);
41
+ if (!target)
42
+ return null;
43
+ switch (op) {
44
+ case '>=': return compare(version, target) >= 0;
45
+ case '<=': return compare(version, target) <= 0;
46
+ case '>': return compare(version, target) > 0;
47
+ case '<': return compare(version, target) < 0;
48
+ case '^': return compare(version, target) >= 0 && compare(version, caretUpper(target)) < 0;
49
+ case '~': return compare(version, target) >= 0 && compare(version, [target[0], target[1] + 1, 0]) < 0;
50
+ default: return compare(version, target) === 0; // '=' albo goła wersja
51
+ }
52
+ }
53
+ /**
54
+ * Czy `version` mieści się w `range`. Obsługuje koniunkcję przez spację
55
+ * (">=0.6.0 <1.0.0") i alternatywę przez "||". Zakres, którego nie da się
56
+ * rozłożyć, traktujemy jako spełniony — patrz nagłówek pliku.
57
+ */
58
+ export function satisfies(version, range) {
59
+ const parsed = parseVersion(version);
60
+ if (!parsed)
61
+ return true;
62
+ for (const alternative of range.split('||')) {
63
+ const comparators = alternative.trim().split(/\s+/).filter(Boolean);
64
+ if (comparators.length === 0)
65
+ continue;
66
+ const results = comparators.map((c) => satisfiesOne(parsed, c));
67
+ if (results.some((r) => r === null))
68
+ return true; // nieznany kształt — nie oceniamy
69
+ if (results.every(Boolean))
70
+ return true;
71
+ }
72
+ return false;
73
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@konce-pt/ui",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "CLI Koncept UI — instaluje bloki (copy-paste) do projektu Angular: init, add, list.",
5
5
  "license": "MIT",
6
6
  "author": "konce.pt",