@khwarizmi/cli 0.3.1 → 0.4.0

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/README.md CHANGED
@@ -1,107 +1,26 @@
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
1
+ # @khwarizmi/cli
2
+
3
+ CLI inisialisasi untuk [`@khwarizmi/svelte-ui`](https://www.npmjs.com/package/@khwarizmi/svelte-ui).
4
+ CLI ini tidak menyalin source komponen ke project.
5
+
6
+ ```bash
7
+ npm install @khwarizmi/svelte-ui
8
+ npx @khwarizmi/cli init
9
+ ```
10
+
11
+ `init` menambahkan import stylesheet package ke `src/app.css` atau
12
+ `src/routes/app.css`. Untuk lokasi lain:
13
+
14
+ ```bash
15
+ npx @khwarizmi/cli init --css src/styles/app.css
16
+ ```
17
+
18
+ Perintah ini idempoten: menjalankannya kembali tidak menggandakan import.
19
+
20
+ Setelah itu, gunakan komponen langsung dari package:
21
+
22
+ ```svelte
23
+ <script lang="ts">
24
+ import { Button, Card } from '@khwarizmi/svelte-ui';
25
+ </script>
26
+ ```
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@khwarizmi/cli",
3
- "version": "0.3.1",
4
- "description": "CLI buat memasang komponen Khwarizmi UI ke project kamu.",
3
+ "version": "0.4.0",
4
+ "description": "Inisialisasi @khwarizmi/svelte-ui di project Svelte.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "khwarizmi-ui": "./src/index.js"
@@ -11,6 +11,9 @@
11
11
  "README.md",
12
12
  "LICENSE"
13
13
  ],
14
+ "scripts": {
15
+ "test": "node --test"
16
+ },
14
17
  "engines": {
15
18
  "node": ">=18"
16
19
  },
@@ -19,7 +22,7 @@
19
22
  "svelte",
20
23
  "ui",
21
24
  "components",
22
- "tailwind"
25
+ "init"
23
26
  ],
24
27
  "license": "MIT",
25
28
  "homepage": "https://ui.khwarizmi.co.id"
package/src/index.js CHANGED
@@ -1,207 +1,53 @@
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
- }
1
+ #!/usr/bin/env node
2
+ import { resolve } from 'node:path';
3
+ import { c, die, log } from './util.js';
4
+ import { init } from './init.js';
61
5
 
