fpasoterm 1.6.0

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.

Potentially problematic release.


This version of fpasoterm might be problematic. Click here for more details.

Files changed (101) hide show
  1. package/CHANGELOG.md +230 -0
  2. package/CONTRIBUTING.md +67 -0
  3. package/INSTALL.ja.md +204 -0
  4. package/INSTALL.md +229 -0
  5. package/LICENSE +21 -0
  6. package/README.ja.md +389 -0
  7. package/README.md +607 -0
  8. package/bin/fpasoterm +2882 -0
  9. package/completions/_fpasoterm +81 -0
  10. package/completions/fpasoterm.bash +55 -0
  11. package/completions/fpasoterm.fish +64 -0
  12. package/completions/fpasoterm.ps1 +47 -0
  13. package/docs/capability-diagnostics.en.md +77 -0
  14. package/docs/capability-diagnostics.ja.md +63 -0
  15. package/docs/completion.en.md +141 -0
  16. package/docs/completion.ja.md +113 -0
  17. package/docs/config.en.md +554 -0
  18. package/docs/config.ja.md +544 -0
  19. package/docs/debugging.en.md +72 -0
  20. package/docs/debugging.ja.md +66 -0
  21. package/docs/diagnostics.en.md +69 -0
  22. package/docs/diagnostics.ja.md +64 -0
  23. package/docs/font-diagnostics.en.md +116 -0
  24. package/docs/font-diagnostics.ja.md +105 -0
  25. package/docs/fpasoterm-plugin.d.ts +51 -0
  26. package/docs/known-issues.en.md +23 -0
  27. package/docs/known-issues.ja.md +23 -0
  28. package/docs/plugins.en.md +266 -0
  29. package/docs/plugins.ja.md +176 -0
  30. package/docs/pr-review.en.md +177 -0
  31. package/docs/pr-review.ja.md +170 -0
  32. package/docs/release-checklist.en.md +72 -0
  33. package/docs/release-checklist.ja.md +71 -0
  34. package/docs/security.en.md +36 -0
  35. package/docs/security.ja.md +31 -0
  36. package/docs/spec.en.md +106 -0
  37. package/docs/spec.ja.md +99 -0
  38. package/docs/sshfs.en.md +28 -0
  39. package/docs/sshfs.ja.md +28 -0
  40. package/docs/sync.en.md +280 -0
  41. package/docs/sync.ja.md +280 -0
  42. package/examples/apply-default-appearance.bat +8 -0
  43. package/examples/apply-default-appearance.ps1 +7 -0
  44. package/examples/apply-default-appearance.sh +7 -0
  45. package/examples/apply-runtime-appearance.bat +8 -0
  46. package/examples/apply-runtime-appearance.ps1 +7 -0
  47. package/examples/apply-runtime-appearance.sh +7 -0
  48. package/examples/config/default-appearance.toml +36 -0
  49. package/examples/config/minimal.toml +30 -0
  50. package/examples/config/profiles.toml +20 -0
  51. package/examples/config/runtime-appearance.toml +24 -0
  52. package/examples/config/sync-folder.toml +17 -0
  53. package/examples/config/tui-compatibility.toml +7 -0
  54. package/examples/config/with-plugins.toml +33 -0
  55. package/examples/plugins/hello.ts +17 -0
  56. package/examples/plugins/status-banner.ts +14 -0
  57. package/examples/plugins/theme.ts +32 -0
  58. package/examples/plugins/welcome-banner.ts +16 -0
  59. package/extra/linux/icons/hicolor/128x128/apps/fpasoterm.png +0 -0
  60. package/extra/linux/icons/hicolor/16x16/apps/fpasoterm.png +0 -0
  61. package/extra/linux/icons/hicolor/192x192/apps/fpasoterm.png +0 -0
  62. package/extra/linux/icons/hicolor/256x256/apps/fpasoterm.png +0 -0
  63. package/extra/linux/icons/hicolor/32x32/apps/fpasoterm.png +0 -0
  64. package/extra/linux/icons/hicolor/48x48/apps/fpasoterm.png +0 -0
  65. package/extra/linux/icons/hicolor/512x512/apps/fpasoterm.png +0 -0
  66. package/extra/linux/icons/hicolor/64x64/apps/fpasoterm.png +0 -0
  67. package/extra/linux/io.github.oyoguhito.fpasoterm.desktop +10 -0
  68. package/extra/logo/fpasoterm.png +0 -0
  69. package/extra/macos/fpasoterm.icns +0 -0
  70. package/extra/windows/fpasoterm.cmd +40 -0
  71. package/extra/windows/fpasoterm.ico +0 -0
  72. package/package.json +81 -0
  73. package/scripts/build-artifacts.js +183 -0
  74. package/scripts/generate-icon.js +251 -0
  75. package/scripts/init-jj-empty-main +41 -0
  76. package/scripts/install-linux-desktop.js +238 -0
  77. package/scripts/run +16 -0
  78. package/scripts/security/scan-secrets.js +89 -0
  79. package/scripts/tests/smoke.js +2476 -0
  80. package/scripts/uninstall-desktop.js +20 -0
  81. package/scripts/uninstall-linux-desktop.js +96 -0
  82. package/scripts/uninstall-windows-path.js +94 -0
  83. package/src/config.js +855 -0
  84. package/src/renderer/confirm.html +71 -0
  85. package/src/renderer/index.html +223 -0
  86. package/src/renderer/renderer.js +4166 -0
  87. package/src/renderer/styles.css +969 -0
  88. package/src/renderer/vendor/addon-fit/addon-fit.js +2 -0
  89. package/src/renderer/vendor/addon-image/LICENSE +19 -0
  90. package/src/renderer/vendor/addon-image/addon-image.js +3 -0
  91. package/src/renderer/vendor/addon-web-links/LICENSE +19 -0
  92. package/src/renderer/vendor/addon-web-links/addon-web-links.js +2 -0
  93. package/src/renderer/vendor/xterm/xterm.css +292 -0
  94. package/src/renderer/vendor/xterm/xterm.js +2 -0
  95. package/src-tauri/Cargo.lock +5253 -0
  96. package/src-tauri/Cargo.toml +29 -0
  97. package/src-tauri/build.rs +140 -0
  98. package/src-tauri/capabilities/default.json +13 -0
  99. package/src-tauri/default-config.toml +128 -0
  100. package/src-tauri/src/main.rs +9347 -0
  101. package/src-tauri/tauri.conf.json +60 -0
