aztomiq 1.0.2 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aztomiq",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "AZtomiq - A comprehensive A-Z multi-tool framework with atomic architecture",
5
5
  "main": "scripts/build.js",
6
6
  "bin": {
@@ -9,8 +9,11 @@
9
9
  "files": [
10
10
  "bin",
11
11
  "scripts",
12
+ "src/assets",
12
13
  "src/includes",
13
14
  "src/templates",
15
+ "src/locales",
16
+ "server.js",
14
17
  "package.json",
15
18
  "README.md",
16
19
  "LICENSE"
@@ -82,38 +82,46 @@ async function buildAssets() {
82
82
  }
83
83
 
84
84
  // 1. Global CSS
85
- if (await fs.pathExists(cssSrc)) {
86
- const files = await fs.readdir(cssSrc);
85
+ const cssDirs = [path.join(paths.CORE_ROOT, 'src/assets/css'), cssSrc].filter(fs.existsSync);
86
+ const cssFiles = {};
87
+ for (const dir of cssDirs) {
88
+ const files = fs.readdirSync(dir);
87
89
  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
- }
90
+ if (file.endsWith('.css')) cssFiles[file] = path.join(dir, file);
91
+ }
92
+ }
93
+
94
+ for (const [file, srcPath] of Object.entries(cssFiles)) {
95
+ const destPath = path.join(cssDist, file);
96
+ if (hasChanged(srcPath) || !fs.existsSync(destPath)) {
97
+ if (isSecure) {
98
+ console.time(`🎨 Minifying Global CSS: ${file}`);
99
+ try { execSync(`npx clean-css-cli -o "${destPath}" "${srcPath}"`); }
100
+ catch (e) { await fs.copy(srcPath, destPath); }
101
+ console.timeEnd(`🎨 Minifying Global CSS: ${file}`);
102
+ } else {
103
+ console.time(`🎨 Copying Global CSS: ${file}`);
104
+ await fs.copy(srcPath, destPath);
105
+ console.timeEnd(`🎨 Copying Global CSS: ${file}`);
102
106
  }
103
107
  }
104
108
  }
105
109
 
106
110
  // 2. Global JS
107
- if (await fs.pathExists(jsSrc)) {
108
- const files = await fs.readdir(jsSrc);
111
+ const jsDirs = [path.join(paths.CORE_ROOT, 'src/assets/js'), jsSrc].filter(fs.existsSync);
112
+ const jsFiles = {};
113
+ for (const dir of jsDirs) {
114
+ const files = fs.readdirSync(dir);
109
115
  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);
116
+ if (file.endsWith('.js')) jsFiles[file] = path.join(dir, file);
114
117
  }
115
118
  }
116
119
 
120
+ for (const [file, srcPath] of Object.entries(jsFiles)) {
121
+ const destPath = path.join(jsDist, file);
122
+ processJs(srcPath, destPath, file);
123
+ }
124
+
117
125
  // 3. Feature Assets
