@akash-chowdhury-24/deployhub 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 (80) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +176 -0
  3. package/install.ps1 +55 -0
  4. package/install.sh +99 -0
  5. package/package.json +86 -0
  6. package/src/adapters/dotnet.adapter.js +41 -0
  7. package/src/adapters/go.adapter.js +40 -0
  8. package/src/adapters/index.js +48 -0
  9. package/src/adapters/java.adapter.js +46 -0
  10. package/src/adapters/node.adapter.js +77 -0
  11. package/src/adapters/php.adapter.js +43 -0
  12. package/src/adapters/python.adapter.js +54 -0
  13. package/src/adapters/rails.adapter.js +66 -0
  14. package/src/artifact/engine.js +473 -0
  15. package/src/cli/index.js +45 -0
  16. package/src/commands/artifact.js +88 -0
  17. package/src/commands/build.js +44 -0
  18. package/src/commands/clean.js +50 -0
  19. package/src/commands/deploy.js +82 -0
  20. package/src/commands/doctor.js +630 -0
  21. package/src/commands/init.js +795 -0
  22. package/src/commands/logs.js +33 -0
  23. package/src/commands/rollback.js +50 -0
  24. package/src/commands/storage.js +116 -0
  25. package/src/commands/update.js +63 -0
  26. package/src/commands/verify.js +55 -0
  27. package/src/core/config.js +168 -0
  28. package/src/core/pipeline.js +77 -0
  29. package/src/core/stages.js +210 -0
  30. package/src/deployment/index.js +156 -0
  31. package/src/deployment/providers/azure-vm.js +7 -0
  32. package/src/deployment/providers/docker.js +30 -0
  33. package/src/deployment/providers/ec2.js +7 -0
  34. package/src/deployment/providers/gcp-vm.js +7 -0
  35. package/src/deployment/providers/kubernetes.js +30 -0
  36. package/src/deployment/providers/platforms/_shared.js +167 -0
  37. package/src/deployment/providers/platforms/aws-amplify.js +164 -0
  38. package/src/deployment/providers/platforms/azure-static-web-apps.js +68 -0
  39. package/src/deployment/providers/platforms/cloudflare-pages.js +103 -0
  40. package/src/deployment/providers/platforms/firebase-app-hosting.js +95 -0
  41. package/src/deployment/providers/platforms/firebase-hosting.js +99 -0
  42. package/src/deployment/providers/platforms/index.js +44 -0
  43. package/src/deployment/providers/platforms/netlify.js +102 -0
  44. package/src/deployment/providers/platforms/vercel.js +92 -0
  45. package/src/deployment/providers/ssh.js +365 -0
  46. package/src/detectors/angular.js +23 -0
  47. package/src/detectors/backend.detector.js +304 -0
  48. package/src/detectors/dotnet.js +18 -0
  49. package/src/detectors/frontend.detector.js +219 -0
  50. package/src/detectors/go.js +20 -0
  51. package/src/detectors/index.js +78 -0
  52. package/src/detectors/java.js +24 -0
  53. package/src/detectors/nextjs.js +23 -0
  54. package/src/detectors/node.js +28 -0
  55. package/src/detectors/php.js +17 -0
  56. package/src/detectors/python.js +22 -0
  57. package/src/detectors/react.js +29 -0
  58. package/src/detectors/vue.js +23 -0
  59. package/src/logger/index.js +44 -0
  60. package/src/notifications/email.js +53 -0
  61. package/src/notifications/index.js +34 -0
  62. package/src/notifications/slack.js +18 -0
  63. package/src/notifications/webhook.js +21 -0
  64. package/src/rollback/engine.js +102 -0
  65. package/src/storage/index.js +109 -0
  66. package/src/storage/providers/aws.js +96 -0
  67. package/src/storage/providers/azure.js +45 -0
  68. package/src/storage/providers/dropbox.js +49 -0
  69. package/src/storage/providers/ftp.js +69 -0
  70. package/src/storage/providers/gcp.js +45 -0
  71. package/src/storage/providers/gdrive.js +80 -0
  72. package/src/storage/providers/local.js +61 -0
  73. package/src/utils/author.js +141 -0
  74. package/src/utils/checksums.js +53 -0
  75. package/src/utils/firebase-config-generator.js +35 -0
  76. package/src/utils/github-actions.js +389 -0
  77. package/src/utils/init-platform.js +229 -0
  78. package/src/utils/nginx.js +34 -0
  79. package/src/utils/platform-env.js +132 -0
  80. package/src/utils/version.js +31 -0
