@consilioweb/payload-seo-analyzer 1.7.1
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 +1201 -0
- package/dist/client.cjs +19286 -0
- package/dist/client.d.cts +133 -0
- package/dist/client.d.ts +133 -0
- package/dist/client.js +19261 -0
- package/dist/index.cjs +11836 -0
- package/dist/index.d.cts +1416 -0
- package/dist/index.d.ts +1416 -0
- package/dist/index.js +11752 -0
- package/dist/views.cjs +216 -0
- package/dist/views.d.cts +67 -0
- package/dist/views.d.ts +67 -0
- package/dist/views.js +206 -0
- package/package.json +122 -0
- package/scripts/uninstall.mjs +282 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Cleanup script for @consilioweb/seo-analyzer
|
|
4
|
+
// Removes all imports and plugin calls from source files before uninstalling the package.
|
|
5
|
+
// Usage: npx seo-analyzer-uninstall
|
|
6
|
+
|
|
7
|
+
import fs from 'node:fs'
|
|
8
|
+
import path from 'node:path'
|
|
9
|
+
import { execSync } from 'node:child_process'
|
|
10
|
+
|
|
11
|
+
const PACKAGE_NAME = '@consilioweb/seo-analyzer'
|
|
12
|
+
|
|
13
|
+
// Regex to match any import line from @consilioweb/seo-analyzer (value + type imports)
|
|
14
|
+
const IMPORT_RE = /^\s*import\s+(?:type\s+)?(?:\{[^}]*\}|[\w]+)\s+from\s+['"]@consilioweb\/seo-analyzer(?:\/[^'"]*)?['"]\s*;?\s*$/gm
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Extract imported names from a file that come from @consilioweb/seo-analyzer.
|
|
18
|
+
* Returns the list of identifiers (after "as" renaming if any).
|
|
19
|
+
* e.g. `import { seoPlugin as myPlugin, seoFields } from '...'` → ['myPlugin', 'seoFields']
|
|
20
|
+
*/
|
|
21
|
+
function extractImportedNames(content) {
|
|
22
|
+
const names = []
|
|
23
|
+
const re = /^\s*import\s+(?:type\s+)?\{([^}]*)\}\s+from\s+['"]@consilioweb\/seo-analyzer(?:\/[^'"]*)?['"]\s*;?\s*$/gm
|
|
24
|
+
let match
|
|
25
|
+
while ((match = re.exec(content)) !== null) {
|
|
26
|
+
const specifiers = match[1]
|
|
27
|
+
for (const spec of specifiers.split(',')) {
|
|
28
|
+
const trimmed = spec.trim()
|
|
29
|
+
if (!trimmed) continue
|
|
30
|
+
// Handle `foo as bar` → use bar (local name)
|
|
31
|
+
const asParts = trimmed.split(/\s+as\s+/)
|
|
32
|
+
names.push(asParts.length > 1 ? asParts[1].trim() : trimmed)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return names
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Recursively find all .ts and .tsx files in a directory
|
|
40
|
+
*/
|
|
41
|
+
function findSourceFiles(dir) {
|
|
42
|
+
const results = []
|
|
43
|
+
if (!fs.existsSync(dir)) return results
|
|
44
|
+
|
|
45
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true })
|
|
46
|
+
for (const entry of entries) {
|
|
47
|
+
const fullPath = path.join(dir, entry.name)
|
|
48
|
+
if (entry.isDirectory()) {
|
|
49
|
+
// Skip node_modules and hidden directories
|
|
50
|
+
if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue
|
|
51
|
+
results.push(...findSourceFiles(fullPath))
|
|
52
|
+
} else if (/\.(ts|tsx)$/.test(entry.name)) {
|
|
53
|
+
results.push(fullPath)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return results
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Remove a plugin call like `seoAnalyzerPlugin({ ... })` from a plugins array.
|
|
61
|
+
* Only removes calls for function names that were actually imported from our package.
|
|
62
|
+
* Handles nested braces/parens across multiple lines.
|
|
63
|
+
*/
|
|
64
|
+
function removePluginCalls(content, callNames) {
|
|
65
|
+
let modified = content
|
|
66
|
+
|
|
67
|
+
for (const fnName of callNames) {
|
|
68
|
+
let searchFrom = 0
|
|
69
|
+
while (true) {
|
|
70
|
+
const callIndex = modified.indexOf(`${fnName}(`, searchFrom)
|
|
71
|
+
if (callIndex === -1) break
|
|
72
|
+
|
|
73
|
+
// Verify this is actually our function call (not something like "mySeoAnalyzerPlugin")
|
|
74
|
+
if (callIndex > 0 && /[\w$]/.test(modified[callIndex - 1])) {
|
|
75
|
+
searchFrom = callIndex + fnName.length
|
|
76
|
+
continue
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Also check the char after fnName( isn't making a different identifier
|
|
80
|
+
// (already handled by the `(` in the search string)
|
|
81
|
+
|
|
82
|
+
// Find the start of this expression line
|
|
83
|
+
let lineStart = callIndex
|
|
84
|
+
while (lineStart > 0 && modified[lineStart - 1] !== '\n') {
|
|
85
|
+
lineStart--
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Find the matching closing paren for the function call
|
|
89
|
+
const openParen = callIndex + fnName.length
|
|
90
|
+
let depth = 0
|
|
91
|
+
let endIndex = openParen
|
|
92
|
+
for (let i = openParen; i < modified.length; i++) {
|
|
93
|
+
if (modified[i] === '(') depth++
|
|
94
|
+
else if (modified[i] === ')') {
|
|
95
|
+
depth--
|
|
96
|
+
if (depth === 0) {
|
|
97
|
+
endIndex = i + 1
|
|
98
|
+
break
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Check for trailing comma and whitespace
|
|
104
|
+
let removeEnd = endIndex
|
|
105
|
+
const afterCall = modified.slice(endIndex)
|
|
106
|
+
const trailingMatch = afterCall.match(/^\s*,/)
|
|
107
|
+
if (trailingMatch) {
|
|
108
|
+
removeEnd = endIndex + trailingMatch[0].length
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Determine the full range to remove
|
|
112
|
+
let removeStart = lineStart
|
|
113
|
+
// Include the newline before this line
|
|
114
|
+
if (removeStart > 0 && modified[removeStart - 1] === '\n') {
|
|
115
|
+
removeStart--
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// If no trailing comma, remove a leading comma instead
|
|
119
|
+
if (!trailingMatch) {
|
|
120
|
+
let lookBack = removeStart
|
|
121
|
+
while (lookBack > 0 && /[\s\n]/.test(modified[lookBack - 1])) {
|
|
122
|
+
lookBack--
|
|
123
|
+
}
|
|
124
|
+
if (lookBack > 0 && modified[lookBack - 1] === ',') {
|
|
125
|
+
removeStart = lookBack - 1
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Remove the block
|
|
130
|
+
modified = modified.slice(0, removeStart) + modified.slice(removeEnd)
|
|
131
|
+
// Don't advance searchFrom since content shifted
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return modified
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Clean up consecutive empty lines (max 1 empty line between content)
|
|
140
|
+
*/
|
|
141
|
+
function cleanEmptyLines(content) {
|
|
142
|
+
return content.replace(/\n{3,}/g, '\n\n')
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Clean orphan trailing commas before closing brackets/parens
|
|
147
|
+
* e.g., `,\n]` becomes `\n]`
|
|
148
|
+
*/
|
|
149
|
+
function cleanOrphanCommas(content) {
|
|
150
|
+
return content.replace(/,(\s*\n\s*[)\]])/g, '$1')
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Process a single file: remove imports and plugin calls
|
|
155
|
+
*/
|
|
156
|
+
function processFile(filePath) {
|
|
157
|
+
const original = fs.readFileSync(filePath, 'utf-8')
|
|
158
|
+
|
|
159
|
+
// Check if this file references the package at all
|
|
160
|
+
if (!original.includes(PACKAGE_NAME)) {
|
|
161
|
+
return null
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
let content = original
|
|
165
|
+
|
|
166
|
+
// 1. Extract the names actually imported from our package (before removing imports)
|
|
167
|
+
const importedNames = extractImportedNames(content)
|
|
168
|
+
|
|
169
|
+
// 2. Remove import lines
|
|
170
|
+
content = content.replace(IMPORT_RE, '')
|
|
171
|
+
|
|
172
|
+
// 3. Remove plugin calls only for names imported from our package
|
|
173
|
+
if (importedNames.length > 0) {
|
|
174
|
+
content = removePluginCalls(content, importedNames)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// 4. Clean up
|
|
178
|
+
content = cleanOrphanCommas(content)
|
|
179
|
+
content = cleanEmptyLines(content)
|
|
180
|
+
|
|
181
|
+
if (content === original) {
|
|
182
|
+
return null
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return content
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ── Helpers ───────────────────────────────────────────────
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Detect which package manager is being used in the project
|
|
192
|
+
*/
|
|
193
|
+
function detectPackageManager(projectDir) {
|
|
194
|
+
if (fs.existsSync(path.join(projectDir, 'pnpm-lock.yaml'))) return 'pnpm'
|
|
195
|
+
if (fs.existsSync(path.join(projectDir, 'yarn.lock'))) return 'yarn'
|
|
196
|
+
if (fs.existsSync(path.join(projectDir, 'bun.lockb')) || fs.existsSync(path.join(projectDir, 'bun.lock'))) return 'bun'
|
|
197
|
+
return 'npm'
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Run a shell command, print output, swallow errors
|
|
202
|
+
*/
|
|
203
|
+
function run(cmd, cwd) {
|
|
204
|
+
console.log(` \x1b[90m$ ${cmd}\x1b[0m`)
|
|
205
|
+
try {
|
|
206
|
+
execSync(cmd, { cwd, stdio: 'inherit' })
|
|
207
|
+
return true
|
|
208
|
+
} catch {
|
|
209
|
+
return false
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ── Main ──────────────────────────────────────────────────
|
|
214
|
+
|
|
215
|
+
function main() {
|
|
216
|
+
// Determine project root
|
|
217
|
+
const projectDir = process.env.INIT_CWD || process.cwd()
|
|
218
|
+
const srcDir = path.join(projectDir, 'src')
|
|
219
|
+
const pm = detectPackageManager(projectDir)
|
|
220
|
+
|
|
221
|
+
console.log('')
|
|
222
|
+
console.log(' \x1b[36m@consilioweb/seo-analyzer\x1b[0m — Full Uninstall')
|
|
223
|
+
console.log(' ─────────────────────────────────────────────')
|
|
224
|
+
console.log(` Project: \x1b[33m${projectDir}\x1b[0m`)
|
|
225
|
+
console.log(` Package manager: \x1b[33m${pm}\x1b[0m`)
|
|
226
|
+
console.log('')
|
|
227
|
+
|
|
228
|
+
// ── Step 1: Clean source files ──
|
|
229
|
+
console.log(' \x1b[36m[1/3]\x1b[0m Cleaning source files...')
|
|
230
|
+
|
|
231
|
+
if (!fs.existsSync(srcDir)) {
|
|
232
|
+
console.log(' \x1b[33m⚠\x1b[0m No src/ directory found. Skipping code cleanup.')
|
|
233
|
+
} else {
|
|
234
|
+
const files = findSourceFiles(srcDir)
|
|
235
|
+
const modified = []
|
|
236
|
+
|
|
237
|
+
for (const filePath of files) {
|
|
238
|
+
const result = processFile(filePath)
|
|
239
|
+
if (result !== null) {
|
|
240
|
+
fs.writeFileSync(filePath, result, 'utf-8')
|
|
241
|
+
const rel = path.relative(projectDir, filePath)
|
|
242
|
+
modified.push(rel)
|
|
243
|
+
console.log(` \x1b[32m✓\x1b[0m Cleaned: ${rel}`)
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (modified.length === 0) {
|
|
248
|
+
console.log(' \x1b[32m✓\x1b[0m No references found in source files.')
|
|
249
|
+
} else {
|
|
250
|
+
console.log(` \x1b[32m✓\x1b[0m ${modified.length} file(s) cleaned.`)
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
console.log('')
|
|
255
|
+
|
|
256
|
+
// ── Step 2: Remove the package ──
|
|
257
|
+
console.log(' \x1b[36m[2/3]\x1b[0m Removing package...')
|
|
258
|
+
const removeCmd = pm === 'npm' ? 'npm uninstall' : `${pm} remove`
|
|
259
|
+
run(`${removeCmd} ${PACKAGE_NAME}`, projectDir)
|
|
260
|
+
|
|
261
|
+
console.log('')
|
|
262
|
+
|
|
263
|
+
// ── Step 3: Regenerate importmap ──
|
|
264
|
+
console.log(' \x1b[36m[3/3]\x1b[0m Regenerating importmap...')
|
|
265
|
+
const importmapCmd = pm === 'npm' ? 'npx' : pm === 'yarn' ? 'yarn' : pm
|
|
266
|
+
run(`${importmapCmd} generate:importmap`, projectDir)
|
|
267
|
+
|
|
268
|
+
console.log('')
|
|
269
|
+
|
|
270
|
+
// ── Done ──
|
|
271
|
+
console.log(' \x1b[32m✓ Uninstall complete!\x1b[0m')
|
|
272
|
+
console.log('')
|
|
273
|
+
console.log(' \x1b[36mOptional:\x1b[0m Drop plugin collections from your database:')
|
|
274
|
+
console.log(' \x1b[90m - seo-score-history')
|
|
275
|
+
console.log(' - seo-settings')
|
|
276
|
+
console.log(' - seo-redirects')
|
|
277
|
+
console.log(' - seo-performance')
|
|
278
|
+
console.log(' - seo-logs\x1b[0m')
|
|
279
|
+
console.log('')
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
main()
|