@squeditor/squeditor-framework 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 (77) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +106 -0
  3. package/package.json +36 -0
  4. package/php/functions.php +92 -0
  5. package/project-template/package.json +29 -0
  6. package/project-template/postcss.config.js +6 -0
  7. package/project-template/squeditor.config.js +81 -0
  8. package/project-template/src/404.php +21 -0
  9. package/project-template/src/assets/css/squeditor-icons.css +4719 -0
  10. package/project-template/src/assets/css/tailwind.css +3 -0
  11. package/project-template/src/assets/css/uikit-components.css +14586 -0
  12. package/project-template/src/assets/js/gsap-advanced.js +26 -0
  13. package/project-template/src/assets/js/gsap-init.js +672 -0
  14. package/project-template/src/assets/js/gsap-modules/cursor-preview.js +132 -0
  15. package/project-template/src/assets/js/gsap-modules/cursor.js +456 -0
  16. package/project-template/src/assets/js/gsap-modules/loop-panels.js +78 -0
  17. package/project-template/src/assets/js/gsap-modules/marquee.js +106 -0
  18. package/project-template/src/assets/js/gsap-modules/pinned-panels.js +105 -0
  19. package/project-template/src/assets/js/gsap-modules/scroll-to.js +54 -0
  20. package/project-template/src/assets/js/gsap-modules/swipe-slider.js +121 -0
  21. package/project-template/src/assets/js/gsap-modules/text-mask.js +93 -0
  22. package/project-template/src/assets/js/gsap-modules/tilt.js +70 -0
  23. package/project-template/src/assets/js/main.js +302 -0
  24. package/project-template/src/assets/js/uikit-components.js +18171 -0
  25. package/project-template/src/assets/scss/_base.scss +140 -0
  26. package/project-template/src/assets/scss/_components.scss +165 -0
  27. package/project-template/src/assets/scss/_config.scss +13 -0
  28. package/project-template/src/assets/scss/_functions.scss +81 -0
  29. package/project-template/src/assets/scss/_tokens.scss +229 -0
  30. package/project-template/src/assets/scss/_transitions.scss +36 -0
  31. package/project-template/src/assets/scss/_uikit-overrides.scss +187 -0
  32. package/project-template/src/assets/scss/_uikit_dynamic.scss +43 -0
  33. package/project-template/src/assets/scss/_utilities.scss +31 -0
  34. package/project-template/src/assets/scss/custom.scss +10 -0
  35. package/project-template/src/assets/scss/main.scss +11 -0
  36. package/project-template/src/assets/static/fonts/squeditor-icons/squeditor-icons.eot +0 -0
  37. package/project-template/src/assets/static/fonts/squeditor-icons/squeditor-icons.svg +1183 -0
  38. package/project-template/src/assets/static/fonts/squeditor-icons/squeditor-icons.ttf +0 -0
  39. package/project-template/src/assets/static/fonts/squeditor-icons/squeditor-icons.woff +0 -0
  40. package/project-template/src/config/site-config.php +34 -0
  41. package/project-template/src/data/blog.php +21 -0
  42. package/project-template/src/data/portfolio.php +23 -0
  43. package/project-template/src/data/team.php +23 -0
  44. package/project-template/src/index.php +57 -0
  45. package/project-template/src/init.php +19 -0
  46. package/project-template/src/page-templates/base.php +39 -0
  47. package/project-template/src/page-templates/body-scripts.php +26 -0
  48. package/project-template/src/page-templates/head.php +47 -0
  49. package/project-template/src/page-templates/transition.php +45 -0
  50. package/project-template/src/sections/cards/cards-grid.php +34 -0
  51. package/project-template/src/sections/cards/cards-horizontal.php +28 -0
  52. package/project-template/src/sections/cta/cta-banner.php +34 -0
  53. package/project-template/src/sections/cta/cta-newsletter.php +19 -0
  54. package/project-template/src/sections/footer/layout-01.php +35 -0
  55. package/project-template/src/sections/header/layout-01.php +36 -0
  56. package/project-template/src/sections/hero/hero-centered.php +44 -0
  57. package/project-template/src/sections/hero/hero-split.php +132 -0
  58. package/project-template/src/sections/hero/hero-video.php +22 -0
  59. package/project-template/src/sections/sidebar/sidebar-right.php +11 -0
  60. package/project-template/src/template-parts/breadcrumbs.php +17 -0
  61. package/project-template/src/template-parts/footer.php +74 -0
  62. package/project-template/src/template-parts/header.php +120 -0
  63. package/project-template/src/template-parts/mega-menu.php +7 -0
  64. package/project-template/src/template-parts/nav.php +16 -0
  65. package/project-template/src/template-parts/page-title-bar.php +14 -0
  66. package/project-template/tailwind.config.js +26 -0
  67. package/project-template/vite.config.js +67 -0
  68. package/scripts/build-components.js +109 -0
  69. package/scripts/copy-static.js +150 -0
  70. package/scripts/dev-router.php +23 -0
  71. package/scripts/dev.js +55 -0
  72. package/scripts/get-port.js +27 -0
  73. package/scripts/package-customer.js +278 -0
  74. package/scripts/package-dist.js +54 -0
  75. package/scripts/scaffold.js +72 -0
  76. package/scripts/snapshot.js +74 -0
  77. package/uikit-manifest.json +248 -0
