gazill 1.0.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/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +74 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logout.d.ts +2 -0
- package/dist/commands/logout.d.ts.map +1 -0
- package/dist/commands/logout.js +13 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/logs.d.ts +2 -0
- package/dist/commands/logs.d.ts.map +1 -0
- package/dist/commands/logs.js +74 -0
- package/dist/commands/logs.js.map +1 -0
- package/dist/commands/new.d.ts +3 -0
- package/dist/commands/new.d.ts.map +1 -0
- package/dist/commands/new.js +126 -0
- package/dist/commands/new.js.map +1 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +51 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/api.d.ts +13 -0
- package/dist/lib/api.d.ts.map +1 -0
- package/dist/lib/api.js +75 -0
- package/dist/lib/api.js.map +1 -0
- package/dist/lib/config.d.ts +9 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +70 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/watcher.d.ts +11 -0
- package/dist/lib/watcher.d.ts.map +1 -0
- package/dist/lib/watcher.js +154 -0
- package/dist/lib/watcher.js.map +1 -0
- package/package.json +35 -0
- package/src/commands/login.ts +90 -0
- package/src/commands/logout.ts +15 -0
- package/src/commands/logs.ts +90 -0
- package/src/commands/new.ts +162 -0
- package/src/commands/status.ts +70 -0
- package/src/index.ts +48 -0
- package/src/lib/api.ts +104 -0
- package/src/lib/config.ts +75 -0
- package/src/lib/watcher.ts +181 -0
- package/tsconfig.json +8 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { BASE_DOMAIN } from '@gazill/shared';
|
|
4
|
+
import { listProjects } from '../lib/api.js';
|
|
5
|
+
import { isAuthenticated } from '../lib/config.js';
|
|
6
|
+
|
|
7
|
+
const STATUS_COLORS: Record<string, (text: string) => string> = {
|
|
8
|
+
pending: chalk.yellow,
|
|
9
|
+
building: chalk.blue,
|
|
10
|
+
running: chalk.green,
|
|
11
|
+
stopped: chalk.gray,
|
|
12
|
+
error: chalk.red,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export async function statusCommand(): Promise<void> {
|
|
16
|
+
// Check authentication
|
|
17
|
+
if (!(await isAuthenticated())) {
|
|
18
|
+
console.log(chalk.red('Please log in first.'));
|
|
19
|
+
console.log(chalk.gray('Run "gazill login" to authenticate.'));
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const spinner = ora('Fetching projects...').start();
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const { projects } = await listProjects();
|
|
27
|
+
|
|
28
|
+
if (projects.length === 0) {
|
|
29
|
+
spinner.info('No projects found.');
|
|
30
|
+
console.log(chalk.gray('\nRun "gazill new <name>" to create a project.'));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
spinner.stop();
|
|
35
|
+
|
|
36
|
+
console.log(chalk.bold('\nYour Projects:\n'));
|
|
37
|
+
|
|
38
|
+
// Calculate column widths
|
|
39
|
+
const nameWidth = Math.max(...projects.map((p) => p.name.length), 4);
|
|
40
|
+
const statusWidth = 8;
|
|
41
|
+
const urlWidth = Math.max(
|
|
42
|
+
...projects.map((p) => `${p.subdomain}.${BASE_DOMAIN}`.length),
|
|
43
|
+
3
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
// Header
|
|
47
|
+
console.log(
|
|
48
|
+
chalk.gray(
|
|
49
|
+
`${'NAME'.padEnd(nameWidth)} ${'STATUS'.padEnd(statusWidth)} ${'URL'.padEnd(urlWidth)}`
|
|
50
|
+
)
|
|
51
|
+
);
|
|
52
|
+
console.log(chalk.gray('-'.repeat(nameWidth + statusWidth + urlWidth + 4)));
|
|
53
|
+
|
|
54
|
+
// Projects
|
|
55
|
+
for (const project of projects) {
|
|
56
|
+
const colorFn = STATUS_COLORS[project.status] || chalk.white;
|
|
57
|
+
const url = `${project.subdomain}.${BASE_DOMAIN}`;
|
|
58
|
+
|
|
59
|
+
console.log(
|
|
60
|
+
`${project.name.padEnd(nameWidth)} ${colorFn(project.status.padEnd(statusWidth))} ${chalk.underline(url)}`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log('');
|
|
65
|
+
} catch (error) {
|
|
66
|
+
const message = error instanceof Error ? error.message : 'Failed to fetch projects';
|
|
67
|
+
spinner.fail(chalk.red(message));
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import { loginCommand } from './commands/login.js';
|
|
5
|
+
import { logoutCommand } from './commands/logout.js';
|
|
6
|
+
import { newCommand, watchCommand } from './commands/new.js';
|
|
7
|
+
import { statusCommand } from './commands/status.js';
|
|
8
|
+
import { logsCommand } from './commands/logs.js';
|
|
9
|
+
|
|
10
|
+
const program = new Command();
|
|
11
|
+
|
|
12
|
+
program
|
|
13
|
+
.name('gazill')
|
|
14
|
+
.description('Auto-deploy platform for developers')
|
|
15
|
+
.version('1.0.0');
|
|
16
|
+
|
|
17
|
+
program
|
|
18
|
+
.command('login')
|
|
19
|
+
.description('Authenticate with Gazill')
|
|
20
|
+
.action(loginCommand);
|
|
21
|
+
|
|
22
|
+
program
|
|
23
|
+
.command('logout')
|
|
24
|
+
.description('Log out from Gazill')
|
|
25
|
+
.action(logoutCommand);
|
|
26
|
+
|
|
27
|
+
program
|
|
28
|
+
.command('new <name>')
|
|
29
|
+
.description('Create a new project and start watching for changes')
|
|
30
|
+
.action(newCommand);
|
|
31
|
+
|
|
32
|
+
program
|
|
33
|
+
.command('watch')
|
|
34
|
+
.description('Watch the current project for changes')
|
|
35
|
+
.action(watchCommand);
|
|
36
|
+
|
|
37
|
+
program
|
|
38
|
+
.command('status')
|
|
39
|
+
.alias('ls')
|
|
40
|
+
.description('List all your projects')
|
|
41
|
+
.action(statusCommand);
|
|
42
|
+
|
|
43
|
+
program
|
|
44
|
+
.command('logs [name]')
|
|
45
|
+
.description('View logs for a project')
|
|
46
|
+
.action(logsCommand);
|
|
47
|
+
|
|
48
|
+
program.parse();
|
package/src/lib/api.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AuthResponse,
|
|
3
|
+
CreateProjectResponse,
|
|
4
|
+
ProjectListResponse,
|
|
5
|
+
DeployResponse,
|
|
6
|
+
LogsResponse,
|
|
7
|
+
ApiError,
|
|
8
|
+
DeployFile,
|
|
9
|
+
} from '@gazill/shared';
|
|
10
|
+
import { getApiUrl, getAuth } from './config.js';
|
|
11
|
+
|
|
12
|
+
interface RequestOptions {
|
|
13
|
+
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
14
|
+
body?: unknown;
|
|
15
|
+
auth?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function request<T>(endpoint: string, options: RequestOptions): Promise<T> {
|
|
19
|
+
const apiUrl = await getApiUrl();
|
|
20
|
+
const url = `${apiUrl}${endpoint}`;
|
|
21
|
+
|
|
22
|
+
const headers: Record<string, string> = {
|
|
23
|
+
'Content-Type': 'application/json',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
if (options.auth !== false) {
|
|
27
|
+
const auth = await getAuth();
|
|
28
|
+
if (auth?.token) {
|
|
29
|
+
headers['Authorization'] = `Bearer ${auth.token}`;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const response = await fetch(url, {
|
|
34
|
+
method: options.method,
|
|
35
|
+
headers,
|
|
36
|
+
body: options.body ? JSON.stringify(options.body) : undefined,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const data = await response.json();
|
|
40
|
+
|
|
41
|
+
if (!response.ok) {
|
|
42
|
+
const error = data as ApiError;
|
|
43
|
+
throw new Error(error.message || 'Request failed');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return data as T;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Auth API
|
|
50
|
+
export async function signup(email: string, password: string): Promise<AuthResponse> {
|
|
51
|
+
return request<AuthResponse>('/api/auth/signup', {
|
|
52
|
+
method: 'POST',
|
|
53
|
+
body: { email, password },
|
|
54
|
+
auth: false,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function signin(email: string, password: string): Promise<AuthResponse> {
|
|
59
|
+
return request<AuthResponse>('/api/auth/signin', {
|
|
60
|
+
method: 'POST',
|
|
61
|
+
body: { email, password },
|
|
62
|
+
auth: false,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Projects API
|
|
67
|
+
export async function createProject(name: string): Promise<CreateProjectResponse> {
|
|
68
|
+
return request<CreateProjectResponse>('/api/projects', {
|
|
69
|
+
method: 'POST',
|
|
70
|
+
body: { name },
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function listProjects(): Promise<ProjectListResponse> {
|
|
75
|
+
return request<ProjectListResponse>('/api/projects', {
|
|
76
|
+
method: 'GET',
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function getProject(id: string): Promise<{ project: CreateProjectResponse['project']; url: string }> {
|
|
81
|
+
return request('/api/projects/' + id, {
|
|
82
|
+
method: 'GET',
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function deleteProject(id: string): Promise<void> {
|
|
87
|
+
await request('/api/projects/' + id, {
|
|
88
|
+
method: 'DELETE',
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Deployments API
|
|
93
|
+
export async function deploy(projectId: string, files: DeployFile[]): Promise<DeployResponse> {
|
|
94
|
+
return request<DeployResponse>(`/api/projects/${projectId}/deploy`, {
|
|
95
|
+
method: 'POST',
|
|
96
|
+
body: { files },
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function getLogs(projectId: string, lines: number = 100): Promise<LogsResponse> {
|
|
101
|
+
return request<LogsResponse>(`/api/projects/${projectId}/logs?lines=${lines}`, {
|
|
102
|
+
method: 'GET',
|
|
103
|
+
});
|
|
104
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import type { CliConfig, CliAuth } from '@gazill/shared';
|
|
5
|
+
import { API_BASE_URL } from '@gazill/shared';
|
|
6
|
+
|
|
7
|
+
const CONFIG_DIR = path.join(os.homedir(), '.gazill');
|
|
8
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
9
|
+
const AUTH_FILE = path.join(CONFIG_DIR, 'auth.json');
|
|
10
|
+
|
|
11
|
+
// Ensure config directory exists
|
|
12
|
+
async function ensureConfigDir(): Promise<void> {
|
|
13
|
+
try {
|
|
14
|
+
await fs.mkdir(CONFIG_DIR, { recursive: true });
|
|
15
|
+
} catch {
|
|
16
|
+
// Directory might already exist
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Get CLI configuration
|
|
21
|
+
export async function getConfig(): Promise<CliConfig> {
|
|
22
|
+
try {
|
|
23
|
+
const content = await fs.readFile(CONFIG_FILE, 'utf-8');
|
|
24
|
+
return JSON.parse(content) as CliConfig;
|
|
25
|
+
} catch {
|
|
26
|
+
return {
|
|
27
|
+
apiUrl: API_BASE_URL,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Save CLI configuration
|
|
33
|
+
export async function saveConfig(config: CliConfig): Promise<void> {
|
|
34
|
+
await ensureConfigDir();
|
|
35
|
+
await fs.writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Get authentication info
|
|
39
|
+
export async function getAuth(): Promise<CliAuth | null> {
|
|
40
|
+
try {
|
|
41
|
+
const content = await fs.readFile(AUTH_FILE, 'utf-8');
|
|
42
|
+
return JSON.parse(content) as CliAuth;
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Save authentication info
|
|
49
|
+
export async function saveAuth(auth: CliAuth): Promise<void> {
|
|
50
|
+
await ensureConfigDir();
|
|
51
|
+
await fs.writeFile(AUTH_FILE, JSON.stringify(auth, null, 2), {
|
|
52
|
+
mode: 0o600, // Read/write for owner only
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Clear authentication info
|
|
57
|
+
export async function clearAuth(): Promise<void> {
|
|
58
|
+
try {
|
|
59
|
+
await fs.unlink(AUTH_FILE);
|
|
60
|
+
} catch {
|
|
61
|
+
// File might not exist
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Check if user is authenticated
|
|
66
|
+
export async function isAuthenticated(): Promise<boolean> {
|
|
67
|
+
const auth = await getAuth();
|
|
68
|
+
return auth !== null && !!auth.token;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Get API URL
|
|
72
|
+
export async function getApiUrl(): Promise<string> {
|
|
73
|
+
const config = await getConfig();
|
|
74
|
+
return config.apiUrl;
|
|
75
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import chokidar from 'chokidar';
|
|
2
|
+
import { promises as fs } from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import debounce from 'lodash/debounce.js';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import ora from 'ora';
|
|
7
|
+
import type { DeployFile } from '@gazill/shared';
|
|
8
|
+
import { DEPLOY_DEBOUNCE_MS, MAX_FILE_SIZE_BYTES } from '@gazill/shared';
|
|
9
|
+
import { deploy } from './api.js';
|
|
10
|
+
|
|
11
|
+
const IGNORED_PATTERNS = [
|
|
12
|
+
/(^|[/\\])\../, // dotfiles
|
|
13
|
+
'node_modules/**',
|
|
14
|
+
'.git/**',
|
|
15
|
+
'*.log',
|
|
16
|
+
'.gazill/**',
|
|
17
|
+
'dist/**',
|
|
18
|
+
'build/**',
|
|
19
|
+
'__pycache__/**',
|
|
20
|
+
'*.pyc',
|
|
21
|
+
'.env',
|
|
22
|
+
'.env.*',
|
|
23
|
+
'Dockerfile',
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
interface WatcherOptions {
|
|
27
|
+
projectId: string;
|
|
28
|
+
projectDir: string;
|
|
29
|
+
onDeploy?: (files: DeployFile[]) => void;
|
|
30
|
+
onError?: (error: Error) => void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function startWatcher(options: WatcherOptions): Promise<chokidar.FSWatcher> {
|
|
34
|
+
const { projectId, projectDir, onDeploy, onError } = options;
|
|
35
|
+
|
|
36
|
+
const changedFiles = new Set<string>();
|
|
37
|
+
let isDeploying = false;
|
|
38
|
+
|
|
39
|
+
// Collect all files for initial deploy
|
|
40
|
+
async function collectAllFiles(): Promise<DeployFile[]> {
|
|
41
|
+
const files: DeployFile[] = [];
|
|
42
|
+
|
|
43
|
+
async function walkDir(dir: string): Promise<void> {
|
|
44
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
45
|
+
|
|
46
|
+
for (const entry of entries) {
|
|
47
|
+
const fullPath = path.join(dir, entry.name);
|
|
48
|
+
const relativePath = path.relative(projectDir, fullPath);
|
|
49
|
+
|
|
50
|
+
// Skip ignored patterns
|
|
51
|
+
if (IGNORED_PATTERNS.some((pattern) => {
|
|
52
|
+
if (typeof pattern === 'string') {
|
|
53
|
+
return relativePath.includes(pattern.replace('/**', ''));
|
|
54
|
+
}
|
|
55
|
+
return pattern.test(relativePath);
|
|
56
|
+
})) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (entry.isDirectory()) {
|
|
61
|
+
await walkDir(fullPath);
|
|
62
|
+
} else if (entry.isFile()) {
|
|
63
|
+
try {
|
|
64
|
+
const stats = await fs.stat(fullPath);
|
|
65
|
+
if (stats.size <= MAX_FILE_SIZE_BYTES) {
|
|
66
|
+
const content = await fs.readFile(fullPath, 'utf-8');
|
|
67
|
+
files.push({ path: relativePath, content });
|
|
68
|
+
}
|
|
69
|
+
} catch {
|
|
70
|
+
// Skip files that can't be read
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
await walkDir(projectDir);
|
|
77
|
+
return files;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Deploy changed files
|
|
81
|
+
const doDeploy = debounce(async () => {
|
|
82
|
+
if (isDeploying || changedFiles.size === 0) return;
|
|
83
|
+
|
|
84
|
+
isDeploying = true;
|
|
85
|
+
const filesToDeploy = Array.from(changedFiles);
|
|
86
|
+
changedFiles.clear();
|
|
87
|
+
|
|
88
|
+
const spinner = ora('Deploying changes...').start();
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
const files: DeployFile[] = [];
|
|
92
|
+
|
|
93
|
+
for (const filePath of filesToDeploy) {
|
|
94
|
+
const fullPath = path.join(projectDir, filePath);
|
|
95
|
+
try {
|
|
96
|
+
const stats = await fs.stat(fullPath);
|
|
97
|
+
if (stats.size <= MAX_FILE_SIZE_BYTES) {
|
|
98
|
+
const content = await fs.readFile(fullPath, 'utf-8');
|
|
99
|
+
files.push({ path: filePath, content });
|
|
100
|
+
}
|
|
101
|
+
} catch {
|
|
102
|
+
// File might have been deleted
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (files.length > 0) {
|
|
107
|
+
const result = await deploy(projectId, files);
|
|
108
|
+
spinner.succeed(
|
|
109
|
+
chalk.green(`Deployed ${files.length} file(s) - ${result.url}`)
|
|
110
|
+
);
|
|
111
|
+
onDeploy?.(files);
|
|
112
|
+
} else {
|
|
113
|
+
spinner.info('No files to deploy');
|
|
114
|
+
}
|
|
115
|
+
} catch (error) {
|
|
116
|
+
const message = error instanceof Error ? error.message : 'Deploy failed';
|
|
117
|
+
spinner.fail(chalk.red(message));
|
|
118
|
+
onError?.(error instanceof Error ? error : new Error(message));
|
|
119
|
+
} finally {
|
|
120
|
+
isDeploying = false;
|
|
121
|
+
}
|
|
122
|
+
}, DEPLOY_DEBOUNCE_MS);
|
|
123
|
+
|
|
124
|
+
// Create watcher
|
|
125
|
+
const watcher = chokidar.watch(projectDir, {
|
|
126
|
+
ignored: IGNORED_PATTERNS,
|
|
127
|
+
persistent: true,
|
|
128
|
+
ignoreInitial: true,
|
|
129
|
+
awaitWriteFinish: {
|
|
130
|
+
stabilityThreshold: 100,
|
|
131
|
+
pollInterval: 50,
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Handle file changes
|
|
136
|
+
watcher.on('add', (filePath) => {
|
|
137
|
+
const relativePath = path.relative(projectDir, filePath);
|
|
138
|
+
console.log(chalk.gray(`+ ${relativePath}`));
|
|
139
|
+
changedFiles.add(relativePath);
|
|
140
|
+
doDeploy();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
watcher.on('change', (filePath) => {
|
|
144
|
+
const relativePath = path.relative(projectDir, filePath);
|
|
145
|
+
console.log(chalk.gray(`~ ${relativePath}`));
|
|
146
|
+
changedFiles.add(relativePath);
|
|
147
|
+
doDeploy();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
watcher.on('unlink', (filePath) => {
|
|
151
|
+
const relativePath = path.relative(projectDir, filePath);
|
|
152
|
+
console.log(chalk.gray(`- ${relativePath}`));
|
|
153
|
+
// Note: File deletion isn't fully handled - would need separate API endpoint
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
watcher.on('error', (error) => {
|
|
157
|
+
console.error(chalk.red('Watcher error:'), error);
|
|
158
|
+
onError?.(error);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Initial deploy of all files
|
|
162
|
+
console.log(chalk.blue('Scanning files...'));
|
|
163
|
+
const allFiles = await collectAllFiles();
|
|
164
|
+
|
|
165
|
+
if (allFiles.length > 0) {
|
|
166
|
+
const spinner = ora(`Deploying ${allFiles.length} files...`).start();
|
|
167
|
+
try {
|
|
168
|
+
const result = await deploy(projectId, allFiles);
|
|
169
|
+
spinner.succeed(
|
|
170
|
+
chalk.green(`Initial deploy complete - ${result.url}`)
|
|
171
|
+
);
|
|
172
|
+
} catch (error) {
|
|
173
|
+
const message = error instanceof Error ? error.message : 'Deploy failed';
|
|
174
|
+
spinner.fail(chalk.red(message));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
console.log(chalk.blue('\nWatching for changes... (Ctrl+C to stop)\n'));
|
|
179
|
+
|
|
180
|
+
return watcher;
|
|
181
|
+
}
|