@kitecd/cli 1.0.0 → 1.2.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.
Files changed (80) hide show
  1. package/dist/doctor.js +181 -0
  2. package/dist/export.js +144 -0
  3. package/dist/ignore.js +38 -0
  4. package/dist/import.js +333 -0
  5. package/dist/index.js +271 -9
  6. package/dist/ops.js +338 -0
  7. package/dist/pack.js +15 -5
  8. package/dist/serve.js +27 -1
  9. package/dist/server/index.js +14562 -5621
  10. package/dist/upload.js +11 -3
  11. package/dist/verify.js +161 -0
  12. package/dist/web/assets/AuditLog-BFmJfgzL.js +1 -0
  13. package/dist/web/assets/ConfirmDialog-CJ8lJeUc.js +1 -0
  14. package/dist/web/assets/ConfirmDialog-D8avT8FJ.css +1 -0
  15. package/dist/web/assets/Dashboard-BTliTkq1.js +1 -0
  16. package/dist/web/assets/DefaultLayout-BG_y85yG.js +1 -0
  17. package/dist/web/assets/DefaultLayout-CZENO67n.css +1 -0
  18. package/dist/web/assets/FileExplorer-Bf32_MMS.js +1 -0
  19. package/dist/web/assets/LogBoard-0so-XiW3.css +1 -0
  20. package/dist/web/assets/LogBoard-CDz4n0DY.js +6 -0
  21. package/dist/web/assets/Login-C3zfObzP.js +1 -0
  22. package/dist/web/assets/Migration-BaKlcCkB.js +1 -0
  23. package/dist/web/assets/ProjectDetail-BL_D0OJY.js +1 -0
  24. package/dist/web/assets/ProjectDetail-ia6-z1kZ.css +1 -0
  25. package/dist/web/assets/ProjectList-7AnhOS7h.css +1 -0
  26. package/dist/web/assets/ProjectList-C4KuZEq4.js +1 -0
  27. package/dist/web/assets/Settings-CzWG9312.js +1 -0
  28. package/dist/web/assets/Storage-BCao_e7Y.js +1 -0
  29. package/dist/web/assets/Storage-DNadqpUy.css +1 -0
  30. package/dist/web/assets/{activity-DItEGOtI.js → activity-Ba-YPfmn.js} +1 -1
  31. package/dist/web/assets/archive-CJ5gDBKY.js +1 -0
  32. package/dist/web/assets/arrow-left-WEOMLXIi.js +1 -0
  33. package/dist/web/assets/chevron-right-DLiDJVJl.js +1 -0
  34. package/dist/web/assets/{circle-alert-Bfrn_ovD.js → circle-alert-DQzM-U4P.js} +1 -1
  35. package/dist/web/assets/clock-IwxBKgP4.js +1 -0
  36. package/dist/web/assets/constants-Ch47JPs3.js +1 -0
  37. package/dist/web/assets/copy-C1rADgcQ.js +1 -0
  38. package/dist/web/assets/createLucideIcon-CE-ry2oA.js +1 -0
  39. package/dist/web/assets/database-CI6acTSY.js +1 -0
  40. package/dist/web/assets/eye-Cas8HsmK.js +1 -0
  41. package/dist/web/assets/eye-off-4PD31MPV.js +1 -0
  42. package/dist/web/assets/file-text-C6nzo1us.js +1 -0
  43. package/dist/web/assets/folder-D9pvA6oI.js +1 -0
  44. package/dist/web/assets/folder-open-CGeIri0U.js +1 -0
  45. package/dist/web/assets/hard-drive-CGWwV4Ei.js +1 -0
  46. package/dist/web/assets/house-8ZvvlaEh.js +1 -0
  47. package/dist/web/assets/index-BFE6PEIL.js +2 -0
  48. package/dist/web/assets/index-XrzJwjrk.css +1 -0
  49. package/dist/web/assets/loader-circle-uiC7GaCG.js +1 -0
  50. package/dist/web/assets/plus-CfMi1Jv1.js +1 -0
  51. package/dist/web/assets/refresh-cw-0yb8DZDc.js +1 -0
  52. package/dist/web/assets/rotate-ccw-YevaWXw9.js +1 -0
  53. package/dist/web/assets/save-BuzCP3T1.js +1 -0
  54. package/dist/web/assets/scroll-text-BIYMFNN5.js +1 -0
  55. package/dist/web/assets/{server-C33taHNn.js → server-B31hj0g7.js} +1 -1
  56. package/dist/web/assets/{square-terminal-C8toRwjx.js → square-terminal-DT6aoXm0.js} +1 -1
  57. package/dist/web/assets/sun-BaEbKNDV.js +1 -0
  58. package/dist/web/assets/trash-2-BWqtqkeW.js +1 -0
  59. package/dist/web/index.html +3 -3
  60. package/package.json +2 -2
  61. package/dist/web/assets/Dashboard-pjIWWLub.js +0 -1
  62. package/dist/web/assets/DefaultLayout-Bj8fPWym.css +0 -1
  63. package/dist/web/assets/DefaultLayout-DelfwTTT.js +0 -1
  64. package/dist/web/assets/FileExplorer-xY5ejhhN.js +0 -1
  65. package/dist/web/assets/LogBoard-DzW-cEqH.css +0 -1
  66. package/dist/web/assets/LogBoard-tT61QjOx.js +0 -6
  67. package/dist/web/assets/Login-B4C149oC.js +0 -1
  68. package/dist/web/assets/ProjectDetail-Z8cZoqr5.js +0 -1
  69. package/dist/web/assets/ProjectList-9rbMuJeY.js +0 -1
  70. package/dist/web/assets/Settings-CtCNDUXY.js +0 -1
  71. package/dist/web/assets/clock-BPXGSCIV.js +0 -1
  72. package/dist/web/assets/constants-C4Zrkm2g.js +0 -1
  73. package/dist/web/assets/createLucideIcon-Cgv1AIRL.js +0 -1
  74. package/dist/web/assets/folder-open-jX-_Q7bA.js +0 -1
  75. package/dist/web/assets/index-C615tnMi.js +0 -2
  76. package/dist/web/assets/index-C9LiRc31.css +0 -1
  77. package/dist/web/assets/project-BFuaDcvV.js +0 -1
  78. package/dist/web/assets/refresh-cw-DWmqwQRn.js +0 -1
  79. package/dist/web/assets/save-BkiMrL9q.js +0 -1
  80. package/dist/web/assets/settings-CrCWmNyB.js +0 -1
