ai-extension-preview 0.1.5 → 0.1.7
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/index.js
CHANGED
|
File without changes
|
|
@@ -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,96 @@ 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
|
+
// Note: We will evaluate actual extension root later, but base is STAGING_DIR
|
|
44
|
+
const EXTENSION_PATH = isWSL ? 'C:/Temp/ai-ext-preview' : STAGING_DIR;
|
|
45
|
+
// --- SYNC FUNCTION ---
|
|
46
|
+
const syncToStaging = async () => {
|
|
43
47
|
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;
|
|
48
|
+
if (fs.existsSync(STAGING_DIR)) {
|
|
49
|
+
fs.emptyDirSync(STAGING_DIR);
|
|
65
50
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
51
|
+
fs.ensureDirSync(STAGING_DIR);
|
|
52
|
+
fs.copySync(DIST_DIR, STAGING_DIR);
|
|
53
|
+
await ctx.actions.runAction('core:log', { level: 'info', message: `Synced code to Staging` });
|
|
54
|
+
// Emit staged event for ServerPlugin (optional for now, but good practice)
|
|
55
|
+
ctx.events.emit('browser:staged', { path: STAGING_DIR });
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
await ctx.actions.runAction('core:log', { level: 'error', message: `Failed to sync to staging: ${err.message}` });
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
// --- Helper to find actual extension root (handle nested folder in zip) ---
|
|
62
|
+
const findExtensionRoot = (dir) => {
|
|
63
|
+
if (fs.existsSync(path.join(dir, 'manifest.json')))
|
|
64
|
+
return dir;
|
|
65
|
+
// Check immediate subdirectories (depth 1)
|
|
66
|
+
try {
|
|
67
|
+
const items = fs.readdirSync(dir);
|
|
68
|
+
for (const item of items) {
|
|
69
|
+
const fullPath = path.join(dir, item);
|
|
70
|
+
if (fs.statSync(fullPath).isDirectory()) {
|
|
71
|
+
if (fs.existsSync(path.join(fullPath, 'manifest.json'))) {
|
|
72
|
+
return fullPath;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
70
75
|
}
|
|
71
|
-
fs.ensureDirSync(WIN_TEMP_DIR);
|
|
72
|
-
// Copy dist content
|
|
73
|
-
fs.copySync(DIST_DIR, WIN_TEMP_DIR);
|
|
74
|
-
extensionPath = WIN_PATH_FOR_CHROME;
|
|
75
76
|
}
|
|
76
|
-
catch (
|
|
77
|
-
|
|
78
|
-
// Fallback to original path (might fail if not mapped)
|
|
77
|
+
catch (e) {
|
|
78
|
+
// Dir might be empty or invalid
|
|
79
79
|
}
|
|
80
|
+
return null;
|
|
81
|
+
};
|
|
82
|
+
// Initial Sync
|
|
83
|
+
await syncToStaging();
|
|
84
|
+
// Resolve proper root AFTER sync
|
|
85
|
+
let extensionRoot = findExtensionRoot(STAGING_DIR) || STAGING_DIR;
|
|
86
|
+
// Check if we found a valid root
|
|
87
|
+
if (!fs.existsSync(path.join(extensionRoot, 'manifest.json'))) {
|
|
88
|
+
await ctx.actions.runAction('core:log', { level: 'error', message: `[CRITICAL] manifest.json not found in ${STAGING_DIR}. Extension will not load.` });
|
|
89
|
+
}
|
|
90
|
+
else if (extensionRoot !== STAGING_DIR) {
|
|
91
|
+
await ctx.actions.runAction('core:log', { level: 'info', message: `Detected nested extension at: ${path.basename(extensionRoot)}` });
|
|
80
92
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
//
|
|
93
|
+
// Listen for updates and re-sync
|
|
94
|
+
ctx.events.on('downloader:updated', async (data) => {
|
|
95
|
+
await ctx.actions.runAction('core:log', { level: 'info', message: 'Update detected. Syncing to staging...' });
|
|
96
|
+
await syncToStaging();
|
|
97
|
+
});
|
|
98
|
+
await ctx.actions.runAction('core:log', { level: 'info', message: 'Browser running in Detached Mode.' });
|
|
99
|
+
// Launch Logic
|
|
88
100
|
if (isWSL) {
|
|
89
101
|
const driveLetter = chromePath.match(/\/mnt\/([a-z])\//)?.[1] || 'c';
|
|
90
102
|
const winChromePath = chromePath
|
|
91
103
|
.replace(new RegExp(`^/mnt/${driveLetter}/`), `${driveLetter.toUpperCase()}:\\`)
|
|
92
104
|
.replace(/\//g, '\\');
|
|
93
|
-
|
|
94
|
-
//
|
|
95
|
-
|
|
105
|
+
// Calculate Windows path for the extension root
|
|
106
|
+
// Base Win: C:\Temp\ai-ext-preview
|
|
107
|
+
// Base Linux: /mnt/c/Temp/ai-ext-preview
|
|
108
|
+
// If extensionRoot is /mnt/c/Temp/ai-ext-preview/subdir => C:\Temp\ai-ext-preview\subdir
|
|
109
|
+
// Use relative path logic to be safe
|
|
110
|
+
const baseLinux = '/mnt/c/Temp/ai-ext-preview';
|
|
111
|
+
const relative = path.relative(baseLinux, extensionRoot);
|
|
112
|
+
const winDistRoot = relative ? `C:\\Temp\\ai-ext-preview\\${relative}` : 'C:\\Temp\\ai-ext-preview';
|
|
113
|
+
const finalWinDist = winDistRoot.replace(/\//g, '\\');
|
|
96
114
|
const winProfile = 'C:\\Temp\\ai-ext-profile';
|
|
97
|
-
// Create the batch file content
|
|
98
115
|
const batContent = `@echo off
|
|
99
|
-
start "" "${winChromePath}" --load-extension="${
|
|
116
|
+
start "" "${winChromePath}" --load-extension="${finalWinDist}" --user-data-dir="${winProfile}" --no-first-run --no-default-browser-check --disable-gpu about:blank
|
|
100
117
|
exit
|
|
101
118
|
`;
|
|
102
|
-
const batPath = '
|
|
119
|
+
const batPath = path.join(STAGING_DIR, 'launch.bat');
|
|
103
120
|
const winBatPath = 'C:\\Temp\\ai-ext-preview\\launch.bat';
|
|
104
121
|
try {
|
|
105
122
|
fs.writeFileSync(batPath, batContent);
|
|
106
123
|
}
|
|
107
124
|
catch (e) {
|
|
108
|
-
|
|
109
|
-
return false;
|
|
125
|
+
// Fallback if staging writes fail inside WSL mount for some reason?
|
|
110
126
|
}
|
|
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
127
|
const cli = 'cmd.exe';
|
|
114
|
-
await ctx.actions.runAction('core:log', { level: 'info', message: `SPAWN (WSL): ${cli} /c ${winBatPath}` });
|
|
115
128
|
const subprocess = spawn(cli, ['/c', winBatPath], {
|
|
116
129
|
detached: true,
|
|
117
130
|
stdio: 'ignore',
|
|
@@ -121,22 +134,20 @@ exit
|
|
|
121
134
|
return true;
|
|
122
135
|
}
|
|
123
136
|
else {
|
|
124
|
-
//
|
|
125
|
-
//
|
|
126
|
-
const safeDist = path.resolve(
|
|
127
|
-
const safeProfile = path.
|
|
137
|
+
// Native Windows / Linux
|
|
138
|
+
// Use extensionRoot which points to the detected subfolder or root
|
|
139
|
+
const safeDist = path.resolve(extensionRoot);
|
|
140
|
+
const safeProfile = path.join(path.dirname(config.workDir), 'profile'); // ~/.ai-extension-preview/profile
|
|
128
141
|
await ctx.actions.runAction('core:log', { level: 'info', message: `SPAWN: ${executable}` });
|
|
129
142
|
await ctx.actions.runAction('core:log', { level: 'info', message: `EXT PATH: ${safeDist}` });
|
|
130
|
-
// Reconstruct args with safe paths
|
|
131
143
|
const cleanArgs = [
|
|
132
144
|
`--load-extension=${safeDist}`,
|
|
133
145
|
`--user-data-dir=${safeProfile}`,
|
|
134
146
|
'--no-first-run',
|
|
135
147
|
'--no-default-browser-check',
|
|
136
148
|
'--disable-gpu',
|
|
137
|
-
'chrome://extensions'
|
|
149
|
+
'chrome://extensions'
|
|
138
150
|
];
|
|
139
|
-
await ctx.actions.runAction('core:log', { level: 'info', message: `ARGS: ${cleanArgs.join(' ')}` });
|
|
140
151
|
const subprocess = spawn(executable, cleanArgs, {
|
|
141
152
|
detached: true,
|
|
142
153
|
stdio: 'ignore'
|
|
@@ -148,62 +159,9 @@ exit
|
|
|
148
159
|
ctx.actions.registerAction({
|
|
149
160
|
id: 'browser:start',
|
|
150
161
|
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.' });
|
|
162
|
+
// Force Detached Mode for Reliability on ALL platforms
|
|
163
|
+
// This creates the stable "Staging" workflow we want.
|
|
164
|
+
return await launchDetached();
|
|
207
165
|
}
|
|
208
166
|
});
|
|
209
167
|
}
|
|
@@ -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);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-extension-preview",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Local preview tool for AI Extension Builder",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"author": "AI Extension Builder",
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"scripts": {
|
|
23
|
-
"build": "shx rm -rf dist && tsc -b",
|
|
23
|
+
"build": "shx rm -rf dist && tsc -b && shx chmod +x dist/index.js",
|
|
24
24
|
"start": "tsx src/index.ts",
|
|
25
25
|
"dev": "tsx watch src/index.ts",
|
|
26
26
|
"preview": "node dist/index.js"
|
|
@@ -49,4 +49,4 @@
|
|
|
49
49
|
"tsx": "^4.21.0",
|
|
50
50
|
"typescript": "^5.7.2"
|
|
51
51
|
}
|
|
52
|
-
}
|
|
52
|
+
}
|