codex-overleaf-link 1.8.6 → 1.9.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/README.md +45 -21
- package/bin/codex-overleaf-link.mjs +58 -0
- package/extension/assets/icons/codex-overleaf-dialog-icon.png +0 -0
- package/extension/assets/icons/codex-overleaf-icon.png +0 -0
- package/extension/assets/icons/icon128.png +0 -0
- package/extension/assets/icons/icon16.png +0 -0
- package/extension/assets/icons/icon32.png +0 -0
- package/extension/assets/icons/icon48.png +0 -0
- package/extension/bootstrap/background.js +404 -0
- package/extension/bootstrap/manifest.template.json +51 -0
- package/extension/bootstrap/popup.css +26 -0
- package/extension/bootstrap/popup.html +38 -0
- package/extension/bootstrap/popup.js +44 -0
- package/extension/bootstrap/runtimeContext.js +2 -0
- package/extension/runtime-manifest.json +60 -0
- package/extension/src/background.js +532 -0
- package/extension/src/content/applyResultFormatters.js +387 -0
- package/extension/src/content/composerAttachments.js +522 -0
- package/extension/src/content/composerPanel.js +254 -0
- package/extension/src/content/contentRuntime.js +8895 -0
- package/extension/src/content/contextTray.js +686 -0
- package/extension/src/content/diagnosticsController.js +659 -0
- package/extension/src/content/diagnosticsPanel.js +342 -0
- package/extension/src/content/diffReviewPanel.js +797 -0
- package/extension/src/content/localSkillsPanel.js +280 -0
- package/extension/src/content/markdownText.js +606 -0
- package/extension/src/content/mirrorHealth.js +92 -0
- package/extension/src/content/modelPicker.js +489 -0
- package/extension/src/content/nativeChannel.js +62 -0
- package/extension/src/content/otWarmMirror.js +964 -0
- package/extension/src/content/otWarmMirrorController.js +174 -0
- package/extension/src/content/panelMaintenance.js +367 -0
- package/extension/src/content/panelRenderer.js +253 -0
- package/extension/src/content/recentProjects.js +1063 -0
- package/extension/src/content/reviewHunks.js +247 -0
- package/extension/src/content/runController.js +242 -0
- package/extension/src/content/runTimelineView.js +1126 -0
- package/extension/src/content/sessionManager.js +487 -0
- package/extension/src/content/sessionPanel.js +263 -0
- package/extension/src/content/settingsPanel.js +527 -0
- package/extension/src/content/themeController.js +91 -0
- package/extension/src/content/updateIdle.js +67 -0
- package/extension/src/content/writebackController.js +300 -0
- package/extension/src/content/writebackOrchestrator.js +850 -0
- package/extension/src/contentScript.js +8 -0
- package/extension/src/page/compileBridge.js +460 -0
- package/extension/src/page/overleafCapabilities.js +100 -0
- package/extension/src/page/overleafEditor.js +282 -0
- package/extension/src/page/overleafProjectSnapshot.js +234 -0
- package/extension/src/page/overleafRealtimeObserver.js +491 -0
- package/extension/src/page/pageBridgeCapability.js +71 -0
- package/extension/src/page/snapshotRouter.js +948 -0
- package/extension/src/page/trackedChangesLifecycle.js +1506 -0
- package/extension/src/page/treeOperations.js +1584 -0
- package/extension/src/page/writeGuard.js +142 -0
- package/extension/src/page/writebackRouter.js +1934 -0
- package/extension/src/pageBridge.js +2224 -0
- package/extension/src/popup.js +247 -0
- package/extension/src/shared/compatibility.js +1 -1
- package/extension/styles/panel.css +4048 -0
- package/native-host/src/index.js +9 -2
- package/native-host/src/managedInstall.js +393 -0
- package/native-host/src/managedLauncherRuntime.js +71 -0
- package/native-host/src/nativeHostPlatform.js +16 -0
- package/native-host/src/runtimeInstaller.js +6 -1
- package/native-host/src/taskRunnerRuntime.js +8 -0
- package/native-host/src/updateArchive.js +253 -0
- package/native-host/src/updateManager.js +548 -0
- package/native-host/src/updateTrust.js +156 -0
- package/package.json +11 -2
- package/scripts/install-managed.mjs +64 -0
- package/scripts/uninstall-managed.mjs +48 -0
package/native-host/src/index.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
const { decodeFrames, encodeMessage } = require('./nativeMessaging');
|
|
5
5
|
const { logDebug } = require('./debugLog');
|
|
6
6
|
const { handleRequest } = require('./taskRunner');
|
|
7
|
+
const { getActiveNativeWorkState } = require('./taskRunnerRuntime');
|
|
8
|
+
const { handleUpdateRequest, isUpdateMethod } = require('./updateManager');
|
|
7
9
|
const { buildNativeRuntimeEnv, summarizeNativeEnvironment } = require('./nativeEnvironment');
|
|
8
10
|
|
|
9
11
|
let buffered = Buffer.alloc(0);
|
|
@@ -39,13 +41,18 @@ process.stdin.on('data', chunk => {
|
|
|
39
41
|
async function handleDecodedMessage(message) {
|
|
40
42
|
try {
|
|
41
43
|
logDebug('request.received', summarizeRequest(message));
|
|
42
|
-
const response =
|
|
44
|
+
const response = isUpdateMethod(message?.method)
|
|
45
|
+
? await handleUpdateRequest(message, {
|
|
46
|
+
env: runtimeEnv,
|
|
47
|
+
getWorkState: getActiveNativeWorkState
|
|
48
|
+
})
|
|
49
|
+
: await handleRequest(message, runtimeEnv, event => {
|
|
43
50
|
writeResponse({
|
|
44
51
|
id: message?.id,
|
|
45
52
|
ok: true,
|
|
46
53
|
event
|
|
47
54
|
});
|
|
48
|
-
|
|
55
|
+
});
|
|
49
56
|
logDebug('response.ready', summarizeResponse(response));
|
|
50
57
|
writeResponse(response);
|
|
51
58
|
} catch (error) {
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const os = require('node:os');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const { spawnSync } = require('node:child_process');
|
|
7
|
+
const { buildLauncher } = require('./launcher');
|
|
8
|
+
const {
|
|
9
|
+
DEFAULT_CHROME_EXTENSION_ID,
|
|
10
|
+
buildAllowedOrigin,
|
|
11
|
+
buildHostManifest,
|
|
12
|
+
validateChromeExtensionId
|
|
13
|
+
} = require('./manifest');
|
|
14
|
+
const {
|
|
15
|
+
getDefaultBridgePath,
|
|
16
|
+
getDefaultManagedExtensionRoot,
|
|
17
|
+
getDefaultManagedNativeRoot,
|
|
18
|
+
getDefaultManagedRoot,
|
|
19
|
+
getNativeHostRegistrationTarget
|
|
20
|
+
} = require('./nativeHostPlatform');
|
|
21
|
+
const { buildRuntimeFileManifest } = require('./runtimeInstaller');
|
|
22
|
+
const { parseSemver, updateError } = require('./updateTrust');
|
|
23
|
+
|
|
24
|
+
const MANAGED_BY = 'codex-overleaf-link';
|
|
25
|
+
const EXTENSION_MARKER = '.codex-overleaf-managed-extension.json';
|
|
26
|
+
const NATIVE_MARKER = '.codex-overleaf-managed-native.json';
|
|
27
|
+
|
|
28
|
+
function installManagedDistribution(options = {}) {
|
|
29
|
+
const packageRoot = path.resolve(options.packageRoot || path.resolve(__dirname, '../..'));
|
|
30
|
+
const pkg = readJson(path.join(packageRoot, 'package.json'));
|
|
31
|
+
const version = String(options.version || pkg.version || '');
|
|
32
|
+
if (!parseSemver(version)) {
|
|
33
|
+
throw updateError('managed_version_invalid', 'Managed installation requires a stable semantic version.');
|
|
34
|
+
}
|
|
35
|
+
const platform = options.platform || process.platform;
|
|
36
|
+
const browser = options.browser || 'chrome';
|
|
37
|
+
const env = options.env || process.env;
|
|
38
|
+
const platformPath = platform === 'win32' ? path.win32 : path.posix;
|
|
39
|
+
const managedRoot = resolveForPlatform(options.managedRoot || getDefaultManagedRoot({ platform, env }), platformPath);
|
|
40
|
+
const extensionRoot = resolveForPlatform(options.extensionRoot || platformPath.join(managedRoot, 'extension'), platformPath);
|
|
41
|
+
const nativeRoot = resolveForPlatform(options.nativeRoot || platformPath.join(managedRoot, 'native'), platformPath);
|
|
42
|
+
const extensionId = options.extensionId || DEFAULT_CHROME_EXTENSION_ID;
|
|
43
|
+
if (!validateChromeExtensionId(extensionId)) {
|
|
44
|
+
throw new Error('Invalid Chrome extension id: ' + extensionId);
|
|
45
|
+
}
|
|
46
|
+
assertSafeManagedRoot(managedRoot, { platformPath, packageRoot, env, platform });
|
|
47
|
+
assertManagedChildRoot(extensionRoot, managedRoot, platformPath, 'extension');
|
|
48
|
+
assertManagedChildRoot(nativeRoot, managedRoot, platformPath, 'native');
|
|
49
|
+
|
|
50
|
+
const parent = path.dirname(path.resolve(extensionRoot));
|
|
51
|
+
fs.mkdirSync(parent, { recursive: true });
|
|
52
|
+
const unique = process.pid + '-' + Date.now() + '-' + Math.random().toString(16).slice(2);
|
|
53
|
+
const extensionStage = path.join(parent, '.extension.staging-' + unique);
|
|
54
|
+
const extensionRollback = path.join(parent, '.extension.rollback-' + unique);
|
|
55
|
+
buildManagedExtensionTree({ packageRoot, targetRoot: extensionStage, version, allowedFiles: options.allowedFiles });
|
|
56
|
+
|
|
57
|
+
const previousExtensionMarker = readManagedMarker(extensionRoot, EXTENSION_MARKER);
|
|
58
|
+
if (fs.existsSync(extensionRoot) && !isManagedMarker(previousExtensionMarker, 'extension')) {
|
|
59
|
+
safeRemove(extensionStage);
|
|
60
|
+
throw new Error('Refusing to replace an unmarked managed extension root: ' + extensionRoot);
|
|
61
|
+
}
|
|
62
|
+
let extensionRollbackCreated = false;
|
|
63
|
+
try {
|
|
64
|
+
if (fs.existsSync(extensionRoot)) {
|
|
65
|
+
fs.renameSync(extensionRoot, extensionRollback);
|
|
66
|
+
extensionRollbackCreated = true;
|
|
67
|
+
}
|
|
68
|
+
fs.renameSync(extensionStage, extensionRoot);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
safeRemove(extensionStage);
|
|
71
|
+
if (extensionRollbackCreated && !fs.existsSync(extensionRoot)) {
|
|
72
|
+
fs.renameSync(extensionRollback, extensionRoot);
|
|
73
|
+
}
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
installManagedNativeVersion({ packageRoot, nativeRoot, version });
|
|
79
|
+
installManagedNativeRegistration({
|
|
80
|
+
browser,
|
|
81
|
+
env,
|
|
82
|
+
extensionId,
|
|
83
|
+
managedRoot,
|
|
84
|
+
nativeRoot,
|
|
85
|
+
platform
|
|
86
|
+
});
|
|
87
|
+
safeRemove(extensionRollback);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
safeRemove(extensionRoot);
|
|
90
|
+
if (extensionRollbackCreated && fs.existsSync(extensionRollback)) {
|
|
91
|
+
fs.renameSync(extensionRollback, extensionRoot);
|
|
92
|
+
}
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
ok: true,
|
|
98
|
+
action: previousExtensionMarker ? 'updated' : 'installed',
|
|
99
|
+
browser: browser === 'auto' ? 'chrome' : browser,
|
|
100
|
+
extensionId,
|
|
101
|
+
version,
|
|
102
|
+
managedRoot,
|
|
103
|
+
extensionRoot,
|
|
104
|
+
nativeRoot,
|
|
105
|
+
loadUnpackedPath: extensionRoot,
|
|
106
|
+
allowedOrigin: buildAllowedOrigin(extensionId)
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function buildManagedExtensionTree(options = {}) {
|
|
111
|
+
const packageRoot = path.resolve(options.packageRoot || path.resolve(__dirname, '../..'));
|
|
112
|
+
const targetRoot = path.resolve(String(options.targetRoot || ''));
|
|
113
|
+
const version = String(options.version || readJson(path.join(packageRoot, 'package.json')).version || '');
|
|
114
|
+
if (!parseSemver(version)) {
|
|
115
|
+
throw new Error('Managed extension version is invalid.');
|
|
116
|
+
}
|
|
117
|
+
if (fs.existsSync(targetRoot) && fs.readdirSync(targetRoot).length) {
|
|
118
|
+
throw new Error('Managed extension target must be empty: ' + targetRoot);
|
|
119
|
+
}
|
|
120
|
+
fs.mkdirSync(targetRoot, { recursive: true, mode: 0o700 });
|
|
121
|
+
const allowedFiles = normalizeAllowedFiles(options.allowedFiles);
|
|
122
|
+
const templatePath = path.join(packageRoot, 'extension/bootstrap/manifest.template.json');
|
|
123
|
+
const manifest = readJson(templatePath);
|
|
124
|
+
manifest.version = version;
|
|
125
|
+
fs.writeFileSync(path.join(targetRoot, 'manifest.json'), JSON.stringify(manifest, null, 2) + '\n', 'utf8');
|
|
126
|
+
|
|
127
|
+
copyPackageTree(packageRoot, 'extension/bootstrap', targetRoot, 'bootstrap', {
|
|
128
|
+
allowedFiles,
|
|
129
|
+
exclude: new Set(['extension/bootstrap/manifest.template.json'])
|
|
130
|
+
});
|
|
131
|
+
copyPackageTree(packageRoot, 'extension/assets', targetRoot, 'assets', { allowedFiles });
|
|
132
|
+
copyPackageTree(packageRoot, 'extension/src', targetRoot, 'runtime/src', { allowedFiles });
|
|
133
|
+
copyPackageTree(packageRoot, 'extension/styles', targetRoot, 'runtime/styles', { allowedFiles });
|
|
134
|
+
copyPackageFile(packageRoot, 'extension/runtime-manifest.json', targetRoot, 'runtime/runtime-manifest.json', allowedFiles);
|
|
135
|
+
fs.writeFileSync(path.join(targetRoot, EXTENSION_MARKER), JSON.stringify({
|
|
136
|
+
managedBy: MANAGED_BY,
|
|
137
|
+
kind: 'extension',
|
|
138
|
+
bootstrapProtocol: 1,
|
|
139
|
+
version,
|
|
140
|
+
installedAt: new Date().toISOString()
|
|
141
|
+
}, null, 2) + '\n', 'utf8');
|
|
142
|
+
return { targetRoot, version };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function installManagedNativeVersion({ packageRoot, nativeRoot, version }) {
|
|
146
|
+
const marker = readManagedMarker(nativeRoot, NATIVE_MARKER);
|
|
147
|
+
if (fs.existsSync(nativeRoot) && !isManagedMarker(marker, 'native')) {
|
|
148
|
+
throw new Error('Refusing to replace an unmarked managed native root: ' + nativeRoot);
|
|
149
|
+
}
|
|
150
|
+
fs.mkdirSync(path.join(nativeRoot, 'versions'), { recursive: true });
|
|
151
|
+
fs.mkdirSync(path.join(nativeRoot, 'updates'), { recursive: true });
|
|
152
|
+
fs.mkdirSync(path.join(nativeRoot, 'bootstrap'), { recursive: true });
|
|
153
|
+
|
|
154
|
+
const stage = path.join(nativeRoot, 'versions', '.' + version + '.staging-' + process.pid + '-' + Date.now());
|
|
155
|
+
const target = path.join(nativeRoot, 'versions', version);
|
|
156
|
+
for (const entry of buildRuntimeFileManifest({ packageRoot })) {
|
|
157
|
+
copyFile(entry.sourcePath, path.join(stage, entry.relativePath));
|
|
158
|
+
}
|
|
159
|
+
fs.writeFileSync(path.join(stage, '.codex-overleaf-version.json'), JSON.stringify({ version }, null, 2) + '\n');
|
|
160
|
+
if (fs.existsSync(target)) {
|
|
161
|
+
safeRemove(target);
|
|
162
|
+
}
|
|
163
|
+
fs.renameSync(stage, target);
|
|
164
|
+
|
|
165
|
+
copyFile(
|
|
166
|
+
path.join(packageRoot, 'native-host/src/managedLauncherRuntime.js'),
|
|
167
|
+
path.join(nativeRoot, 'bootstrap/launcher.js')
|
|
168
|
+
);
|
|
169
|
+
const previous = readVersionPointer(nativeRoot, 'active-version');
|
|
170
|
+
if (previous && previous !== version) {
|
|
171
|
+
atomicWrite(path.join(nativeRoot, 'previous-version'), previous + '\n');
|
|
172
|
+
}
|
|
173
|
+
atomicWrite(path.join(nativeRoot, 'active-version'), version + '\n');
|
|
174
|
+
fs.writeFileSync(path.join(nativeRoot, NATIVE_MARKER), JSON.stringify({
|
|
175
|
+
managedBy: MANAGED_BY,
|
|
176
|
+
kind: 'native',
|
|
177
|
+
bootstrapProtocol: 1,
|
|
178
|
+
version,
|
|
179
|
+
installedAt: new Date().toISOString()
|
|
180
|
+
}, null, 2) + '\n', 'utf8');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function installManagedNativeRegistration({ browser, env, extensionId, managedRoot, nativeRoot, platform }) {
|
|
184
|
+
const bridgePath = getDefaultBridgePath({ platform, browser, env });
|
|
185
|
+
const platformPath = platform === 'win32' ? path.win32 : path.posix;
|
|
186
|
+
const bootstrapEntry = platformPath.join(nativeRoot, 'bootstrap', 'launcher.js');
|
|
187
|
+
const bridge = buildLauncher({
|
|
188
|
+
platform,
|
|
189
|
+
nodePath: process.execPath,
|
|
190
|
+
bridgeEntryPath: bootstrapEntry,
|
|
191
|
+
agentPath: bootstrapEntry
|
|
192
|
+
});
|
|
193
|
+
fs.mkdirSync(platformPath.dirname(bridgePath), { recursive: true });
|
|
194
|
+
fs.writeFileSync(bridgePath, bridge, 'utf8');
|
|
195
|
+
if (platform !== 'win32') {
|
|
196
|
+
fs.chmodSync(bridgePath, 0o755);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const target = getNativeHostRegistrationTarget({ platform, browser, env });
|
|
200
|
+
const manifest = buildHostManifest({ extensionId, extensionIds: [extensionId], bridgePath, platform });
|
|
201
|
+
fs.mkdirSync(platformPath.dirname(target.manifestPath), { recursive: true });
|
|
202
|
+
fs.writeFileSync(target.manifestPath, JSON.stringify(manifest, null, 2) + '\n', 'utf8');
|
|
203
|
+
if (target.kind === 'registry') {
|
|
204
|
+
runRegistry(['add', target.registryKey, '/ve', '/t', 'REG_SZ', '/d', target.manifestPath, '/f']);
|
|
205
|
+
}
|
|
206
|
+
fs.writeFileSync(path.join(managedRoot, '.codex-overleaf-managed.json'), JSON.stringify({
|
|
207
|
+
managedBy: MANAGED_BY,
|
|
208
|
+
extensionId,
|
|
209
|
+
browser: target.browser,
|
|
210
|
+
bridgePath,
|
|
211
|
+
manifestPath: target.manifestPath
|
|
212
|
+
}, null, 2) + '\n');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function uninstallManagedDistribution(options = {}) {
|
|
216
|
+
const platform = options.platform || process.platform;
|
|
217
|
+
const browser = options.browser || 'chrome';
|
|
218
|
+
const env = options.env || process.env;
|
|
219
|
+
const managedRoot = path.resolve(options.managedRoot || getDefaultManagedRoot({ platform, env }));
|
|
220
|
+
const extensionRoot = path.resolve(options.extensionRoot || path.join(managedRoot, 'extension'));
|
|
221
|
+
const nativeRoot = path.resolve(options.nativeRoot || path.join(managedRoot, 'native'));
|
|
222
|
+
assertSafeManagedRoot(managedRoot, { packageRoot: path.resolve(__dirname, '../..'), env, platform });
|
|
223
|
+
assertManagedChildRoot(extensionRoot, managedRoot, path, 'extension');
|
|
224
|
+
assertManagedChildRoot(nativeRoot, managedRoot, path, 'native');
|
|
225
|
+
if (fs.existsSync(extensionRoot) && !isManagedMarker(readManagedMarker(extensionRoot, EXTENSION_MARKER), 'extension')) {
|
|
226
|
+
throw new Error('Refusing to remove an unmarked managed extension root.');
|
|
227
|
+
}
|
|
228
|
+
if (fs.existsSync(nativeRoot) && !isManagedMarker(readManagedMarker(nativeRoot, NATIVE_MARKER), 'native')) {
|
|
229
|
+
throw new Error('Refusing to remove an unmarked managed native root.');
|
|
230
|
+
}
|
|
231
|
+
const registration = getNativeHostRegistrationTarget({ platform, browser, env });
|
|
232
|
+
const bridgePath = getDefaultBridgePath({ platform, browser, env });
|
|
233
|
+
if (registration.kind === 'registry') {
|
|
234
|
+
runRegistry(['delete', registration.registryKey, '/f'], { allowMissing: true });
|
|
235
|
+
}
|
|
236
|
+
fs.rmSync(registration.manifestPath, { force: true });
|
|
237
|
+
fs.rmSync(bridgePath, { force: true });
|
|
238
|
+
safeRemove(extensionRoot);
|
|
239
|
+
safeRemove(nativeRoot);
|
|
240
|
+
try {
|
|
241
|
+
if (fs.existsSync(managedRoot) && fs.readdirSync(managedRoot).length === 1 && fs.existsSync(path.join(managedRoot, '.codex-overleaf-managed.json'))) {
|
|
242
|
+
safeRemove(managedRoot);
|
|
243
|
+
}
|
|
244
|
+
} catch (_error) {
|
|
245
|
+
// Managed roots are already removed; leave harmless metadata when another process owns it.
|
|
246
|
+
}
|
|
247
|
+
return { ok: true, managedRoot, extensionRoot, nativeRoot, removed: true };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function assertSafeManagedRoot(root, options = {}) {
|
|
251
|
+
const platformPath = options.platformPath || path;
|
|
252
|
+
const resolved = platformPath.resolve(root);
|
|
253
|
+
const home = platformPath.resolve(options.platform === 'win32'
|
|
254
|
+
? (options.env?.USERPROFILE || os.homedir())
|
|
255
|
+
: (options.env?.HOME || os.homedir()));
|
|
256
|
+
const repo = platformPath.resolve(options.packageRoot || path.resolve(__dirname, '../..'));
|
|
257
|
+
if (resolved === platformPath.parse(resolved).root || resolved === home || resolved === repo) {
|
|
258
|
+
throw new Error('Refusing unsafe managed installation root: ' + root);
|
|
259
|
+
}
|
|
260
|
+
const temp = platformPath.resolve(os.tmpdir());
|
|
261
|
+
const underHome = isSameOrDescendant(resolved, home, platformPath);
|
|
262
|
+
const underTemp = isSameOrDescendant(resolved, temp, platformPath);
|
|
263
|
+
if (!underHome && !underTemp) {
|
|
264
|
+
throw new Error('Managed installation root must stay under the user home or temporary directory: ' + root);
|
|
265
|
+
}
|
|
266
|
+
const trustedBase = underHome ? home : temp;
|
|
267
|
+
if (options.platform !== 'win32' || process.platform === 'win32') {
|
|
268
|
+
assertNoSymlinkAncestors(resolved, trustedBase, platformPath);
|
|
269
|
+
}
|
|
270
|
+
return resolved;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function assertManagedChildRoot(target, managedRoot, platformPath, label) {
|
|
274
|
+
const resolvedTarget = platformPath.resolve(target);
|
|
275
|
+
const resolvedRoot = platformPath.resolve(managedRoot);
|
|
276
|
+
if (resolvedTarget === resolvedRoot || !isSameOrDescendant(resolvedTarget, resolvedRoot, platformPath)) {
|
|
277
|
+
throw new Error('Managed ' + label + ' root must stay inside the managed installation root.');
|
|
278
|
+
}
|
|
279
|
+
if (process.platform !== 'win32' || platformPath === path.win32) {
|
|
280
|
+
assertNoSymlinkAncestors(resolvedTarget, resolvedRoot, platformPath);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function assertNoSymlinkAncestors(target, stopAt, platformPath) {
|
|
285
|
+
let current = platformPath.resolve(target);
|
|
286
|
+
const boundary = platformPath.resolve(stopAt);
|
|
287
|
+
while (current !== boundary) {
|
|
288
|
+
if (fs.existsSync(current) && fs.lstatSync(current).isSymbolicLink()) {
|
|
289
|
+
throw new Error('Managed installation path contains a symlink: ' + current);
|
|
290
|
+
}
|
|
291
|
+
const parent = platformPath.dirname(current);
|
|
292
|
+
if (parent === current || !isSameOrDescendant(parent, boundary, platformPath)) break;
|
|
293
|
+
current = parent;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function isSameOrDescendant(target, parent, platformPath) {
|
|
298
|
+
if (target === parent) return true;
|
|
299
|
+
const relative = platformPath.relative(parent, target);
|
|
300
|
+
return Boolean(relative) && !relative.startsWith('..') && !platformPath.isAbsolute(relative);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function copyPackageTree(packageRoot, sourceRelative, targetRoot, targetRelative, options = {}) {
|
|
304
|
+
const source = path.join(packageRoot, sourceRelative);
|
|
305
|
+
for (const dirent of fs.readdirSync(source, { withFileTypes: true })) {
|
|
306
|
+
const childSourceRelative = path.posix.join(sourceRelative.replace(/\\/g, '/'), dirent.name);
|
|
307
|
+
const childTargetRelative = path.posix.join(targetRelative.replace(/\\/g, '/'), dirent.name);
|
|
308
|
+
if (dirent.isSymbolicLink()) {
|
|
309
|
+
throw new Error('Managed package source cannot contain symlinks: ' + childSourceRelative);
|
|
310
|
+
}
|
|
311
|
+
if (dirent.isDirectory()) {
|
|
312
|
+
copyPackageTree(packageRoot, childSourceRelative, targetRoot, childTargetRelative, options);
|
|
313
|
+
} else if (dirent.isFile() && !options.exclude?.has(childSourceRelative)) {
|
|
314
|
+
copyPackageFile(packageRoot, childSourceRelative, targetRoot, childTargetRelative, options.allowedFiles);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function copyPackageFile(packageRoot, sourceRelative, targetRoot, targetRelative, allowedFiles) {
|
|
320
|
+
if (allowedFiles && !allowedFiles.has(sourceRelative)) {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
copyFile(path.join(packageRoot, sourceRelative), path.join(targetRoot, targetRelative));
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function copyFile(source, target) {
|
|
327
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
328
|
+
fs.copyFileSync(source, target);
|
|
329
|
+
fs.chmodSync(target, fs.statSync(source).mode & 0o777);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function normalizeAllowedFiles(value) {
|
|
333
|
+
if (!value) return null;
|
|
334
|
+
return value instanceof Set ? value : new Set(value);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function readManagedMarker(root, markerName) {
|
|
338
|
+
try {
|
|
339
|
+
return readJson(path.join(root, markerName));
|
|
340
|
+
} catch (_error) {
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function isManagedMarker(marker, kind) {
|
|
346
|
+
return marker?.managedBy === MANAGED_BY && marker?.kind === kind && marker?.bootstrapProtocol === 1;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function readVersionPointer(nativeRoot, name) {
|
|
350
|
+
try {
|
|
351
|
+
const value = fs.readFileSync(path.join(nativeRoot, name), 'utf8').trim();
|
|
352
|
+
return parseSemver(value) ? value : '';
|
|
353
|
+
} catch (_error) {
|
|
354
|
+
return '';
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function atomicWrite(target, content) {
|
|
359
|
+
const temp = target + '.tmp-' + process.pid + '-' + Date.now();
|
|
360
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
361
|
+
fs.writeFileSync(temp, content, { mode: 0o600 });
|
|
362
|
+
fs.renameSync(temp, target);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function runRegistry(args, options = {}) {
|
|
366
|
+
const result = spawnSync(process.env.CODEX_OVERLEAF_REG_EXE || 'reg.exe', args, { encoding: 'utf8' });
|
|
367
|
+
if (result.error) throw result.error;
|
|
368
|
+
if (result.status !== 0 && !options.allowMissing) {
|
|
369
|
+
throw new Error(result.stderr || result.stdout || 'Windows registry command failed.');
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function resolveForPlatform(value, platformPath) {
|
|
374
|
+
return platformPath.isAbsolute(value) ? value : platformPath.resolve(value);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function safeRemove(target) {
|
|
378
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function readJson(filePath) {
|
|
382
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
module.exports = {
|
|
386
|
+
EXTENSION_MARKER,
|
|
387
|
+
MANAGED_BY,
|
|
388
|
+
NATIVE_MARKER,
|
|
389
|
+
assertSafeManagedRoot,
|
|
390
|
+
buildManagedExtensionTree,
|
|
391
|
+
installManagedDistribution,
|
|
392
|
+
uninstallManagedDistribution
|
|
393
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const { spawn } = require('node:child_process');
|
|
7
|
+
|
|
8
|
+
const nativeRoot = path.resolve(__dirname, '..');
|
|
9
|
+
const managedRoot = path.dirname(nativeRoot);
|
|
10
|
+
const extensionRoot = path.join(managedRoot, 'extension');
|
|
11
|
+
const activeVersion = readVersionPointer('active-version');
|
|
12
|
+
const previousVersion = readVersionPointer('previous-version');
|
|
13
|
+
const selectedVersion = selectRunnableVersion(activeVersion, previousVersion);
|
|
14
|
+
|
|
15
|
+
if (!selectedVersion) {
|
|
16
|
+
process.stderr.write('Codex Overleaf managed native runtime is unavailable. Run install-managed again.\n');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const versionRoot = path.join(nativeRoot, 'versions', selectedVersion);
|
|
21
|
+
const entryPath = path.join(versionRoot, 'native-host', 'src', 'index.js');
|
|
22
|
+
const agentPath = path.join(versionRoot, 'scripts', 'codex-json-agent.mjs');
|
|
23
|
+
const env = {
|
|
24
|
+
...process.env,
|
|
25
|
+
CODEX_OVERLEAF_MANAGED: '1',
|
|
26
|
+
CODEX_OVERLEAF_MANAGED_ROOT: managedRoot,
|
|
27
|
+
CODEX_OVERLEAF_MANAGED_NATIVE_ROOT: nativeRoot,
|
|
28
|
+
CODEX_OVERLEAF_MANAGED_EXTENSION_ROOT: extensionRoot,
|
|
29
|
+
CODEX_OVERLEAF_ACTIVE_VERSION: selectedVersion,
|
|
30
|
+
CODEX_OVERLEAF_AGENT_FILE: process.execPath,
|
|
31
|
+
CODEX_OVERLEAF_AGENT_ARGS_JSON: JSON.stringify([agentPath])
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const child = spawn(process.execPath, [entryPath], {
|
|
35
|
+
env,
|
|
36
|
+
stdio: 'inherit',
|
|
37
|
+
windowsHide: true
|
|
38
|
+
});
|
|
39
|
+
child.on('error', error => {
|
|
40
|
+
process.stderr.write('Failed to start Codex Overleaf managed runtime: ' + error.message + '\n');
|
|
41
|
+
process.exit(1);
|
|
42
|
+
});
|
|
43
|
+
child.on('exit', (code, signal) => {
|
|
44
|
+
if (signal) {
|
|
45
|
+
process.kill(process.pid, signal);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
process.exit(Number.isInteger(code) ? code : 1);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
function readVersionPointer(name) {
|
|
52
|
+
try {
|
|
53
|
+
const value = fs.readFileSync(path.join(nativeRoot, name), 'utf8').trim();
|
|
54
|
+
return /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(value) ? value : '';
|
|
55
|
+
} catch (_error) {
|
|
56
|
+
return '';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function selectRunnableVersion(active, previous) {
|
|
61
|
+
for (const version of [active, previous]) {
|
|
62
|
+
if (!version) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const entry = path.join(nativeRoot, 'versions', version, 'native-host', 'src', 'index.js');
|
|
66
|
+
if (fs.existsSync(entry) && fs.statSync(entry).isFile()) {
|
|
67
|
+
return version;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return '';
|
|
71
|
+
}
|
|
@@ -27,6 +27,19 @@ function getDefaultRuntimeRoot(options = {}) {
|
|
|
27
27
|
return platformPath.join(getCodexOverleafHome(options), 'native-host-runtime');
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
function getDefaultManagedRoot(options = {}) {
|
|
31
|
+
const platformPath = getPathModule(options);
|
|
32
|
+
return platformPath.join(getCodexOverleafHome(options), 'managed');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getDefaultManagedExtensionRoot(options = {}) {
|
|
36
|
+
return getPathModule(options).join(getDefaultManagedRoot(options), 'extension');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function getDefaultManagedNativeRoot(options = {}) {
|
|
40
|
+
return getPathModule(options).join(getDefaultManagedRoot(options), 'native');
|
|
41
|
+
}
|
|
42
|
+
|
|
30
43
|
function getDefaultBridgePath(options = {}) {
|
|
31
44
|
const platformPath = getPathModule(options);
|
|
32
45
|
const bridgeName = getNativeHostPlatform(options) === 'win32'
|
|
@@ -104,6 +117,9 @@ function throwUnsupportedBrowser(platform, browser) {
|
|
|
104
117
|
module.exports = {
|
|
105
118
|
getCodexOverleafHome,
|
|
106
119
|
getDefaultBridgePath,
|
|
120
|
+
getDefaultManagedExtensionRoot,
|
|
121
|
+
getDefaultManagedNativeRoot,
|
|
122
|
+
getDefaultManagedRoot,
|
|
107
123
|
getDefaultRuntimeRoot,
|
|
108
124
|
getHomeDir,
|
|
109
125
|
getNativeHostPlatform,
|
|
@@ -11,6 +11,9 @@ const DEFAULT_RUNTIME_ROOT = getDefaultRuntimeRoot();
|
|
|
11
11
|
|
|
12
12
|
function buildRuntimeFileManifest(options = {}) {
|
|
13
13
|
const packageRoot = path.resolve(options.packageRoot || getDefaultPackageRoot());
|
|
14
|
+
const allowedFiles = options.allowedFiles
|
|
15
|
+
? new Set([...options.allowedFiles].map((relativePath) => String(relativePath).replace(/\\/g, '/')))
|
|
16
|
+
: null;
|
|
14
17
|
const files = [
|
|
15
18
|
fileEntry(packageRoot, 'package.json'),
|
|
16
19
|
...directoryEntries(packageRoot, 'native-host/src'),
|
|
@@ -20,7 +23,9 @@ function buildRuntimeFileManifest(options = {}) {
|
|
|
20
23
|
fileEntry(packageRoot, 'scripts/uninstall-native-host.mjs')
|
|
21
24
|
];
|
|
22
25
|
|
|
23
|
-
return files
|
|
26
|
+
return files
|
|
27
|
+
.filter((entry) => !allowedFiles || allowedFiles.has(entry.relativePath))
|
|
28
|
+
.sort((left, right) => left.relativePath.localeCompare(right.relativePath));
|
|
24
29
|
}
|
|
25
30
|
|
|
26
31
|
function assertSafeManagedRuntimeRoot(runtimeRoot, options = {}) {
|
|
@@ -1155,9 +1155,17 @@ function errorResponse(id, code, message, details = {}) {
|
|
|
1155
1155
|
};
|
|
1156
1156
|
}
|
|
1157
1157
|
|
|
1158
|
+
function getActiveNativeWorkState() {
|
|
1159
|
+
return {
|
|
1160
|
+
projectLocks: activeProjectLocks.size,
|
|
1161
|
+
runControllers: activeRunControllers.size
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1158
1165
|
module.exports = {
|
|
1159
1166
|
NATIVE_REQUEST_QUOTAS,
|
|
1160
1167
|
buildDefaultTaskResult,
|
|
1168
|
+
getActiveNativeWorkState,
|
|
1161
1169
|
handleRequest,
|
|
1162
1170
|
parseAgentEventLines,
|
|
1163
1171
|
purgeExpiredPendingPlans
|