package/dist/doctor.js ADDED
@@ -0,0 +1,181 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { execSync } from 'node:child_process';
5
+ import chalk from 'chalk';
6
+ import { getKiteHome, readGlobalConfig } from './home.js';
7
+ function fmt(level) {
8
+ if (level === 'ok')
9
+ return chalk.green('✓');
10
+ if (level === 'warn')
11
+ return chalk.yellow('!');
12
+ return chalk.red('✗');
13
+ }
14
+ function homeRelative(p) {
15
+ const home = os.homedir();
16
+ return p.startsWith(home) ? '~' + p.slice(home.length) : p;
17
+ }
18
+ function checkNodeVersion() {
19
+ const major = Number(process.versions.node.split('.')[0]);
20
+ if (Number.isFinite(major) && major >= 18) {
21
+ return { name: 'Node.js', level: 'ok', detail: `v${process.versions.node} (>=18 required)` };
22
+ }
23
+ return { name: 'Node.js', level: 'error', detail: `v${process.versions.node} (require >=18)` };
24
+ }
25
+ function checkKiteHome() {
26
+ const home = getKiteHome();
27
+ const out = [];
28
+ try {
29
+ fs.mkdirSync(home, { recursive: true });
30
+ const probe = path.join(home, `.doctor-${Date.now()}.tmp`);
31
+ fs.writeFileSync(probe, 'ok');
32
+ fs.unlinkSync(probe);
33
+ out.push({ name: 'Kite Home', level: 'ok', detail: homeRelative(home) });
34
+ }
35
+ catch (err) {
36
+ out.push({ name: 'Kite Home', level: 'error', detail: `${homeRelative(home)} not writable: ${err?.message}` });
37
+ }
38
+ for (const sub of ['deployments', 'tmp']) {
39
+ const p = path.join(home, sub);
40
+ try {
41
+ fs.mkdirSync(p, { recursive: true });
42
+ out.push({ name: `Kite Home / ${sub}`, level: 'ok', detail: homeRelative(p) });
43
+ }
44
+ catch (err) {
45
+ out.push({ name: `Kite Home / ${sub}`, level: 'error', detail: `${homeRelative(p)} create failed: ${err?.message}` });
46
+ }
47
+ }
48
+ return out;
49
+ }
50
+ function checkDiskFree() {
51
+ if (process.platform === 'win32') {
52
+ return { name: 'Disk free', level: 'warn', detail: 'skipped on Windows' };
53
+ }
54
+ try {
55
+ const home = getKiteHome();
56
+ const out = execSync(`df -kP "${home}"`, { encoding: 'utf-8', timeout: 3000 });
57
+ const lines = out.trim().split('\n');
58
+ const cols = lines[lines.length - 1].trim().split(/\s+/).slice(-5);
59
+ const total = Number(cols[0]);
60
+ const avail = Number(cols[2]);
61
+ if (!Number.isFinite(total) || !Number.isFinite(avail) || total <= 0) {
62
+ return { name: 'Disk free', level: 'warn', detail: 'unable to parse df output' };
63
+ }
64
+ const freeGB = (avail / 1024 / 1024).toFixed(2);
65
+ const pctUsed = Math.round(((total - avail) / total) * 100);
66
+ if (pctUsed >= 95)
67
+ return { name: 'Disk free', level: 'error', detail: `${freeGB} GB free (${pctUsed}% used)` };
68
+ if (pctUsed >= 85)
69
+ return { name: 'Disk free', level: 'warn', detail: `${freeGB} GB free (${pctUsed}% used)` };
70
+ return { name: 'Disk free', level: 'ok', detail: `${freeGB} GB free (${pctUsed}% used)` };
71
+ }
72
+ catch (err) {
73
+ return { name: 'Disk free', level: 'warn', detail: `df failed: ${err?.message}` };
74
+ }
75
+ }
76
+ function checkLocalConfig() {
77
+ const cfg = readGlobalConfig();
78
+ if (cfg.serverUrl)
79
+ return { name: 'Global config', level: 'ok', detail: `serverUrl=${cfg.serverUrl}` };
80
+ return { name: 'Global config', level: 'warn', detail: 'no serverUrl in ~/.kite/config.json (use `kite config set serverUrl <url>`)' };
81
+ }
82
+ async function checkRemote(opts) {
83
+ if (!opts.serverUrl) {
84
+ return [{ name: 'Remote', level: 'warn', detail: 'no server url (pass --server or configure globally)' }];
85
+ }
86
+ const base = opts.serverUrl.replace(/\/$/, '');
87
+ const out = [];
88
+ try {
89
+ const res = await fetch(`${base}/api/health`);
90
+ if (res.ok) {
91
+ const body = await res.json().catch(() => ({}));
92
+ out.push({ name: 'GET /api/health', level: 'ok', detail: `status=${body.status} uptime=${body.uptime}s version=${body.version}` });
93
+ }
94
+ else {
95
+ out.push({ name: 'GET /api/health', level: 'error', detail: `HTTP ${res.status}` });
96
+ return out;
97
+ }
98
+ }
99
+ catch (err) {
100
+ out.push({ name: 'GET /api/health', level: 'error', detail: `network error: ${err?.message}` });
101
+ return out;
102
+ }
103
+ if (!opts.token) {
104
+ out.push({ name: 'GET /api/health/detail', level: 'warn', detail: 'no admin token (pass --token or configure globally) — detail skipped' });
105
+ return out;
106
+ }
107
+ try {
108
+ const res = await fetch(`${base}/api/health/detail`, { headers: { Authorization: `Bearer ${opts.token}` } });
109
+ if (res.status === 401) {
110
+ out.push({ name: 'GET /api/health/detail', level: 'error', detail: 'unauthorized — token mismatch' });
111
+ return out;
112
+ }
113
+ const body = await res.json().catch(() => ({}));
114
+ if (!res.ok) {
115
+ out.push({ name: 'GET /api/health/detail', level: 'error', detail: `HTTP ${res.status}` });
116
+ }
117
+ if (body?.db) {
118
+ out.push({ name: 'Remote DB', level: body.db.ok ? 'ok' : 'error', detail: `${body.db.path} (${body.db.latencyMs}ms)${body.db.error ? ' ' + body.db.error : ''}` });
119
+ }
120
+ if (body?.kiteHome) {
121
+ out.push({ name: 'Remote Kite Home', level: body.kiteHome.writable ? 'ok' : 'error', detail: `${body.kiteHome.path} writable=${body.kiteHome.writable} tmp=${body.kiteHome.tmpWritable}` });
122
+ }
123
+ if (body?.disk) {
124
+ const d = body.disk;
125
+ if (d.percentUsed == null) {
126
+ out.push({ name: 'Remote Disk', level: 'warn', detail: 'unavailable on remote platform' });
127
+ }
128
+ else {
129
+ const lvl = d.percentUsed >= 95 ? 'error' : d.percentUsed >= 85 ? 'warn' : 'ok';
130
+ const freeGB = d.freeBytes != null ? (d.freeBytes / 1024 / 1024 / 1024).toFixed(2) + ' GB' : '?';
131
+ out.push({ name: 'Remote Disk', level: lvl, detail: `${freeGB} free (${d.percentUsed}% used)` });
132
+ }
133
+ }
134
+ if (body?.deploy) {
135
+ const rate = body.deploy.successRate;
136
+ const lvl = rate == null ? 'warn' : rate >= 0.8 ? 'ok' : rate >= 0.5 ? 'warn' : 'error';
137
+ out.push({ name: 'Recent deploys', level: lvl, detail: `last ${body.deploy.last5.length} success rate=${rate == null ? 'n/a' : (rate * 100).toFixed(0) + '%'}` });
138
+ }
139
+ if (body?.runtime) {
140
+ out.push({ name: 'Remote runtime', level: 'ok', detail: `${body.runtime.name} ${body.runtime.version} uptime=${body.uptimeSec}s` });
141
+ }
142
+ }
143
+ catch (err) {
144
+ out.push({ name: 'GET /api/health/detail', level: 'error', detail: `network error: ${err?.message}` });
145
+ }
146
+ return out;
147
+ }
148
+ export async function runDoctor(opts = {}) {
149
+ const cfg = readGlobalConfig();
150
+ const serverUrl = opts.server || process.env.KITE_SERVER_URL || cfg.serverUrl;
151
+ const token = opts.token || process.env.KITE_TOKEN || cfg.token;
152
+ const localChecks = [
153
+ checkNodeVersion(),
154
+ ...checkKiteHome(),
155
+ checkDiskFree(),
156
+ checkLocalConfig(),
157
+ ];
158
+ const remoteChecks = await checkRemote({ serverUrl, token });
159
+ console.log(chalk.bold('\n[Local]'));
160
+ for (const c of localChecks) {
161
+ console.log(` ${fmt(c.level)} ${c.name.padEnd(22)} ${chalk.gray(c.detail)}`);
162
+ }
163
+ console.log(chalk.bold(`\n[Remote ${serverUrl || '(none)'}]`));
164
+ for (const c of remoteChecks) {
165
+ console.log(` ${fmt(c.level)} ${c.name.padEnd(22)} ${chalk.gray(c.detail)}`);
166
+ }
167
+ const all = [...localChecks, ...remoteChecks];
168
+ const hasError = all.some(c => c.level === 'error');
169
+ const hasWarn = all.some(c => c.level === 'warn');
170
+ console.log('');
171
+ if (hasError) {
172
+ console.log(chalk.red('Doctor: some checks failed.'));
173
+ return 1;
174
+ }
175
+ if (hasWarn) {
176
+ console.log(chalk.yellow('Doctor: passed with warnings.'));
177
+ return 0;
178
+ }
179
+ console.log(chalk.green('Doctor: all checks passed.'));
180
+ return 0;
181
+ }
package/dist/export.js ADDED
@@ -0,0 +1,144 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import archiver from 'archiver';
4
+ import ora from 'ora';
5
+ import chalk from 'chalk';
6
+ import { createClient } from '@libsql/client';
7
+ import { ensureKiteHome } from './home.js';
8
+ import { packProject } from './pack.js';
9
+ import { parseIgnoreOption } from './ignore.js';
10
+ const rowsToObjects = (rows) => rows.map(row => ({ ...row }));
11
+ const formatBytes = (bytes) => {
12
+ if (bytes < 1024)
13
+ return `${bytes} B`;
14
+ if (bytes < 1024 * 1024)
15
+ return `${(bytes / 1024).toFixed(1)} KB`;
16
+ return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
17
+ };
18
+ const tableExists = async (client, name) => {
19
+ const result = await client.execute({
20
+ sql: `SELECT name FROM sqlite_master WHERE type='table' AND name = ?`,
21
+ args: [name],
22
+ });
23
+ return result.rows.length > 0;
24
+ };
25
+ const dumpTable = async (client, table) => {
26
+ if (!(await tableExists(client, table)))
27
+ return [];
28
+ const result = await client.execute(`SELECT * FROM ${table}`);
29
+ return rowsToObjects(result.rows);
30
+ };
31
+ export async function runExport(options, kiteVersion) {
32
+ const home = ensureKiteHome();
33
+ const dbPath = path.join(home, 'kite.db');
34
+ if (!fs.existsSync(dbPath)) {
35
+ console.error(chalk.red(`No Kite database found at ${dbPath}`));
36
+ console.error(chalk.gray('Run `kite serve` at least once on the source machine to initialize the database.'));
37
+ process.exit(1);
38
+ }
39
+ const includeArtifacts = !!options.includeArtifacts;
40
+ const includeLogs = !!options.includeLogs;
41
+ const stamp = new Date().toISOString().replace(/[-:.TZ]/g, '').slice(0, 14);
42
+ const outPath = path.resolve(process.cwd(), options.out || `kite-export-${stamp}.zip`);
43
+ const client = createClient({ url: `file:${dbPath}` });
44
+ try {
45
+ const projectFilter = (options.projects || '')
46
+ .split(',')
47
+ .map(s => s.trim())
48
+ .filter(Boolean);
49
+ const spinner = ora('Reading Kite database...').start();
50
+ const allProjects = await dumpTable(client, 'projects');
51
+ const projects = projectFilter.length > 0
52
+ ? allProjects.filter(p => projectFilter.includes(String(p.id)))
53
+ : allProjects;
54
+ const settings = await dumpTable(client, 'settings');
55
+ const deployments = includeLogs ? await dumpTable(client, 'deployments') : [];
56
+ const filteredDeployments = projectFilter.length > 0
57
+ ? deployments.filter(d => projectFilter.includes(String(d.project_id)))
58
+ : deployments;
59
+ spinner.succeed(chalk.green(`Read database: ${projects.length} projects, ${settings.length} settings, ${filteredDeployments.length} deployments`));
60
+ const manifest = {
61
+ schemaVersion: 1,
62
+ exportedAt: new Date().toISOString(),
63
+ kiteVersion,
64
+ includes: {
65
+ settings: settings.length > 0,
66
+ projects: projects.length > 0,
67
+ deployments: includeLogs,
68
+ artifacts: includeArtifacts,
69
+ },
70
+ projectIds: projects.map(p => String(p.id)),
71
+ artifacts: [],
72
+ };
73
+ const tmpDir = path.join(home, 'tmp', `export-${stamp}`);
74
+ fs.mkdirSync(tmpDir, { recursive: true });
75
+ const artifactsDir = path.join(tmpDir, 'artifacts');
76
+ if (includeArtifacts) {
77
+ fs.mkdirSync(artifactsDir, { recursive: true });
78
+ const artifactsSpinner = ora('Packing project artifacts...').start();
79
+ const ignoreCustom = parseIgnoreOption(options.ignore);
80
+ const ignoreBuiltin = options.ignoreBuiltin === false ? true : false;
81
+ for (const project of projects) {
82
+ const projectId = String(project.id);
83
+ const deployPath = String(project.deploy_path || '');
84
+ if (!deployPath || !fs.existsSync(deployPath)) {
85
+ manifest.artifacts.push({ projectId, deployPath, skipped: 'deploy path not found' });
86
+ continue;
87
+ }
88
+ try {
89
+ const stat = fs.statSync(deployPath);
90
+ if (!stat.isDirectory()) {
91
+ manifest.artifacts.push({ projectId, deployPath, skipped: 'deploy path is not a directory' });
92
+ continue;
93
+ }
94
+ const archiveName = `${projectId}.zip`;
95
+ const archivePath = path.join(artifactsDir, archiveName);
96
+ await packProject(deployPath, archivePath, {
97
+ ignore: ignoreCustom,
98
+ ignoreBuiltin,
99
+ });
100
+ manifest.artifacts.push({ projectId, deployPath, archive: `artifacts/${archiveName}` });
101
+ }
102
+ catch (err) {
103
+ manifest.artifacts.push({ projectId, deployPath, skipped: `pack error: ${err.message}` });
104
+ }
105
+ }
106
+ artifactsSpinner.succeed(chalk.green(`Packed artifacts for ${manifest.artifacts.filter(a => a.archive).length} projects`));
107
+ }
108
+ fs.writeFileSync(path.join(tmpDir, 'manifest.json'), `${JSON.stringify(manifest, null, 2)}\n`);
109
+ fs.writeFileSync(path.join(tmpDir, 'projects.json'), `${JSON.stringify(projects, null, 2)}\n`);
110
+ fs.writeFileSync(path.join(tmpDir, 'settings.json'), `${JSON.stringify(settings, null, 2)}\n`);
111
+ if (includeLogs) {
112
+ fs.writeFileSync(path.join(tmpDir, 'deployments.json'), `${JSON.stringify(filteredDeployments, null, 2)}\n`);
113
+ }
114
+ const zipSpinner = ora(`Writing ${path.basename(outPath)}...`).start();
115
+ await new Promise((resolve, reject) => {
116
+ const output = fs.createWriteStream(outPath);
117
+ const archive = archiver('zip', { zlib: { level: 9 } });
118
+ output.on('close', () => resolve());
119
+ archive.on('error', reject);
120
+ archive.pipe(output);
121
+ archive.directory(tmpDir, 'kite-export');
122
+ archive.finalize();
123
+ });
124
+ zipSpinner.succeed(chalk.green(`Wrote ${outPath}`));
125
+ fs.rmSync(tmpDir, { recursive: true, force: true });
126
+ const stat = fs.statSync(outPath);
127
+ console.log(chalk.gray(` Size: ${formatBytes(stat.size)}`));
128
+ console.log(chalk.gray(` Projects: ${projects.length}${projectFilter.length > 0 ? ` (filtered from ${allProjects.length})` : ''}`));
129
+ console.log(chalk.gray(` Settings: ${settings.length}`));
130
+ if (includeLogs)
131
+ console.log(chalk.gray(` Deployments: ${filteredDeployments.length}`));
132
+ if (includeArtifacts) {
133
+ const ok = manifest.artifacts.filter(a => a.archive).length;
134
+ const skipped = manifest.artifacts.filter(a => a.skipped).length;
135
+ console.log(chalk.gray(` Artifacts: ${ok} packed, ${skipped} skipped`));
136
+ for (const a of manifest.artifacts.filter(x => x.skipped)) {
137
+ console.log(chalk.yellow(` ! ${a.projectId}: ${a.skipped}`));
138
+ }
139
+ }
140
+ }
141
+ finally {
142
+ client.close();
143
+ }
144
+ }
package/dist/ignore.js ADDED
@@ -0,0 +1,38 @@
1
+ export const DEFAULT_IGNORED = [
2
+ '.git/**',
3
+ '.svn/**',
4
+ '.hg/**',
5
+ 'node_modules/**',
6
+ '.next/**',
7
+ '.nuxt/**',
8
+ '.turbo/**',
9
+ '.cache/**',
10
+ '.parcel-cache/**',
11
+ '.vite/**',
12
+ 'coverage/**',
13
+ '.nyc_output/**',
14
+ '.DS_Store',
15
+ 'Thumbs.db',
16
+ '.idea/**',
17
+ '.vscode/**',
18
+ '*.log',
19
+ 'logs/**',
20
+ 'tmp/**',
21
+ '.tmp/**',
22
+ '.deploy-archive.zip',
23
+ 'kite-export-*.tar.gz',
24
+ '.env*',
25
+ ];
26
+ export function mergeIgnore(opts = {}) {
27
+ const { custom = [], ignoreBuiltin = false } = opts;
28
+ const base = ignoreBuiltin ? [] : DEFAULT_IGNORED;
29
+ return Array.from(new Set([...base, ...custom]));
30
+ }
31
+ export function parseIgnoreOption(raw) {
32
+ if (!raw)
33
+ return [];
34
+ if (Array.isArray(raw)) {
35
+ return raw.flatMap(item => String(item).split(',')).map(s => s.trim()).filter(Boolean);
36
+ }
37
+ return String(raw).split(',').map(s => s.trim()).filter(Boolean);
38
+ }