quicklify 0.1.3 → 0.1.5

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/README.md CHANGED
@@ -6,6 +6,7 @@
6
6
  ![Downloads](https://img.shields.io/npm/dw/quicklify)
7
7
  ![License](https://img.shields.io/badge/license-MIT-blue)
8
8
  ![GitHub stars](https://img.shields.io/github/stars/omrfc/quicklify?style=flat-square)
9
+ [![Socket Badge](https://socket.dev/api/badge/npm/package/quicklify)](https://socket.dev/npm/package/quicklify)
9
10
 
10
11
  > Deploy Coolify to any cloud VPS in 4 minutes
11
12
 
package/SECURITY.md ADDED
@@ -0,0 +1,39 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ | Version | Supported |
6
+ | ------- | ------------------ |
7
+ | 0.1.x | :white_check_mark: |
8
+
9
+ ## Reporting a Vulnerability
10
+
11
+ If you discover a security vulnerability in Quicklify:
12
+
13
+ 1. **DO NOT** open a public issue
14
+ 2. Email: hello@omrfc.dev
15
+ 3. Include:
16
+ - Description of the vulnerability
17
+ - Steps to reproduce
18
+ - Potential impact
19
+ - Suggested fix (if any)
20
+
21
+ Response time: Within 48 hours
22
+
23
+ ## Security Measures
24
+
25
+ - All dependencies scanned with Socket.dev
26
+ - No credentials stored in code
27
+ - API tokens handled via environment variables
28
+ - SSH keys created with secure permissions (600)
29
+ - Input validation on all user inputs
30
+ - Automated security checks via GitHub Actions
31
+
32
+ ## Third-party Dependencies
33
+
34
+ Quicklify uses audited dependencies:
35
+ - Hetzner Cloud API (official SDK)
36
+ - All dependencies regularly updated
37
+ - Socket.dev security monitoring enabled
38
+
39
+ Security scan: https://socket.dev/npm/package/quicklify
package/package.json CHANGED
@@ -1,12 +1,18 @@
1
1
  {
2
2
  "name": "quicklify",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Automate Coolify deployment on cloud providers",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
8
8
  "quicklify": "dist/index.js"
9
9
  },
10
+ "files": [
11
+ "dist/",
12
+ "README.md",
13
+ "LICENSE",
14
+ "SECURITY.md"
15
+ ],
10
16
  "scripts": {
11
17
  "dev": "tsx src/index.ts",
12
18
  "build": "tsc && chmod +x dist/index.js",
@@ -22,6 +28,19 @@
22
28
  "cli"
23
29
  ],
24
30
  "license": "MIT",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "https://github.com/omrfc/quicklify.git"
34
+ },
35
+ "bugs": {
36
+ "url": "https://github.com/omrfc/quicklify/issues"
37
+ },
38
+ "homepage": "https://quicklify.omrfc.dev",
39
+ "author": {
40
+ "name": "Ömer",
41
+ "email": "hello@omrfc.dev",
42
+ "url": "https://omrfc.dev"
43
+ },
25
44
  "dependencies": {
26
45
  "axios": "^1.7.9",
27
46
  "chalk": "^5.4.1",
@@ -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
- };
@@ -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();
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -1,14 +0,0 @@
1
- const axios = {
2
- get: jest.fn(),
3
- post: jest.fn(),
4
- put: jest.fn(),
5
- delete: jest.fn(),
6
- patch: jest.fn(),
7
- defaults: {
8
- headers: {
9
- common: {},
10
- },
11
- },
12
- };
13
-
14
- export default axios;
@@ -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;
@@ -1,5 +0,0 @@
1
- const inquirer = {
2
- prompt: jest.fn(),
3
- };
4
-
5
- export default inquirer;
@@ -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 };