@@ -0,0 +1,150 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { execSync } = require('child_process');
4
+
5
+ const projectRoot = process.cwd();
6
+ const config = require(path.join(projectRoot, 'squeditor.config.js'));
7
+ const micromatch = require(path.join(projectRoot, 'node_modules/micromatch'));
8
+
9
+ let sharp;
10
+ try {
11
+ sharp = require(path.join(projectRoot, 'node_modules/sharp'));
12
+ } catch (e) {
13
+ console.warn('[Squeditor] ⚠️ sharp not found. Image optimization will be skipped.');
14
+ }
15
+
16
+ let ffmpeg;
17
+ try {
18
+ ffmpeg = require(path.join(projectRoot, 'node_modules/fluent-ffmpeg'));
19
+ } catch (e) {
20
+ console.warn('[Squeditor] ⚠️ fluent-ffmpeg not found. Video optimization will be skipped.');
21
+ }
22
+
23
+ const srcDir = path.join(projectRoot, 'src/assets/static');
24
+ const destDir = path.join(projectRoot, config.snapshot.outputDir, 'assets/static');
25
+
26
+ const mediaConfig = config.media || {};
27
+ const optConfig = mediaConfig.optimize || { enabled: false };
28
+
29
+ async function processFile(src, dest, relPath) {
30
+ const isOptimizeEnabled = optConfig.enabled;
31
+ const isMatched = isOptimizeEnabled && micromatch.isMatch(relPath, optConfig.include || [], { ignore: optConfig.exclude || [] });
32
+
33
+ const ext = path.extname(src).toLowerCase();
34
+ const isImage = ['.jpg', '.jpeg', '.png', '.webp', '.gif'].includes(ext);
35
+ const isVideo = ['.mp4', '.webm'].includes(ext);
36
+
37
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
38
+
39
+ if (isMatched) {
40
+ if (isImage && sharp) {
41
+ try {
42
+ console.log(` - Optimizing image: ${relPath}`);
43
+ let pipeline = sharp(src);
44
+
45
+ const quality = optConfig.imageQuality || 80;
46
+
47
+ if (ext === '.jpg' || ext === '.jpeg') {
48
+ pipeline = pipeline.jpeg({ quality, mozjpeg: true });
49
+ } else if (ext === '.png') {
50
+ pipeline = pipeline.png({ quality, compressionLevel: 9, palette: true });
51
+ } else if (ext === '.webp') {
52
+ pipeline = pipeline.webp({ quality, effort: 6 });
53
+ } else if (ext === '.gif') {
54
+ pipeline = pipeline.gif(); // Standard gif optimization
55
+ }
56
+
57
+ await pipeline.toFile(dest);
58
+
59
+ // --- Regression Check ---
60
+ // If optimized file is somehow larger than original, fallback to original
61
+ const originalSize = fs.statSync(src).size;
62
+ const optimizedSize = fs.statSync(dest).size;
63
+
64
+ if (optimizedSize > originalSize) {
65
+ // console.log(` - ℹ️ Optimized size (${optimizedSize}) > original (${originalSize}). Falling back to original.`);
66
+ fs.copyFileSync(src, dest);
67
+ }
68
+
69
+ return;
70
+ } catch (err) {
71
+ console.error(` - ❌ Failed to optimize image ${relPath}:`, err.message);
72
+ }
73
+ } else if (isVideo && ffmpeg) {
74
+ try {
75
+ console.log(` - Optimizing video: ${relPath} (This may take a while...)`);
76
+ return new Promise((resolve, reject) => {
77
+ ffmpeg(src)
78
+ .outputOptions([
79
+ '-c:v libx264',
80
+ `-crf ${optConfig.videoQuality || 28}`,
81
+ '-preset slower',
82
+ '-c:a aac',
83
+ '-b:a 128k',
84
+ '-movflags +faststart'
85
+ ])
86
+ .save(dest)
87
+ .on('end', () => {
88
+ // Video regression check
89
+ const originalSize = fs.statSync(src).size;
90
+ const optimizedSize = fs.statSync(dest).size;
91
+ if (optimizedSize > originalSize) {
92
+ fs.copyFileSync(src, dest);
93
+ }
94
+ resolve();
95
+ })
96
+ .on('error', (err) => {
97
+ console.error(` - ❌ Failed to optimize video ${relPath}:`, err.message);
98
+ fs.copyFileSync(src, dest);
99
+ resolve();
100
+ });
101
+ });
102
+ } catch (err) {
103
+ console.error(` - ❌ Video optimization error for ${relPath}:`, err.message);
104
+ }
105
+ }
106
+ }
107
+
108
+ // Default: Just copy
109
+ fs.copyFileSync(src, dest);
110
+ }
111
+
112
+ async function walkAndProcess(currentSrc, currentDest, baseDir) {
113
+ const files = fs.readdirSync(currentSrc);
114
+ for (const file of files) {
115
+ if (file === '.DS_Store') continue;
116
+
117
+ const srcPath = path.join(currentSrc, file);
118
+ const destPath = path.join(currentDest, file);
119
+ const relPath = path.relative(baseDir, srcPath);
120
+ const stat = fs.statSync(srcPath);
121
+
122
+ if (stat.isDirectory()) {
123
+ await walkAndProcess(srcPath, destPath, baseDir);
124
+ } else {
125
+ await processFile(srcPath, destPath, relPath);
126
+ }
127
+ }
128
+ }
129
+
130
+ async function run() {
131
+ if (fs.existsSync(srcDir)) {
132
+ console.log(`[Squeditor] 📂 Processing static assets from src/assets/static to dist/assets/static...`);
133
+ fs.mkdirSync(destDir, { recursive: true });
134
+
135
+ await walkAndProcess(srcDir, destDir, srcDir);
136
+
137
+ console.log(`[Squeditor] ✅ Static assets processed.`);
138
+
139
+ const uikitJs = path.join(projectRoot, 'src/assets/js/uikit-components.js');
140
+ const uikitDest = path.join(projectRoot, config.snapshot.outputDir, 'assets/js/uikit-components.js');
141
+ if (fs.existsSync(uikitJs)) {
142
+ fs.mkdirSync(path.dirname(uikitDest), { recursive: true });
143
+ fs.copyFileSync(uikitJs, uikitDest);
144
+ }
145
+ } else {
146
+ console.warn(`[Squeditor] ⚠️ Source directory for static assets not found: ${srcDir}`);
147
+ }
148
+ }
149
+
150
+ run();
@@ -0,0 +1,23 @@
1
+ <?php
2
+ // scripts/dev-router.php
3
+ // Custom router for PHP built-in development server (npm run dev)
4
+
5
+ $uri = urldecode(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH));
6
+ $doc_root = $_SERVER['DOCUMENT_ROOT'];
7
+
8
+ // 1. If it's a direct file request that exists, serve it natively
9
+ if ($uri !== '/' && file_exists($doc_root . $uri)) {
10
+ return false;
11
+ }
12
+
13
+ // 2. If it is an HTML file request, route it to the equivalent PHP file
14
+ if (preg_match('/\.html$/', $uri)) {
15
+ $php_file = preg_replace('/\.html$/', '.php', $uri);
16
+ if (file_exists($doc_root . $php_file)) {
17
+ require $doc_root . $php_file;
18
+ return true;
19
+ }
20
+ }
21
+
22
+ // 3. Let PHP's core handle index.php fallback for directories or 404s
23
+ return false;
package/scripts/dev.js ADDED
@@ -0,0 +1,55 @@
1
+ const { spawn } = require('child_process');
2
+ const path = require('path');
3
+ const fs = require('fs');
4
+ const getAvailablePort = require('./get-port');
5
+
6
+ async function startDev() {
7
+ const projectRoot = process.cwd();
8
+
9
+ // Find available ports
10
+ const phpPort = await getAvailablePort(3001);
11
+ const vitePort = await getAvailablePort(5173);
12
+
13
+ console.log(`[Squeditor] 🚀 Starting Dev Servers...`);
14
+ console.log(` - PHP Server: http://127.0.0.1:${phpPort}`);
15
+ console.log(` - Vite Server: http://127.0.0.1:${vitePort}`);
16
+
17
+ // Read config to resolve framework path dynamically
18
+ const configPath = path.join(projectRoot, 'squeditor.config.js');
19
+ let fwRoot = '..';
20
+ if (fs.existsSync(configPath)) {
21
+ const config = require(configPath);
22
+ if (config.framework) {
23
+ fwRoot = config.framework;
24
+ }
25
+ }
26
+ const devRouterPath = path.join(fwRoot, 'scripts/dev-router.php');
27
+
28
+ // Start PHP Server
29
+ const php = spawn('php', [
30
+ '-S', `127.0.0.1:${phpPort}`,
31
+ '-t', 'src',
32
+ devRouterPath
33
+ ], {
34
+ stdio: 'inherit',
35
+ env: { ...process.env, SQUEDITOR_PHP_PORT: phpPort, SQUEDITOR_VITE_PORT: vitePort }
36
+ });
37
+
38
+ // Start Vite
39
+ const vite = spawn('npx', [
40
+ 'vite',
41
+ '--port', vitePort.toString(),
42
+ '--strictPort', 'false'
43
+ ], {
44
+ stdio: 'inherit',
45
+ env: { ...process.env, SQUEDITOR_PHP_PORT: phpPort, SQUEDITOR_VITE_PORT: vitePort }
46
+ });
47
+
48
+ process.on('SIGINT', () => {
49
+ php.kill();
50
+ vite.kill();
51
+ process.exit();
52
+ });
53
+ }
54
+
55
+ startDev();
@@ -0,0 +1,27 @@
1
+ const net = require('net');
2
+
3
+ /**
4
+ * Finds an available port starting from the given port.
5
+ * @param {number} startPort
6
+ * @returns {Promise<number>}
7
+ */
8
+ function getAvailablePort(startPort) {
9
+ return new Promise((resolve) => {
10
+ const server = net.createServer();
11
+ server.listen(startPort, '127.0.0.1', () => {
12
+ const { port } = server.address();
13
+ server.close(() => resolve(port));
14
+ });
15
+ server.on('error', () => {
16
+ resolve(getAvailablePort(startPort + 1));
17
+ });
18
+ });
19
+ }
20
+
21
+ // If run directly, output the port for shell scripts to capture
22
+ if (require.main === module) {
23
+ const basePort = parseInt(process.argv[2]) || 3000;
24
+ getAvailablePort(basePort).then(port => process.stdout.write(port.toString()));
25
+ }
26
+
27
+ module.exports = getAvailablePort;
@@ -0,0 +1,278 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { execSync } = require('child_process');
4
+
5
+ const projectRoot = process.cwd();
6
+ const config = require(path.join(projectRoot, 'squeditor.config.js'));
7
+ const micromatch = require(path.join(projectRoot, 'node_modules/micromatch'));
8
+
9
+ let sharp;
10
+ try {
11
+ sharp = require(path.join(projectRoot, 'node_modules/sharp'));
12
+ } catch (e) { }
13
+
14
+ let ffmpeg;
15
+ try {
16
+ ffmpeg = require(path.join(projectRoot, 'node_modules/fluent-ffmpeg'));
17
+ } catch (e) { }
18
+
19
+ const customerBuildDir = path.join(projectRoot, 'build-customer-package');
20
+ const distDir = path.join(projectRoot, 'dist');
21
+ const srcDir = path.join(projectRoot, 'src');
22
+ const zipName = config.dist.zipName ? config.dist.zipName.replace('.zip', '-customer.zip') : 'customer-package.zip';
23
+ const zipPath = path.join(projectRoot, zipName);
24
+
25
+ const mediaConfig = config.media || {};
26
+ const blurConfig = mediaConfig.blur || { enabled: false };
27
+ const optConfig = mediaConfig.optimize || { enabled: false };
28
+
29
+ async function processMediaFile(src, dest, relPath) {
30
+ const isBlurEnabled = blurConfig.enabled;
31
+ const isMatched = isBlurEnabled && micromatch.isMatch(relPath, blurConfig.include || [], { ignore: blurConfig.exclude || [] });
32
+
33
+ const ext = path.extname(src).toLowerCase();
34
+ const isImage = ['.jpg', '.jpeg', '.png', '.webp', '.gif'].includes(ext);
35
+ const isVideo = ['.mp4', '.webm'].includes(ext);
36
+
37
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
38
+
39
+ if (isMatched) {
40
+ if (isImage && sharp) {
41
+ try {
42
+ console.log(` - Blurring image: ${relPath}`);
43
+ let pipeline = sharp(src).blur(blurConfig.amount || 20);
44
+
45
+ // Re-apply optimization settings to the blurred output to prevent size bloat
46
+ const quality = optConfig.imageQuality || 80;
47
+ if (ext === '.jpg' || ext === '.jpeg') {
48
+ pipeline = pipeline.jpeg({ quality, mozjpeg: true });
49
+ } else if (ext === '.png') {
50
+ pipeline = pipeline.png({ quality, compressionLevel: 9, palette: true });
51
+ } else if (ext === '.webp') {
52
+ pipeline = pipeline.webp({ quality, effort: 6 });
53
+ }
54
+
55
+ await pipeline.toFile(dest);
56
+ return;
57
+ } catch (err) {
58
+ console.error(` - ❌ Failed to blur image ${relPath}:`, err.message);
59
+ }
60
+ } else if (isVideo && ffmpeg) {
61
+ try {
62
+ console.log(` - Blurring video: ${relPath} (This may take a while...)`);
63
+ return new Promise((resolve, reject) => {
64
+ ffmpeg(src)
65
+ .videoFilters(`boxblur=${blurConfig.amount || 20}:1`)
66
+ .outputOptions([
67
+ `-crf ${optConfig.videoQuality || 28}`,
68
+ '-preset slower',
69
+ '-c:a aac',
70
+ '-b:a 128k'
71
+ ])
72
+ .save(dest)
73
+ .on('end', () => {
74
+ // Video regression check
75
+ const originalSize = fs.statSync(src).size;
76
+ const optimizedSize = fs.statSync(dest).size;
77
+ if (optimizedSize > originalSize) {
78
+ fs.copyFileSync(src, dest);
79
+ }
80
+ resolve();
81
+ })
82
+ .on('error', (err) => {
83
+ console.error(` - ❌ Failed to blur video ${relPath}:`, err.message);
84
+ fs.copyFileSync(src, dest);
85
+ resolve();
86
+ });
87
+ });
88
+ } catch (err) {
89
+ console.error(` - ❌ Video blur error for ${relPath}:`, err.message);
90
+ }
91
+ }
92
+ }
93
+
94
+ // Default: Just copy
95
+ fs.copyFileSync(src, dest);
96
+ }
97
+
98
+ async function walkAndProcessMedia(currentSrc, currentDest, baseDir) {
99
+ const files = fs.readdirSync(currentSrc);
100
+ for (const file of files) {
101
+ if (file === '.DS_Store') continue;
102
+
103
+ const srcPath = path.join(currentSrc, file);
104
+ const destPath = path.join(currentDest, file);
105
+ const relPath = path.relative(baseDir, srcPath);
106
+ const stat = fs.statSync(srcPath);
107
+
108
+ if (stat.isDirectory()) {
109
+ await walkAndProcessMedia(srcPath, destPath, baseDir);
110
+ } else {
111
+ await processMediaFile(srcPath, destPath, relPath);
112
+ }
113
+ }
114
+ }
115
+
116
+ async function createCustomerPackage() {
117
+ console.log('[Squeditor] 📦 Assembling Customer Package...');
118
+
119
+ if (fs.existsSync(customerBuildDir)) {
120
+ try {
121
+ execSync(`rm -rf "${customerBuildDir}"`);
122
+ } catch (e) {
123
+ console.warn(` - ⚠️ Warning: Could not fully clean ${customerBuildDir}. You may need to run 'sudo rm -rf' on it manually.`);
124
+ }
125
+ }
126
+ if (fs.existsSync(zipPath)) {
127
+ fs.unlinkSync(zipPath);
128
+ }
129
+
130
+ fs.mkdirSync(path.join(customerBuildDir, 'src/assets'), { recursive: true });
131
+
132
+ console.log(' - Copying compiled HTML to src/');
133
+ const distHtmlFiles = fs.readdirSync(distDir).filter(file => file.endsWith('.html'));
134
+
135
+ distHtmlFiles.forEach(file => {
136
+ let htmlContent = fs.readFileSync(path.join(distDir, file), 'utf8');
137
+ htmlContent = htmlContent.replace(
138
+ /<link rel="stylesheet" href="assets\/css\/main_css\.css">/g,
139
+ '<link rel="stylesheet" href="assets/scss/main.scss">'
140
+ );
141
+ htmlContent = htmlContent.replace(
142
+ /<script src="assets\/js\/uikit-components\.js"><\/script>/g,
143
+ '<script type="module" src="assets/js/uikit-components.js"></script>'
144
+ );
145
+ fs.writeFileSync(path.join(customerBuildDir, 'src', file), htmlContent);
146
+ });
147
+
148
+ console.log(' - Copying necessary assets to src/assets');
149
+ fs.mkdirSync(path.join(customerBuildDir, 'src/assets/css'), { recursive: true });
150
+ fs.copyFileSync(path.join(distDir, 'assets/css/tailwind.css'), path.join(customerBuildDir, 'src/assets/css/tailwind.css'));
151
+ if (fs.existsSync(path.join(distDir, 'assets/css/squeditor-icons.css'))) {
152
+ fs.copyFileSync(path.join(distDir, 'assets/css/squeditor-icons.css'), path.join(customerBuildDir, 'src/assets/css/squeditor-icons.css'));
153
+ }
154
+
155
+ fs.cpSync(path.join(srcDir, 'assets/scss'), path.join(customerBuildDir, 'src/assets/scss'), { recursive: true });
156
+
157
+ const customerScssDir = path.join(customerBuildDir, 'src/assets/scss');
158
+ const mainScssPath = path.join(customerScssDir, 'main.scss');
159
+ if (fs.existsSync(mainScssPath)) {
160
+ let mainScssContent = fs.readFileSync(mainScssPath, 'utf8');
161
+ mainScssContent = mainScssContent.replace(/@import\s+['"]custom['"];\n?/g, '');
162
+ mainScssContent += `\n// --- CUSTOM SCRIPTS --- (These run last to override themes block cleanly)\n@import 'custom-config';\n@import 'custom';\n`;
163
+ fs.writeFileSync(mainScssPath, mainScssContent);
164
+
165
+ const customConfigContent = `// custom-config.scss\n// Define your theme variable overrides here.\n:root, [class*="theme-"] {\n // --sq-color-primary: #ff0000;\n}\n`;
166
+ fs.writeFileSync(path.join(customerScssDir, 'custom-config.scss'), customConfigContent);
167
+ }
168
+
169
+ fs.mkdirSync(path.join(customerBuildDir, 'src/assets/js'), { recursive: true });
170
+ fs.copyFileSync(path.join(distDir, 'assets/js/uikit-components.js'), path.join(customerBuildDir, 'src/assets/js/uikit-components.js'));
171
+ fs.copyFileSync(path.join(distDir, 'assets/js/main.js'), path.join(customerBuildDir, 'src/assets/js/main.js'));
172
+
173
+ const staticDistPath = path.join(distDir, 'assets/static');
174
+ const staticCustomerPath = path.join(customerBuildDir, 'src/assets/static');
175
+ if (fs.existsSync(staticDistPath)) {
176
+ await walkAndProcessMedia(staticDistPath, staticCustomerPath, staticDistPath);
177
+ }
178
+
179
+ console.log(' - Generating lean package.json and vite.config.js');
180
+ const originalPkg = require(path.join(projectRoot, 'package.json'));
181
+ const customerPkg = {
182
+ name: originalPkg.name,
183
+ private: true,
184
+ scripts: {
185
+ "dev": "vite",
186
+ "build": "vite build",
187
+ "preview": "vite preview",
188
+ "format": "prettier --write \"src/**/*.html\""
189
+ },
190
+ dependencies: {
191
+ "uikit": originalPkg.dependencies.uikit
192
+ },
193
+ devDependencies: {
194
+ "sass": originalPkg.devDependencies.sass,
195
+ "vite": originalPkg.devDependencies.vite,
196
+ "tailwindcss": originalPkg.devDependencies.tailwindcss,
197
+ "autoprefixer": originalPkg.devDependencies.autoprefixer,
198
+ "prettier": originalPkg.devDependencies.prettier || "^3.0.0"
199
+ }
200
+ };
201
+ fs.writeFileSync(path.join(customerBuildDir, 'package.json'), JSON.stringify(customerPkg, null, 2));
202
+
203
+ const prettierrcContent = `{\n "printWidth": 10000,\n "tabWidth": 4,\n "useTabs": false,\n "semi": true,\n "singleQuote": true,\n "trailingComma": "es5",\n "bracketSpacing": true,\n "htmlWhitespaceSensitivity": "ignore"\n}`;
204
+ fs.writeFileSync(path.join(customerBuildDir, '.prettierrc'), prettierrcContent);
205
+
206
+ let rollupInputs = '';
207
+ distHtmlFiles.forEach(file => {
208
+ const name = file.replace('.html', '');
209
+ rollupInputs += ` '${name}': path.resolve(__dirname, 'src/${file}'),\n`;
210
+ });
211
+
212
+ const viteConfigContent = `import { defineConfig } from 'vite';
213
+ import path from 'path';
214
+
215
+ export default defineConfig({
216
+ root: 'src',
217
+ base: './',
218
+ publicDir: false,
219
+ server: {
220
+ host: '127.0.0.1',
221
+ port: 5173,
222
+ strictPort: false,
223
+ },
224
+ build: {
225
+ outDir: path.resolve(__dirname, 'dist'),
226
+ emptyOutDir: true,
227
+ rollupOptions: {
228
+ input: {
229
+ ${rollupInputs} },
230
+ output: {
231
+ entryFileNames: 'assets/js/[name]-[hash].js',
232
+ assetFileNames: (assetInfo) => {
233
+ const name = assetInfo.names ? assetInfo.names[0] : assetInfo.name;
234
+ if (name.endsWith('.css')) return 'assets/css/[name]-[hash][extname]';
235
+ return 'assets/[name]-[hash][extname]';
236
+ },
237
+ },
238
+ },
239
+ },
240
+ css: {
241
+ preprocessorOptions: {
242
+ scss: {
243
+ api: 'modern-compiler',
244
+ silenceDeprecations: ['import', 'legacy-js-api'],
245
+ additionalData: '@import "/assets/scss/_tokens.scss";',
246
+ },
247
+ },
248
+ },
249
+ });
250
+ `;
251
+ fs.writeFileSync(path.join(customerBuildDir, 'vite.config.js'), viteConfigContent);
252
+
253
+ const tailwindConfig = `/** @type {import('tailwindcss').Config} */\nmodule.exports = {\n darkMode: ['selector', '.sq-theme-dark'],\n content: [ './src/**/*.html' ],\n theme: {\n extend: {\n colors: {\n primary: 'var(--sq-color-primary)',\n secondary: 'var(--sq-color-secondary)',\n accent: 'var(--sq-color-accent)',\n },\n fontFamily: {\n sans: ['var(--sq-font-sans)'],\n serif: ['var(--sq-font-serif)'],\n mono: ['var(--sq-font-mono)'],\n },\n },\n },\n plugins: [],\n}`;
254
+ fs.writeFileSync(path.join(customerBuildDir, 'tailwind.config.js'), tailwindConfig);
255
+
256
+ const postcssConfig = `module.exports = {\n plugins: {\n tailwindcss: {},\n autoprefixer: {},\n },\n}`;
257
+ fs.writeFileSync(path.join(customerBuildDir, 'postcss.config.js'), postcssConfig);
258
+
259
+ const readmeContent = `# ${config.name} - Customer Package\n\nThis package contains everything you need to use, customize, and deploy your template.\n\n## Directory Structure\n- \`src/\`: Source HTML files.\n- \`src/assets/\`: Source files for customization (SCSS, JS, Images).\n\n## How to Customize Styles\n1. Install dependencies: \`npm install\`\n2. Run live development server: \`npm run dev\`\n3. Build production assets: \`npm run build\`\n`;
260
+ fs.writeFileSync(path.join(customerBuildDir, 'README.md'), readmeContent);
261
+
262
+ try {
263
+ console.log(' - Formatting customer HTML files with Prettier...');
264
+ execSync('npx prettier --write "src/**/*.html" --print-width 10000 --tab-width 4', { cwd: customerBuildDir, stdio: 'ignore' });
265
+ } catch (e) { }
266
+
267
+ console.log(`[Squeditor] 📦 Zipping Customer Package to ${zipName}...`);
268
+ try {
269
+ // Explicitly clean up any .DS_Store files that might have been created by the OS
270
+ execSync(`find "${customerBuildDir}" -name ".DS_Store" -delete`, { stdio: 'ignore' });
271
+ execSync(`cd "${customerBuildDir}" && zip -r -9 "${zipPath}" . -x "*.DS_Store" -x "*/.DS_Store"`, { stdio: 'ignore' });
272
+ console.log(`[Squeditor] ✅ ZIP created: ${zipName}`);
273
+ } catch (e) {
274
+ console.error(`[Squeditor] ❌ Failed to create ZIP archive.`, e.message);
275
+ }
276
+ }
277
+
278
+ createCustomerPackage();
@@ -0,0 +1,54 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { execSync } = require('child_process');
4
+
5
+ const projectRoot = process.cwd();
6
+ const config = require(path.join(projectRoot, 'squeditor.config.js'));
7
+
8
+ const distDir = path.join(projectRoot, 'dist');
9
+ const zipName = config.dist.zipName || 'squeditor-dist.zip';
10
+ const zipPath = path.join(projectRoot, zipName);
11
+
12
+ // Clean up existing ZIP
13
+ if (fs.existsSync(zipPath)) {
14
+ fs.unlinkSync(zipPath);
15
+ }
16
+
17
+ // 1. Create ZIP using system zip command
18
+ console.log('[Squeditor] 📦 Creating ZIP archive...');
19
+ try {
20
+ // Explicitly clean up any .DS_Store files that might have been created by the OS
21
+ execSync(`find "${distDir}" -name ".DS_Store" -delete`, { stdio: 'inherit' });
22
+ execSync(`cd "${distDir}" && zip -r -9 "${zipPath}" . -x "*.DS_Store" -x "*/.DS_Store"`, { stdio: 'inherit' });
23
+ console.log(`[Squeditor] ✅ Customer Ready: ${zipName}`);
24
+ } catch (e) {
25
+ console.error(`[Squeditor] ❌ Failed to create ZIP archive using system zip.`, e);
26
+ }
27
+
28
+ const requiredDirs = ['assets/css', 'assets/js', 'assets/static/images'];
29
+ requiredDirs.forEach(dir => {
30
+ if (!fs.existsSync(path.join(distDir, dir))) {
31
+ console.warn(`[Squeditor] ⚠️ Missing in dist: ${dir} — please verify build integrity.`);
32
+ }
33
+ });
34
+
35
+ // 2. Deploy preview
36
+ function deployPreview() {
37
+ const platform = config.dist.previewPlatform;
38
+ if (!platform) return; // Skip deployment if none configured
39
+ console.log(`[Squeditor] 🚀 Deploying preview to ${platform}...`);
40
+
41
+ if (platform === 'netlify') {
42
+ try {
43
+ execSync('netlify deploy --dir=dist', { stdio: 'inherit', cwd: projectRoot }); // removed --open for headless
44
+ } catch (e) { console.error("[Squeditor] Failed to deploy netlify (you may need to login or install CLI)", e.message); }
45
+ } else if (platform === 'vercel') {
46
+ try {
47
+ execSync('vercel dist --yes', { stdio: 'inherit', cwd: projectRoot });
48
+ } catch (e) { console.error("[Squeditor] Failed to deploy vercel", e); }
49
+ } else {
50
+ console.log('[Squeditor] ⚠️ Unknown preview platform. Set config.dist.previewPlatform to "netlify" or "vercel".');
51
+ }
52
+ }
53
+
54
+ deployPreview();
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env node
2
+ // scripts/scaffold.js
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ const args = process.argv.slice(2);
7
+ const projectName = args[0];
8
+
9
+ if (!projectName) {
10
+ console.error("Usage: node scripts/scaffold.js <project-name> --dest <optional-dest>");
11
+ process.exit(1);
12
+ }
13
+
14
+ let destIndex = args.indexOf('--dest');
15
+ let destPath = destIndex !== -1 ? args[destIndex + 1] : projectName;
16
+ const sourceDir = path.join(__dirname, '..', 'project-template');
17
+ const targetDir = path.resolve(process.cwd(), destPath);
18
+
19
+ if (!fs.existsSync(sourceDir)) {
20
+ console.error(`Source template directory does not exist: ${sourceDir}`);
21
+ process.exit(1);
22
+ }
23
+
24
+ if (fs.existsSync(targetDir)) {
25
+ console.error(`Target directory already exists: ${targetDir}`);
26
+ process.exit(1);
27
+ }
28
+
29
+ function copyDirectory(src, dest) {
30
+ fs.mkdirSync(dest, { recursive: true });
31
+ const entries = fs.readdirSync(src, { withFileTypes: true });
32
+
33
+ for (const entry of entries) {
34
+ if (entry.name === '.DS_Store') continue;
35
+ const srcPath = path.join(src, entry.name);
36
+ const destPath = path.join(dest, entry.name);
37
+
38
+ if (entry.isDirectory()) {
39
+ copyDirectory(srcPath, destPath);
40
+ } else {
41
+ // Must use copyFileSync to preserve binary data (fonts, images)
42
+ fs.copyFileSync(srcPath, destPath);
43
+ }
44
+ }
45
+ }
46
+
47
+ console.log(`[Squeditor] Scaffolding new project: ${projectName}...`);
48
+ copyDirectory(sourceDir, targetDir);
49
+
50
+ // Update package.json name
51
+ const pkgJsonPath = path.join(targetDir, 'package.json');
52
+ if (fs.existsSync(pkgJsonPath)) {
53
+ const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'));
54
+ pkg.name = projectName;
55
+ fs.writeFileSync(pkgJsonPath, JSON.stringify(pkg, null, 2));
56
+ }
57
+
58
+ // Update squeditor.config.js properties
59
+ const configPath = path.join(targetDir, 'squeditor.config.js');
60
+ if (fs.existsSync(configPath)) {
61
+ let configContent = fs.readFileSync(configPath, 'utf8');
62
+ configContent = configContent.replace(/name:\s*['"][^'"]+['"]/, `name: '${projectName}'`);
63
+ configContent = configContent.replace(/zipName:\s*['"][^'"]+['"]/, `zipName: '${projectName}.zip'`);
64
+ fs.writeFileSync(configPath, configContent);
65
+ }
66
+
67
+ console.log(`[Squeditor] ✅ Scaffolded ${projectName} successfully at ${targetDir}`);
68
+ console.log(`Next steps:`);
69
+ console.log(` cd ${destPath}`);
70
+ console.log(` npm install`);
71
+ console.log(` npm run dev`);
72
+ console.log(` Note: For mac, use sudo if you get permission errors!`);