profitlich-template-toolkit 0.4.4 → 0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "profitlich-template-toolkit",
3
- "version": "0.4.4",
4
- "description": "Shared SCSS layout system, JS utilities and components for profitlich template repos",
3
+ "version": "0.5.0",
4
+ "description": "Shared SCSS layout system, JS utilities, components and build scripts for profitlich template repos",
5
5
  "type": "module",
6
6
  "exports": {
7
7
  "./dev": "./dev/Dev.js",
@@ -14,15 +14,24 @@
14
14
  "./utils/Vh100": "./utils/Vh100.js",
15
15
  "./utils/BodyScrolled": "./utils/BodyScrolled.js",
16
16
  "./components/menu-toggle/MenuToggle": "./components/menu-toggle/MenuToggle.js",
17
- "./components/mux-player/MuxPlayer": "./components/mux-player/MuxPlayer.js"
17
+ "./components/mux-player/MuxPlayer": "./components/mux-player/MuxPlayer.js",
18
+ "./scripts/copy-files": "./scripts/copy-files.js",
19
+ "./scripts/deploy": "./scripts/deploy.js"
18
20
  },
19
21
  "files": [
20
22
  "dev/",
21
23
  "scss/",
22
24
  "utils/",
23
- "components/"
25
+ "components/",
26
+ "scripts/"
24
27
  ],
25
28
  "dependencies": {
29
+ "basic-ftp": "^5.0.0",
30
+ "chokidar": "^4.0.0",
31
+ "cli-progress": "^3.12.0",
32
+ "dotenv": "^17.0.0",
33
+ "fs-extra": "^11.3.0",
34
+ "glob": "^11.0.0",
26
35
  "lil-gui": "^0.21.0"
27
36
  },
28
37
  "peerDependencies": {
@@ -38,4 +47,4 @@
38
47
  "url": "https://github.com/profitlich-ch/profitlich-template-toolkit"
39
48
  },
40
49
  "license": "MIT"
