antigravity-seo-kit 1.2.8 → 1.3.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/lib/downloader.js CHANGED
@@ -4,6 +4,7 @@ const https = require('https');
4
4
  const zlib = require('zlib');
5
5
  const fs = require('fs');
6
6
  const path = require('path');
7
+ const { smartMergeJsonFile } = require('./utils');
7
8
 
8
9
  // ─── Download ───────────────────────────────────────────────────────────────
9
10
 
@@ -145,8 +146,24 @@ function extractTarGz(gzBuffer, targetDir) {
145
146
  fs.mkdirSync(targetPath, { recursive: true });
146
147
  } else if (typeFlag === ASCII_0 || typeFlag === 0) {
147
148
  // Regular file
149
+ const isConfigJson = fullName.endsWith('.json') &&
150
+ (fullName.startsWith('config/') ||
151
+ fullName.startsWith('.agent/config/') ||
152
+ fullName.includes('/config/'));
153
+
148
154
  fs.mkdirSync(path.dirname(targetPath), { recursive: true });
149
- fs.writeFileSync(targetPath, tarBuffer.subarray(offset, offset + size));
155
+
156
+ if (isConfigJson && fs.existsSync(targetPath)) {
157
+ const tempPath = targetPath + '.tmp';
158
+ fs.writeFileSync(tempPath, tarBuffer.subarray(offset, offset + size));
159
+ smartMergeJsonFile(tempPath, targetPath);
160
+ try {
161
+ fs.unlinkSync(tempPath);
162
+ } catch (e) {}
163
+ } else {
164
+ fs.writeFileSync(targetPath, tarBuffer.subarray(offset, offset + size));
165
+ }
166
+
150
167
  files.push(fullName);
151
168
  count++;
152
169
  }
