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 STAGING_DIR = isWSL ? '/mnt/c/Temp/ai-ext-preview' : path.join(config.workDir, '../staging');
101
- const WIN_PROFILE_DIR = 'C:/Temp/ai-ext-profile';
102
- // For native windows/linux, use local staging path
103
- // Note: We will evaluate actual extension root later, but base is STAGING_DIR
104
- const EXTENSION_PATH = isWSL ? 'C:/Temp/ai-ext-preview' : STAGING_DIR;
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. Get Windows User Profile Path
163
- let userProfileWin = '';
164
- try {
165
- // Use async exec to avoid blocking
166
- const { exec } = await import('child_process');
167
- const util = await import('util');
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 = stagingDirWin;
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(stagingDirWin.replace(/\\/g, '/'), relative).replace(/\//g, '\\');
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=$extPath",
219
- "--user-data-dir=$profilePath",
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
- "chrome://extensions"
205
+ "about:blank"
224
206
  )
225
207
 
226
- Start-Process -FilePath $chromePath -ArgumentList $argsList
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
- const psPath = path.join(wslStagingDir, 'launch.ps1');
229
- const winPsPath = path.posix.join(stagingDirWin.replace(/\\/g, '/'), 'launch.ps1').replace(/\//g, '\\');
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
- const cli = 'powershell.exe';
238
- try {
239
- const subprocess = spawn(cli, ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', winPsPath], {
240
- detached: true, // We still detach the PowerShell process itself
241
- stdio: 'ignore',
242
- cwd: `/mnt/${driveLetter}`
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
- catch (spawnErr) {
247
- await ctx.actions.runAction('core:log', { level: 'error', message: `WSL Spawn Error: ${spawnErr.message}` });
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
- const safeDist = path.resolve(extensionRoot);
255
- const safeProfile = path.join(path.dirname(config.workDir), 'profile'); // ~/.ai-extension-preview/profile
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 = [
@@ -32,7 +32,10 @@ export const CorePlugin = {
32
32
  break;
33
33
  default:
34
34
  logger.info(message);
35
+ break;
35
36
  }
37
+ // Emit event for UI
38
+ ctx.events.emit('log', { level, message, timestamp: new Date().toISOString() });
36
39
  return true;
37
40
  }
38
41
  });
@@ -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 (newVersion !== lastModified) {
70
- await ctx.actions.runAction('core:log', { level: 'info', message: `New version detected (Old: "${lastModified}", New: "${newVersion}")` });
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
- console.error('[DownloaderPlugin] Starting polling loop (Interval: 2000ms)');
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, but console.error for guaranteed debug output
193
- // await ctx.actions.runAction('core:log', { level: 'info', message: '[DEBUG] Polling Tick...' });
194
- console.error('[DownloaderPlugin] Tick - Checking Status...');
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
- console.error('[DownloaderPlugin] Poll Error:', err);
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.9",
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
+ }