aztomiq 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 +146 -0
- package/bin/aztomiq.js +336 -0
- package/bin/create-aztomiq.js +77 -0
- package/package.json +58 -0
- package/scripts/analyze-screenshots.js +217 -0
- package/scripts/build.js +39 -0
- package/scripts/builds/admin.js +17 -0
- package/scripts/builds/assets.js +167 -0
- package/scripts/builds/cache.js +48 -0
- package/scripts/builds/config.js +31 -0
- package/scripts/builds/data.js +210 -0
- package/scripts/builds/pages.js +288 -0
- package/scripts/builds/playground-examples.js +50 -0
- package/scripts/builds/templates.js +118 -0
- package/scripts/builds/utils.js +37 -0
- package/scripts/create-bug-tracker.js +277 -0
- package/scripts/deploy.js +135 -0
- package/scripts/feedback-generator.js +102 -0
- package/scripts/ui-test.js +624 -0
- package/scripts/utils/extract-examples.js +44 -0
- package/scripts/utils/migrate-icons.js +67 -0
- package/src/includes/breadcrumbs.ejs +100 -0
- package/src/includes/cloud-tags.ejs +120 -0
- package/src/includes/footer.ejs +37 -0
- package/src/includes/generator.ejs +226 -0
- package/src/includes/head.ejs +73 -0
- package/src/includes/header-data-only.ejs +43 -0
- package/src/includes/header.ejs +71 -0
- package/src/includes/layout.ejs +68 -0
- package/src/includes/legacy-banner.ejs +19 -0
- package/src/includes/mega-menu.ejs +80 -0
- package/src/includes/schema.ejs +20 -0
- package/src/templates/manifest.json.ejs +30 -0
- package/src/templates/readme-dist.md.ejs +58 -0
- package/src/templates/robots.txt.ejs +4 -0
- package/src/templates/sitemap.xml.ejs +69 -0
- package/src/templates/sw.js.ejs +78 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Screenshot Layout Analyzer
|
|
5
|
+
* Analyzes all screenshots to verify layout consistency
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs').promises;
|
|
9
|
+
const path = require('path');
|
|
10
|
+
|
|
11
|
+
const SCREENSHOTS_DIR = './ui-test-results/screenshots';
|
|
12
|
+
const OUTPUT_FILE = './ui-test-results/layout-analysis.md';
|
|
13
|
+
|
|
14
|
+
async function analyzeScreenshots() {
|
|
15
|
+
console.log('🔍 Analyzing screenshots for layout consistency...\n');
|
|
16
|
+
|
|
17
|
+
const files = await fs.readdir(SCREENSHOTS_DIR);
|
|
18
|
+
const desktopScreenshots = files.filter(f => f.includes('_desktop.png'));
|
|
19
|
+
const mobileScreenshots = files.filter(f => f.includes('_mobile.png'));
|
|
20
|
+
|
|
21
|
+
console.log(`📊 Found ${desktopScreenshots.length} desktop screenshots`);
|
|
22
|
+
console.log(`📱 Found ${mobileScreenshots.length} mobile screenshots\n`);
|
|
23
|
+
|
|
24
|
+
// Group by page type
|
|
25
|
+
const pages = {
|
|
26
|
+
home: [],
|
|
27
|
+
tools: [],
|
|
28
|
+
master: [],
|
|
29
|
+
static: []
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
desktopScreenshots.forEach(file => {
|
|
33
|
+
const name = file.replace('_desktop.png', '');
|
|
34
|
+
|
|
35
|
+
// Remove leading underscores and parse
|
|
36
|
+
const cleanName = name.replace(/^_+/, '');
|
|
37
|
+
|
|
38
|
+
if (cleanName === 'vi_' || cleanName === 'en_' || cleanName === 'vi' || cleanName === 'en') {
|
|
39
|
+
// Homepage
|
|
40
|
+
pages.home.push(name);
|
|
41
|
+
} else if (cleanName.includes('master')) {
|
|
42
|
+
// Master tools
|
|
43
|
+
pages.master.push(name);
|
|
44
|
+
} else if (cleanName.includes('about') || cleanName.includes('privacy') || cleanName.includes('terms') || cleanName.includes('categories')) {
|
|
45
|
+
// Static pages
|
|
46
|
+
pages.static.push(name);
|
|
47
|
+
} else {
|
|
48
|
+
// Regular tools
|
|
49
|
+
pages.tools.push(name);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Generate report
|
|
54
|
+
let report = `# 📸 Layout Analysis Report
|
|
55
|
+
|
|
56
|
+
**Generated**: ${new Date().toLocaleString()}
|
|
57
|
+
**Total Screenshots**: ${desktopScreenshots.length * 2} (desktop + mobile)
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## 📊 SCREENSHOT BREAKDOWN
|
|
62
|
+
|
|
63
|
+
| Category | Count | Files |
|
|
64
|
+
|----------|-------|-------|
|
|
65
|
+
| **Homepage** | ${pages.home.length} | ${pages.home.slice(0, 3).join(', ')}... |
|
|
66
|
+
| **Tools** | ${pages.tools.length} | ${pages.tools.slice(0, 3).join(', ')}... |
|
|
67
|
+
| **Master Tools** | ${pages.master.length} | ${pages.master.join(', ')} |
|
|
68
|
+
| **Static Pages** | ${pages.static.length} | ${pages.static.join(', ')} |
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## 🎯 LAYOUT CONSISTENCY CHECKLIST
|
|
73
|
+
|
|
74
|
+
### Expected Layout (1400px max-width)
|
|
75
|
+
|
|
76
|
+
All pages should show:
|
|
77
|
+
- ✅ Header aligned at 1400px
|
|
78
|
+
- ✅ Content container at 1400px
|
|
79
|
+
- ✅ Clear borders on containers
|
|
80
|
+
- ✅ Consistent spacing (2rem)
|
|
81
|
+
- ✅ No horizontal overflow
|
|
82
|
+
|
|
83
|
+
### Manual Review Required
|
|
84
|
+
|
|
85
|
+
Please review screenshots for:
|
|
86
|
+
|
|
87
|
+
#### 1. **Container Alignment**
|
|
88
|
+
Check that header and content align perfectly:
|
|
89
|
+
\`\`\`
|
|
90
|
+
┌─────────────────────────────────────────────┐
|
|
91
|
+
│ Header (1400px) ← Should align │
|
|
92
|
+
├─────────────────────────────────────────────┤
|
|
93
|
+
│ Content (1400px) ← Should align │
|
|
94
|
+
└─────────────────────────────────────────────┘
|
|
95
|
+
\`\`\`
|
|
96
|
+
|
|
97
|
+
#### 2. **Border Visibility**
|
|
98
|
+
All containers should have:
|
|
99
|
+
- Clear 1px border
|
|
100
|
+
- Border radius (12px)
|
|
101
|
+
- Box shadow
|
|
102
|
+
|
|
103
|
+
#### 3. **Spacing Consistency**
|
|
104
|
+
- 2rem padding inside containers
|
|
105
|
+
- 2rem gap between elements
|
|
106
|
+
- 2rem margin top/bottom
|
|
107
|
+
|
|
108
|
+
#### 4. **Master Tools Layout**
|
|
109
|
+
Should show sidebar + content:
|
|
110
|
+
\`\`\`
|
|
111
|
+
┌────────┐ ┌──────────────┐
|
|
112
|
+
│Sidebar │ │ Content │
|
|
113
|
+
│(250px) │ │ (flex) │
|
|
114
|
+
└────────┘ └──────────────┘
|
|
115
|
+
\`\`\`
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## 📁 SCREENSHOTS TO REVIEW
|
|
120
|
+
|
|
121
|
+
### Homepage (${pages.home.length} screenshots)
|
|
122
|
+
${pages.home.map(name => `- [ ] \`${name}\``).join('\n')}
|
|
123
|
+
|
|
124
|
+
### Tools (${pages.tools.length} screenshots)
|
|
125
|
+
${pages.tools.map(name => `- [ ] \`${name}\``).join('\n')}
|
|
126
|
+
|
|
127
|
+
### Master Tools (${pages.master.length} screenshots)
|
|
128
|
+
${pages.master.map(name => `- [ ] \`${name}\``).join('\n')}
|
|
129
|
+
|
|
130
|
+
### Static Pages (${pages.static.length} screenshots)
|
|
131
|
+
${pages.static.map(name => `- [ ] \`${name}\``).join('\n')}
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## 🔍 REVIEW PROCESS
|
|
136
|
+
|
|
137
|
+
1. **Open screenshots folder**:
|
|
138
|
+
\`\`\`bash
|
|
139
|
+
open ui-test-results/screenshots/
|
|
140
|
+
\`\`\`
|
|
141
|
+
|
|
142
|
+
2. **Compare desktop vs mobile**:
|
|
143
|
+
- Desktop should show full layout
|
|
144
|
+
- Mobile should stack vertically
|
|
145
|
+
- Both should maintain borders
|
|
146
|
+
|
|
147
|
+
3. **Check alignment**:
|
|
148
|
+
- Use ruler/guides in image viewer
|
|
149
|
+
- Verify 1400px max-width
|
|
150
|
+
- Check header/content alignment
|
|
151
|
+
|
|
152
|
+
4. **Look for issues**:
|
|
153
|
+
- Horizontal overflow
|
|
154
|
+
- Missing borders
|
|
155
|
+
- Inconsistent spacing
|
|
156
|
+
- Broken layouts
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## ✅ EXPECTED RESULTS
|
|
161
|
+
|
|
162
|
+
After layout unification, all pages should:
|
|
163
|
+
|
|
164
|
+
1. **Perfect Alignment** ✅
|
|
165
|
+
- Header at 1400px
|
|
166
|
+
- Content at 1400px
|
|
167
|
+
- Footer at 1400px
|
|
168
|
+
|
|
169
|
+
2. **Clear Borders** ✅
|
|
170
|
+
- All containers bordered
|
|
171
|
+
- Consistent border color
|
|
172
|
+
- Rounded corners
|
|
173
|
+
|
|
174
|
+
3. **Consistent Spacing** ✅
|
|
175
|
+
- 2rem padding standard
|
|
176
|
+
- 2rem gaps
|
|
177
|
+
- No cramped layouts
|
|
178
|
+
|
|
179
|
+
4. **Responsive** ✅
|
|
180
|
+
- Desktop: Full layout
|
|
181
|
+
- Mobile: Stacked layout
|
|
182
|
+
- Borders maintained
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## 📝 NOTES
|
|
187
|
+
|
|
188
|
+
- Screenshots are at 1400x900 (desktop) and 375x667 (mobile)
|
|
189
|
+
- All screenshots taken with latest CSS changes
|
|
190
|
+
- Review both light and dark mode if applicable
|
|
191
|
+
- Check for any visual regressions
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
**Status**: Ready for manual review
|
|
196
|
+
**Action**: Open screenshots and verify layout consistency
|
|
197
|
+
**Report**: Mark checkboxes as you review each screenshot
|
|
198
|
+
`;
|
|
199
|
+
|
|
200
|
+
await fs.writeFile(OUTPUT_FILE, report);
|
|
201
|
+
console.log(`✅ Analysis report generated: ${OUTPUT_FILE}\n`);
|
|
202
|
+
|
|
203
|
+
// Print summary
|
|
204
|
+
console.log('📊 SUMMARY');
|
|
205
|
+
console.log('─'.repeat(50));
|
|
206
|
+
console.log(`Total Pages: ${desktopScreenshots.length}`);
|
|
207
|
+
console.log(` - Homepage: ${pages.home.length}`);
|
|
208
|
+
console.log(` - Tools: ${pages.tools.length}`);
|
|
209
|
+
console.log(` - Master Tools: ${pages.master.length}`);
|
|
210
|
+
console.log(` - Static: ${pages.static.length}`);
|
|
211
|
+
console.log('─'.repeat(50));
|
|
212
|
+
console.log(`\n💡 Next: Review screenshots in ${SCREENSHOTS_DIR}`);
|
|
213
|
+
console.log(`📄 Full report: ${OUTPUT_FILE}\n`);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Run
|
|
217
|
+
analyzeScreenshots().catch(console.error);
|
package/scripts/build.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const { paths, isSecure, forceRebuild } = require('./builds/config');
|
|
3
|
+
const { saveCache } = require('./builds/cache');
|
|
4
|
+
const { buildAssets } = require('./builds/assets');
|
|
5
|
+
const { buildPages } = require('./builds/pages');
|
|
6
|
+
const { buildTemplates, createRootRedirect, copyRootFiles } = require('./builds/templates');
|
|
7
|
+
// const { buildAdmin } = require('./builds/admin');
|
|
8
|
+
|
|
9
|
+
(async () => {
|
|
10
|
+
console.time('🚀 Build Duration');
|
|
11
|
+
console.log(`🚀 Starting build (Secure Mode: ${isSecure ? 'ON' : 'OFF'})...`);
|
|
12
|
+
|
|
13
|
+
if (forceRebuild) {
|
|
14
|
+
try {
|
|
15
|
+
console.time("🧹 Cleaned dist folder");
|
|
16
|
+
await fs.emptyDir(paths.DIST);
|
|
17
|
+
console.timeEnd("🧹 Cleaned dist folder");
|
|
18
|
+
} catch (e) { }
|
|
19
|
+
} else {
|
|
20
|
+
await fs.ensureDir(paths.DIST);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
await buildAssets();
|
|
25
|
+
await buildPages();
|
|
26
|
+
// await buildAdmin();
|
|
27
|
+
await createRootRedirect();
|
|
28
|
+
await buildTemplates();
|
|
29
|
+
await copyRootFiles();
|
|
30
|
+
|
|
31
|
+
saveCache();
|
|
32
|
+
console.log('✅ Build complete!');
|
|
33
|
+
} catch (err) {
|
|
34
|
+
console.error('❌ Build Failed:', err);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
console.timeEnd('🚀 Build Duration');
|
|
39
|
+
})();
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { paths } = require('./config');
|
|
4
|
+
|
|
5
|
+
async function buildAdmin() {
|
|
6
|
+
const adminSrc = path.join(paths.SRC, 'admin');
|
|
7
|
+
const adminDist = path.join(paths.DIST, 'admin');
|
|
8
|
+
|
|
9
|
+
if (fs.existsSync(adminSrc)) {
|
|
10
|
+
console.log('🛡️ Building Admin Panel...');
|
|
11
|
+
await fs.ensureDir(adminDist);
|
|
12
|
+
await fs.copy(adminSrc, adminDist);
|
|
13
|
+
console.log('✅ Admin Panel copied.');
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module.exports = { buildAdmin };
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { execSync } = require('child_process');
|
|
4
|
+
const { paths, isSecure } = require('./config');
|
|
5
|
+
const { hasChanged } = require('./cache');
|
|
6
|
+
const { getHash } = require('./utils');
|
|
7
|
+
|
|
8
|
+
const ASSET_HASHES = {};
|
|
9
|
+
function getAssetHash(relPath) {
|
|
10
|
+
if (ASSET_HASHES[relPath]) return ASSET_HASHES[relPath];
|
|
11
|
+
const fullPath = path.join(paths.DIST, relPath);
|
|
12
|
+
if (fs.existsSync(fullPath)) {
|
|
13
|
+
const content = fs.readFileSync(fullPath);
|
|
14
|
+
const hash = getHash(content).substring(0, 8);
|
|
15
|
+
ASSET_HASHES[relPath] = hash;
|
|
16
|
+
return hash;
|
|
17
|
+
}
|
|
18
|
+
return '';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function minifyJs(src, dest) {
|
|
22
|
+
try {
|
|
23
|
+
execSync(`npx terser "${src}" --compress --mangle --output "${dest}"`);
|
|
24
|
+
} catch (e) {
|
|
25
|
+
console.error(`Minify failed ${src}, copy raw`);
|
|
26
|
+
fs.copySync(src, dest);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function processJs(srcPath, destPath, fileName) {
|
|
31
|
+
if (hasChanged(srcPath) || !fs.existsSync(destPath)) {
|
|
32
|
+
if (isSecure) {
|
|
33
|
+
console.time(`📦 Obfuscating JS: ${fileName}`);
|
|
34
|
+
try {
|
|
35
|
+
const cmd = `npx javascript-obfuscator "${srcPath}" --output "${destPath}" \
|
|
36
|
+
--compact true --control-flow-flattening true --control-flow-flattening-threshold 0.5 \
|
|
37
|
+
--dead-code-injection true --identifier-names-generator hexadecimal \
|
|
38
|
+
--rename-globals true --string-array true --string-array-threshold 0.5 \
|
|
39
|
+
--transform-object-keys true`;
|
|
40
|
+
execSync(cmd);
|
|
41
|
+
} catch (e) {
|
|
42
|
+
console.error(`Obfuscation failed for ${fileName}, fallback to minify.`);
|
|
43
|
+
minifyJs(srcPath, destPath);
|
|
44
|
+
}
|
|
45
|
+
console.timeEnd(`📦 Obfuscating JS: ${fileName}`);
|
|
46
|
+
} else {
|
|
47
|
+
console.time(`📄 Copying JS: ${fileName}`);
|
|
48
|
+
fs.copySync(srcPath, destPath);
|
|
49
|
+
console.timeEnd(`📄 Copying JS: ${fileName}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function buildAssets() {
|
|
55
|
+
const cssSrc = path.join(paths.SRC, 'assets', 'css');
|
|
56
|
+
const jsSrc = path.join(paths.SRC, 'assets', 'js');
|
|
57
|
+
const featuresDir = path.join(paths.SRC, 'features');
|
|
58
|
+
const cssDist = path.join(paths.ASSETS_DIST, 'css');
|
|
59
|
+
const jsDist = path.join(paths.ASSETS_DIST, 'js');
|
|
60
|
+
const featuresDist = path.join(paths.ASSETS_DIST, 'features');
|
|
61
|
+
|
|
62
|
+
await fs.ensureDir(paths.ASSETS_DIST);
|
|
63
|
+
await fs.ensureDir(cssDist);
|
|
64
|
+
await fs.ensureDir(jsDist);
|
|
65
|
+
await fs.ensureDir(featuresDist);
|
|
66
|
+
|
|
67
|
+
console.time('🎨 Assets Build');
|
|
68
|
+
|
|
69
|
+
// 0. Copy all raw assets (images, fonts, vendor, etc)
|
|
70
|
+
const assetsSrc = path.join(paths.SRC, 'assets');
|
|
71
|
+
if (fs.existsSync(assetsSrc)) {
|
|
72
|
+
const items = fs.readdirSync(assetsSrc);
|
|
73
|
+
for (const item of items) {
|
|
74
|
+
if (['css', 'js'].includes(item)) continue;
|
|
75
|
+
const srcPath = path.join(assetsSrc, item);
|
|
76
|
+
const destPath = path.join(paths.ASSETS_DIST, item);
|
|
77
|
+
if (hasChanged(srcPath, 'assets-copy/', false)) {
|
|
78
|
+
await fs.copy(srcPath, destPath);
|
|
79
|
+
hasChanged(srcPath, 'assets-copy/', true);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 1. Global CSS
|
|
85
|
+
if (await fs.pathExists(cssSrc)) {
|
|
86
|
+
const files = await fs.readdir(cssSrc);
|
|
87
|
+
for (const file of files) {
|
|
88
|
+
if (!file.endsWith('.css')) continue;
|
|
89
|
+
const srcPath = path.join(cssSrc, file);
|
|
90
|
+
const destPath = path.join(cssDist, file);
|
|
91
|
+
if (hasChanged(srcPath) || !fs.existsSync(destPath)) {
|
|
92
|
+
if (isSecure) {
|
|
93
|
+
console.time(`🎨 Minifying Global CSS: ${file}`);
|
|
94
|
+
try { execSync(`npx clean-css-cli -o "${destPath}" "${srcPath}"`); }
|
|
95
|
+
catch (e) { await fs.copy(srcPath, destPath); }
|
|
96
|
+
console.timeEnd(`🎨 Minifying Global CSS: ${file}`);
|
|
97
|
+
} else {
|
|
98
|
+
console.time(`🎨 Copying Global CSS: ${file}`);
|
|
99
|
+
await fs.copy(srcPath, destPath);
|
|
100
|
+
console.timeEnd(`🎨 Copying Global CSS: ${file}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 2. Global JS
|
|
107
|
+
if (await fs.pathExists(jsSrc)) {
|
|
108
|
+
const files = await fs.readdir(jsSrc);
|
|
109
|
+
for (const file of files) {
|
|
110
|
+
if (!file.endsWith('.js')) continue;
|
|
111
|
+
const srcPath = path.join(jsSrc, file);
|
|
112
|
+
const destPath = path.join(jsDist, file);
|
|
113
|
+
processJs(srcPath, destPath, file);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 3. Feature Assets
|
|
118
|
+
if (await fs.pathExists(featuresDir)) {
|
|
119
|
+
const features = await fs.readdir(featuresDir);
|
|
120
|
+
for (const feature of features) {
|
|
121
|
+
const featDir = path.join(featuresDir, feature);
|
|
122
|
+
if (!(await fs.stat(featDir)).isDirectory()) continue;
|
|
123
|
+
|
|
124
|
+
const featDistDir = path.join(featuresDist, feature);
|
|
125
|
+
await fs.ensureDir(featDistDir);
|
|
126
|
+
|
|
127
|
+
// Feature CSS
|
|
128
|
+
const cssPath = path.join(featDir, 'style.css');
|
|
129
|
+
if (fs.existsSync(cssPath)) {
|
|
130
|
+
const destPath = path.join(featDistDir, 'style.css');
|
|
131
|
+
if (hasChanged(cssPath) || !fs.existsSync(destPath)) {
|
|
132
|
+
if (isSecure) {
|
|
133
|
+
console.time(`🎨 Minifying Feature CSS: ${feature}/style.css`);
|
|
134
|
+
try { execSync(`npx clean-css-cli -o "${destPath}" "${cssPath}"`); }
|
|
135
|
+
catch (e) { await fs.copy(cssPath, destPath); }
|
|
136
|
+
console.timeEnd(`🎨 Minifying Feature CSS: ${feature}/style.css`);
|
|
137
|
+
} else {
|
|
138
|
+
console.time(`🎨 Copying Feature CSS: ${feature}/style.css`);
|
|
139
|
+
await fs.copy(cssPath, destPath);
|
|
140
|
+
console.timeEnd(`🎨 Copying Feature CSS: ${feature}/style.css`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Feature JS
|
|
146
|
+
const files = fs.readdirSync(featDir);
|
|
147
|
+
for (const file of files) {
|
|
148
|
+
if (file.endsWith('.js') && file !== 'toolConfig.js') {
|
|
149
|
+
const jsSrcPath = path.join(featDir, file);
|
|
150
|
+
const jsDestPath = path.join(featDistDir, file);
|
|
151
|
+
processJs(jsSrcPath, jsDestPath, `${feature}/${file}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// 4. Special: Building Playground Examples from folders
|
|
158
|
+
const { buildPlaygroundExamples } = require('./playground-examples');
|
|
159
|
+
await buildPlaygroundExamples();
|
|
160
|
+
|
|
161
|
+
console.timeEnd('🎨 Assets Build');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
module.exports = {
|
|
165
|
+
getAssetHash,
|
|
166
|
+
buildAssets
|
|
167
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const yaml = require('js-yaml');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { paths, isSecure, forceRebuild } = require('./config');
|
|
5
|
+
const { getHash } = require('./utils');
|
|
6
|
+
|
|
7
|
+
let buildCache = {};
|
|
8
|
+
|
|
9
|
+
if (fs.existsSync(paths.CACHE_FILE)) {
|
|
10
|
+
try {
|
|
11
|
+
buildCache = yaml.load(fs.readFileSync(paths.CACHE_FILE, 'utf8')) || {};
|
|
12
|
+
} catch (e) {
|
|
13
|
+
console.warn("⚠️ Failed to load build cache, starting fresh.");
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function hasChanged(filePath, keyPrefix = '', update = true) {
|
|
18
|
+
if (forceRebuild) return true;
|
|
19
|
+
if (!fs.existsSync(filePath)) return true;
|
|
20
|
+
if (fs.statSync(filePath).isDirectory()) return true;
|
|
21
|
+
|
|
22
|
+
const content = fs.readFileSync(filePath);
|
|
23
|
+
const currentHash = getHash(content);
|
|
24
|
+
|
|
25
|
+
// Separate cache for dev and prod
|
|
26
|
+
const modePrefix = isSecure ? 'prod/' : 'dev/';
|
|
27
|
+
const relPath = path.relative(paths.SRC, filePath);
|
|
28
|
+
const cacheKey = modePrefix + keyPrefix + relPath;
|
|
29
|
+
|
|
30
|
+
if (buildCache[cacheKey] !== currentHash) {
|
|
31
|
+
if (update) buildCache[cacheKey] = currentHash;
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function saveCache() {
|
|
38
|
+
try {
|
|
39
|
+
fs.writeFileSync(paths.CACHE_FILE, yaml.dump(buildCache));
|
|
40
|
+
} catch (e) {
|
|
41
|
+
console.error("⚠️ Failed to save build cache:", e);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = {
|
|
46
|
+
hasChanged,
|
|
47
|
+
saveCache
|
|
48
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
|
|
3
|
+
// CLI Args
|
|
4
|
+
const isSecure = process.argv.includes('--obfuscate');
|
|
5
|
+
const isDev = process.argv.includes('--dev');
|
|
6
|
+
let forceRebuild = process.argv.includes('--force');
|
|
7
|
+
|
|
8
|
+
// Paths
|
|
9
|
+
const ROOT_DIR = process.cwd();
|
|
10
|
+
const SRC_DIR = path.join(ROOT_DIR, 'src');
|
|
11
|
+
const DIST_DIR = path.join(ROOT_DIR, isDev ? 'dist-dev' : 'dist');
|
|
12
|
+
const ASSETS_DIST = path.join(DIST_DIR, 'assets');
|
|
13
|
+
const CACHE_FILE = path.join(ROOT_DIR, '.build-cache.yaml');
|
|
14
|
+
|
|
15
|
+
// Internal paths for the framework itself (when running from node_modules)
|
|
16
|
+
const CORE_DIR = path.resolve(__dirname, '../../');
|
|
17
|
+
|
|
18
|
+
module.exports = {
|
|
19
|
+
isSecure,
|
|
20
|
+
isDev,
|
|
21
|
+
forceRebuild,
|
|
22
|
+
paths: {
|
|
23
|
+
ROOT: ROOT_DIR,
|
|
24
|
+
CORE_ROOT: CORE_DIR,
|
|
25
|
+
SRC: SRC_DIR,
|
|
26
|
+
DIST: DIST_DIR,
|
|
27
|
+
ASSETS_DIST: ASSETS_DIST,
|
|
28
|
+
CACHE_FILE: CACHE_FILE
|
|
29
|
+
},
|
|
30
|
+
setForceRebuild: (val) => { forceRebuild = val; }
|
|
31
|
+
};
|