nivii 0.2.3 → 0.2.4

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.
@@ -1,159 +0,0 @@
1
- import path from 'path';
2
- import chalk from 'chalk';
3
- import { detectFramework } from '../core/detect.js';
4
- import { runBuild } from '../core/build.js';
5
- import { uploadDeploy } from '../core/upload.js';
6
- import { generateSlug } from '../utils/hash.js';
7
- import { loadConfig, updateConfig } from '../utils/config.js';
8
- import { showQR, copyToClipboard, openBrowser } from '../core/qr.js';
9
- import { startLiveSync } from '../core/tunnel.js';
10
- import { showBanner } from '../ui/welcome.js';
11
- import { step, success, warn } from '../ui/spinner.js';
12
- import { theme } from '../ui/theme.js';
13
-
14
- export interface ShareOptions {
15
- dir?: string;
16
- slug?: string;
17
- pass?: string;
18
- otp?: boolean;
19
- expires?: string;
20
- live?: boolean;
21
- collab?: boolean;
22
- noBuild?: boolean;
23
- cmd?: string;
24
- port?: number;
25
- maxViews?: number;
26
- selfDestruct?: boolean;
27
- open?: boolean;
28
- qr?: boolean;
29
- }
30
-
31
- export async function runShare(opts: ShareOptions) {
32
- showBanner();
33
-
34
- const cwd = opts.dir ? path.resolve(opts.dir) : process.cwd();
35
- const config = loadConfig();
36
-
37
- // ─── 1. Detect framework ───────────────────────────────────────────────────
38
- step('🔍', 'Detecting project type…', cwd);
39
- const detected = detectFramework(cwd);
40
-
41
- const frameworkLabel: Record<string, string> = {
42
- nextjs: 'Next.js', vite: 'Vite', 'react-cra': 'React (CRA)',
43
- vue: 'Vue', svelte: 'Svelte', sveltekit: 'SvelteKit',
44
- astro: 'Astro', remix: 'Remix', nuxt: 'Nuxt',
45
- solid: 'Solid.js', express: 'Express', fastify: 'Fastify',
46
- hono: 'Hono', static: 'Static HTML', unknown: 'Unknown',
47
- };
48
-
49
- success(`Detected ${chalk.cyan(frameworkLabel[detected.framework] || detected.framework)} ` +
50
- `${theme.muted(`(${detected.confidence}% confidence)`)}`);
51
-
52
- if (detected.framework === 'unknown' && !opts.noBuild) {
53
- warn('Could not detect framework. Using current directory as static site.');
54
- }
55
-
56
- // ─── 2. Build ──────────────────────────────────────────────────────────────
57
- const { outputDir } = await runBuild({
58
- cwd,
59
- detect: detected,
60
- customCmd: opts.cmd,
61
- noBuild: opts.noBuild,
62
- });
63
-
64
- const fullOutputDir = path.resolve(cwd, outputDir);
65
-
66
- // ─── 3. Slug ───────────────────────────────────────────────────────────────
67
- let slug = opts.slug;
68
- if (!slug) {
69
- slug = generateSlug(8);
70
- } else if (config.plan !== 'pro') {
71
- warn('Custom slugs require Nivii Pro. Using random slug instead.');
72
- slug = generateSlug(8);
73
- }
74
-
75
- // ─── 4. Upload (files directly, no zip) ────────────────────────────────────
76
- const deployResult = await uploadDeploy({
77
- buildDir: fullOutputDir,
78
- slug,
79
- token: config.token,
80
- password: opts.pass,
81
- otp: opts.otp,
82
- expires: opts.expires || (config.plan === 'pro' ? undefined : '48h'),
83
- live: opts.live,
84
- collab: opts.collab,
85
- maxViews: opts.maxViews,
86
- selfDestruct: opts.selfDestruct,
87
- });
88
-
89
- // ─── 5. Save to config ─────────────────────────────────────────────────────
90
- const deployments = config.deployments || [];
91
- deployments.unshift({
92
- slug: deployResult.slug,
93
- url: deployResult.url,
94
- createdAt: new Date().toISOString(),
95
- expiresAt: deployResult.expiresAt,
96
- });
97
- updateConfig({ deployments: deployments.slice(0, 20) });
98
-
99
- // ─── 6. Output ─────────────────────────────────────────────────────────────
100
- console.log('');
101
- console.log(theme.box([
102
- theme.accent(' 🚀 Your project is live!'),
103
- '',
104
- ' ' + theme.primary('URL:') + ' ' + chalk.underline.cyan(deployResult.url),
105
- ...(deployResult.expiresAt ? [' ' + theme.muted('Expires:') + ' ' + theme.muted(new Date(deployResult.expiresAt).toLocaleString())] : []),
106
- ...(opts.pass ? [' ' + theme.muted('Protected:') + ' ' + theme.success('✓ Password')] : []),
107
- ...(opts.otp && deployResult.otpCode ? [' ' + theme.muted('OTP:') + ' ' + theme.warning(deployResult.otpCode)] : []),
108
- ...(opts.live ? [' ' + theme.muted('Live sync:') + ' ' + theme.success('✓ Active')] : []),
109
- ...(opts.collab ? [' ' + theme.muted('Collab:') + ' ' + theme.success('✓ Active')] : []),
110
- ]));
111
- console.log('');
112
-
113
- // ─── 7. Copy URL to clipboard ──────────────────────────────────────────────
114
- const copied = await copyToClipboard(deployResult.url);
115
- if (copied) {
116
- success('URL copied to clipboard');
117
- }
118
-
119
- // ─── 8. QR code ────────────────────────────────────────────────────────────
120
- if (opts.qr !== false) {
121
- await showQR(deployResult.url);
122
- }
123
-
124
- // ─── 9. Open browser ───────────────────────────────────────────────────────
125
- if (opts.open !== false) {
126
- await openBrowser(deployResult.url);
127
- }
128
-
129
- // ─── 10. Live sync ─────────────────────────────────────────────────────────
130
- if (opts.live && deployResult.liveToken) {
131
- console.log('');
132
- console.log(theme.accent(' ⚡ Live Sync Active') + theme.muted(' — watching for changes…'));
133
- console.log(theme.muted(' Press Ctrl+C to stop'));
134
- console.log('');
135
-
136
- const stopLive = startLiveSync({
137
- watchDir: fullOutputDir,
138
- slug: deployResult.slug,
139
- liveToken: deployResult.liveToken,
140
- onUpdate: async () => {
141
- await uploadDeploy({
142
- buildDir: fullOutputDir,
143
- slug: deployResult.slug,
144
- token: config.token,
145
- live: true,
146
- });
147
- },
148
- });
149
-
150
- process.on('SIGINT', () => {
151
- stopLive();
152
- console.log('');
153
- console.log(theme.muted(' ↳ Live sync stopped'));
154
- process.exit(0);
155
- });
156
-
157
- await new Promise(() => {});
158
- }
159
- }
@@ -1,16 +0,0 @@
1
- import chalk from 'chalk';
2
- import { loadConfig } from '../utils/config.js';
3
- import { theme } from '../ui/theme.js';
4
-
5
- export function runWhoami() {
6
- const config = loadConfig();
7
- console.log('');
8
- if (!config.token) {
9
- console.log(theme.muted(' Not logged in. Using anonymous mode.'));
10
- console.log(' ' + theme.accent('nivii login') + theme.muted(' to unlock Pro features.'));
11
- } else {
12
- console.log(theme.success(' ✓ Logged in'));
13
- console.log(' ' + chalk.cyan('Plan: ') + chalk.white(config.plan || 'free'));
14
- }
15
- console.log('');
16
- }
@@ -1,64 +0,0 @@
1
- /**
2
- * Nivii Analytics — View deployment analytics
3
- */
4
- import { loadConfig } from '../utils/config.js';
5
- import { theme } from '../ui/theme.js';
6
- import chalk from 'chalk';
7
-
8
- export interface AnalyticsData {
9
- views: number;
10
- sessions: Array<{
11
- timestamp: string;
12
- country: string;
13
- city: string;
14
- ua: string;
15
- }>;
16
- }
17
-
18
- export async function runAnalytics(slug?: string) {
19
- const config = loadConfig();
20
- const apiBase = config.apiBase || 'https://api.nivii.app';
21
-
22
- if (!slug) {
23
- const deployments = config.deployments || [];
24
- if (!deployments.length) {
25
- console.log(theme.muted('\n No deployments found. Run: nivii share\n'));
26
- return;
27
- }
28
- slug = deployments[0].slug;
29
- }
30
-
31
- console.log('');
32
- console.log(theme.primary(' Analytics') + theme.muted(` for ${slug}.nivii.app`));
33
- console.log('');
34
-
35
- try {
36
- const { default: fetch } = await import('node-fetch');
37
- const res = await fetch(`${apiBase}/analytics/${slug}`, {
38
- headers: config.token ? { Authorization: `Bearer ${config.token}` } : {},
39
- });
40
-
41
- if (!res.ok) {
42
- console.log(theme.error(' Failed to fetch analytics: ' + res.status));
43
- return;
44
- }
45
-
46
- const data = await res.json() as AnalyticsData;
47
-
48
- console.log(' ' + chalk.cyan('Total views:') + ' ' + chalk.white(String(data.views)));
49
- console.log('');
50
-
51
- if (data.sessions && data.sessions.length > 0) {
52
- console.log(' ' + theme.muted('Recent visitors:'));
53
- console.log('');
54
- data.sessions.slice(-10).reverse().forEach(s => {
55
- const time = new Date(s.timestamp).toLocaleString();
56
- console.log(` ${chalk.hex('#6B7280')(time)} ${chalk.cyan(s.country)}/${chalk.white(s.city)}`);
57
- });
58
- }
59
-
60
- console.log('');
61
- } catch (err: any) {
62
- console.log(theme.error(' ' + err.message));
63
- }
64
- }
package/src/core/build.ts DELETED
@@ -1,46 +0,0 @@
1
- import { spawn } from 'child_process';
2
- import type { DetectResult } from './detect.js';
3
- import { createSpinner, step } from '../ui/spinner.js';
4
- import chalk from 'chalk';
5
-
6
- export interface BuildOptions {
7
- cwd: string;
8
- detect: DetectResult;
9
- customCmd?: string;
10
- noBuild?: boolean;
11
- }
12
-
13
- export async function runBuild(opts: BuildOptions): Promise<{ outputDir: string }> {
14
- const { cwd, detect, customCmd, noBuild } = opts;
15
-
16
- if (noBuild || detect.buildCommand === null) {
17
- step('📁', 'Skipping build', detect.outputDir);
18
- return { outputDir: detect.outputDir };
19
- }
20
-
21
- const cmd = customCmd || detect.buildCommand;
22
- const spinner = createSpinner(`Building with ${chalk.cyan(detect.framework)}…`);
23
- spinner.start();
24
-
25
- return new Promise((resolve, reject) => {
26
- const proc = spawn(cmd, [], {
27
- cwd,
28
- shell: true,
29
- env: { ...process.env, NODE_ENV: 'production', CI: '1' },
30
- });
31
-
32
- let stderr = '';
33
- proc.stderr.on('data', d => { stderr += d.toString(); });
34
-
35
- proc.on('close', code => {
36
- if (code === 0) {
37
- spinner.succeed(chalk.hex('#10B981')(`Build complete `) + chalk.hex('#6B7280')(`(${detect.framework})`));
38
- resolve({ outputDir: detect.outputDir });
39
- } else {
40
- spinner.fail(chalk.hex('#EF4444')('Build failed'));
41
- console.log(chalk.hex('#6B7280')(stderr.slice(-800)));
42
- reject(new Error(`Build exited with code ${code}`));
43
- }
44
- });
45
- });
46
- }
@@ -1,159 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
-
4
- export type Framework =
5
- | 'nextjs'
6
- | 'vite'
7
- | 'react-cra'
8
- | 'vue'
9
- | 'svelte'
10
- | 'sveltekit'
11
- | 'astro'
12
- | 'remix'
13
- | 'nuxt'
14
- | 'solid'
15
- | 'express'
16
- | 'fastify'
17
- | 'hono'
18
- | 'static'
19
- | 'unknown';
20
-
21
- export interface DetectResult {
22
- framework: Framework;
23
- buildCommand: string | null;
24
- outputDir: string;
25
- isServer: boolean;
26
- port?: number;
27
- packageManager: 'npm' | 'pnpm' | 'yarn' | 'bun';
28
- confidence: number; // 0-100
29
- }
30
-
31
- export function detectFramework(cwd: string): DetectResult {
32
- const pkg = readJson(path.join(cwd, 'package.json'));
33
- const deps = {
34
- ...(pkg?.dependencies || {}),
35
- ...(pkg?.devDependencies || {}),
36
- };
37
-
38
- const has = (name: string) => name in deps;
39
- const hasFile = (...files: string[]) =>
40
- files.some(f => fs.existsSync(path.join(cwd, f)));
41
-
42
- const pm = detectPackageManager(cwd);
43
-
44
- // Next.js
45
- if (has('next')) {
46
- const outputDir = fs.existsSync(path.join(cwd, 'out')) ? 'out' :
47
- fs.existsSync(path.join(cwd, '.next')) ? '.next' : 'out';
48
- return {
49
- framework: 'nextjs',
50
- buildCommand: `${pm} run build`,
51
- outputDir,
52
- isServer: false,
53
- packageManager: pm,
54
- confidence: 99,
55
- };
56
- }
57
-
58
- // SvelteKit
59
- if (has('@sveltejs/kit')) {
60
- return {
61
- framework: 'sveltekit',
62
- buildCommand: `${pm} run build`,
63
- outputDir: 'build',
64
- isServer: false,
65
- packageManager: pm,
66
- confidence: 95,
67
- };
68
- }
69
-
70
- // Astro
71
- if (has('astro')) {
72
- return {
73
- framework: 'astro',
74
- buildCommand: `${pm} run build`,
75
- outputDir: 'dist',
76
- isServer: false,
77
- packageManager: pm,
78
- confidence: 95,
79
- };
80
- }
81
-
82
- // Remix
83
- if (has('@remix-run/react') || has('@remix-run/node')) {
84
- return {
85
- framework: 'remix',
86
- buildCommand: `${pm} run build`,
87
- outputDir: 'public',
88
- isServer: true,
89
- packageManager: pm,
90
- confidence: 90,
91
- };
92
- }
93
-
94
- // Nuxt
95
- if (has('nuxt') || has('nuxt3') || has('nuxt-edge')) {
96
- return {
97
- framework: 'nuxt',
98
- buildCommand: `${pm} run generate`,
99
- outputDir: '.output/public',
100
- isServer: false,
101
- packageManager: pm,
102
- confidence: 90,
103
- };
104
- }
105
-
106
- // Vite (React/Vue/Svelte/Solid)
107
- if (has('vite')) {
108
- const outputDir = 'dist';
109
- if (has('react') || has('react-dom')) {
110
- return { framework: 'react-cra', buildCommand: `${pm} run build`, outputDir, isServer: false, packageManager: pm, confidence: 88 };
111
- }
112
- if (has('vue')) {
113
- return { framework: 'vue', buildCommand: `${pm} run build`, outputDir, isServer: false, packageManager: pm, confidence: 88 };
114
- }
115
- if (has('svelte')) {
116
- return { framework: 'svelte', buildCommand: `${pm} run build`, outputDir, isServer: false, packageManager: pm, confidence: 88 };
117
- }
118
- if (has('solid-js')) {
119
- return { framework: 'solid', buildCommand: `${pm} run build`, outputDir, isServer: false, packageManager: pm, confidence: 88 };
120
- }
121
- return { framework: 'vite', buildCommand: `${pm} run build`, outputDir, isServer: false, packageManager: pm, confidence: 85 };
122
- }
123
-
124
- // Express/Fastify/Hono server
125
- if (has('express')) {
126
- return { framework: 'express', buildCommand: null, outputDir: '.', isServer: true, port: 3000, packageManager: pm, confidence: 80 };
127
- }
128
- if (has('fastify')) {
129
- return { framework: 'fastify', buildCommand: null, outputDir: '.', isServer: true, port: 3000, packageManager: pm, confidence: 80 };
130
- }
131
- if (has('hono')) {
132
- return { framework: 'hono', buildCommand: null, outputDir: '.', isServer: true, port: 3000, packageManager: pm, confidence: 80 };
133
- }
134
-
135
- // Static HTML/dist/build
136
- if (hasFile('index.html') || hasFile('dist/index.html') || hasFile('build/index.html') || hasFile('out/index.html')) {
137
- const outputDir = fs.existsSync(path.join(cwd, 'dist/index.html')) ? 'dist' :
138
- fs.existsSync(path.join(cwd, 'build/index.html')) ? 'build' :
139
- fs.existsSync(path.join(cwd, 'out/index.html')) ? 'out' : '.';
140
- return { framework: 'static', buildCommand: null, outputDir, isServer: false, packageManager: pm, confidence: 70 };
141
- }
142
-
143
- return { framework: 'unknown', buildCommand: null, outputDir: '.', isServer: false, packageManager: pm, confidence: 10 };
144
- }
145
-
146
- function detectPackageManager(cwd: string): 'npm' | 'pnpm' | 'yarn' | 'bun' {
147
- if (fs.existsSync(path.join(cwd, 'bun.lockb'))) return 'bun';
148
- if (fs.existsSync(path.join(cwd, 'pnpm-lock.yaml'))) return 'pnpm';
149
- if (fs.existsSync(path.join(cwd, 'yarn.lock'))) return 'yarn';
150
- return 'npm';
151
- }
152
-
153
- function readJson(filePath: string): any {
154
- try {
155
- return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
156
- } catch {
157
- return null;
158
- }
159
- }
package/src/core/qr.ts DELETED
@@ -1,30 +0,0 @@
1
- import qrcode from 'qrcode-terminal';
2
- import chalk from 'chalk';
3
- import { theme } from '../ui/theme.js';
4
-
5
- export async function showQR(url: string): Promise<void> {
6
- console.log('');
7
- console.log(theme.muted(' ┌─────────────────────────────────┐'));
8
- console.log(theme.muted(' │') + ' ' + theme.accent('Scan to open on mobile') + ' ' + theme.muted('│'));
9
- console.log(theme.muted(' └─────────────────────────────────┘'));
10
- console.log('');
11
- qrcode.generate(url, { small: true });
12
- console.log('');
13
- }
14
-
15
- export async function copyToClipboard(text: string): Promise<boolean> {
16
- try {
17
- const { default: clipboardy } = await import('clipboardy');
18
- await clipboardy.write(text);
19
- return true;
20
- } catch {
21
- return false;
22
- }
23
- }
24
-
25
- export async function openBrowser(url: string): Promise<void> {
26
- try {
27
- const { default: open } = await import('open');
28
- await open(url);
29
- } catch {}
30
- }
@@ -1,70 +0,0 @@
1
- import WebSocket from 'ws';
2
- import chokidar from 'chokidar';
3
- import path from 'path';
4
- import chalk from 'chalk';
5
- import { theme } from '../ui/theme.js';
6
-
7
- export interface LiveSyncOptions {
8
- watchDir: string;
9
- slug: string;
10
- liveToken: string;
11
- wsBase?: string;
12
- onUpdate?: () => Promise<void>;
13
- }
14
-
15
- export function startLiveSync(opts: LiveSyncOptions): () => void {
16
- const wsUrl = opts.wsBase || 'wss://live.nivii.app';
17
- let ws: WebSocket | null = null;
18
- let reconnectTimer: ReturnType<typeof setTimeout> | null = null;
19
-
20
- function connect() {
21
- ws = new WebSocket(`${wsUrl}/live/${opts.slug}?token=${opts.liveToken}`);
22
-
23
- ws.on('open', () => {
24
- console.log(theme.success(' ⚡') + ' ' + chalk.white('Live sync connected'));
25
- });
26
-
27
- ws.on('close', () => {
28
- console.log(theme.muted(' ↻ Live sync reconnecting…'));
29
- reconnectTimer = setTimeout(connect, 3000);
30
- });
31
-
32
- ws.on('error', () => {
33
- // silent
34
- });
35
- }
36
-
37
- connect();
38
-
39
- // Watch for file changes
40
- const watcher = chokidar.watch(opts.watchDir, {
41
- ignored: /(^|[/\\])\..|(node_modules)/,
42
- persistent: true,
43
- ignoreInitial: true,
44
- });
45
-
46
- let debounceTimer: ReturnType<typeof setTimeout> | null = null;
47
- const triggerUpdate = (filePath: string) => {
48
- if (debounceTimer) clearTimeout(debounceTimer);
49
- debounceTimer = setTimeout(async () => {
50
- console.log('');
51
- console.log(theme.accent(' ⚡') + ' ' + chalk.white('Change detected: ') + chalk.hex('#6B7280')(path.relative(opts.watchDir, filePath)));
52
- if (opts.onUpdate) {
53
- await opts.onUpdate();
54
- }
55
- if (ws?.readyState === WebSocket.OPEN) {
56
- ws.send(JSON.stringify({ type: 'reload', slug: opts.slug }));
57
- }
58
- }, 300);
59
- };
60
-
61
- watcher.on('change', triggerUpdate);
62
- watcher.on('add', triggerUpdate);
63
- watcher.on('unlink', triggerUpdate);
64
-
65
- return () => {
66
- watcher.close();
67
- if (reconnectTimer) clearTimeout(reconnectTimer);
68
- ws?.close();
69
- };
70
- }
@@ -1,95 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import os from 'os';
4
- import fetch from 'node-fetch';
5
- import FormData from 'form-data';
6
- import { createSpinner } from '../ui/spinner.js';
7
- import { API_BASE } from '../utils/config.js';
8
- import chalk from 'chalk';
9
-
10
- export interface UploadOptions {
11
- buildDir: string; // path to the built output directory
12
- slug: string;
13
- token?: string;
14
- password?: string;
15
- otp?: boolean;
16
- expires?: string;
17
- live?: boolean;
18
- collab?: boolean;
19
- maxViews?: number;
20
- selfDestruct?: boolean;
21
- }
22
-
23
- export interface DeployResult {
24
- url: string;
25
- slug: string;
26
- liveToken?: string;
27
- collabToken?: string;
28
- otpCode?: string;
29
- expiresAt?: string;
30
- }
31
-
32
- /** Recursively collect all files under a directory */
33
- function collectFiles(dir: string, base: string = dir): { abs: string; rel: string }[] {
34
- const results: { abs: string; rel: string }[] = [];
35
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
36
- const abs = path.join(dir, entry.name);
37
- if (entry.isDirectory()) {
38
- results.push(...collectFiles(abs, base));
39
- } else {
40
- results.push({ abs, rel: path.relative(base, abs).replace(/\\/g, '/') });
41
- }
42
- }
43
- return results;
44
- }
45
-
46
- export async function uploadDeploy(opts: UploadOptions): Promise<DeployResult> {
47
- const spinner = createSpinner('Uploading to edge network…');
48
- spinner.start();
49
-
50
- try {
51
- const files = collectFiles(opts.buildDir);
52
-
53
- if (files.length === 0) {
54
- throw new Error('No files found in build directory');
55
- }
56
-
57
- const form = new FormData();
58
- form.append('slug', opts.slug);
59
- form.append('fileCount', String(files.length));
60
- if (opts.password) form.append('password', opts.password);
61
- if (opts.otp) form.append('otp', 'true');
62
- if (opts.expires) form.append('expires', opts.expires);
63
- if (opts.live) form.append('live', 'true');
64
- if (opts.collab) form.append('collab', 'true');
65
- if (opts.maxViews) form.append('maxViews', String(opts.maxViews));
66
- if (opts.selfDestruct) form.append('selfDestruct', 'true');
67
-
68
- // Attach each file with its relative path as the field name
69
- for (const { abs, rel } of files) {
70
- form.append('file[]', fs.createReadStream(abs), { filename: rel });
71
- form.append('path[]', rel);
72
- }
73
-
74
- const res = await fetch(`${API_BASE}/deploy`, {
75
- method: 'POST',
76
- headers: {
77
- ...(opts.token ? { Authorization: `Bearer ${opts.token}` } : {}),
78
- ...form.getHeaders(),
79
- },
80
- body: form,
81
- });
82
-
83
- if (!res.ok) {
84
- const body = await res.text();
85
- throw new Error(`Upload failed: ${res.status} ${body}`);
86
- }
87
-
88
- const result = await res.json() as DeployResult;
89
- spinner.succeed(chalk.hex('#10B981')('Deployed to edge ') + chalk.hex('#6B7280')(`(${opts.slug})`));
90
- return result;
91
- } catch (err: any) {
92
- spinner.fail(chalk.hex('#EF4444')('Upload failed'));
93
- throw err;
94
- }
95
- }
package/src/ui/spinner.ts DELETED
@@ -1,36 +0,0 @@
1
- import ora, { Ora } from 'ora';
2
- import chalk from 'chalk';
3
-
4
- const FRAMES = ['⠋','⠙','⠹','⠸','⠼','⠴','⠦','⠧','⠇','⠏'];
5
-
6
- export function createSpinner(text: string): Ora {
7
- return ora({
8
- text: chalk.hex('#A78BFA')(text),
9
- spinner: {
10
- interval: 80,
11
- frames: FRAMES.map(f => chalk.hex('#7C3AED')(f)),
12
- },
13
- color: 'magenta',
14
- });
15
- }
16
-
17
- export function step(emoji: string, text: string, detail?: string) {
18
- const msg = chalk.hex('#A78BFA')(emoji + ' ' + chalk.white(text));
19
- if (detail) {
20
- console.log(msg + ' ' + chalk.hex('#6B7280')(detail));
21
- } else {
22
- console.log(msg);
23
- }
24
- }
25
-
26
- export function success(text: string) {
27
- console.log(chalk.hex('#10B981')(' ✓ ') + chalk.white(text));
28
- }
29
-
30
- export function warn(text: string) {
31
- console.log(chalk.hex('#F59E0B')(' ⚠ ') + chalk.white(text));
32
- }
33
-
34
- export function error(text: string) {
35
- console.log(chalk.hex('#EF4444')(' ✗ ') + chalk.white(text));
36
- }