@@ -0,0 +1,2476 @@
1
+ const assert = require('node:assert/strict');
2
+ const { spawnSync } = require('node:child_process');
3
+ const fs = require('node:fs');
4
+ const os = require('node:os');
5
+ const path = require('node:path');
6
+ const toml = require('smol-toml');
7
+ const vm = require('node:vm');
8
+ const {
9
+ deleteWindowState,
10
+ defaultConfigExample,
11
+ discoverPluginFiles,
12
+ loadConfig,
13
+ migrateLegacyTerminalLineHeight,
14
+ mergeConfig,
15
+ missingConfigKeys,
16
+ platformDefaultConfig,
17
+ pluginMetadata,
18
+ profileNames,
19
+ pruneUnsupportedConfig,
20
+ resolvePluginSelector,
21
+ selectProfileConfig,
22
+ validateUserConfig,
23
+ writableConfigDefaults,
24
+ windowStatePath,
25
+ writeWindowState,
26
+ } = require('../../src/config');
27
+
28
+ assert.equal(platformDefaultConfig('darwin', 'x64').terminal.fontSize, 12);
29
+ assert.equal(platformDefaultConfig('darwin', 'arm64').terminal.fontSize, 14);
30
+ assert.equal(platformDefaultConfig('darwin', 'x64').terminal.lineHeight, 1);
31
+ assert.equal(platformDefaultConfig('darwin', 'arm64').terminal.lineHeight, 1);
32
+ assert.equal(platformDefaultConfig('linux', 'x64').terminal.lineHeight, 1);
33
+ assert.equal(
34
+ migrateLegacyTerminalLineHeight({ terminal: { lineHeight: 0.92 } }, 'darwin').terminal.lineHeight,
35
+ 1,
36
+ );
37
+ assert.equal(
38
+ migrateLegacyTerminalLineHeight({ terminal: { lineHeight: 0.75 } }, 'darwin').terminal.lineHeight,
39
+ 0.75,
40
+ );
41
+ assert.equal(platformDefaultConfig('win32', 'x64').terminal.fontSize, 14);
42
+ assert.equal(platformDefaultConfig('linux', 'x64').terminal.minimumContrastRatio, 1);
43
+ assert.equal(platformDefaultConfig('linux', 'x64').terminal.rescaleOverlappingGlyphs, false);
44
+ assert.equal(platformDefaultConfig('linux', 'x64').terminal.kittyKeyboard, false);
45
+ assert.equal(platformDefaultConfig('linux', 'x64').terminal.encoding, 'utf-8');
46
+ assert.equal(platformDefaultConfig('linux', 'x64').security.osc8Open, false);
47
+ assert.equal(platformDefaultConfig('linux', 'x64').security.oscNotifications, false);
48
+ assert.equal(platformDefaultConfig('linux', 'x64').security.oscNotificationMinIntervalMs, 5000);
49
+ assert.equal(platformDefaultConfig('linux', 'x64').keybindings.openCwd, 'o');
50
+ assert.match(platformDefaultConfig('darwin', 'arm64').terminal.fontFamily, /^Menlo, "SF Mono"/);
51
+ assert.match(platformDefaultConfig('win32', 'x64').terminal.fontFamily, /^"DejaVu Sans Mono"/);
52
+ assert.match(platformDefaultConfig('win32', 'x64').terminal.fontFamily, /"Noto Sans CJK KR"/);
53
+ assert.match(platformDefaultConfig('darwin', 'arm64').terminal.fontFamily, /"Apple SD Gothic Neo"/);
54
+ assert.match(platformDefaultConfig('win32', 'x64').terminal.fontFamily, /"Symbols Nerd Font Mono"/);
55
+ assert.match(defaultConfigExample('darwin', 'x64'), /fontSize = 12/);
56
+ assert.match(defaultConfigExample('darwin', 'arm64'), /fontSize = 14/);
57
+ assert.match(defaultConfigExample('darwin', 'arm64'), /fontFamily = "Menlo, \\"SF Mono\\"/);
58
+ const missingDefaults = missingConfigKeys(writableConfigDefaults(), { keybindings: { prefix: 'Ctrl+Alt' } });
59
+ assert.ok(missingDefaults.includes('keybindings.newWindow'));
60
+ assert.equal(missingDefaults.includes('terminal.images.enabled'), false);
61
+ assert.equal(missingDefaults.includes('keybindings.prefix'), false);
62
+ assert.deepEqual(
63
+ pruneUnsupportedConfig(writableConfigDefaults(), {
64
+ window: { title: 'test', oldSetting: true },
65
+ retiredSection: { enabled: true },
66
+ }),
67
+ { config: { window: { title: 'test' } }, removed: ['window.oldSetting', 'retiredSection'] },
68
+ );
69
+ assert.deepEqual(profileNames({ profiles: { transparent: {}, largeFont: {} } }), ['largeFont', 'transparent']);
70
+ assert.deepEqual(
71
+ selectProfileConfig({ terminal: { fontSize: 14 }, profiles: { large: { terminal: { fontSize: 18 } } } }, 'large'),
72
+ { baseConfig: { terminal: { fontSize: 14 } }, profileConfig: { terminal: { fontSize: 18 } }, activeProfile: 'large' },
73
+ );
74
+ assert.throws(() => selectProfileConfig({ profiles: {} }, 'missing'), /profile 'missing' does not exist/);
75
+ assert.deepEqual(
76
+ pluginMetadata('// @fpasoterm-plugin version: 1.2.3\n// @fpasoterm-plugin description: Example plugin\n'),
77
+ { version: '1.2.3', description: 'Example plugin' },
78
+ );
79
+ assert.deepEqual(
80
+ pluginMetadata('// Existing plugin comment\nconst value = 1;\n'),
81
+ { version: '(not declared)', description: 'Existing plugin comment' },
82
+ );
83
+
84
+ const root = path.resolve(__dirname, '..', '..');
85
+ const removedSnakeHttpUiPattern = new RegExp(['web', 'console'].join('_'));
86
+ const removedKebabHttpUiPattern = new RegExp(['web', 'console'].join('-'));
87
+ const removedCamelHttpUiPattern = new RegExp(['web', 'Console'].join(''));
88
+ const removedTemporaryHttpUiPattern = new RegExp(['temporary', 'web', 'console'].join(' '));
89
+ const removedTemporaryHttpUiDocPattern = new RegExp(['temporary', 'web', 'console'].join('-'));
90
+ const removedHttpUiEnvPattern = new RegExp(['FPASOTERM', 'WEB', 'CONSOLE'].join('_'));
91
+
92
+ // Reads a repository file as UTF-8 for assertions.
93
+ function read(relativePath) {
94
+ return fs.readFileSync(path.join(root, relativePath), 'utf8');
95
+ }
96
+
97
+ // Asserts that a required source, doc, or asset file is present.
98
+ function assertFile(relativePath) {
99
+ assert.ok(fs.existsSync(path.join(root, relativePath)), `${relativePath} should exist`);
100
+ }
101
+
102
+ const packageJson = JSON.parse(read('package.json'));
103
+ const packageLock = JSON.parse(read('package-lock.json'));
104
+
105
+ assert.equal(packageJson.name, 'fpasoterm');
106
+ assert.match(packageJson.version, /^\d+\.\d+\.\d+$/);
107
+ assert.equal(packageLock.version, packageJson.version);
108
+ assert.equal(packageLock.packages[''].version, packageJson.version);
109
+ assert.equal(packageJson.bin.fpasoterm, 'bin/fpasoterm');
110
+ assert.equal(packageJson.license, 'MIT');
111
+ assert.equal(packageJson.repository.url, 'git+https://github.com/oyoguhito/fpasoterm.git');
112
+ assert.ok(packageJson.dependencies['@tauri-apps/api'], '@tauri-apps/api should expose Tauri APIs');
113
+ assert.ok(packageJson.dependencies['@tauri-apps/cli'], '@tauri-apps/cli should launch and build the app');
114
+ assert.ok(packageJson.dependencies['smol-toml'], 'smol-toml should parse config.toml');
115
+ assert.ok(packageJson.dependencies['@xterm/addon-image'], '@xterm/addon-image should render Kitty graphics');
116
+ assert.ok(packageJson.dependencies.typescript, 'typescript should be available for .ts plugins');
117
+ assert.equal(packageJson.scripts.start, 'node ./bin/fpasoterm');
118
+ assert.equal(packageJson.scripts.build, 'tauri build');
119
+ assert.match(packageJson.scripts.check, /cargo check --manifest-path src-tauri\/Cargo\.toml/);
120
+ assert.equal(packageJson.scripts['uninstall:desktop'], 'node scripts/uninstall-desktop.js');
121
+ const oldRuntimePackage = `${'elect'}${'ron'}`;
122
+ const oldPtyPackage = `${'node'}-${'pty'}`;
123
+ assert.equal(Object.hasOwn(packageJson.dependencies, oldRuntimePackage), false);
124
+ assert.equal(Object.hasOwn(packageJson.dependencies, oldPtyPackage), false);
125
+ assert.equal(Object.hasOwn(packageJson, 'allowScripts'), false);
126
+ assert.ok(packageJson.files.includes('completions/'));
127
+
128
+ for (const file of [
129
+ 'bin/fpasoterm',
130
+ 'LICENSE',
131
+ 'CHANGELOG.md',
132
+ 'CONTRIBUTING.md',
133
+ 'INSTALL.md',
134
+ 'INSTALL.ja.md',
135
+ 'README.ja.md',
136
+ 'docs/completion.en.md',
137
+ 'docs/completion.ja.md',
138
+ 'docs/config.en.md',
139
+ 'docs/config.ja.md',
140
+ 'docs/diagnostics.en.md',
141
+ 'docs/diagnostics.ja.md',
142
+ 'docs/fpasoterm-plugin.d.ts',
143
+ 'docs/plugins.en.md',
144
+ 'docs/plugins.ja.md',
145
+ 'docs/known-issues.en.md',
146
+ 'docs/known-issues.ja.md',
147
+ 'docs/pr-review.en.md',
148
+ 'docs/pr-review.ja.md',
149
+ 'docs/spec.en.md',
150
+ 'docs/spec.ja.md',
151
+ 'docs/security.en.md',
152
+ 'docs/security.ja.md',
153
+ 'docs/sync.en.md',
154
+ 'docs/sync.ja.md',
155
+ 'docs/sshfs.en.md',
156
+ 'docs/sshfs.ja.md',
157
+ 'docs/capability-diagnostics.en.md',
158
+ 'docs/capability-diagnostics.ja.md',
159
+ 'examples/apply-default-appearance.sh',
160
+ 'examples/apply-default-appearance.ps1',
161
+ 'examples/apply-default-appearance.bat',
162
+ 'examples/apply-runtime-appearance.sh',
163
+ 'examples/apply-runtime-appearance.ps1',
164
+ 'examples/apply-runtime-appearance.bat',
165
+ 'examples/plugins/hello.ts',
166
+ 'examples/plugins/theme.ts',
167
+ 'examples/plugins/status-banner.ts',
168
+ 'examples/plugins/welcome-banner.ts',
169
+ 'examples/config/default-appearance.toml',
170
+ 'examples/config/minimal.toml',
171
+ 'examples/config/profiles.toml',
172
+ 'examples/config/runtime-appearance.toml',
173
+ 'examples/config/sync-folder.toml',
174
+ 'examples/config/tui-compatibility.toml',
175
+ 'examples/config/with-plugins.toml',
176
+ 'extra/logo/fpasoterm.png',
177
+ 'extra/macos/fpasoterm.icns',
178
+ 'extra/windows/fpasoterm.ico',
179
+ 'extra/windows/fpasoterm.cmd',
180
+ 'completions/fpasoterm.bash',
181
+ 'completions/_fpasoterm',
182
+ 'completions/fpasoterm.fish',
183
+ 'completions/fpasoterm.ps1',
184
+ 'scripts/install-linux-desktop.js',
185
+ 'scripts/uninstall-desktop.js',
186
+ 'scripts/uninstall-linux-desktop.js',
187
+ 'scripts/uninstall-windows-path.js',
188
+ 'scripts/security/scan-secrets.js',
189
+ 'src/config.js',
190
+ 'src/renderer/index.html',
191
+ 'src/renderer/renderer.js',
192
+ 'src/renderer/styles.css',
193
+ 'src/renderer/vendor/xterm/xterm.css',
194
+ 'src/renderer/vendor/xterm/xterm.js',
195
+ 'src/renderer/vendor/addon-fit/addon-fit.js',
196
+ 'src/renderer/vendor/addon-image/addon-image.js',
197
+ 'src/renderer/vendor/addon-image/LICENSE',
198
+ 'src/renderer/vendor/addon-web-links/addon-web-links.js',
199
+ 'src/renderer/vendor/addon-web-links/LICENSE',
200
+ 'src-tauri/Cargo.toml',
201
+ 'src-tauri/build.rs',
202
+ 'src-tauri/default-config.toml',
203
+ 'src-tauri/capabilities/default.json',
204
+ 'src-tauri/tauri.conf.json',
205
+ 'src-tauri/src/main.rs',
206
+ 'extra/linux/io.github.oyoguhito.fpasoterm.desktop',
207
+ '.github/workflows/release.yml',
208
+ '.github/pull_request_template.md',
209
+ ]) {
210
+ assertFile(file);
211
+ }
212
+
213
+ assert.ok(!fs.existsSync(path.join(root, 'src', 'main.js')), 'old desktop process file should be removed');
214
+ assert.ok(!fs.existsSync(path.join(root, 'src', 'preload.js')), 'old bridge file should be removed');
215
+
216
+ for (const size of [16, 32, 48, 64, 128, 192, 256, 512]) {
217
+ assertFile(`extra/linux/icons/hicolor/${size}x${size}/apps/fpasoterm.png`);
218
+ }
219
+
220
+ const bin = read('bin/fpasoterm');
221
+ assert.match(bin, /--help/);
222
+ assert.match(bin, /--version/);
223
+ assert.match(bin, /-v, --version/);
224
+ assert.match(bin, /--update-check/);
225
+ assert.match(bin, /--doctor/);
226
+ assert.match(bin, /runDoctor/);
227
+ assert.match(bin, /npmAuditSummary/);
228
+ assert.match(bin, /installPublicPlugin/);
229
+ assert.match(bin, /installLocalPluginPort/);
230
+ assert.match(bin, /installLocalPluginFile/);
231
+ assert.match(bin, /--plugin-ports-dir/);
232
+ assert.match(bin, /--plugin-install-file/);
233
+ assert.match(bin, /without starting Cargo/);
234
+ assert.match(bin, /--plugin-search/);
235
+ assert.match(bin, /-l, --list/);
236
+ assert.match(bin, /-q, --close <pid\|title\|all>/);
237
+ assert.match(bin, /printRunningInstances/);
238
+ assert.match(bin, /closeRunningInstance/);
239
+ assert.match(bin, /--dev/);
240
+ assert.match(bin, /--foreground/);
241
+ assert.match(bin, /FPASOTERM_LAUNCHER_DETACHED/);
242
+ assert.match(bin, /launcher phase=/);
243
+ assert.match(bin, /startupPreparationStatus/);
244
+ assert.match(bin, /building local runtime with cargo/);
245
+ assert.match(bin, /cargo-build-start/);
246
+ assert.match(bin, /cargo-build-complete/);
247
+ assert.match(bin, /--config/);
248
+ assert.match(bin, /--profile/);
249
+ assert.match(bin, /--profile-list/);
250
+ assert.match(bin, /showProfileList/);
251
+ assert.match(bin, /\[profiles\.large-font\.terminal\]/);
252
+ assert.match(bin, /Find config: fpasoterm --config-path/);
253
+ assert.match(bin, /--show-config/);
254
+ assert.match(bin, /--config-check/);
255
+ assert.match(bin, /--config-path/);
256
+ assert.match(bin, /--config-example/);
257
+ assert.match(bin, /--diagnostics/);
258
+ assert.match(bin, /--open-log-dir/);
259
+ assert.match(bin, /--copy-diagnostics/);
260
+ assert.match(bin, /diagnosticsMarkdown/);
261
+ assert.match(bin, /--plugin-list/);
262
+ assert.match(bin, /--plugin-path/);
263
+ assert.match(bin, /--update-config/);
264
+ assert.match(bin, /--prune-config/);
265
+ assert.match(bin, /--self-update/);
266
+ assert.match(bin, /--self-update-checkout/);
267
+ assert.match(bin, /--update-desktop/);
268
+ assert.match(bin, /--shell/);
269
+ assert.match(bin, /--cwd/);
270
+ assert.match(bin, /--command/);
271
+ assert.match(bin, /--title/);
272
+ assert.match(bin, /--titlebar-color/);
273
+ assert.match(bin, /--reset-window-state/);
274
+ assert.match(bin, /-R, --reset-config/);
275
+ assert.match(bin, /--enable-plugin/);
276
+ assert.match(bin, /--disable-plugin/);
277
+ assert.match(bin, /--plugin-enable/);
278
+ assert.match(bin, /--plugin-disable/);
279
+ assert.match(bin, /--size/);
280
+ assert.match(bin, /-t, --title/);
281
+ assert.match(bin, /-b, --titlebar-color/);
282
+ assert.match(bin, /FPASOTERM_WINDOW_TITLE/);
283
+ assert.match(bin, /FPASOTERM_TITLEBAR_COLOR/);
284
+ assert.match(bin, /applyWindowRuntimeOverrides/);
285
+ assert.match(bin, /applyTerminalRuntimeOverrides/);
286
+ assert.match(bin, /--console-diagnostics/);
287
+ assert.doesNotMatch(bin, /--debug-opaque-terminal/);
288
+ assert.doesNotMatch(bin, /--x11/);
289
+ assert.match(bin, /--disable-dmabuf/);
290
+ assert.match(bin, /WEBKIT_DISABLE_DMABUF_RENDERER/);
291
+ assert.match(bin, /FPASOTERM_RUNTIME_CONFIG_JSON/);
292
+ assert.match(bin, /FPASOTERM_LAUNCHER_LOG/);
293
+ assert.match(bin, /appendLauncherLog/);
294
+ assert.match(bin, /function npmInvocation/);
295
+ assert.match(bin, /npm-cli\.js/);
296
+ assert.match(bin, /runChecked/);
297
+ assert.match(bin, /isSourceCheckout/);
298
+ assert.match(bin, /isJjCheckout/);
299
+ assert.match(bin, /ensureCleanGitCheckout/);
300
+ assert.match(bin, /updateDesktopIntegration/);
301
+ assert.match(bin, /selfUpdate/);
302
+ assert.match(bin, /selfUpdateCheckout/);
303
+ assert.match(bin, /npmInvocation\(\['install', '-g', 'fpasoterm@latest'\]\)/);
304
+ assert.match(bin, /git', \['pull', '--ff-only'\]/);
305
+ assert.match(bin, /npmInvocation\(\['install'\]\)/);
306
+ assert.doesNotMatch(bin, /shell: process\.platform === 'win32'/);
307
+ assert.match(bin, /debugKeys/);
308
+ assert.match(bin, /consoleDiagnostics/);
309
+ assert.doesNotMatch(bin, /node_modules.*@tauri-apps.*tauri\.js/);
310
+ assert.match(bin, /detached: !options\.foreground/);
311
+ assert.match(bin, /child\.unref\(\)/);
312
+ assert.match(bin, /windowsHide: !options\.foreground/);
313
+ assert.match(bin, /isBuiltBinaryCurrent/);
314
+ assert.match(bin, /latestRuntimeSourceMtime/);
315
+ assert.match(bin, /latestPathMtime/);
316
+ assert.match(bin, /src\/renderer/);
317
+ assert.match(bin, /cargo-clean-start/);
318
+ assert.match(bin, /'clean'/);
319
+ assert.match(bin, /buildTauriBinary/);
320
+ assert.match(bin, /buildStampPath/);
321
+ assert.match(bin, /readBuildStamp/);
322
+ assert.match(bin, /writeBuildStamp/);
323
+ assert.match(bin, /findStampedTauriBinary/);
324
+ assert.match(bin, /requiresLocalRuntimeBuild/);
325
+ assert.match(bin, /phase 1\/3 - preparing the local Rust runtime/);
326
+ assert.match(bin, /phase 2\/3 - cargo build started/);
327
+ assert.match(bin, /phase 3\/3 - opening the native window/);
328
+ assert.doesNotMatch(bin, /FPASOTERM_X11/);
329
+ assert.match(bin, /launcher build=skip current binary/);
330
+ assert.match(bin, /\.fpasoterm-normal-build\.json/);
331
+ assert.match(bin, /cargo', \['build', '--manifest-path'/);
332
+ assert.match(bin, /launcher mode=\$\{options\.dev \? 'debug-binary' : 'binary'\}/);
333
+ assert.match(bin, /const tauriBinary = preparedBinary \|\| await buildTauriBinary\(options\)/);
334
+ assert.match(bin, /src-tauri\/src\/main\.rs/);
335
+ assert.doesNotMatch(bin, new RegExp(`--${'ozo'}${'ne'}-platform`));
336
+ assert.doesNotMatch(bin, new RegExp(`FPASOTERM_${'OZONE'}_PLATFORM`));
337
+ assert.doesNotMatch(bin, new RegExp(`--disable-${'g'}${'pu'}`));
338
+ assert.doesNotMatch(bin, /--position/);
339
+ assert.doesNotMatch(bin, /FPASOTERM_WINDOW_X/);
340
+
341
+ // Exercises file-name selection, comma-separated values, and repeated options.
342
+ const cliTestDir = fs.mkdtempSync(path.join(os.tmpdir(), 'fpasoterm-plugin-cli-'));
343
+ const cliConfigPath = path.join(cliTestDir, 'User', 'config.toml');
344
+ const cliPluginsDir = path.join(cliTestDir, 'User', 'plugins');
345
+ fs.mkdirSync(cliPluginsDir, { recursive: true });
346
+ fs.writeFileSync(path.join(cliPluginsDir, 'hello.ts'), [
347
+ '// @fpasoterm-plugin version: 1.2.3',
348
+ '// @fpasoterm-plugin description: Test plugin',
349
+ '',
350
+ ].join('\n'));
351
+ fs.writeFileSync(path.join(cliPluginsDir, 'theme.js'), '// test plugin\n');
352
+
353
+ const runCli = (...args) => spawnSync(
354
+ process.execPath,
355
+ [path.join(root, 'bin', 'fpasoterm'), '--config', cliConfigPath, ...args],
356
+ { encoding: 'utf8' },
357
+ );
358
+
359
+ // Covers the data transformations used by --update-config and --prune-config
360
+ // without relying on a nested launcher process.
361
+ const migratedConfig = mergeConfig(writableConfigDefaults(), {
362
+ keybindings: { prefix: 'Ctrl+Alt' },
363
+ });
364
+ assert.equal(migratedConfig.keybindings.prefix, 'Ctrl+Alt');
365
+ assert.equal(migratedConfig.keybindings.newWindow, 'N');
366
+ assert.equal(migratedConfig.terminal.images, undefined);
367
+ const prunedConfig = pruneUnsupportedConfig(writableConfigDefaults(), {
368
+ ...migratedConfig,
369
+ retired: { enabled: true },
370
+ });
371
+ assert.deepEqual(prunedConfig.removed, ['retired']);
372
+
373
+ const versionResult = runCli('--version');
374
+ assert.equal(versionResult.status, 0, versionResult.stderr);
375
+ const sourceCommit = spawnSync(fs.existsSync(path.join(root, '.jj')) ? 'jj' : 'git', fs.existsSync(path.join(root, '.jj'))
376
+ ? ['-R', root, 'log', '-r', '@', '--no-graph', '-T', 'commit_id.short(12)']
377
+ : ['-C', root, 'rev-parse', '--short=12', 'HEAD'], {
378
+ encoding: 'utf8',
379
+ });
380
+ const expectedVersion = `fpasoterm ${packageJson.version} (commit ${sourceCommit.stdout.trim() || 'unknown'})`;
381
+ assert.equal(versionResult.stdout.trim(), expectedVersion);
382
+
383
+ const shortVersionResult = runCli('-v');
384
+ assert.equal(shortVersionResult.status, 0, shortVersionResult.stderr);
385
+ assert.equal(shortVersionResult.stdout.trim(), expectedVersion);
386
+
387
+ const bashCompletionResult = runCli('--completion', 'bash');
388
+ assert.equal(bashCompletionResult.status, 0, bashCompletionResult.stderr);
389
+ assert.match(bashCompletionResult.stdout, /complete -F _fpasoterm fpasoterm/);
390
+ const powershellCompletionResult = runCli('--completion', 'powershell');
391
+ assert.equal(powershellCompletionResult.status, 0, powershellCompletionResult.stderr);
392
+ assert.match(powershellCompletionResult.stdout, /Register-ArgumentCompleter/);
393
+ assert.match(fs.readFileSync(path.join(root, 'bin', 'fpasoterm'), 'utf8'), /ToBase64String\(\[System\.Text\.Encoding\]::Unicode/);
394
+ assert.match(fs.readFileSync(path.join(root, 'src-tauri', 'src', 'main.rs'), 'utf8'), /decode_powershell_profile_path/);
395
+ assert.match(fs.readFileSync(path.join(root, 'docs', 'completion.en.md'), 'utf8'), /Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned/);
396
+ assert.match(fs.readFileSync(path.join(root, 'docs', 'completion.ja.md'), 'utf8'), /profile自体を削除/);
397
+ const invalidCompletionResult = runCli('--completion', 'invalid-shell');
398
+ assert.equal(invalidCompletionResult.status, 1);
399
+ assert.match(invalidCompletionResult.stderr, /unsupported completion shell: invalid-shell/);
400
+ const missingCompletionShellResult = runCli('--completion-uninstall');
401
+ assert.equal(missingCompletionShellResult.status, 2);
402
+ assert.match(missingCompletionShellResult.stderr, /requires a shell: bash, zsh, fish, or powershell/);
403
+ const completionDataHome = fs.mkdtempSync(path.join(os.tmpdir(), 'fpasoterm-completion-'));
404
+ const completionEnvironment = { ...process.env, XDG_DATA_HOME: completionDataHome };
405
+ const completionInstallResult = spawnSync(
406
+ process.execPath,
407
+ [path.join(root, 'bin', 'fpasoterm'), '--completion-install', 'bash'],
408
+ { encoding: 'utf8', env: completionEnvironment },
409
+ );
410
+ assert.equal(completionInstallResult.status, 0, completionInstallResult.stderr);
411
+ const installedBashCompletion = path.join(completionDataHome, 'bash-completion', 'completions', 'fpasoterm');
412
+ assert.ok(fs.existsSync(installedBashCompletion), 'bash completion should be installed under XDG_DATA_HOME');
413
+ assert.match(fs.readFileSync(installedBashCompletion, 'utf8'), /complete -F _fpasoterm fpasoterm/);
414
+ const completionUninstallResult = spawnSync(
415
+ process.execPath,
416
+ [path.join(root, 'bin', 'fpasoterm'), '--completion-uninstall', 'bash'],
417
+ { encoding: 'utf8', env: completionEnvironment },
418
+ );
419
+ assert.equal(completionUninstallResult.status, 0, completionUninstallResult.stderr);
420
+ assert.equal(fs.existsSync(installedBashCompletion), false);
421
+
422
+ const configPathResult = runCli('--config-path');
423
+ assert.equal(configPathResult.status, 0, configPathResult.stderr);
424
+ assert.equal(configPathResult.stdout.trim(), cliConfigPath);
425
+ const configExampleResult = runCli('--config-example');
426
+ assert.equal(configExampleResult.status, 0, configExampleResult.stderr);
427
+ assert.match(configExampleResult.stdout, /\[window\]/);
428
+ const configCheckResult = runCli('--config-check');
429
+ assert.equal(configCheckResult.status, 0, configCheckResult.stderr);
430
+ assert.match(configCheckResult.stdout, new RegExp(`config: ${cliConfigPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`));
431
+ assert.match(configCheckResult.stdout, /status: ok/);
432
+ const diagnosticsResult = runCli('--diagnostics');
433
+ assert.equal(diagnosticsResult.status, 0, diagnosticsResult.stderr);
434
+ assert.match(diagnosticsResult.stdout, /## fpasoterm diagnostics/);
435
+ assert.match(diagnosticsResult.stdout, /- config path:/);
436
+ assert.match(diagnosticsResult.stdout, /- logging:/);
437
+ fs.writeFileSync(cliConfigPath, [
438
+ '[terminal]',
439
+ 'fontSize = 14',
440
+ '',
441
+ '[profiles.large-font.terminal]',
442
+ 'fontSize = 18',
443
+ '',
444
+ '[sync]',
445
+ `commandSecret = "${'x'.repeat(32)}"`,
446
+ '',
447
+ ].join('\n'));
448
+ const profileListResult = runCli('--profile-list');
449
+ assert.equal(profileListResult.status, 0, profileListResult.stderr);
450
+ assert.match(profileListResult.stdout, /Available profiles:\n large-font/);
451
+ const selectedProfileResult = runCli('--profile', 'large-font', '--show-config');
452
+ assert.equal(selectedProfileResult.status, 0, selectedProfileResult.stderr);
453
+ assert.match(selectedProfileResult.stdout, /Active profile: large-font/);
454
+ assert.match(selectedProfileResult.stdout, /fontSize = 18/);
455
+ assert.match(selectedProfileResult.stdout, /commandSecret = "\[redacted\]"/);
456
+ assert.doesNotMatch(selectedProfileResult.stdout, /x{32}/);
457
+ const missingProfileResult = runCli('--profile', 'missing', '--show-config');
458
+ assert.equal(missingProfileResult.status, 1);
459
+ assert.match(missingProfileResult.stderr, /profile 'missing' does not exist/);
460
+ const missingProfileLaunch = runCli('--profile', 'missing');
461
+ assert.equal(missingProfileLaunch.status, 1);
462
+ assert.match(missingProfileLaunch.stderr, /profile 'missing' does not exist/);
463
+ assert.doesNotMatch(missingProfileLaunch.stdout, /starting in background/);
464
+
465
+ const listCacheDir = fs.mkdtempSync(path.join(os.tmpdir(), 'fpasoterm-list-cli-'));
466
+ const listMarkerDir = path.join(listCacheDir, 'fpasoterm', 'instances');
467
+ fs.mkdirSync(listMarkerDir, { recursive: true });
468
+ fs.writeFileSync(path.join(listMarkerDir, `${process.pid}.pid`), JSON.stringify({
469
+ pid: process.pid,
470
+ baseTitle: 'fpasoterm',
471
+ title: 'review-shell',
472
+ createdAt: 1234,
473
+ }));
474
+ fs.writeFileSync(path.join(listMarkerDir, '999999999.pid'), JSON.stringify({
475
+ pid: 999999999,
476
+ title: 'stale',
477
+ createdAt: 1,
478
+ }));
479
+ const listResult = spawnSync(process.execPath, [path.join(root, 'bin', 'fpasoterm'), '--list'], {
480
+ encoding: 'utf8',
481
+ env: { ...process.env, XDG_CACHE_HOME: listCacheDir },
482
+ });
483
+ assert.equal(listResult.status, 0, listResult.stderr);
484
+ assert.match(listResult.stdout, new RegExp(`session=${process.pid} pid=${process.pid} title="review-shell" started=`));
485
+ assert.doesNotMatch(listResult.stdout, /title="stale"/);
486
+ assert.equal(fs.existsSync(path.join(listMarkerDir, '999999999.pid')), false);
487
+ const closeResult = spawnSync(process.execPath, [path.join(root, 'bin', 'fpasoterm'), '--close', 'review-shell'], {
488
+ encoding: 'utf8',
489
+ env: { ...process.env, XDG_CACHE_HOME: listCacheDir },
490
+ });
491
+ assert.equal(closeResult.status, 0, closeResult.stderr);
492
+ assert.match(closeResult.stdout, new RegExp(`requested close session=${process.pid}`));
493
+ assert.deepEqual(
494
+ JSON.parse(fs.readFileSync(path.join(listCacheDir, 'fpasoterm', 'close.json'), 'utf8')).pids,
495
+ [process.pid],
496
+ );
497
+ const closeAllResult = spawnSync(process.execPath, [path.join(root, 'bin', 'fpasoterm'), '-q', 'all'], {
498
+ encoding: 'utf8',
499
+ env: { ...process.env, XDG_CACHE_HOME: listCacheDir },
500
+ });
501
+ assert.equal(closeAllResult.status, 0, closeAllResult.stderr);
502
+ assert.match(closeAllResult.stdout, new RegExp(`requested close session=${process.pid}`));
503
+ assert.deepEqual(
504
+ JSON.parse(fs.readFileSync(path.join(listCacheDir, 'fpasoterm', 'close.json'), 'utf8')).pids,
505
+ [process.pid],
506
+ );
507
+ const missingCloseResult = spawnSync(process.execPath, [path.join(root, 'bin', 'fpasoterm'), '-q', 'missing'], {
508
+ encoding: 'utf8',
509
+ env: { ...process.env, XDG_CACHE_HOME: listCacheDir },
510
+ });
511
+ assert.equal(missingCloseResult.status, 1);
512
+ assert.match(missingCloseResult.stderr, /no running window matches: missing/);
513
+ assert.doesNotMatch(missingCloseResult.stderr, /Usage: fpasoterm \[options\]/);
514
+
515
+ const unknownOptionResult = runCli('--foo');
516
+ assert.equal(unknownOptionResult.status, 2);
517
+ assert.match(unknownOptionResult.stderr, /fpasoterm: unknown option: --foo/);
518
+ assert.doesNotMatch(unknownOptionResult.stderr, /Usage: fpasoterm \[options\]/);
519
+
520
+ for (const unknownOption of ['--hoge', '-?']) {
521
+ const result = runCli(unknownOption);
522
+ assert.equal(result.status, 2);
523
+ assert.ok(result.stderr.includes(`unknown option: ${unknownOption}`));
524
+ assert.doesNotMatch(result.stderr, /Usage: fpasoterm \[options\]/);
525
+ }
526
+
527
+ const resetConfigDir = fs.mkdtempSync(path.join(os.tmpdir(), 'fpasoterm-reset-config-'));
528
+ const resetConfigPath = path.join(resetConfigDir, 'User', 'config.toml');
529
+ const resetStatePath = path.join(path.dirname(resetConfigPath), 'window-state.json');
530
+ fs.mkdirSync(path.dirname(resetConfigPath), { recursive: true });
531
+ fs.writeFileSync(resetConfigPath, '[window]\ntitle = "custom"\n');
532
+ fs.mkdirSync(path.dirname(resetStatePath), { recursive: true });
533
+ fs.writeFileSync(resetStatePath, '{"window":{"width":777,"height":333}}\n');
534
+ const resetConfigResult = spawnSync(
535
+ process.execPath,
536
+ [path.join(root, 'bin', 'fpasoterm'), '--config', resetConfigPath, '--reset-config'],
537
+ { encoding: 'utf8', env: { ...process.env, XDG_CONFIG_HOME: resetConfigDir } },
538
+ );
539
+ assert.equal(resetConfigResult.status, 0, resetConfigResult.stderr);
540
+ assert.match(resetConfigResult.stdout, /reset config/);
541
+ assert.match(resetConfigResult.stdout, /renamed previous config/);
542
+ assert.match(resetConfigResult.stdout, /deleted saved window state/);
543
+ const resetConfigValue = toml.parse(fs.readFileSync(resetConfigPath, 'utf8'));
544
+ assert.equal(resetConfigValue.window.title, 'fpasoterm');
545
+ assert.equal(resetConfigValue.window.width, 1000);
546
+ assert.equal(resetConfigValue.window.height, 680);
547
+ assert.equal(resetConfigValue.terminal.fontSize, platformDefaultConfig().terminal.fontSize);
548
+ assert.equal(resetConfigValue.sync.enabled, false);
549
+ const resetBackups = fs.readdirSync(path.dirname(resetConfigPath))
550
+ .filter((name) => name.startsWith('config.toml.backup-'));
551
+ assert.equal(resetBackups.length, 1);
552
+ assert.match(fs.readFileSync(path.join(path.dirname(resetConfigPath), resetBackups[0]), 'utf8'), /custom/);
553
+ assert.equal(fs.existsSync(resetStatePath), false);
554
+ fs.rmSync(resetConfigDir, { recursive: true, force: true });
555
+
556
+ const pluginPathResult = runCli('--plugin-path');
557
+ assert.equal(pluginPathResult.status, 0, pluginPathResult.stderr);
558
+ assert.equal(pluginPathResult.stdout.trim(), cliPluginsDir);
559
+
560
+ const emptyPluginListResult = runCli('--plugin-list');
561
+ assert.equal(emptyPluginListResult.status, 0, emptyPluginListResult.stderr);
562
+ assert.match(emptyPluginListResult.stdout, /Local plugins \(User\/plugins\):/);
563
+ assert.match(emptyPluginListResult.stdout, /plugins\/hello\.ts/);
564
+ assert.match(emptyPluginListResult.stdout, /Enabled plugins:\n \(none enabled\)/);
565
+
566
+ const enableResult = runCli('--plugin-enable', 'hello, theme');
567
+ assert.equal(enableResult.status, 0, enableResult.stderr);
568
+ assert.deepEqual(toml.parse(fs.readFileSync(cliConfigPath, 'utf8')).plugins.enabled, [
569
+ 'plugins/hello.ts',
570
+ 'plugins/theme.js',
571
+ ]);
572
+
573
+ const pluginListResult = runCli('--plugin-list');
574
+ assert.equal(pluginListResult.status, 0, pluginListResult.stderr);
575
+ assert.match(pluginListResult.stdout, /Enabled plugins:\n plugins\/hello\.ts\n plugins\/theme\.js/);
576
+ assert.match(pluginListResult.stdout, /plugins\/hello\.ts \[version 1\.2\.3\]/);
577
+
578
+ const pluginInfoResult = runCli('--plugin-info', 'hello');
579
+ assert.equal(pluginInfoResult.status, 0, pluginInfoResult.stderr);
580
+ assert.match(pluginInfoResult.stdout, /Version: 1\.2\.3/);
581
+ assert.match(pluginInfoResult.stdout, /Description: Test plugin/);
582
+
583
+ const uninstallResult = runCli('--plugin-uninstall', 'theme');
584
+ assert.equal(uninstallResult.status, 0, uninstallResult.stderr);
585
+ assert.match(uninstallResult.stdout, /uninstalled local plugin plugins\/theme\.js/);
586
+ assert.equal(fs.existsSync(path.join(cliPluginsDir, 'theme.js')), false);
587
+ assert.equal(fs.existsSync(path.join(path.dirname(cliConfigPath), 'cache', 'plugins', 'theme.js')), false);
588
+ assert.deepEqual(toml.parse(fs.readFileSync(cliConfigPath, 'utf8')).plugins.enabled, [
589
+ 'plugins/hello.ts',
590
+ ]);
591
+
592
+ const incompatibleUninstallResult = runCli('--plugin-uninstall', 'hello.ts', '--plugin-enable', 'hello.ts');
593
+ assert.equal(incompatibleUninstallResult.status, 2);
594
+ assert.match(incompatibleUninstallResult.stderr, /cannot be combined with other plugin mutation options/);
595
+
596
+ const disableResult = runCli('--plugin-disable', 'hello');
597
+ assert.equal(disableResult.status, 0, disableResult.stderr);
598
+ assert.deepEqual(toml.parse(fs.readFileSync(cliConfigPath, 'utf8')).plugins.enabled, []);
599
+
600
+ fs.mkdirSync(path.join(cliPluginsDir, 'nested'), { recursive: true });
601
+ fs.writeFileSync(path.join(cliPluginsDir, 'nested', 'hello.ts'), '// duplicate test plugin\n');
602
+ const discoveredPlugins = discoverPluginFiles(cliConfigPath);
603
+ assert.deepEqual(discoveredPlugins, [
604
+ 'plugins/hello.ts',
605
+ 'plugins/nested/hello.ts',
606
+ ]);
607
+ assert.throws(
608
+ () => resolvePluginSelector('hello.ts', discoveredPlugins, 'enable'),
609
+ /plugin name 'hello\.ts' is ambiguous.*plugins\/nested\/hello\.ts/,
610
+ );
611
+ assert.equal(resolvePluginSelector('nested/hello.ts', discoveredPlugins, 'enable'), 'plugins/nested/hello.ts');
612
+ assert.equal(resolvePluginSelector('nested/hello', discoveredPlugins, 'enable'), 'plugins/nested/hello.ts');
613
+ fs.rmSync(cliTestDir, { recursive: true, force: true });
614
+
615
+ const originalConfigHome = process.env.XDG_CONFIG_HOME;
616
+ const originalConfigPath = process.env.FPASOTERM_CONFIG_PATH;
617
+ const stateTestDir = fs.mkdtempSync(path.join(os.tmpdir(), 'fpasoterm-window-state-'));
618
+ process.env.XDG_CONFIG_HOME = stateTestDir;
619
+ delete process.env.FPASOTERM_CONFIG_PATH;
620
+ fs.mkdirSync(path.join(stateTestDir, 'fpasoterm', 'User'), { recursive: true });
621
+ const statePath = path.join(stateTestDir, 'fpasoterm', 'User', 'window-state.json');
622
+ fs.writeFileSync(path.join(stateTestDir, 'fpasoterm', 'User', 'config.toml'), [
623
+ '[window]',
624
+ 'width = 777',
625
+ '',
626
+ '[keybindings]',
627
+ 'prefix = "Ctrl+Alt"',
628
+ '',
629
+ ].join('\n'));
630
+ fs.writeFileSync(path.join(stateTestDir, 'fpasoterm', 'User', 'config.toml.example'), 'old example\n');
631
+ writeWindowState({ window: { width: 1200, height: 900 } });
632
+ const stateConfig = loadConfig();
633
+ const generatedExample = fs.readFileSync(path.join(stateTestDir, 'fpasoterm', 'User', 'config.toml.example'), 'utf8');
634
+ assert.match(generatedExample, /# fpasoterm user configuration/);
635
+ assert.match(generatedExample, /rememberBounds = true/);
636
+ assert.match(generatedExample, /shell = ""/);
637
+ assert.equal(windowStatePath(), path.join(stateTestDir, 'fpasoterm', 'User', 'window-state.json'));
638
+ assert.equal(stateConfig.config.window.width, 1200);
639
+ assert.equal(stateConfig.config.window.height, 900);
640
+ assert.equal(stateConfig.config.terminal.shell, '');
641
+ assert.equal(stateConfig.config.keybindings.prefix, 'Ctrl+Alt');
642
+ assert.equal(stateConfig.config.keybindings.newWindow, 'N');
643
+ assert.ok(fs.existsSync(path.join(stateTestDir, 'fpasoterm', 'User', 'plugins')));
644
+ fs.writeFileSync(path.join(stateTestDir, 'fpasoterm', 'User', 'config.toml'), [
645
+ '[terminal]',
646
+ 'fontSize = 0',
647
+ '',
648
+ '[plugins]',
649
+ 'enabled = ["plugins/missing.ts"]',
650
+ '',
651
+ ].join('\n'));
652
+ const configDiagnostics = validateUserConfig(path.join(stateTestDir, 'fpasoterm', 'User', 'config.toml'));
653
+ assert.equal(configDiagnostics.error, '');
654
+ assert.ok(configDiagnostics.warnings.includes('terminal.fontSize should be a positive number'));
655
+ assert.ok(configDiagnostics.warnings.includes('plugins.enabled includes plugins/missing.ts but file does not exist'));
656
+ const profileDiagnostics = validateUserConfig(path.join(stateTestDir, 'fpasoterm', 'User', 'config.toml'), 'missing');
657
+ assert.ok(profileDiagnostics.warnings.includes("selected profile 'missing' does not exist"));
658
+ deleteWindowState();
659
+ assert.ok(!fs.existsSync(statePath));
660
+ if (originalConfigHome === undefined) {
661
+ delete process.env.XDG_CONFIG_HOME;
662
+ } else {
663
+ process.env.XDG_CONFIG_HOME = originalConfigHome;
664
+ }
665
+ if (originalConfigPath === undefined) {
666
+ delete process.env.FPASOTERM_CONFIG_PATH;
667
+ } else {
668
+ process.env.FPASOTERM_CONFIG_PATH = originalConfigPath;
669
+ }
670
+ fs.rmSync(stateTestDir, { recursive: true, force: true });
671
+
672
+ const runScript = read('scripts/run');
673
+ assert.doesNotMatch(runScript, /--foreground/);
674
+
675
+ const buildArtifacts = read('scripts/build-artifacts.js');
676
+ assert.match(buildArtifacts, /--bundles', 'deb,rpm'/);
677
+ assert.match(buildArtifacts, /entry\.name\.includes\(version\)/);
678
+ assert.match(buildArtifacts, /--source-only/);
679
+ assert.match(buildArtifacts, /--bundles-only/);
680
+ assert.match(buildArtifacts, /FPASOTERM_ARTIFACT_LABEL/);
681
+ assert.match(buildArtifacts, /labelArtifactName/);
682
+ assert.match(buildArtifacts, /copyWindowsCommandWrapper/);
683
+ assert.match(buildArtifacts, /fpasoterm\.cmd/);
684
+ assert.match(buildArtifacts, /run\('npm', buildArgs\)/);
685
+ assert.match(buildArtifacts, /function npmInvocation/);
686
+ assert.match(buildArtifacts, /npm-cli\.js/);
687
+ assert.match(buildArtifacts, /spawnSync/);
688
+ assert.doesNotMatch(buildArtifacts, /shell: process\.platform === 'win32'/);
689
+ assert.match(buildArtifacts, /\.dmg/);
690
+ assert.match(buildArtifacts, /\.msi/);
691
+ assert.match(buildArtifacts, /\.exe/);
692
+ assert.match(buildArtifacts, /\.app\.tar\.gz/);
693
+
694
+ const windowsCommandWrapper = read('extra/windows/fpasoterm.cmd');
695
+ assert.match(windowsCommandWrapper, /--update-check/);
696
+ assert.match(windowsCommandWrapper, /--doctor/);
697
+ assert.match(windowsCommandWrapper, /FPASOTERM_ARGS/);
698
+ assert.match(windowsCommandWrapper, /--plugin-search/);
699
+ assert.match(windowsCommandWrapper, /--plugin-install/);
700
+ assert.match(windowsCommandWrapper, /--plugin-install-file/);
701
+ assert.match(windowsCommandWrapper, /call "%FPASOTERM_EXE%" %FPASOTERM_ARGS%/);
702
+ assert.doesNotMatch(windowsCommandWrapper, /start "" \/wait/);
703
+ assert.match(windowsCommandWrapper, /--plugin-path/);
704
+ assert.match(windowsCommandWrapper, /--version/);
705
+ assert.match(windowsCommandWrapper, /--broadcast --broadcast-target --broadcast-sync/);
706
+ assert.match(windowsCommandWrapper, /start "" "%FPASOTERM_EXE%" %FPASOTERM_ARGS%/);
707
+
708
+ const releaseWorkflow = read('.github/workflows/release.yml');
709
+ assert.match(releaseWorkflow, /ubuntu-24\.04-arm/);
710
+ assert.match(releaseWorkflow, /macos-15-intel/);
711
+ assert.match(releaseWorkflow, /macos-latest/);
712
+ assert.match(releaseWorkflow, /windows-latest/);
713
+ assert.match(releaseWorkflow, /FPASOTERM_ARTIFACT_LABEL/);
714
+ assert.match(releaseWorkflow, /actions\/checkout@v5/);
715
+ assert.match(releaseWorkflow, /actions\/setup-node@v5/);
716
+ assert.match(releaseWorkflow, /actions\/upload-artifact@v6/);
717
+ assert.match(releaseWorkflow, /gh run download "\$\{GITHUB_RUN_ID\}" --repo "\$\{GITHUB_REPOSITORY\}" --dir release-artifacts/);
718
+ assert.doesNotMatch(releaseWorkflow, /actions\/download-artifact@/);
719
+ assert.match(releaseWorkflow, /gh release upload/);
720
+ assert.match(releaseWorkflow, /--clobber/);
721
+ assert.match(releaseWorkflow, /Verify macOS bundles/);
722
+ assert.match(releaseWorkflow, /codesign --verify --deep --strict/);
723
+ assert.match(releaseWorkflow, /hdiutil verify/);
724
+ assert.match(releaseWorkflow, /Scan for committed secrets/);
725
+ assert.doesNotMatch(releaseWorkflow, /npm run security/);
726
+ assert.doesNotMatch(releaseWorkflow, /ref: \$\{\{ github\.event_name == 'workflow_dispatch' && inputs\.tag/);
727
+
728
+ const checkWorkflow = read('.github/workflows/check.yml');
729
+ assert.match(checkWorkflow, /timeout-minutes: 30/);
730
+ assert.doesNotMatch(checkWorkflow, /Run security checks/);
731
+ assert.match(checkWorkflow, /Swatinem\/rust-cache@v2/);
732
+ assert.doesNotMatch(checkWorkflow, /github\.event_name == 'push' && github\.ref == 'refs\/heads\/main'/);
733
+ assert.match(releaseWorkflow, /Swatinem\/rust-cache@v2/);
734
+
735
+ const securityWorkflow = read('.github/workflows/security.yml');
736
+ assert.match(securityWorkflow, /Scan for committed secrets/);
737
+ assert.match(securityWorkflow, /Audit production dependencies/);
738
+ assert.match(securityWorkflow, /set -o pipefail/);
739
+ assert.match(securityWorkflow, /Invalid package tree/);
740
+
741
+ const installDesktop = read('scripts/install-linux-desktop.js');
742
+ assert.match(installDesktop, /XDG_BIN_HOME/);
743
+ assert.match(installDesktop, /fpasoterm command/);
744
+ assert.match(installDesktop, /APP_ROOT=/);
745
+ assert.match(installDesktop, /buildLocalBinary/);
746
+ assert.match(installDesktop, /cargo',\s*\[/);
747
+ assert.match(installDesktop, /'clean'/);
748
+ assert.match(installDesktop, /latestPathMtime/);
749
+ assert.match(installDesktop, /src\/renderer/);
750
+ assert.match(installDesktop, /writeBuildStamp/);
751
+ assert.match(installDesktop, /\.fpasoterm-normal-build\.json/);
752
+ assert.match(installDesktop, /FPASOTERM_SKIP_DESKTOP_BUILD/);
753
+ assert.match(installDesktop, /installDesktopEntry/);
754
+ assert.match(installDesktop, /desktopExec/);
755
+ assert.doesNotMatch(installDesktop, /TryExec=/);
756
+ assert.match(installDesktop, /currentNodePath/);
757
+ assert.match(installDesktop, /INSTALL_NODE=/);
758
+ assert.match(installDesktop, /launcher\.log/);
759
+ assert.match(installDesktop, /XDG_CACHE_HOME/);
760
+ assert.match(installDesktop, /\.cargo\/bin/);
761
+ assert.match(installDesktop, /FPASOTERM_LAUNCHER_LOG/);
762
+ assert.match(installDesktop, /\/usr\/bin\/node/);
763
+ assert.match(installDesktop, /Icon=io\.github\.oyoguhito\.fpasoterm/);
764
+ assert.match(installDesktop, /fpasoterm\.desktop/);
765
+ assert.match(installDesktop, /io\.github\.oyoguhito\.fpasoterm\.png/);
766
+ assert.doesNotMatch(installDesktop, /mise exec node/);
767
+ assert.match(installDesktop, /io\.github\.oyoguhito\.FpasoTerm\.desktop/);
768
+
769
+ const uninstallDesktop = read('scripts/uninstall-linux-desktop.js');
770
+ assert.match(uninstallDesktop, /XDG_BIN_HOME/);
771
+ assert.match(uninstallDesktop, /removed:/);
772
+ assert.match(uninstallDesktop, /io\.github\.oyoguhito\.fpasoterm\.desktop/);
773
+ assert.match(uninstallDesktop, /fpasoterm\.desktop/);
774
+ assert.match(uninstallDesktop, /io\.github\.oyoguhito\.FpasoTerm\.desktop/);
775
+ assert.match(uninstallDesktop, /fpasoterm\.png/);
776
+ assert.match(uninstallDesktop, /io\.github\.oyoguhito\.fpasoterm\.png/);
777
+ assert.match(uninstallDesktop, /XDG_CONFIG_HOME/);
778
+ assert.match(uninstallDesktop, /XDG_CACHE_HOME/);
779
+ assert.match(uninstallDesktop, /io\.github\.oyoguhito\.fpasoterm/);
780
+ assert.match(uninstallDesktop, /removeDir/);
781
+
782
+ const uninstallEntry = read('scripts/uninstall-desktop.js');
783
+ assert.match(uninstallEntry, /process\.platform === 'win32'/);
784
+ assert.match(uninstallEntry, /uninstall-windows-path\.js/);
785
+ assert.match(uninstallEntry, /uninstall-linux-desktop\.js/);
786
+
787
+ const uninstallWindowsPath = read('scripts/uninstall-windows-path.js');
788
+ assert.match(uninstallWindowsPath, /GetEnvironmentVariable\('Path', 'User'\)/);
789
+ assert.match(uninstallWindowsPath, /SetEnvironmentVariable\('Path'/);
790
+ assert.match(uninstallWindowsPath, /isFpasotermPathEntry/);
791
+ assert.match(uninstallWindowsPath, /fpasoterm/);
792
+ assert.doesNotMatch(uninstallWindowsPath, /setx/i);
793
+
794
+ const tauriConfig = read('src-tauri/tauri.conf.json');
795
+ assert.match(tauriConfig, /"withGlobalTauri": true/);
796
+ assert.match(tauriConfig, /"enableGTKAppId": false/);
797
+ assert.match(tauriConfig, /"macOSPrivateApi": true/);
798
+ assert.match(tauriConfig, /"macOS": \{/);
799
+ assert.match(tauriConfig, /"signingIdentity": "-"/);
800
+ assert.match(tauriConfig, /"transparent": true/);
801
+ assert.match(tauriConfig, /"backgroundColor": "#00000000"/);
802
+ assert.match(tauriConfig, /"decorations": false/);
803
+ assert.match(tauriConfig, /"assetProtocol": \{/);
804
+ assert.match(tauriConfig, /\$HOME\/\.config\/fpasoterm\/User\/plugins\/\*\*\/*/);
805
+ assert.match(tauriConfig, /\$HOME\/\.config\/fpasoterm\/User\/cache\/plugins\/\*\*\/*/);
806
+
807
+ const cargoToml = read('src-tauri/Cargo.toml');
808
+ assert.match(cargoToml, new RegExp(`version = "${packageJson.version}"`));
809
+ assert.match(cargoToml, /tauri =/);
810
+ assert.match(cargoToml, /macos-private-api/);
811
+ assert.match(cargoToml, /image-png/);
812
+ assert.match(cargoToml, /protocol-asset/);
813
+ assert.match(cargoToml, /portable-pty/);
814
+ assert.match(cargoToml, /toml =/);
815
+ assert.match(cargoToml, /windows-sys/);
816
+ assert.match(cargoToml, /Win32_System_Console/);
817
+
818
+ const tauriCapabilities = read('src-tauri/capabilities/default.json');
819
+ assert.match(tauriCapabilities, /core:event:allow-listen/);
820
+ assert.match(tauriCapabilities, /core:window:allow-start-resize-dragging/);
821
+ assert.match(tauriCapabilities, /"windows": \["main"\]/);
822
+
823
+ const rustMain = read('src-tauri/src/main.rs');
824
+ const rustBuild = read('src-tauri/build.rs');
825
+ assert.match(rustBuild, /FPASOTERM_BUILD_COMMIT/);
826
+ assert.match(rustBuild, /jj_working_copy_commit/);
827
+ assert.match(rustBuild, /rev-parse.*HEAD/);
828
+ assert.match(rustBuild, /rerun-if-changed/);
829
+ assert.match(rustMain, /windows_subsystem = "windows"/);
830
+ assert.match(rustMain, /HELP_TEXT/);
831
+ assert.match(rustMain, /apply_direct_cli_env_overrides/);
832
+ assert.match(rustMain, /set_env_from_cli/);
833
+ assert.match(rustMain, /sanitize_cli_value/);
834
+ assert.match(rustMain, /set_env_from_cli\("FPASOTERM_SHELL"/);
835
+ assert.match(rustMain, /set_env_from_cli\("FPASOTERM_WINDOW_TITLE"/);
836
+ assert.match(rustMain, /FPASOTERM_WINDOW_TITLE_LOCKED/);
837
+ assert.match(rustMain, /title_locked/);
838
+ assert.match(rustMain, /set_env_from_cli\("FPASOTERM_START_COMMAND"/);
839
+ assert.match(rustMain, /cli_has_flag\(&\["--help", "-h"\]\)/);
840
+ assert.match(rustMain, /fn cli_help_text/);
841
+ assert.match(rustMain, /print_cli_text\(&cli_help_text\(\)\)/);
842
+ assert.match(rustMain, /cli_has_flag\(&\["--version", "-v"\]\)/);
843
+ assert.match(rustMain, /fn npm_update_check/);
844
+ assert.match(rustMain, /fn public_plugin_search_text/);
845
+ assert.match(rustMain, /fn public_plugin_catalog_entries/);
846
+ assert.match(rustMain, /fn plugin_catalog/);
847
+ assert.match(rustMain, /fn update_check/);
848
+ assert.match(rustMain, /cli_has_flag\(&\["--list", "-l"\]\)/);
849
+ assert.match(rustMain, /fn print_running_instances/);
850
+ assert.match(rustMain, /fn broadcast_targeted_close_request/);
851
+ assert.match(rustMain, /cli_option_value_any\(&\["--close", "-q"\]\)/);
852
+ assert.match(rustMain, /fn broadcast_terminal_input_cli/);
853
+ assert.match(rustMain, /--broadcast-target and --broadcast-sync require --broadcast <text>/);
854
+ assert.match(rustMain, /cli_has_flag\(&\["--broadcast-sync"\]\)/);
855
+ assert.match(rustMain, /write_terminal_broadcast_item\(&local_broadcast_directory\(\), &item\)/);
856
+ assert.match(rustMain, /cli_has_flag\(&\["--reset-config", "-R"\]\)/);
857
+ assert.match(rustMain, /fn reset_config_cli/);
858
+ assert.match(rustMain, /fn app_version/);
859
+ assert.match(rustMain, /FPASOTERM_BUILD_COMMIT/);
860
+ assert.match(rustMain, /commit \{\}/);
861
+ assert.match(rustMain, /fn exit_requested_app_instance/);
862
+ assert.match(rustMain, /app\.exit\(0\)/);
863
+ assert.match(rustMain, /stdout\.flush/);
864
+ assert.match(rustMain, /fn write_macos_cli_shim/);
865
+ assert.match(rustMain, /join\("\.local"\)\.join\("bin"\)/);
866
+ assert.match(rustMain, /joined_terminal_path\(vec!\[local_bin_dir, config_bin_dir, app_dir\]\)/);
867
+ assert.match(rustMain, /exec \{quoted_executable\} \\"\$@\\"/);
868
+ assert.match(rustMain, /fn shell_single_quote/);
869
+ assert.match(rustMain, /fn close_request_path/);
870
+ assert.match(rustMain, /"baseTitle": title/);
871
+ assert.match(rustMain, /fn instance_marker_is_fresh/);
872
+ assert.match(rustMain, /INSTANCE_HEARTBEAT_INTERVAL/);
873
+ assert.match(rustMain, /fn instance_number_from_display_title/);
874
+ assert.match(rustMain, /fn tile_grid/);
875
+ assert.match(rustMain, /fn detach_nested_macos_launch/);
876
+ assert.match(rustMain, /command\.env\("PATH", path_value\)/);
877
+ assert.match(rustMain, /CARGO_PKG_VERSION/);
878
+ assert.match(rustMain, /cli_has_flag\(&\["--show-config"\]\)/);
879
+ assert.match(rustMain, /print_show_config/);
880
+ assert.match(rustMain, /--profile-list/);
881
+ assert.match(rustMain, /print_profile_list_cli/);
882
+ assert.match(rustMain, /validate_selected_profile_cli/);
883
+ assert.match(rustMain, /active_profile/);
884
+ assert.match(rustMain, /--plugin-list/);
885
+ assert.match(rustMain, /--plugin-path/);
886
+ assert.match(rustMain, /--plugin-info <file>/);
887
+ assert.match(rustMain, /--plugin-uninstall <file>/);
888
+ assert.match(rustMain, /fn plugin_cli/);
889
+ assert.match(rustMain, /fn uninstall_local_plugins_cli/);
890
+ assert.match(rustMain, /fn resolve_direct_plugin_urls/);
891
+ assert.match(rustMain, /\[profiles\.large-font\.terminal\]/);
892
+ assert.match(rustMain, /--config-check/);
893
+ assert.match(rustMain, /config_check_cli/);
894
+ assert.match(rustMain, /--doctor/);
895
+ assert.match(rustMain, /fn doctor_cli/);
896
+ assert.match(rustMain, /diagnostics_markdown_cli/);
897
+ assert.match(rustMain, /open_log_directory_cli/);
898
+ assert.match(rustMain, /cli_has_flag\(&\["--reset-window-state", "-r"\]\)/);
899
+ assert.match(rustMain, /reset_window_state_cli/);
900
+ assert.match(rustMain, /print_cli_text_windows/);
901
+ assert.match(rustMain, /AttachConsole/);
902
+ assert.match(rustMain, /CONOUT\$/);
903
+ assert.match(rustMain, /portable_pty/);
904
+ assert.match(rustMain, /ChildKiller/);
905
+ assert.match(rustMain, /terminal_start/);
906
+ assert.match(rustMain, /terminal_write/);
907
+ assert.match(rustMain, /terminal_resize/);
908
+ assert.match(rustMain, /diagnostics:event/);
909
+ assert.match(rustMain, /command\.env\("TERM", "xterm-256color"\)/);
910
+ assert.match(rustMain, /command\.env\("COLORTERM", "truecolor"\)/);
911
+ assert.match(rustMain, /fn configure_utf8_terminal_locale/);
912
+ assert.match(rustMain, /TerminalEncoding::ShiftJis/);
913
+ assert.match(rustMain, /TerminalEncoding::EucJp/);
914
+ assert.match(rustMain, /config_set_terminal_encoding/);
915
+ assert.match(rustMain, /fn terminal_capabilities\(\)/);
916
+ assert.match(rustMain, /"term": "xterm-256color"/);
917
+ assert.match(rustMain, /TERM_PROGRAM/);
918
+ assert.match(rustMain, /config_apply_path/);
919
+ assert.match(rustMain, /runtime_config_from_path/);
920
+ assert.match(rustMain, /direct_runtime_config/);
921
+ assert.match(rustMain, /apply_direct_cli_overrides/);
922
+ assert.match(rustMain, /merge_runtime_config_from_path/);
923
+ assert.match(rustMain, /merge_json_value/);
924
+ assert.match(rustMain, /applied runtime config/);
925
+ assert.match(rustMain, /terminal_write bytes/);
926
+ assert.match(rustMain, /read_configured_shell/);
927
+ assert.match(rustMain, /FPASOTERM_SHELL/);
928
+ assert.match(rustMain, /FPASOTERM_WINDOW_TITLE/);
929
+ assert.match(rustMain, /FPASOTERM_TITLEBAR_COLOR/);
930
+ assert.match(rustMain, /cli_option_value/);
931
+ assert.match(rustMain, /cli_option_value_any\(&\["--shell", "-s"\]\)/);
932
+ assert.match(rustMain, /cli_option_value_any\(&\["--cwd", "-o"\]\)/);
933
+ assert.match(rustMain, /cli_option_value_any\(&\["--config", "-c"\]\)/);
934
+ assert.match(rustMain, /cli_option_value_any\(&\["--title", "-t"\]\)/);
935
+ assert.match(rustMain, /cli_option_value_any\(&\["--titlebar-color", "-b"\]\)/);
936
+ assert.match(rustMain, /cli_option_value_any\(&\["--command", "-e"\]\)/);
937
+ assert.match(rustMain, /cli_positive_u32_option_any\(&\["--width", "-W"\]\)/);
938
+ assert.match(rustMain, /cli_positive_u32_option_any\(&\["--height", "-H"\]\)/);
939
+ assert.match(rustMain, /cli_size_option/);
940
+ assert.match(rustMain, /cli_has_flag\(&\["--debug-keys", "-k"\]\)/);
941
+ assert.match(rustMain, /cli_has_flag\(&\["--console-diagnostics", "-C"\]\)/);
942
+ assert.doesNotMatch(rustMain, /--debug-opaque-terminal/);
943
+ assert.doesNotMatch(rustMain, /--x11/);
944
+ assert.match(rustMain, /cli_has_flag\(&\["--disable-dmabuf"\]\)/);
945
+ assert.match(rustMain, /resolve_shell_command/);
946
+ assert.match(rustMain, /sanitize_shell_value/);
947
+ assert.match(rustMain, /resolve_windows_shell/);
948
+ assert.match(rustMain, /default_windows_shell/);
949
+ assert.match(rustMain, /windows_pwsh_candidates/);
950
+ assert.match(rustMain, /windows_path_executable/);
951
+ assert.match(rustMain, /terminal_path_with_app_dir/);
952
+ assert.match(rustMain, /env::current_exe/);
953
+ assert.match(rustMain, /command\.env\("Path", path_value\)/);
954
+ assert.match(rustMain, /replace\('\\0', ""\)/);
955
+ assert.match(rustMain, /PowerShell\\\\7\\\\pwsh\.exe/);
956
+ assert.match(rustMain, /clone_killer/);
957
+ assert.match(rustMain, /\.wait\(\)/);
958
+ assert.match(rustMain, /macos_login_shell/);
959
+ assert.match(rustMain, /dscl/);
960
+ assert.match(rustMain, /read_saved_window_size/);
961
+ assert.match(rustMain, /window-state\.json/);
962
+ assert.match(rustMain, /UserShell/);
963
+ assert.match(rustMain, /window_save_bounds/);
964
+ assert.match(rustMain, /window_get_bounds/);
965
+ assert.match(rustMain, /window_set_bounds/);
966
+ assert.match(rustMain, /window_minimize/);
967
+ assert.match(rustMain, /window_toggle_maximize/);
968
+ assert.match(rustMain, /fn window_arrange/);
969
+ assert.match(rustMain, /fn window_close_all/);
970
+ assert.match(rustMain, /fn window_new/);
971
+ assert.match(rustMain, /spawn_new_instance/);
972
+ assert.match(rustMain, /fn window_confirm_close_all/);
973
+ assert.match(rustMain, /fn window_close_all_confirmed/);
974
+ assert.match(rustMain, /fn windows_confirm_close_all/);
975
+ assert.match(rustMain, /MessageBoxW/);
976
+ assert.match(rustMain, /MB_OKCANCEL/);
977
+ assert.match(rustMain, /WebviewWindowBuilder/);
978
+ assert.match(rustMain, /OpenProcess/);
979
+ assert.match(rustMain, /GetExitCodeProcess/);
980
+ assert.match(rustMain, /PROCESS_QUERY_LIMITED_INFORMATION/);
981
+ assert.match(rustMain, /\.decorations\(true\)/);
982
+ assert.match(rustMain, /\.transparent\(false\)/);
983
+ assert.match(rustMain, /\.visible\(true\)/);
984
+ assert.match(rustMain, /\.always_on_top\(true\)/);
985
+ assert.match(rustMain, /window\.set_focus\(\)/);
986
+ assert.match(rustMain, /fn window_focus_main/);
987
+ assert.match(rustMain, /fn window_cancel_close_all/);
988
+ assert.match(rustMain, /Ignore a close request left by an earlier application session/);
989
+ assert.match(rustMain, /close_all_request_path/);
990
+ assert.match(rustMain, /start_arrange_listener/);
991
+ assert.match(rustMain, /ArrangeRequest/);
992
+ assert.match(rustMain, /fpasoterm-debug\.log/);
993
+ assert.match(rustMain, /arrange tile minimum override/);
994
+ assert.match(rustMain, /arrange requested windows=.*cells=/);
995
+ assert.match(rustMain, /let \(columns, rows\) = tile_grid\(count\)/);
996
+ assert.doesNotMatch(rustMain, /width_capacity/);
997
+ assert.match(rustMain, /startup window size restore skipped because Tile was requested/);
998
+ assert.match(rustMain, /arrange immediate pid=/);
999
+ assert.match(rustMain, /tile_gap/);
1000
+ assert.match(rustMain, /scale_factor/);
1001
+ assert.match(rustMain, /footprint_width/);
1002
+ assert.match(rustMain, /renderer-available/);
1003
+ assert.match(rustMain, /coordinate_scale/);
1004
+ assert.match(rustMain, /renderer-available-scaled/);
1005
+ assert.doesNotMatch(rustMain, /GDK_BACKEND/);
1006
+ assert.match(rustMain, /PhysicalSize::new\(\s*config\.config\.window\.min_width/);
1007
+ assert.match(rustMain, /ArrangeScreen/);
1008
+ assert.match(rustMain, /\.minimize\(\)/);
1009
+ assert.match(rustMain, /\.maximize\(\)/);
1010
+ assert.match(rustMain, /\.unmaximize\(\)/);
1011
+ assert.match(rustMain, /set_title/);
1012
+ assert.match(rustMain, /set_fpasoterm_window_icon/);
1013
+ assert.match(rustMain, /include_bytes!\("\.\.\/\.\.\/extra\/logo\/fpasoterm\.png"\)/);
1014
+ assert.doesNotMatch(rustMain, /default_window_icon/);
1015
+ assert.match(rustMain, /\.set_icon\(icon\)/);
1016
+ assert.match(rustMain, /PhysicalPosition/);
1017
+ assert.match(rustMain, /restoring window size/);
1018
+ assert.match(rustMain, /PhysicalSize::new\(config\.config\.window\.width/);
1019
+ assert.match(rustMain, /schedule_startup_size_restore/);
1020
+ assert.match(rustMain, /Duration::from_millis\(650\)/);
1021
+ assert.match(rustMain, /startup window size restore requested/);
1022
+ assert.match(rustMain, /claim_instance_index/);
1023
+ assert.match(rustMain, /InstanceMarker/);
1024
+ assert.match(rustMain, /startup same-title instance index/);
1025
+ assert.match(rustMain, /apply_instance_identity/);
1026
+ assert.match(rustMain, /varied_titlebar_color/);
1027
+ assert.match(rustMain, /parse_css_color/);
1028
+ assert.match(rustMain, /publish_runtime_config/);
1029
+ assert.match(rustMain, /format!\("\{\}-\{\}"/);
1030
+ assert.match(rustMain, /cache_dir_path/);
1031
+ assert.match(rustMain, /WindowEvent::Resized\(size\)/);
1032
+ assert.match(rustMain, /save_window_size\(\*size/);
1033
+ assert.doesNotMatch(rustMain, /WindowEvent::Moved/);
1034
+ assert.doesNotMatch(rustMain, /WindowEvent::Destroyed/);
1035
+ assert.match(rustMain, /config_get/);
1036
+ assert.match(rustMain, /WEBKIT_DISABLE_DMABUF_RENDERER/);
1037
+ assert.match(rustMain, /FPASOTERM_RUNTIME_CONFIG_JSON/);
1038
+ assert.match(rustMain, /sync_status/);
1039
+ assert.match(rustMain, /sync_clean/);
1040
+ assert.match(rustMain, /sync_status_text/);
1041
+ assert.match(rustMain, /inspect_sync_channels/);
1042
+ assert.match(rustMain, /removed_expired_commands/);
1043
+ assert.doesNotMatch(rustMain, /sync_write_clipboard/);
1044
+ assert.doesNotMatch(rustMain, /sync_read_clipboard/);
1045
+ assert.match(rustMain, /sync_write_diagnostics/);
1046
+ assert.match(rustMain, /clipboard_read/);
1047
+ assert.match(rustMain, /clipboard_write/);
1048
+ assert.match(rustMain, /clipboard_read_output/);
1049
+ assert.match(rustMain, /timeout/);
1050
+ assert.match(rustMain, /remain alive as the clipboard owner/);
1051
+ assert.match(rustMain, /program == "wl-copy"/);
1052
+ assert.match(rustMain, /wl-paste/);
1053
+ assert.match(rustMain, /--type", "text\/plain/);
1054
+ assert.match(rustMain, /xclip/);
1055
+ assert.match(rustMain, /UTF8_STRING/);
1056
+ assert.match(rustMain, /pbpaste/);
1057
+ assert.match(rustMain, /Get-Clipboard -Raw/);
1058
+ assert.match(rustMain, /WINDOWS_CF_UNICODETEXT/);
1059
+ assert.match(rustMain, /read_windows_clipboard_native/);
1060
+ assert.match(rustMain, /write_windows_clipboard_native/);
1061
+ assert.match(rustMain, /String::from_utf16_lossy/);
1062
+ assert.match(rustMain, /SetClipboardData\(WINDOWS_CF_UNICODETEXT/);
1063
+ assert.match(rustMain, /text\.encode_utf16\(\)/);
1064
+ assert.match(rustMain, /read_windows_clipboard_with_powershell/);
1065
+ assert.match(rustMain, /write_windows_clipboard_with_powershell/);
1066
+ assert.match(rustMain, /windows_clipboard_temp_path/);
1067
+ assert.match(rustMain, /strip_utf8_bom/);
1068
+ assert.match(rustMain, /Set-Content -LiteralPath \$args\[0\] -Encoding UTF8/);
1069
+ assert.match(rustMain, /Get-Content -LiteralPath \$args\[0\] -Raw -Encoding UTF8/);
1070
+ assert.doesNotMatch(rustMain, /Set-Clipboard -Value \(\[Console\]::In\.ReadToEnd\(\)\)/);
1071
+ assert.doesNotMatch(rustMain, removedSnakeHttpUiPattern);
1072
+ assert.doesNotMatch(rustMain, removedKebabHttpUiPattern);
1073
+ assert.doesNotMatch(rustMain, /TcpListener::bind/);
1074
+ assert.doesNotMatch(rustMain, /handle\.join\(\)/);
1075
+ assert.match(rustMain, /terminal_log_start/);
1076
+ assert.match(rustMain, /terminal_log_stop/);
1077
+ assert.match(rustMain, /terminal_log_status/);
1078
+ assert.match(rustMain, /terminal_log_show/);
1079
+ assert.match(rustMain, /terminal_log_clear/);
1080
+ assert.match(rustMain, /terminal_log_list/);
1081
+ assert.match(rustMain, /terminal_log_delete/);
1082
+ assert.match(rustMain, /TerminalLogPreview/);
1083
+ assert.match(rustMain, /TerminalLogItem/);
1084
+ assert.match(rustMain, /last_terminal_log_path/);
1085
+ assert.match(rustMain, /latest_terminal_log_path/);
1086
+ assert.match(rustMain, /read_terminal_log_tail/);
1087
+ assert.match(rustMain, /clean_terminal_log_preview_text/);
1088
+ assert.match(rustMain, /struct TerminalOutputDecoder/);
1089
+ assert.match(rustMain, /struct TerminalTextNormalizer/);
1090
+ assert.match(rustMain, /jis_x0201_kana/);
1091
+ assert.match(rustMain, /jis_x0201_half_width_kana/);
1092
+ assert.match(rustMain, /utf8_sequence_width/);
1093
+ assert.match(rustMain, /decoder\.decode\(&buffer\[..read\]\)/);
1094
+ assert.match(rustMain, /log\.normalizer\.normalize\(&text\)/);
1095
+ assert.match(rustMain, /terminal_output_decoder_preserves_utf8/);
1096
+ assert.match(rustMain, /terminal_output_decoder_maps_shift_jis_half_width_kana/);
1097
+ assert.match(rustMain, /terminal_output_decoder_decodes_shift_jis_text/);
1098
+ assert.match(rustMain, /terminal_output_decoder_maps_invalid_utf8_half_width_kana_bytes/);
1099
+ assert.match(rustMain, /terminal_output_decoder_maps_iso2022_half_width_kana_across_chunks/);
1100
+ assert.match(rustMain, /terminal_text_normalizer_converts_cursor_rows_to_newlines/);
1101
+ assert.match(rustMain, /terminal_text_normalizer_keeps_cursor_forward_as_spaces/);
1102
+ assert.match(rustMain, /terminal_text_normalizer_composes_japanese_voicing_marks/);
1103
+ assert.match(rustMain, /terminal_text_normalizer_preserves_half_width_kana/);
1104
+ assert.match(rustMain, /Appends cleaned PTY output bytes/);
1105
+ assert.match(rustMain, /first_csi_numeric_parameter/);
1106
+ assert.match(rustMain, /first_two_csi_numeric_parameters/);
1107
+ assert.doesNotMatch(rustMain, /push_terminal_preview_newline/);
1108
+ assert.match(rustMain, /struct TerminalLog/);
1109
+ assert.match(rustMain, /append_terminal_log/);
1110
+ assert.match(rustMain, /stopped log files deleted/);
1111
+ assert.match(rustMain, /terminal output log files deleted/);
1112
+ assert.match(rustMain, /selected terminal output log deleted/);
1113
+ assert.match(rustMain, /delete_or_clear_terminal_log_file/);
1114
+ assert.match(rustMain, /locked log files emptied/);
1115
+ assert.match(rustMain, /terminal-\{\}-\{\}\.log/);
1116
+ assert.match(rustMain, /log_file_component/);
1117
+ assert.match(rustMain, /terminal_broadcast/);
1118
+ assert.match(rustMain, /TerminalBroadcastItem/);
1119
+ assert.match(rustMain, /sync_command_directory/);
1120
+ assert.match(rustMain, /target_instance_ids/);
1121
+ assert.match(rustMain, /terminal_broadcast_targets/);
1122
+ assert.match(rustMain, /live_terminal_broadcast_targets/);
1123
+ assert.match(rustMain, /expand_path_variables/);
1124
+ assert.match(rustMain, /env::var\(&name\)/);
1125
+ assert.match(rustMain, /SyncItem/);
1126
+ assert.doesNotMatch(rustMain, /clipboard\.json/);
1127
+ assert.match(rustMain, /diagnostics\.json/);
1128
+
1129
+ const indexHtml = read('src/renderer/index.html');
1130
+ assert.match(indexHtml, /id="terminal-broadcast-target-list"/);
1131
+ assert.match(indexHtml, /id="terminal-broadcast-control"/);
1132
+ assert.match(indexHtml, /id="terminal-broadcast-control-insert"/);
1133
+ assert.match(indexHtml, /Enter \/ CR \(0x0D\)/);
1134
+ assert.match(indexHtml, /id="terminal-broadcast-hint"/);
1135
+ assert.match(indexHtml, /<strong>Send: Shift\+Enter<\/strong>\. New line: Enter\./);
1136
+ assert.match(indexHtml, /id="terminal-broadcast-focus-status"/);
1137
+ assert.match(indexHtml, />Send \(Shift\+Enter\)</);
1138
+ assert.match(indexHtml, /id="terminal-broadcast-confirm"/);
1139
+ assert.match(indexHtml, /id="terminal-broadcast-confirm-ok"/);
1140
+ assert.match(indexHtml, /id="new-window-cwd"/);
1141
+ assert.match(indexHtml, /id="diagnostics-menu-items"[\s\S]*id="terminal-kill"/);
1142
+ assert.match(indexHtml, /id="terminal-url-dialog"/);
1143
+ assert.match(indexHtml, /id="terminal-url-copy"/);
1144
+ assert.match(indexHtml, /id="terminal-url-open"/);
1145
+ assert.match(indexHtml, />Send Anyway</);
1146
+ assert.match(indexHtml, /Ctrl\+C \(0x03\)/);
1147
+ assert.match(indexHtml, /Ctrl\+X \(0x18\)/);
1148
+ assert.doesNotMatch(indexHtml, /value="escape"/);
1149
+ assert.match(indexHtml, /id="terminal-capability-test"/);
1150
+ assert.match(indexHtml, /id="terminal-capability-preview"/);
1151
+ assert.match(indexHtml, /data-panel-drag-handle/);
1152
+ assert.match(indexHtml, /Select All/);
1153
+ assert.match(indexHtml, /Select None/);
1154
+ const confirmHtml = read('src/renderer/confirm.html');
1155
+ assert.match(indexHtml, /id="drag-region"/);
1156
+ assert.match(indexHtml, /id="window-title"/);
1157
+ assert.match(indexHtml, /id="window-controls"/);
1158
+ assert.doesNotMatch(indexHtml, /id="sync-menu"/);
1159
+ assert.match(indexHtml, /id="sync-status"/);
1160
+ assert.match(indexHtml, /id="sync-clean"/);
1161
+ assert.match(indexHtml, /id="sync-menu-toggle"/);
1162
+ assert.match(indexHtml, /id="sync-menu-items"/);
1163
+ assert.match(indexHtml, /id="sync-menu-items"[\s\S]*id="sshfs-manager"/);
1164
+ assert.doesNotMatch(indexHtml, /id="sync-copy"/);
1165
+ assert.doesNotMatch(indexHtml, /id="sync-paste"/);
1166
+ assert.doesNotMatch(indexHtml, /id="sync-diagnostics"/);
1167
+ assert.doesNotMatch(indexHtml, /Copy Selection/);
1168
+ assert.doesNotMatch(indexHtml, /Pull to Clipboard/);
1169
+ assert.doesNotMatch(indexHtml, /Write Diagnostics/);
1170
+ assert.doesNotMatch(indexHtml, /id="copy-diagnostics"/);
1171
+ assert.match(indexHtml, /id="close-diagnostics"/);
1172
+ assert.doesNotMatch(indexHtml, removedKebabHttpUiPattern);
1173
+ assert.doesNotMatch(indexHtml, /id="log-menu"/);
1174
+ assert.match(indexHtml, /id="log-menu-toggle"/);
1175
+ assert.match(indexHtml, /id="terminal-log-status" hidden aria-live="polite">Logging</);
1176
+ assert.match(indexHtml, /id="log-menu-items"/);
1177
+ assert.match(indexHtml, /id="terminal-log-toggle"/);
1178
+ assert.match(indexHtml, /id="terminal-log-show"/);
1179
+ assert.match(indexHtml, /id="diagnostics-menu-toggle"/);
1180
+ assert.match(indexHtml, /id="diagnostics-menu-items"/);
1181
+ assert.match(indexHtml, /id="window-actions-menu-toggle"/);
1182
+ assert.match(indexHtml, /id="window-actions-menu-items"/);
1183
+ assert.match(indexHtml, /id="window-actions-menu-items"[\s\S]*id="new-window"/);
1184
+ assert.ok(indexHtml.indexOf('id="sshfs-manager"') < indexHtml.indexOf('id="window-actions-menu-items"'));
1185
+ assert.match(indexHtml, /id="sshfs-manager-dialog"/);
1186
+ assert.match(indexHtml, /id="sshfs-manager-form"/);
1187
+ assert.match(indexHtml, /id="sshfs-manager-unmount-all"/);
1188
+ assert.match(indexHtml, /id="sshfs-manager-forget"/);
1189
+ assert.match(indexHtml, /id="sshfs-manager-password-toggle"/);
1190
+ assert.match(indexHtml, /id="sshfs-manager-local-path"/);
1191
+ assert.match(rustMain, /fn sshfs_mount/);
1192
+ assert.match(rustMain, /fn unix_mount_table_contains/);
1193
+ assert.match(rustMain, /wait_for_macos_sshfs_mount/);
1194
+ assert.match(rustMain, /command\.arg\("-f"\)/);
1195
+ assert.match(rustMain, /active_mount_names/);
1196
+ assert.match(rustMain, /FPASOTERM_SSHFS_PATH/);
1197
+ assert.match(rustMain, /SSHFS-Win/);
1198
+ assert.match(rustMain, /fn windows_available_drive_letter/);
1199
+ assert.match(rustMain, /fn windows_sshfs_unc/);
1200
+ assert.match(rustMain, /if path\.is_empty\(\)/);
1201
+ assert.match(rustMain, /let available = sshfs_program\.is_file\(\)/);
1202
+ assert.match(rustMain, /WNetAddConnection3W/);
1203
+ assert.match(rustMain, /WNetGetConnectionW/);
1204
+ assert.match(rustMain, /fn windows_verify_network_drive/);
1205
+ assert.match(rustMain, /fn windows_verify_network_drive_unmounted/);
1206
+ assert.match(rustMain, /write_sshfs_mount_records\(&mounts\)\?/);
1207
+ assert.match(rustMain, /window\s*\.hwnd\(\)/);
1208
+ assert.match(rustMain, /WNetCancelConnection2W/);
1209
+ assert.match(rustMain, /fn windows_confirm_force_sshfs_unmount/);
1210
+ assert.match(rustMain, /fn windows_force_unmount_network_drive/);
1211
+ assert.match(rustMain, /taskkill/);
1212
+ assert.match(rustMain, /fn windows_mount_network_drive/);
1213
+ assert.match(rustMain, /RESOURCETYPE_DISK/);
1214
+ assert.match(rustMain, /CONNECT_TEMPORARY \| CONNECT_INTERACTIVE \| CONNECT_PROMPT/);
1215
+ assert.match(rustMain, /program_path/);
1216
+ assert.match(rustMain, /password_stdin/);
1217
+ assert.match(rustMain, /fn sshfs_unmount/);
1218
+ assert.match(rustMain, /fn sshfs_forget/);
1219
+ assert.match(read('docs/sshfs.en.md'), /ssh-agent/);
1220
+ assert.match(read('docs/sshfs.en.md'), /network-drive interface/);
1221
+ assert.match(read('docs/sshfs.en.md'), /forced recovery.*SSHFS-Win/i);
1222
+ assert.match(read('docs/sshfs.ja.md'), /password_stdin/);
1223
+ assert.match(read('docs/sshfs.ja.md'), /ディスク型network drive/);
1224
+ assert.match(read('docs/sshfs.ja.md'), /強制復旧/);
1225
+ assert.match(indexHtml, /id="terminal-copy"/);
1226
+ assert.match(indexHtml, /id="terminal-paste"/);
1227
+ assert.match(indexHtml, /id="keybinding-prefix"/);
1228
+ assert.match(indexHtml, /Shortcut prefix: Ctrl\+Shift/);
1229
+ assert.match(indexHtml, /aria-keyshortcuts="[^"]*Control\+Shift\+L[^"]*"/);
1230
+ assert.match(indexHtml, /aria-keyshortcuts="Control\+Shift\+S"/);
1231
+ assert.match(indexHtml, /aria-keyshortcuts="Control\+Shift\+P"/);
1232
+ assert.match(indexHtml, /aria-keyshortcuts="Control\+Shift\+C"/);
1233
+ assert.match(indexHtml, /aria-keyshortcuts="Control\+Shift\+V"/);
1234
+ assert.doesNotMatch(indexHtml, /id="terminal-log-clear"/);
1235
+ assert.match(indexHtml, /id="terminal-log-select"/);
1236
+ assert.match(indexHtml, /id="terminal-log-search"/);
1237
+ assert.match(indexHtml, /id="terminal-log-search-next"/);
1238
+ assert.match(indexHtml, /Use N for next and P for previous/);
1239
+ assert.match(indexHtml, /id="terminal-log-search-status"/);
1240
+ assert.match(indexHtml, /id="terminal-log-show-selected"/);
1241
+ assert.match(indexHtml, /id="terminal-log-delete-selected"/);
1242
+ assert.match(indexHtml, /id="terminal-log-delete-all"/);
1243
+ assert.match(indexHtml, /id="diagnostics-panel" hidden tabindex="-1"/);
1244
+ assert.match(indexHtml, /id="terminal-log-confirm"/);
1245
+ assert.match(indexHtml, /role="dialog"/);
1246
+ assert.match(indexHtml, /id="terminal-log-confirm-ok"/);
1247
+ assert.match(indexHtml, /id="terminal-log-confirm-cancel"/);
1248
+ assert.doesNotMatch(indexHtml, />Log \(\^L\)</);
1249
+ assert.match(indexHtml, />Log Start \(S\)</);
1250
+ assert.match(indexHtml, />Log Show \(P\)</);
1251
+ assert.match(indexHtml, />Copy \(C\)</);
1252
+ assert.match(indexHtml, />Paste \(V\)</);
1253
+ assert.doesNotMatch(indexHtml, />Clear</);
1254
+ assert.match(indexHtml, /id="minimize-window"/);
1255
+ assert.match(indexHtml, /id="maximize-window"/);
1256
+ assert.match(indexHtml, /id="new-window"/);
1257
+ assert.match(indexHtml, />New \(N\)</);
1258
+ assert.match(indexHtml, /aria-keyshortcuts="Control\+Shift\+N"/);
1259
+ assert.match(indexHtml, /id="arrange-window"/);
1260
+ assert.match(indexHtml, /id="close-all-windows"/);
1261
+ assert.match(indexHtml, />Close All \(X\)</);
1262
+ assert.match(indexHtml, /aria-keyshortcuts="Control\+Shift\+X"/);
1263
+ assert.match(indexHtml, /id="close-all-confirm" hidden role="dialog"/);
1264
+ assert.match(indexHtml, /id="close-all-confirm-ok"/);
1265
+ assert.match(indexHtml, /id="close-all-confirm-cancel"/);
1266
+ assert.match(confirmHtml, /Close all fpasoterm windows/);
1267
+ assert.match(confirmHtml, /window_close_all_confirmed/);
1268
+ assert.match(confirmHtml, /event\.key === 'Tab'/);
1269
+ assert.match(confirmHtml, /event\.key === 'Enter'/);
1270
+ assert.match(confirmHtml, /currentWindow\.setFocus/);
1271
+ assert.match(confirmHtml, /cancel\.focus/);
1272
+ assert.match(confirmHtml, /window_cancel_close_all/);
1273
+ assert.match(indexHtml, />Tile \(T\)</);
1274
+ assert.match(indexHtml, /aria-keyshortcuts="Control\+Shift\+T"/);
1275
+ assert.match(indexHtml, /id="window-menu-toggle"/);
1276
+ assert.match(indexHtml, /id="window-menu-toggle"[^>]+aria-keyshortcuts="Control\+Shift\+M Control\+Shift\+L"/);
1277
+ assert.match(indexHtml, /id="keyboard-shortcuts-help"/);
1278
+ assert.match(indexHtml, />Help \(H\)</);
1279
+ assert.match(indexHtml, /aria-keyshortcuts="Control\+Shift\+H"/);
1280
+ assert.match(indexHtml, /id="window-menu-items"/);
1281
+ assert.match(indexHtml, /id="plugin-menu-section"/);
1282
+ assert.match(indexHtml, /id="plugin-menu-toggle"/);
1283
+ assert.match(indexHtml, /id="plugin-command-items"/);
1284
+ assert.match(indexHtml, /id="close-window"/);
1285
+ assert.match(indexHtml, /id="terminal"/);
1286
+ assert.match(indexHtml, /id="ime-preedit"/);
1287
+ assert.match(indexHtml, /id="ime-status"/);
1288
+ assert.match(indexHtml, /id="terminal-encoding-select"/);
1289
+ assert.match(indexHtml, /value="shift-jis"/);
1290
+ assert.match(indexHtml, /id="terminal-encoding-apply"/);
1291
+ assert.match(indexHtml, /id="terminal-mirror"/);
1292
+ assert.match(indexHtml, /vendor\/xterm\/xterm\.js/);
1293
+ assert.match(indexHtml, /vendor\/addon-fit\/addon-fit\.js/);
1294
+
1295
+ const readme = read('README.md');
1296
+ const readmeJa = read('README.ja.md');
1297
+ assert.match(readme, /Tauri, xterm\.js, and a Rust PTY bridge/);
1298
+ assert.match(readme, /~\/\.config\/fpasoterm\/User\/config\.toml/);
1299
+ assert.match(readme, /plugins\/.*\.ts/);
1300
+ assert.match(readme, /docs\/config\.en\.md/);
1301
+ assert.match(readme, /docs\/sync\.en\.md/);
1302
+ assert.match(readme, /README\.ja\.md/);
1303
+ assert.match(readme, /INSTALL\.ja\.md/);
1304
+ assert.doesNotMatch(readme, removedTemporaryHttpUiDocPattern);
1305
+ assert.match(readme, /--setup-sync/);
1306
+ assert.match(readme, /--sync-status/);
1307
+ assert.match(readme, /--sync-clean/);
1308
+ assert.match(readme, /--broadcast/);
1309
+ assert.match(readme, /node \.\\bin\\fpasoterm --setup-sync/);
1310
+ assert.match(readme, /docs\/pr-review\.en\.md/);
1311
+ assert.match(readme, /examples\/plugins/);
1312
+ assert.match(readme, /registerCommand\(\)/);
1313
+ assert.match(readme, /--plugin-list/);
1314
+ assert.match(readme, /--plugin-uninstall/);
1315
+ assert.match(readme, /docs\/plugins\.en\.md/);
1316
+ assert.match(readme, /--shell/);
1317
+ assert.match(readme, /--profile <name>/);
1318
+ assert.match(readme, /PowerShell\\7\\pwsh\.exe/);
1319
+ assert.match(readme, /PowerShell 7 \(`pwsh\.exe`\) by default/);
1320
+ assert.match(readme, /fpasoterm executable directory at the[\s\S]*front of `Path`/);
1321
+ assert.match(readme, /GTK application id is disabled/);
1322
+ assert.match(readme, /Tile \(\^T\)/);
1323
+ assert.match(readme, /ChromeOS shelf/);
1324
+ assert.match(readme, /removes fpasoterm-specific directories from the[\s\S]*current user's `Path`/);
1325
+ assert.match(readme, /By default, fpasoterm keeps its configured title/);
1326
+ assert.match(readme, /--command/);
1327
+ assert.match(readme, /--cwd/);
1328
+ assert.match(readme, /--version/);
1329
+ assert.match(readme, /fpasoterm -v/);
1330
+ assert.match(readme, /--reset-window-state/);
1331
+ assert.match(readme, /--disable-dmabuf/);
1332
+ assert.match(readme, /OSC 777|033\]777/);
1333
+ assert.match(readme, /POSIX shell/);
1334
+ assert.match(readme, /Windows PowerShell and cmd\.exe do not run those `printf` examples as-is/);
1335
+ assert.match(readme, /\[Console\]::Write\("\$\(\[char\]27\)\]777;title=work;titlebarColor=#2e7d32/);
1336
+ assert.match(readme, /examples\/config\/runtime-appearance\.toml/);
1337
+ assert.match(readme, /apply-runtime-appearance\.sh/);
1338
+ assert.match(readme, /apply-runtime-appearance\.ps1/);
1339
+ assert.match(readme, /apply-runtime-appearance\.bat/);
1340
+ assert.match(readme, /\\a\\r\\n/);
1341
+ assert.match(readme, /Runtime config application keeps the current shell session running/);
1342
+ assert.match(readme, /window-state\.json/);
1343
+ assert.match(readme, /known-issues\.en\.md/);
1344
+ assert.match(readme, /Google Drive API or OAuth/);
1345
+ assert.doesNotMatch(readme, /temporary read-only/);
1346
+ assert.doesNotMatch(readme, /一時的なリモート出力取得/);
1347
+ assert.match(readme, /docs\/debugging\.en\.md/);
1348
+ assert.match(readmeJa, /docs\/debugging\.ja\.md/);
1349
+ assert.match(readmeJa, /CONTRIBUTING\.ja\.md/);
1350
+
1351
+ const debuggingDocsEn = read('docs/debugging.en.md');
1352
+ const debuggingDocsJa = read('docs/debugging.ja.md');
1353
+ const contributingJa = read('CONTRIBUTING.ja.md');
1354
+ assert.match(debuggingDocsEn, /--dev --foreground --debug-keys --console-diagnostics/);
1355
+ assert.match(debuggingDocsEn, /compositionstart\|compositionupdate\|compositionend/);
1356
+ assert.match(debuggingDocsJa, /IME Composition Trace/);
1357
+ assert.match(debuggingDocsJa, /--debug-keys/);
1358
+ assert.match(contributingJa, /npm run check/);
1359
+ assert.match(contributingJa, /docs\/debugging\.ja\.md/);
1360
+
1361
+ const configDocsEn = read('docs/config.en.md');
1362
+ assert.match(configDocsEn, /\[window\]/);
1363
+ assert.match(configDocsEn, /title = "fpasoterm"/);
1364
+ assert.match(configDocsEn, /width = 1000/);
1365
+ assert.match(configDocsEn, /height = 680/);
1366
+ assert.match(configDocsEn, /titlebarColor = "#1565c0"/);
1367
+ assert.match(configDocsEn, /titleLocked = true/);
1368
+ assert.match(configDocsEn, /titleLocked` defaults to `true`/);
1369
+ assert.match(configDocsEn, /shell-emitted title changes are ignored/);
1370
+ assert.match(configDocsEn, /rememberBounds = true/);
1371
+ assert.match(configDocsEn, /frame = false/);
1372
+ assert.match(configDocsEn, /allowTransparency = true/);
1373
+ assert.match(configDocsEn, /backgroundOpacity = 0\.65/);
1374
+ assert.match(configDocsEn, /termName = "xterm-256color"/);
1375
+ assert.match(configDocsEn, /encoding = "utf-8"/);
1376
+ assert.match(configDocsEn, /fontSize = 14/);
1377
+ assert.match(configDocsEn, /lineHeight = 1/);
1378
+ assert.match(configDocsEn, /terminal\.lineHeight/);
1379
+ assert.match(configDocsEn, /minimumContrastRatio = 1/);
1380
+ assert.match(configDocsEn, /selected ANSI and RGB colors/);
1381
+ assert.match(configDocsEn, /rescaleOverlappingGlyphs = false/);
1382
+ assert.match(configDocsEn, /half-width kana/);
1383
+ assert.match(configDocsEn, /shell = ""/);
1384
+ assert.match(configDocsEn, /kittyKeyboard = false/);
1385
+ assert.match(configDocsEn, /pwsh\.exe/);
1386
+ assert.match(configDocsEn, /PowerShell 7 \(`pwsh\.exe`\) is the default/);
1387
+ assert.match(configDocsEn, /PowerShell\\7\\pwsh\.exe/);
1388
+ assert.match(configDocsEn, /On Windows/);
1389
+ assert.match(configDocsEn, /\.config\/fpasoterm\/bin\/fpasoterm/);
1390
+ assert.match(configDocsEn, /\.local\/bin\/fpasoterm/);
1391
+ assert.match(configDocsEn, /forwards every argument unchanged/);
1392
+ assert.match(configDocsEn, /nested macOS GUI launch detaches/);
1393
+ assert.doesNotMatch(configDocsEn, /duplicateWindowMs/);
1394
+ assert.match(configDocsEn, /window-state\.json/);
1395
+ assert.match(configDocsEn, /same table to be defined more than once/);
1396
+ assert.match(configDocsEn, /OSC 777/);
1397
+ assert.match(configDocsEn, /The following `printf` examples are for POSIX shells/);
1398
+ assert.match(configDocsEn, /They do not run as-is in Windows PowerShell or cmd\.exe/);
1399
+ assert.match(configDocsEn, /\[Console\]::Write\("\$\(\[char\]27\)\]777;title=work;titlebarColor=#2e7d32/);
1400
+ assert.match(configDocsEn, /titlebarColor=#2e7d32/);
1401
+ assert.match(configDocsEn, /opacity=0\.65/);
1402
+ assert.match(configDocsEn, /examples\/config\/runtime-appearance\.toml/);
1403
+ assert.match(configDocsEn, /apply-runtime-appearance\.sh/);
1404
+ assert.match(configDocsEn, /apply-runtime-appearance\.ps1/);
1405
+ assert.match(configDocsEn, /apply-runtime-appearance\.bat/);
1406
+ assert.match(configDocsEn, /RUNTIME SAMPLE ACTIVE/);
1407
+ assert.match(configDocsEn, /apply-default-appearance\.sh/);
1408
+ assert.match(configDocsEn, /apply-default-appearance\.ps1/);
1409
+ assert.match(configDocsEn, /apply-default-appearance\.bat/);
1410
+ assert.match(configDocsEn, /\\a\\r\\n/);
1411
+ assert.match(configDocsEn, /Settings that require a new PTY/);
1412
+ assert.match(configDocsEn, /TERM=xterm-256color/);
1413
+ assert.match(configDocsEn, /Do not run `kitten icat`, `chafa --format kitty`, or `chafa --format sixels`/);
1414
+ assert.match(configDocsEn, /`\[terminal\.images\]` is reserved and ignored by current builds/);
1415
+ assert.match(configDocsEn, /\[sync\]/);
1416
+ assert.match(configDocsEn, /provider = "folder"/);
1417
+ assert.match(configDocsEn, /sync\.en\.md/);
1418
+ assert.match(configDocsEn, /\[logging\]/);
1419
+ assert.match(configDocsEn, /Ctrl\+Shift\+L/);
1420
+ assert.match(configDocsEn, /Ctrl\+Shift\+S/);
1421
+ assert.match(configDocsEn, /Ctrl\+Shift\+P/);
1422
+ assert.match(configDocsEn, /Ctrl\+Esc.*invalid/);
1423
+ assert.match(configDocsEn, /Ctrl\+Alt\+Escape/);
1424
+ assert.match(configDocsEn, /absolute path of the configuration/);
1425
+ assert.match(configDocsEn, /--update-config/);
1426
+ assert.match(configDocsEn, /--prune-config/);
1427
+ assert.match(configDocsEn, /Delete All/);
1428
+ assert.match(configDocsEn, /opens a selector/);
1429
+ assert.match(configDocsEn, /delete the selected stopped log/);
1430
+ assert.match(configDocsEn, /readable terminal output with control sequences removed/);
1431
+ assert.match(configDocsEn, /%USERPROFILE%/);
1432
+ assert.match(configDocsEn, /\$HOME/);
1433
+ assert.match(configDocsEn, /most portable form/);
1434
+ assert.doesNotMatch(configDocsEn, /\[web[C]onsole\]/);
1435
+ assert.match(configDocsEn, /log=start/);
1436
+
1437
+ const configDocsJa = read('docs/config.ja.md');
1438
+ assert.match(configDocsJa, /全デフォルト/);
1439
+ assert.match(configDocsJa, /\[window\]/);
1440
+ assert.match(configDocsJa, /examples\/plugins/);
1441
+ assert.match(configDocsJa, /title = "fpasoterm"/);
1442
+ assert.match(configDocsJa, /titlebarColor = "#1565c0"/);
1443
+ assert.match(configDocsJa, /titleLocked = true/);
1444
+ assert.match(configDocsJa, /`titleLocked` は既定で `true`/);
1445
+ assert.match(configDocsJa, /shell が送る title change は無視/);
1446
+ assert.match(configDocsJa, /rememberBounds = true/);
1447
+ assert.match(configDocsJa, /frame = false/);
1448
+ assert.match(configDocsJa, /allowTransparency = true/);
1449
+ assert.match(configDocsJa, /backgroundOpacity = 0\.65/);
1450
+ assert.match(configDocsJa, /termName = "xterm-256color"/);
1451
+ assert.match(configDocsJa, /lineHeight = 1/);
1452
+ assert.match(configDocsJa, /terminal\.lineHeight/);
1453
+ assert.match(configDocsJa, /minimumContrastRatio = 1/);
1454
+ assert.match(configDocsJa, /ANSI\/RGB色/);
1455
+ assert.match(configDocsJa, /rescaleOverlappingGlyphs = false/);
1456
+ assert.match(configDocsJa, /半角カタカナ/);
1457
+ assert.match(configDocsJa, /shell = ""/);
1458
+ assert.match(configDocsJa, /pwsh\.exe/);
1459
+ assert.match(configDocsJa, /PowerShell 7 \(`pwsh\.exe`\) が利用可能な場合に既定 shell/);
1460
+ assert.match(configDocsJa, /PowerShell\\7\\pwsh\.exe/);
1461
+ assert.match(configDocsJa, /Windowsでは/);
1462
+ assert.match(configDocsJa, /\.config\/fpasoterm\/bin\/fpasoterm/);
1463
+ assert.match(configDocsJa, /全引数を変更せず転送/);
1464
+ assert.match(configDocsJa, /新しいGUIを起動する場合は既定でprocessを切り離/);
1465
+ assert.match(configDocsJa, /window-state\.json/);
1466
+ assert.match(configDocsJa, /同じ table を複数回定義できません/);
1467
+ assert.match(configDocsJa, /OSC 777/);
1468
+ assert.match(configDocsJa, /`printf` 例は POSIX shell/);
1469
+ assert.match(configDocsJa, /PowerShell や cmd\.exe ではそのまま使えません/);
1470
+ assert.match(configDocsJa, /\[Console\]::Write\("\$\(\[char\]27\)\]777;title=work;titlebarColor=#2e7d32/);
1471
+ assert.match(configDocsJa, /titlebarColor=#2e7d32/);
1472
+ assert.match(configDocsJa, /opacity=0\.65/);
1473
+ assert.match(configDocsJa, /examples\/config\/runtime-appearance\.toml/);
1474
+ assert.match(configDocsJa, /apply-runtime-appearance\.sh/);
1475
+ assert.match(configDocsJa, /apply-runtime-appearance\.ps1/);
1476
+ assert.match(configDocsJa, /apply-runtime-appearance\.bat/);
1477
+ assert.match(configDocsJa, /RUNTIME SAMPLE ACTIVE/);
1478
+ assert.match(configDocsJa, /apply-default-appearance\.sh/);
1479
+ assert.match(configDocsJa, /apply-default-appearance\.ps1/);
1480
+ assert.match(configDocsJa, /apply-default-appearance\.bat/);
1481
+ assert.match(configDocsJa, /\\a\\r\\n/);
1482
+ assert.match(configDocsJa, /現在の shell session は維持されます/);
1483
+ assert.match(configDocsJa, /TERM=xterm-256color/);
1484
+ assert.match(configDocsJa, /\[sync\]/);
1485
+ assert.match(configDocsJa, /provider = "folder"/);
1486
+ assert.match(configDocsJa, /sync\.ja\.md/);
1487
+ assert.match(configDocsJa, /\[logging\]/);
1488
+ assert.match(configDocsJa, /Ctrl\+Shift\+L/);
1489
+ assert.match(configDocsJa, /Ctrl\+Shift\+S/);
1490
+ assert.match(configDocsJa, /Ctrl\+Shift\+P/);
1491
+ assert.match(configDocsJa, /Ctrl\+Esc.*無効/);
1492
+ assert.match(configDocsJa, /Ctrl\+Alt\+Escape/);
1493
+ assert.match(configDocsJa, /設定ファイルの絶対path/);
1494
+ assert.match(configDocsJa, /--update-config/);
1495
+ assert.match(configDocsJa, /--prune-config/);
1496
+ assert.match(configDocsJa, /Delete All/);
1497
+ assert.match(configDocsJa, /一覧から表示対象を選択/);
1498
+ assert.match(configDocsJa, /選択した停止済み log/);
1499
+ assert.match(configDocsJa, /制御シーケンスを除去した readable terminal output/);
1500
+ assert.match(configDocsJa, /%USERPROFILE%/);
1501
+ assert.match(configDocsJa, /\$HOME/);
1502
+ assert.match(configDocsJa, /最も扱いやすい指定/);
1503
+ assert.doesNotMatch(configDocsJa, /\[web[C]onsole\]/);
1504
+ assert.match(configDocsJa, /log=start/);
1505
+
1506
+ const knownIssuesEn = read('docs/known-issues.en.md');
1507
+ assert.match(knownIssuesEn, /ChromeOS\/Baguette Window Position/);
1508
+ assert.match(knownIssuesEn, /restores window size only/);
1509
+ assert.match(knownIssuesEn, /future task/);
1510
+ assert.match(knownIssuesEn, /WEBKIT_DISABLE_DMABUF_RENDERER=1/);
1511
+
1512
+ const knownIssuesJa = read('docs/known-issues.ja.md');
1513
+ assert.match(knownIssuesJa, /ChromeOS\/Baguette のウィンドウ位置/);
1514
+ assert.match(knownIssuesJa, /window size のみ復元/);
1515
+ assert.match(knownIssuesJa, /今後の課題/);
1516
+ assert.match(knownIssuesJa, /WEBKIT_DISABLE_DMABUF_RENDERER=1/);
1517
+
1518
+ const specEn = read('docs/spec.en.md');
1519
+ assert.doesNotMatch(specEn, removedTemporaryHttpUiPattern);
1520
+ assert.match(specEn, /OSC 52 copy requests/);
1521
+ assert.match(specEn, /selected terminal text/);
1522
+ assert.match(specEn, /hamburger window menu contains `Log Start/);
1523
+ assert.match(specEn, /`Ctrl\+Shift\+L` opens that menu/);
1524
+ assert.match(specEn, /`Ctrl\+Shift\+M` opens the window menu/);
1525
+ assert.match(specEn, /`Help \(\^H\)` item or `Ctrl\+Shift\+H`/);
1526
+ assert.match(specEn, /`Kill \(\^K\)`, `Copy \(\^C\)`, and `Paste \(\^V\)`/);
1527
+ assert.match(specEn, /On Unix, `Kill \(\^K\)` or `Ctrl\+Shift\+K` sends `SIGKILL` to the foreground PTY process group/);
1528
+ assert.match(specEn, /On Windows, it force-terminates descendants of the terminal shell/);
1529
+ assert.match(specEn, /diagnostics and log panel textareas/);
1530
+ assert.match(specEn, /Terminal paste first reads the WebView clipboard API/);
1531
+ assert.match(specEn, /otherwise it pastes/);
1532
+ assert.match(specEn, /Pane-specific logging is delegated to multiplexers/);
1533
+
1534
+ const specJa = read('docs/spec.ja.md');
1535
+ assert.doesNotMatch(specJa, removedTemporaryHttpUiPattern);
1536
+ assert.match(specJa, /OSC 52 copy request/);
1537
+ assert.match(specJa, /選択した terminal text/);
1538
+ assert.match(specJa, /hamburger の window menu には `Log Start/);
1539
+ assert.match(specJa, /`Ctrl\+Shift\+L` は log 操作に focus/);
1540
+ assert.match(specJa, /`Ctrl\+Shift\+M` で window menu を開き/);
1541
+ assert.match(specJa, /`Help \(\^H\)` または `Ctrl\+Shift\+H`/);
1542
+ assert.match(specJa, /`Kill \(\^K\)`、`Copy \(\^C\)`、`Paste \(\^V\)`/);
1543
+ assert.match(specJa, /`Ctrl\+Shift\+K` が前景 PTY process group に `SIGKILL` を送ります/);
1544
+ assert.match(specJa, /Windowsではterminal shellの子孫processを深い順に強制終了/);
1545
+ assert.match(specJa, /diagnostics \/ log panel の textarea/);
1546
+ assert.match(specJa, /terminal paste は、user gesture中のWebView clipboard APIを先に読み/);
1547
+ assert.match(specJa, /selection がない場合は paste/);
1548
+ assert.match(specJa, /pane 単位の log は tmux/);
1549
+
1550
+ const prReviewEn = read('docs/pr-review.en.md');
1551
+ assert.match(prReviewEn, /Ordinary pull requests do not include release artifacts/);
1552
+ assert.match(prReviewEn, /gh pr checkout <number>/);
1553
+ assert.match(prReviewEn, /fpasoterm\.exe --help/);
1554
+ assert.match(prReviewEn, /Do not use tag release assets as substitutes for PR artifacts/);
1555
+ assert.match(prReviewEn, /pull_request/);
1556
+
1557
+ const prReviewJa = read('docs/pr-review.ja.md');
1558
+ assert.match(prReviewJa, /通常の pull request には/);
1559
+ assert.match(prReviewJa, /gh pr checkout <number>/);
1560
+ assert.match(prReviewJa, /fpasoterm\.exe --help/);
1561
+ assert.match(prReviewJa, /tag release asset は PR artifact の代わりには使いません/);
1562
+ assert.match(prReviewJa, /pull_request/);
1563
+
1564
+ const samplePlugin = read('examples/plugins/hello.ts');
1565
+ assert.match(samplePlugin, /fpasotermPluginApi/);
1566
+ assert.match(samplePlugin, /onReady/);
1567
+ assert.match(samplePlugin, /hello\.ts loaded/);
1568
+ assert.match(samplePlugin, /@fpasoterm-plugin version: 1\.0\.0/);
1569
+
1570
+ // Executes each public sample in isolation with the supported renderer API.
1571
+ // This catches syntax and lifecycle regressions before users copy a sample.
1572
+ function runSamplePlugin(relativePath) {
1573
+ const output = [];
1574
+ const logs = [];
1575
+ const readyCallbacks = [];
1576
+ const commands = new Map();
1577
+ const api = {
1578
+ version: '1.5.6-test',
1579
+ terminal: {
1580
+ options: {},
1581
+ write: (value) => output.push(value),
1582
+ writeln: (value) => output.push(value),
1583
+ focus: () => {},
1584
+ },
1585
+ fitAddon: { fit: () => {} },
1586
+ config: {
1587
+ window: { width: 900, height: 700 },
1588
+ terminal: { theme: { background: '#000000' } },
1589
+ plugins: { enabled: ['plugins/hello.ts'] },
1590
+ },
1591
+ log: (message) => logs.push(message),
1592
+ getOfficialPluginIndex: async () => [],
1593
+ onReady: (callback) => readyCallbacks.push(callback),
1594
+ registerCommand: (id, title, handler) => commands.set(id, { title, handler }),
1595
+ };
1596
+ vm.runInNewContext(read(relativePath), { window: { fpasotermPluginApi: api } }, { filename: relativePath });
1597
+ readyCallbacks.forEach((callback) => callback());
1598
+ return { api, commands, logs, output };
1599
+ }
1600
+
1601
+ const helloPluginRun = runSamplePlugin('examples/plugins/hello.ts');
1602
+ assert.deepEqual(helloPluginRun.output, ['', '[fpasoterm plugin] hello.ts loaded']);
1603
+ assert.deepEqual(helloPluginRun.logs, ['hello.ts loaded']);
1604
+ assert.equal(helloPluginRun.commands.get('hello.show').title, 'Show Hello Banner');
1605
+
1606
+ const themePluginRun = runSamplePlugin('examples/plugins/theme.ts');
1607
+ assert.equal(themePluginRun.api.terminal.options.theme.background, 'rgba(8, 42, 48, 0.86)');
1608
+ assert.match(themePluginRun.output.join('\n'), /theme\.ts applied the teal sample palette/);
1609
+ assert.equal(themePluginRun.commands.get('theme.apply-teal').title, 'Apply Teal Theme');
1610
+
1611
+ const welcomePluginRun = runSamplePlugin('examples/plugins/welcome-banner.ts');
1612
+ assert.match(welcomePluginRun.output.join('\n'), /Welcome banner plugin is active/);
1613
+ assert.equal(welcomePluginRun.commands.get('welcome-banner.show').title, 'Show Welcome Banner');
1614
+
1615
+ const statusPluginRun = runSamplePlugin('examples/plugins/status-banner.ts');
1616
+ assert.equal(statusPluginRun.commands.get('status-banner.show').title, 'Show Plugin Status');
1617
+ statusPluginRun.commands.get('status-banner.show').handler();
1618
+ assert.match(statusPluginRun.output.join('\n'), /Status: 900x700, enabled plugins: 1/);
1619
+
1620
+ const pluginDocsEn = read('docs/plugins.en.md');
1621
+ assert.match(pluginDocsEn, /\[plugins\]/);
1622
+ assert.match(pluginDocsEn, /User\/plugins/);
1623
+ assert.match(pluginDocsEn, /fpasoterm-plugin\.d\.ts/);
1624
+ assert.match(pluginDocsEn, /local files that you trust/);
1625
+ assert.match(pluginDocsEn, /--plugin-list/);
1626
+ assert.match(pluginDocsEn, /--plugin-uninstall/);
1627
+ assert.match(pluginDocsEn, /registerCommand/);
1628
+ assert.match(pluginDocsEn, /@fpasoterm-plugin version/);
1629
+
1630
+ const pluginDocsJa = read('docs/plugins.ja.md');
1631
+ assert.match(pluginDocsJa, /\[plugins\]/);
1632
+ assert.match(pluginDocsJa, /User\/plugins/);
1633
+ assert.match(pluginDocsJa, /fpasoterm-plugin\.d\.ts/);
1634
+ assert.match(pluginDocsJa, /信頼できるローカル file/);
1635
+ assert.match(pluginDocsJa, /--plugin-list/);
1636
+ assert.match(pluginDocsJa, /--plugin-uninstall/);
1637
+ assert.match(pluginDocsJa, /registerCommand/);
1638
+ assert.match(pluginDocsJa, /@fpasoterm-plugin version/);
1639
+
1640
+ const welcomeBannerPlugin = read('examples/plugins/welcome-banner.ts');
1641
+ assert.match(welcomeBannerPlugin, /fpasotermPluginApi/);
1642
+ assert.match(welcomeBannerPlugin, /Welcome banner plugin is active/);
1643
+ assert.match(welcomeBannerPlugin, /onReady/);
1644
+ assert.match(welcomeBannerPlugin, /@fpasoterm-plugin version: 1\.0\.0/);
1645
+
1646
+ const statusBannerPlugin = read('examples/plugins/status-banner.ts');
1647
+ assert.match(statusBannerPlugin, /fpasotermPluginApi/);
1648
+ assert.match(statusBannerPlugin, /enabled plugins/);
1649
+ assert.match(statusBannerPlugin, /registerCommand/);
1650
+ assert.match(statusBannerPlugin, /@fpasoterm-plugin version: 1\.0\.0/);
1651
+
1652
+ const sampleConfig = read('examples/config/with-plugins.toml');
1653
+ assert.match(sampleConfig, /\[plugins\]/);
1654
+ assert.match(sampleConfig, /plugins\/hello\.ts/);
1655
+ assert.match(sampleConfig, /shell = ""/);
1656
+ assert.match(sampleConfig, /titlebarColor = "#1565c0"/);
1657
+ assert.match(sampleConfig, /backgroundOpacity = 0\.65/);
1658
+ assert.match(sampleConfig, /lineHeight = 1/);
1659
+ assert.match(sampleConfig, /termName = "xterm-256color"/);
1660
+
1661
+
1662
+ const syncConfig = read('examples/config/sync-folder.toml');
1663
+ assert.match(syncConfig, /\[sync\]/);
1664
+ assert.match(syncConfig, /enabled = true/);
1665
+ assert.match(syncConfig, /Google Drive/);
1666
+ assert.match(syncConfig, /maxBytes = 1048576/);
1667
+ assert.match(syncConfig, /\[logging\]/);
1668
+ assert.match(syncConfig, /fpasoterm-sync\/logs/);
1669
+
1670
+ const syncDocsEn = read('docs/sync.en.md');
1671
+ assert.match(syncDocsEn, /Google Drive API/);
1672
+ assert.match(syncDocsEn, /OAuth/);
1673
+ assert.match(syncDocsEn, /fpasoterm --setup-sync/);
1674
+ assert.match(syncDocsEn, /node \.\\bin\\fpasoterm --setup-sync/);
1675
+ assert.match(syncDocsEn, /node bin\\fpasoterm --setup-sync/);
1676
+ assert.match(syncDocsEn, /--sync-status/);
1677
+ assert.match(syncDocsEn, /--sync-clean/);
1678
+ assert.match(syncDocsEn, /--sync-diagnostics/);
1679
+ assert.match(syncDocsEn, /fpasoterm --broadcast "git status"/);
1680
+ assert.match(syncDocsEn, /--broadcast-target/);
1681
+ assert.match(syncDocsEn, /--broadcast-sync/);
1682
+ assert.match(syncDocsEn, /commandSecret/);
1683
+ assert.match(syncDocsEn, /HMAC-SHA-256/);
1684
+ assert.match(syncDocsEn, /candidate number/);
1685
+ assert.match(syncDocsEn, /Sync Channel/);
1686
+ assert.match(syncDocsEn, /same `path` and the same `channel`/);
1687
+ assert.match(syncDocsEn, /chromeos-test/);
1688
+ assert.match(syncDocsEn, /Share with Linux/);
1689
+ assert.match(syncDocsEn, /\/mnt\/chromeos\/shared\/GoogleDrive\/MyDrive\/shared\/fpasoterm-sync/);
1690
+ assert.match(syncDocsEn, /\/mnt\/chromeos\/shared\/GoogleDrive\/MyDrive\/temp\/fpasoterm-sync/);
1691
+ assert.match(syncDocsEn, /renaming `test` to `temp`/);
1692
+ assert.match(syncDocsEn, /Share with Linux` again/);
1693
+ assert.match(syncDocsEn, /\/mnt\/shared\/GoogleDrive\/MyDrive\/shared\/fpasoterm-sync/);
1694
+ assert.match(syncDocsEn, /\/mnt\/chromeos\/GoogleDrive\/MyDrive\/shared\/fpasoterm-sync/);
1695
+ assert.match(syncDocsEn, /GoogleDrive-<account>/);
1696
+ assert.match(syncDocsEn, /G:\\My Drive\\fpasoterm-sync/);
1697
+ assert.match(syncDocsEn, /G:\\マイドライブ\\fpasoterm-sync/);
1698
+ assert.match(syncDocsEn, /Test-Path 'G:\\My Drive'/);
1699
+ assert.match(syncDocsEn, /rclone mount/);
1700
+ assert.doesNotMatch(syncDocsEn, /clipboard\.json/);
1701
+ assert.match(syncDocsEn, /diagnostics\.json/);
1702
+ assert.doesNotMatch(syncDocsEn, /Copy Selection/);
1703
+ assert.doesNotMatch(syncDocsEn, /Pull to Clipboard/);
1704
+ assert.doesNotMatch(syncDocsEn, /Write Diagnostics/);
1705
+ assert.match(syncDocsEn, /debounced diagnostics snapshot/);
1706
+ assert.match(syncDocsEn, /sync\.enabled = false/);
1707
+ assert.match(syncDocsEn, /not the terminal output log/);
1708
+ assert.doesNotMatch(syncDocsEn, /titlebar shows a compact `Sync:` status/);
1709
+ assert.match(syncDocsEn, /Ctrl\+Shift\+L/);
1710
+ assert.match(syncDocsEn, /Ctrl\+Shift\+S/);
1711
+ assert.match(syncDocsEn, /Ctrl\+Shift\+P/);
1712
+ assert.match(syncDocsEn, /Delete All/);
1713
+ assert.match(syncDocsEn, /opens a selector/);
1714
+ assert.match(syncDocsEn, /delete the selected stopped log/);
1715
+ assert.match(syncDocsEn, /press `Ctrl\+Shift\+C`/);
1716
+ assert.match(syncDocsEn, /same clipboard path/);
1717
+ assert.match(syncDocsEn, /tmux capture-pane/);
1718
+ assert.match(syncDocsEn, /Store terminal output logs in the sync folder/);
1719
+ assert.match(syncDocsEn, /Terminal log directory/);
1720
+ assert.doesNotMatch(syncDocsEn, /Synced terminal log directory/);
1721
+ assert.match(syncDocsEn, /G:\\マイドライブ\\fpasoterm-sync\\logs/);
1722
+ assert.match(syncDocsEn, /That is still a local Windows path/);
1723
+ assert.match(syncDocsEn, /%USERPROFILE%/);
1724
+ assert.match(syncDocsEn, /explicit per-OS paths/);
1725
+ assert.match(syncDocsEn, /log=start/);
1726
+
1727
+ const syncDocsJa = read('docs/sync.ja.md');
1728
+ assert.match(syncDocsJa, /Google Drive API/);
1729
+ assert.match(syncDocsJa, /OAuth/);
1730
+ assert.match(syncDocsJa, /fpasoterm --setup-sync/);
1731
+ assert.match(syncDocsJa, /node \.\\bin\\fpasoterm --setup-sync/);
1732
+ assert.match(syncDocsJa, /node bin\\fpasoterm --setup-sync/);
1733
+ assert.match(syncDocsJa, /--sync-status/);
1734
+ assert.match(syncDocsJa, /--sync-clean/);
1735
+ assert.match(syncDocsJa, /--sync-diagnostics/);
1736
+ assert.match(syncDocsJa, /fpasoterm --broadcast "git status"/);
1737
+ assert.match(syncDocsJa, /--broadcast-target/);
1738
+ assert.match(syncDocsJa, /--broadcast-sync/);
1739
+ assert.match(syncDocsJa, /commandSecret/);
1740
+ assert.match(syncDocsJa, /HMAC-SHA-256/);
1741
+ assert.match(syncDocsJa, /候補番号/);
1742
+ assert.match(syncDocsJa, /Sync channel/);
1743
+ assert.match(syncDocsJa, /同じ `path` と同じ `channel`/);
1744
+ assert.match(syncDocsJa, /chromeos-test/);
1745
+ assert.match(syncDocsJa, /Share with Linux/);
1746
+ assert.match(syncDocsJa, /\/mnt\/chromeos\/shared\/GoogleDrive\/MyDrive\/shared\/fpasoterm-sync/);
1747
+ assert.match(syncDocsJa, /\/mnt\/chromeos\/shared\/GoogleDrive\/MyDrive\/temp\/fpasoterm-sync/);
1748
+ assert.match(syncDocsJa, /`test` から `temp` へ rename/);
1749
+ assert.match(syncDocsJa, /`Linux と共有` を実行/);
1750
+ assert.match(syncDocsJa, /\/mnt\/shared\/GoogleDrive\/MyDrive\/shared\/fpasoterm-sync/);
1751
+ assert.match(syncDocsJa, /\/mnt\/chromeos\/GoogleDrive\/MyDrive\/shared\/fpasoterm-sync/);
1752
+ assert.match(syncDocsJa, /GoogleDrive-<account>/);
1753
+ assert.match(syncDocsJa, /G:\\My Drive\\fpasoterm-sync/);
1754
+ assert.match(syncDocsJa, /G:\\マイドライブ\\fpasoterm-sync/);
1755
+ assert.match(syncDocsJa, /Test-Path 'G:\\My Drive'/);
1756
+ assert.match(syncDocsJa, /rclone mount/);
1757
+ assert.doesNotMatch(syncDocsJa, /Copy Selection/);
1758
+ assert.doesNotMatch(syncDocsJa, /Pull to Clipboard/);
1759
+ assert.doesNotMatch(syncDocsJa, /Write Diagnostics/);
1760
+ assert.match(syncDocsJa, /diagnostics snapshot/);
1761
+ assert.match(syncDocsJa, /sync\.enabled = false/);
1762
+ assert.match(syncDocsJa, /terminal output log ではありません/);
1763
+ assert.doesNotMatch(syncDocsJa, /titlebar に `Sync:` status が表示されます/);
1764
+ assert.doesNotMatch(syncDocsJa, /clipboard\.json/);
1765
+ assert.match(syncDocsJa, /diagnostics\.json/);
1766
+ assert.match(syncDocsJa, /Ctrl\+Shift\+L/);
1767
+ assert.match(syncDocsJa, /Ctrl\+Shift\+S/);
1768
+ assert.match(syncDocsJa, /Ctrl\+Shift\+P/);
1769
+ assert.match(syncDocsJa, /Delete All/);
1770
+ assert.match(syncDocsJa, /一覧から表示対象を選択/);
1771
+ assert.match(syncDocsJa, /選択した停止済み log/);
1772
+ assert.match(syncDocsJa, /`Ctrl\+Shift\+C` を押す/);
1773
+ assert.match(syncDocsJa, /同じ clipboard 経路/);
1774
+ assert.match(syncDocsJa, /tmux capture-pane/);
1775
+ assert.match(syncDocsJa, /Store terminal output logs in the sync folder/);
1776
+ assert.match(syncDocsJa, /Terminal log directory/);
1777
+ assert.doesNotMatch(syncDocsJa, /Synced terminal log directory/);
1778
+ assert.match(syncDocsJa, /G:\\マイドライブ\\fpasoterm-sync\\logs/);
1779
+ assert.match(syncDocsJa, /Windows 上のローカル path/);
1780
+ assert.match(syncDocsJa, /%USERPROFILE%/);
1781
+ assert.match(syncDocsJa, /各 OS ごとの実 path/);
1782
+ assert.match(syncDocsJa, /log=start/);
1783
+
1784
+ const securityDocsEn = read('docs/security.en.md');
1785
+ const securityDocsJa = read('docs/security.ja.md');
1786
+ assert.match(securityDocsEn, /HMAC-SHA-256/);
1787
+ assert.match(securityDocsEn, /Local plugins/);
1788
+ assert.match(securityDocsEn, /cargo audit/);
1789
+ assert.match(securityDocsJa, /HMAC-SHA-256/);
1790
+ assert.match(securityDocsJa, /Local plugin/);
1791
+ assert.match(securityDocsJa, /cargo audit/);
1792
+
1793
+ const runtimeConfig = read('examples/config/runtime-appearance.toml');
1794
+ assert.match(runtimeConfig, /title = "RUNTIME SAMPLE ACTIVE"/);
1795
+ assert.match(runtimeConfig, /POSIX shell/);
1796
+ assert.match(runtimeConfig, /apply-runtime-appearance\.ps1 or \.bat/);
1797
+ assert.match(runtimeConfig, /titlebarColor = "#d81b60"/);
1798
+ assert.match(runtimeConfig, /# fontSize = 18/);
1799
+ assert.match(runtimeConfig, /# width = 1180/);
1800
+ assert.match(runtimeConfig, /backgroundOpacity = 0\.95/);
1801
+ assert.match(runtimeConfig, /background = "#32004f"/);
1802
+ assert.match(runtimeConfig, /foreground = "#fff176"/);
1803
+ assert.match(runtimeConfig, /cursor = "#00e5ff"/);
1804
+
1805
+ const runtimeApplyScript = read('examples/apply-runtime-appearance.sh');
1806
+ assert.match(runtimeApplyScript, /runtime-appearance\.toml/);
1807
+ assert.match(runtimeApplyScript, /printf '\\033\]777;config=%s\\a\\r\\n'/);
1808
+
1809
+ const runtimeApplyPowerShell = read('examples/apply-runtime-appearance.ps1');
1810
+ assert.match(runtimeApplyPowerShell, /runtime-appearance\.toml/);
1811
+ assert.match(runtimeApplyPowerShell, /\[Console\]::Write/);
1812
+ assert.match(runtimeApplyPowerShell, /`e\]777;config=\$ConfigPath`a`r`n/);
1813
+
1814
+ const runtimeApplyBatch = read('examples/apply-runtime-appearance.bat');
1815
+ assert.match(runtimeApplyBatch, /runtime-appearance\.toml/);
1816
+ assert.match(runtimeApplyBatch, /powershell\.exe -NoProfile -ExecutionPolicy Bypass/);
1817
+ assert.match(runtimeApplyBatch, /\[char\]27/);
1818
+ assert.match(runtimeApplyBatch, /\[char\]7/);
1819
+
1820
+ const defaultConfig = read('examples/config/default-appearance.toml');
1821
+ assert.match(defaultConfig, /title = "fpasoterm"/);
1822
+ assert.match(defaultConfig, /POSIX shell/);
1823
+ assert.match(defaultConfig, /apply-default-appearance\.ps1 or \.bat/);
1824
+ assert.match(defaultConfig, /titlebarColor = "#1565c0"/);
1825
+ assert.match(defaultConfig, /backgroundOpacity = 0\.65/);
1826
+ assert.match(defaultConfig, /lineHeight = 1/);
1827
+ assert.match(defaultConfig, /minimumContrastRatio = 1/);
1828
+ assert.match(defaultConfig, /rescaleOverlappingGlyphs = false/);
1829
+ assert.match(defaultConfig, /background = "rgba\(16, 19, 23, 0\.65\)"/);
1830
+ assert.match(defaultConfig, /foreground = "#e8edf2"/);
1831
+ assert.match(defaultConfig, /cursor = "#f5d76e"/);
1832
+ assert.match(defaultConfig, /\[keybindings\]/);
1833
+ assert.match(defaultConfig, /prefix = "Mod\+Shift"/);
1834
+ assert.match(defaultConfig, /newWindow = "N"/);
1835
+
1836
+ const minimalConfig = read('examples/config/minimal.toml');
1837
+ assert.match(minimalConfig, /\[keybindings\]/);
1838
+ assert.match(minimalConfig, /prefix = "Mod\+Shift"/);
1839
+ assert.match(minimalConfig, /tile = "T"/);
1840
+
1841
+ const defaultApplyScript = read('examples/apply-default-appearance.sh');
1842
+ assert.match(defaultApplyScript, /default-appearance\.toml/);
1843
+ assert.match(defaultApplyScript, /printf '\\033\]777;config=%s\\a\\r\\n'/);
1844
+
1845
+ const defaultApplyPowerShell = read('examples/apply-default-appearance.ps1');
1846
+ assert.match(defaultApplyPowerShell, /default-appearance\.toml/);
1847
+ assert.match(defaultApplyPowerShell, /\[Console\]::Write/);
1848
+ assert.match(defaultApplyPowerShell, /`e\]777;config=\$ConfigPath`a`r`n/);
1849
+
1850
+ const defaultApplyBatch = read('examples/apply-default-appearance.bat');
1851
+ assert.match(defaultApplyBatch, /default-appearance\.toml/);
1852
+ assert.match(defaultApplyBatch, /powershell\.exe -NoProfile -ExecutionPolicy Bypass/);
1853
+ assert.match(defaultApplyBatch, /\[char\]27/);
1854
+ assert.match(defaultApplyBatch, /\[char\]7/);
1855
+
1856
+ const pluginTypes = read('docs/fpasoterm-plugin.d.ts');
1857
+ assert.match(pluginTypes, /fpasotermPluginApi/);
1858
+ assert.doesNotMatch(pluginTypes, /duplicateWindowMs/);
1859
+ assert.match(pluginTypes, /version: string/);
1860
+ assert.match(pluginTypes, /onReady:/);
1861
+ assert.match(pluginTypes, /registerCommand:/);
1862
+ assert.match(pluginTypes, /getOfficialPluginIndex:/);
1863
+
1864
+ const renderer = read('src/renderer/renderer.js');
1865
+ assert.match(renderer, /function terminalBroadcastFocusItems\(\)/);
1866
+ assert.match(renderer, /function focusTerminalBroadcastItem\(delta\)/);
1867
+ assert.match(renderer, /function terminalBroadcastFocusLabel\(element\)/);
1868
+ assert.match(renderer, /function updateTerminalBroadcastFocusStatus\(element\)/);
1869
+ assert.match(renderer, /function terminalBroadcastSubmitKeyEvent\(\)/);
1870
+ assert.match(renderer, /function shouldAppendTerminalBroadcastEnter\(text\)/);
1871
+ assert.match(renderer, /terminal:broadcast-key/);
1872
+ assert.match(renderer, /key: 'Enter', code: 'Enter', shiftKey: false/);
1873
+ assert.match(renderer, /function handleTerminalBroadcastKeyboard\(event\)/);
1874
+ assert.match(renderer, /event\.key === 'Enter' && event\.shiftKey && !event\.isComposing/);
1875
+ assert.match(renderer, /decodeTerminalBroadcastControls\(normalizePasteText\(rawText\)\.replace\(\/\\r\+\$\/, ''\)\)/);
1876
+ assert.match(renderer, /registerPluginReadyCallback/);
1877
+ assert.match(renderer, /notifyPluginsReady/);
1878
+ assert.match(renderer, /schedulePluginsReadyAfterTerminalOutput/);
1879
+ assert.match(renderer, /pluginReadyGeneration/);
1880
+ assert.match(renderer, /registerPluginCommand/);
1881
+ assert.match(renderer, /getPluginCatalog: \(\) => invoke\('plugin_catalog'\)/);
1882
+ assert.match(renderer, /getOfficialPluginIndex: \(\) => window\.fpasoterm\.getPluginCatalog\(\)/);
1883
+ assert.match(renderer, /plugin command registered id=/);
1884
+ assert.match(renderer, /function updatePluginMenuVisibility\(\)/);
1885
+ assert.match(renderer, /pluginMenuSection\.hidden = !hasCommands/);
1886
+ assert.match(renderer, /plugin loaded \$\{plugin\.name\} commands=\$\{registeredCommandCount\}/);
1887
+ assert.match(renderer, /installTauriApiAdapter/);
1888
+ assert.match(renderer, /__TAURI__/);
1889
+ assert.match(renderer, /startWindowDrag/);
1890
+ assert.match(renderer, /minimizeWindow/);
1891
+ assert.match(renderer, /toggleMaximizeWindow/);
1892
+ assert.match(renderer, /newWindow/);
1893
+ assert.match(renderer, /arrangeWindows/);
1894
+ assert.match(renderer, /closeAllWindows/);
1895
+ assert.match(renderer, /confirmCloseAllWindows/);
1896
+ assert.match(renderer, /confirmCloseAllWindows/);
1897
+ assert.match(renderer, /resolveCloseAllWindows/);
1898
+ assert.match(renderer, /Close all running fpasoterm windows/);
1899
+ assert.match(renderer, /isCloseAllShortcut/);
1900
+ assert.match(renderer, /isNewWindowShortcut/);
1901
+ assert.match(renderer, /matchesKeybinding\(event, 'newWindow'\)/);
1902
+ assert.match(renderer, /getAvailableScreenBounds/);
1903
+ assert.match(renderer, /matchesKeybinding\(event, 'tile'\)/);
1904
+ assert.match(renderer, /setWindowMenuOpen/);
1905
+ assert.match(renderer, /showKeyboardShortcutsHelp/);
1906
+ assert.match(renderer, /Config: \$\{activeConfigPath/);
1907
+ assert.match(renderer, /activeConfigPath = String\(runtimeConfig\.configPath/);
1908
+ assert.match(renderer, /getAppVersion: \(\) => invoke\('app_version'\)/);
1909
+ assert.match(renderer, /`fpasoterm \$\{version\}`/);
1910
+ assert.match(renderer, /function matchesKeybinding/);
1911
+ assert.match(renderer, /function applyKeybindingLabels/);
1912
+ assert.match(renderer, /function isValidKeybindingPrefix/);
1913
+ assert.match(renderer, /function isValidFullKeybinding/);
1914
+ assert.match(renderer, /Invalid prefix/);
1915
+ assert.match(renderer, /Ctrl\+Alt\+KeyN/);
1916
+ assert.match(renderer, /keybindingLabel\('menu'\)/);
1917
+ assert.match(renderer, /terminalLogStatusElement\.hidden = !status\.active/);
1918
+ assert.match(renderer, /logMenuToggleButton/);
1919
+ assert.match(renderer, /startWindowResize/);
1920
+ assert.match(renderer, /startWindowResizeDrag/);
1921
+ assert.match(renderer, /startResizeDragging/);
1922
+ assert.match(renderer, /saveWindowBounds/);
1923
+ assert.match(renderer, /getWindowBounds/);
1924
+ assert.match(renderer, /setWindowBounds/);
1925
+ assert.match(renderer, /scheduleWindowStateSave/);
1926
+ assert.match(renderer, /scheduleFitAndResize/);
1927
+ assert.match(renderer, /scheduleDeferredFitAndResize/);
1928
+ assert.match(renderer, /afterNextPaint/);
1929
+ assert.match(renderer, /removeXtermVisualOverlays/);
1930
+ assert.match(renderer, /installXtermOverlayPruner/);
1931
+ assert.match(renderer, /logXtermCanvasDiagnostics/);
1932
+ assert.match(renderer, /logXtermTextDiagnostics/);
1933
+ assert.match(renderer, /repeated-w/);
1934
+ assert.match(renderer, /MutationObserver/);
1935
+ assert.match(renderer, /screenReaderMode: false/);
1936
+ assert.match(renderer, /startManualWindowResize/);
1937
+ assert.match(renderer, /manual window resize failed/);
1938
+ assert.match(renderer, /toTauriResizeDirection/);
1939
+ assert.match(renderer, /renderer terminal data bytes/);
1940
+ assert.match(renderer, /renderer terminal write parsed bytes/);
1941
+ assert.match(renderer, /mirrorTerminalData/);
1942
+ assert.match(renderer, /terminal write failed/);
1943
+ assert.match(renderer, /sendTerminalInput/);
1944
+ assert.match(renderer, /lineHeight/);
1945
+ assert.match(renderer, /minimumContrastRatio/);
1946
+ assert.match(renderer, /terminalLogStatusElement\.hidden = !status\.active/);
1947
+ assert.doesNotMatch(renderer, /Logging \(\^L\)/);
1948
+ assert.match(renderer, /normalizePasteText/);
1949
+ assert.match(renderer, /pasteClipboardToTerminal/);
1950
+ assert.match(renderer, /writeClipboardText/);
1951
+ assert.match(renderer, /writeBrowserClipboardText/);
1952
+ assert.match(renderer, /copyTerminalSelection/);
1953
+ assert.match(renderer, /terminalKillButton/);
1954
+ assert.match(renderer, /killTerminal: \(\) => invoke\('terminal_kill'\)/);
1955
+ assert.match(renderer, /keybindingLabel\('kill'\)/);
1956
+ assert.match(renderer, /Kill the running terminal command and keep its shell open/);
1957
+ assert.match(rustMain, /process_group_leader/);
1958
+ assert.match(rustMain, /libc::SIGKILL/);
1959
+ assert.match(rustMain, /kill_windows_shell_descendants/);
1960
+ assert.match(renderer, /terminalCopyButton/);
1961
+ assert.match(renderer, /terminalPasteButton/);
1962
+ assert.match(renderer, /selectedTerminalText/);
1963
+ assert.match(renderer, /selectedDiagnosticsClipboardText/);
1964
+ assert.match(renderer, /selectedClipboardText/);
1965
+ assert.match(renderer, /copyTerminalSelection\(\)\.catch/);
1966
+ assert.doesNotMatch(renderer, /focusLogMenuItem/);
1967
+ assert.match(renderer, /focusWindowMenuItem/);
1968
+ assert.match(renderer, /function handleWindowMenuKeyboard\(event\)/);
1969
+ assert.match(renderer, /handleWindowMenuKeyboard\(event\)/);
1970
+ assert.match(renderer, /focusTerminalInput\(\);/);
1971
+ assert.match(renderer, /event\.key === 'ArrowRight' \|\| event\.key === 'ArrowLeft'/);
1972
+ assert.match(renderer, /diagnosticsPanelFocusItems/);
1973
+ assert.match(renderer, /focusDiagnosticsPanelItem/);
1974
+ assert.match(renderer, /focusTerminalLogPanel/);
1975
+ assert.match(renderer, /searchTerminalLogText/);
1976
+ assert.match(renderer, /scrollDiagnosticsSelectionIntoView/);
1977
+ assert.match(renderer, /terminalLogSearchState/);
1978
+ assert.match(renderer, /sameSearch/);
1979
+ assert.match(renderer, /matches/);
1980
+ assert.match(renderer, /cursor/);
1981
+ assert.match(renderer, /searchTerminalLogText\(1\)/);
1982
+ assert.match(renderer, /searchTerminalLogText\(-1\)/);
1983
+ assert.match(renderer, /terminalLogSearchStatusElement\.textContent = `\$\{terminalLogSearchState\.cursor \+ 1\}\/\$\{terminalLogSearchState\.matches\.length\}`/);
1984
+ assert.match(renderer, /restoreLogPanelFocus/);
1985
+ assert.match(renderer, /terminalLogSelectElement\?\.options\?\.length > 0/);
1986
+ assert.match(renderer, /options = \{\}/);
1987
+ assert.match(renderer, /options\.repeat/);
1988
+ assert.match(renderer, /afterNextPaint\(\)\.then/);
1989
+ assert.match(renderer, /diagnosticsPanel\.focus\(\{ preventScroll: true \}\)/);
1990
+ assert.match(renderer, /\[0, 50, 120, 240\]/);
1991
+ assert.match(renderer, /confirmTerminalLogAction/);
1992
+ assert.match(renderer, /resolveTerminalLogConfirm/);
1993
+ assert.match(renderer, /terminalLogConfirmResolver/);
1994
+ assert.match(renderer, /closeDiagnosticsButton/);
1995
+ assert.match(renderer, /toggleTerminalOutputLog/);
1996
+ assert.match(renderer, /showTerminalOutputLogFromMenu/);
1997
+ assert.match(renderer, /focusTarget\.focus\(\)/);
1998
+ assert.match(renderer, /event\.key === 'ArrowDown'/);
1999
+ assert.match(renderer, /event\.key === 'ArrowUp'/);
2000
+ assert.match(renderer, /event\.key === 'Escape'/);
2001
+ assert.match(renderer, /event\.key === 'Tab'/);
2002
+ assert.match(renderer, /event\.shiftKey \? -1 : 1/);
2003
+ assert.match(renderer, /event\.key !== 'Enter'/);
2004
+ assert.match(renderer, /key\.toLowerCase\(\) === 'j'/);
2005
+ assert.match(renderer, /key\.toLowerCase\(\) === 'k'/);
2006
+ assert.match(renderer, /matchesKeybinding\(event, 'logMenu'\)/);
2007
+ assert.match(renderer, /matchesKeybinding\(event, 'logToggle'\)/);
2008
+ assert.match(renderer, /matchesKeybinding\(event, 'logShow'\)/);
2009
+ assert.doesNotMatch(renderer, /document\.execCommand\('copy'\)/);
2010
+ assert.match(renderer, /event\.clipboardData\?\.setData\('text\/plain', text\)/);
2011
+ assert.match(renderer, /event\.clipboardData\?\.setData\('text', text\)/);
2012
+ assert.match(renderer, /navigator\.clipboard\.write\(\[item\]\)/);
2013
+ assert.match(renderer, /navigator\.clipboard\.writeText\(text\)/);
2014
+ assert.match(renderer, /term\.getSelection\(\)/);
2015
+ assert.match(renderer, /term\.hasSelection/);
2016
+ assert.match(renderer, /selection copied/);
2017
+ assert.match(renderer, /selection copied via copy event/);
2018
+ assert.match(renderer, /browser clipboard write failed/);
2019
+ assert.match(renderer, /backend clipboard write failed/);
2020
+ assert.doesNotMatch(renderer, /selectedDiagnosticsText/);
2021
+ assert.match(renderer, /selectionStart/);
2022
+ assert.match(renderer, /selectionEnd/);
2023
+ assert.match(renderer, /installTerminalPasteHandlers/);
2024
+ assert.match(renderer, /readClipboard/);
2025
+ assert.match(renderer, /writeClipboard/);
2026
+ assert.match(renderer, /decodeOsc52Text/);
2027
+ assert.match(renderer, /applyOsc52Clipboard/);
2028
+ assert.match(renderer, /OSC 52 clipboard wrote/);
2029
+ assert.match(renderer, /contextmenu/);
2030
+ assert.match(renderer, /terminal context paste failed/);
2031
+ assert.match(renderer, /terminal menu copy failed/);
2032
+ assert.match(renderer, /terminal menu paste failed/);
2033
+ assert.doesNotMatch(renderer, /copyDiagnosticsButton/);
2034
+ assert.doesNotMatch(renderer, /diagnostics copy failed/);
2035
+ assert.match(renderer, /matchesKeybinding\(event, 'paste'\)/);
2036
+ assert.match(renderer, /matchesKeybinding\(event, 'copy'\)/);
2037
+ assert.doesNotMatch(renderer, /setPointerCapture/);
2038
+ assert.doesNotMatch(renderer, /syncStatusElement/);
2039
+ assert.match(renderer, /scheduleSyncDiagnosticsWrite/);
2040
+ assert.match(renderer, /writeDiagnosticsSnapshot/);
2041
+ assert.match(renderer, /showSyncStatus/);
2042
+ assert.match(renderer, /cleanSyncFolder/);
2043
+ assert.doesNotMatch(renderer, /Sync: Ready/);
2044
+ assert.doesNotMatch(renderer, /Sync: Writing/);
2045
+ assert.doesNotMatch(renderer, /Sync: Updated/);
2046
+ assert.doesNotMatch(renderer, /Sync: Empty/);
2047
+ assert.doesNotMatch(renderer, /Sync: Error/);
2048
+ assert.doesNotMatch(renderer, /Sync: Synced/);
2049
+ assert.match(renderer, /syncMenuToggleButton/);
2050
+ assert.doesNotMatch(renderer, /setSyncMenuOpen/);
2051
+ assert.doesNotMatch(renderer, /closeSyncMenu/);
2052
+ assert.doesNotMatch(renderer, /pulled to OS clipboard/);
2053
+ assert.match(renderer, /renderer terminal \$\{source\} bytes=\$\{data\.length\} data=\$\{JSON\.stringify\(data\)\}/);
2054
+ assert.match(renderer, /function needsImePreeditOverlay\(\)/);
2055
+ assert.match(renderer, /function needsImePreeditOverlay\(\)/);
2056
+ assert.match(renderer, /return !\/Mac\/i\.test/);
2057
+ assert.match(renderer, /installCompositionObserver/);
2058
+ assert.match(renderer, /compositionupdate/);
2059
+ assert.match(renderer, /beforeinput/);
2060
+ assert.match(renderer, /insertCompositionText/);
2061
+ assert.match(renderer, /Observes composition events for visual feedback only/);
2062
+ assert.match(renderer, /Do not suppress,\s*\/\/ replay, replace, or directly commit them/);
2063
+ assert.match(renderer, /function traceImeEvent\(name, event\)/);
2064
+ assert.match(renderer, /function extendImeTraceWindow\(\)/);
2065
+ assert.match(renderer, /imeTraceUntil = Date\.now\(\) \+ 800/);
2066
+ assert.match(renderer, /renderer ime \$\{name\} data=/);
2067
+ assert.doesNotMatch(renderer, /clearStaleImeTextareaAfterInput/);
2068
+ assert.doesNotMatch(renderer, /renderer ime cleared stale helper value=/);
2069
+ assert.doesNotMatch(renderer, /scheduleCommittedImeTextareaClear/);
2070
+ assert.match(renderer, /function clearStaleImeTextareaAtCompositionStart\(textarea\)/);
2071
+ assert.match(renderer, /renderer ime cleared stale helper at compositionstart value=/);
2072
+ assert.match(renderer, /function installImeVisualFallback\(\)/);
2073
+ assert.match(renderer, /document-compositionstart/);
2074
+ assert.match(renderer, /document-compositionupdate/);
2075
+ assert.match(renderer, /document-keydown/);
2076
+ assert.doesNotMatch(renderer, /routesImeCompositionCommit/);
2077
+ assert.doesNotMatch(renderer, /writeExactImeComposition/);
2078
+ assert.doesNotMatch(renderer, /pendingExactImeReplay/);
2079
+ assert.match(renderer, /function showImePreedit\(text, showComposingFallback = false\)/);
2080
+ assert.match(renderer, /function updateImeStatus\(text\)/);
2081
+ assert.match(renderer, /showComposingFallback/);
2082
+ assert.match(renderer, /'composing\.\.\.'/);
2083
+ assert.match(renderer, /ime-preedit-overlay/);
2084
+ assert.match(renderer, /function clearImePreedit\(delay = 0\)/);
2085
+ assert.match(renderer, /document\.body\.classList\.remove\('ime-preedit-overlay'\)/);
2086
+ assert.match(renderer, /function isMacPlatform\(\)/);
2087
+ assert.match(renderer, /macOS pasteboard/);
2088
+ assert.match(renderer, /setTerminalEncoding: \(encoding\)/);
2089
+ assert.match(renderer, /setTerminalEncodingControlsVisible/);
2090
+ assert.doesNotMatch(renderer, /correctCompositionData/);
2091
+ assert.doesNotMatch(renderer, /removeImmediateCompositionOverlap/);
2092
+ assert.doesNotMatch(renderer, /removeCommittedCompositionOverlap/);
2093
+ assert.doesNotMatch(renderer, /sendManualImeCommit/);
2094
+ assert.doesNotMatch(renderer, /shouldDropManualImeXtermInput/);
2095
+ assert.doesNotMatch(renderer, /renderer blocked xterm composition keydown/);
2096
+ assert.doesNotMatch(renderer, /renderer dropped xterm IME replay/);
2097
+ assert.doesNotMatch(renderer, /manualImeCompositionActive/);
2098
+ assert.match(renderer, /fpasotermPluginApi/);
2099
+ assert.match(renderer, /closeWindowButton/);
2100
+ assert.match(renderer, /minimizeWindowButton/);
2101
+ assert.match(renderer, /maximizeWindowButton/);
2102
+ assert.match(renderer, /closeWindow\(\)/);
2103
+ assert.match(renderer, /applyWindowAppearance/);
2104
+ assert.match(renderer, /applyTerminalAppearance/);
2105
+ assert.match(renderer, /normalizeOpacity/);
2106
+ assert.match(renderer, /colorWithOpacity/);
2107
+ assert.match(renderer, /terminalThemeWithOpacity/);
2108
+ assert.match(renderer, /applyRuntimeConfig/);
2109
+ assert.match(renderer, /applyRuntimeConfigPath/);
2110
+ assert.match(renderer, /await afterNextPaint\(\);\s*fitAndResize\(\);/s);
2111
+ assert.match(renderer, /applyConfigPath/);
2112
+ assert.match(renderer, /setRuntimeWindowTitle/);
2113
+ assert.match(renderer, /titleLocked/);
2114
+ assert.match(renderer, /ignored shell title change while title is locked/);
2115
+ assert.match(renderer, /setRuntimeWindowTitle\(value, \{ force: true \}\)/);
2116
+ assert.match(renderer, /setRuntimeTitlebarColor/);
2117
+ assert.match(renderer, /applyFpasotermOsc/);
2118
+ assert.match(renderer, /processRuntimeOsc/);
2119
+ assert.doesNotMatch(renderer, /syncWriteClipboard/);
2120
+ assert.doesNotMatch(renderer, /syncReadClipboard/);
2121
+ assert.match(renderer, /syncWriteDiagnostics/);
2122
+ assert.match(renderer, /installSyncControls/);
2123
+ assert.doesNotMatch(renderer, removedCamelHttpUiPattern);
2124
+ assert.doesNotMatch(renderer, /web console/);
2125
+ assert.match(renderer, /startTerminalLog/);
2126
+ assert.match(renderer, /stopTerminalLog/);
2127
+ assert.match(renderer, /terminalLogStatus/);
2128
+ assert.match(renderer, /logMenuToggleButton/);
2129
+ assert.doesNotMatch(renderer, /setLogMenuOpen/);
2130
+ assert.doesNotMatch(renderer, /closeLogMenu/);
2131
+ assert.match(renderer, /terminalLogToggleButton/);
2132
+ assert.match(renderer, /terminalLogShowButton/);
2133
+ assert.doesNotMatch(renderer, /terminalLogClearButton/);
2134
+ assert.match(renderer, /normalizeJapaneseTerminalText/);
2135
+ assert.match(renderer, /normalize\('NFC'\)/);
2136
+ assert.doesNotMatch(renderer, /normalize\('NFKC'\)/);
2137
+ assert.match(renderer, /terminalLogSelectElement/);
2138
+ assert.match(renderer, /terminalLogDeleteSelectedButton/);
2139
+ assert.match(renderer, /showTerminalLog/);
2140
+ assert.match(renderer, /listTerminalLogs/);
2141
+ assert.match(renderer, /deleteTerminalLog/);
2142
+ assert.match(renderer, /clearTerminalLog/);
2143
+ assert.match(renderer, /showTerminalOutputLog/);
2144
+ assert.match(renderer, /refreshTerminalLogList/);
2145
+ assert.match(renderer, /deleteSelectedTerminalOutputLog/);
2146
+ assert.match(renderer, /clearTerminalOutputLog/);
2147
+ assert.match(renderer, /normalize\('NFC'\)/);
2148
+ assert.match(renderer, /Clear all terminal output logs/);
2149
+ assert.match(renderer, /delete all stopped terminal-\*\.log files/);
2150
+ assert.match(renderer, /This cannot be undone/);
2151
+ assert.doesNotMatch(renderer, /window\.confirm/);
2152
+ assert.match(renderer, /keybindingLabel\('logToggle'\)/);
2153
+ assert.match(renderer, /keybindingLabel\('logShow'\)/);
2154
+ assert.match(renderer, /closeDiagnosticsButton/);
2155
+ assert.match(renderer, /startTerminalOutputLog/);
2156
+ assert.match(renderer, /stopTerminalOutputLog/);
2157
+ assert.match(renderer, /getSelection/);
2158
+ assert.match(renderer, /onTitleChange/);
2159
+ assert.match(renderer, /ImageAddon\.ImageAddon/);
2160
+ assert.match(renderer, /vtExtensions: \{ kittyKeyboard: appConfig\.terminal\?\.kittyKeyboard === true \}/);
2161
+ assert.match(renderer, /kittySupport/);
2162
+ assert.match(renderer, /kittySizeLimit/);
2163
+ assert.match(renderer, /function terminalPtySize/);
2164
+ assert.match(renderer, /pixelWidth/);
2165
+ assert.match(renderer, /pixelHeight/);
2166
+ assert.match(renderer, /onImageAdded/);
2167
+ assert.match(renderer, /interrupt fallback/);
2168
+ assert.match(renderer, /sendTerminalInput\('\\x03'/);
2169
+ assert.match(renderer, /Let the native WebView create a paste event first/);
2170
+ assert.match(renderer, /terminalPasteFallbackTimer/);
2171
+ assert.match(renderer, /isNonTerminalEditableControl/);
2172
+ assert.doesNotMatch(renderer, /materializeKittyFileTransfers/);
2173
+ assert.doesNotMatch(renderer, /readKittyImageFile/);
2174
+ assert.doesNotMatch(renderer, /transfer !== 'f' && transfer !== 't'/);
2175
+ assert.match(rustMain, /pixel_width/);
2176
+ assert.match(rustMain, /pixel_height/);
2177
+ assert.doesNotMatch(rustMain, /kitty_image_file_read/);
2178
+ assert.doesNotMatch(rustMain, /KITTY_IMAGE_FILE_LIMIT_BYTES/);
2179
+ assert.match(renderer, /terminal_broadcast/);
2180
+ assert.match(renderer, /openTerminalBroadcastDialog/);
2181
+ assert.match(renderer, /keybindingLabel\('broadcast'\)/);
2182
+ assert.match(renderer, /showFontGlyphTest/);
2183
+ assert.match(renderer, /windowMenuSubmenus/);
2184
+ assert.match(renderer, /setWindowMenuSubmenuOpen/);
2185
+ assert.match(renderer, /closeWindowMenuSubmenus/);
2186
+ assert.match(renderer, /windowActionsMenuToggleButton/);
2187
+ assert.match(renderer, /fontFamily:/);
2188
+ assert.match(renderer, /fontSize:/);
2189
+ assert.match(renderer, /lineHeight:/);
2190
+ assert.match(renderer, /CJK: 日本語 漢字 ひらがな カタカナ 中文 한국어/);
2191
+ assert.match(renderer, /Half-width kana: アイウエオ/);
2192
+ assert.match(renderer, /Box drawing: ┌─┬─┐/);
2193
+ assert.match(renderer, /Nerd Font \(Powerline\):/);
2194
+ assert.match(renderer, /terminalBroadcastTargets/);
2195
+ assert.match(renderer, /renderTerminalBroadcastTargets/);
2196
+ assert.match(renderer, /targetInstanceIds/);
2197
+ assert.match(renderer, /\\x1b\\\]\(777\|52\|7\|133\|9\|99\);/);
2198
+ assert.match(renderer, /function applyOsc7WorkingDirectory/);
2199
+ assert.match(renderer, /function openWindowAtCurrentDirectory/);
2200
+ assert.match(renderer, /newWindowAtCwd/);
2201
+ assert.match(renderer, /cwdHost/);
2202
+ assert.match(renderer, /New CWD is unavailable until the shell reports its current directory/);
2203
+ assert.match(rustMain, /file:\/\/localhost%s/);
2204
+ assert.match(rustMain, /fn configure_osc7_shell_integration/);
2205
+ assert.match(rustMain, /PROMPT_COMMAND/);
2206
+ assert.match(renderer, /function applyOsc133ShellIntegration/);
2207
+ assert.match(renderer, /function applyOscNotification/);
2208
+ assert.match(renderer, /oscSecurityConfig\(\)\.osc8Open/);
2209
+ assert.match(renderer, /oscSecurityConfig\(\)\.oscNotifications/);
2210
+ assert.match(renderer, /openExternalUrl/);
2211
+ assert.match(renderer, /function osc52MaximumBytes/);
2212
+ assert.match(renderer, /security\.osc52/);
2213
+ assert.match(renderer, /key === 'config'/);
2214
+ assert.match(renderer, /key === 'opacity'/);
2215
+ assert.match(renderer, /CSS\.supports\('color'/);
2216
+ assert.match(renderer, /titlebarColor/);
2217
+ assert.match(renderer, /--titlebar-background/);
2218
+ assert.match(renderer, /function fitWindowMenuToViewport\(\)/);
2219
+ assert.match(renderer, /window\.innerHeight - top - 8/);
2220
+ assert.match(renderer, /window\.addEventListener\('resize', fitWindowMenuToViewport\)/);
2221
+
2222
+ const styles = read('src/renderer/styles.css');
2223
+ assert.match(styles, /This is visual-only and never writes to the PTY/);
2224
+ assert.match(styles, /#ime-preedit/);
2225
+ assert.match(styles, /#ime-preedit\s*\{[\s\S]*z-index: 10010/);
2226
+ assert.match(styles, /\.xterm \.composition-view\s*\{[\s\S]*border-bottom: 1px solid #f5d76e/);
2227
+ assert.match(styles, /body\.ime-preedit-overlay \.xterm \.composition-view/);
2228
+ assert.match(styles, /#ime-preedit::before/);
2229
+ assert.match(styles, /#ime-status/);
2230
+ assert.match(styles, /#ime-status\s*\{[\s\S]*bottom: 8px/);
2231
+ assert.match(styles, /#ime-status\s*\{[\s\S]*text-decoration: underline/);
2232
+ assert.match(styles, /\.window-menu-submenu\[hidden\]\s*\{\s*display: none;/);
2233
+ assert.match(styles, /xterm-image-layer-top/);
2234
+ assert.match(styles, /#drag-region/);
2235
+ assert.doesNotMatch(styles, removedKebabHttpUiPattern);
2236
+ assert.match(styles, /--titlebar-background: #1565c0/);
2237
+ assert.match(styles, /background: var\(--titlebar-background\)/);
2238
+ assert.match(styles, /-webkit-app-region: drag/);
2239
+ assert.match(styles, /app-region: drag/);
2240
+ assert.match(styles, /--titlebar-height: 34px/);
2241
+ assert.match(styles, /grid-template-rows: var\(--titlebar-height\) minmax\(0, 1fr\)/);
2242
+ assert.match(styles, /height: var\(--titlebar-height\)/);
2243
+ assert.match(styles, /pointer-events: none/);
2244
+ assert.match(styles, /#window-controls/);
2245
+ assert.match(styles, /#window-title[\s\S]*flex: 1 1 auto/);
2246
+ assert.match(styles, /#window-menu-items[\s\S]*max-width: calc\(100vw - 12px\)/);
2247
+ assert.match(styles, /#window-menu-items[\s\S]*max-height: calc\(100vh - var\(--titlebar-height\) - 8px\)/);
2248
+ assert.match(styles, /#window-menu-items[\s\S]*overflow-y: auto/);
2249
+ assert.match(styles, /#terminal-log-status/);
2250
+ assert.match(styles, /#terminal-log-status\[hidden\]/);
2251
+ assert.doesNotMatch(styles, /#sync-menu/);
2252
+ assert.doesNotMatch(styles, /#sync-menu-items/);
2253
+ assert.doesNotMatch(styles, /#log-menu/);
2254
+ assert.match(styles, /#terminal-log-search/);
2255
+ assert.match(styles, /#terminal-log-search-next/);
2256
+ assert.match(styles, /#terminal-log-search-status/);
2257
+ assert.match(styles, /#terminal-log-confirm/);
2258
+ assert.match(styles, /\.terminal-log-confirm-card/);
2259
+ assert.match(styles, /#sshfs-manager-dialog \{[\s\S]*overflow-y: auto;/);
2260
+ assert.match(styles, /#sshfs-manager-dialog \{[\s\S]*position: fixed;/);
2261
+ assert.match(styles, /#sshfs-manager-dialog \{[\s\S]*top: var\(--titlebar-height\);/);
2262
+ assert.match(styles, /\.sshfs-manager-card \{[\s\S]*max-width: 680px;/);
2263
+ assert.match(styles, /#terminal-log-confirm-ok/);
2264
+ assert.match(styles, /#diagnostics-panel button:focus/);
2265
+ assert.match(styles, /#diagnostics-panel select:focus-visible/);
2266
+ assert.match(styles, /outline: 3px solid #f5d76e/);
2267
+ assert.match(styles, /box-shadow: 0 0 0 2px/);
2268
+ assert.match(styles, /#diagnostics::selection/);
2269
+ assert.match(styles, /#font-glyph-preview/);
2270
+ assert.match(styles, /#terminal-broadcast-dialog/);
2271
+ assert.match(styles, /position: fixed/);
2272
+ assert.match(styles, /100dvw/);
2273
+ assert.match(styles, /right: 8px/);
2274
+ assert.match(styles, /#terminal-broadcast-confirm\s*\{[\s\S]*position: fixed[\s\S]*z-index: 10040/);
2275
+ assert.match(styles, /cursor: move/);
2276
+ assert.match(styles, /#terminal-broadcast-control-picker/);
2277
+ assert.match(styles, /\.keyboard-focus/);
2278
+
2279
+ const broadcastControlDocsEn = read('docs/config.en.md');
2280
+ const broadcastControlDocsJa = read('docs/config.ja.md');
2281
+ assert.match(broadcastControlDocsEn, /Every send uses the target terminal's actual Enter key event/);
2282
+ assert.match(broadcastControlDocsEn, /Its \*\*Control byte\*\* picker inserts visible/);
2283
+ assert.match(broadcastControlDocsEn, /`\\x03`/);
2284
+ assert.match(broadcastControlDocsEn, /second confirmation/);
2285
+ assert.match(broadcastControlDocsEn, /git reset/);
2286
+ assert.match(broadcastControlDocsJa, /送信時は常に本文の後にtarget terminalの実際のEnter key event/);
2287
+ assert.match(broadcastControlDocsJa, /dialogの \*\*Control byte\*\* pickerでは/);
2288
+ assert.match(broadcastControlDocsJa, /`\\x03`/);
2289
+ assert.match(broadcastControlDocsJa, /追加確認/);
2290
+ assert.match(renderer, /function insertTerminalBroadcastControl/);
2291
+ assert.match(renderer, /function decodeTerminalBroadcastControls/);
2292
+ assert.match(renderer, /\\\\x0d/);
2293
+ assert.match(renderer, /function dangerousBroadcastCommandLabels/);
2294
+ assert.match(renderer, /'git reset --hard'/);
2295
+ assert.match(renderer, /function confirmDangerousBroadcast/);
2296
+ assert.match(renderer, /function resolveDangerousBroadcastConfirmation/);
2297
+ assert.match(renderer, /'ctrl-x': \{ text: '\\\\x18'/);
2298
+ assert.match(renderer, /enter: \{ text: '\\\\x0D'/);
2299
+ assert.match(renderer, /pluginMenuToggleButton\.addEventListener\('click'/);
2300
+ assert.match(renderer, /setRangeText/);
2301
+
2302
+ const capabilityDiagnosticsEn = read('docs/capability-diagnostics.en.md');
2303
+ const capabilityDiagnosticsJa = read('docs/capability-diagnostics.ja.md');
2304
+ assert.match(capabilityDiagnosticsEn, /OSC 52/);
2305
+ assert.match(capabilityDiagnosticsEn, /OSC 9 \/ OSC 99/);
2306
+ assert.match(capabilityDiagnosticsEn, /New CWD/);
2307
+ assert.match(capabilityDiagnosticsEn, /Bracketed paste/);
2308
+ assert.match(capabilityDiagnosticsJa, /truecolor/);
2309
+ assert.match(capabilityDiagnosticsJa, /OSC 8/);
2310
+ assert.match(capabilityDiagnosticsJa, /OSC 9 \/ OSC 99/);
2311
+ assert.match(capabilityDiagnosticsJa, /New CWD/);
2312
+ assert.match(styles, /\.window-menu-submenu-items/);
2313
+ assert.match(styles, /white-space: pre-wrap/);
2314
+ assert.match(styles, /padding: 0 0 8px/);
2315
+ assert.match(styles, /\.resize-edge-bottom\s*\{[^}]*height: 4px/s);
2316
+ assert.match(styles, /\.resize-edge-bottom\s*\{[^}]*left: 96px/s);
2317
+ assert.match(styles, /\.resize-edge-left\s*\{[^}]*bottom: 64px/s);
2318
+ assert.match(styles, /\.resize-corner-bottom-left\s*\{[^}]*display: none/s);
2319
+ assert.match(styles, /#close-window/);
2320
+ assert.match(styles, /resize-edge/);
2321
+ assert.match(styles, /resize-corner/);
2322
+ assert.match(styles, /rgba\(0, 0, 0, 0\.001\)/);
2323
+ assert.match(styles, /app-region: no-drag/);
2324
+ assert.match(styles, /padding: 0/);
2325
+ assert.match(styles, /background: transparent/);
2326
+ assert.match(styles, /width: 100%/);
2327
+ assert.match(styles, /xterm-accessibility/);
2328
+ assert.match(styles, /display: none !important/);
2329
+ assert.match(styles, /visibility: hidden !important/);
2330
+ assert.match(styles, /xterm-helper-textarea/);
2331
+ assert.match(styles, /xterm-char-measure-element/);
2332
+ assert.match(styles, /left: -9999em !important/);
2333
+ assert.match(styles, /-webkit-text-fill-color: transparent !important/);
2334
+ assert.doesNotMatch(styles, /\.xterm-screen,\s*\.xterm-screen canvas\s*\{[^}]*height: 100% !important/s);
2335
+ assert.doesNotMatch(styles, /\.xterm-screen canvas\s*\{[^}]*display: block/s);
2336
+
2337
+ const config = read('src/config.js');
2338
+ assert.match(config, /defaultConfig/);
2339
+ assert.match(config, /defaultConfigExample/);
2340
+ assert.match(config, /title: 'fpasoterm'/);
2341
+ assert.match(config, /titlebarColor: '#1565c0'/);
2342
+ assert.match(config, /titleLocked: true/);
2343
+ assert.match(config, /backgroundOpacity: 0\.65/);
2344
+
2345
+ const rendererFallbackConfig = read('src/renderer/renderer.js');
2346
+ assert.match(rendererFallbackConfig, /backgroundOpacity: 0\.65/);
2347
+ assert.match(rendererFallbackConfig, /background: 'rgba\(16, 19, 23, 0\.65\)'/);
2348
+ assert.match(config, /lineHeight: defaultTerminalLineHeight/);
2349
+ assert.match(read('src/renderer/vendor/xterm/xterm.js'), /case"lineHeight":if\(i<\.5\)/);
2350
+ assert.match(config, /minimumContrastRatio: 1/);
2351
+ assert.match(config, /rescaleOverlappingGlyphs: false/);
2352
+ assert.match(config, /BIZ UDGothic/);
2353
+ assert.match(config, /半角カタカナ|half-width kana/);
2354
+ assert.match(config, /termName: 'xterm-256color'/);
2355
+ assert.match(config, /encoding: 'utf-8'/);
2356
+ assert.match(config, /shell: ''/);
2357
+ assert.match(config, /kittyKeyboard: false/);
2358
+ assert.match(config, /keybindings:/);
2359
+ assert.match(config, /prefix: 'Mod\+Shift'/);
2360
+ assert.match(config, /Ctrl\+Alt\+KeyN/);
2361
+ assert.match(config, /enabled: false/);
2362
+ assert.match(config, /kittySupport: false/);
2363
+ assert.match(config, /function writableConfigDefaults/);
2364
+ assert.match(config, /sixelSupport: false/);
2365
+ assert.match(config, /commandTtlSeconds: 60/);
2366
+ assert.match(config, /sync:/);
2367
+ assert.match(config, /provider: 'folder'/);
2368
+ assert.match(config, /logging:/);
2369
+ assert.match(config, /autoStart: false/);
2370
+ assert.match(config, /maxBytes: 10485760/);
2371
+ assert.match(config, /writeDefaultConfigExample/);
2372
+ assert.match(config, /profileDir/);
2373
+ assert.match(config, /'User'/);
2374
+ assert.match(config, /smol-toml/);
2375
+ assert.match(config, /config\.toml/);
2376
+ assert.match(config, /FPASOTERM_CONFIG_PATH/);
2377
+ assert.match(config, /readUserConfig/);
2378
+ assert.match(config, /writeUserConfig/);
2379
+ assert.match(config, /windowStatePath/);
2380
+ assert.match(config, /readWindowState/);
2381
+ assert.match(config, /\.example/);
2382
+ assert.match(config, /typescript/);
2383
+ assert.match(config, /\.ts/);
2384
+ assert.match(config, /transpileModule/);
2385
+
2386
+ const launcher = read('bin/fpasoterm');
2387
+ assert.match(launcher, /const runtimeConfig = loadConfig\(\);\s+printPluginList\(targetPath, \[\.\.\.enabled\]\.sort\(\), runtimeConfig\.pluginUrls\);/);
2388
+ assert.match(launcher, /--plugin-info <file>/);
2389
+ assert.match(launcher, /--plugin-uninstall <file>/);
2390
+ assert.match(launcher, /function uninstallLocalPlugins/);
2391
+ const windowsLauncher = read('extra/windows/fpasoterm.cmd');
2392
+ assert.match(windowsLauncher, /--plugin-uninstall/);
2393
+ assert.match(launcher, /function showPluginInfo\(options\)/);
2394
+ assert.match(launcher, /--setup-sync/);
2395
+ assert.match(launcher, /--sync-status/);
2396
+ assert.match(launcher, /--sync-clean/);
2397
+ assert.match(launcher, /--sync-diagnostics/);
2398
+ assert.match(launcher, /function broadcastTerminalInput\(options\)/);
2399
+ assert.match(launcher, /--broadcast-target and --broadcast-sync require --broadcast <text>/);
2400
+ assert.match(launcher, /localBroadcastCommandDirectory/);
2401
+ assert.match(launcher, /--version/);
2402
+ assert.match(launcher, /printVersion/);
2403
+ assert.doesNotMatch(launcher, removedKebabHttpUiPattern);
2404
+ assert.doesNotMatch(launcher, removedHttpUiEnvPattern);
2405
+ assert.match(launcher, /setupSync/);
2406
+ assert.match(launcher, /expandPathVariables/);
2407
+ assert.match(launcher, /%\(\[\^%\]\+\)%/);
2408
+ assert.match(launcher, /googleDriveCandidates/);
2409
+ assert.match(launcher, /\/mnt\/chromeos\/shared\/GoogleDrive\/MyDrive/);
2410
+ assert.match(launcher, /\/mnt\/shared\/GoogleDrive\/MyDrive/);
2411
+ assert.match(launcher, /\/mnt\/chromeos\/GoogleDrive\/MyDrive/);
2412
+ assert.match(launcher, /writableDriveCandidates/);
2413
+ assert.match(launcher, /canWriteDirectory/);
2414
+ assert.match(launcher, /syncPathCandidates/);
2415
+ assert.match(launcher, /candidate number/);
2416
+ assert.match(launcher, /Use the same sync channel on devices that should share data/);
2417
+ assert.match(launcher, /Press Enter to keep "default"/);
2418
+ assert.match(launcher, /Terminal output logs stay local by default/);
2419
+ assert.match(launcher, /Answer y only when you want readable terminal output logs stored in the selected sync folder/);
2420
+ assert.match(launcher, /Terminal log directory/);
2421
+ assert.doesNotMatch(launcher, /Synced terminal log directory/);
2422
+ assert.match(launcher, /G:\\\\My Drive/);
2423
+ assert.match(launcher, /マイドライブ/);
2424
+ assert.match(launcher, /DEFGHIJKLMNOPQRSTUVWXYZ/);
2425
+ assert.match(launcher, /GoogleDrive-/);
2426
+ assert.match(launcher, /G:\\\\My Drive/);
2427
+ assert.match(launcher, /readline\.createInterface/);
2428
+ assert.match(launcher, /writeUserConfig/);
2429
+ assert.match(renderer, /function pluginScriptSource\(plugin\)/);
2430
+ assert.match(renderer, /convertFileSrc/);
2431
+ assert.match(renderer, /failed to load plugin \$\{plugin\.name\} source=\$\{source\}/);
2432
+ assert.match(renderer, /function openSshfsManagerModal/);
2433
+ assert.match(renderer, /sshfsManagerPasswordToggleButton\.addEventListener/);
2434
+ assert.match(renderer, /sshfsManagerForgetButton\.addEventListener/);
2435
+ assert.match(renderer, /function selectedSshfsMountName/);
2436
+ assert.match(renderer, /const mountName = selectedSshfsMountName\(\)/);
2437
+ assert.match(renderer, /sshfsManagerLocalPathElement\.addEventListener/);
2438
+ assert.match(renderer, /sshfsManagerResult\.scrollIntoView/);
2439
+ assert.match(renderer, /activeMountNames/);
2440
+ assert.match(renderer, /\(not mounted\)/);
2441
+ assert.match(renderer, /SSHFS mount ready: \$\{mounted\.mountPoint\}/);
2442
+ assert.doesNotMatch(renderer, /openSshfsManager: \(\) => invoke\('sshfs_manager_open'\)/);
2443
+ assert.match(renderer, /mountSshfs: \(request\)/);
2444
+ assert.match(renderer, /function copyTerminalLink/);
2445
+ assert.match(renderer, /function installTerminalPathLinks/);
2446
+ assert.match(renderer, /WebLinksAddon/);
2447
+ assert.match(read('src/renderer/index.html'), /vendor\/addon-web-links\/addon-web-links\.js/);
2448
+ const buildScript = read('src-tauri/build.rs');
2449
+ assert.match(buildScript, /fn watch_frontend_assets/);
2450
+ assert.match(buildScript, /src\/renderer/);
2451
+
2452
+ const desktop = read('extra/linux/io.github.oyoguhito.fpasoterm.desktop');
2453
+ assert.match(desktop, /^Name=fpasoterm$/m);
2454
+ assert.match(desktop, /^Icon=io\.github\.oyoguhito\.fpasoterm$/m);
2455
+ assert.match(desktop, /^StartupWMClass=fpasoterm$/m);
2456
+ assert.match(read('README.md'), /StartupWMClass=fpasoterm/);
2457
+ assert.match(read('INSTALL.md'), /StartupWMClass=fpasoterm/);
2458
+ assert.match(read('INSTALL.ja.md'), /StartupWMClass=fpasoterm/);
2459
+ assert.match(read('INSTALL.ja.md'), /Linux Desktop Entry/);
2460
+ assert.match(read('README.ja.md'), /docs\/config\.ja\.md/);
2461
+
2462
+ for (const file of [
2463
+ 'README.md',
2464
+ 'CHANGELOG.md',
2465
+ 'CONTRIBUTING.md',
2466
+ 'INSTALL.md',
2467
+ 'docs/spec.en.md',
2468
+ 'docs/spec.ja.md',
2469
+ 'docs/release-checklist.en.md',
2470
+ 'docs/release-checklist.ja.md',
2471
+ ]) {
2472
+ const externalEditorPattern = new RegExp(`${'vs'}\\s*${'code'}|${'vs'}${'code'}`, 'i');
2473
+ assert.doesNotMatch(read(file), externalEditorPattern, `${file} should not mention external editor wording`);
2474
+ }
2475
+
2476
+ console.log('smoke checks passed');