ai-extension-preview 0.1.9 → 0.1.11
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.
|
@@ -97,11 +97,14 @@ export const BrowserPlugin = {
|
|
|
97
97
|
if (!isWSL && process.platform === 'win32') {
|
|
98
98
|
executable = normalizePathToWindows(chromePath);
|
|
99
99
|
}
|
|
100
|
-
const
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
100
|
+
const isWin = process.platform === 'win32';
|
|
101
|
+
const STAGING_DIR = isWSL
|
|
102
|
+
? '/mnt/c/Temp/ai-ext-preview'
|
|
103
|
+
: (isWin ? 'C:\\Temp\\ai-ext-preview' : path.join(config.workDir, '../staging'));
|
|
104
|
+
// On Windows (Native or WSL host), Chrome sees:
|
|
105
|
+
const EXTENSION_PATH = (isWSL || isWin) ? 'C:\\Temp\\ai-ext-preview' : STAGING_DIR;
|
|
106
|
+
// Clean profile path for everyone
|
|
107
|
+
const WIN_PROFILE_DIR = 'C:\\Temp\\ai-ext-profile';
|
|
105
108
|
// --- SYNC FUNCTION ---
|
|
106
109
|
const syncToStaging = async () => {
|
|
107
110
|
try {
|
|
@@ -111,12 +114,6 @@ export const BrowserPlugin = {
|
|
|
111
114
|
fs.ensureDirSync(STAGING_DIR);
|
|
112
115
|
fs.copySync(DIST_DIR, STAGING_DIR);
|
|
113
116
|
await ctx.actions.runAction('core:log', { level: 'info', message: `Synced code to Staging` });
|
|
114
|
-
// DEBUG: Log contents of staging
|
|
115
|
-
try {
|
|
116
|
-
const files = fs.readdirSync(STAGING_DIR);
|
|
117
|
-
await ctx.actions.runAction('core:log', { level: 'info', message: `Staging Contents: ${files.join(', ')}` });
|
|
118
|
-
}
|
|
119
|
-
catch (e) { }
|
|
120
117
|
// Emit staged event for ServerPlugin (optional for now, but good practice)
|
|
121
118
|
ctx.events.emit('browser:staged', { path: STAGING_DIR });
|
|
122
119
|
}
|
|
@@ -159,48 +156,22 @@ export const BrowserPlugin = {
|
|
|
159
156
|
// 1. Use Windows User Profile for staging to avoid Permission/Path issues
|
|
160
157
|
// 2. Use PowerShell script to launch Chrome to reliably pass arguments
|
|
161
158
|
// -------------------------------------------------------------------------
|
|
162
|
-
// 1.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
const execAsync = util.promisify(exec);
|
|
169
|
-
const { stdout } = await execAsync('cmd.exe /c echo %USERPROFILE%', { encoding: 'utf8' });
|
|
170
|
-
userProfileWin = stdout.trim();
|
|
171
|
-
}
|
|
172
|
-
catch (e) {
|
|
173
|
-
await ctx.actions.runAction('core:log', { level: 'error', message: 'Failed to detect Windows User Profile. Defaulting to C:\\Temp' });
|
|
174
|
-
userProfileWin = 'C:\\Temp';
|
|
175
|
-
}
|
|
176
|
-
const stagingDirName = '.ai-extension-preview';
|
|
177
|
-
const stagingDirWin = path.posix.join(userProfileWin.replace(/\\/g, '/'), stagingDirName).replace(/\//g, '\\');
|
|
178
|
-
// Map Win Path -> WSL Path for copying
|
|
179
|
-
const driveMatch = userProfileWin.match(/^([a-zA-Z]):/);
|
|
180
|
-
const driveLetter = driveMatch ? driveMatch[1].toLowerCase() : 'c';
|
|
181
|
-
const userProfileRoute = userProfileWin.substring(3).replace(/\\/g, '/'); // Users/Name
|
|
182
|
-
const wslStagingBase = `/mnt/${driveLetter}/${userProfileRoute}`;
|
|
183
|
-
const wslStagingDir = path.posix.join(wslStagingBase, stagingDirName);
|
|
184
|
-
try {
|
|
185
|
-
if (await fs.pathExists(wslStagingDir))
|
|
186
|
-
await fs.remove(wslStagingDir);
|
|
187
|
-
// Use async copy to prevent blocking event loop (Fixes 25s lag)
|
|
188
|
-
await fs.copy(STAGING_DIR, wslStagingDir);
|
|
189
|
-
}
|
|
190
|
-
catch (copyErr) {
|
|
191
|
-
await ctx.actions.runAction('core:log', { level: 'error', message: `WSL Staging Copy Failed: ${copyErr.message}` });
|
|
192
|
-
}
|
|
159
|
+
// 1. Setup Safe Paths (C:\Temp)
|
|
160
|
+
// We use the same path that syncToStaging() used (/mnt/c/Temp/ai-ext-preview)
|
|
161
|
+
const winStagingDir = 'C:\\Temp\\ai-ext-preview';
|
|
162
|
+
const winProfile = 'C:\\Temp\\ai-ext-profile';
|
|
163
|
+
let userProfileWin = 'C:\\Temp'; // Legacy variable support
|
|
164
|
+
const driveLetter = 'c';
|
|
193
165
|
// Calculate final paths
|
|
194
|
-
let finalWinExtensionPath =
|
|
166
|
+
let finalWinExtensionPath = winStagingDir;
|
|
195
167
|
// Handle nested extension root
|
|
196
168
|
if (extensionRoot !== STAGING_DIR) {
|
|
197
169
|
const relative = path.relative(STAGING_DIR, extensionRoot);
|
|
198
|
-
finalWinExtensionPath = path.posix.join(
|
|
170
|
+
finalWinExtensionPath = path.posix.join(winStagingDir.replace(/\\/g, '/'), relative).replace(/\//g, '\\');
|
|
199
171
|
}
|
|
200
172
|
const winChromePath = chromePath
|
|
201
173
|
.replace(new RegExp(`^/mnt/${driveLetter}/`), `${driveLetter.toUpperCase()}:\\`)
|
|
202
174
|
.replace(/\//g, '\\');
|
|
203
|
-
const winProfile = path.posix.join(userProfileWin.replace(/\\/g, '/'), '.ai-extension-profile').replace(/\//g, '\\');
|
|
204
175
|
await ctx.actions.runAction('core:log', { level: 'info', message: `WSL Launch Target (Win): ${finalWinExtensionPath}` });
|
|
205
176
|
// await ctx.actions.runAction('core:log', { level: 'info', message: `WSL Profile (Win): ${winProfile}` });
|
|
206
177
|
// Create PowerShell Launch Script
|
|
@@ -209,50 +180,88 @@ $chromePath = "${winChromePath}"
|
|
|
209
180
|
$extPath = "${finalWinExtensionPath}"
|
|
210
181
|
$profilePath = "${winProfile}"
|
|
211
182
|
|
|
183
|
+
Write-Host "DEBUG: ChromePath: $chromePath"
|
|
184
|
+
Write-Host "DEBUG: ExtPath: $extPath"
|
|
185
|
+
Write-Host "DEBUG: ProfilePath: $profilePath"
|
|
186
|
+
|
|
187
|
+
# Verify Paths
|
|
188
|
+
if (-not (Test-Path -Path $extPath)) {
|
|
189
|
+
Write-Host "ERROR: Extension Path NOT FOUND!"
|
|
190
|
+
} else {
|
|
191
|
+
Write-Host "DEBUG: Extension Path Exists."
|
|
192
|
+
}
|
|
193
|
+
|
|
212
194
|
# Create Profile Dir if needed
|
|
213
195
|
if (-not (Test-Path -Path $profilePath)) {
|
|
214
196
|
New-Item -ItemType Directory -Force -Path $profilePath | Out-Null
|
|
215
197
|
}
|
|
216
198
|
|
|
217
199
|
$argsList = @(
|
|
218
|
-
"--load-extension
|
|
219
|
-
"--user-data-dir
|
|
200
|
+
"--load-extension=""$extPath""",
|
|
201
|
+
"--user-data-dir=""$profilePath""",
|
|
220
202
|
"--no-first-run",
|
|
221
203
|
"--no-default-browser-check",
|
|
222
204
|
"--disable-gpu",
|
|
223
|
-
"
|
|
205
|
+
"about:blank"
|
|
224
206
|
)
|
|
225
207
|
|
|
226
|
-
Start-Process
|
|
208
|
+
# Convert to single string to ensure Start-Process handles it safely
|
|
209
|
+
$argStr = $argsList -join " "
|
|
210
|
+
Write-Host "DEBUG: Args: $argStr"
|
|
211
|
+
|
|
212
|
+
Write-Host "DEBUG: Launching Chrome..."
|
|
213
|
+
Start-Process -FilePath $chromePath -ArgumentList $argStr
|
|
227
214
|
`;
|
|
228
|
-
|
|
229
|
-
const
|
|
215
|
+
// Write ps1 to /mnt/c/Temp/ai-ext-preview/launch.ps1 (Same as STAGING_DIR)
|
|
216
|
+
const psPath = path.join(STAGING_DIR, 'launch.ps1');
|
|
230
217
|
try {
|
|
231
218
|
await fs.writeFile(psPath, psContent);
|
|
232
219
|
}
|
|
233
220
|
catch (e) {
|
|
234
221
|
await ctx.actions.runAction('core:log', { level: 'error', message: `WSL Write PS1 Failed: ${e.message}` });
|
|
235
222
|
}
|
|
236
|
-
// Execute PowerShell
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
223
|
+
// Execute via PowerShell (Spawn detached)
|
|
224
|
+
// psPathWin is C:\\Temp\\ai-ext-preview\\launch.ps1
|
|
225
|
+
const psPathWin = `${winStagingDir}\\launch.ps1`;
|
|
226
|
+
const child = spawn('powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', psPathWin], {
|
|
227
|
+
detached: true,
|
|
228
|
+
stdio: ['ignore', 'pipe', 'pipe'] // Pipe stderr AND stdout to catch launch errors/debug
|
|
229
|
+
});
|
|
230
|
+
if (child.stdout) {
|
|
231
|
+
child.stdout.on('data', async (chunk) => {
|
|
232
|
+
const msg = chunk.toString();
|
|
233
|
+
await ctx.actions.runAction('core:log', { level: 'info', message: `[PS1] ${msg.trim()}` });
|
|
243
234
|
});
|
|
244
|
-
subprocess.unref();
|
|
245
235
|
}
|
|
246
|
-
|
|
247
|
-
|
|
236
|
+
if (child.stderr) {
|
|
237
|
+
child.stderr.on('data', async (chunk) => {
|
|
238
|
+
const msg = chunk.toString();
|
|
239
|
+
await ctx.actions.runAction('core:log', { level: 'error', message: `Launch Error (Stderr): ${msg}` });
|
|
240
|
+
if (msg.includes('Exec format error')) {
|
|
241
|
+
await ctx.actions.runAction('core:log', { level: 'error', message: `CRITICAL: WSL Interop is broken. Cannot launch Chrome.` });
|
|
242
|
+
await ctx.actions.runAction('core:log', { level: 'error', message: `FIX: Open PowerShell as Admin and run: wsl --shutdown` });
|
|
243
|
+
ctx.events.emit('browser:launch-failed', { reason: 'WSL_INTEROP_BROKEN' });
|
|
244
|
+
}
|
|
245
|
+
});
|
|
248
246
|
}
|
|
247
|
+
child.on('error', async (err) => {
|
|
248
|
+
await ctx.actions.runAction('core:log', { level: 'error', message: `Launch Failed: ${err.message}` });
|
|
249
|
+
ctx.events.emit('browser:launch-failed', { reason: err.message });
|
|
250
|
+
});
|
|
251
|
+
child.unref();
|
|
249
252
|
return true;
|
|
250
253
|
}
|
|
251
254
|
else {
|
|
252
255
|
// Native Windows / Linux
|
|
253
256
|
// Use extensionRoot which points to the detected subfolder or root
|
|
254
|
-
|
|
255
|
-
|
|
257
|
+
let safeDist = path.resolve(extensionRoot);
|
|
258
|
+
let safeProfile = path.join(path.dirname(config.workDir), 'profile'); // Default Linux/Mac
|
|
259
|
+
// FIX: On Git Bash (win32), ensure paths are C:\Style for Chrome
|
|
260
|
+
if (process.platform === 'win32') {
|
|
261
|
+
safeDist = normalizePathToWindows(safeDist);
|
|
262
|
+
// Use C:\Temp profile to avoid permissions issues, matching WSL strategy
|
|
263
|
+
safeProfile = WIN_PROFILE_DIR;
|
|
264
|
+
}
|
|
256
265
|
await ctx.actions.runAction('core:log', { level: 'info', message: `Native Launch Executable: ${executable}` });
|
|
257
266
|
await ctx.actions.runAction('core:log', { level: 'info', message: `Native Launch Target: ${safeDist}` });
|
|
258
267
|
const cleanArgs = [
|
|
@@ -66,8 +66,17 @@ export const DownloaderPlugin = {
|
|
|
66
66
|
// First run, just verify it exists
|
|
67
67
|
}
|
|
68
68
|
if (job.status === 'completed') {
|
|
69
|
-
if
|
|
70
|
-
|
|
69
|
+
// Check if files actually exist
|
|
70
|
+
let forceDownload = false;
|
|
71
|
+
const manifestPath = path.join(DIST_DIR, 'manifest.json');
|
|
72
|
+
if (!fs.existsSync(manifestPath)) {
|
|
73
|
+
await ctx.actions.runAction('core:log', { level: 'warn', message: 'Version match but files missing. Forcing download...' });
|
|
74
|
+
forceDownload = true;
|
|
75
|
+
}
|
|
76
|
+
if (newVersion !== lastModified || forceDownload) {
|
|
77
|
+
if (newVersion !== lastModified) {
|
|
78
|
+
await ctx.actions.runAction('core:log', { level: 'info', message: `New version detected (Old: "${lastModified}", New: "${newVersion}")` });
|
|
79
|
+
}
|
|
71
80
|
const success = await ctx.actions.runAction('downloader:download', null);
|
|
72
81
|
if (success) {
|
|
73
82
|
lastModified = newVersion;
|
|
@@ -186,16 +195,26 @@ console.log('[Hot Reload] Active for Job:', CURRENT_JOB_ID);
|
|
|
186
195
|
}
|
|
187
196
|
});
|
|
188
197
|
// Start Polling (Loop)
|
|
189
|
-
|
|
198
|
+
void ctx.actions.runAction('core:log', { level: 'info', message: 'Starting polling loop (Interval: 2000ms)' });
|
|
199
|
+
// Listen for browser failure to stop polling
|
|
200
|
+
ctx.events.on('browser:launch-failed', () => {
|
|
201
|
+
if (checkInterval) {
|
|
202
|
+
clearInterval(checkInterval);
|
|
203
|
+
checkInterval = undefined;
|
|
204
|
+
ctx.actions.runAction('core:log', { level: 'warn', message: 'Polling stopped due to browser launch failure.' });
|
|
205
|
+
// Update status happens in UI
|
|
206
|
+
}
|
|
207
|
+
});
|
|
190
208
|
checkInterval = setInterval(async () => {
|
|
191
209
|
try {
|
|
192
|
-
// Use actions for main log
|
|
193
|
-
//
|
|
194
|
-
|
|
210
|
+
// Use actions for main log (UI Plugin captures this)
|
|
211
|
+
// console.error('[DownloaderPlugin] Tick - Checking Status...'); // REMOVE (Outside UI)
|
|
212
|
+
// Silent polling for CLI mode
|
|
213
|
+
// await ctx.actions.runAction('core:log', { level: 'info', message: '[DEBUG] Polling...' });
|
|
195
214
|
await ctx.actions.runAction('downloader:check', null);
|
|
196
215
|
}
|
|
197
216
|
catch (err) {
|
|
198
|
-
|
|
217
|
+
await ctx.actions.runAction('core:log', { level: 'error', message: `Poll Error: ${err.message}` });
|
|
199
218
|
}
|
|
200
219
|
}, 2000);
|
|
201
220
|
},
|
|
@@ -24,7 +24,7 @@ export const ServerPlugin = {
|
|
|
24
24
|
}
|
|
25
25
|
if (req.url === '/status') {
|
|
26
26
|
const currentJobId = ctx.host.config.jobId;
|
|
27
|
-
ctx.actions.runAction('core:log', { level: 'info', message: `[DEBUG] Server: Extension requested status (Reporting: ${currentVersion})` });
|
|
27
|
+
// ctx.actions.runAction('core:log', { level: 'info', message: `[DEBUG] Server: Extension requested status (Reporting: ${currentVersion})` });
|
|
28
28
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
29
29
|
res.end(JSON.stringify({
|
|
30
30
|
version: currentVersion,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-extension-preview",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"description": "Local preview tool for AI Extension Builder",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -50,4 +50,4 @@
|
|
|
50
50
|
"typescript": "^5.7.2",
|
|
51
51
|
"vitest": "^4.0.16"
|
|
52
52
|
}
|
|
53
|
-
}
|
|
53
|
+
}
|