aztomiq 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 CHANGED
@@ -61,7 +61,7 @@ AZtomiq isn't just another static site generator. It's an **Ecosystem** designed
61
61
  **Option A: Scaffolding a new project (Recommended)**
62
62
 
63
63
  ```bash
64
- npx create-aztomiq my-awesome-app
64
+ npx aztomiq init my-awesome-app
65
65
  cd my-awesome-app
66
66
  npm install
67
67
  ```
package/bin/aztomiq.js CHANGED
@@ -270,6 +270,79 @@ meta:
270
270
 
271
271
  console.log(`āœ… Tool "${name}" created successfully!`);
272
272
  console.log(`šŸ‘‰ Edit it at: src/features/${name}`);
273
+ },
274
+ init: async () => {
275
+ const name = args[1] || 'my-aztomiq-site';
276
+ const projectPath = path.resolve(PROJECT_ROOT, name);
277
+
278
+ if (await fs.pathExists(projectPath)) {
279
+ console.error(`āŒ Directory "${name}" already exists.`);
280
+ return;
281
+ }
282
+
283
+ console.log(`šŸš€ Initializing new AZtomiq project: ${name}...`);
284
+
285
+ // 1. Create structure
286
+ const templateDirs = [
287
+ 'src/assets/css',
288
+ 'src/assets/js',
289
+ 'src/assets/images',
290
+ 'src/data',
291
+ 'src/features/hello-world/locales',
292
+ 'src/includes',
293
+ 'src/locales/en',
294
+ 'src/locales/vi',
295
+ 'src/pages',
296
+ 'src/templates'
297
+ ];
298
+
299
+ for (const dir of templateDirs) {
300
+ await fs.ensureDir(path.join(projectPath, dir));
301
+ }
302
+
303
+ // 2. Create package.json
304
+ const packageJson = {
305
+ name: name,
306
+ version: '1.0.0',
307
+ scripts: {
308
+ "dev": "aztomiq dev",
309
+ "build": "aztomiq build",
310
+ "status": "aztomiq status"
311
+ },
312
+ dependencies: {
313
+ "aztomiq": "latest"
314
+ }
315
+ };
316
+ await fs.writeJson(path.join(projectPath, 'package.json'), packageJson, { spaces: 2 });
317
+
318
+ // 3. Create global.yaml
319
+ const globalYaml = `site:
320
+ title: "${name}"
321
+ description: "Built with AZtomiq"
322
+ build:
323
+ locales: ["en", "vi"]
324
+ default_locale: "en"
325
+ `;
326
+ await fs.writeFile(path.join(projectPath, 'src/data/global.yaml'), globalYaml);
327
+
328
+ // 4. Create sample tool
329
+ const toolYaml = `id: hello-world
330
+ category: general
331
+ icon: smile
332
+ status: active
333
+ `;
334
+ await fs.writeFile(path.join(projectPath, 'src/features/hello-world/tool.yaml'), toolYaml);
335
+
336
+ const toolEjs = `<h1>Hello World</h1>
337
+ <p>Welcome to your new AZtomiq site!</p>
338
+ `;
339
+ await fs.writeFile(path.join(projectPath, 'src/features/hello-world/index.ejs'), toolEjs);
340
+
341
+ console.log(`\nāœ… Project "${name}" initialized successfully!`);
342
+ console.log(`šŸ‘‰ Next steps:`);
343
+ console.log(` cd ${name}`);
344
+ console.log(` npm install`);
345
+ console.log(` npm run dev`);
273
346
  }
274
347
  };
275
348
 
@@ -284,6 +357,7 @@ Commands:
284
357
  aztomiq dev Start development server (Watcher + Node)
285
358
  aztomiq build Build for production (--force, --obfuscate)
286
359
  aztomiq deploy Deploy to public distribution repository
360
+ aztomiq init <name> Initialize a new AZtomiq project
287
361
  aztomiq status Scan ecosystem health (Locales, Tests, Config)
288
362
  aztomiq analyze Analyze tool payloads and ecosystem size
289
363
  aztomiq cleanup Remove build artifacts (--drafts to delete draft tools)
package/package.json CHANGED
@@ -1,17 +1,19 @@
1
1
  {
2
2
  "name": "aztomiq",
3
- "version": "1.0.1",
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": {
7
- "aztomiq": "bin/aztomiq.js",
8
- "create-aztomiq": "bin/create-aztomiq.js"
7
+ "aztomiq": "bin/aztomiq.js"
9
8
  },
10
9
  "files": [
11
10
  "bin",
12
11
  "scripts",
12
+ "src/assets",
13
13
  "src/includes",
14
14
  "src/templates",
15
+ "src/locales",
16
+ "server.js",
15
17
  "package.json",
16
18
  "README.md",
17
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
+ }