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.
Files changed (47) hide show
  1. package/dist/commands/login.d.ts +2 -0
  2. package/dist/commands/login.d.ts.map +1 -0
  3. package/dist/commands/login.js +74 -0
  4. package/dist/commands/login.js.map +1 -0
  5. package/dist/commands/logout.d.ts +2 -0
  6. package/dist/commands/logout.d.ts.map +1 -0
  7. package/dist/commands/logout.js +13 -0
  8. package/dist/commands/logout.js.map +1 -0
  9. package/dist/commands/logs.d.ts +2 -0
  10. package/dist/commands/logs.d.ts.map +1 -0
  11. package/dist/commands/logs.js +74 -0
  12. package/dist/commands/logs.js.map +1 -0
  13. package/dist/commands/new.d.ts +3 -0
  14. package/dist/commands/new.d.ts.map +1 -0
  15. package/dist/commands/new.js +126 -0
  16. package/dist/commands/new.js.map +1 -0
  17. package/dist/commands/status.d.ts +2 -0
  18. package/dist/commands/status.d.ts.map +1 -0
  19. package/dist/commands/status.js +51 -0
  20. package/dist/commands/status.js.map +1 -0
  21. package/dist/index.d.ts +3 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +39 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/lib/api.d.ts +13 -0
  26. package/dist/lib/api.d.ts.map +1 -0
  27. package/dist/lib/api.js +75 -0
  28. package/dist/lib/api.js.map +1 -0
  29. package/dist/lib/config.d.ts +9 -0
  30. package/dist/lib/config.d.ts.map +1 -0
  31. package/dist/lib/config.js +70 -0
  32. package/dist/lib/config.js.map +1 -0
  33. package/dist/lib/watcher.d.ts +11 -0
  34. package/dist/lib/watcher.d.ts.map +1 -0
  35. package/dist/lib/watcher.js +154 -0
  36. package/dist/lib/watcher.js.map +1 -0
  37. package/package.json +35 -0
  38. package/src/commands/login.ts +90 -0
  39. package/src/commands/logout.ts +15 -0
  40. package/src/commands/logs.ts +90 -0
  41. package/src/commands/new.ts +162 -0
  42. package/src/commands/status.ts +70 -0
  43. package/src/index.ts +48 -0
  44. package/src/lib/api.ts +104 -0
  45. package/src/lib/config.ts +75 -0
  46. package/src/lib/watcher.ts +181 -0
  47. 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
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src"
6
+ },
7
+ "include": ["src/**/*"]
8
+ }