package/lib/installer.js CHANGED
@@ -457,12 +457,14 @@ async function installPlugin(licenseKey) {
457
457
  version: PACKAGE_VERSION,
458
458
  description: "Professional SEO Analysis Toolkit for Google Antigravity AI Agent — 44 specialized skills covering technical audit, E-E-A-T, schema, GEO, local SEO & more",
459
459
  author: {
460
- name: "Antigravity SEO Kit"
460
+ name: "Antigravity SEO Kit",
461
+ email: "admin@solann.io",
461
462
  },
463
+ repository: "https://solann.io/antigravity-seo-kit",
462
464
  license: "SEE LICENSE IN LICENSE",
463
465
  keywords: [
464
466
  "seo",
465
- "universal-seo-kit",
467
+ "agentic-seo",
466
468
  "ai-agent",
467
469
  "antigravity",
468
470
  "seo-geo",
@@ -528,7 +530,7 @@ function setupWorkspace(cwd) {
528
530
  // Only copy necessary directories. agents and rules are loaded globally via the plugin.
529
531
  // skills are copied but filtered to exclude .md files to save space and avoid duplicate prompts/instructions.
530
532
  const directoriesToCopy = ['.shared', 'config', 'dashboard', 'docs', 'scripts', 'workflows'];
531
- const filesToCopy = ['ARCHITECTURE.md'];
533
+ const filesToCopy = ['seo-architecture.md'];
532
534
 
533
535
  let count = 0;
534
536
  for (const dir of directoriesToCopy) {
@@ -541,6 +543,8 @@ function setupWorkspace(cwd) {
541
543
  overwrite: true,
542
544
  filter: (filePath) => !filePath.endsWith('.md')
543
545
  });
546
+ } else if (dir === 'config') {
547
+ count += copyRecursive(src, dest, { overwrite: true, mergeJson: true });
544
548
  } else {
545
549
  count += copyRecursive(src, dest, { overwrite: true });
546
550
  }
package/lib/utils.js CHANGED
@@ -88,11 +88,80 @@ function spinner(message) {
88
88
 
89
89
  // ─── File Operations ────────────────────────────────────────────────────────
90
90
 
91
+ /**
92
+ * Check if a value is a plain object.
93
+ */
94
+ function isObject(item) {
95
+ return (item && typeof item === 'object' && !Array.isArray(item));
96
+ }
97
+
98
+ /**
99
+ * Recursively merge two JSON objects (deep merge).
100
+ * Prioritizes properties from sourceObj (user config) while preserving/appending new properties from targetObj (template config).
101
+ */
102
+ function deepMergeJson(targetObj, sourceObj) {
103
+ const output = { ...targetObj };
104
+ if (isObject(targetObj) && isObject(sourceObj)) {
105
+ for (const key of Object.keys(sourceObj)) {
106
+ if (isObject(sourceObj[key])) {
107
+ if (!(key in targetObj)) {
108
+ output[key] = sourceObj[key];
109
+ } else {
110
+ output[key] = deepMergeJson(targetObj[key], sourceObj[key]);
111
+ }
112
+ } else {
113
+ output[key] = sourceObj[key];
114
+ }
115
+ }
116
+ }
117
+ return output;
118
+ }
119
+
120
+ /**
121
+ * Merges JSON file from srcPath (new template) into destPath (existing user config) safely.
122
+ */
123
+ function smartMergeJsonFile(srcPath, destPath) {
124
+ if (!fs.existsSync(srcPath)) return;
125
+
126
+ if (!fs.existsSync(destPath)) {
127
+ fs.mkdirSync(path.dirname(destPath), { recursive: true });
128
+ fs.copyFileSync(srcPath, destPath);
129
+ return;
130
+ }
131
+
132
+ let newJson;
133
+ try {
134
+ newJson = JSON.parse(fs.readFileSync(srcPath, 'utf-8'));
135
+ } catch (err) {
136
+ fs.copyFileSync(srcPath, destPath);
137
+ return;
138
+ }
139
+
140
+ let oldJson;
141
+ try {
142
+ oldJson = JSON.parse(fs.readFileSync(destPath, 'utf-8'));
143
+ } catch (err) {
144
+ const bakPath = destPath + '.bak';
145
+ try {
146
+ fs.copyFileSync(destPath, bakPath);
147
+ warn(`File cấu hình bị lỗi cú pháp JSON và đã được sao lưu thành: ${path.basename(bakPath)}`);
148
+ } catch (bakErr) {
149
+ warn(`Không thể sao lưu file cấu hình lỗi: ${bakErr.message}`);
150
+ }
151
+ fs.copyFileSync(srcPath, destPath);
152
+ return;
153
+ }
154
+
155
+ const merged = deepMergeJson(newJson, oldJson);
156
+ fs.writeFileSync(destPath, JSON.stringify(merged, null, 2), 'utf-8');
157
+ }
158
+
91
159
  /**
92
160
  * Recursively copy a directory, merging with existing content.
93
161
  * Does not overwrite existing files unless `overwrite` is true.
162
+ * Supports smart JSON merging if `mergeJson` is enabled.
94
163
  */
95
- function copyRecursive(src, dest, { overwrite = false, fileCallback = null, filter = null } = {}) {
164
+ function copyRecursive(src, dest, { overwrite = false, fileCallback = null, filter = null, mergeJson = false } = {}) {
96
165
  if (!fs.existsSync(src)) return 0;
97
166
  if (filter && !filter(src)) return 0;
98
167
 
@@ -105,12 +174,17 @@ function copyRecursive(src, dest, { overwrite = false, fileCallback = null, filt
105
174
  count += copyRecursive(
106
175
  path.join(src, entry),
107
176
  path.join(dest, entry),
108
- { overwrite, fileCallback, filter }
177
+ { overwrite, fileCallback, filter, mergeJson }
109
178
  );
110
179
  }
111
180
  } else {
112
181
  fs.mkdirSync(path.dirname(dest), { recursive: true });
113
- if (overwrite || !fs.existsSync(dest)) {
182
+ const isJson = src.endsWith('.json');
183
+ if (isJson && mergeJson && fs.existsSync(dest)) {
184
+ smartMergeJsonFile(src, dest);
185
+ count = 1;
186
+ if (fileCallback) fileCallback(dest);
187
+ } else if (overwrite || !fs.existsSync(dest)) {
114
188
  fs.copyFileSync(src, dest);
115
189
  count = 1;
116
190
  if (fileCallback) fileCallback(dest);
@@ -299,5 +373,7 @@ module.exports = {
299
373
  writeLicenseFile,
300
374
  removeLicenseFile,
301
375
  LICENSE_FILE,
302
- PACKAGE_VERSION: '1.2.8',
376
+ PACKAGE_VERSION: '1.3.0',
377
+ deepMergeJson,
378
+ smartMergeJsonFile,
303
379
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antigravity-seo-kit",
3
- "version": "1.2.8",
3
+ "version": "1.3.0",
4
4
  "description": "Professional SEO Analysis Toolkit for Google Antigravity AI Agent — 44 specialized skills covering technical audit, E-E-A-T, schema, GEO, local SEO & more",
5
5
  "main": "lib/installer.js",
6
6
  "bin": {