@profitlich/template-toolkit 2.4.3 → 2.5.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/package.json +1 -1
- package/scripts/deploy.js +88 -23
package/package.json
CHANGED
package/scripts/deploy.js
CHANGED
|
@@ -2,27 +2,50 @@ import ftp from 'basic-ftp';
|
|
|
2
2
|
import dotenv from 'dotenv';
|
|
3
3
|
import { glob } from 'glob';
|
|
4
4
|
import path from 'path';
|
|
5
|
+
import fs from 'fs/promises';
|
|
5
6
|
import readline from 'readline';
|
|
6
7
|
import cliProgress from 'cli-progress';
|
|
7
8
|
|
|
8
9
|
// Helper for making sure the upload progress bars go to 100%
|
|
9
10
|
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
async function isNewer(client, localFile, remotePath) {
|
|
13
|
+
const localStat = await fs.stat(localFile);
|
|
14
|
+
try {
|
|
15
|
+
const remoteDate = await client.lastMod(remotePath);
|
|
16
|
+
return localStat.mtimeMs > remoteDate.getTime();
|
|
17
|
+
} catch {
|
|
18
|
+
// File doesn't exist remotely (550) or server doesn't support MDTM — upload it
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function createClient(accessOptions) {
|
|
12
24
|
const client = new ftp.Client();
|
|
13
25
|
client.ftp.verbose = false;
|
|
26
|
+
await client.access(accessOptions);
|
|
27
|
+
return client;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function runDeploy(mode, uploadTasks, options = {}) {
|
|
31
|
+
const parallel = options.parallel ?? 3;
|
|
32
|
+
const modeUpper = mode.toUpperCase();
|
|
33
|
+
|
|
34
|
+
const accessOptions = {
|
|
35
|
+
host: process.env[`FTP_HOST_${modeUpper}`],
|
|
36
|
+
user: process.env[`FTP_USER_${modeUpper}`],
|
|
37
|
+
password: process.env[`FTP_PASSWORD_${modeUpper}`],
|
|
38
|
+
secure: true
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const mainClient = new ftp.Client();
|
|
42
|
+
mainClient.ftp.verbose = false;
|
|
14
43
|
let activeProgressBar = null;
|
|
15
44
|
|
|
16
45
|
try {
|
|
17
|
-
const modeUpper = mode.toUpperCase();
|
|
18
46
|
console.log(`🚀 Starting deployment for: ${modeUpper}`);
|
|
19
47
|
|
|
20
|
-
await
|
|
21
|
-
host: process.env[`FTP_HOST_${modeUpper}`],
|
|
22
|
-
user: process.env[`FTP_USER_${modeUpper}`],
|
|
23
|
-
password: process.env[`FTP_PASSWORD_${modeUpper}`],
|
|
24
|
-
secure: true
|
|
25
|
-
});
|
|
48
|
+
await mainClient.access(accessOptions);
|
|
26
49
|
|
|
27
50
|
for (const task of uploadTasks) {
|
|
28
51
|
console.log(`\nProcessing Task: ${task.name}`);
|
|
@@ -38,24 +61,65 @@ export async function runDeploy(mode, uploadTasks) {
|
|
|
38
61
|
continue;
|
|
39
62
|
}
|
|
40
63
|
|
|
41
|
-
|
|
64
|
+
// Determine which files are newer than their remote counterparts
|
|
65
|
+
process.stdout.write(` Checking ${files.length} files...`);
|
|
66
|
+
const filesToUpload = [];
|
|
67
|
+
for (const file of files) {
|
|
68
|
+
const relativeFile = path.relative(task.localBase, file);
|
|
69
|
+
const remotePath = path.join(task.remoteDir, relativeFile).replace(/\\/g, '/');
|
|
70
|
+
if (await isNewer(mainClient, file, remotePath)) {
|
|
71
|
+
filesToUpload.push({ file, remotePath });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
process.stdout.write(` ${filesToUpload.length} changed.\n`);
|
|
75
|
+
|
|
76
|
+
if (filesToUpload.length === 0) {
|
|
77
|
+
console.log(' All files are up to date, skipping.');
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const progressBar = new cliProgress.SingleBar({
|
|
42
82
|
format: ' Upload |{bar}| {percentage}% {value}/{total} files {duration_formatted}',
|
|
43
83
|
barCompleteChar: '█',
|
|
44
84
|
barIncompleteChar: '░',
|
|
45
85
|
hideCursor: true
|
|
46
86
|
});
|
|
47
87
|
|
|
48
|
-
activeProgressBar =
|
|
49
|
-
|
|
88
|
+
activeProgressBar = progressBar;
|
|
89
|
+
progressBar.start(filesToUpload.length, 0);
|
|
50
90
|
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
91
|
+
if (parallel <= 1) {
|
|
92
|
+
for (const { file, remotePath } of filesToUpload) {
|
|
93
|
+
await mainClient.ensureDir(path.dirname(remotePath));
|
|
94
|
+
await mainClient.uploadFrom(file, remotePath);
|
|
95
|
+
progressBar.increment();
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
// Pre-create all required remote directories with the main client
|
|
99
|
+
const uniqueDirs = [...new Set(filesToUpload.map(({ remotePath }) => path.dirname(remotePath)))];
|
|
100
|
+
for (const dir of uniqueDirs) {
|
|
101
|
+
await mainClient.ensureDir(dir);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Spawn parallel worker connections
|
|
105
|
+
const workerCount = Math.min(parallel, filesToUpload.length);
|
|
106
|
+
const workers = await Promise.all(
|
|
107
|
+
Array.from({ length: workerCount }, () => createClient(accessOptions))
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
const queue = [...filesToUpload];
|
|
111
|
+
await Promise.all(workers.map(async (workerClient) => {
|
|
112
|
+
try {
|
|
113
|
+
while (true) {
|
|
114
|
+
const item = queue.shift();
|
|
115
|
+
if (!item) break;
|
|
116
|
+
await workerClient.uploadFrom(item.file, item.remotePath);
|
|
117
|
+
progressBar.increment();
|
|
118
|
+
}
|
|
119
|
+
} finally {
|
|
120
|
+
workerClient.close();
|
|
121
|
+
}
|
|
122
|
+
}));
|
|
59
123
|
}
|
|
60
124
|
|
|
61
125
|
activeProgressBar.stop();
|
|
@@ -71,15 +135,16 @@ export async function runDeploy(mode, uploadTasks) {
|
|
|
71
135
|
}
|
|
72
136
|
console.error('Deployment failed:', err);
|
|
73
137
|
} finally {
|
|
74
|
-
|
|
138
|
+
mainClient.close();
|
|
75
139
|
}
|
|
76
140
|
}
|
|
77
141
|
|
|
78
142
|
/**
|
|
79
143
|
* Run the deploy script.
|
|
80
144
|
* @param {Array} uploadTasks - array of { name, localPattern, localBase, remoteDir, ignore? }
|
|
145
|
+
* @param {Object} options - { parallel: number } (default: { parallel: 3 })
|
|
81
146
|
*/
|
|
82
|
-
export function run(uploadTasks) {
|
|
147
|
+
export function run(uploadTasks, options = {}) {
|
|
83
148
|
const mode = process.argv[2];
|
|
84
149
|
if (!mode || (mode !== 'staging' && mode !== 'production')) {
|
|
85
150
|
console.error('Error: a mode needs to be given, either "staging" or "production"');
|
|
@@ -99,7 +164,7 @@ export function run(uploadTasks) {
|
|
|
99
164
|
rl.close();
|
|
100
165
|
if (answer.toLowerCase() === 'yes') {
|
|
101
166
|
console.log('Confirmed. Starting upload...');
|
|
102
|
-
runDeploy(mode, uploadTasks);
|
|
167
|
+
runDeploy(mode, uploadTasks, options);
|
|
103
168
|
} else {
|
|
104
169
|
console.log('❌ Deployment aborted.');
|
|
105
170
|
process.exit(0);
|
|
@@ -107,6 +172,6 @@ export function run(uploadTasks) {
|
|
|
107
172
|
});
|
|
108
173
|
// In all other modes run deploy without prompt
|
|
109
174
|
} else {
|
|
110
|
-
runDeploy(mode, uploadTasks);
|
|
175
|
+
runDeploy(mode, uploadTasks, options);
|
|
111
176
|
}
|
|
112
177
|
}
|