lapikit 0.6.8 → 0.6.10

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/bin/helpers.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import readline from 'node:readline/promises';
2
+ import { emitKeypressEvents, moveCursor, cursorTo, clearLine } from 'node:readline';
2
3
 
3
4
  const color = {
4
5
  red: (text) => `\x1b[31m${text}\x1b[0m`,
@@ -61,7 +62,16 @@ export const terminal = (type = 'info', msg) => {
61
62
  };
62
63
 
63
64
  export function createRL() {
64
- return readline.createInterface({ input: process.stdin, output: process.stdout });
65
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
66
+
67
+ rl.on('SIGINT', () => {
68
+ if (process.stdin.isTTY) process.stdin.setRawMode(false);
69
+ console.log('\n');
70
+ terminal('warn', 'installation canceled.');
71
+ process.exit(0);
72
+ });
73
+
74
+ return rl;
65
75
  }
66
76
 
67
77
  export async function toggle(rl, message, initial = true) {
@@ -87,7 +97,7 @@ export async function text(rl, message, initial = '', validate) {
87
97
  }
88
98
  }
89
99
 
90
- export async function select(rl, message, choices) {
100
+ async function selectFallback(rl, message, choices) {
91
101
  console.log(`\n${message}`);
92
102
  choices.forEach((c, i) => console.log(` ${i + 1}. ${c.title}`));
93
103
 
@@ -98,3 +108,144 @@ export async function select(rl, message, choices) {
98
108
  terminal('warn', `Please enter a number between 1 and ${choices.length}`);
99
109
  }
100
110
  }
111
+
112
+ async function multiselectFallback(rl, message, choices) {
113
+ console.log(`\n${message}`);
114
+ choices.forEach((c, i) => console.log(` ${i + 1}. ${c.title}`));
115
+ console.log(` (enter numbers separated by commas, e.g. 1,2 - leave empty for none)`);
116
+
117
+ while (true) {
118
+ const answer = (await rl.question(`Choice: `)).trim();
119
+ if (answer === '') return [];
120
+
121
+ const indexes = answer.split(',').map((part) => parseInt(part.trim(), 10) - 1);
122
+ const valid = indexes.every((index) => index >= 0 && index < choices.length);
123
+ if (valid) return [...new Set(indexes)].map((index) => choices[index].value);
124
+
125
+ terminal('warn', `Please enter numbers between 1 and ${choices.length}, separated by commas`);
126
+ }
127
+ }
128
+
129
+ async function interactiveList(rl, message, hint, choices, renderLine, onKey) {
130
+ console.log(`\n${message}`);
131
+ console.log(ansi.color.cyan(` (${hint})`));
132
+ choices.forEach((_, i) => console.log(renderLine(i)));
133
+
134
+ const { stdin } = process;
135
+ rl.pause();
136
+ const wasRaw = stdin.isRaw;
137
+ emitKeypressEvents(stdin, rl);
138
+ stdin.setRawMode(true);
139
+ stdin.resume();
140
+
141
+ return new Promise((resolve) => {
142
+ const redrawLine = (index) => {
143
+ const rowsUp = choices.length - index;
144
+ moveCursor(process.stdout, 0, -rowsUp);
145
+ cursorTo(process.stdout, 0);
146
+ clearLine(process.stdout, 1);
147
+ process.stdout.write(renderLine(index));
148
+ moveCursor(process.stdout, 0, rowsUp);
149
+ cursorTo(process.stdout, 0);
150
+ };
151
+
152
+ const cleanup = () => {
153
+ stdin.removeListener('keypress', onKeypress);
154
+ stdin.setRawMode(wasRaw);
155
+ rl.resume();
156
+ };
157
+
158
+ const onKeypress = (str, key) => {
159
+ if (key.ctrl && key.name === 'c') {
160
+ cleanup();
161
+ process.stdout.write('\n');
162
+ terminal('warn', 'installation canceled.');
163
+ process.exit(130);
164
+ }
165
+ onKey(key, redrawLine, (value) => {
166
+ cleanup();
167
+ console.log('');
168
+ resolve(value);
169
+ });
170
+ };
171
+
172
+ stdin.on('keypress', onKeypress);
173
+ });
174
+ }
175
+
176
+ export async function select(rl, message, choices) {
177
+ const { stdin } = process;
178
+ if (!stdin.isTTY || typeof stdin.setRawMode !== 'function') {
179
+ return selectFallback(rl, message, choices);
180
+ }
181
+
182
+ let cursor = 0;
183
+ const renderLine = (index) => {
184
+ const radio = index === cursor ? ansi.color.green('(●)') : '( )';
185
+ const pointer = index === cursor ? ansi.color.cyan('❯') : ' ';
186
+ return `${pointer} ${radio} ${choices[index].title}`;
187
+ };
188
+
189
+ return interactiveList(
190
+ rl,
191
+ message,
192
+ '↑/↓ move, enter to confirm',
193
+ choices,
194
+ renderLine,
195
+ (key, redrawLine, resolve) => {
196
+ const previousCursor = cursor;
197
+ if (key.name === 'up') {
198
+ cursor = (cursor - 1 + choices.length) % choices.length;
199
+ redrawLine(previousCursor);
200
+ redrawLine(cursor);
201
+ } else if (key.name === 'down') {
202
+ cursor = (cursor + 1) % choices.length;
203
+ redrawLine(previousCursor);
204
+ redrawLine(cursor);
205
+ } else if (key.name === 'return') {
206
+ resolve(choices[cursor].value);
207
+ }
208
+ }
209
+ );
210
+ }
211
+
212
+ export async function multiselect(rl, message, choices) {
213
+ const { stdin } = process;
214
+ if (!stdin.isTTY || typeof stdin.setRawMode !== 'function') {
215
+ return multiselectFallback(rl, message, choices);
216
+ }
217
+
218
+ const selected = new Set();
219
+ let cursor = 0;
220
+ const renderLine = (index) => {
221
+ const checked = selected.has(index) ? ansi.color.green('[x]') : '[ ]';
222
+ const pointer = index === cursor ? ansi.color.cyan('❯') : ' ';
223
+ return `${pointer} ${checked} ${choices[index].title}`;
224
+ };
225
+
226
+ return interactiveList(
227
+ rl,
228
+ message,
229
+ '↑/↓ move, space to select, enter to confirm',
230
+ choices,
231
+ renderLine,
232
+ (key, redrawLine, resolve) => {
233
+ const previousCursor = cursor;
234
+ if (key.name === 'up') {
235
+ cursor = (cursor - 1 + choices.length) % choices.length;
236
+ redrawLine(previousCursor);
237
+ redrawLine(cursor);
238
+ } else if (key.name === 'down') {
239
+ cursor = (cursor + 1) % choices.length;
240
+ redrawLine(previousCursor);
241
+ redrawLine(cursor);
242
+ } else if (key.name === 'space') {
243
+ if (selected.has(cursor)) selected.delete(cursor);
244
+ else selected.add(cursor);
245
+ redrawLine(cursor);
246
+ } else if (key.name === 'return') {
247
+ resolve([...selected].sort((a, b) => a - b).map((index) => choices[index].value));
248
+ }
249
+ }
250
+ );
251
+ }
package/bin/hooks.js CHANGED
@@ -1,10 +1,157 @@
1
1
  #!/usr/bin/env node
2
2
  import { promises as fs } from 'node:fs';
3
3
  import path from 'node:path';
4
+ import { spawn } from 'node:child_process';
4
5
  import { terminal } from './helpers.js';
5
6
 
7
+ const INSTALL_ARGS = {
8
+ npm: ['install', '--save-dev'],
9
+ yarn: ['add', '-D'],
10
+ pnpm: ['add', '--save-dev'],
11
+ bun: ['add', '-D']
12
+ };
13
+
14
+ export function installDependency(pkgManager, packageName, cwd) {
15
+ const args = INSTALL_ARGS[pkgManager];
16
+ if (!args) throw new Error(`Unsupported package manager: ${pkgManager}`);
17
+ return new Promise((resolve, reject) => {
18
+ const child = spawn(pkgManager, [...args, packageName], {
19
+ cwd,
20
+ stdio: 'inherit',
21
+ shell: process.platform === 'win32'
22
+ });
23
+ child.on('error', reject);
24
+ child.on('close', (code) =>
25
+ code === 0 ? resolve() : reject(new Error(`${pkgManager} exited with code ${code}`))
26
+ );
27
+ });
28
+ }
29
+
30
+ function findMatchingDelimiter(content, openIndex, openChar, closeChar) {
31
+ let depth = 0;
32
+ for (let i = openIndex; i < content.length; i++) {
33
+ const ch = content[i];
34
+ if (ch === '"' || ch === "'" || ch === '`') {
35
+ i = skipString(content, i);
36
+ continue;
37
+ }
38
+ if (ch === openChar) depth++;
39
+ else if (ch === closeChar) {
40
+ depth--;
41
+ if (depth === 0) return i;
42
+ }
43
+ }
44
+ return -1;
45
+ }
46
+
47
+ function skipString(source, start) {
48
+ const quote = source[start];
49
+ for (let i = start + 1; i < source.length; i++) {
50
+ if (source[i] === '\\') {
51
+ i++;
52
+ continue;
53
+ }
54
+ if (source[i] === quote) return i;
55
+ }
56
+ return source.length - 1;
57
+ }
58
+
59
+ function insertImportLine(content, importLine) {
60
+ const lines = content.split('\n');
61
+ let importInsertIndex = 0;
62
+ for (let i = 0; i < lines.length; i++) {
63
+ if (lines[i].trim().startsWith('import ')) importInsertIndex = i + 1;
64
+ }
65
+ lines.splice(importInsertIndex, 0, importLine);
66
+ return lines.join('\n');
67
+ }
68
+
69
+ function buildPreprocessCall(pluginKeys = []) {
70
+ if (!pluginKeys.length) return `lapikitPreprocess()`;
71
+ const plugins = pluginKeys.map((key) => `'${key}'`).join(', ');
72
+ return `lapikitPreprocess({ plugins: [${plugins}] })`;
73
+ }
74
+
75
+ function mergeExistingPreprocessCall(content, pluginKeys) {
76
+ if (!pluginKeys.length) return null;
77
+
78
+ const callMatch = content.match(/lapikitPreprocess\s*\(\s*(\{[\s\S]*?\})?\s*\)/);
79
+ if (!callMatch) return null;
80
+
81
+ const [fullMatch, optionsSource] = callMatch;
82
+ const pluginsMatch = optionsSource?.match(/plugins\s*:\s*\[([^\]]*)\]/);
83
+ const existingKeys = pluginsMatch
84
+ ? [...pluginsMatch[1].matchAll(/'([^']+)'|"([^"]+)"/g)].map((m) => m[1] ?? m[2])
85
+ : [];
86
+
87
+ const merged = [...new Set([...existingKeys, ...pluginKeys])];
88
+ if (merged.length === existingKeys.length) return null;
89
+
90
+ const newCall = buildPreprocessCall(merged);
91
+ return (
92
+ content.slice(0, callMatch.index) + newCall + content.slice(callMatch.index + fullMatch.length)
93
+ );
94
+ }
95
+
96
+ function findValueEnd(source, start) {
97
+ let depth = 0;
98
+ for (let i = start; i < source.length; i++) {
99
+ const ch = source[i];
100
+ if (ch === '"' || ch === "'" || ch === '`') {
101
+ i = skipString(source, i);
102
+ continue;
103
+ }
104
+ if (ch === '(' || ch === '[' || ch === '{') depth++;
105
+ else if (ch === ')' || ch === ']' || ch === '}') {
106
+ if (depth === 0) return i;
107
+ depth--;
108
+ } else if (ch === ',' && depth === 0) return i;
109
+ }
110
+ return source.length;
111
+ }
112
+
113
+ function injectPreprocessEntry(objectSource, pluginKeys = []) {
114
+ const call = buildPreprocessCall(pluginKeys);
115
+
116
+ const keyMatch = objectSource.match(/preprocess\s*:\s*/);
117
+ if (!keyMatch) {
118
+ return objectSource.replace('{', `{\n\t\t\tpreprocess: [${call}],`);
119
+ }
120
+ const valueStart = keyMatch.index + keyMatch[0].length;
121
+
122
+ if (objectSource[valueStart] === '[') {
123
+ const openBracketIndex = valueStart;
124
+ const closeBracketIndex = findMatchingDelimiter(objectSource, openBracketIndex, '[', ']');
125
+ const inner = objectSource.slice(openBracketIndex + 1, closeBracketIndex);
126
+ const trimmed = inner.trim();
127
+
128
+ let newInner;
129
+ if (!trimmed) {
130
+ newInner = call;
131
+ } else if (inner.includes('\n')) {
132
+ const firstItemMatch = inner.match(/\n(\s*)\S/);
133
+ const indent = firstItemMatch ? firstItemMatch[1] : '\t\t';
134
+ const closingMatch = inner.match(/\n(\s*)$/);
135
+ const closingIndent = closingMatch ? closingMatch[1] : '\t';
136
+ const innerTrimmed = inner.trimEnd();
137
+ const sep = innerTrimmed.endsWith(',') ? '' : ',';
138
+ newInner = `${innerTrimmed}${sep}\n${indent}${call}\n${closingIndent}`;
139
+ } else {
140
+ const sep = trimmed.endsWith(',') ? ' ' : ', ';
141
+ newInner = `${trimmed}${sep}${call}`;
142
+ }
143
+ return (
144
+ objectSource.slice(0, openBracketIndex + 1) + newInner + objectSource.slice(closeBracketIndex)
145
+ );
146
+ }
147
+
148
+ const valueEnd = findValueEnd(objectSource, valueStart);
149
+ const expr = objectSource.slice(valueStart, valueEnd).trim();
150
+ return objectSource.slice(0, valueStart) + `[${expr}, ${call}]` + objectSource.slice(valueEnd);
151
+ }
152
+
6
153
  export async function findSvelteConfigFile(projectPath) {
7
- for (const ext of ['js', 'ts']) {
154
+ for (const ext of ['js', 'mjs', 'cjs', 'ts']) {
8
155
  const file = path.join(projectPath, `svelte.config.${ext}`);
9
156
  try {
10
157
  await fs.access(file);
@@ -13,56 +160,159 @@ export async function findSvelteConfigFile(projectPath) {
13
160
  // lapikit other step
14
161
  }
15
162
  }
16
- throw new Error('No svelte.config.js or svelte.config.ts file found');
163
+ throw new Error('No svelte.config file found');
17
164
  }
18
165
 
19
- export async function addLiliPreprocess(svelteConfigFile) {
166
+ export async function addLiliPreprocess(svelteConfigFile, pluginKeys = []) {
20
167
  let content = await fs.readFile(svelteConfigFile, 'utf-8');
21
- const lapikitImport = `import { lapikitPreprocess } from 'lapikit/labs/preprocess';`;
168
+ const lapikitImport = `import { lapikitPreprocess } from 'lapikit/preprocess';`;
22
169
 
23
- if (content.includes(`from 'lapikit/labs/preprocess'`)) {
24
- terminal('info', `lapikitPreprocess already imported in ${svelteConfigFile}`);
170
+ if (content.includes(`from 'lapikit/preprocess'`)) {
171
+ const updated = mergeExistingPreprocessCall(content, pluginKeys);
172
+ if (!updated) {
173
+ terminal('warn', `lapikitPreprocess already imported in ${svelteConfigFile}`);
174
+ return;
175
+ }
176
+ await fs.writeFile(svelteConfigFile, updated);
177
+ terminal('success', `lapikitPreprocess plugins updated in ${svelteConfigFile}`);
25
178
  return;
26
179
  }
27
180
 
28
- const lines = content.split('\n');
29
- let importInsertIndex = 0;
30
- for (let i = 0; i < lines.length; i++) {
31
- if (lines[i].trim().startsWith('import ')) importInsertIndex = i + 1;
181
+ const match = content.match(/(?:const\s+\w+\s*=\s*|export\s+default\s*)(\{)/);
182
+ if (!match) {
183
+ throw new Error(`Could not find the exported config object in ${svelteConfigFile}`);
32
184
  }
33
- lines.splice(importInsertIndex, 0, lapikitImport);
34
- content = lines.join('\n');
35
185
 
36
- if (!content.match(/preprocess\s*:/)) {
37
- content = content.replace(
38
- /(const\s+\w+\s*=\s*\{|export\s+default\s*\{)/,
39
- (m) => `${m}\n\tpreprocess: [lapikitPreprocess()],`
40
- );
41
- } else if (content.match(/preprocess\s*:\s*\[/)) {
42
- content = content.replace(/preprocess\s*:\s*\[([\s\S]*?)\]/, (_, inner) => {
43
- const trimmed = inner.trim();
44
- if (!trimmed) return `preprocess: [lapikitPreprocess()]`;
45
-
46
- if (inner.includes('\n')) {
47
- const firstItemMatch = inner.match(/\n(\s*)\S/);
48
- const indent = firstItemMatch ? firstItemMatch[1] : '\t\t';
49
- const closingMatch = inner.match(/\n(\s*)$/);
50
- const closingIndent = closingMatch ? closingMatch[1] : '\t';
51
- const innerTrimmed = inner.trimEnd();
52
- const sep = innerTrimmed.endsWith(',') ? '' : ',';
53
- return `preprocess: [${innerTrimmed}${sep}\n${indent}lapikitPreprocess()\n${closingIndent}]`;
54
- } else {
55
- const sep = trimmed.endsWith(',') ? ' ' : ', ';
56
- return `preprocess: [${trimmed}${sep}lapikitPreprocess()]`;
57
- }
58
- });
59
- } else {
60
- content = content.replace(
61
- /preprocess\s*:\s*([^,\n\]{}]+)/,
62
- (_, val) => `preprocess: [${val.trim()}, lapikitPreprocess()]`
63
- );
186
+ const openBraceIndex = match.index + match[0].length - 1;
187
+ const closeBraceIndex = findMatchingDelimiter(content, openBraceIndex, '{', '}');
188
+ if (closeBraceIndex === -1) {
189
+ throw new Error(`Could not parse the config object in ${svelteConfigFile}`);
64
190
  }
65
191
 
192
+ const objectSource = content.slice(openBraceIndex, closeBraceIndex + 1);
193
+ const updatedObject = injectPreprocessEntry(objectSource, pluginKeys);
194
+ content = content.slice(0, openBraceIndex) + updatedObject + content.slice(closeBraceIndex + 1);
195
+ content = insertImportLine(content, lapikitImport);
196
+
66
197
  await fs.writeFile(svelteConfigFile, content);
67
198
  terminal('success', `lapikitPreprocess added to ${svelteConfigFile}`);
68
199
  }
200
+
201
+ export async function findViteConfigFile(projectPath) {
202
+ for (const ext of ['ts', 'js', 'mjs', 'cjs']) {
203
+ const file = path.join(projectPath, `vite.config.${ext}`);
204
+ try {
205
+ await fs.access(file);
206
+ return file;
207
+ } catch {
208
+ // lapikit other step
209
+ }
210
+ }
211
+ return null;
212
+ }
213
+
214
+ function findSveltekitPluginCall(content) {
215
+ const match = content.match(/sveltekit\s*\(\s*\{/);
216
+ if (!match) return null;
217
+
218
+ const openBraceIndex = match.index + match[0].length - 1;
219
+ const closeBraceIndex = findMatchingDelimiter(content, openBraceIndex, '{', '}');
220
+ if (closeBraceIndex === -1) return null;
221
+
222
+ return { openBraceIndex, closeBraceIndex };
223
+ }
224
+
225
+ export async function addLiliPreprocessToViteConfig(viteConfigFile, pluginKeys = []) {
226
+ let content = await fs.readFile(viteConfigFile, 'utf-8');
227
+ const lapikitImport = `import { lapikitPreprocess } from 'lapikit/preprocess';`;
228
+
229
+ if (content.includes(`from 'lapikit/preprocess'`)) {
230
+ const updated = mergeExistingPreprocessCall(content, pluginKeys);
231
+ if (!updated) {
232
+ terminal('warn', `lapikitPreprocess already imported in ${viteConfigFile}`);
233
+ return;
234
+ }
235
+ await fs.writeFile(viteConfigFile, updated);
236
+ terminal('success', `lapikitPreprocess plugins updated in ${viteConfigFile}`);
237
+ return;
238
+ }
239
+
240
+ const pluginCall = findSveltekitPluginCall(content);
241
+ if (!pluginCall) {
242
+ throw new Error(`Could not find a sveltekit({ ... }) plugin call in ${viteConfigFile}`);
243
+ }
244
+
245
+ const { openBraceIndex, closeBraceIndex } = pluginCall;
246
+ const objectSource = content.slice(openBraceIndex, closeBraceIndex + 1);
247
+ const updatedObject = injectPreprocessEntry(objectSource, pluginKeys);
248
+ content = content.slice(0, openBraceIndex) + updatedObject + content.slice(closeBraceIndex + 1);
249
+ content = insertImportLine(content, lapikitImport);
250
+
251
+ await fs.writeFile(viteConfigFile, content);
252
+ terminal('success', `lapikitPreprocess added to ${viteConfigFile}`);
253
+ }
254
+
255
+ // Since SvelteKit 2.62, Svelte/preprocess config can be passed directly to the
256
+ // sveltekit() vite plugin instead of svelte.config.js — when it is, svelte.config.js
257
+ // is ignored, so the vite.config plugin call takes priority when both exist.
258
+ export async function resolveSveltePreprocessTarget(projectPath) {
259
+ const viteConfigFile = await findViteConfigFile(projectPath);
260
+ if (viteConfigFile) {
261
+ const content = await fs.readFile(viteConfigFile, 'utf-8');
262
+ if (findSveltekitPluginCall(content)) {
263
+ return { file: viteConfigFile, add: addLiliPreprocessToViteConfig };
264
+ }
265
+ }
266
+
267
+ try {
268
+ const svelteConfigFile = await findSvelteConfigFile(projectPath);
269
+ return { file: svelteConfigFile, add: addLiliPreprocess };
270
+ } catch {
271
+ throw new Error(
272
+ 'No svelte.config file found, and no sveltekit({ ... }) plugin config found in vite.config.(js|ts) ' +
273
+ "Add lapikitPreprocess() manually: import { lapikitPreprocess } from 'lapikit/preprocess'; " +
274
+ 'then add it to your preprocess array.'
275
+ );
276
+ }
277
+ }
278
+
279
+ export async function findEslintConfigFile(projectPath) {
280
+ for (const ext of ['js', 'mjs', 'cjs', 'ts']) {
281
+ const file = path.join(projectPath, `eslint.config.${ext}`);
282
+ try {
283
+ await fs.access(file);
284
+ return file;
285
+ } catch {
286
+ // lapikit other step
287
+ }
288
+ }
289
+ throw new Error('No eslint.config.js file found');
290
+ }
291
+
292
+ export async function addLapikitEslintConfig(eslintConfigFile) {
293
+ let content = await fs.readFile(eslintConfigFile, 'utf-8');
294
+ const lapikitImport = `import lapikitConfig from 'eslint-config-lapikit';`;
295
+
296
+ if (content.includes(`from 'eslint-config-lapikit'`)) {
297
+ terminal('warn', `eslint-config-lapikit already imported in ${eslintConfigFile}`);
298
+ return;
299
+ }
300
+
301
+ content = insertImportLine(content, lapikitImport);
302
+
303
+ // Matches `export default [...]` as well as wrapped forms like
304
+ // `export default defineConfig([...])`, `export default defineConfig(a, b)`
305
+ // (rest-args form, no array) and member-expression wrappers like
306
+ // `export default ts.config(...)`.
307
+ const exportArrayPattern = /export\s+default\s*(?:\[|[\w.]+\s*\()/;
308
+ if (!exportArrayPattern.test(content)) {
309
+ throw new Error(
310
+ `Could not find "export default [...]" in ${eslintConfigFile}. Please add "...lapikitConfig" manually.`
311
+ );
312
+ }
313
+
314
+ content = content.replace(exportArrayPattern, (m) => `${m}\n\t...lapikitConfig,`);
315
+
316
+ await fs.writeFile(eslintConfigFile, content);
317
+ terminal('success', `eslint-config-lapikit added to ${eslintConfigFile}`);
318
+ }
package/bin/index.js CHANGED
@@ -1,31 +1,111 @@
1
1
  #!/usr/bin/env node
2
- import { ansi, terminal, createRL, toggle } from './helpers.js';
3
- import { addLiliPreprocess, findSvelteConfigFile } from './hooks.js';
2
+ import { ansi, terminal, createRL, toggle, select, multiselect } from './helpers.js';
3
+ import {
4
+ resolveSveltePreprocessTarget,
5
+ installDependency,
6
+ findEslintConfigFile,
7
+ addLapikitEslintConfig
8
+ } from './hooks.js';
4
9
 
5
- async function run() {
6
- const rl = createRL();
10
+ const ADDONS = [{ title: '@lapikit/repl', value: '@lapikit/repl', key: 'repl' }];
11
+
12
+ const PKG_MANAGER = [
13
+ { title: 'npm', value: 'npm' },
14
+ { title: 'yarn', value: 'yarn' },
15
+ { title: 'pnpm', value: 'pnpm' },
16
+ { title: 'bun', value: 'bun' }
17
+ ];
18
+
19
+ function buildSteps(config, projectPath) {
20
+ const pluginKeys = config.addons
21
+ .map((value) => ADDONS.find((addon) => addon.value === value)?.key)
22
+ .filter(Boolean);
23
+
24
+ const steps = [];
25
+
26
+ steps.push({
27
+ id: 'preprocess',
28
+ label: 'Add lapikitPreprocess to your project',
29
+ run: async () => {
30
+ const target = await resolveSveltePreprocessTarget(projectPath);
31
+ await target.add(target.file, pluginKeys);
32
+ }
33
+ });
34
+
35
+ for (const addonValue of config.addons) {
36
+ steps.push({
37
+ id: `addon:${addonValue}`,
38
+ label: `Install ${addonValue} (${config.pkgManager})`,
39
+ run: () => installDependency(config.pkgManager, addonValue, projectPath)
40
+ });
41
+ }
7
42
 
8
- console.log(' _ _ _ _ _ ');
9
- console.log(' | | (_) | (_) | ');
10
- console.log(' | | __ _ _ __ _| | ___| |_ ');
11
- console.log(" | | / _` | '_ \\| | |/ / | __|");
12
- console.log(' | |___| (_| | |_) | | <| | |_ ');
13
- console.log(' |______\\__,_| .__/|_|_|\\_\\_|\\__|');
14
- console.log(' | | ');
15
- console.log(' |_| \n');
43
+ if (config.installEslintConfig) {
44
+ steps.push({
45
+ id: 'eslint-install',
46
+ label: `Install eslint-config-lapikit (${config.pkgManager})`,
47
+ run: () => installDependency(config.pkgManager, 'eslint-config-lapikit', projectPath)
48
+ });
49
+ steps.push({
50
+ id: 'eslint-config',
51
+ needs: 'eslint-install',
52
+ label: 'Add eslint-config-lapikit to eslint.config',
53
+ run: async () => {
54
+ const eslintConfigFile = await findEslintConfigFile(projectPath);
55
+ await addLapikitEslintConfig(eslintConfigFile);
56
+ }
57
+ });
58
+ }
59
+
60
+ return steps;
61
+ }
16
62
 
17
- terminal('none', `${ansi.bold.blue('Lapikit')} - Component Library for Svelte\n\n`);
63
+ async function runSteps(config, projectPath) {
64
+ const steps = buildSteps(config, projectPath);
65
+ const results = [];
66
+ const failed = new Set();
18
67
 
19
- console.log(
20
- 'This installer will guide you through the process of installing Lapikit on your Svelte project.\n'
21
- );
68
+ for (let i = 0; i < steps.length; i++) {
69
+ const step = steps[i];
70
+ const tag = ansi.bold.blue(`[${i + 1}/${steps.length}]`);
22
71
 
23
- console.log('List actions that will be done:');
24
- console.log(
25
- ansi.color.green('✓') +
26
- ' Add lili preprocess (named: lapikitPreprocess) on your svelte.config.js file\n'
27
- );
28
- console.log(ansi.underline.purple('Setup will take less than 5 seconds\n'));
72
+ if (step.needs && failed.has(step.needs)) {
73
+ results.push({ label: step.label, ok: false, skipped: true });
74
+ terminal('warn', `${tag} ${step.label} - skipped (prerequisite failed)`);
75
+ continue;
76
+ }
77
+
78
+ try {
79
+ await step.run();
80
+ results.push({ label: step.label, ok: true });
81
+ terminal('success', `${tag} ${step.label}`);
82
+ } catch (error) {
83
+ if (step.id) failed.add(step.id);
84
+ results.push({ label: step.label, ok: false, error: error.message });
85
+ terminal('error', `${tag} ${step.label} - ${error.message}`);
86
+ }
87
+ }
88
+
89
+ return results;
90
+ }
91
+
92
+ async function run() {
93
+ const rl = createRL();
94
+ const config = {
95
+ installEslintConfig: false,
96
+ addons: []
97
+ };
98
+
99
+ console.log(ansi.color.blue(' ██╗ █████╗ ██████╗ ██╗██╗ ██╗██╗████████╗'));
100
+ console.log(ansi.color.blue(' ██║ ██╔══██╗██╔══██╗██║██║ ██╔╝██║╚══██╔══╝'));
101
+ console.log(ansi.color.blue(' ██║ ███████║██████╔╝██║█████╔╝ ██║ ██║ '));
102
+ console.log(ansi.color.blue(' ██║ ██╔══██║██╔═══╝ ██║██╔═██╗ ██║ ██║ '));
103
+ console.log(ansi.color.blue(' ██████╗ ██║ ██║██║ ██║██║ ██╗██║ ██║ '));
104
+ console.log(ansi.color.blue(' ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ '));
105
+
106
+ terminal('none', `${ansi.bold.blue('Lapikit')} - Components Library for Svelte`);
107
+ terminal('none', `Developed by ${ansi.bold.blue('Nycolaide')}`);
108
+ terminal('none', `Documentation: https://lapikit.dev\n`);
29
109
 
30
110
  const confirm = await toggle(rl, 'Launch install Lapikit on your project?');
31
111
  if (!confirm) {
@@ -33,25 +113,20 @@ async function run() {
33
113
  process.exit(0);
34
114
  }
35
115
 
36
- console.log('\n');
116
+ config.installEslintConfig = await toggle(rl, 'Install eslint-config-lapikit?');
117
+ config.pkgManager = await select(rl, 'Select package manager:', PKG_MANAGER);
118
+ config.addons = await multiselect(rl, 'Select addons to install:', ADDONS);
37
119
 
38
- try {
39
- const svelteConfigFile = await findSvelteConfigFile(process.cwd());
40
- await addLiliPreprocess(svelteConfigFile);
41
- } catch (error) {
42
- terminal('warn', `Warning: Could not update svelte.config file: ${error.message}`);
43
- }
120
+ const results = await runSteps(config, process.cwd());
121
+ rl.close();
122
+ return results;
44
123
  }
45
124
 
46
125
  run()
47
- .then(() => {
48
- terminal('none', `\n\nThank's for installing Lapikit!\n`);
49
- terminal('none', `Website: https://lapikit.dev`);
50
- terminal('none', `Github: https://github.com/lapikit/lapikit`);
51
- terminal('none', `Support the developement: https://buymeacoffee.com/nycolaide`);
52
- process.exit(0);
126
+ .then((results) => {
127
+ process.exitCode = results?.some((r) => !r.ok) ? 1 : 0;
53
128
  })
54
129
  .catch((error) => {
55
130
  terminal('error', `Error: ${error}`);
56
- process.exit(1);
131
+ process.exitCode = 1;
57
132
  });
@@ -79,14 +79,17 @@
79
79
 
80
80
  <style>
81
81
  .kit-avatar {
82
+ --kit-avatar-bg: var(--kit-color-surface-3);
83
+ --kit-avatar-fg: var(--kit-color-text);
84
+
82
85
  display: inline-flex;
83
86
  align-items: center;
84
87
  justify-content: center;
85
88
  width: calc(var(--kit-avatar-h) + var(--kit-avatar-density-offset) / 2);
86
89
  height: calc(var(--kit-avatar-h) + var(--kit-avatar-density-offset) / 2);
87
90
  overflow: hidden;
88
- background: var(--kit-color-surface-3);
89
- color: var(--kit-color-text);
91
+ background: var(--kit-avatar-bg);
92
+ color: var(--kit-avatar-fg);
90
93
  border-radius: var(--kit-avatar-radius);
91
94
  border: 0;
92
95
  font-weight: 600;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lapikit",
3
- "version": "0.6.8",
3
+ "version": "0.6.10",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -83,6 +83,7 @@
83
83
  "@tabler/icons-svelte": "^3.40.0",
84
84
  "@testing-library/jest-dom": "^6.6.3",
85
85
  "@testing-library/svelte": "^5.2.4",
86
+ "@types/node": "^26.1.0",
86
87
  "eslint": "^9.18.0",
87
88
  "eslint-config-prettier": "^10.0.1",
88
89
  "eslint-plugin-svelte": "^3.0.0",
@@ -98,7 +99,7 @@
98
99
  "typescript": "^5.0.0",
99
100
  "typescript-eslint": "^8.20.0",
100
101
  "vite": "^7.2.2",
101
- "vitest": "^3.0.0"
102
+ "vitest": "^4.1.11"
102
103
  },
103
104
  "keywords": [
104
105
  "svelte",