41
- }
50
+ }
@@ -0,0 +1,74 @@
1
+ import fs from 'fs-extra';
2
+ import { glob } from 'glob';
3
+ import path from 'path';
4
+ import chokidar from 'chokidar';
5
+
6
+ async function runTask(task) {
7
+ const files = await glob(task.src, { nodir: true, dot: true });
8
+ if (files.length === 0) return;
9
+
10
+ for (const file of files) {
11
+ const relativePath = path.relative(task.base, file);
12
+ const destPath = path.join(task.dest, relativePath);
13
+ await fs.copy(file, destPath);
14
+ }
15
+ }
16
+
17
+ export async function copyAll(copyTasks) {
18
+ console.log('šŸš€ Starting initial copy of all files...');
19
+ // empty folders to prevent orphaned files
20
+ for (const task of copyTasks) {
21
+ await fs.emptyDir(task.dest);
22
+ }
23
+ // run tasks parallely
24
+ await Promise.all(copyTasks.map(task => runTask(task)));
25
+ console.log('āœ… Initial copy complete.');
26
+ }
27
+
28
+ export function watchFiles(copyTasks, watchDir = 'src') {
29
+ console.log(`šŸ‘€ Watching for file changes in ${watchDir}/`);
30
+
31
+ // set lock variable to prevent parallel copy tasks
32
+ let isCopying = false;
33
+
34
+ const watcher = chokidar.watch(watchDir, { ignored: /(^|[\/\\])\../, persistent: true });
35
+
36
+ watcher.on('all', async (event, filePath) => {
37
+ if (['add', 'change', 'unlink'].includes(event)) {
38
+
39
+ // only proceed if copy job is not running
40
+ if (isCopying) {
41
+ return;
42
+ }
43
+
44
+ console.log(`[${event}] ${filePath}. Re-copying all files...`);
45
+
46
+ // secure copy job with try...finally
47
+ try {
48
+ isCopying = true; // activate lock
49
+ await copyAll(copyTasks);
50
+ } catch (err) {
51
+ console.error("Error during copy:", err);
52
+ } finally {
53
+ isCopying = false; // deactivate lock, allow new copy job
54
+ }
55
+ }
56
+ });
57
+ }
58
+
59
+ /**
60
+ * Run the copy-files script.
61
+ * @param {Array} copyTasks - array of { name, src, dest, base } objects
62
+ * @param {object} [options] - optional settings
63
+ * @param {string} [options.watchDir] - directory to watch (default: 'src')
64
+ */
65
+ export function run(copyTasks, options = {}) {
66
+ const command = process.argv[2];
67
+ const watchDir = options.watchDir || 'src';
68
+
69
+ if (command === 'dev') {
70
+ copyAll(copyTasks).then(() => watchFiles(copyTasks, watchDir));
71
+ } else if (command === 'build') {
72
+ copyAll(copyTasks);
73
+ }
74
+ }
@@ -0,0 +1,112 @@
1
+ import ftp from 'basic-ftp';
2
+ import dotenv from 'dotenv';
3
+ import { glob } from 'glob';
4
+ import path from 'path';
5
+ import readline from 'readline';
6
+ import cliProgress from 'cli-progress';
7
+
8
+ // Helper for making sure the upload progress bars go to 100%
9
+ const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
10
+
11
+ export async function runDeploy(mode, uploadTasks) {
12
+ const client = new ftp.Client();
13
+ client.ftp.verbose = false;
14
+ let activeProgressBar = null;
15
+
16
+ try {
17
+ const modeUpper = mode.toUpperCase();
18
+ console.log(`šŸš€ Starting deployment for: ${modeUpper}`);
19
+
20
+ await client.access({
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
+ });
26
+
27
+ for (const task of uploadTasks) {
28
+ console.log(`\nProcessing Task: ${task.name}`);
29
+
30
+ const files = await glob(task.localPattern, {
31
+ nodir: true,
32
+ dot: true,
33
+ ignore: task.ignore
34
+ });
35
+
36
+ if (files.length === 0) {
37
+ console.log('No files found for this task.');
38
+ continue;
39
+ }
40
+
41
+ const newBar = new cliProgress.SingleBar({
42
+ format: ' Upload |{bar}| {percentage}% {value}/{total} files {duration_formatted}',
43
+ barCompleteChar: 'ā–ˆ',
44
+ barIncompleteChar: 'ā–‘',
45
+ hideCursor: true
46
+ });
47
+
48
+ activeProgressBar = newBar;
49
+ activeProgressBar.start(files.length, 0);
50
+
51
+ for (const file of files) {
52
+ const relativeFile = path.relative(task.localBase, file);
53
+ const remotePath = path.join(task.remoteDir, relativeFile).replace(/\\/g, '/');
54
+
55
+ await client.ensureDir(path.dirname(remotePath));
56
+ await client.uploadFrom(file, remotePath);
57
+
58
+ activeProgressBar.increment();
59
+ }
60
+
61
+ activeProgressBar.stop();
62
+ activeProgressBar = null;
63
+ await delay(50);
64
+ }
65
+
66
+ console.log('\nāœ… Deployment completed successfully!');
67
+
68
+ } catch (err) {
69
+ if (activeProgressBar) {
70
+ activeProgressBar.stop();
71
+ }
72
+ console.error('Deployment failed:', err);
73
+ } finally {
74
+ client.close();
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Run the deploy script.
80
+ * @param {Array} uploadTasks - array of { name, localPattern, localBase, remoteDir, ignore? }
81
+ */
82
+ export function run(uploadTasks) {
83
+ const mode = process.argv[2];
84
+ if (!mode || (mode !== 'staging' && mode !== 'production')) {
85
+ console.error('Error: a mode needs to be given, either "staging" or "production"');
86
+ process.exit(1);
87
+ }
88
+
89
+ dotenv.config();
90
+
91
+ // In production mode prompt for confirmation
92
+ if (mode === 'production') {
93
+ const rl = readline.createInterface({
94
+ input: process.stdin,
95
+ output: process.stdout
96
+ });
97
+
98
+ rl.question('šŸ”“ Do you really want to deploy to PRODUCTION? Type "yes" for confirmation: ', (answer) => {
99
+ rl.close();
100
+ if (answer.toLowerCase() === 'yes') {
101
+ console.log('Confirmed. Starting upload...');
102
+ runDeploy(mode, uploadTasks);
103
+ } else {
104
+ console.log('āŒ Deployment aborted.');
105
+ process.exit(0);
106
+ }
107
+ });
108
+ // In all other modes run deploy without prompt
109
+ } else {
110
+ runDeploy(mode, uploadTasks);
111
+ }
112
+ }
package/scss/forward.scss CHANGED
@@ -1,3 +1,4 @@
1
+ @forward "config";
1
2
  @forward "core/mediaqueries";
2
3
  @forward "core/layout";
3
4
  @forward "core/hamburger";