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
- // WSL Detection & Handling
40
- let extensionPath = DIST_DIR;
41
- const isWSL = fs.existsSync('/mnt/c'); // Simple check for WSL
42
- if (isWSL) {
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
- const WIN_TEMP_DIR = '/mnt/c/Temp/ai-ext-preview';
45
- const WIN_PATH_FOR_CHROME = 'C:/Temp/ai-ext-preview';
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(WIN_TEMP_DIR);
72
- // Copy dist content
73
- fs.copySync(DIST_DIR, WIN_TEMP_DIR);
74
- extensionPath = WIN_PATH_FOR_CHROME;
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 (copyErr) {
77
- await ctx.actions.runAction('core:log', { level: 'error', message: `Failed to copy to Windows Temp: ${copyErr.message}` });
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
- await ctx.actions.runAction('core:log', { level: 'warning', message: 'Switching to Detached Mode (WSL/GitBash detected).' });
82
- await ctx.actions.runAction('core:log', { level: 'info', message: 'Browser polling/logging is disabled. Please reload manually on updates.' });
83
- const userDataDir = 'C:/Temp/ai-ext-profile';
84
- // Convert Chrome path to Windows format if in WSL
85
- // /mnt/c/Program Files/... -> C:\Program Files\...
86
- let executable = chromePath;
87
- // If WSL, use a batch file to handle the launch robustly
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 = '/mnt/c/Temp/ai-ext-preview/launch.bat';
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
- await ctx.actions.runAction('core:log', { level: 'error', message: `Failed to write batch file: ${e.message}` });
109
- return false;
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
- // Standard Windows / Linux Launch (Git Bash / Native)
125
- // Normalize paths (stripping trailing slashes which Chrome hates)
126
- const safeDist = path.resolve(extensionPath);
127
- const safeProfile = path.resolve(userDataDir);
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' // Better for verifying if it loaded
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
- // On Windows (including Git Bash), web-ext is unreliable for loading extensions correctly.
152
- // We force detached mode to ensure the extension loads.
153
- if (process.platform === 'win32') {
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({ version: currentVersion }));
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.5",
3
+ "version": "0.1.6",
4
4
  "description": "Local preview tool for AI Extension Builder",
5
5
  "type": "module",
6
6
  "bin": {