@squeditor/squeditor-framework 1.0.1 → 1.0.3
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/README.md +7 -15
- package/package.json +1 -1
- package/project-template/package.json +3 -1
- package/project-template/squeditor.config.js +21 -9
- package/project-template/src/assets/js/_slider_dynamic.js +2 -0
- package/project-template/src/assets/js/gsap-init.js +28 -1
- package/project-template/src/assets/js/main.js +1 -0
- package/project-template/src/assets/js/modules/splide-init.js +207 -0
- package/project-template/src/assets/js/modules/swiper-init.js +216 -0
- package/project-template/src/assets/js/uikit-components.js +27 -21
- package/project-template/src/assets/scss/_swiper.scss +30 -0
- package/project-template/src/assets/scss/main.scss +2 -1
- package/project-template/src/assets/scss/themes/_two.scss +95 -0
- package/project-template/src/assets/static/images/og-default.png +0 -0
- package/project-template/src/assets/static/images/placeholder.png +0 -0
- package/project-template/src/index.php +5 -5
- package/project-template/src/init.php +38 -2
- package/project-template/src/page-templates/head.php +9 -1
- package/project-template/src/slider-test.php +87 -0
- package/project-template/src/template-parts/header.php +11 -11
- package/scripts/build-components.js +78 -1
- package/scripts/copy-static.js +26 -0
- package/scripts/dev-router.php +10 -1
- package/scripts/dev.js +30 -1
- package/scripts/package-customer.js +52 -21
- package/scripts/package-dist.js +1 -1
- package/scripts/scaffold.js +18 -5
- package/scripts/snapshot.js +134 -33
- package/scripts/utils/resolve-pages.js +47 -0
|
@@ -16,10 +16,11 @@ try {
|
|
|
16
16
|
ffmpeg = require(path.join(projectRoot, 'node_modules/fluent-ffmpeg'));
|
|
17
17
|
} catch (e) { }
|
|
18
18
|
|
|
19
|
-
const customerBuildDir = path.join(projectRoot, '
|
|
19
|
+
const customerBuildDir = path.join(projectRoot, config.name || 'customer-package');
|
|
20
20
|
const distDir = path.join(projectRoot, 'dist');
|
|
21
21
|
const srcDir = path.join(projectRoot, 'src');
|
|
22
|
-
|
|
22
|
+
// Derive ZIP name from config.name directly to avoid double-suffix issues
|
|
23
|
+
const zipName = (config.name || 'customer-package') + '.zip';
|
|
23
24
|
const zipPath = path.join(projectRoot, zipName);
|
|
24
25
|
|
|
25
26
|
const mediaConfig = config.media || {};
|
|
@@ -116,40 +117,61 @@ async function walkAndProcessMedia(currentSrc, currentDest, baseDir) {
|
|
|
116
117
|
async function createCustomerPackage() {
|
|
117
118
|
console.log('[Squeditor] 📦 Assembling Customer Package...');
|
|
118
119
|
|
|
120
|
+
if (path.resolve(customerBuildDir) === path.resolve(projectRoot)) {
|
|
121
|
+
console.error(`[Squeditor] 🚨 CRITICAL ERROR: customerBuildDir resolves to the active project workspace root!`);
|
|
122
|
+
console.error(`[Squeditor] Aborting immediately to prevent recursive deletion. Please update 'name' in your squeditor.config.js so it does not target the parent folder (e.g., avoid '../folder-name').`);
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
|
|
119
126
|
if (fs.existsSync(customerBuildDir)) {
|
|
120
127
|
try {
|
|
121
|
-
|
|
128
|
+
fs.rmSync(customerBuildDir, { recursive: true, force: true });
|
|
122
129
|
} catch (e) {
|
|
123
|
-
console.warn(` - ⚠️ Warning: Could not fully clean ${customerBuildDir}. You may need to
|
|
130
|
+
console.warn(` - ⚠️ Warning: Could not fully clean ${customerBuildDir}. You may need to remove it manually.`);
|
|
124
131
|
}
|
|
125
132
|
}
|
|
126
133
|
if (fs.existsSync(zipPath)) {
|
|
127
134
|
fs.unlinkSync(zipPath);
|
|
128
135
|
}
|
|
129
136
|
|
|
130
|
-
fs.mkdirSync(path.join(customerBuildDir, '
|
|
137
|
+
fs.mkdirSync(path.join(customerBuildDir, 'dist/assets'), { recursive: true });
|
|
131
138
|
|
|
132
|
-
console.log(' - Copying compiled HTML to src/');
|
|
139
|
+
console.log(' - Copying compiled HTML to dist/ and src/');
|
|
133
140
|
const distHtmlFiles = fs.readdirSync(distDir).filter(file => file.endsWith('.html'));
|
|
141
|
+
fs.mkdirSync(path.join(customerBuildDir, 'src'), { recursive: true });
|
|
134
142
|
|
|
135
143
|
distHtmlFiles.forEach(file => {
|
|
136
144
|
let htmlContent = fs.readFileSync(path.join(distDir, file), 'utf8');
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
);
|
|
145
|
-
fs.writeFileSync(path.join(customerBuildDir, 'src', file), htmlContent);
|
|
145
|
+
// dist/ gets production HTML referencing compiled CSS
|
|
146
|
+
fs.writeFileSync(path.join(customerBuildDir, 'dist', file), htmlContent);
|
|
147
|
+
// src/ gets dev HTML: rewrite CSS refs to raw SCSS for Vite HMR
|
|
148
|
+
let devHtml = htmlContent;
|
|
149
|
+
devHtml = devHtml.replace(/href="assets\/css\/main\.min\.css"/g, 'href="assets/scss/main.scss"');
|
|
150
|
+
devHtml = devHtml.replace(/href="assets\/css\/tailwind\.css"/g, 'href="assets/css/tailwind.css"');
|
|
151
|
+
fs.writeFileSync(path.join(customerBuildDir, 'src', file), devHtml);
|
|
146
152
|
});
|
|
147
153
|
|
|
148
|
-
console.log(' - Copying necessary
|
|
154
|
+
console.log(' - Copying necessary developer source files to src/assets');
|
|
149
155
|
fs.mkdirSync(path.join(customerBuildDir, 'src/assets/css'), { recursive: true });
|
|
156
|
+
// Also copy dist production files
|
|
157
|
+
fs.mkdirSync(path.join(customerBuildDir, 'dist/assets/css'), { recursive: true });
|
|
158
|
+
|
|
150
159
|
fs.copyFileSync(path.join(distDir, 'assets/css/tailwind.css'), path.join(customerBuildDir, 'src/assets/css/tailwind.css'));
|
|
160
|
+
fs.copyFileSync(path.join(distDir, 'assets/css/tailwind.css'), path.join(customerBuildDir, 'dist/assets/css/tailwind.css'));
|
|
161
|
+
|
|
151
162
|
if (fs.existsSync(path.join(distDir, 'assets/css/squeditor-icons.css'))) {
|
|
152
163
|
fs.copyFileSync(path.join(distDir, 'assets/css/squeditor-icons.css'), path.join(customerBuildDir, 'src/assets/css/squeditor-icons.css'));
|
|
164
|
+
fs.copyFileSync(path.join(distDir, 'assets/css/squeditor-icons.css'), path.join(customerBuildDir, 'dist/assets/css/squeditor-icons.css'));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (fs.existsSync(path.join(distDir, 'assets/css/main.min.css'))) {
|
|
168
|
+
fs.copyFileSync(path.join(distDir, 'assets/css/main.min.css'), path.join(customerBuildDir, 'dist/assets/css/main.min.css'));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// slider.min.css contains the slider library CSS (Splide/Swiper)
|
|
172
|
+
if (fs.existsSync(path.join(distDir, 'assets/css/slider.min.css'))) {
|
|
173
|
+
fs.copyFileSync(path.join(distDir, 'assets/css/slider.min.css'), path.join(customerBuildDir, 'src/assets/css/slider.min.css'));
|
|
174
|
+
fs.copyFileSync(path.join(distDir, 'assets/css/slider.min.css'), path.join(customerBuildDir, 'dist/assets/css/slider.min.css'));
|
|
153
175
|
}
|
|
154
176
|
|
|
155
177
|
fs.cpSync(path.join(srcDir, 'assets/scss'), path.join(customerBuildDir, 'src/assets/scss'), { recursive: true });
|
|
@@ -167,13 +189,19 @@ async function createCustomerPackage() {
|
|
|
167
189
|
}
|
|
168
190
|
|
|
169
191
|
fs.mkdirSync(path.join(customerBuildDir, 'src/assets/js'), { recursive: true });
|
|
192
|
+
fs.mkdirSync(path.join(customerBuildDir, 'dist/assets/js'), { recursive: true });
|
|
170
193
|
fs.copyFileSync(path.join(distDir, 'assets/js/uikit-components.js'), path.join(customerBuildDir, 'src/assets/js/uikit-components.js'));
|
|
194
|
+
fs.copyFileSync(path.join(distDir, 'assets/js/uikit-components.js'), path.join(customerBuildDir, 'dist/assets/js/uikit-components.js'));
|
|
171
195
|
fs.copyFileSync(path.join(distDir, 'assets/js/main.js'), path.join(customerBuildDir, 'src/assets/js/main.js'));
|
|
196
|
+
fs.copyFileSync(path.join(distDir, 'assets/js/main.js'), path.join(customerBuildDir, 'dist/assets/js/main.js'));
|
|
172
197
|
|
|
173
198
|
const staticDistPath = path.join(distDir, 'assets/static');
|
|
174
|
-
const
|
|
199
|
+
const staticCustomerSourcePath = path.join(customerBuildDir, 'src/assets/static');
|
|
200
|
+
const staticCustomerDistPath = path.join(customerBuildDir, 'dist/assets/static');
|
|
175
201
|
if (fs.existsSync(staticDistPath)) {
|
|
176
|
-
await walkAndProcessMedia(staticDistPath,
|
|
202
|
+
await walkAndProcessMedia(staticDistPath, staticCustomerSourcePath, staticDistPath);
|
|
203
|
+
// Also move these final processed media assets directly from src/ back to dist/
|
|
204
|
+
fs.cpSync(staticCustomerSourcePath, staticCustomerDistPath, { recursive: true });
|
|
177
205
|
}
|
|
178
206
|
|
|
179
207
|
console.log(' - Generating lean package.json and vite.config.js');
|
|
@@ -256,13 +284,16 @@ ${rollupInputs} },
|
|
|
256
284
|
const postcssConfig = `module.exports = {\n plugins: {\n tailwindcss: {},\n autoprefixer: {},\n },\n}`;
|
|
257
285
|
fs.writeFileSync(path.join(customerBuildDir, 'postcss.config.js'), postcssConfig);
|
|
258
286
|
|
|
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
|
|
287
|
+
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/\`: Developer Source files (\`npm run dev\` needed).\n- \`dist/\`: Production-ready compiled HTML snapshot (Drop into any hosting).\n\n## How to Customize Styles\n1. Install dependencies: \`npm install\`\n2. Run live development server: \`npm run dev\`\n3. Edit styles in \`src/assets/scss/main.scss\`\n4. Build production assets to \`dist/\`: \`npm run build\`\n`;
|
|
260
288
|
fs.writeFileSync(path.join(customerBuildDir, 'README.md'), readmeContent);
|
|
261
289
|
|
|
290
|
+
// Format customer HTML files with Prettier (fallback in case snapshot.js Prettier was skipped)
|
|
262
291
|
try {
|
|
263
292
|
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) {
|
|
293
|
+
execSync('npx prettier --write "src/**/*.html" "dist/**/*.html" --print-width 10000 --tab-width 4', { cwd: customerBuildDir, stdio: 'ignore' });
|
|
294
|
+
} catch (e) {
|
|
295
|
+
console.warn(' - ⚠️ Prettier formatting skipped.');
|
|
296
|
+
}
|
|
266
297
|
|
|
267
298
|
console.log(`[Squeditor] 📦 Zipping Customer Package to ${zipName}...`);
|
|
268
299
|
try {
|
package/scripts/package-dist.js
CHANGED
|
@@ -20,7 +20,7 @@ try {
|
|
|
20
20
|
// Explicitly clean up any .DS_Store files that might have been created by the OS
|
|
21
21
|
execSync(`find "${distDir}" -name ".DS_Store" -delete`, { stdio: 'inherit' });
|
|
22
22
|
execSync(`cd "${distDir}" && zip -r -9 "${zipPath}" . -x "*.DS_Store" -x "*/.DS_Store"`, { stdio: 'inherit' });
|
|
23
|
-
console.log(`[Squeditor] ✅
|
|
23
|
+
console.log(`[Squeditor] ✅ Dist ZIP Ready: ${zipName}`);
|
|
24
24
|
} catch (e) {
|
|
25
25
|
console.error(`[Squeditor] ❌ Failed to create ZIP archive using system zip.`, e);
|
|
26
26
|
}
|
package/scripts/scaffold.js
CHANGED
|
@@ -26,17 +26,17 @@ if (fs.existsSync(targetDir)) {
|
|
|
26
26
|
process.exit(1);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
function copyDirectory(src, dest) {
|
|
29
|
+
function copyDirectory(src, dest, ignoreList = []) {
|
|
30
30
|
fs.mkdirSync(dest, { recursive: true });
|
|
31
31
|
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
32
32
|
|
|
33
33
|
for (const entry of entries) {
|
|
34
|
-
if (entry.name === '.DS_Store') continue;
|
|
34
|
+
if (entry.name === '.DS_Store' || ignoreList.includes(entry.name)) continue;
|
|
35
35
|
const srcPath = path.join(src, entry.name);
|
|
36
36
|
const destPath = path.join(dest, entry.name);
|
|
37
37
|
|
|
38
38
|
if (entry.isDirectory()) {
|
|
39
|
-
copyDirectory(srcPath, destPath);
|
|
39
|
+
copyDirectory(srcPath, destPath, ignoreList);
|
|
40
40
|
} else {
|
|
41
41
|
// Must use copyFileSync to preserve binary data (fonts, images)
|
|
42
42
|
fs.copyFileSync(srcPath, destPath);
|
|
@@ -45,6 +45,19 @@ function copyDirectory(src, dest) {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
console.log(`[Squeditor] Scaffolding new project: ${projectName}...`);
|
|
48
|
+
|
|
49
|
+
// 1. Copy the framework core
|
|
50
|
+
const frameworkSourceDir = path.join(__dirname, '..');
|
|
51
|
+
const frameworkTargetDir = path.resolve(process.cwd(), 'squeditor-framework');
|
|
52
|
+
|
|
53
|
+
if (!fs.existsSync(frameworkTargetDir)) {
|
|
54
|
+
console.log(`[Squeditor] Installing local framework core at ./squeditor-framework...`);
|
|
55
|
+
// Pass the name of the target directory to the ignore list to prevent infinite loop
|
|
56
|
+
const ignoreCoreList = ['project-template', 'showcase', 'node_modules', '.git', '.github', 'squeditor-framework'];
|
|
57
|
+
copyDirectory(frameworkSourceDir, frameworkTargetDir, ignoreCoreList);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 2. Copy the project template
|
|
48
61
|
copyDirectory(sourceDir, targetDir);
|
|
49
62
|
|
|
50
63
|
// Update package.json name
|
|
@@ -59,8 +72,8 @@ if (fs.existsSync(pkgJsonPath)) {
|
|
|
59
72
|
const configPath = path.join(targetDir, 'squeditor.config.js');
|
|
60
73
|
if (fs.existsSync(configPath)) {
|
|
61
74
|
let configContent = fs.readFileSync(configPath, 'utf8');
|
|
62
|
-
configContent = configContent.replace(/name:\s*['"][^'"]+['"]/, `name: '${projectName}'`);
|
|
63
|
-
configContent = configContent.replace(/zipName:\s*['"][^'"]+['"]/, `zipName: '${projectName}.zip'`);
|
|
75
|
+
configContent = configContent.replace(/name:\s*['"][^'"]+['"]/, `name: '${projectName}-customer'`);
|
|
76
|
+
configContent = configContent.replace(/zipName:\s*['"][^'"]+['"]/, `zipName: '${projectName}-customer.zip'`);
|
|
64
77
|
fs.writeFileSync(configPath, configContent);
|
|
65
78
|
}
|
|
66
79
|
|
package/scripts/snapshot.js
CHANGED
|
@@ -1,49 +1,114 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const http = require('http');
|
|
4
|
-
const { spawn } = require('child_process');
|
|
4
|
+
const { spawn, execSync } = require('child_process');
|
|
5
5
|
|
|
6
6
|
const projectRoot = process.cwd();
|
|
7
7
|
const config = require(path.join(projectRoot, 'squeditor.config.js'));
|
|
8
|
+
const resolvePages = require('./utils/resolve-pages');
|
|
9
|
+
const getAvailablePort = require('./get-port');
|
|
8
10
|
|
|
9
|
-
const {
|
|
11
|
+
const { outputDir, rewriteExtension } = config.snapshot;
|
|
10
12
|
const distDir = path.join(projectRoot, outputDir);
|
|
11
13
|
|
|
12
|
-
|
|
14
|
+
// Build a reverse lookup: pagePath -> themeKey
|
|
15
|
+
// This allows snapshot.pages to be the AUTHORITATIVE list while themes are applied per-page
|
|
16
|
+
function buildThemeLookup() {
|
|
17
|
+
const lookup = {};
|
|
18
|
+
if (config.themes) {
|
|
19
|
+
for (const [themeKey, theme] of Object.entries(config.themes)) {
|
|
20
|
+
const resolvedThemePages = resolvePages(theme.pages || [], projectRoot);
|
|
21
|
+
for (const p of resolvedThemePages) {
|
|
22
|
+
lookup[p] = { themeKey, distSubfolder: theme.distSubfolder || '' };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return lookup;
|
|
27
|
+
}
|
|
13
28
|
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
})
|
|
29
|
+
// Poll the PHP server until it responds (max ~10 seconds)
|
|
30
|
+
function waitForServer(url, maxRetries = 50, interval = 200) {
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
let attempts = 0;
|
|
33
|
+
const check = () => {
|
|
34
|
+
http.get(url, (res) => {
|
|
35
|
+
resolve();
|
|
36
|
+
}).on('error', () => {
|
|
37
|
+
attempts++;
|
|
38
|
+
if (attempts >= maxRetries) {
|
|
39
|
+
reject(new Error('PHP server failed to start within timeout'));
|
|
40
|
+
} else {
|
|
41
|
+
setTimeout(check, interval);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
check();
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function runSnapshot() {
|
|
50
|
+
fs.mkdirSync(distDir, { recursive: true });
|
|
22
51
|
|
|
23
|
-
//
|
|
24
|
-
|
|
25
|
-
|
|
52
|
+
// Use a dynamic port to avoid collisions with a running dev server
|
|
53
|
+
const snapshotPort = await getAvailablePort(config.devServer.port + 100);
|
|
54
|
+
const snapshotBaseUrl = `http://127.0.0.1:${snapshotPort}`;
|
|
26
55
|
|
|
27
|
-
|
|
56
|
+
// Resolve dev-router path for proper .html and extensionless URL handling
|
|
57
|
+
const fwRoot = path.resolve(projectRoot, config.framework);
|
|
58
|
+
const devRouterPath = path.join(fwRoot, 'scripts/dev-router.php');
|
|
59
|
+
|
|
60
|
+
// Start PHP built-in server WITH the dev-router
|
|
61
|
+
const phpServer = spawn('php', [
|
|
62
|
+
'-S', `127.0.0.1:${snapshotPort}`,
|
|
63
|
+
'-t', path.join(projectRoot, 'src'),
|
|
64
|
+
devRouterPath
|
|
65
|
+
], {
|
|
66
|
+
stdio: 'ignore',
|
|
67
|
+
env: { ...process.env, SQUEDITOR_SNAPSHOT: '1' }
|
|
68
|
+
});
|
|
28
69
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
70
|
+
try {
|
|
71
|
+
// Wait for PHP server to be ready instead of a fixed timeout
|
|
72
|
+
await waitForServer(snapshotBaseUrl);
|
|
32
73
|
|
|
33
|
-
|
|
74
|
+
console.log('[Squeditor] 📸 Starting snapshot...');
|
|
75
|
+
|
|
76
|
+
// snapshot.pages is the AUTHORITATIVE list of pages to capture
|
|
77
|
+
const allPages = resolvePages(config.snapshot.pages || ['*'], projectRoot);
|
|
78
|
+
const themeLookup = buildThemeLookup();
|
|
79
|
+
|
|
80
|
+
for (const pagePath of allPages) {
|
|
34
81
|
try {
|
|
82
|
+
// Determine which theme applies to this page (default if not in any theme)
|
|
83
|
+
const themeInfo = themeLookup[pagePath] || { themeKey: 'default', distSubfolder: '' };
|
|
84
|
+
const { themeKey, distSubfolder } = themeInfo;
|
|
85
|
+
|
|
35
86
|
console.log(`[Squeditor] Fetching ${pagePath} (Theme: ${themeKey})...`);
|
|
36
|
-
|
|
87
|
+
|
|
88
|
+
// Resolve fetch URI: '/' -> '/index.php'
|
|
89
|
+
let fetchUri = pagePath;
|
|
90
|
+
if (fetchUri === '/') fetchUri = '/index.php';
|
|
91
|
+
|
|
92
|
+
const normalizedPagePath = fetchUri.startsWith('/') ? fetchUri : `/${fetchUri}`;
|
|
93
|
+
const urlToFetch = `${snapshotBaseUrl}${normalizedPagePath}?theme=${themeKey}&snapshot=1`;
|
|
37
94
|
const html = await fetchPage(urlToFetch);
|
|
38
|
-
const rewrittenHtml = rewriteLinks(html);
|
|
39
95
|
|
|
40
|
-
//
|
|
41
|
-
let savePath = pagePath
|
|
42
|
-
if (savePath === ''
|
|
43
|
-
|
|
44
|
-
|
|
96
|
+
// Construct save path
|
|
97
|
+
let savePath = pagePath;
|
|
98
|
+
if (savePath === '/' || savePath === '') {
|
|
99
|
+
savePath = 'index.html';
|
|
100
|
+
} else {
|
|
101
|
+
savePath = savePath.startsWith('/') ? savePath.slice(1) : savePath;
|
|
102
|
+
if (rewriteExtension && savePath.endsWith('.php')) {
|
|
103
|
+
savePath = savePath.replace(/\.php$/, '.html');
|
|
104
|
+
}
|
|
45
105
|
}
|
|
46
106
|
|
|
107
|
+
const themeDistDir = path.join(distDir, distSubfolder);
|
|
108
|
+
fs.mkdirSync(themeDistDir, { recursive: true });
|
|
109
|
+
|
|
110
|
+
const rewrittenHtml = rewriteLinks(html, savePath, distSubfolder);
|
|
111
|
+
|
|
47
112
|
const fullPath = path.join(themeDistDir, savePath);
|
|
48
113
|
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
49
114
|
fs.writeFileSync(fullPath, rewrittenHtml);
|
|
@@ -51,15 +116,32 @@ setTimeout(async () => {
|
|
|
51
116
|
console.error(`[Squeditor] Failed to snapshot ${pagePath}:`, err.message);
|
|
52
117
|
}
|
|
53
118
|
}
|
|
119
|
+
|
|
120
|
+
// Format all generated HTML with Prettier
|
|
121
|
+
try {
|
|
122
|
+
console.log('[Squeditor] 💅 Formatting dist HTML with Prettier...');
|
|
123
|
+
execSync(`npx prettier --write "${outputDir}/**/*.html" --print-width 10000 --tab-width 4`, { cwd: projectRoot, stdio: 'ignore' });
|
|
124
|
+
} catch (e) {
|
|
125
|
+
console.warn('[Squeditor] ⚠️ Prettier formatting skipped (not installed or errored).');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
console.log('[Squeditor] 🏁 Snapshot complete.');
|
|
129
|
+
} finally {
|
|
130
|
+
phpServer.kill();
|
|
54
131
|
}
|
|
132
|
+
}
|
|
55
133
|
|
|
56
|
-
|
|
57
|
-
console.
|
|
58
|
-
|
|
134
|
+
runSnapshot().catch(err => {
|
|
135
|
+
console.error('[Squeditor] ❌ Snapshot failed:', err.message);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
});
|
|
59
138
|
|
|
60
139
|
function fetchPage(url) {
|
|
61
140
|
return new Promise((resolve, reject) => {
|
|
62
141
|
http.get(url, (res) => {
|
|
142
|
+
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
143
|
+
return reject(new Error(`Status Code: ${res.statusCode} for ${url}`));
|
|
144
|
+
}
|
|
63
145
|
let data = '';
|
|
64
146
|
res.on('data', chunk => data += chunk);
|
|
65
147
|
res.on('end', () => resolve(data));
|
|
@@ -67,8 +149,27 @@ function fetchPage(url) {
|
|
|
67
149
|
});
|
|
68
150
|
}
|
|
69
151
|
|
|
70
|
-
function rewriteLinks(html) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
152
|
+
function rewriteLinks(html, savePath, distSubfolder) {
|
|
153
|
+
let result = html;
|
|
154
|
+
|
|
155
|
+
// Calculate relative path back to dist root
|
|
156
|
+
const subfolderDepth = distSubfolder ? distSubfolder.split('/').filter(Boolean).length : 0;
|
|
157
|
+
const savePathDepth = path.dirname(savePath) === '.' ? 0 : path.dirname(savePath).split('/').length;
|
|
158
|
+
|
|
159
|
+
const totalDepth = subfolderDepth + savePathDepth;
|
|
160
|
+
const prefix = totalDepth > 0 ? '../'.repeat(totalDepth) : '';
|
|
161
|
+
|
|
162
|
+
// Rewrite root-relative and purely relative references to depth-adjusted references
|
|
163
|
+
result = result.replace(/(href|src)=["']\/?([^"']+)["']/g, (match, attr, targetPath) => {
|
|
164
|
+
if (targetPath.startsWith('http') || targetPath.startsWith('//') || targetPath.startsWith('#')) {
|
|
165
|
+
return match;
|
|
166
|
+
}
|
|
167
|
+
return `${attr}="${prefix}${targetPath}"`;
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
if (rewriteExtension) {
|
|
171
|
+
result = result.replace(/href=["']([^"']*?)\.php([^"']*?)["']/g, 'href="$1.html$2"');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return result;
|
|
74
175
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
module.exports = function resolvePages(pagesConfig, projectRoot) {
|
|
5
|
+
if (!pagesConfig || !Array.isArray(pagesConfig)) return [];
|
|
6
|
+
|
|
7
|
+
// Check if any element has a glob (* or !)
|
|
8
|
+
const hasGlob = pagesConfig.some(p => p.includes('*') || p.startsWith('!'));
|
|
9
|
+
|
|
10
|
+
if (hasGlob) {
|
|
11
|
+
let micromatch;
|
|
12
|
+
try {
|
|
13
|
+
micromatch = require(path.join(projectRoot, 'node_modules/micromatch'));
|
|
14
|
+
} catch (e) {
|
|
15
|
+
console.warn('[Squeditor] micromatch not found. Please run npm install.');
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const srcDir = path.join(projectRoot, 'src');
|
|
20
|
+
if (!fs.existsSync(srcDir)) return [];
|
|
21
|
+
|
|
22
|
+
// Exclude known non-page PHP files that should never be snapshotted
|
|
23
|
+
const NON_PAGE_FILES = ['init.php'];
|
|
24
|
+
const allFiles = fs.readdirSync(srcDir)
|
|
25
|
+
.filter(f => f.endsWith('.php') && !NON_PAGE_FILES.includes(f));
|
|
26
|
+
|
|
27
|
+
// Micromatch expects naked filenames logically.
|
|
28
|
+
// We ensure config patterns like '/' become 'index.php' for matching,
|
|
29
|
+
// and remove leading slashes so they match naked filenames natively.
|
|
30
|
+
const normalizedConfig = pagesConfig.map(p => {
|
|
31
|
+
if (p === '/') return 'index.php';
|
|
32
|
+
if (p.startsWith('/')) return p.slice(1);
|
|
33
|
+
return p;
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const matched = micromatch(allFiles, normalizedConfig);
|
|
37
|
+
|
|
38
|
+
// Convert back to URL-style paths that the rest of the application expects
|
|
39
|
+
return matched.map(f => f === 'index.php' ? '/' : `/${f}`);
|
|
40
|
+
} else {
|
|
41
|
+
// Direct explicit list (legacy map support)
|
|
42
|
+
return pagesConfig.map(p => {
|
|
43
|
+
if (p === '/') return '/';
|
|
44
|
+
return p.startsWith('/') ? p : `/${p}`;
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
};
|