@@ -35,13 +35,13 @@ arguments:
35
35
const path = require('path');
36
36
const os = require('os');
37
37
(async () => {
38
- const url = "${{ release_url }}";
38
+ const url = "${{release_url}}";
39
39
if (!url || !url.trim()) throw new Error('release_url is empty');
40
40
const isWin = process.platform === 'win32';
41
41
const tmp = isWin ? (process.env.TEMP || os.tmpdir()) : os.tmpdir();
42
42
const zipPath = isWin ? path.join(tmp, 'terminator-browser-extension.zip') : path.join(tmp, 'terminator-browser-extension.zip');
43
43
const destDir = isWin ? path.join(tmp, 'terminator-bridge') : path.join(tmp, 'terminator-bridge');
44
-
44
+ const existedBefore = fs.existsSync(destDir);
45
45
try { fs.rmSync(destDir, { recursive: true, force: true }); } catch (_) {}
46
46
try { fs.mkdirSync(destDir, { recursive: true }); } catch (e) { throw new Error('Failed to create dest dir: ' + e.message); }
47
47
@@ -50,7 +50,11 @@ arguments:
50
50
const arrayBuf = await res.arrayBuffer();
51
51
fs.writeFileSync(zipPath, Buffer.from(arrayBuf));
52
52
53
- return { zipPath, destDir };
53
+ // Export values via ::set-env for the workflow engine AND return set_env for robust propagation
54
+ console.log(`::set-env name=zip_path::${zipPath}`);
55
+ console.log(`::set-env name=extension_dir::${destDir}`);
56
+ console.log(`::set-env name=is_update_mode::${existedBefore}`);
57
+ return { set_env: { zip_path: zipPath, extension_dir: destDir, is_update_mode: existedBefore } };
54
58
})();
55
59
delay_ms : 200
56
60
@@ -59,63 +63,72 @@ arguments:
59
63
arguments :
60
64
windows_command : |
61
65
$ErrorActionPreference = 'Stop'
66
+ # Avoid template substitution issues: compute paths directly
62
67
$zip = Join-Path $env:TEMP 'terminator-browser-extension.zip'
63
- $dest = ("${{ extension_dir }}" -replace '%TEMP%', $env:TEMP)
68
+ $dest = Join-Path $env:TEMP 'terminator-bridge'
64
69
if (Test-Path $dest) { Remove-Item -Recurse -Force $dest }
65
70
New-Item -ItemType Directory -Force -Path $dest | Out-Null
66
71
Expand-Archive -Path $zip -DestinationPath $dest -Force
67
72
unix_command : |
68
73
bash -lc '
69
74
set -euo pipefail
70
- ZIP="${TMPDIR:-/tmp}/terminator-browser-extension.zip"
71
- DEST="$(printf "%s" "${{ extension_dir }}" | sed "s|%TEMP%|${TMPDIR:-/tmp}|g")"
75
+ ZIP_RAW="${{env.zip_path}}"
76
+ DEST_RAW="${{env.extension_dir}}"
77
+ ZIP="$(printf "%s" "$ZIP_RAW" | sed "s|%TEMP%|${TMPDIR:-/tmp}|g")"
78
+ DEST="$(printf "%s" "$DEST_RAW" | sed "s|%TEMP%|${TMPDIR:-/tmp}|g")"
72
79
rm -rf "$DEST" && mkdir -p "$DEST"
73
80
unzip -o "$ZIP" -d "$DEST" >/dev/null
74
81
'
75
82
delay_ms : 400
76
83
77
- # Open Chrome directly to the Extensions page
78
- - tool_name : run_command
84
+ # Find the actual folder that contains manifest.json (some zips have a nested folder)
85
+ - tool_name : run_javascript
79
86
arguments :
80
- windows_command : |
81
- $ErrorActionPreference = 'Stop'
82
- $cmd = Get-Command 'chrome.exe' -ErrorAction SilentlyContinue
83
- if ($cmd) {
84
- $chrome = $cmd.Path
85
- } else {
86
- $paths = @(
87
- "$env:LOCALAPPDATA\Google\Chrome\Application\chrome.exe",
88
- "$env:ProgramFiles\Google\Chrome\Application\chrome.exe",
89
- "$env:ProgramFiles(x86)\Google\Chrome\Application\chrome.exe"
90
- )
91
- $chrome = $paths | Where-Object { Test-Path $_ } | Select-Object -First 1
92
- }
93
- if (-not $chrome) { throw 'Google Chrome not found' }
94
- Start-Process -FilePath $chrome -ArgumentList 'chrome://extensions'
95
- unix_command : |
96
- bash -lc '
97
- set -euo pipefail
98
- for C in google-chrome google-chrome-stable chromium chromium-browser; do
99
- if command -v "$C" >/dev/null 2>&1; then nohup "$C" chrome://extensions >/dev/null 2>&1 & exit 0; fi
100
- done
101
- echo "Chrome/Chromium not found" >&2; exit 1
102
- '
103
- delay_ms : 1000
87
+ script : |
88
+ const fs = require('fs');
89
+ const path = require('path');
90
+ const os = require('os');
91
+ (async () => {
92
+ const isWin = process.platform === 'win32';
93
+ const root = isWin ? path.join(process.env.TEMP || os.tmpdir(), 'terminator-bridge') : path.join(os.tmpdir(), 'terminator-bridge');
94
+ const stack = [root];
95
+ let picked = null;
96
+ while (stack.length) {
97
+ const dir = stack.pop();
98
+ let entries;
99
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (_) { continue; }
100
+ if (entries.some(e => e.isFile && e.name.toLowerCase() === 'manifest.json' || (!e.isFile && !e.isDirectory && e.name && e.name.toLowerCase() === 'manifest.json'))) {
101
+ picked = dir; break;
102
+ }
103
+ for (const e of entries) {
104
+ if ((e.isDirectory && e.isDirectory()) || (e.isDirectory === true)) {
105
+ stack.push(path.join(dir, e.name));
106
+ }
107
+ }
108
+ }
109
+ if (!picked) {
110
+ console.log(`::set-env name=extension_dir_text::${root}`);
111
+ return { set_env: { extension_dir_text: root } };
112
+ }
113
+ console.log(`::set-env name=extension_dir_text::${picked}`);
114
+ return { set_env: { extension_dir_text: picked } };
115
+ })();
116
+ continue_on_error : false
117
+ delay_ms : 100
104
118
105
- # Best-effort: bring Chrome to foreground (skippable)
106
- - tool_name : activate_element
119
+ # Navigate directly to the Extensions page using browser navigation tool
120
+ - tool_name : navigate_browser
107
121
arguments :
108
- selector : " role:Window|name:Google Chrome"
109
- timeout_ms : 3000
110
- continue_on_error : true
111
- delay_ms : 400
122
+ url : " chrome://extensions"
123
+ browser : " chrome"
124
+ delay_ms : 1000
112
125
113
- # Ensure we actually land on chrome://extensions by forcing nav via address bar
126
+ # Fallback: force the URL in the address bar if Chrome didn't navigate
114
127
- tool_name : wait_for_element
115
128
arguments :
116
129
selector : " ${{ selectors.address_bar }}"
117
- condition : " exists "
118
- timeout_ms : 20000
130
+ condition : " visible "
131
+ timeout_ms : 15000
119
132
continue_on_error : true
120
133
121
134
- tool_name : click_element
@@ -134,10 +147,10 @@ arguments:
134
147
- tool_name : press_key_global
135
148
arguments :
136
149
key : " {Enter}"
137
- delay_ms : 1000
150
+ delay_ms : 800
138
151
continue_on_error : true
139
152
140
- # Ensure Developer mode is ON and click "Load unpacked" via JavaScript (conditional logic)
153
+ # Ensure Developer mode is ON (presence-based; do not trust is_toggled). Do NOT click "Load unpacked" here.
141
154
- tool_name : run_javascript
142
155
arguments :
143
156
script : |
@@ -147,38 +160,84 @@ arguments:
147
160
148
161
// Wait for Developer mode toggle to appear
149
162
const devToggle = await desktop.locator(toggleSel).wait(30000);
150
-
151
- // Only enable if currently off (camelCase API)
152
- const isOn = await devToggle.isToggled();
153
- if (!isOn ) {
154
- await devToggle.setToggled(true );
163
+ // Presence-based check: if Load unpacked is not visible yet, toggle Dev Mode once
164
+ let loadVisible = false;
165
+ try { await desktop.locator(loadSel).wait(1500); loadVisible = true; } catch (_) {}
166
+ if (!loadVisible ) {
167
+ await devToggle.click( );
155
168
await sleep(300);
156
169
}
170
+ // No explicit click on Load unpacked here; later steps handle it
171
+ continue_on_error : true
172
+ delay_ms : 200
173
+
174
+ # Safely remove only the Terminator Bridge card if present (scoped and validated)
175
+ - tool_name : highlight_element
176
+ arguments :
177
+ selector : " role:Window|name:Extensions - Google Chrome >> name:Terminator Bridge"
178
+ timeout_ms : 1200
179
+ continue_on_error : true
180
+ delay_ms : 100
181
+
182
+ - tool_name : validate_element
183
+ arguments :
184
+ selector : " role:Window|name:Extensions - Google Chrome >> role:Button|name:Remove | near:name:Terminator Bridge"
185
+ alternative_selectors : " role:Window|name:Extensions - Google Chrome >> role:Button|name:Remove | below:name:Terminator Bridge,role:Window|name:Extensions - Google Chrome >> role:Button|name:Remove"
186
+ timeout_ms : 2500
187
+ continue_on_error : true
188
+ delay_ms : 100
157
189
158
- // Click Load unpacked (wait if needed)
159
- const loadBtn = await desktop.locator(loadSel).wait(10000);
160
- await loadBtn.click();
161
- await sleep(600);
162
- return { devModeInitiallyOn: isOn };
190
+ - tool_name : click_element
191
+ arguments :
192
+ selector : " role:Window|name:Extensions - Google Chrome >> role:Button|name:Remove | near:name:Terminator Bridge"
193
+ alternative_selectors : " role:Window|name:Extensions - Google Chrome >> role:Button|name:Remove | below:name:Terminator Bridge,role:Window|name:Extensions - Google Chrome >> role:Button|name:Remove"
194
+ continue_on_error : true
195
+ delay_ms : 300
196
+
197
+ - tool_name : wait_for_element
198
+ arguments :
199
+ selector : " role:Window|name:/^Remove/"
200
+ condition : " visible"
201
+ timeout_ms : 2000
202
+ continue_on_error : true
203
+ delay_ms : 100
204
+
205
+ - tool_name : press_key_global
206
+ arguments :
207
+ key : " {Enter}"
208
+ delay_ms : 800
209
+ continue_on_error : true
210
+
211
+ # Click Load unpacked, then handle folder picker dialog (Windows)
212
+ - tool_name : click_element
213
+ arguments :
214
+ selector : " ${{ selectors.load_unpacked }}"
215
+ continue_on_error : false
216
+ delay_ms : 300
163
217
164
218
# Folder picker dialog (Windows)
165
219
- tool_name : wait_for_element
166
220
arguments :
167
221
selector : " ${{ selectors.folder_field }}"
168
222
condition : " exists"
169
- timeout_ms : 10000
223
+ timeout_ms : 3000
224
+ continue_on_error : true
225
+
226
+ # Use the resolved folder containing manifest.json
170
227
171
228
- tool_name : type_into_element
172
229
arguments :
173
230
selector : " ${{ selectors.folder_field }}"
174
- text_to_type : " ${{ extension_dir }}"
231
+ text_to_type : " ${{env.extension_dir_text }}"
175
232
clear_before_typing : true
176
233
verify_action : false
234
+ continue_on_error : true
177
235
178
236
- tool_name : click_element
179
237
arguments :
180
238
selector : " ${{ selectors.select_folder_btn }}"
181
239
delay_ms : 1200
240
+ continue_on_error : true
182
241
183
242
# Verification: look for the Reload button that appears on unpacked extensions
184
243
- tool_name : wait_for_element
0 commit comments