@@ -0,0 +1,49 @@
1
+ import { Dropbox } from 'dropbox';
2
+ import fs from 'fs-extra';
3
+ import path from 'path';
4
+
5
+ export function createDropboxProvider(env = process.env) {
6
+ const token = env.DROPBOX_ACCESS_TOKEN;
7
+ if (!token) {
8
+ throw new Error('Dropbox credentials incomplete. Set DROPBOX_ACCESS_TOKEN.');
9
+ }
10
+
11
+ const dbx = new Dropbox({ accessToken: token });
12
+
13
+ async function upload(localPath, remoteKey) {
14
+ const key = `/${remoteKey || path.basename(localPath)}`;
15
+ const contents = await fs.readFile(localPath);
16
+ await dbx.filesUpload({ path: key, contents, mode: { '.tag': 'overwrite' } });
17
+ }
18
+
19
+ async function download(remoteKey, localPath) {
20
+ const key = remoteKey.startsWith('/') ? remoteKey : `/${remoteKey}`;
21
+ const response = await dbx.filesDownload({ path: key });
22
+ const fileBlob = response.result.fileBinary;
23
+ await fs.ensureDir(path.dirname(localPath));
24
+ await fs.writeFile(localPath, fileBlob);
25
+ }
26
+
27
+ async function verify(remoteKey) {
28
+ const key = remoteKey.startsWith('/') ? remoteKey : `/${remoteKey}`;
29
+ try {
30
+ await dbx.filesGetMetadata({ path: key });
31
+ return true;
32
+ } catch {
33
+ return false;
34
+ }
35
+ }
36
+
37
+ async function deleteObject(remoteKey) {
38
+ const key = remoteKey.startsWith('/') ? remoteKey : `/${remoteKey}`;
39
+ await dbx.filesDeleteV2({ path: key });
40
+ }
41
+
42
+ async function testConnection() {
43
+ await dbx.usersGetCurrentAccount();
44
+ }
45
+
46
+ return { upload, download, verify, delete: deleteObject, testConnection };
47
+ }
48
+
49
+ export default { createDropboxProvider };
@@ -0,0 +1,69 @@
1
+ import { Client } from 'basic-ftp';
2
+ import fs from 'fs-extra';
3
+ import path from 'path';
4
+
5
+ export function createFtpProvider(env = process.env) {
6
+ const host = env.FTP_HOST;
7
+ const user = env.FTP_USER;
8
+ const password = env.FTP_PASSWORD;
9
+ const port = parseInt(env.FTP_PORT || '21', 10);
10
+ const basePath = env.FTP_PATH || '/uploads';
11
+
12
+ if (!host || !user) {
13
+ throw new Error('FTP credentials incomplete. Set FTP_HOST, FTP_USER, and FTP_PASSWORD.');
14
+ }
15
+
16
+ async function withClient(fn) {
17
+ const client = new Client();
18
+ try {
19
+ await client.access({ host, user, password, port, secure: false });
20
+ return await fn(client);
21
+ } finally {
22
+ client.close();
23
+ }
24
+ }
25
+
26
+ async function upload(localPath, remoteKey) {
27
+ const remotePath = `${basePath}/${remoteKey || path.basename(localPath)}`;
28
+ await withClient(async (client) => {
29
+ await client.uploadFrom(localPath, remotePath);
30
+ });
31
+ }
32
+
33
+ async function download(remoteKey, localPath) {
34
+ const remotePath = `${basePath}/${remoteKey}`;
35
+ await fs.ensureDir(path.dirname(localPath));
36
+ await withClient(async (client) => {
37
+ await client.downloadTo(localPath, remotePath);
38
+ });
39
+ }
40
+
41
+ async function verify(remoteKey) {
42
+ const remotePath = `${basePath}/${remoteKey}`;
43
+ try {
44
+ await withClient(async (client) => {
45
+ await client.size(remotePath);
46
+ });
47
+ return true;
48
+ } catch {
49
+ return false;
50
+ }
51
+ }
52
+
53
+ async function deleteObject(remoteKey) {
54
+ const remotePath = `${basePath}/${remoteKey}`;
55
+ await withClient(async (client) => {
56
+ await client.remove(remotePath);
57
+ });
58
+ }
59
+
60
+ async function testConnection() {
61
+ await withClient(async (client) => {
62
+ await client.pwd();
63
+ });
64
+ }
65
+
66
+ return { upload, download, verify, delete: deleteObject, testConnection };
67
+ }
68
+
69
+ export default { createFtpProvider };
@@ -0,0 +1,45 @@
1
+ import { Storage } from '@google-cloud/storage';
2
+ import fs from 'fs-extra';
3
+ import path from 'path';
4
+
5
+ export function createGcpProvider(env = process.env) {
6
+ const bucketName = env.GCP_BUCKET;
7
+ const keyFile = env.GCP_KEY_FILE;
8
+
9
+ if (!bucketName) {
10
+ throw new Error('GCP credentials incomplete. Set GCP_BUCKET and GCP_KEY_FILE.');
11
+ }
12
+
13
+ const storage = new Storage({
14
+ projectId: env.GCP_PROJECT_ID,
15
+ keyFilename: keyFile || undefined,
16
+ });
17
+ const bucket = storage.bucket(bucketName);
18
+
19
+ async function upload(localPath, remoteKey) {
20
+ const key = remoteKey || path.basename(localPath);
21
+ await bucket.upload(localPath, { destination: key });
22
+ }
23
+
24
+ async function download(remoteKey, localPath) {
25
+ await fs.ensureDir(path.dirname(localPath));
26
+ await bucket.file(remoteKey).download({ destination: localPath });
27
+ }
28
+
29
+ async function verify(remoteKey) {
30
+ const [exists] = await bucket.file(remoteKey).exists();
31
+ return exists;
32
+ }
33
+
34
+ async function deleteObject(remoteKey) {
35
+ await bucket.file(remoteKey).delete({ ignoreNotFound: true });
36
+ }
37
+
38
+ async function testConnection() {
39
+ await bucket.getMetadata();
40
+ }
41
+
42
+ return { upload, download, verify, delete: deleteObject, testConnection };
43
+ }
44
+
45
+ export default { createGcpProvider };
@@ -0,0 +1,80 @@
1
+ import { google } from 'googleapis';
2
+ import fs from 'fs-extra';
3
+ import path from 'path';
4
+ import { createReadStream, createWriteStream } from 'fs';
5
+
6
+ export function createGdriveProvider(env = process.env) {
7
+ const clientId = env.GDRIVE_CLIENT_ID;
8
+ const clientSecret = env.GDRIVE_CLIENT_SECRET;
9
+ const refreshToken = env.GDRIVE_REFRESH_TOKEN;
10
+ const folderId = env.GDRIVE_FOLDER_ID;
11
+
12
+ if (!clientId || !clientSecret || !refreshToken) {
13
+ throw new Error(
14
+ 'Google Drive credentials incomplete. Set GDRIVE_CLIENT_ID, GDRIVE_CLIENT_SECRET, and GDRIVE_REFRESH_TOKEN.'
15
+ );
16
+ }
17
+
18
+ const oauth2Client = new google.auth.OAuth2(clientId, clientSecret);
19
+ oauth2Client.setCredentials({ refresh_token: refreshToken });
20
+ const drive = google.drive({ version: 'v3', auth: oauth2Client });
21
+
22
+ async function upload(localPath, remoteKey) {
23
+ const fileName = remoteKey || path.basename(localPath);
24
+ const metadata = { name: fileName };
25
+ if (folderId) metadata.parents = [folderId];
26
+
27
+ await drive.files.create({
28
+ requestBody: metadata,
29
+ media: { body: createReadStream(localPath) },
30
+ fields: 'id',
31
+ });
32
+ }
33
+
34
+ async function download(remoteKey, localPath) {
35
+ const res = await drive.files.list({
36
+ q: `name='${remoteKey}' and trashed=false`,
37
+ fields: 'files(id)',
38
+ });
39
+ const file = res.data.files?.[0];
40
+ if (!file?.id) throw new Error(`Google Drive file not found: ${remoteKey}`);
41
+
42
+ await fs.ensureDir(path.dirname(localPath));
43
+ const dest = createWriteStream(localPath);
44
+ const response = await drive.files.get(
45
+ { fileId: file.id, alt: 'media' },
46
+ { responseType: 'stream' }
47
+ );
48
+ await new Promise((resolve, reject) => {
49
+ response.data.pipe(dest);
50
+ dest.on('finish', resolve);
51
+ dest.on('error', reject);
52
+ });
53
+ }
54
+
55
+ async function verify(remoteKey) {
56
+ const res = await drive.files.list({
57
+ q: `name='${remoteKey}' and trashed=false`,
58
+ fields: 'files(id)',
59
+ });
60
+ return (res.data.files?.length || 0) > 0;
61
+ }
62
+
63
+ async function deleteObject(remoteKey) {
64
+ const res = await drive.files.list({
65
+ q: `name='${remoteKey}' and trashed=false`,
66
+ fields: 'files(id)',
67
+ });
68
+ for (const file of res.data.files || []) {
69
+ if (file.id) await drive.files.delete({ fileId: file.id });
70
+ }
71
+ }
72
+
73
+ async function testConnection() {
74
+ await drive.files.list({ pageSize: 1, fields: 'files(id)' });
75
+ }
76
+
77
+ return { upload, download, verify, delete: deleteObject, testConnection };
78
+ }
79
+
80
+ export default { createGdriveProvider };
@@ -0,0 +1,61 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+
4
+ /**
5
+ * @param {Record<string, string>} [_env]
6
+ */
7
+ export function createLocalProvider(_env = process.env) {
8
+ const baseDir = path.join(process.cwd(), '.deployhub-storage');
9
+
10
+ /**
11
+ * @param {string} localPath
12
+ * @param {string} [remoteKey]
13
+ */
14
+ async function upload(localPath, remoteKey) {
15
+ const key = remoteKey || path.basename(localPath);
16
+ const dest = path.join(baseDir, key);
17
+ await fs.ensureDir(path.dirname(dest));
18
+ await fs.copy(localPath, dest);
19
+ }
20
+
21
+ /**
22
+ * @param {string} remoteKey
23
+ * @param {string} localPath
24
+ */
25
+ async function download(remoteKey, localPath) {
26
+ const src = path.join(baseDir, remoteKey);
27
+ if (!(await fs.pathExists(src))) {
28
+ throw new Error(`Local storage file not found: ${remoteKey}`);
29
+ }
30
+ await fs.ensureDir(path.dirname(localPath));
31
+ await fs.copy(src, localPath);
32
+ }
33
+
34
+ /**
35
+ * @param {string} remoteKey
36
+ */
37
+ async function verify(remoteKey) {
38
+ return fs.pathExists(path.join(baseDir, remoteKey));
39
+ }
40
+
41
+ /**
42
+ * @param {string} remoteKey
43
+ */
44
+ async function deleteObject(remoteKey) {
45
+ const target = path.join(baseDir, remoteKey);
46
+ if (await fs.pathExists(target)) {
47
+ await fs.remove(target);
48
+ }
49
+ }
50
+
51
+ async function testConnection() {
52
+ await fs.ensureDir(baseDir);
53
+ const testFile = path.join(baseDir, '.connection-test');
54
+ await fs.writeFile(testFile, 'ok');
55
+ await fs.remove(testFile);
56
+ }
57
+
58
+ return { upload, download, verify, delete: deleteObject, testConnection };
59
+ }
60
+
61
+ export default { createLocalProvider };
@@ -0,0 +1,141 @@
1
+ import chalk from 'chalk';
2
+ import { readFileSync } from 'fs';
3
+ import path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+
6
+ export const AUTHOR = {
7
+ name: 'Akash Chowdhury',
8
+ email: 'akashbumbac24@gmail.com',
9
+ linkedin: 'https://www.linkedin.com/in/akash-chowdhury-12141a222/',
10
+ linkedinShort: 'linkedin.com/in/akash-chowdhury-12141a222',
11
+ };
12
+
13
+ export const TOOL_NAME = 'DeployHub';
14
+
15
+ /** GitHub repository slug (owner/repo) for releases and install scripts */
16
+ export const GITHUB_REPO = 'Akash-Chowdhury-24/DeployHub';
17
+
18
+ let cachedVersion = null;
19
+
20
+ /**
21
+ * @returns {string}
22
+ */
23
+ export function getDeployHubVersion() {
24
+ if (!cachedVersion) {
25
+ const pkgPath = path.join(
26
+ path.dirname(fileURLToPath(import.meta.url)),
27
+ '../../package.json'
28
+ );
29
+ cachedVersion = JSON.parse(readFileSync(pkgPath, 'utf-8')).version;
30
+ }
31
+ return cachedVersion;
32
+ }
33
+
34
+ /**
35
+ * @returns {string}
36
+ */
37
+ export function formatVersionOutput() {
38
+ const version = getDeployHubVersion();
39
+ return `${version}\nBuilt by ${AUTHOR.name} — ${AUTHOR.email}\n${AUTHOR.linkedin}`;
40
+ }
41
+
42
+ /**
43
+ * @returns {object}
44
+ */
45
+ export function getGeneratedByMetadata() {
46
+ return {
47
+ tool: TOOL_NAME,
48
+ version: getDeployHubVersion(),
49
+ author: AUTHOR.name,
50
+ email: AUTHOR.email,
51
+ linkedin: AUTHOR.linkedin,
52
+ };
53
+ }
54
+
55
+ /**
56
+ * @returns {string}
57
+ */
58
+ export function getArtifactReadmeFooter() {
59
+ return `---
60
+ Generated by DeployHub
61
+ Built by ${AUTHOR.name} <${AUTHOR.email}>
62
+ ${AUTHOR.linkedin}
63
+
64
+ This artifact was automatically created and stored by DeployHub.
65
+ `;
66
+ }
67
+
68
+ /**
69
+ * @returns {string}
70
+ */
71
+ export function getWorkflowHeaderComment() {
72
+ return `# ─────────────────────────────────────────────
73
+ # This workflow was auto-generated by DeployHub
74
+ # Built by ${AUTHOR.name} <${AUTHOR.email}>
75
+ # ${AUTHOR.linkedin}
76
+ # Do not edit this file manually.
77
+ # ─────────────────────────────────────────────
78
+ `;
79
+ }
80
+
81
+ export function printBanner() {
82
+ const dim = chalk.dim;
83
+ const padLine = (content) => {
84
+ const visible = content.replace(/\u001b\[[0-9;]*m/g, '');
85
+ const padding = Math.max(0, 49 - visible.length);
86
+ return dim('│') + content + ' '.repeat(padding) + dim('│');
87
+ };
88
+
89
+ console.log('');
90
+ console.log(dim('┌─────────────────────────────────────────────────┐'));
91
+ console.log(dim('│ │'));
92
+ console.log(padLine(' 🚀 ' + chalk.bold.white('DeployHub')));
93
+ console.log(padLine(' ' + chalk.cyan(`Built by ${AUTHOR.name}`)));
94
+ console.log(padLine(' ' + chalk.gray(AUTHOR.email)));
95
+ console.log(padLine(' ' + chalk.gray(AUTHOR.linkedinShort)));
96
+ console.log(dim('│ │'));
97
+ console.log(dim('└─────────────────────────────────────────────────┘'));
98
+ console.log('');
99
+ }
100
+
101
+ export function printAuthorFooter() {
102
+ console.log(chalk.dim('─────────────────────────────────────────────'));
103
+ console.log(chalk.cyan(` Built with ❤ by ${AUTHOR.name}`));
104
+ console.log(' Questions or feedback?');
105
+ console.log(chalk.gray(` 📧 ${AUTHOR.email}`));
106
+ console.log(chalk.gray(` 💼 ${AUTHOR.linkedinShort}`));
107
+ console.log(chalk.dim('─────────────────────────────────────────────'));
108
+ console.log('');
109
+ }
110
+
111
+ export function printDoctorFooter() {
112
+ console.log(
113
+ chalk.gray(` DeployHub by ${AUTHOR.name} — ${AUTHOR.email}`)
114
+ );
115
+ }
116
+
117
+ /**
118
+ * @returns {boolean}
119
+ */
120
+ export function shouldShowBanner() {
121
+ const args = process.argv.slice(2);
122
+ if (args.includes('--help') || args.includes('-h')) return false;
123
+ if (args.includes('--version') || args.includes('-V')) return false;
124
+ const subcommand = args.find((arg) => !arg.startsWith('-'));
125
+ return Boolean(subcommand);
126
+ }
127
+
128
+ export default {
129
+ AUTHOR,
130
+ TOOL_NAME,
131
+ GITHUB_REPO,
132
+ getDeployHubVersion,
133
+ formatVersionOutput,
134
+ getGeneratedByMetadata,
135
+ getArtifactReadmeFooter,
136
+ getWorkflowHeaderComment,
137
+ printBanner,
138
+ printAuthorFooter,
139
+ printDoctorFooter,
140
+ shouldShowBanner,
141
+ };
@@ -0,0 +1,53 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import crypto from 'crypto';
4
+
5
+ /**
6
+ * @param {string} filePath
7
+ * @returns {Promise<string>}
8
+ */
9
+ export async function sha256File(filePath) {
10
+ const hash = crypto.createHash('sha256');
11
+ const stream = fs.createReadStream(filePath);
12
+ return new Promise((resolve, reject) => {
13
+ stream.on('data', (chunk) => hash.update(chunk));
14
+ stream.on('end', () => resolve(hash.digest('hex')));
15
+ stream.on('error', reject);
16
+ });
17
+ }
18
+
19
+ /**
20
+ * @param {string} dir
21
+ * @returns {Promise<Record<string, string>>}
22
+ */
23
+ export async function generateChecksums(dir) {
24
+ /** @type {Record<string, string>} */
25
+ const checksums = {};
26
+
27
+ async function walk(currentDir, base = '') {
28
+ const entries = await fs.readdir(currentDir, { withFileTypes: true });
29
+ for (const entry of entries) {
30
+ const fullPath = path.join(currentDir, entry.name);
31
+ const relativePath = path.join(base, entry.name).replace(/\\/g, '/');
32
+ if (entry.isDirectory()) {
33
+ await walk(fullPath, relativePath);
34
+ } else {
35
+ checksums[relativePath] = await sha256File(fullPath);
36
+ }
37
+ }
38
+ }
39
+
40
+ await walk(dir);
41
+ return checksums;
42
+ }
43
+
44
+ /**
45
+ * @param {Record<string, string>} checksums
46
+ * @returns {string}
47
+ */
48
+ export function formatChecksums(checksums) {
49
+ return Object.entries(checksums)
50
+ .sort(([a], [b]) => a.localeCompare(b))
51
+ .map(([file, hash]) => `${hash} ${file}`)
52
+ .join('\n');
53
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Generate firebase.json for Firebase Hosting if missing.
3
+ *
4
+ * @param {string} buildOutput
5
+ * @returns {object}
6
+ */
7
+ export function generateFirebaseHostingConfig(buildOutput = 'dist') {
8
+ return {
9
+ hosting: {
10
+ public: buildOutput,
11
+ ignore: ['firebase.json', '**/.*', '**/node_modules/**'],
12
+ rewrites: [{ source: '**', destination: '/index.html' }],
13
+ },
14
+ };
15
+ }
16
+
17
+ /**
18
+ * @param {string} buildOutput
19
+ * @param {string} [cwd]
20
+ */
21
+ export async function ensureFirebaseJson(buildOutput = 'dist', cwd = process.cwd()) {
22
+ const fs = (await import('fs-extra')).default;
23
+ const path = (await import('path')).default;
24
+ const firebasePath = path.join(cwd, 'firebase.json');
25
+
26
+ if (await fs.pathExists(firebasePath)) {
27
+ return firebasePath;
28
+ }
29
+
30
+ const config = generateFirebaseHostingConfig(buildOutput);
31
+ await fs.writeJson(firebasePath, config, { spaces: 2 });
32
+ return firebasePath;
33
+ }
34
+
35
+ export default { generateFirebaseHostingConfig, ensureFirebaseJson };