@konce-pt/ui 0.7.9 → 0.7.11
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/add.js +130 -84
- package/dist/semver.js +4 -2
- package/package.json +1 -1
package/dist/commands/add.js
CHANGED
|
@@ -9,13 +9,47 @@ export async function addCommand(args) {
|
|
|
9
9
|
if (requested.length === 0)
|
|
10
10
|
throw new UserError('Podaj nazwę bloku: add <nazwa...>');
|
|
11
11
|
const { path: configPath, config } = requireConfig(process.cwd());
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
const ctx = {
|
|
13
|
+
base: flag(args, 'registry') ?? config.registry,
|
|
14
|
+
config,
|
|
15
|
+
projectRoot: path.dirname(configPath),
|
|
16
|
+
targetDirRel: flag(args, 'path') ?? config.blocksDir,
|
|
17
|
+
overwrite: hasFlag(args, 'overwrite'),
|
|
18
|
+
};
|
|
15
19
|
const dryRun = hasFlag(args, 'dry-run');
|
|
16
|
-
const
|
|
17
|
-
const index = await loadIndex(base);
|
|
20
|
+
const index = await loadIndex(ctx.base);
|
|
18
21
|
// 1) Rozwiąż zależności registry rekurencyjnie (kolejność zależnościowa, wykryj cykle).
|
|
22
|
+
const order = resolveOrder(requested, index);
|
|
23
|
+
// 2) Pomiń już zainstalowane (chyba że --overwrite).
|
|
24
|
+
const toInstall = order.filter((name) => ctx.overwrite || !config.installedBlocks[name]);
|
|
25
|
+
logAlreadyInstalled(order, ctx);
|
|
26
|
+
if (toInstall.length === 0) {
|
|
27
|
+
log.info('Nic do zrobienia.');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
// 3) Wczytaj definicje, sprawdź zależności npm.
|
|
31
|
+
const metas = new Map();
|
|
32
|
+
for (const name of toInstall)
|
|
33
|
+
metas.set(name, await loadBlockMeta(ctx.base, name));
|
|
34
|
+
checkNpmDependencies(metas, ctx.projectRoot);
|
|
35
|
+
// 4) Pobierz WSZYSTKIE pliki i zaplanuj zapis (instalacja atomowa — nic nie zapisujemy wcześniej).
|
|
36
|
+
const plan = await planWrites(toInstall, metas, ctx);
|
|
37
|
+
// 5) Zapis (albo raport dry-run).
|
|
38
|
+
if (dryRun) {
|
|
39
|
+
log.info('— dry-run: żadnych zapisów —');
|
|
40
|
+
for (const p of plan)
|
|
41
|
+
log.step(`${labelFor(p.action)} ${p.target}`);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
applyPlan(plan, ctx.projectRoot);
|
|
45
|
+
markInstalled(config, toInstall, index.libraryVersion);
|
|
46
|
+
writeConfig(configPath, config);
|
|
47
|
+
}
|
|
48
|
+
printSummary(toInstall, metas, dryRun);
|
|
49
|
+
}
|
|
50
|
+
// — pomocnicze —
|
|
51
|
+
/** Kolejność instalacji: zależności registry przed blokiem, który ich potrzebuje. Wykrywa cykle. */
|
|
52
|
+
function resolveOrder(requested, index) {
|
|
19
53
|
const order = [];
|
|
20
54
|
const done = new Set();
|
|
21
55
|
const stack = new Set();
|
|
@@ -36,57 +70,49 @@ export async function addCommand(args) {
|
|
|
36
70
|
};
|
|
37
71
|
for (const name of requested)
|
|
38
72
|
visit(name);
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
log.step(`${name} — już zainstalowany (pomijam; --overwrite by nadpisać)`);
|
|
44
|
-
if (toInstall.length === 0) {
|
|
45
|
-
log.info('Nic do zrobienia.');
|
|
73
|
+
return order;
|
|
74
|
+
}
|
|
75
|
+
function logAlreadyInstalled(order, ctx) {
|
|
76
|
+
if (ctx.overwrite)
|
|
46
77
|
return;
|
|
78
|
+
for (const name of order) {
|
|
79
|
+
if (ctx.config.installedBlocks[name]) {
|
|
80
|
+
log.step(`${name} — już zainstalowany (pomijam; --overwrite by nadpisać)`);
|
|
81
|
+
}
|
|
47
82
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
metas.set(name, await loadBlockMeta(base, name));
|
|
52
|
-
checkNpmDependencies(metas, projectRoot);
|
|
53
|
-
// 4) Pobierz WSZYSTKIE pliki i zaplanuj zapis (instalacja atomowa — nic nie zapisujemy wcześniej).
|
|
83
|
+
}
|
|
84
|
+
/** Pobiera pliki wszystkich bloków i planuje zapis — bez dotykania dysku. */
|
|
85
|
+
async function planWrites(toInstall, metas, ctx) {
|
|
54
86
|
const plan = [];
|
|
55
87
|
for (const name of toInstall) {
|
|
56
88
|
const meta = metas.get(name);
|
|
57
89
|
for (const file of meta.files) {
|
|
58
|
-
const raw = await loadBlockFile(base, name, file.path);
|
|
59
|
-
const content = rewriteImports(raw, config);
|
|
60
|
-
const absolute = path.resolve(projectRoot, targetDirRel, name, file.path);
|
|
61
|
-
plan.push(planWrite(name, absolute, content, projectRoot, overwrite));
|
|
90
|
+
const raw = await loadBlockFile(ctx.base, name, file.path);
|
|
91
|
+
const content = rewriteImports(raw, ctx.config);
|
|
92
|
+
const absolute = path.resolve(ctx.projectRoot, ctx.targetDirRel, name, file.path);
|
|
93
|
+
plan.push(planWrite(name, absolute, content, ctx.projectRoot, ctx.overwrite));
|
|
62
94
|
}
|
|
63
95
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
for (const p of plan) {
|
|
72
|
-
if (p.action === 'skip-identical') {
|
|
73
|
-
log.step(`bez zmian ${p.target}`);
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
const dest = p.action === 'conflict-new' ? `${p.absolute}.new` : p.absolute;
|
|
77
|
-
mkdirSync(path.dirname(dest), { recursive: true });
|
|
78
|
-
writeFileSync(dest, p.content);
|
|
79
|
-
log.step(`${labelFor(p.action)} ${path.relative(projectRoot, dest)}`);
|
|
80
|
-
}
|
|
81
|
-
const now = new Date().toISOString().slice(0, 10);
|
|
82
|
-
for (const name of toInstall) {
|
|
83
|
-
config.installedBlocks[name] = { version: index.libraryVersion, installedAt: now };
|
|
96
|
+
return plan;
|
|
97
|
+
}
|
|
98
|
+
function applyPlan(plan, projectRoot) {
|
|
99
|
+
for (const p of plan) {
|
|
100
|
+
if (p.action === 'skip-identical') {
|
|
101
|
+
log.step(`bez zmian ${p.target}`);
|
|
102
|
+
continue;
|
|
84
103
|
}
|
|
85
|
-
|
|
104
|
+
const dest = p.action === 'conflict-new' ? `${p.absolute}.new` : p.absolute;
|
|
105
|
+
mkdirSync(path.dirname(dest), { recursive: true });
|
|
106
|
+
writeFileSync(dest, p.content);
|
|
107
|
+
log.step(`${labelFor(p.action)} ${path.relative(projectRoot, dest)}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function markInstalled(config, installed, libraryVersion) {
|
|
111
|
+
const now = new Date().toISOString().slice(0, 10);
|
|
112
|
+
for (const name of installed) {
|
|
113
|
+
config.installedBlocks[name] = { version: libraryVersion, installedAt: now };
|
|
86
114
|
}
|
|
87
|
-
printSummary(toInstall, metas, dryRun);
|
|
88
115
|
}
|
|
89
|
-
// — pomocnicze —
|
|
90
116
|
function planWrite(block, absolute, content, projectRoot, overwrite) {
|
|
91
117
|
const target = path.relative(projectRoot, absolute);
|
|
92
118
|
if (!existsSync(absolute))
|
|
@@ -110,7 +136,7 @@ function labelFor(action) {
|
|
|
110
136
|
function rewriteImports(content, config) {
|
|
111
137
|
let out = content.replaceAll('@/', `${config.alias}/`);
|
|
112
138
|
if (config.prefix !== 'kpt') {
|
|
113
|
-
out = out.
|
|
139
|
+
out = out.replaceAll(/(?<![-\w])kpt-/g, `${config.prefix}-`);
|
|
114
140
|
}
|
|
115
141
|
return out;
|
|
116
142
|
}
|
|
@@ -123,60 +149,80 @@ function rewriteImports(content, config) {
|
|
|
123
149
|
* inaczej niż npm, byłby gorszy od ostrzeżenia.
|
|
124
150
|
*/
|
|
125
151
|
function checkNpmDependencies(metas, projectRoot) {
|
|
126
|
-
const
|
|
127
|
-
const installed = {};
|
|
128
|
-
if (existsSync(pkgPath)) {
|
|
129
|
-
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
130
|
-
Object.assign(installed, pkg.dependencies ?? {}, pkg.devDependencies ?? {});
|
|
131
|
-
}
|
|
152
|
+
const installed = readInstalledDeps(projectRoot);
|
|
132
153
|
const missing = new Map();
|
|
133
154
|
// Set, bo kilka bloków deklaruje zwykle ten sam peer (@angular/core) — bez tego
|
|
134
155
|
// to samo ostrzeżenie poszłoby raz na blok.
|
|
135
156
|
const peerNotes = new Set();
|
|
136
|
-
for (const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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}`);
|
|
157
|
+
for (const { kind, name, range } of declaredDependencies(metas)) {
|
|
158
|
+
const have = installed[name];
|
|
159
|
+
const verdict = inspectDependency(range, have);
|
|
160
|
+
if (verdict === 'ok')
|
|
161
|
+
continue;
|
|
162
|
+
if (kind === 'npm') {
|
|
163
|
+
if (verdict === 'missing')
|
|
164
|
+
missing.set(name, range);
|
|
165
|
+
else {
|
|
166
|
+
throw new UserError(`Zależność ${name} jest w wersji ${have}, blok wymaga ${range}. ` +
|
|
167
|
+
`Zaktualizuj ją samodzielnie i uruchom ponownie.`);
|
|
160
168
|
}
|
|
169
|
+
continue;
|
|
161
170
|
}
|
|
171
|
+
peerNotes.add(verdict === 'missing'
|
|
172
|
+
? `${name} (peer) — brak w projekcie, blok wymaga ${range}`
|
|
173
|
+
: `${name} (peer) jest w wersji ${have}, blok wymaga ${range}`);
|
|
162
174
|
}
|
|
163
|
-
|
|
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}`);
|
|
169
|
-
}
|
|
175
|
+
reportMissing(missing);
|
|
170
176
|
for (const note of peerNotes)
|
|
171
177
|
log.warn(note);
|
|
172
178
|
}
|
|
179
|
+
/** `dependencies` + `devDependencies` z package.json projektu (devDeps wygrywają). */
|
|
180
|
+
function readInstalledDeps(projectRoot) {
|
|
181
|
+
const pkgPath = path.join(projectRoot, 'package.json');
|
|
182
|
+
if (!existsSync(pkgPath))
|
|
183
|
+
return {};
|
|
184
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
185
|
+
// Rozwinięcie `undefined` w literale obiektu jest legalne i nic nie wnosi — bez `?? {}`.
|
|
186
|
+
return { ...pkg.dependencies, ...pkg.devDependencies };
|
|
187
|
+
}
|
|
188
|
+
/** Płaska lista zależności zadeklarowanych przez wszystkie instalowane bloki. */
|
|
189
|
+
function* declaredDependencies(metas) {
|
|
190
|
+
for (const meta of metas.values()) {
|
|
191
|
+
yield* dependenciesOfKind('npm', meta.dependencies?.npm);
|
|
192
|
+
yield* dependenciesOfKind('peer', meta.dependencies?.peer);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function* dependenciesOfKind(kind, deps) {
|
|
196
|
+
if (!deps)
|
|
197
|
+
return;
|
|
198
|
+
for (const [name, range] of Object.entries(deps))
|
|
199
|
+
yield { kind, name, range };
|
|
200
|
+
}
|
|
201
|
+
/** Werdykt dla jednej zależności — bez rozróżnienia na npm/peer, to robi wołający. */
|
|
202
|
+
function inspectDependency(range, have) {
|
|
203
|
+
if (!have)
|
|
204
|
+
return 'missing';
|
|
205
|
+
// Specyfikatory bez konkretnej wersji (workspace:*, *, latest, git/file:) — nie oceniamy.
|
|
206
|
+
if (!/\d/.test(have))
|
|
207
|
+
return 'ok';
|
|
208
|
+
return satisfies(have, range) ? 'ok' : 'mismatch';
|
|
209
|
+
}
|
|
210
|
+
function reportMissing(missing) {
|
|
211
|
+
if (missing.size === 0)
|
|
212
|
+
return;
|
|
213
|
+
// Do instalacji podajemy same nazwy — zakres bywa złożony (">=0.6.0 <1.0.0")
|
|
214
|
+
// i wklejony do shella by się rozpadł. Wymagania pokazujemy osobno.
|
|
215
|
+
log.warn(`Brakujące zależności npm — zainstaluj je: ${[...missing.keys()].join(' ')}`);
|
|
216
|
+
for (const [name, range] of missing)
|
|
217
|
+
log.step(`${name} wymaga ${range}`);
|
|
218
|
+
}
|
|
173
219
|
function printSummary(installed, metas, dryRun) {
|
|
174
220
|
log.info(`\n${dryRun ? 'Zainstalowałbym' : 'Zainstalowano'}: ${installed.join(', ')}`);
|
|
175
221
|
for (const name of installed) {
|
|
176
222
|
const meta = metas.get(name);
|
|
177
223
|
if (meta.usage) {
|
|
178
224
|
log.info(`\n${name}:`);
|
|
179
|
-
log.step(meta.usage.
|
|
225
|
+
log.step(meta.usage.replaceAll('\n', '\n '));
|
|
180
226
|
}
|
|
181
227
|
const ports = meta.api?.ports ?? [];
|
|
182
228
|
for (const port of ports) {
|
package/dist/semver.js
CHANGED
|
@@ -31,7 +31,9 @@ function caretUpper([maj, min, patch]) {
|
|
|
31
31
|
}
|
|
32
32
|
/** Pojedynczy komparator. `null` = nie rozumiem tego kształtu. */
|
|
33
33
|
function satisfiesOne(version, comparator) {
|
|
34
|
-
|
|
34
|
+
// `\S` po `\s*` jest istotne: `\s*(.+)` pozwalałoby dopasować spację obu członom,
|
|
35
|
+
// co daje wykładniczy nawrót przy patologicznym wejściu.
|
|
36
|
+
const m = /^(>=|<=|>|<|=|\^|~|)\s*(\S.*)$/.exec(comparator.trim());
|
|
35
37
|
if (!m)
|
|
36
38
|
return null;
|
|
37
39
|
const [, op, rest] = m;
|
|
@@ -64,7 +66,7 @@ export function satisfies(version, range) {
|
|
|
64
66
|
if (comparators.length === 0)
|
|
65
67
|
continue;
|
|
66
68
|
const results = comparators.map((c) => satisfiesOne(parsed, c));
|
|
67
|
-
if (results.
|
|
69
|
+
if (results.includes(null))
|
|
68
70
|
return true; // nieznany kształt — nie oceniamy
|
|
69
71
|
if (results.every(Boolean))
|
|
70
72
|
return true;
|