@khwarizmi/cli 0.3.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.
- package/LICENSE +21 -0
- package/README.md +107 -0
- package/package.json +26 -0
- package/src/index.js +207 -0
- package/src/registry-url.js +5 -0
- package/src/util.js +77 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 UwanGraph
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# khwarizmi-ui
|
|
2
|
+
|
|
3
|
+
CLI buat menyalin komponen [Khwarizmi UI](https://ui.khwarizmi.co.id) ke project kamu.
|
|
4
|
+
|
|
5
|
+
Komponennya bukan library yang kamu import sebagai paket runtime — source file-nya
|
|
6
|
+
disalin ke project supaya bebas kamu ubah. Project tetap membutuhkan Svelte 5,
|
|
7
|
+
Tailwind CSS v4, dan dependency eksternal komponen yang relevan.
|
|
8
|
+
|
|
9
|
+
## Pakai
|
|
10
|
+
|
|
11
|
+
Untuk memasang semua komponen dari npm:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx khwarizmi-ui@latest add --all
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Perintah di atas membuat file-file berikut di `src/lib/components/khwarizmi-ui/`, mis.:
|
|
18
|
+
|
|
19
|
+
```text
|
|
20
|
+
src/lib/components/khwarizmi-ui/Button.svelte
|
|
21
|
+
src/lib/components/khwarizmi-ui/Card.svelte
|
|
22
|
+
src/lib/components/khwarizmi-ui/Input.svelte
|
|
23
|
+
src/lib/components/khwarizmi-ui/Select.svelte
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Dependency yang dibutuhkan, termasuk `lucide-svelte`, dipasang otomatis.
|
|
27
|
+
CLI juga memasang stylesheet global Khwarizmi UI dan mengimpornya otomatis ke
|
|
28
|
+
`src/app.css` atau `src/routes/app.css`. Jika tidak terdeteksi, CLI meminta path-nya.
|
|
29
|
+
|
|
30
|
+
Atau pasang komponen tertentu:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npx khwarizmi-ui add button
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Beberapa sekaligus:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npx khwarizmi-ui add button card input select
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Semuanya sekaligus:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npx khwarizmi-ui add --all
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Lihat semua yang tersedia:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npx khwarizmi-ui list
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Perintah
|
|
55
|
+
|
|
56
|
+
| Perintah | Fungsi |
|
|
57
|
+
| --- | --- |
|
|
58
|
+
| `add <komponen...>` | Pasang satu atau beberapa komponen |
|
|
59
|
+
| `add --all` | Pasang semua komponen yang ada di registry |
|
|
60
|
+
| `list` | Daftar komponen yang tersedia |
|
|
61
|
+
|
|
62
|
+
## Opsi
|
|
63
|
+
|
|
64
|
+
| Opsi | Fungsi |
|
|
65
|
+
| --- | --- |
|
|
66
|
+
| `--all`, `-a` | Pasang semua komponen yang ada di registry |
|
|
67
|
+
| `--overwrite` | Timpa file yang sudah ada tanpa nanya |
|
|
68
|
+
| `--cwd <path>` | Jalankan di folder lain |
|
|
69
|
+
| `--no-deps` | Jangan pasang dependency npm |
|
|
70
|
+
| `--help`, `--version` | |
|
|
71
|
+
|
|
72
|
+
## Cara kerjanya
|
|
73
|
+
|
|
74
|
+
1. Ambil definisi komponen dari registry.
|
|
75
|
+
2. Pasang stylesheet global dan import-nya sekali.
|
|
76
|
+
3. Tulis file ke path tujuannya, mis. `src/lib/components/khwarizmi-ui/Button.svelte`.
|
|
77
|
+
4. Pasang dependency npm yang belum ada, pakai package manager yang terdeteksi
|
|
78
|
+
dari lockfile project kamu (npm, pnpm, yarn, atau bun).
|
|
79
|
+
|
|
80
|
+
File yang sudah ada tidak ditimpa diam-diam — kamu akan ditanya dulu, kecuali
|
|
81
|
+
memakai `--overwrite`. Kalau dijalankan bukan di terminal interaktif (mis. CI),
|
|
82
|
+
file yang sudah ada dilewati supaya aman.
|
|
83
|
+
|
|
84
|
+
## Prasyarat
|
|
85
|
+
|
|
86
|
+
Komponennya dibuat untuk **Svelte 5** (runes) dan **Tailwind CSS v4**.
|
|
87
|
+
Sebagian komponen memakai `lucide-svelte`; dependency ini dipasang otomatis oleh CLI.
|
|
88
|
+
|
|
89
|
+
## Catatan versi `npx`
|
|
90
|
+
|
|
91
|
+
Sesaat setelah rilis baru, registry npm mungkin masih menyebarkan versi terbaru.
|
|
92
|
+
Kalau `npx khwarizmi-ui` masih menjalankan versi lama atau hasilnya belum sesuai,
|
|
93
|
+
coba `@latest`:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
npx khwarizmi-ui@latest add --all
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Registry sendiri
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
KHWARIZMI_UI_REGISTRY=http://localhost:5173 npx khwarizmi-ui add button
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Lisensi
|
|
106
|
+
|
|
107
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@khwarizmi/cli",
|
|
3
|
+
"version": "0.3.1",
|
|
4
|
+
"description": "CLI buat memasang komponen Khwarizmi UI ke project kamu.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"khwarizmi-ui": "./src/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"src",
|
|
11
|
+
"README.md",
|
|
12
|
+
"LICENSE"
|
|
13
|
+
],
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=18"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"khwarizmi",
|
|
19
|
+
"svelte",
|
|
20
|
+
"ui",
|
|
21
|
+
"components",
|
|
22
|
+
"tailwind"
|
|
23
|
+
],
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"homepage": "https://ui.khwarizmi.co.id"
|
|
26
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { mkdirSync, writeFileSync, existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, join, resolve } from 'node:path';
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
5
|
+
import { createInterface } from 'node:readline/promises';
|
|
6
|
+
import { REGISTRY_URL } from './registry-url.js';
|
|
7
|
+
import { c, log, die, fetchJson, detectPackageManager, installArgs, missingDeps } from './util.js';
|
|
8
|
+
|
|
9
|
+
const VERSION = '0.3.1';
|
|
10
|
+
const cwd = process.cwd();
|
|
11
|
+
|
|
12
|
+
function usage() {
|
|
13
|
+
log.plain(`
|
|
14
|
+
${c.bold('khwarizmi-ui')} ${c.dim('v' + VERSION)}
|
|
15
|
+
|
|
16
|
+
${c.dim('Pasang komponen Khwarizmi UI ke project kamu.')}
|
|
17
|
+
|
|
18
|
+
${c.bold('Perintah')}
|
|
19
|
+
${c.orange('add')} <komponen...> Pasang satu atau beberapa komponen
|
|
20
|
+
${c.orange('add')} --all Pasang semua komponen sekaligus
|
|
21
|
+
${c.orange('list')} Lihat semua komponen yang tersedia
|
|
22
|
+
|
|
23
|
+
${c.bold('Opsi')}
|
|
24
|
+
--all, -a Pasang semua komponen yang ada di registry
|
|
25
|
+
--overwrite Timpa file yang sudah ada tanpa nanya
|
|
26
|
+
--cwd <path> Jalankan di folder lain
|
|
27
|
+
--no-deps Jangan pasang dependency npm
|
|
28
|
+
--help, --version
|
|
29
|
+
|
|
30
|
+
${c.bold('Contoh')}
|
|
31
|
+
${c.dim('$')} npx khwarizmi-ui add button
|
|
32
|
+
${c.dim('$')} npx khwarizmi-ui add button card input
|
|
33
|
+
${c.dim('$')} npx khwarizmi-ui add --all
|
|
34
|
+
`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function parseArgs(argv) {
|
|
38
|
+
const opts = { overwrite: false, deps: true, all: false, cwd };
|
|
39
|
+
const rest = [];
|
|
40
|
+
for (let i = 0; i < argv.length; i++) {
|
|
41
|
+
const a = argv[i];
|
|
42
|
+
if (a === '--overwrite') opts.overwrite = true;
|
|
43
|
+
else if (a === '--all' || a === '-a') opts.all = true;
|
|
44
|
+
else if (a === '--no-deps') opts.deps = false;
|
|
45
|
+
else if (a === '--cwd') opts.cwd = resolve(argv[++i] ?? '.');
|
|
46
|
+
else if (a === '--help' || a === '-h') opts.help = true;
|
|
47
|
+
else if (a === '--version' || a === '-v') opts.version = true;
|
|
48
|
+
else rest.push(a);
|
|
49
|
+
}
|
|
50
|
+
return { opts, rest };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function confirm(question) {
|
|
54
|
+
// Kalau bukan terminal interaktif (mis. di CI), jangan menggantung — tolak saja.
|
|
55
|
+
if (!process.stdin.isTTY) return false;
|
|
56
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
57
|
+
const answer = await rl.question(`${c.orange('?')} ${question} ${c.dim('(y/N)')} `);
|
|
58
|
+
rl.close();
|
|
59
|
+
return /^y(es)?$/i.test(answer.trim());
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function ensureGlobalStyles(styles, opts) {
|
|
63
|
+
if (!styles) return;
|
|
64
|
+
|
|
65
|
+
const styleTarget = join(opts.cwd, styles.target);
|
|
66
|
+
if (!existsSync(styleTarget) || opts.overwrite) {
|
|
67
|
+
mkdirSync(dirname(styleTarget), { recursive: true });
|
|
68
|
+
writeFileSync(styleTarget, styles.content, 'utf8');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const candidates = ['src/app.css', 'src/routes/app.css'];
|
|
72
|
+
let appCss = candidates.map((path) => join(opts.cwd, path)).find(existsSync);
|
|
73
|
+
|
|
74
|
+
if (!appCss && process.stdin.isTTY) {
|
|
75
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
76
|
+
const answer = await rl.question(`${c.orange('?')} Path stylesheet global aplikasi ${c.dim('(contoh: src/app.css)')} `);
|
|
77
|
+
rl.close();
|
|
78
|
+
if (answer.trim()) {
|
|
79
|
+
const candidate = resolve(opts.cwd, answer.trim());
|
|
80
|
+
if (existsSync(candidate)) appCss = candidate;
|
|
81
|
+
else log.warn(`Stylesheet tidak ditemukan: ${answer.trim()}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const importLine = `@import '$lib/components/khwarizmi-ui/styles.css';`;
|
|
86
|
+
if (!appCss) {
|
|
87
|
+
log.warn(`Tambahkan ${importLine} ke stylesheet global aplikasi.`);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const content = readFileSync(appCss, 'utf8');
|
|
92
|
+
if (!content.includes(importLine)) writeFileSync(appCss, `${importLine}\n${content}`, 'utf8');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function listCommand() {
|
|
96
|
+
const index = await fetchJson(`${REGISTRY_URL}/r/registry.json`);
|
|
97
|
+
if (!index) die('Registry tidak ditemukan.', REGISTRY_URL);
|
|
98
|
+
|
|
99
|
+
log.plain(`\n ${c.bold('Komponen tersedia')} ${c.dim(REGISTRY_URL)}\n`);
|
|
100
|
+
const pad = Math.max(...index.items.map((i) => i.name.length));
|
|
101
|
+
for (const item of index.items) {
|
|
102
|
+
log.plain(` ${c.orange(item.name.padEnd(pad))} ${c.dim(item.description ?? '')}`);
|
|
103
|
+
}
|
|
104
|
+
log.plain(`\n ${c.dim('Pasang dengan:')} npx khwarizmi-ui add <nama>\n`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Ambil item yang mau dipasang. Untuk --all cukup sekali request ke indeks,
|
|
109
|
+
* karena indeksnya sudah memuat isi file tiap komponen.
|
|
110
|
+
*/
|
|
111
|
+
async function resolveItems(names, opts) {
|
|
112
|
+
if (opts.all) {
|
|
113
|
+
const index = await fetchJson(`${REGISTRY_URL}/r/registry.json`);
|
|
114
|
+
if (!index) die('Registry tidak ditemukan.', REGISTRY_URL);
|
|
115
|
+
log.step(`Mengambil ${c.bold('semua')} komponen (${index.items.length})`);
|
|
116
|
+
return { items: index.items, globalStyles: index.globalStyles };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const items = [];
|
|
120
|
+
let globalStyles;
|
|
121
|
+
for (const name of names) {
|
|
122
|
+
log.step(`Mengambil ${c.bold(name)}`);
|
|
123
|
+
const item = await fetchJson(`${REGISTRY_URL}/r/${name}.json`);
|
|
124
|
+
if (!item) {
|
|
125
|
+
log.error(`Komponen "${name}" tidak ada di registry.`);
|
|
126
|
+
log.plain(` ${c.dim('Lihat daftarnya: npx khwarizmi-ui list')}`);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
globalStyles ??= item.globalStyles;
|
|
130
|
+
items.push(item);
|
|
131
|
+
}
|
|
132
|
+
return { items, globalStyles };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function addCommand(names, opts) {
|
|
136
|
+
if (!names.length && !opts.all) {
|
|
137
|
+
die('Sebutkan komponen yang mau dipasang.', 'satu: npx khwarizmi-ui add button | semua: npx khwarizmi-ui add --all');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const written = [];
|
|
141
|
+
const skipped = [];
|
|
142
|
+
const deps = new Set();
|
|
143
|
+
|
|
144
|
+
const { items, globalStyles } = await resolveItems(names, opts);
|
|
145
|
+
await ensureGlobalStyles(globalStyles, opts);
|
|
146
|
+
|
|
147
|
+
for (const item of items) {
|
|
148
|
+
for (const dep of item.dependencies ?? []) deps.add(dep);
|
|
149
|
+
|
|
150
|
+
for (const file of item.files ?? []) {
|
|
151
|
+
const target = file.target ?? file.path;
|
|
152
|
+
const dest = join(opts.cwd, target);
|
|
153
|
+
|
|
154
|
+
if (existsSync(dest) && !opts.overwrite) {
|
|
155
|
+
const ok = await confirm(`${target} sudah ada. Timpa?`);
|
|
156
|
+
if (!ok) {
|
|
157
|
+
skipped.push(target);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
163
|
+
writeFileSync(dest, file.content, 'utf8');
|
|
164
|
+
written.push(target);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Dependency dipasang setelah semua file beres, biar package manager
|
|
169
|
+
// cuma jalan sekali walau komponen yang dipasang banyak.
|
|
170
|
+
if (opts.deps && deps.size) {
|
|
171
|
+
const need = missingDeps(opts.cwd, [...deps]);
|
|
172
|
+
if (need.length) {
|
|
173
|
+
const pm = detectPackageManager(opts.cwd);
|
|
174
|
+
log.step(`Memasang dependency lewat ${c.bold(pm)}: ${need.join(', ')}`);
|
|
175
|
+
const res = spawnSync(pm, installArgs[pm](need), {
|
|
176
|
+
cwd: opts.cwd,
|
|
177
|
+
stdio: 'inherit',
|
|
178
|
+
shell: process.platform === 'win32'
|
|
179
|
+
});
|
|
180
|
+
if (res.status !== 0) log.warn(`Gagal memasang dependency. Pasang manual: ${pm} add ${need.join(' ')}`);
|
|
181
|
+
else log.done('Dependency terpasang.');
|
|
182
|
+
} else {
|
|
183
|
+
log.done('Semua dependency sudah ada.');
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
log.plain();
|
|
188
|
+
if (written.length) {
|
|
189
|
+
log.done(`${written.length} file dibuat:`);
|
|
190
|
+
for (const f of written) log.plain(` ${c.dim('•')} ${f}`);
|
|
191
|
+
}
|
|
192
|
+
if (skipped.length) {
|
|
193
|
+
log.warn(`${skipped.length} file dilewati:`);
|
|
194
|
+
for (const f of skipped) log.plain(` ${c.dim('•')} ${f}`);
|
|
195
|
+
}
|
|
196
|
+
if (!written.length && !skipped.length) die('Tidak ada yang dipasang.');
|
|
197
|
+
log.plain();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const { opts, rest } = parseArgs(process.argv.slice(2));
|
|
201
|
+
const [command, ...names] = rest;
|
|
202
|
+
|
|
203
|
+
if (opts.version) log.plain(VERSION);
|
|
204
|
+
else if (opts.help || !command) usage();
|
|
205
|
+
else if (command === 'list') await listCommand();
|
|
206
|
+
else if (command === 'add') await addCommand(names, opts);
|
|
207
|
+
else die(`Perintah tidak dikenal: ${command}`, 'jalankan: npx khwarizmi-ui --help');
|
package/src/util.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
// Warna ANSI dimatikan kalau output-nya bukan terminal (mis. dipipe ke file).
|
|
5
|
+
const on = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
6
|
+
const wrap = (code) => (s) => (on ? `\x1b[${code}m${s}\x1b[0m` : s);
|
|
7
|
+
|
|
8
|
+
export const c = {
|
|
9
|
+
orange: wrap('38;5;208'),
|
|
10
|
+
dim: wrap('2'),
|
|
11
|
+
bold: wrap('1'),
|
|
12
|
+
red: wrap('31'),
|
|
13
|
+
green: wrap('32')
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const log = {
|
|
17
|
+
step: (m) => console.log(`${c.dim('-')} ${m}`),
|
|
18
|
+
done: (m) => console.log(`${c.green('✔')} ${m}`),
|
|
19
|
+
warn: (m) => console.log(`${c.orange('!')} ${m}`),
|
|
20
|
+
error: (m) => console.error(`${c.red('✖')} ${m}`),
|
|
21
|
+
plain: (m = '') => console.log(m)
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export function die(message, hint) {
|
|
25
|
+
log.error(message);
|
|
26
|
+
if (hint) log.plain(` ${c.dim(hint)}`);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Deteksi package manager dari lockfile yang ada, biar dependency-nya
|
|
32
|
+
* dipasang pakai tool yang sama dengan yang dipakai project itu.
|
|
33
|
+
*/
|
|
34
|
+
export function detectPackageManager(cwd) {
|
|
35
|
+
if (existsSync(join(cwd, 'bun.lock')) || existsSync(join(cwd, 'bun.lockb'))) return 'bun';
|
|
36
|
+
if (existsSync(join(cwd, 'pnpm-lock.yaml'))) return 'pnpm';
|
|
37
|
+
if (existsSync(join(cwd, 'yarn.lock'))) return 'yarn';
|
|
38
|
+
return 'npm';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const installArgs = {
|
|
42
|
+
npm: (pkgs) => ['install', ...pkgs],
|
|
43
|
+
pnpm: (pkgs) => ['add', ...pkgs],
|
|
44
|
+
yarn: (pkgs) => ['add', ...pkgs],
|
|
45
|
+
bun: (pkgs) => ['add', ...pkgs]
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/** Dependency yang sudah ada di package.json nggak perlu dipasang ulang. */
|
|
49
|
+
export function missingDeps(cwd, deps) {
|
|
50
|
+
const pkgPath = join(cwd, 'package.json');
|
|
51
|
+
if (!existsSync(pkgPath)) return deps;
|
|
52
|
+
try {
|
|
53
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
54
|
+
const have = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
55
|
+
// Buang penanda versi ("lucide-svelte@^1.0.0" -> "lucide-svelte"),
|
|
56
|
+
// termasuk buat paket scoped yang namanya diawali "@".
|
|
57
|
+
return deps.filter((d) => {
|
|
58
|
+
const at = d.lastIndexOf('@');
|
|
59
|
+
const name = at > 0 ? d.slice(0, at) : d;
|
|
60
|
+
return !have[name];
|
|
61
|
+
});
|
|
62
|
+
} catch {
|
|
63
|
+
return deps;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function fetchJson(url) {
|
|
68
|
+
let res;
|
|
69
|
+
try {
|
|
70
|
+
res = await fetch(url);
|
|
71
|
+
} catch (err) {
|
|
72
|
+
die(`Gagal menghubungi registry: ${url}`, err.message);
|
|
73
|
+
}
|
|
74
|
+
if (res.status === 404) return null;
|
|
75
|
+
if (!res.ok) die(`Registry membalas ${res.status} untuk ${url}`);
|
|
76
|
+
return res.json();
|
|
77
|
+
}
|