62
- async function ensureGlobalStyles(styles, opts) {
63
- if (!styles) return;
6
+ const VERSION = '1.0.0';
64
7
 
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
- }
8
+ function usage() {
9
+ log.plain(`
10
+ ${c.bold('khwarizmi-ui')} ${c.dim('v' + VERSION)}
70
11
 
71
- const candidates = ['src/app.css', 'src/routes/app.css'];
72
- let appCss = candidates.map((path) => join(opts.cwd, path)).find(existsSync);
12
+ ${c.dim('Siapkan @khwarizmi/svelte-ui di project Svelte kamu.')}
73
13
 
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
- }
14
+ ${c.bold('Perintah')}
15
+ ${c.orange('init')} Tambahkan stylesheet global Khwarizmi UI
84
16
 
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
- }
17
+ ${c.bold('Opsi')}
18
+ --cwd <path> Jalankan di folder lain
19
+ --css <path> Pilih stylesheet global aplikasi
20
+ --help, --version
90
21
 
91
- const content = readFileSync(appCss, 'utf8');
92
- if (!content.includes(importLine)) writeFileSync(appCss, `${importLine}\n${content}`, 'utf8');
22
+ ${c.bold('Contoh')}
23
+ ${c.dim('$')} npm install @khwarizmi/svelte-ui
24
+ ${c.dim('$')} npx @khwarizmi/cli init
25
+ `);
93
26
  }
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 };
27
+
28
+ function parseArgs(argv) {
29
+ const opts = { cwd: process.cwd() };
30
+ const rest = [];
31
+ for (let i = 0; i < argv.length; i++) {
32
+ const arg = argv[i];
33
+ if (arg === '--cwd') opts.cwd = resolve(argv[++i] ?? '.');
34
+ else if (arg === '--css') opts.css = argv[++i];
35
+ else if (arg === '--help' || arg === '-h') opts.help = true;
36
+ else if (arg === '--version' || arg === '-v') opts.version = true;
37
+ else rest.push(arg);
117
38
  }
39
+ return { opts, rest };
40
+ }
41
+
42
+ const { opts, rest } = parseArgs(process.argv.slice(2));
43
+ const [command] = rest;
118
44
 
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);
45
+ if (opts.version) log.plain(VERSION);
46
+ else if (opts.help || !command) usage();
47
+ else if (command === 'init') {
48
+ try {
49
+ init(opts);
50
+ } catch (error) {
51
+ die(error.message);
131
52
  }
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');
53
+ } else die(`Perintah tidak dikenal: ${command}`, 'jalankan: khwarizmi-ui --help');
package/src/init.js ADDED
@@ -0,0 +1,20 @@
1
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { hasPackage, log } from './util.js';
4
+
5
+ export const STYLE_IMPORT = "@import '@khwarizmi/svelte-ui/styles.css';";
6
+
7
+ export function init(opts) {
8
+ if (!hasPackage(opts.cwd, '@khwarizmi/svelte-ui')) {
9
+ throw new Error('@khwarizmi/svelte-ui belum terpasang. Jalankan package manager untuk memasangnya terlebih dahulu.');
10
+ }
11
+
12
+ const candidates = opts.css ? [opts.css] : ['src/app.css', 'src/routes/app.css'];
13
+ const relative = candidates.find((file) => existsSync(join(opts.cwd, file)));
14
+ if (!relative) throw new Error('Stylesheet global tidak ditemukan. Tentukan dengan --css <path>.');
15
+
16
+ const target = join(opts.cwd, relative);
17
+ const content = readFileSync(target, 'utf8');
18
+ if (!content.includes(STYLE_IMPORT)) writeFileSync(target, `${STYLE_IMPORT}\n${content}`, 'utf8');
19
+ log.done(`Khwarizmi UI siap di ${relative}`);
20
+ }
package/src/util.js CHANGED
@@ -1,77 +1,29 @@
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
- }
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+
4
+ const on = process.stdout.isTTY && !process.env.NO_COLOR;
5
+ const wrap = (code) => (text) => (on ? `\x1b[${code}m${text}\x1b[0m` : text);
6
+
7
+ export const c = { orange: wrap('38;5;208'), dim: wrap('2'), bold: wrap('1'), red: wrap('31'), green: wrap('32') };
8
+ export const log = {
9
+ done: (message) => console.log(`${c.green('')} ${message}`),
10
+ error: (message) => console.error(`${c.red('')} ${message}`),
11
+ plain: (message = '') => console.log(message)
12
+ };
13
+
14
+ export function die(message, hint) {
15
+ log.error(message);
16
+ if (hint) log.plain(` ${c.dim(hint)}`);
17
+ process.exit(1);
18
+ }
19
+
20
+ export function hasPackage(cwd, name) {
21
+ const path = join(cwd, 'package.json');
22
+ if (!existsSync(path)) return false;
23
+ try {
24
+ const pkg = JSON.parse(readFileSync(path, 'utf8'));
25
+ return Boolean(pkg.dependencies?.[name] || pkg.devDependencies?.[name]);
26
+ } catch {
27
+ return false;
28
+ }
29
+ }
@@ -1,5 +0,0 @@
1
- /**
2
- * Alamat registry. Bisa ditimpa lewat env var buat nyoba versi lokal:
3
- * KHWARIZMI_UI_REGISTRY=http://localhost:5177 npx khwarizmi-ui add button
4
- */
5
- export const REGISTRY_URL = process.env.KHWARIZMI_UI_REGISTRY ?? 'https://ui.khwarizmi.co.id';