quicklify 0.1.4 → 0.1.6
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/bin/quicklify +2 -0
- package/package.json +9 -2
- package/.claude/settings.local.json +0 -26
- package/.github/workflows/ci.yml +0 -42
- package/.github/workflows/publish.yml +0 -25
- package/jest.config.cjs +0 -30
- package/src/commands/init.ts +0 -101
- package/src/index.ts +0 -18
- package/src/providers/base.ts +0 -11
- package/src/providers/digitalocean.ts +0 -37
- package/src/providers/hetzner.ts +0 -85
- package/src/types/index.ts +0 -36
- package/src/utils/cloudInit.ts +0 -29
- package/src/utils/logger.ts +0 -37
- package/src/utils/prompts.ts +0 -84
- package/tests/__mocks__/axios.ts +0 -14
- package/tests/__mocks__/chalk.ts +0 -15
- package/tests/__mocks__/inquirer.ts +0 -5
- package/tests/__mocks__/ora.ts +0 -16
- package/tests/e2e/init.test.ts +0 -190
- package/tests/integration/hetzner.test.ts +0 -274
- package/tests/unit/cloudInit.test.ts +0 -53
- package/tests/unit/logger.test.ts +0 -76
- package/tests/unit/prompts.test.ts +0 -220
- package/tests/unit/validators.test.ts +0 -63
- package/tsconfig.json +0 -19
- package/tsconfig.test.json +0 -10
package/bin/quicklify
ADDED
package/package.json
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "quicklify",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Automate Coolify deployment on cloud providers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
|
8
|
-
"quicklify": "
|
|
8
|
+
"quicklify": "bin/quicklify"
|
|
9
9
|
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin/",
|
|
12
|
+
"dist/",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE",
|
|
15
|
+
"SECURITY.md"
|
|
16
|
+
],
|
|
10
17
|
"scripts": {
|
|
11
18
|
"dev": "tsx src/index.ts",
|
|
12
19
|
"build": "tsc && chmod +x dist/index.js",
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"permissions": {
|
|
3
|
-
"allow": [
|
|
4
|
-
"Bash(npm install:*)",
|
|
5
|
-
"Bash(npx tsc:*)",
|
|
6
|
-
"Bash(git init:*)",
|
|
7
|
-
"Bash(git add:*)",
|
|
8
|
-
"Bash(npm run build:*)",
|
|
9
|
-
"Bash(npm run dev:*)",
|
|
10
|
-
"Bash(npx jest:*)",
|
|
11
|
-
"Bash(gh repo view:*)",
|
|
12
|
-
"Bash(git branch:*)",
|
|
13
|
-
"Bash(git remote add:*)",
|
|
14
|
-
"Bash(git fetch:*)",
|
|
15
|
-
"Bash(git pull:*)",
|
|
16
|
-
"Bash(git stash:*)",
|
|
17
|
-
"Bash(git reset:*)",
|
|
18
|
-
"Bash(git checkout:*)",
|
|
19
|
-
"Bash(git rm:*)",
|
|
20
|
-
"Bash(git commit -m \"$\\(cat <<''EOF''\nInitial commit: Quicklify v0.1.0\n\n- CLI tool for automated Coolify deployment\n- Hetzner Cloud provider support\n- 79 tests with 100% coverage\n- Multi-platform CI/CD \\(Ubuntu, macOS, Windows\\)\n- TypeScript + Commander.js + Inquirer.js\n\nCo-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>\nEOF\n\\)\")",
|
|
21
|
-
"Bash(git push:*)",
|
|
22
|
-
"Bash(git remote set-url:*)",
|
|
23
|
-
"Bash(git commit:*)"
|
|
24
|
-
]
|
|
25
|
-
}
|
|
26
|
-
}
|
package/.github/workflows/ci.yml
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
name: CI
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [main]
|
|
6
|
-
pull_request:
|
|
7
|
-
branches: [main]
|
|
8
|
-
|
|
9
|
-
jobs:
|
|
10
|
-
test:
|
|
11
|
-
name: Test (${{ matrix.os }}, Node ${{ matrix.node-version }})
|
|
12
|
-
runs-on: ${{ matrix.os }}
|
|
13
|
-
strategy:
|
|
14
|
-
fail-fast: false
|
|
15
|
-
matrix:
|
|
16
|
-
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
17
|
-
node-version: [18, 20, 22]
|
|
18
|
-
|
|
19
|
-
steps:
|
|
20
|
-
- uses: actions/checkout@v4
|
|
21
|
-
|
|
22
|
-
- name: Setup Node.js ${{ matrix.node-version }}
|
|
23
|
-
uses: actions/setup-node@v4
|
|
24
|
-
with:
|
|
25
|
-
node-version: ${{ matrix.node-version }}
|
|
26
|
-
cache: npm
|
|
27
|
-
|
|
28
|
-
- name: Install dependencies
|
|
29
|
-
run: npm ci
|
|
30
|
-
|
|
31
|
-
- name: TypeScript type check
|
|
32
|
-
run: npx tsc --noEmit
|
|
33
|
-
|
|
34
|
-
- name: Run tests
|
|
35
|
-
run: npm test
|
|
36
|
-
|
|
37
|
-
- name: Run tests with coverage
|
|
38
|
-
if: matrix.os == 'ubuntu-latest' && matrix.node-version == 22
|
|
39
|
-
run: npm run test:coverage
|
|
40
|
-
|
|
41
|
-
- name: Build
|
|
42
|
-
run: npx tsc
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
name: Publish to npm
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
tags:
|
|
6
|
-
- 'v*'
|
|
7
|
-
|
|
8
|
-
jobs:
|
|
9
|
-
publish:
|
|
10
|
-
runs-on: ubuntu-latest
|
|
11
|
-
steps:
|
|
12
|
-
- uses: actions/checkout@v4
|
|
13
|
-
|
|
14
|
-
- uses: actions/setup-node@v4
|
|
15
|
-
with:
|
|
16
|
-
node-version: '20'
|
|
17
|
-
registry-url: 'https://registry.npmjs.org'
|
|
18
|
-
|
|
19
|
-
- run: npm ci
|
|
20
|
-
|
|
21
|
-
- run: npm test
|
|
22
|
-
|
|
23
|
-
- run: npm publish
|
|
24
|
-
env:
|
|
25
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/jest.config.cjs
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
|
2
|
-
module.exports = {
|
|
3
|
-
testEnvironment: 'node',
|
|
4
|
-
roots: ['<rootDir>/tests'],
|
|
5
|
-
moduleNameMapper: {
|
|
6
|
-
'^(\\.{1,2}/.*)\\.js$': '$1',
|
|
7
|
-
'^chalk$': '<rootDir>/tests/__mocks__/chalk.ts',
|
|
8
|
-
'^ora$': '<rootDir>/tests/__mocks__/ora.ts',
|
|
9
|
-
'^inquirer$': '<rootDir>/tests/__mocks__/inquirer.ts',
|
|
10
|
-
'^axios$': '<rootDir>/tests/__mocks__/axios.ts',
|
|
11
|
-
},
|
|
12
|
-
transform: {
|
|
13
|
-
'^.+\\.tsx?$': ['ts-jest', {
|
|
14
|
-
tsconfig: 'tsconfig.test.json',
|
|
15
|
-
}],
|
|
16
|
-
},
|
|
17
|
-
collectCoverageFrom: [
|
|
18
|
-
'src/**/*.ts',
|
|
19
|
-
'!src/index.ts',
|
|
20
|
-
],
|
|
21
|
-
coverageDirectory: 'coverage',
|
|
22
|
-
coverageThreshold: {
|
|
23
|
-
global: {
|
|
24
|
-
branches: 80,
|
|
25
|
-
functions: 80,
|
|
26
|
-
lines: 80,
|
|
27
|
-
statements: 80,
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
};
|
package/src/commands/init.ts
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import { HetznerProvider } from "../providers/hetzner.js";
|
|
2
|
-
import { getDeploymentConfig, confirmDeployment } from "../utils/prompts.js";
|
|
3
|
-
import { getCoolifyCloudInit } from "../utils/cloudInit.js";
|
|
4
|
-
import { logger, createSpinner } from "../utils/logger.js";
|
|
5
|
-
|
|
6
|
-
export async function initCommand() {
|
|
7
|
-
logger.title("Quicklify - Deploy Coolify in 60 seconds");
|
|
8
|
-
|
|
9
|
-
// For MVP, we only support Hetzner
|
|
10
|
-
// Later: Add provider selection prompt
|
|
11
|
-
logger.info("Using Hetzner Cloud (more providers coming soon!)");
|
|
12
|
-
|
|
13
|
-
const provider = new HetznerProvider(""); // Token will be set from config
|
|
14
|
-
|
|
15
|
-
// Get deployment configuration
|
|
16
|
-
const config = await getDeploymentConfig(provider);
|
|
17
|
-
|
|
18
|
-
// Update provider with actual token
|
|
19
|
-
const providerWithToken = new HetznerProvider(config.apiToken);
|
|
20
|
-
|
|
21
|
-
// Confirm deployment
|
|
22
|
-
const confirmed = await confirmDeployment(config, providerWithToken);
|
|
23
|
-
if (!confirmed) {
|
|
24
|
-
logger.warning("Deployment cancelled");
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
try {
|
|
29
|
-
// Validate API token
|
|
30
|
-
const tokenSpinner = createSpinner("Validating API token...");
|
|
31
|
-
tokenSpinner.start();
|
|
32
|
-
|
|
33
|
-
const isValid = await providerWithToken.validateToken(config.apiToken);
|
|
34
|
-
if (!isValid) {
|
|
35
|
-
tokenSpinner.fail("Invalid API token");
|
|
36
|
-
logger.error("Please check your API token and try again");
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
tokenSpinner.succeed("API token validated");
|
|
40
|
-
|
|
41
|
-
// Generate cloud-init script
|
|
42
|
-
const cloudInit = getCoolifyCloudInit(config.serverName);
|
|
43
|
-
|
|
44
|
-
// Create server
|
|
45
|
-
const serverSpinner = createSpinner("Creating VPS server...");
|
|
46
|
-
serverSpinner.start();
|
|
47
|
-
|
|
48
|
-
const server = await providerWithToken.createServer({
|
|
49
|
-
name: config.serverName,
|
|
50
|
-
region: config.region,
|
|
51
|
-
size: config.serverSize,
|
|
52
|
-
cloudInit,
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
serverSpinner.succeed(`Server created (ID: ${server.id})`);
|
|
56
|
-
|
|
57
|
-
// Wait for server to be running
|
|
58
|
-
const statusSpinner = createSpinner("Waiting for server to boot...");
|
|
59
|
-
statusSpinner.start();
|
|
60
|
-
|
|
61
|
-
let status = await providerWithToken.getServerStatus(server.id);
|
|
62
|
-
let attempts = 0;
|
|
63
|
-
const maxAttempts = 30;
|
|
64
|
-
|
|
65
|
-
while (status !== "running" && attempts < maxAttempts) {
|
|
66
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
67
|
-
status = await providerWithToken.getServerStatus(server.id);
|
|
68
|
-
attempts++;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (status !== "running") {
|
|
72
|
-
statusSpinner.fail("Server failed to start");
|
|
73
|
-
logger.error("Please check your cloud provider dashboard");
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
statusSpinner.succeed("Server is running");
|
|
78
|
-
|
|
79
|
-
// Installing Coolify
|
|
80
|
-
const installSpinner = createSpinner("Installing Coolify (this takes 3-5 minutes)...");
|
|
81
|
-
installSpinner.start();
|
|
82
|
-
|
|
83
|
-
// Wait for Coolify installation (cloud-init runs in background)
|
|
84
|
-
await new Promise((resolve) => setTimeout(resolve, 180000));
|
|
85
|
-
|
|
86
|
-
installSpinner.succeed("Coolify installation completed");
|
|
87
|
-
|
|
88
|
-
// Success message
|
|
89
|
-
logger.title("Deployment Successful!");
|
|
90
|
-
console.log();
|
|
91
|
-
logger.success(`Server IP: ${server.ip}`);
|
|
92
|
-
logger.success(`Access Coolify: https://${server.ip}:8000`);
|
|
93
|
-
console.log();
|
|
94
|
-
logger.info("Default credentials will be shown on first login");
|
|
95
|
-
logger.info("Please wait 1-2 more minutes for Coolify to fully initialize");
|
|
96
|
-
console.log();
|
|
97
|
-
} catch (error: any) {
|
|
98
|
-
logger.error(`Deployment failed: ${error.message}`);
|
|
99
|
-
process.exit(1);
|
|
100
|
-
}
|
|
101
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { Command } from "commander";
|
|
4
|
-
import { initCommand } from "./commands/init.js";
|
|
5
|
-
|
|
6
|
-
const program = new Command();
|
|
7
|
-
|
|
8
|
-
program
|
|
9
|
-
.name("quicklify")
|
|
10
|
-
.description("Automate Coolify deployment on cloud providers")
|
|
11
|
-
.version("0.1.0");
|
|
12
|
-
|
|
13
|
-
program
|
|
14
|
-
.command("init")
|
|
15
|
-
.description("Deploy a new Coolify instance on a cloud provider")
|
|
16
|
-
.action(initCommand);
|
|
17
|
-
|
|
18
|
-
program.parse();
|
package/src/providers/base.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { Region, ServerSize, ServerConfig, ServerResult } from "../types/index.js";
|
|
2
|
-
|
|
3
|
-
export interface CloudProvider {
|
|
4
|
-
name: string;
|
|
5
|
-
displayName: string;
|
|
6
|
-
validateToken(token: string): Promise<boolean>;
|
|
7
|
-
getRegions(): Region[];
|
|
8
|
-
getServerSizes(): ServerSize[];
|
|
9
|
-
createServer(config: ServerConfig): Promise<ServerResult>;
|
|
10
|
-
getServerStatus(serverId: string): Promise<string>;
|
|
11
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import type { CloudProvider } from "./base.js";
|
|
2
|
-
import type { Region, ServerSize, ServerConfig, ServerResult } from "../types/index.js";
|
|
3
|
-
|
|
4
|
-
export class DigitalOceanProvider implements CloudProvider {
|
|
5
|
-
name = "digitalocean";
|
|
6
|
-
displayName = "DigitalOcean";
|
|
7
|
-
private apiToken: string;
|
|
8
|
-
|
|
9
|
-
constructor(apiToken: string) {
|
|
10
|
-
this.apiToken = apiToken;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
async validateToken(_token: string): Promise<boolean> {
|
|
14
|
-
// TODO: Implement DigitalOcean API call
|
|
15
|
-
return false;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
getRegions(): Region[] {
|
|
19
|
-
// TODO: Implement DigitalOcean regions
|
|
20
|
-
return [];
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
getServerSizes(): ServerSize[] {
|
|
24
|
-
// TODO: Implement DigitalOcean server sizes
|
|
25
|
-
return [];
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
async createServer(_config: ServerConfig): Promise<ServerResult> {
|
|
29
|
-
// TODO: Implement DigitalOcean API call
|
|
30
|
-
throw new Error("Not implemented");
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async getServerStatus(_serverId: string): Promise<string> {
|
|
34
|
-
// TODO: Implement DigitalOcean API call
|
|
35
|
-
return "unknown";
|
|
36
|
-
}
|
|
37
|
-
}
|
package/src/providers/hetzner.ts
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import axios from "axios";
|
|
2
|
-
import type { CloudProvider } from "./base.js";
|
|
3
|
-
import type { Region, ServerSize, ServerConfig, ServerResult } from "../types/index.js";
|
|
4
|
-
|
|
5
|
-
export class HetznerProvider implements CloudProvider {
|
|
6
|
-
name = "hetzner";
|
|
7
|
-
displayName = "Hetzner Cloud";
|
|
8
|
-
private apiToken: string;
|
|
9
|
-
private baseUrl = "https://api.hetzner.cloud/v1";
|
|
10
|
-
|
|
11
|
-
constructor(apiToken: string) {
|
|
12
|
-
this.apiToken = apiToken;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
async validateToken(token: string): Promise<boolean> {
|
|
16
|
-
try {
|
|
17
|
-
await axios.get(`${this.baseUrl}/servers`, {
|
|
18
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
19
|
-
});
|
|
20
|
-
return true;
|
|
21
|
-
} catch {
|
|
22
|
-
return false;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
async createServer(config: ServerConfig): Promise<ServerResult> {
|
|
27
|
-
try {
|
|
28
|
-
const response = await axios.post(
|
|
29
|
-
`${this.baseUrl}/servers`,
|
|
30
|
-
{
|
|
31
|
-
name: config.name,
|
|
32
|
-
server_type: config.size,
|
|
33
|
-
location: config.region,
|
|
34
|
-
image: "ubuntu-24.04",
|
|
35
|
-
user_data: config.cloudInit,
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
headers: {
|
|
39
|
-
Authorization: `Bearer ${this.apiToken}`,
|
|
40
|
-
"Content-Type": "application/json",
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
);
|
|
44
|
-
|
|
45
|
-
return {
|
|
46
|
-
id: response.data.server.id.toString(),
|
|
47
|
-
ip: response.data.server.public_net.ipv4.ip,
|
|
48
|
-
status: response.data.server.status,
|
|
49
|
-
};
|
|
50
|
-
} catch (error: any) {
|
|
51
|
-
throw new Error(
|
|
52
|
-
`Failed to create server: ${error.response?.data?.error?.message || error.message}`,
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async getServerStatus(serverId: string): Promise<string> {
|
|
58
|
-
try {
|
|
59
|
-
const response = await axios.get(`${this.baseUrl}/servers/${serverId}`, {
|
|
60
|
-
headers: { Authorization: `Bearer ${this.apiToken}` },
|
|
61
|
-
});
|
|
62
|
-
return response.data.server.status;
|
|
63
|
-
} catch (error: any) {
|
|
64
|
-
throw new Error(`Failed to get server status: ${error.message}`);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
getRegions(): Region[] {
|
|
69
|
-
return [
|
|
70
|
-
{ id: "nbg1", name: "Nuremberg", location: "Germany" },
|
|
71
|
-
{ id: "fsn1", name: "Falkenstein", location: "Germany" },
|
|
72
|
-
{ id: "hel1", name: "Helsinki", location: "Finland" },
|
|
73
|
-
{ id: "ash", name: "Ashburn", location: "USA" },
|
|
74
|
-
];
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
getServerSizes(): ServerSize[] {
|
|
78
|
-
return [
|
|
79
|
-
{ id: "cax11", name: "CAX11", vcpu: 2, ram: 4, disk: 40, price: "€3.85/mo", recommended: true },
|
|
80
|
-
{ id: "cpx11", name: "CPX11", vcpu: 2, ram: 2, disk: 40, price: "€4.15/mo" },
|
|
81
|
-
{ id: "cax21", name: "CAX21", vcpu: 4, ram: 8, disk: 80, price: "€7.05/mo" },
|
|
82
|
-
{ id: "cpx21", name: "CPX21", vcpu: 3, ram: 4, disk: 80, price: "€7.35/mo" },
|
|
83
|
-
];
|
|
84
|
-
}
|
|
85
|
-
}
|
package/src/types/index.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
export interface Region {
|
|
2
|
-
id: string;
|
|
3
|
-
name: string;
|
|
4
|
-
location: string;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export interface ServerSize {
|
|
8
|
-
id: string;
|
|
9
|
-
name: string;
|
|
10
|
-
vcpu: number;
|
|
11
|
-
ram: number;
|
|
12
|
-
disk: number;
|
|
13
|
-
price: string;
|
|
14
|
-
recommended?: boolean;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface ServerConfig {
|
|
18
|
-
name: string;
|
|
19
|
-
size: string;
|
|
20
|
-
region: string;
|
|
21
|
-
cloudInit: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface ServerResult {
|
|
25
|
-
id: string;
|
|
26
|
-
ip: string;
|
|
27
|
-
status: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface DeploymentConfig {
|
|
31
|
-
provider: string;
|
|
32
|
-
apiToken: string;
|
|
33
|
-
region: string;
|
|
34
|
-
serverSize: string;
|
|
35
|
-
serverName: string;
|
|
36
|
-
}
|
package/src/utils/cloudInit.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
export function getCoolifyCloudInit(serverName: string): string {
|
|
2
|
-
return `#!/bin/bash
|
|
3
|
-
set -e
|
|
4
|
-
|
|
5
|
-
echo "=================================="
|
|
6
|
-
echo "Quicklify Auto-Installer"
|
|
7
|
-
echo "Server: ${serverName}"
|
|
8
|
-
echo "=================================="
|
|
9
|
-
|
|
10
|
-
# Update system
|
|
11
|
-
echo "Updating system packages..."
|
|
12
|
-
apt-get update -y
|
|
13
|
-
|
|
14
|
-
# Install Coolify
|
|
15
|
-
echo "Installing Coolify..."
|
|
16
|
-
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
|
|
17
|
-
|
|
18
|
-
# Wait for services
|
|
19
|
-
echo "Waiting for Coolify services to start..."
|
|
20
|
-
sleep 30
|
|
21
|
-
|
|
22
|
-
echo "=================================="
|
|
23
|
-
echo "Coolify installation completed!"
|
|
24
|
-
echo "=================================="
|
|
25
|
-
echo ""
|
|
26
|
-
echo "Please wait 2-3 more minutes for Coolify to fully initialize."
|
|
27
|
-
echo "Then access your instance at: https://YOUR_SERVER_IP:8000"
|
|
28
|
-
`;
|
|
29
|
-
}
|
package/src/utils/logger.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
|
-
import ora, { type Ora } from "ora";
|
|
3
|
-
|
|
4
|
-
export const logger = {
|
|
5
|
-
info: (message: string) => {
|
|
6
|
-
console.log(chalk.blue("ℹ"), message);
|
|
7
|
-
},
|
|
8
|
-
|
|
9
|
-
success: (message: string) => {
|
|
10
|
-
console.log(chalk.green("✔"), message);
|
|
11
|
-
},
|
|
12
|
-
|
|
13
|
-
error: (message: string) => {
|
|
14
|
-
console.log(chalk.red("✖"), message);
|
|
15
|
-
},
|
|
16
|
-
|
|
17
|
-
warning: (message: string) => {
|
|
18
|
-
console.log(chalk.yellow("⚠"), message);
|
|
19
|
-
},
|
|
20
|
-
|
|
21
|
-
title: (message: string) => {
|
|
22
|
-
console.log();
|
|
23
|
-
console.log(chalk.bold.cyan(message));
|
|
24
|
-
console.log();
|
|
25
|
-
},
|
|
26
|
-
|
|
27
|
-
step: (message: string) => {
|
|
28
|
-
console.log(chalk.gray("→"), message);
|
|
29
|
-
},
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
export function createSpinner(text: string): Ora {
|
|
33
|
-
return ora({
|
|
34
|
-
text,
|
|
35
|
-
color: "cyan",
|
|
36
|
-
});
|
|
37
|
-
}
|
package/src/utils/prompts.ts
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import inquirer from "inquirer";
|
|
2
|
-
import type { CloudProvider } from "../providers/base.js";
|
|
3
|
-
import type { DeploymentConfig } from "../types/index.js";
|
|
4
|
-
|
|
5
|
-
export async function getDeploymentConfig(provider: CloudProvider): Promise<DeploymentConfig> {
|
|
6
|
-
const answers = await inquirer.prompt([
|
|
7
|
-
{
|
|
8
|
-
type: "password",
|
|
9
|
-
name: "apiToken",
|
|
10
|
-
message: `Enter your ${provider.displayName} API token:`,
|
|
11
|
-
validate: (input: string) => {
|
|
12
|
-
if (!input || input.trim().length === 0) {
|
|
13
|
-
return "API token is required";
|
|
14
|
-
}
|
|
15
|
-
return true;
|
|
16
|
-
},
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
type: "list",
|
|
20
|
-
name: "region",
|
|
21
|
-
message: "Select region:",
|
|
22
|
-
choices: provider.getRegions().map((r) => ({
|
|
23
|
-
name: `${r.name} (${r.location})`,
|
|
24
|
-
value: r.id,
|
|
25
|
-
})),
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
type: "list",
|
|
29
|
-
name: "size",
|
|
30
|
-
message: "Select server size:",
|
|
31
|
-
choices: provider.getServerSizes().map((s) => ({
|
|
32
|
-
name: `${s.name} - ${s.vcpu} vCPU, ${s.ram}GB RAM - ${s.price}${s.recommended ? " ⭐ Recommended" : ""}`,
|
|
33
|
-
value: s.id,
|
|
34
|
-
})),
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
type: "input",
|
|
38
|
-
name: "serverName",
|
|
39
|
-
message: "Server name:",
|
|
40
|
-
default: "coolify-server",
|
|
41
|
-
validate: (input: string) => {
|
|
42
|
-
if (!input || input.trim().length === 0) {
|
|
43
|
-
return "Server name is required";
|
|
44
|
-
}
|
|
45
|
-
if (!/^[a-z0-9-]+$/.test(input)) {
|
|
46
|
-
return "Server name must contain only lowercase letters, numbers, and hyphens";
|
|
47
|
-
}
|
|
48
|
-
return true;
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
]);
|
|
52
|
-
|
|
53
|
-
return {
|
|
54
|
-
provider: provider.name,
|
|
55
|
-
apiToken: answers.apiToken.trim(),
|
|
56
|
-
region: answers.region,
|
|
57
|
-
serverSize: answers.size,
|
|
58
|
-
serverName: answers.serverName.trim(),
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export async function confirmDeployment(config: DeploymentConfig, provider: CloudProvider): Promise<boolean> {
|
|
63
|
-
const region = provider.getRegions().find((r) => r.id === config.region);
|
|
64
|
-
const size = provider.getServerSizes().find((s) => s.id === config.serverSize);
|
|
65
|
-
|
|
66
|
-
console.log("\nDeployment Summary:");
|
|
67
|
-
console.log(` Provider: ${provider.displayName}`);
|
|
68
|
-
console.log(` Region: ${region?.name} (${region?.location})`);
|
|
69
|
-
console.log(` Size: ${size?.name} - ${size?.vcpu} vCPU, ${size?.ram}GB RAM`);
|
|
70
|
-
console.log(` Price: ${size?.price}`);
|
|
71
|
-
console.log(` Server Name: ${config.serverName}`);
|
|
72
|
-
console.log();
|
|
73
|
-
|
|
74
|
-
const { confirm } = await inquirer.prompt([
|
|
75
|
-
{
|
|
76
|
-
type: "confirm",
|
|
77
|
-
name: "confirm",
|
|
78
|
-
message: "Proceed with deployment?",
|
|
79
|
-
default: true,
|
|
80
|
-
},
|
|
81
|
-
]);
|
|
82
|
-
|
|
83
|
-
return confirm;
|
|
84
|
-
}
|
package/tests/__mocks__/axios.ts
DELETED
package/tests/__mocks__/chalk.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
const identity = (s: string) => s;
|
|
2
|
-
|
|
3
|
-
const bold = Object.assign(identity, { cyan: identity });
|
|
4
|
-
|
|
5
|
-
const chalk = {
|
|
6
|
-
blue: identity,
|
|
7
|
-
green: identity,
|
|
8
|
-
red: identity,
|
|
9
|
-
yellow: identity,
|
|
10
|
-
gray: identity,
|
|
11
|
-
cyan: identity,
|
|
12
|
-
bold,
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export default chalk;
|
package/tests/__mocks__/ora.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
const createMockSpinner = () => {
|
|
2
|
-
const spinner: any = {
|
|
3
|
-
text: '',
|
|
4
|
-
color: 'cyan',
|
|
5
|
-
};
|
|
6
|
-
spinner.start = jest.fn(() => spinner);
|
|
7
|
-
spinner.succeed = jest.fn(() => spinner);
|
|
8
|
-
spinner.fail = jest.fn(() => spinner);
|
|
9
|
-
spinner.stop = jest.fn(() => spinner);
|
|
10
|
-
return spinner;
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
const ora = jest.fn(() => createMockSpinner());
|
|
14
|
-
|
|
15
|
-
export default ora;
|
|
16
|
-
export { createMockSpinner };
|