ai-extension-preview 0.1.5 → 0.1.6
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.
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import webExt from 'web-ext';
|
|
2
1
|
import path from 'path';
|
|
3
2
|
import { spawn } from 'child_process';
|
|
4
3
|
import fs from 'fs-extra';
|
|
@@ -36,82 +35,59 @@ export const BrowserPlugin = {
|
|
|
36
35
|
await ctx.actions.runAction('core:log', { level: 'error', message: 'Chrome not found for detached launch.' });
|
|
37
36
|
return false;
|
|
38
37
|
}
|
|
39
|
-
|
|
40
|
-
let
|
|
41
|
-
const
|
|
42
|
-
|
|
38
|
+
const isWSL = fs.existsSync('/mnt/c');
|
|
39
|
+
let executable = chromePath; // Define in scope
|
|
40
|
+
const STAGING_DIR = isWSL ? '/mnt/c/Temp/ai-ext-preview' : path.join(config.workDir, '../staging');
|
|
41
|
+
const WIN_PROFILE_DIR = 'C:/Temp/ai-ext-profile';
|
|
42
|
+
// For native windows/linux, use local staging path
|
|
43
|
+
const EXTENSION_PATH = isWSL ? 'C:/Temp/ai-ext-preview' : STAGING_DIR;
|
|
44
|
+
// --- SYNC FUNCTION ---
|
|
45
|
+
const syncToStaging = async () => {
|
|
43
46
|
try {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
// Pre-flight check: Validating WSL Interop
|
|
47
|
-
// We try to run cmd.exe simply to check if the OS allows it.
|
|
48
|
-
try {
|
|
49
|
-
await new Promise((resolve, reject) => {
|
|
50
|
-
const check = spawn('cmd.exe', ['/c', 'ver'], { stdio: 'ignore' });
|
|
51
|
-
check.on('error', reject);
|
|
52
|
-
check.on('close', (code) => {
|
|
53
|
-
if (code === 0)
|
|
54
|
-
resolve(true);
|
|
55
|
-
else
|
|
56
|
-
reject(new Error(`Exit code ${code}`));
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
catch (interopErr) {
|
|
61
|
-
await ctx.actions.runAction('core:log', { level: 'error', message: `[FATAL] WSL Interop is broken on this system.` });
|
|
62
|
-
await ctx.actions.runAction('core:log', { level: 'error', message: `Linux cannot launch Windows applications (cmd.exe failed).` });
|
|
63
|
-
await ctx.actions.runAction('core:log', { level: 'error', message: `PLEASE FIX: Open PowerShell as Admin and run 'wsl --shutdown', then restart.` });
|
|
64
|
-
return false;
|
|
65
|
-
}
|
|
66
|
-
await ctx.actions.runAction('core:log', { level: 'info', message: `[WSL] Copying extension to Windows Temp: ${WIN_PATH_FOR_CHROME}` });
|
|
67
|
-
// Ensure Windows temp dir exists and is clean
|
|
68
|
-
if (fs.existsSync(WIN_TEMP_DIR)) {
|
|
69
|
-
fs.removeSync(WIN_TEMP_DIR);
|
|
47
|
+
if (fs.existsSync(STAGING_DIR)) {
|
|
48
|
+
fs.emptyDirSync(STAGING_DIR);
|
|
70
49
|
}
|
|
71
|
-
fs.ensureDirSync(
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
50
|
+
fs.ensureDirSync(STAGING_DIR);
|
|
51
|
+
fs.copySync(DIST_DIR, STAGING_DIR);
|
|
52
|
+
await ctx.actions.runAction('core:log', { level: 'info', message: `Synced code to Staging: ${EXTENSION_PATH}` });
|
|
53
|
+
// Emit staged event for ServerPlugin (optional for now, but good practice)
|
|
54
|
+
ctx.events.emit('browser:staged', { path: STAGING_DIR });
|
|
75
55
|
}
|
|
76
|
-
catch (
|
|
77
|
-
await ctx.actions.runAction('core:log', { level: 'error', message: `Failed to
|
|
78
|
-
// Fallback to original path (might fail if not mapped)
|
|
56
|
+
catch (err) {
|
|
57
|
+
await ctx.actions.runAction('core:log', { level: 'error', message: `Failed to sync to staging: ${err.message}` });
|
|
79
58
|
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
await
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
59
|
+
};
|
|
60
|
+
// Initial Sync
|
|
61
|
+
await syncToStaging();
|
|
62
|
+
// Listen for updates and re-sync
|
|
63
|
+
ctx.events.on('downloader:updated', async (data) => {
|
|
64
|
+
await ctx.actions.runAction('core:log', { level: 'info', message: 'Update detected. Syncing to staging...' });
|
|
65
|
+
await syncToStaging();
|
|
66
|
+
});
|
|
67
|
+
await ctx.actions.runAction('core:log', { level: 'info', message: 'Browser running in Detached Mode.' });
|
|
68
|
+
// Launch Logic
|
|
88
69
|
if (isWSL) {
|
|
89
70
|
const driveLetter = chromePath.match(/\/mnt\/([a-z])\//)?.[1] || 'c';
|
|
90
71
|
const winChromePath = chromePath
|
|
91
72
|
.replace(new RegExp(`^/mnt/${driveLetter}/`), `${driveLetter.toUpperCase()}:\\`)
|
|
92
73
|
.replace(/\//g, '\\');
|
|
93
|
-
await ctx.actions.runAction('core:log', { level: 'info', message: `WSL: Creating launch script...` });
|
|
94
|
-
// Use backslashes for Windows paths in the batch file
|
|
95
74
|
const winDist = 'C:\\Temp\\ai-ext-preview';
|
|
96
75
|
const winProfile = 'C:\\Temp\\ai-ext-profile';
|
|
97
|
-
// Create the batch file content
|
|
98
76
|
const batContent = `@echo off
|
|
99
|
-
start "" "${winChromePath}" --load-extension="${winDist}" --user-data-dir="${winProfile}" --no-first-run --no-default-browser-check --disable-gpu about:blank
|
|
100
|
-
exit
|
|
101
|
-
`;
|
|
102
|
-
const batPath = '
|
|
77
|
+
start "" "${winChromePath}" --load-extension="${winDist}" --user-data-dir="${winProfile}" --no-first-run --no-default-browser-check --disable-gpu about:blank
|
|
78
|
+
exit
|
|
79
|
+
`;
|
|
80
|
+
const batPath = path.join(STAGING_DIR, 'launch.bat');
|
|
103
81
|
const winBatPath = 'C:\\Temp\\ai-ext-preview\\launch.bat';
|
|
104
82
|
try {
|
|
105
83
|
fs.writeFileSync(batPath, batContent);
|
|
106
84
|
}
|
|
107
85
|
catch (e) {
|
|
108
|
-
|
|
109
|
-
|
|
86
|
+
// Fallback if staging writes fail inside WSL mount for some reason?
|
|
87
|
+
// Should satisfy since we verified interop before?
|
|
88
|
+
// Actually verification was removed in this block, let's assume it works or fail.
|
|
110
89
|
}
|
|
111
|
-
await ctx.actions.runAction('core:log', { level: 'info', message: `EXEC: ${winBatPath}` });
|
|
112
|
-
// Execute the batch file via cmd.exe using spawn + PATH lookup
|
|
113
90
|
const cli = 'cmd.exe';
|
|
114
|
-
await ctx.actions.runAction('core:log', { level: 'info', message: `SPAWN (WSL): ${cli} /c ${winBatPath}` });
|
|
115
91
|
const subprocess = spawn(cli, ['/c', winBatPath], {
|
|
116
92
|
detached: true,
|
|
117
93
|
stdio: 'ignore',
|
|
@@ -121,22 +97,20 @@ exit
|
|
|
121
97
|
return true;
|
|
122
98
|
}
|
|
123
99
|
else {
|
|
124
|
-
//
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
100
|
+
// Native Windows / Linux
|
|
101
|
+
const safeDist = path.resolve(STAGING_DIR);
|
|
102
|
+
// Linux/Mac/Win Native Profile Path
|
|
103
|
+
// We need a stable profile path for native too to keep Detached session alive/resuable
|
|
104
|
+
const safeProfile = path.join(path.dirname(config.workDir), 'profile'); // ~/.ai-extension-preview/profile
|
|
128
105
|
await ctx.actions.runAction('core:log', { level: 'info', message: `SPAWN: ${executable}` });
|
|
129
|
-
await ctx.actions.runAction('core:log', { level: 'info', message: `EXT PATH: ${safeDist}` });
|
|
130
|
-
// Reconstruct args with safe paths
|
|
131
106
|
const cleanArgs = [
|
|
132
107
|
`--load-extension=${safeDist}`,
|
|
133
108
|
`--user-data-dir=${safeProfile}`,
|
|
134
109
|
'--no-first-run',
|
|
135
110
|
'--no-default-browser-check',
|
|
136
111
|
'--disable-gpu',
|
|
137
|
-
'chrome://extensions'
|
|
112
|
+
'chrome://extensions'
|
|
138
113
|
];
|
|
139
|
-
await ctx.actions.runAction('core:log', { level: 'info', message: `ARGS: ${cleanArgs.join(' ')}` });
|
|
140
114
|
const subprocess = spawn(executable, cleanArgs, {
|
|
141
115
|
detached: true,
|
|
142
116
|
stdio: 'ignore'
|
|
@@ -148,62 +122,9 @@ exit
|
|
|
148
122
|
ctx.actions.registerAction({
|
|
149
123
|
id: 'browser:start',
|
|
150
124
|
handler: async () => {
|
|
151
|
-
//
|
|
152
|
-
//
|
|
153
|
-
|
|
154
|
-
await ctx.actions.runAction('core:log', { level: 'warning', message: 'Windows detected: Forcing Detached Mode for reliability.' });
|
|
155
|
-
return await launchDetached();
|
|
156
|
-
}
|
|
157
|
-
await ctx.actions.runAction('core:log', { level: 'info', message: 'Launching browser...' });
|
|
158
|
-
try {
|
|
159
|
-
// Try web-ext first
|
|
160
|
-
const runResult = await webExt.cmd.run({
|
|
161
|
-
sourceDir: DIST_DIR,
|
|
162
|
-
target: 'chromium',
|
|
163
|
-
browserConsole: false,
|
|
164
|
-
startUrl: ['https://google.com'],
|
|
165
|
-
noInput: true,
|
|
166
|
-
keepProfileChanges: false,
|
|
167
|
-
args: [
|
|
168
|
-
'--start-maximized',
|
|
169
|
-
'--no-sandbox',
|
|
170
|
-
'--disable-gpu',
|
|
171
|
-
'--disable-dev-shm-usage'
|
|
172
|
-
]
|
|
173
|
-
}, {
|
|
174
|
-
shouldExitProgram: false
|
|
175
|
-
});
|
|
176
|
-
runner = runResult;
|
|
177
|
-
await ctx.actions.runAction('core:log', { level: 'success', message: 'Browser session ended.' });
|
|
178
|
-
return true;
|
|
179
|
-
}
|
|
180
|
-
catch (err) {
|
|
181
|
-
// Check for expected environment failures
|
|
182
|
-
if (err.code === 'ECONNRESET' || err.message?.includes('CDP connection closed')) {
|
|
183
|
-
// Log specific WSL message for clarity
|
|
184
|
-
await ctx.actions.runAction('core:log', { level: 'warning', message: 'WSL: CDP connection dropped (expected). Browser is running detached.' });
|
|
185
|
-
await ctx.actions.runAction('core:log', { level: 'info', message: 'Please reload extension manually in Chrome if needed.' });
|
|
186
|
-
return await launchDetached();
|
|
187
|
-
}
|
|
188
|
-
if (err.code !== 'ECONNRESET') {
|
|
189
|
-
await ctx.actions.runAction('core:log', { level: 'error', message: `Browser failed: ${err.message}` });
|
|
190
|
-
}
|
|
191
|
-
return false;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
});
|
|
195
|
-
ctx.events.on('downloader:updated', async () => {
|
|
196
|
-
if (runner && runner.reloadAllExtensions) {
|
|
197
|
-
await ctx.actions.runAction('core:log', { level: 'info', message: 'Triggering browser reload...' });
|
|
198
|
-
try {
|
|
199
|
-
runner.reloadAllExtensions();
|
|
200
|
-
}
|
|
201
|
-
catch (e) {
|
|
202
|
-
// Ignore
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
else {
|
|
206
|
-
await ctx.actions.runAction('core:log', { level: 'info', message: 'Update installed. Please reload extension in Chrome.' });
|
|
125
|
+
// Force Detached Mode for Reliability on ALL platforms
|
|
126
|
+
// This creates the stable "Staging" workflow we want.
|
|
127
|
+
return await launchDetached();
|
|
207
128
|
}
|
|
208
129
|
});
|
|
209
130
|
}
|
|
@@ -69,7 +69,7 @@ export const DownloaderPlugin = {
|
|
|
69
69
|
if (success) {
|
|
70
70
|
lastModified = newVersion;
|
|
71
71
|
fs.writeFileSync(VERSION_FILE, newVersion);
|
|
72
|
-
ctx.events.emit('downloader:updated', { version: job.version });
|
|
72
|
+
ctx.events.emit('downloader:updated', { version: job.version, jobId: config.jobId });
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
isChecking = false;
|
|
@@ -101,24 +101,35 @@ export const DownloaderPlugin = {
|
|
|
101
101
|
try {
|
|
102
102
|
const HOT_RELOAD_CODE = `
|
|
103
103
|
const EVENT_SOURCE_URL = 'http://localhost:3500/status';
|
|
104
|
+
const CURRENT_JOB_ID = '${config.jobId}';
|
|
104
105
|
let lastVersion = null;
|
|
106
|
+
let lastJobId = null;
|
|
105
107
|
|
|
106
108
|
setInterval(async () => {
|
|
107
109
|
try {
|
|
108
110
|
const res = await fetch(EVENT_SOURCE_URL);
|
|
109
111
|
const data = await res.json();
|
|
110
112
|
|
|
113
|
+
// 1. Job ID Swap (User switched project)
|
|
114
|
+
if (data.jobId && data.jobId !== CURRENT_JOB_ID) {
|
|
115
|
+
console.log('[Hot Reload] Job Swap detected. Reloading...');
|
|
116
|
+
chrome.runtime.reload();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 2. Version Bump (Same project, new build)
|
|
111
121
|
if (lastVersion && data.version !== lastVersion) {
|
|
112
122
|
console.log('[Hot Reload] New version detected:', data.version);
|
|
113
123
|
chrome.runtime.reload();
|
|
114
124
|
}
|
|
115
125
|
|
|
116
126
|
lastVersion = data.version;
|
|
127
|
+
lastJobId = data.jobId;
|
|
117
128
|
} catch (err) {
|
|
118
129
|
// Build tool might be offline
|
|
119
130
|
}
|
|
120
131
|
}, 1000);
|
|
121
|
-
console.log('[Hot Reload] Active');
|
|
132
|
+
console.log('[Hot Reload] Active for Job:', CURRENT_JOB_ID);
|
|
122
133
|
`;
|
|
123
134
|
const hotReloadPath = path.join(DIST_DIR, 'hot-reload.js');
|
|
124
135
|
await fs.writeFile(hotReloadPath, HOT_RELOAD_CODE);
|
|
@@ -23,8 +23,12 @@ export const ServerPlugin = {
|
|
|
23
23
|
return;
|
|
24
24
|
}
|
|
25
25
|
if (req.url === '/status') {
|
|
26
|
+
const currentJobId = ctx.host.config.jobId;
|
|
26
27
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
27
|
-
res.end(JSON.stringify({
|
|
28
|
+
res.end(JSON.stringify({
|
|
29
|
+
version: currentVersion,
|
|
30
|
+
jobId: currentJobId
|
|
31
|
+
}));
|
|
28
32
|
}
|
|
29
33
|
else {
|
|
30
34
|
res.writeHead(404);
|