@aexol/spectral 0.9.43 → 0.9.44
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/dist/extensions/seo/analyzers/content-optimize.d.ts +3 -0
- package/dist/extensions/seo/analyzers/content-optimize.d.ts.map +1 -0
- package/dist/extensions/seo/analyzers/content-optimize.js +190 -0
- package/dist/extensions/seo/analyzers/crawler.d.ts +4 -0
- package/dist/extensions/seo/analyzers/crawler.d.ts.map +1 -0
- package/dist/extensions/seo/analyzers/crawler.js +288 -0
- package/dist/extensions/seo/analyzers/keywords.d.ts +9 -0
- package/dist/extensions/seo/analyzers/keywords.d.ts.map +1 -0
- package/dist/extensions/seo/analyzers/keywords.js +183 -0
- package/dist/extensions/seo/analyzers/performance.d.ts +6 -0
- package/dist/extensions/seo/analyzers/performance.d.ts.map +1 -0
- package/dist/extensions/seo/analyzers/performance.js +257 -0
- package/dist/extensions/seo/analyzers/readability.d.ts +4 -0
- package/dist/extensions/seo/analyzers/readability.d.ts.map +1 -0
- package/dist/extensions/seo/analyzers/readability.js +121 -0
- package/dist/extensions/seo/index.d.ts.map +1 -1
- package/dist/extensions/seo/index.js +47 -9
- package/dist/extensions/seo/tools/batch.d.ts +4 -0
- package/dist/extensions/seo/tools/batch.d.ts.map +1 -0
- package/dist/extensions/seo/tools/batch.js +193 -0
- package/dist/extensions/seo/tools/crawl.d.ts +4 -0
- package/dist/extensions/seo/tools/crawl.d.ts.map +1 -0
- package/dist/extensions/seo/tools/crawl.js +166 -0
- package/dist/extensions/seo/tools/full-audit.d.ts +1 -0
- package/dist/extensions/seo/tools/full-audit.d.ts.map +1 -1
- package/dist/extensions/seo/tools/keywords.d.ts +4 -0
- package/dist/extensions/seo/tools/keywords.d.ts.map +1 -0
- package/dist/extensions/seo/tools/keywords.js +234 -0
- package/dist/extensions/seo/tools/performance.d.ts +4 -0
- package/dist/extensions/seo/tools/performance.d.ts.map +1 -0
- package/dist/extensions/seo/tools/performance.js +201 -0
- package/dist/extensions/seo/types.d.ts +156 -0
- package/dist/extensions/seo/types.d.ts.map +1 -1
- package/dist/extensions/seo/utils/url-safety.d.ts.map +1 -1
- package/dist/extensions/seo/utils/url-safety.js +7 -5
- package/dist/server/handlers/paths-pick-directory.d.ts +0 -27
- package/dist/server/handlers/paths-pick-directory.d.ts.map +1 -1
- package/dist/server/handlers/paths-pick-directory.js +86 -92
- package/package.json +1 -1
|
@@ -1,30 +1,18 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Native OS folder picker dialog for project path selection.
|
|
3
|
-
*
|
|
4
|
-
* The "New Project" form in the browser has a "Browse..." button next to
|
|
5
|
-
* the typed-path autocomplete. When clicked, it sends
|
|
6
|
-
* `POST /api/paths/pick-directory` to this handler, which launches a
|
|
7
|
-
* native OS folder picker dialog on the user's machine and returns the
|
|
8
|
-
* absolute path of the selected directory (or `null` when cancelled).
|
|
9
|
-
*
|
|
10
|
-
* Platform strategy (tried in order):
|
|
11
|
-
* 1. macOS — osascript (built-in, zero deps, native look)
|
|
12
|
-
* 2. Linux — zenity (GTK), kdialog (KDE), then Python tkinter
|
|
13
|
-
* 3. Windows — PowerShell (COM object)
|
|
14
|
-
* 4. Fallback — Python tkinter (bundled with most Python installs)
|
|
15
|
-
*
|
|
16
|
-
* The first available tool wins; if none work, we return an error.
|
|
17
|
-
* All child processes are synchronous — the browser is waiting anyway.
|
|
18
|
-
*/
|
|
19
1
|
import { execSync } from "node:child_process";
|
|
20
2
|
import { platform } from "node:os";
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
3
|
+
function execErrMsg(err) {
|
|
4
|
+
if (err && typeof err === "object" && "message" in err) {
|
|
5
|
+
return String(err.message);
|
|
6
|
+
}
|
|
7
|
+
return String(err);
|
|
8
|
+
}
|
|
9
|
+
function execErrStatus(err) {
|
|
10
|
+
if (err && typeof err === "object" && "status" in err) {
|
|
11
|
+
const s = err.status;
|
|
12
|
+
return typeof s === "number" ? s : undefined;
|
|
13
|
+
}
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
28
16
|
function pickMacOS() {
|
|
29
17
|
const script = `
|
|
30
18
|
set folderPath to POSIX path of (choose folder with prompt "Select project directory")
|
|
@@ -36,75 +24,95 @@ function pickMacOS() {
|
|
|
36
24
|
timeout: 180_000, // 3 min — user might take time to browse
|
|
37
25
|
});
|
|
38
26
|
const trimmed = result.trim();
|
|
39
|
-
|
|
27
|
+
if (!trimmed) {
|
|
28
|
+
return { path: null };
|
|
29
|
+
}
|
|
30
|
+
return { path: trimmed };
|
|
40
31
|
}
|
|
41
|
-
catch {
|
|
42
|
-
|
|
43
|
-
|
|
32
|
+
catch (err) {
|
|
33
|
+
if (execErrStatus(err) === 1) {
|
|
34
|
+
return { path: null }; // user cancelled
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
path: null,
|
|
38
|
+
error: `Could not open folder picker (macOS): ${execErrMsg(err)}`,
|
|
39
|
+
};
|
|
44
40
|
}
|
|
45
41
|
}
|
|
46
|
-
/**
|
|
47
|
-
* Run a command that shows a folder picker dialog. Returns `null` on user
|
|
48
|
-
* cancellation, and falls through (re-throws) if the tool is not installed.
|
|
49
|
-
*/
|
|
50
42
|
function tryDialog(cmd) {
|
|
51
43
|
try {
|
|
52
44
|
const result = execSync(cmd, { encoding: "utf-8", timeout: 180_000 });
|
|
53
45
|
return result.trim() || null;
|
|
54
46
|
}
|
|
55
47
|
catch (err) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const status = err && typeof err === "object" && "status" in err
|
|
59
|
-
? err.status
|
|
60
|
-
: undefined;
|
|
61
|
-
if (status === 1)
|
|
62
|
-
return null;
|
|
48
|
+
if (execErrStatus(err) === 1)
|
|
49
|
+
return null; // user cancelled
|
|
63
50
|
throw err;
|
|
64
51
|
}
|
|
65
52
|
}
|
|
66
|
-
/**
|
|
67
|
-
* Linux: Try zenity (GTK), then kdialog (KDE), then Python tkinter.
|
|
68
|
-
*/
|
|
69
53
|
function pickLinux() {
|
|
70
54
|
try {
|
|
71
|
-
|
|
55
|
+
const p = tryDialog(`zenity --file-selection --directory --title="Select project directory"`);
|
|
56
|
+
return p ? { path: p } : { path: null };
|
|
72
57
|
}
|
|
73
58
|
catch {
|
|
74
|
-
// zenity not available
|
|
75
59
|
}
|
|
76
60
|
try {
|
|
77
|
-
|
|
61
|
+
const p = tryDialog(`kdialog --getexistingdirectory "${process.env.HOME ?? "/"}" --title "Select project directory"`);
|
|
62
|
+
return p ? { path: p } : { path: null };
|
|
78
63
|
}
|
|
79
64
|
catch {
|
|
80
|
-
// kdialog not available
|
|
81
65
|
}
|
|
82
|
-
|
|
83
|
-
|
|
66
|
+
const tkResult = pickTkinter();
|
|
67
|
+
if (tkResult.path === null && !tkResult.error) {
|
|
68
|
+
return {
|
|
69
|
+
path: null,
|
|
70
|
+
error: "No folder picker available. Install zenity, kdialog, or python3-tk.",
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return tkResult;
|
|
84
74
|
}
|
|
85
|
-
/**
|
|
86
|
-
* Windows: Use PowerShell to invoke the COM Shell.Application folder picker.
|
|
87
|
-
*/
|
|
88
75
|
function pickWindows() {
|
|
89
76
|
const script = `
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
77
|
+
try {
|
|
78
|
+
Add-Type -AssemblyName System.Windows.Forms -ErrorAction Stop
|
|
79
|
+
$f = New-Object System.Windows.Forms.FolderBrowserDialog
|
|
80
|
+
$f.Description = "Select project directory"
|
|
81
|
+
$f.ShowNewFolderButton = $true
|
|
82
|
+
if ($f.ShowDialog() -eq "OK") { Write-Output $f.SelectedPath } else { Write-Output "__CANCELLED__" }
|
|
83
|
+
} catch {
|
|
84
|
+
Write-Error $_.Exception.Message
|
|
85
|
+
[Environment]::Exit(99)
|
|
86
|
+
}
|
|
95
87
|
`.trim();
|
|
88
|
+
// -EncodedCommand (UTF-16LE base64) avoids cmd.exe quote-escaping bugs
|
|
89
|
+
// that would corrupt -Command strings containing nested double-quotes.
|
|
90
|
+
const encoded = Buffer.from(script, "utf-16le").toString("base64");
|
|
96
91
|
try {
|
|
97
|
-
const result = execSync(`powershell -NoProfile -
|
|
98
|
-
|
|
92
|
+
const result = execSync(`powershell -STA -NoProfile -EncodedCommand ${encoded}`, { encoding: "utf-8", timeout: 180_000 });
|
|
93
|
+
const trimmed = result.trim();
|
|
94
|
+
if (!trimmed || trimmed === "__CANCELLED__") {
|
|
95
|
+
return { path: null };
|
|
96
|
+
}
|
|
97
|
+
return { path: trimmed };
|
|
99
98
|
}
|
|
100
|
-
catch {
|
|
101
|
-
|
|
99
|
+
catch (err) {
|
|
100
|
+
const status = execErrStatus(err);
|
|
101
|
+
if (status === 99) {
|
|
102
|
+
const stderr = err && typeof err === "object" && "stderr" in err
|
|
103
|
+
? String(err.stderr).trim()
|
|
104
|
+
: "";
|
|
105
|
+
return {
|
|
106
|
+
path: null,
|
|
107
|
+
error: stderr || "Folder picker could not be displayed.",
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
path: null,
|
|
112
|
+
error: `Could not open folder picker (Windows): ${execErrMsg(err)}`,
|
|
113
|
+
};
|
|
102
114
|
}
|
|
103
115
|
}
|
|
104
|
-
/**
|
|
105
|
-
* Python tkinter fallback — works on any OS with Python + tkinter installed.
|
|
106
|
-
* tkinter is bundled with macOS Python from python.org and most Linux distros.
|
|
107
|
-
*/
|
|
108
116
|
function pickTkinter() {
|
|
109
117
|
const script = `import tkinter as tk
|
|
110
118
|
from tkinter import filedialog
|
|
@@ -120,61 +128,47 @@ print(d if d else "")
|
|
|
120
128
|
encoding: "utf-8",
|
|
121
129
|
timeout: 180_000,
|
|
122
130
|
});
|
|
123
|
-
|
|
131
|
+
const trimmed = result.trim();
|
|
132
|
+
return trimmed ? { path: trimmed } : { path: null };
|
|
124
133
|
}
|
|
125
134
|
catch {
|
|
126
|
-
// python3 not found — try 'python' (Windows)
|
|
127
135
|
try {
|
|
128
136
|
const result = execSync(`python -c "${script}"`, {
|
|
129
137
|
encoding: "utf-8",
|
|
130
138
|
timeout: 180_000,
|
|
131
139
|
});
|
|
132
|
-
|
|
140
|
+
const trimmed = result.trim();
|
|
141
|
+
return trimmed ? { path: trimmed } : { path: null };
|
|
133
142
|
}
|
|
134
143
|
catch {
|
|
135
|
-
return
|
|
144
|
+
return {
|
|
145
|
+
path: null,
|
|
146
|
+
error: "Python/tkinter not available for folder picker. Type the path manually.",
|
|
147
|
+
};
|
|
136
148
|
}
|
|
137
149
|
}
|
|
138
150
|
}
|
|
139
|
-
// ---------------------------------------------------------------------------
|
|
140
|
-
// Public handler
|
|
141
|
-
// ---------------------------------------------------------------------------
|
|
142
|
-
/**
|
|
143
|
-
* Attempt to open a native folder picker dialog and return the selected path.
|
|
144
|
-
*
|
|
145
|
-
* Synchronous and blocking by design — the browser form is waiting for a
|
|
146
|
-
* response and the user is interacting with the dialog.
|
|
147
|
-
*/
|
|
148
151
|
export function handlePickDirectory() {
|
|
149
152
|
const os = platform();
|
|
150
153
|
try {
|
|
151
|
-
let path = null;
|
|
152
154
|
if (os === "darwin") {
|
|
153
|
-
|
|
155
|
+
return pickMacOS();
|
|
154
156
|
}
|
|
155
157
|
else if (os === "linux") {
|
|
156
|
-
|
|
158
|
+
return pickLinux();
|
|
157
159
|
}
|
|
158
160
|
else if (os === "win32") {
|
|
159
|
-
|
|
161
|
+
return pickWindows();
|
|
160
162
|
}
|
|
161
163
|
else {
|
|
162
|
-
|
|
163
|
-
path = pickTkinter();
|
|
164
|
-
}
|
|
165
|
-
if (path === null) {
|
|
166
|
-
// User cancelled or no picker available. Distinguish:
|
|
167
|
-
// When no path AND no error in the platform pickers, it's a cancel.
|
|
168
|
-
return { path: null };
|
|
164
|
+
return pickTkinter();
|
|
169
165
|
}
|
|
170
|
-
return { path };
|
|
171
166
|
}
|
|
172
167
|
catch (err) {
|
|
173
|
-
// The platform picker failed entirely (not cancelled — process error).
|
|
174
168
|
const msg = err instanceof Error ? err.message : String(err);
|
|
175
169
|
return {
|
|
176
170
|
path: null,
|
|
177
|
-
error: `Could not open folder picker: ${msg}`,
|
|
171
|
+
error: `Could not open folder picker (${os}): ${msg}`,
|
|
178
172
|
};
|
|
179
173
|
}
|
|
180
174
|
}
|