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,36 @@
1
+ # Default appearance config.
2
+ # Apply from a running fpasoterm terminal with a POSIX shell:
3
+ # printf '\033]777;config=/path/to/fpasoterm/examples/config/default-appearance.toml\a\r\n'
4
+ # On Windows, use examples/apply-default-appearance.ps1 or .bat.
5
+
6
+ [window]
7
+ title = "fpasoterm"
8
+ titlebarColor = "#1565c0"
9
+
10
+ [terminal]
11
+ lineHeight = 1
12
+ # Preserve application-selected ANSI/RGB colors and block/box glyph art.
13
+ minimumContrastRatio = 1
14
+ rescaleOverlappingGlyphs = false
15
+ backgroundOpacity = 0.65
16
+
17
+ [terminal.theme]
18
+ background = "rgba(16, 19, 23, 0.65)"
19
+ foreground = "#e8edf2"
20
+ cursor = "#f5d76e"
21
+ selectionBackground = "#35506b"
22
+
23
+ [keybindings]
24
+ prefix = "Mod+Shift"
25
+ logMenu = "L"
26
+ logToggle = "S"
27
+ logShow = "P"
28
+ copy = "C"
29
+ paste = "V"
30
+ menu = "M"
31
+ help = "H"
32
+ newWindow = "N"
33
+ broadcast = "B"
34
+ kill = "K"
35
+ tile = "T"
36
+ closeAll = "X"
@@ -0,0 +1,30 @@
1
+ # Minimal fpasoterm config example.
2
+ # Copy this to ~/.config/fpasoterm/User/config.toml and edit as needed.
3
+
4
+ [window]
5
+ title = "fpasoterm"
6
+ width = 1000
7
+ height = 680
8
+ titlebarColor = "#1565c0"
9
+
10
+ [terminal]
11
+ fontSize = 14
12
+ lineHeight = 1
13
+ minimumContrastRatio = 1
14
+ rescaleOverlappingGlyphs = false
15
+ backgroundOpacity = 0.65
16
+ fontFamily = "\"DejaVu Sans Mono\", \"Noto Sans Mono CJK JP\", \"Noto Sans CJK JP\", \"BIZ UDGothic\", \"Hiragino Sans\", Meiryo, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace"
17
+ termName = "xterm-256color"
18
+ # Keep UTF-8 unless a legacy program explicitly emits Shift_JIS or EUC-JP.
19
+ encoding = "utf-8"
20
+ # Leave empty for the platform default shell.
21
+ # Windows examples: "powershell.exe", "pwsh.exe", or "cmd.exe".
22
+ shell = ""
23
+
24
+ [keybindings]
25
+ prefix = "Mod+Shift"
26
+ newWindow = "N"
27
+ tile = "T"
28
+
29
+ [plugins]
30
+ enabled = []
@@ -0,0 +1,20 @@
1
+ # Apply this file with: fpasoterm --config ./examples/config/profiles.toml --profile large-font
2
+
3
+ [window]
4
+ title = "fpasoterm"
5
+ titlebarColor = "#1565c0"
6
+
7
+ [terminal]
8
+ fontSize = 14
9
+ backgroundOpacity = 0.65
10
+
11
+ # Profiles override normal config sections for one launch. They do not modify
12
+ # config.toml or User/window-state.json.
13
+ [profiles.large-font.terminal]
14
+ fontSize = 18
15
+
16
+ [profiles.transparent.window]
17
+ titlebarColor = "#00695c"
18
+
19
+ [profiles.transparent.terminal]
20
+ backgroundOpacity = 0.65
@@ -0,0 +1,24 @@
1
+ # Runtime appearance test config.
2
+ # Apply from a running fpasoterm terminal with a POSIX shell:
3
+ # printf '\033]777;config=/path/to/fpasoterm/examples/config/runtime-appearance.toml\a\r\n'
4
+ # On Windows, use examples/apply-runtime-appearance.ps1 or .bat.
5
+
6
+ [window]
7
+ title = "RUNTIME SAMPLE ACTIVE"
8
+ titlebarColor = "#d81b60"
9
+
10
+ # Uncomment these only when testing live window resizing.
11
+ # width = 1180
12
+ # height = 720
13
+
14
+ [terminal]
15
+ backgroundOpacity = 0.95
16
+
17
+ # Uncomment this only when testing live font resizing.
18
+ # fontSize = 18
19
+
20
+ [terminal.theme]
21
+ background = "#32004f"
22
+ foreground = "#fff176"
23
+ cursor = "#00e5ff"
24
+ selectionBackground = "#7b1fa2"
@@ -0,0 +1,17 @@
1
+ # Sync folder sample.
2
+ # Use a local folder that is already synchronized by Google Drive for desktop
3
+ # or another sync tool. fpasoterm does not call Google Drive APIs.
4
+
5
+ [sync]
6
+ enabled = true
7
+ provider = "folder"
8
+ path = "~/Google Drive/fpasoterm-sync"
9
+ channel = "default"
10
+ diagnostics = true
11
+ maxBytes = 1048576
12
+
13
+ [logging]
14
+ enabled = true
15
+ directory = "~/Google Drive/fpasoterm-sync/logs"
16
+ autoStart = false
17
+ maxBytes = 10485760
@@ -0,0 +1,7 @@
1
+ # Preserve application-selected ANSI/RGB colors and tightly spaced glyph art.
2
+ # Launch once with:
3
+ # fpasoterm --config examples/config/tui-compatibility.toml
4
+
5
+ [terminal]
6
+ minimumContrastRatio = 1
7
+ rescaleOverlappingGlyphs = false
@@ -0,0 +1,33 @@
1
+ # fpasoterm config example with TypeScript plugins enabled.
2
+ # The plugin paths are relative to ~/.config/fpasoterm/User/.
3
+
4
+ [window]
5
+ title = "fpasoterm plugins"
6
+ width = 1200
7
+ height = 760
8
+ titlebarColor = "#1565c0"
9
+
10
+ [terminal]
11
+ fontSize = 15
12
+ lineHeight = 1
13
+ minimumContrastRatio = 1
14
+ rescaleOverlappingGlyphs = false
15
+ backgroundOpacity = 0.65
16
+ scrollback = 3000
17
+ termName = "xterm-256color"
18
+ # Keep UTF-8 unless a legacy program explicitly emits Shift_JIS or EUC-JP.
19
+ encoding = "utf-8"
20
+ # Leave empty for the platform default shell.
21
+ # Windows examples: "powershell.exe", "pwsh.exe", or "cmd.exe".
22
+ shell = ""
23
+
24
+ [terminal.theme]
25
+ background = "rgba(16, 19, 23, 0.65)"
26
+ foreground = "#e8edf2"
27
+ cursor = "#f5d76e"
28
+
29
+ [plugins]
30
+ enabled = [
31
+ "plugins/hello.ts",
32
+ "plugins/theme.ts",
33
+ ]
@@ -0,0 +1,17 @@
1
+ /// <reference path="../../docs/fpasoterm-plugin.d.ts" />
2
+ // @fpasoterm-plugin version: 1.0.0
3
+ // @fpasoterm-plugin description: Writes a confirmation after the terminal is ready.
4
+
5
+ // This sample demonstrates the minimum plugin shape.
6
+ // Wait for the PTY before writing, so shell initialization cannot erase it.
7
+ const api = window.fpasotermPluginApi;
8
+
9
+ api.log('hello.ts loaded');
10
+ api.registerCommand('hello.show', 'Show Hello Banner', () => {
11
+ api.terminal.writeln('[fpasoterm plugin] hello.ts loaded');
12
+ api.terminal.focus();
13
+ });
14
+ api.onReady(() => {
15
+ api.terminal.writeln('');
16
+ api.terminal.writeln('[fpasoterm plugin] hello.ts loaded');
17
+ });
@@ -0,0 +1,14 @@
1
+ /// <reference path="../../docs/fpasoterm-plugin.d.ts" />
2
+ // @fpasoterm-plugin version: 1.0.0
3
+ // @fpasoterm-plugin description: Adds a Plugins menu command that prints terminal status.
4
+
5
+ // Registers a menu action that shows resolved settings after startup.
6
+ const api = window.fpasotermPluginApi;
7
+ const { width, height } = api.config.window;
8
+ const enabledCount = api.config.plugins.enabled.length;
9
+
10
+ api.log(`status-banner.ts loaded for ${width}x${height}, plugins=${enabledCount}`);
11
+ api.registerCommand('status-banner.show', 'Show Plugin Status', () => {
12
+ api.terminal.writeln(`[fpasoterm] Status: ${width}x${height}, enabled plugins: ${enabledCount}.`);
13
+ api.terminal.focus();
14
+ });
@@ -0,0 +1,32 @@
1
+ /// <reference path="../../docs/fpasoterm-plugin.d.ts" />
2
+ // @fpasoterm-plugin version: 1.0.0
3
+ // @fpasoterm-plugin description: Applies the visible teal sample terminal palette.
4
+
5
+ // This sample applies a visibly different terminal palette after startup.
6
+ // Use config.toml for normal settings; use plugins for temporary behavior changes.
7
+ const api = window.fpasotermPluginApi;
8
+
9
+ function applyTealTheme() {
10
+ api.terminal.options.theme = {
11
+ ...api.config.terminal.theme,
12
+ background: 'rgba(8, 42, 48, 0.86)',
13
+ foreground: '#d8f6f3',
14
+ cursor: '#ffdc73',
15
+ selectionBackground: '#246b73',
16
+ blue: '#75c9ff',
17
+ cyan: '#73e6dd',
18
+ green: '#a9df7d',
19
+ };
20
+ api.fitAddon.fit();
21
+ }
22
+
23
+ api.log('theme.ts loaded: applying the teal sample palette');
24
+ api.registerCommand('theme.apply-teal', 'Apply Teal Theme', () => {
25
+ applyTealTheme();
26
+ api.terminal.writeln('[fpasoterm plugin] theme.ts applied the teal sample palette.');
27
+ api.terminal.focus();
28
+ });
29
+ applyTealTheme();
30
+ api.onReady(() => {
31
+ api.terminal.writeln('[fpasoterm plugin] theme.ts applied the teal sample palette.');
32
+ });
@@ -0,0 +1,16 @@
1
+ /// <reference path="../../docs/fpasoterm-plugin.d.ts" />
2
+ // @fpasoterm-plugin version: 1.0.0
3
+ // @fpasoterm-plugin description: Displays a local welcome banner after startup.
4
+
5
+ // Shows a small, local-only startup message after the PTY is ready.
6
+ const api = window.fpasotermPluginApi;
7
+
8
+ api.log('welcome-banner.ts loaded');
9
+ api.registerCommand('welcome-banner.show', 'Show Welcome Banner', () => {
10
+ api.terminal.writeln(`[fpasoterm ${api.version}] Welcome banner plugin is active.`);
11
+ api.terminal.focus();
12
+ });
13
+ api.onReady(() => {
14
+ api.terminal.writeln('');
15
+ api.terminal.writeln(`[fpasoterm ${api.version}] Welcome banner plugin is active.`);
16
+ });
@@ -0,0 +1,10 @@
1
+ [Desktop Entry]
2
+ Type=Application
3
+ Name=fpasoterm
4
+ Comment=Cross-platform terminal with Japanese IME support
5
+ Exec=fpasoterm
6
+ Icon=io.github.oyoguhito.fpasoterm
7
+ Terminal=false
8
+ Categories=System;TerminalEmulator;
9
+ StartupNotify=true
10
+ StartupWMClass=fpasoterm
Binary file
Binary file
@@ -0,0 +1,40 @@
1
+ @echo off
2
+ setlocal DisableDelayedExpansion
3
+ set "FPASOTERM_ARGS=%*"
4
+
5
+ rem Console wrapper for the GUI-subsystem fpasoterm.exe beside this file.
6
+ rem CLI operations run as a direct child so output and exit codes return to the
7
+ rem invoking PowerShell/cmd console; normal launches remain detached.
8
+ set "FPASOTERM_EXE=%~dp0fpasoterm.exe"
9
+ if not exist "%FPASOTERM_EXE%" (
10
+ echo fpasoterm: executable not found: "%FPASOTERM_EXE%" 1>&2
11
+ exit /b 1
12
+ )
13
+
14
+ :scan
15
+ if "%~1"=="" goto run
16
+ for %%F in (
17
+ --help -h --version -v --update-check --doctor --list -l --close -q --show-config
18
+ --completion --completion-install --completion-uninstall
19
+ --profile-list --config-check --config-path --config-example
20
+ --diagnostics --open-log-dir --copy-diagnostics
21
+ --plugin-list --plugin-path --plugin-info --plugin-uninstall --plugin-search --plugin-install
22
+ --plugin-ports-dir --plugin-install-file
23
+ --force --enable
24
+ --enable-plugin --disable-plugin --plugin-enable --plugin-disable
25
+ --plugin-enable-all --plugin-disable-all
26
+ --reset-window-state -r --reset-config -R --update-config --prune-config
27
+ --setup-sync --foreground -F
28
+ --broadcast --broadcast-target --broadcast-sync
29
+ ) do if /I "%~1"=="%%F" set "FPASOTERM_WAIT=1"
30
+ shift
31
+ goto scan
32
+
33
+ :run
34
+ if defined FPASOTERM_WAIT (
35
+ call "%FPASOTERM_EXE%" %FPASOTERM_ARGS%
36
+ exit /b %ERRORLEVEL%
37
+ )
38
+
39
+ start "" "%FPASOTERM_EXE%" %FPASOTERM_ARGS%
40
+ exit /b 0
Binary file
package/package.json ADDED
@@ -0,0 +1,81 @@
1
+ {
2
+ "name": "fpasoterm",
3
+ "version": "1.6.0",
4
+ "description": "Cross-platform terminal with Japanese IME support.",
5
+ "license": "MIT",
6
+ "homepage": "https://github.com/oyoguhito/fpasoterm#readme",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/oyoguhito/fpasoterm.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/oyoguhito/fpasoterm/issues"
13
+ },
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
17
+ "keywords": [
18
+ "terminal",
19
+ "xterm",
20
+ "ime",
21
+ "japanese",
22
+ "chromeos"
23
+ ],
24
+ "bin": {
25
+ "fpasoterm": "bin/fpasoterm"
26
+ },
27
+ "files": [
28
+ "bin/",
29
+ "completions/",
30
+ "docs/",
31
+ "examples/",
32
+ "extra/",
33
+ "scripts/",
34
+ "src/",
35
+ "src-tauri/Cargo.lock",
36
+ "src-tauri/Cargo.toml",
37
+ "src-tauri/build.rs",
38
+ "src-tauri/capabilities/",
39
+ "src-tauri/default-config.toml",
40
+ "src-tauri/src/",
41
+ "src-tauri/tauri.conf.json",
42
+ "CHANGELOG.md",
43
+ "CONTRIBUTING.md",
44
+ "INSTALL.md",
45
+ "INSTALL.ja.md",
46
+ "README.md",
47
+ "README.ja.md",
48
+ "LICENSE",
49
+ "package.json",
50
+ "package-lock.json"
51
+ ],
52
+ "scripts": {
53
+ "start": "node ./bin/fpasoterm",
54
+ "tauri": "tauri",
55
+ "dev": "tauri dev",
56
+ "build": "tauri build",
57
+ "check": "npm run check:syntax && npm test && cargo check --manifest-path src-tauri/Cargo.toml",
58
+ "check:syntax": "node --check bin/fpasoterm && node --check scripts/uninstall-desktop.js && node --check scripts/uninstall-windows-path.js && node --check src/config.js && node --check src/renderer/renderer.js",
59
+ "test": "node scripts/tests/smoke.js",
60
+ "scan:secrets": "node scripts/security/scan-secrets.js",
61
+ "security": "npm run scan:secrets && npm run audit:prod",
62
+ "audit:prod": "npm audit --omit=dev",
63
+ "generate:icons": "node scripts/generate-icon.js",
64
+ "install:desktop": "node scripts/install-linux-desktop.js",
65
+ "update:desktop": "node scripts/install-linux-desktop.js",
66
+ "uninstall:desktop": "node scripts/uninstall-desktop.js",
67
+ "build:artifacts": "node scripts/build-artifacts.js",
68
+ "build:bundles": "node scripts/build-artifacts.js --bundles-only"
69
+ },
70
+ "dependencies": {
71
+ "@tauri-apps/api": "^2.11.1",
72
+ "@tauri-apps/cli": "^2.11.4",
73
+ "@tauri-apps/plugin-notification": "^2.3.3",
74
+ "@xterm/addon-fit": "0.12.0-beta.288",
75
+ "@xterm/addon-image": "0.10.0-beta.288",
76
+ "@xterm/addon-web-links": "0.12.0",
77
+ "@xterm/xterm": "6.1.0-beta.288",
78
+ "smol-toml": "^1.4.2",
79
+ "typescript": "^5.9.3"
80
+ }
81
+ }
@@ -0,0 +1,183 @@
1
+ const childProcess = require('node:child_process');
2
+ const fs = require('node:fs');
3
+ const path = require('node:path');
4
+
5
+ const root = path.resolve(__dirname, '..');
6
+ const artifactsDir = path.join(root, 'artifacts');
7
+ const npmCacheDir = path.join(artifactsDir, '.npm-cache');
8
+ const packageJson = require(path.join(root, 'package.json'));
9
+ const version = packageJson.version;
10
+ const args = new Set(process.argv.slice(2));
11
+ const sourceOnly = args.has('--source-only');
12
+ const bundlesOnly = args.has('--bundles-only');
13
+ const artifactLabel = process.env.FPASOTERM_ARTIFACT_LABEL || '';
14
+ if (sourceOnly && bundlesOnly) {
15
+ throw new Error('Use only one of --source-only or --bundles-only');
16
+ }
17
+ const packageEntries = [
18
+ 'bin',
19
+ 'docs',
20
+ 'examples',
21
+ 'extra',
22
+ 'scripts',
23
+ 'src',
24
+ 'src-tauri',
25
+ 'CHANGELOG.md',
26
+ 'CONTRIBUTING.md',
27
+ 'INSTALL.md',
28
+ 'README.md',
29
+ 'LICENSE',
30
+ 'package.json',
31
+ 'package-lock.json',
32
+ ];
33
+
34
+ // Resolves npm through its JavaScript entry point when available. This avoids
35
+ // invoking npm.cmd through a shell on Windows, which Node deprecates (DEP0190).
36
+ function npmInvocation(args) {
37
+ const bundledNpmCli = path.join(path.dirname(process.execPath), 'node_modules', 'npm', 'bin', 'npm-cli.js');
38
+ const npmCli = [process.env.npm_execpath, bundledNpmCli].find((candidate) => candidate && fs.existsSync(candidate));
39
+ if (npmCli) {
40
+ return { command: process.execPath, args: [npmCli, ...args] };
41
+ }
42
+ return { command: process.platform === 'win32' ? 'npm.cmd' : 'npm', args };
43
+ }
44
+
45
+ // Runs packaging commands from the repository root with an artifact-local npm cache.
46
+ function run(command, args, options = {}) {
47
+ const invocation = command === 'npm' ? npmInvocation(args) : { command, args };
48
+ const result = childProcess.spawnSync(invocation.command, invocation.args, {
49
+ cwd: root,
50
+ stdio: 'inherit',
51
+ env: {
52
+ ...process.env,
53
+ npm_config_cache: npmCacheDir,
54
+ npm_config_audit: 'false',
55
+ npm_config_fund: 'false',
56
+ npm_config_update_notifier: 'false',
57
+ NO_UPDATE_NOTIFIER: '1',
58
+ },
59
+ ...options,
60
+ });
61
+
62
+ if (result.error) {
63
+ throw result.error;
64
+ }
65
+
66
+ if (result.status !== 0) {
67
+ throw new Error(`${command} ${args.join(' ')} exited with status ${result.status}`);
68
+ }
69
+ }
70
+
71
+ // Copies source entries while excluding generated or dependency directories.
72
+ function copyEntry(entry, targetRoot) {
73
+ fs.cpSync(path.join(root, entry), path.join(targetRoot, entry), {
74
+ recursive: true,
75
+ filter: (source) => {
76
+ const relative = path.relative(root, source).replaceAll('\\', '/');
77
+ return ![
78
+ 'artifacts',
79
+ 'node_modules',
80
+ 'src-tauri/target',
81
+ ].some((ignored) => relative === ignored || relative.startsWith(`${ignored}/`));
82
+ },
83
+ });
84
+ }
85
+
86
+ // Adds the CI platform label to release assets so macOS/Windows bundle names do
87
+ // not collide when every matrix artifact is uploaded to the same GitHub Release.
88
+ function labelArtifactName(fileName) {
89
+ if (!artifactLabel) {
90
+ return fileName;
91
+ }
92
+ const parsed = path.parse(fileName);
93
+ return `${parsed.name}-${artifactLabel}${parsed.ext}`;
94
+ }
95
+
96
+ // Places a console-subsystem wrapper next to the Windows GUI executable. It
97
+ // waits for CLI-only operations so PowerShell redraws its prompt on completion.
98
+ function copyWindowsCommandWrapper() {
99
+ if (process.platform !== 'win32') {
100
+ return;
101
+ }
102
+ const wrapper = path.join(root, 'extra', 'windows', 'fpasoterm.cmd');
103
+ const releaseDir = path.join(root, 'src-tauri', 'target', 'release');
104
+ if (!fs.existsSync(wrapper) || !fs.existsSync(path.join(releaseDir, 'fpasoterm.exe'))) {
105
+ return;
106
+ }
107
+ fs.copyFileSync(wrapper, path.join(releaseDir, 'fpasoterm.cmd'));
108
+ fs.copyFileSync(wrapper, path.join(artifactsDir, labelArtifactName(`fpasoterm-${version}-windows-cli.cmd`)));
109
+ }
110
+
111
+ fs.rmSync(artifactsDir, { recursive: true, force: true });
112
+ fs.mkdirSync(artifactsDir, { recursive: true });
113
+
114
+ if (!bundlesOnly) {
115
+ const npmPackageRoot = path.join(artifactsDir, 'package');
116
+ fs.mkdirSync(npmPackageRoot, { recursive: true });
117
+ for (const entry of packageEntries) {
118
+ copyEntry(entry, npmPackageRoot);
119
+ }
120
+ run('tar', ['-czf', `fpasoterm-${version}.tgz`, 'package'], {
121
+ cwd: artifactsDir,
122
+ });
123
+ fs.rmSync(npmPackageRoot, { recursive: true, force: true });
124
+ }
125
+
126
+ if (!sourceOnly) {
127
+ const buildArgs = ['run', 'build'];
128
+ if (process.platform === 'linux') {
129
+ // AppImage bundling is unstable in some ChromeOS/Baguette and CI
130
+ // environments. Build the Linux package formats that are currently
131
+ // reproducible, and keep the source archives platform-independent.
132
+ buildArgs.push('--', '--bundles', 'deb,rpm');
133
+ }
134
+ run('npm', buildArgs);
135
+ copyWindowsCommandWrapper();
136
+
137
+ const bundleDir = path.join(root, 'src-tauri', 'target', 'release', 'bundle');
138
+ if (fs.existsSync(bundleDir)) {
139
+ const copyBundles = (directory) => {
140
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
141
+ const entryPath = path.join(directory, entry.name);
142
+ if (entry.isDirectory()) {
143
+ if (entry.name.endsWith('.app')) {
144
+ const appArchive = `${entry.name.replace(/\.app$/, '')}-${version}-${artifactLabel || process.arch}.app.tar.gz`;
145
+ run('tar', ['-czf', path.join(artifactsDir, appArchive), entry.name], {
146
+ cwd: directory,
147
+ });
148
+ } else {
149
+ copyBundles(entryPath);
150
+ }
151
+ } else if (
152
+ entry.isFile()
153
+ && ['.deb', '.rpm', '.dmg', '.msi', '.exe'].includes(path.extname(entry.name))
154
+ && entry.name.includes(version)
155
+ ) {
156
+ fs.copyFileSync(entryPath, path.join(artifactsDir, labelArtifactName(entry.name)));
157
+ }
158
+ }
159
+ };
160
+ copyBundles(bundleDir);
161
+ }
162
+ }
163
+
164
+ let portableRoot;
165
+ if (!bundlesOnly) {
166
+ const portableName = `fpasoterm-${version}-source-portable`;
167
+ portableRoot = path.join(artifactsDir, portableName);
168
+ fs.mkdirSync(portableRoot, { recursive: true });
169
+
170
+ for (const entry of packageEntries) {
171
+ copyEntry(entry, portableRoot);
172
+ }
173
+
174
+ run('tar', ['-czf', `${portableName}.tar.gz`, portableName], {
175
+ cwd: artifactsDir,
176
+ });
177
+ }
178
+
179
+ if (portableRoot) {
180
+ fs.rmSync(portableRoot, { recursive: true, force: true });
181
+ }
182
+ fs.rmSync(npmCacheDir, { recursive: true, force: true });
183
+ console.log(`Artifacts written to ${artifactsDir}`);