118
126
  if (await fs.pathExists(featuresDir)) {
119
127
  const features = await fs.readdir(featuresDir);
@@ -44,28 +44,39 @@ function loadLocales(lang) {
44
44
  const srcDir = paths.SRC;
45
45
 
46
46
  // 1. Load legacy file
47
- const legacyPath = path.join(srcDir, 'locales', `${lang}.json`);
48
- if (fs.existsSync(legacyPath)) {
49
- try {
50
- Object.assign(translations, require(legacyPath));
51
- } catch (e) { console.error(`Error loading legacy locale ${lang} `, e); }
47
+ const legacyPaths = [
48
+ path.join(paths.CORE_ROOT, 'src/locales', `${lang}.json`),
49
+ path.join(srcDir, 'locales', `${lang}.json`)
50
+ ];
51
+ for (const p of legacyPaths) {
52
+ if (fs.existsSync(p)) {
53
+ try {
54
+ Object.assign(translations, typeof require(p) === 'string' ? JSON.parse(fs.readFileSync(p, 'utf8')) : require(p));
55
+ } catch (e) { console.error(`Error loading legacy locale ${lang} `, e); }
56
+ }
52
57
  }
53
58
 
54
59
  // 2. Load module folders
55
- const dirPath = path.join(srcDir, 'locales', lang);
56
- if (fs.existsSync(dirPath) && fs.statSync(dirPath).isDirectory()) {
57
- const files = fs.readdirSync(dirPath);
58
- for (const file of files) {
59
- if (file.endsWith('.json')) {
60
- try {
61
- const content = require(path.join(dirPath, file));
62
- Object.assign(translations, content);
63
- } catch (e) { console.error(`Error loading locale module ${lang}/${file}`, e); }
64
- } else if (file.endsWith('.yaml') || file.endsWith('.yml')) {
65
- try {
66
- const content = yaml.load(fs.readFileSync(path.join(dirPath, file), 'utf8'));
67
- Object.assign(translations, content);
68
- } catch (e) { console.error(`Error loading locale module ${lang}/${file}`, e); }
60
+ const dirPaths = [
61
+ path.join(paths.CORE_ROOT, 'src/locales', lang),
62
+ path.join(srcDir, 'locales', lang)
63
+ ];
64
+
65
+ for (const dirPath of dirPaths) {
66
+ if (fs.existsSync(dirPath) && fs.statSync(dirPath).isDirectory()) {
67
+ const files = fs.readdirSync(dirPath);
68
+ for (const file of files) {
69
+ if (file.endsWith('.json')) {
70
+ try {
71
+ const content = JSON.parse(fs.readFileSync(path.join(dirPath, file), 'utf8'));
72
+ Object.assign(translations, content);
73
+ } catch (e) { console.error(`Error loading locale module ${lang}/${file}`, e); }
74
+ } else if (file.endsWith('.yaml') || file.endsWith('.yml')) {
75
+ try {
76
+ const content = yaml.load(fs.readFileSync(path.join(dirPath, file), 'utf8'));
77
+ Object.assign(translations, content);
78
+ } catch (e) { console.error(`Error loading locale module ${lang}/${file}`, e); }
79
+ }
69
80
  }
70
81
  }
71
82
  }
package/server.js ADDED
@@ -0,0 +1,124 @@
1
+
2
+ const express = require('express');
3
+ const bodyParser = require('body-parser');
4
+ const cors = require('cors');
5
+ const fs = require('fs-extra');
6
+ const path = require('path');
7
+ const yaml = require('js-yaml');
8
+ const { exec } = require('child_process');
9
+
10
+ const app = express();
11
+ const PORT = 3000;
12
+
13
+ // Configuration
14
+ const SRC_DIR = path.join(__dirname, 'src');
15
+ const FEATURES_DIR = path.join(SRC_DIR, 'features');
16
+ const DIST_DIR = path.join(__dirname, 'dist-dev');
17
+
18
+ app.use(cors());
19
+ app.use(bodyParser.json());
20
+
21
+ // 1. Static Files (Serve the site)
22
+ app.use(express.static(DIST_DIR));
23
+
24
+ // 2. Admin API
25
+ // GET /api/features - List all features and their configs
26
+ app.get('/api/features', (req, res) => {
27
+ try {
28
+ const features = fs.readdirSync(FEATURES_DIR).filter(f => {
29
+ return fs.statSync(path.join(FEATURES_DIR, f)).isDirectory();
30
+ });
31
+
32
+ const data = features.map(feature => {
33
+ const configPath = path.join(FEATURES_DIR, feature, 'tool.yaml');
34
+ if (fs.existsSync(configPath)) {
35
+ const config = yaml.load(fs.readFileSync(configPath, 'utf8'));
36
+ return { id: feature, config };
37
+ }
38
+ return null;
39
+ }).filter(item => item !== null);
40
+
41
+ res.json(data);
42
+ } catch (err) {
43
+ res.status(500).json({ error: err.message });
44
+ }
45
+ });
46
+
47
+ // POST /api/features/:id - Update feature config
48
+ app.post('/api/features/:id', (req, res) => {
49
+ const { id } = req.params;
50
+ const newConfig = req.body;
51
+ const configPath = path.join(FEATURES_DIR, id, 'tool.yaml');
52
+
53
+ if (!fs.existsSync(configPath)) {
54
+ return res.status(404).json({ error: 'Feature not found' });
55
+ }
56
+
57
+ try {
58
+ fs.writeFileSync(configPath, yaml.dump(newConfig), 'utf8');
59
+ console.log(`📝 Updated config for ${id}`);
60
+
61
+ console.log('🔄 Triggering rebuild...');
62
+ exec('node scripts/build.js', (error, stdout, stderr) => {
63
+ if (error) console.error(`Build error: ${error}`);
64
+ else console.log(`✅ Build complete`);
65
+ });
66
+
67
+ res.json({ success: true, message: 'Config updated and rebuild triggered' });
68
+ } catch (err) {
69
+ res.status(500).json({ error: err.message });
70
+ }
71
+ });
72
+
73
+ // GET /api/global
74
+ app.get('/api/global', (req, res) => {
75
+ try {
76
+ const globalPath = path.join(SRC_DIR, 'data', 'global.yaml');
77
+ if (fs.existsSync(globalPath)) {
78
+ const config = yaml.load(fs.readFileSync(globalPath, 'utf8'));
79
+ res.json(config);
80
+ } else {
81
+ res.json({});
82
+ }
83
+ } catch (err) {
84
+ res.status(500).json({ error: err.message });
85
+ }
86
+ });
87
+
88
+ // POST /api/global
89
+ app.post('/api/global', (req, res) => {
90
+ try {
91
+ const globalPath = path.join(SRC_DIR, 'data', 'global.yaml');
92
+ const newConfig = req.body;
93
+
94
+ fs.writeFileSync(globalPath, yaml.dump(newConfig), 'utf8');
95
+ console.log(`📝 Updated Global Config`);
96
+
97
+ console.log('🔄 Triggering rebuild...');
98
+ exec('node scripts/build.js', (error, stdout, stderr) => {
99
+ if (error) console.error(`Build error: ${error}`);
100
+ else console.log(`✅ Build complete`);
101
+ });
102
+
103
+ res.json({ success: true, message: 'Global config updated' });
104
+ } catch (err) {
105
+ res.status(500).json({ error: err.message });
106
+ }
107
+ });
108
+
109
+ // Fallback to index.html for SPA routing (only for page requests, not assets)
110
+ app.use((req, res) => {
111
+ // If request has an extension, it's likely a missing asset
112
+ if (path.extname(req.path)) {
113
+ return res.status(404).send('Not found');
114
+ }
115
+ res.sendFile(path.join(DIST_DIR, 'index.html'));
116
+ });
117
+
118
+ app.listen(PORT, () => {
119
+ console.log(`
120
+ 🚀 Admin Server running at http://localhost:${PORT}/admin
121
+ 👉 Admin API: http://localhost:${PORT}/api/features
122
+ 👉 Website: http://localhost:${PORT}/
123
+ `);
124
+ });
@@ -0,0 +1,90 @@
1
+ /* Masonry Grid Layout */
2
+ .categories-grid {
3
+ column-count: 2;
4
+ column-gap: 2rem;
5
+ }
6
+
7
+ @media (max-width: 900px) {
8
+ .categories-grid {
9
+ column-count: 1;
10
+ }
11
+ }
12
+
13
+ .category-card {
14
+ break-inside: avoid; /* Prevent card splitting */
15
+ margin-bottom: 2rem;
16
+ padding: 1.5rem;
17
+ background: var(--card-bg);
18
+ border: 1px solid var(--border-color);
19
+ border-radius: 12px;
20
+ box-shadow: var(--shadow);
21
+ }
22
+
23
+ .category-title {
24
+ font-size: 1.25rem;
25
+ margin-bottom: 1.5rem;
26
+ display: flex;
27
+ align-items: center;
28
+ gap: 0.75rem;
29
+ border-bottom: 1px solid var(--border-color);
30
+ padding-bottom: 1rem;
31
+ }
32
+
33
+ .cat-icon {
34
+ display: inline-flex;
35
+ align-items: center;
36
+ justify-content: center;
37
+ width: 36px;
38
+ height: 36px;
39
+ background: var(--bg-hover);
40
+ border-radius: 8px;
41
+ color: var(--primary-color);
42
+ }
43
+
44
+ .cat-count {
45
+ margin-left: auto;
46
+ font-size: 0.8rem;
47
+ background: var(--bg-hover);
48
+ padding: 0.2rem 0.6rem;
49
+ border-radius: 20px;
50
+ color: var(--text-muted);
51
+ white-space: nowrap;
52
+ display: inline-flex;
53
+ align-items: center;
54
+ border: 1px solid var(--border-color);
55
+ }
56
+
57
+ .cat-tools-list {
58
+ display: flex;
59
+ flex-direction: column;
60
+ gap: 0.5rem;
61
+ }
62
+
63
+ .cat-tool-item {
64
+ display: flex;
65
+ align-items: center;
66
+ gap: 0.75rem;
67
+ padding: 0.6rem 0.8rem;
68
+ border-radius: 8px;
69
+ color: var(--text-color); /* Updated text color */
70
+ transition: all 0.2s;
71
+ border: 1px solid transparent; /* Prevent layout shift on hover border */
72
+ position: relative;
73
+ }
74
+
75
+ .cat-tool-item:hover {
76
+ background: var(--bg-hover);
77
+ color: var(--primary-color);
78
+ transform: translateX(5px);
79
+ border-color: var(--border-color);
80
+ }
81
+
82
+ .tool-icon {
83
+ color: var(--text-muted);
84
+ display: flex;
85
+ align-items: center;
86
+ }
87
+
88
+ .cat-tool-item:hover .tool-icon {
89
+ color: var(--primary-color);
90
+ }