@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.
- package/LICENSE +21 -0
- package/README.md +106 -0
- package/package.json +36 -0
- package/php/functions.php +92 -0
- package/project-template/package.json +29 -0
- package/project-template/postcss.config.js +6 -0
- package/project-template/squeditor.config.js +81 -0
- package/project-template/src/404.php +21 -0
- package/project-template/src/assets/css/squeditor-icons.css +4719 -0
- package/project-template/src/assets/css/tailwind.css +3 -0
- package/project-template/src/assets/css/uikit-components.css +14586 -0
- package/project-template/src/assets/js/gsap-advanced.js +26 -0
- package/project-template/src/assets/js/gsap-init.js +672 -0
- package/project-template/src/assets/js/gsap-modules/cursor-preview.js +132 -0
- package/project-template/src/assets/js/gsap-modules/cursor.js +456 -0
- package/project-template/src/assets/js/gsap-modules/loop-panels.js +78 -0
- package/project-template/src/assets/js/gsap-modules/marquee.js +106 -0
- package/project-template/src/assets/js/gsap-modules/pinned-panels.js +105 -0
- package/project-template/src/assets/js/gsap-modules/scroll-to.js +54 -0
- package/project-template/src/assets/js/gsap-modules/swipe-slider.js +121 -0
- package/project-template/src/assets/js/gsap-modules/text-mask.js +93 -0
- package/project-template/src/assets/js/gsap-modules/tilt.js +70 -0
- package/project-template/src/assets/js/main.js +302 -0
- package/project-template/src/assets/js/uikit-components.js +18171 -0
- package/project-template/src/assets/scss/_base.scss +140 -0
- package/project-template/src/assets/scss/_components.scss +165 -0
- package/project-template/src/assets/scss/_config.scss +13 -0
- package/project-template/src/assets/scss/_functions.scss +81 -0
- package/project-template/src/assets/scss/_tokens.scss +229 -0
- package/project-template/src/assets/scss/_transitions.scss +36 -0
- package/project-template/src/assets/scss/_uikit-overrides.scss +187 -0
- package/project-template/src/assets/scss/_uikit_dynamic.scss +43 -0
- package/project-template/src/assets/scss/_utilities.scss +31 -0
- package/project-template/src/assets/scss/custom.scss +10 -0
- package/project-template/src/assets/scss/main.scss +11 -0
- package/project-template/src/assets/static/fonts/squeditor-icons/squeditor-icons.eot +0 -0
- package/project-template/src/assets/static/fonts/squeditor-icons/squeditor-icons.svg +1183 -0
- package/project-template/src/assets/static/fonts/squeditor-icons/squeditor-icons.ttf +0 -0
- package/project-template/src/assets/static/fonts/squeditor-icons/squeditor-icons.woff +0 -0
- package/project-template/src/config/site-config.php +34 -0
- package/project-template/src/data/blog.php +21 -0
- package/project-template/src/data/portfolio.php +23 -0
- package/project-template/src/data/team.php +23 -0
- package/project-template/src/index.php +57 -0
- package/project-template/src/init.php +19 -0
- package/project-template/src/page-templates/base.php +39 -0
- package/project-template/src/page-templates/body-scripts.php +26 -0
- package/project-template/src/page-templates/head.php +47 -0
- package/project-template/src/page-templates/transition.php +45 -0
- package/project-template/src/sections/cards/cards-grid.php +34 -0
- package/project-template/src/sections/cards/cards-horizontal.php +28 -0
- package/project-template/src/sections/cta/cta-banner.php +34 -0
- package/project-template/src/sections/cta/cta-newsletter.php +19 -0
- package/project-template/src/sections/footer/layout-01.php +35 -0
- package/project-template/src/sections/header/layout-01.php +36 -0
- package/project-template/src/sections/hero/hero-centered.php +44 -0
- package/project-template/src/sections/hero/hero-split.php +132 -0
- package/project-template/src/sections/hero/hero-video.php +22 -0
- package/project-template/src/sections/sidebar/sidebar-right.php +11 -0
- package/project-template/src/template-parts/breadcrumbs.php +17 -0
- package/project-template/src/template-parts/footer.php +74 -0
- package/project-template/src/template-parts/header.php +120 -0
- package/project-template/src/template-parts/mega-menu.php +7 -0
- package/project-template/src/template-parts/nav.php +16 -0
- package/project-template/src/template-parts/page-title-bar.php +14 -0
- package/project-template/tailwind.config.js +26 -0
- package/project-template/vite.config.js +67 -0
- package/scripts/build-components.js +109 -0
- package/scripts/copy-static.js +150 -0
- package/scripts/dev-router.php +23 -0
- package/scripts/dev.js +55 -0
- package/scripts/get-port.js +27 -0
- package/scripts/package-customer.js +278 -0
- package/scripts/package-dist.js +54 -0
- package/scripts/scaffold.js +72 -0
- package/scripts/snapshot.js +74 -0
- 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!`);
|