@opentig/cli 0.1.4 → 0.1.5
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.
- package/README.md +73 -17
- package/dist/bin.mjs +2 -2
- package/dist/chunks/{cli-B3AR8LUd.mjs → cli-D_aggpu2.mjs} +232 -57
- package/dist/chunks/service-manager-DkpSHxsP.mjs +394 -0
- package/dist/client/assets/{App-BWpP-n84.js → App-CeDVj5Hh.js} +4 -4
- package/dist/client/assets/{CreatePullRequestDialog-CQKBUxKz.js → CreatePullRequestDialog-DMHY_u8X.js} +2 -2
- package/dist/client/assets/{DiffWorkspace-Dg7YI5fJ.js → DiffWorkspace-CeYQ0YtE.js} +1 -1
- package/dist/client/assets/{EditableFileViewer-CmxzBnAY.js → EditableFileViewer-BhwE-QMO.js} +1 -1
- package/dist/client/assets/{File-CYcpdoXz.js → File-C10_BxOu.js} +1 -1
- package/dist/client/assets/{FilesView-BFCwGLC2.js → FilesView-BT4eHbFo.js} +1 -1
- package/dist/client/assets/{GitHubAvatar-BcncAgKT.js → GitHubAvatar-Ch_PaCv3.js} +1 -1
- package/dist/client/assets/{HistoryView-Dev4CVOY.js → HistoryView-CivGwdxV.js} +1 -1
- package/dist/client/assets/{HtmlFileViewer-jdBVtlNv.js → HtmlFileViewer-Cufzaec9.js} +1 -1
- package/dist/client/assets/{IconEdit-DQZu_Rpb.js → IconEdit-RxH71_V-.js} +1 -1
- package/dist/client/assets/{IconFolderPlus-DvEWPQIQ.js → IconFolderPlus-DSSB-ikf.js} +1 -1
- package/dist/client/assets/{ImageFileViewer-BW1MbklH.js → ImageFileViewer-CFUkcXLH.js} +1 -1
- package/dist/client/assets/{LocalRefsDialog-9ggtgVW_.js → LocalRefsDialog-B2jUCKTx.js} +1 -1
- package/dist/client/assets/{MarkdownFileViewer-Bhrr1l2Q.js → MarkdownFileViewer-BLfJ-Vof.js} +2 -2
- package/dist/client/assets/{PairingPage-CkRMPzli.js → PairingPage-C5Ggk-Z_.js} +1 -1
- package/dist/client/assets/{PierreDiffViewer-CYdNkBG9.js → PierreDiffViewer-DYj1ZThr.js} +1 -1
- package/dist/client/assets/{PierreEditBoundary-Cu19fc5Z.js → PierreEditBoundary-UfvbKDbd.js} +1 -1
- package/dist/client/assets/{PierreWorkerPool-B2iHe3DC.js → PierreWorkerPool-7EVoquIB.js} +1 -1
- package/dist/client/assets/{PullRequestDiff-DZRDxOx9.js → PullRequestDiff-DxLTcm6L.js} +1 -1
- package/dist/client/assets/{PullRequestViewer-Bsh-F3-d.js → PullRequestViewer-CyW39MuF.js} +2 -2
- package/dist/client/assets/{PullRequestsView-Brg-sbGb.js → PullRequestsView-BHcnarLS.js} +1 -1
- package/dist/client/assets/{QuickOpenDialog-CtJqcFuk.js → QuickOpenDialog-CasQU7pk.js} +1 -1
- package/dist/client/assets/{RepositoryProjectsDialog-BIUnKMb-.js → RepositoryProjectsDialog-VAiZrDlA.js} +1 -1
- package/dist/client/assets/{SearchView-C5_R_0ZU.js → SearchView-Bv4bA4UJ.js} +1 -1
- package/dist/client/assets/{SettingsDialog-CyJA7ajO.js → SettingsDialog-D33V9Fry.js} +2 -2
- package/dist/client/assets/{Viewer-xMTNRGDO.js → Viewer-I1lC_oJU.js} +2 -2
- package/dist/client/assets/{abnfDiagram-N423BO3Z-DYcwdF-j.js → abnfDiagram-N423BO3Z-DrlQwm7W.js} +1 -1
- package/dist/client/assets/{arc-CZ6a8_6G.js → arc-B9cXI-QL.js} +1 -1
- package/dist/client/assets/{architectureDiagram-T3A2C74G-CViM1eoQ.js → architectureDiagram-T3A2C74G-DZz7ZXbI.js} +1 -1
- package/dist/client/assets/{blockDiagram-VBNYF7ZC-BzRc4Zb8.js → blockDiagram-VBNYF7ZC-DZFMT63B.js} +1 -1
- package/dist/client/assets/{c4Diagram-5PPSVZJV-BZsdylz9.js → c4Diagram-5PPSVZJV-BnHIZMZp.js} +1 -1
- package/dist/client/assets/channel-CE-pdA-6.js +1 -0
- package/dist/client/assets/{checkbox-iSOOddl1.js → checkbox-BpmQTxn7.js} +1 -1
- package/dist/client/assets/{chunk-2GRJ4B5K-CsL1szop.js → chunk-2GRJ4B5K-CJOlpI9g.js} +1 -1
- package/dist/client/assets/{chunk-2Q5K7J3B-Dpm-BZHi.js → chunk-2Q5K7J3B-DhsnoUrJ.js} +1 -1
- package/dist/client/assets/{chunk-5RXB4S5H-BNNb0lga.js → chunk-5RXB4S5H-Nwsk3OIu.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-CcXWQAK-.js → chunk-5VM5RSS4-Dk9FYU9b.js} +1 -1
- package/dist/client/assets/{chunk-6Q2QTUOP-BIpZKHoj.js → chunk-6Q2QTUOP-C8lX8T87.js} +1 -1
- package/dist/client/assets/{chunk-GF5L2VYU-CXSHzIYB.js → chunk-GF5L2VYU-DWHvTG6c.js} +1 -1
- package/dist/client/assets/{chunk-JWPE2WC7-DxFk0X7_.js → chunk-JWPE2WC7-BLu2-FJl.js} +1 -1
- package/dist/client/assets/{chunk-KBJHAD2P-WRKVeb4h.js → chunk-KBJHAD2P-CEOb10I1.js} +1 -1
- package/dist/client/assets/{chunk-RYQCIY6F-Br5DeHdX.js → chunk-RYQCIY6F-BcdMrb_e.js} +1 -1
- package/dist/client/assets/{chunk-XXDRQBXY-DFCzSljb.js → chunk-XXDRQBXY-CzPimvfs.js} +1 -1
- package/dist/client/assets/classDiagram-JCYQIIEL-2elbt6qi.js +1 -0
- package/dist/client/assets/classDiagram-v2-OCEON4UE-2elbt6qi.js +1 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-CE8E-qIe.js → cose-bilkent-JH36ORCC-BYfzHUkR.js} +1 -1
- package/dist/client/assets/{cynefin-VYW2F7L2-CHf3vLku.js → cynefin-VYW2F7L2-B4mEAleX.js} +1 -1
- package/dist/client/assets/{cynefinDiagram-MW4NZA55-90txNnhw.js → cynefinDiagram-MW4NZA55-CioMvT6V.js} +1 -1
- package/dist/client/assets/{dagre-VZM6K2ZE-6somveWH.js → dagre-VZM6K2ZE-DWPgHi5i.js} +1 -1
- package/dist/client/assets/{diagram-7IWD3JNH-D4GP5bhO.js → diagram-7IWD3JNH-94i9EwB_.js} +1 -1
- package/dist/client/assets/{diagram-B4RE2ZJO-Cz4bx70D.js → diagram-B4RE2ZJO-BRgMOgy8.js} +1 -1
- package/dist/client/assets/{diagram-LBJQPF4R-BXjlhC7t.js → diagram-LBJQPF4R-DHaXTl1T.js} +1 -1
- package/dist/client/assets/{diagram-Q27KOJAE-B_dDT61b.js → diagram-Q27KOJAE-DtLuyYm1.js} +1 -1
- package/dist/client/assets/{diagram-UB23O5K3-BotmyDTm.js → diagram-UB23O5K3-D8R3grCW.js} +1 -1
- package/dist/client/assets/{ebnfDiagram-BXEA7PRR-CBey4stz.js → ebnfDiagram-BXEA7PRR-voe5GU7W.js} +1 -1
- package/dist/client/assets/{erDiagram-JOGREHBK-B2g7aHUP.js → erDiagram-JOGREHBK-BShMJhmY.js} +1 -1
- package/dist/client/assets/{flowDiagram-UKHOOZJN-B181B3KJ.js → flowDiagram-UKHOOZJN-DASXB2wV.js} +1 -1
- package/dist/client/assets/{ganttDiagram-PKOTCBZU-BFslA07w.js → ganttDiagram-PKOTCBZU-Da1ElnJN.js} +1 -1
- package/dist/client/assets/{gh-utils-rGdT2YZN.js → gh-utils-D00lyruw.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-DS77QQ5N-B_uJt_Hp.js → gitGraphDiagram-DS77QQ5N-DoGRli0p.js} +1 -1
- package/dist/client/assets/{index-DaaX_e8C.js → index-BdjG_WtS.js} +2 -2
- package/dist/client/assets/{index-DXR6FOp1.css → index-Uku28hIV.css} +1 -1
- package/dist/client/assets/{infoDiagram-6WML65LV-BuajoWMd.js → infoDiagram-6WML65LV-CxkxF5pn.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-WSZJBQD7-CGRAyQ-L.js → ishikawaDiagram-WSZJBQD7-l6dk2-PS.js} +1 -1
- package/dist/client/assets/{journeyDiagram-NVQOT4AX-D8bgj_4i.js → journeyDiagram-NVQOT4AX-wJY4ZF5o.js} +1 -1
- package/dist/client/assets/{kanban-definition-27J2QSJJ-QrwKHnlk.js → kanban-definition-27J2QSJJ-CIcwUmJo.js} +1 -1
- package/dist/client/assets/{linear-BSIPbUpM.js → linear-Tf-dXwQ9.js} +1 -1
- package/dist/client/assets/{mermaid.core-D4_R4ZQ7.js → mermaid.core-DnaJSnLe.js} +4 -4
- package/dist/client/assets/{mindmap-definition-FAOFIHXS-DTiuF5JK.js → mindmap-definition-FAOFIHXS-jFdJ2bLh.js} +1 -1
- package/dist/client/assets/{pegDiagram-VL7TDLO6-yCIq1xSC.js → pegDiagram-VL7TDLO6-vq0WE66k.js} +1 -1
- package/dist/client/assets/{pieDiagram-7S7Q4E2Y-B_Vgs3pz.js → pieDiagram-7S7Q4E2Y-3edKDMpI.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-CIZ2JOQS-5Ne6-Suh.js → quadrantDiagram-CIZ2JOQS-B6M8U4XV.js} +1 -1
- package/dist/client/assets/{railroadDiagram-AXF67PYL-CRQ23Ne5.js → railroadDiagram-AXF67PYL-C-d5-0YS.js} +1 -1
- package/dist/client/assets/{render-markdown-D7BGt-zy.js → render-markdown-GXIpf9CM.js} +1 -1
- package/dist/client/assets/{requirementDiagram-LRYGKXZP-Ctks1nFE.js → requirementDiagram-LRYGKXZP-FVzmEbFu.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-W5VNT64P-D127Ar8U.js → sankeyDiagram-W5VNT64P-B-qcUamt.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-SI44F4Z6-6hK5AS2n.js → sequenceDiagram-SI44F4Z6-DgFXRLSt.js} +1 -1
- package/dist/client/assets/{sizeCapture-X5ZJPWSS-GhdZlEoa.js → sizeCapture-X5ZJPWSS-CWi_pMb2.js} +1 -1
- package/dist/client/assets/{stateDiagram-OKZ733FA-u3G3OuQX.js → stateDiagram-OKZ733FA-amQwzlgU.js} +1 -1
- package/dist/client/assets/{stateDiagram-v2-UEYNNEHI-BSIBplQO.js → stateDiagram-v2-UEYNNEHI-TdV8eIV2.js} +1 -1
- package/dist/client/assets/{swimlanes-SLNWSIFB-BIfNt9T9.js → swimlanes-SLNWSIFB-L88WlaUb.js} +2 -2
- package/dist/client/assets/swimlanesDiagram-ULZ7WXOC-AWimqvIC.js +8 -0
- package/dist/client/assets/{timeline-definition-Z64GVDOM-CXwiFUZq.js → timeline-definition-Z64GVDOM-D4TJxxc3.js} +1 -1
- package/dist/client/assets/{vennDiagram-T6HMQDX7-D9Rhew2E.js → vennDiagram-T6HMQDX7-ClJWOyzQ.js} +1 -1
- package/dist/client/assets/{viewer-tabs-BW5-uV6M.js → viewer-tabs-Cp_MC_J3.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-T6FBY63Y-CzCcAcBb.js → wardleyDiagram-T6FBY63Y-Cxfjpd6V.js} +1 -1
- package/dist/client/assets/{xychartDiagram-ELKLHX3M-BfXVbrBJ.js → xychartDiagram-ELKLHX3M-BYqbTvmt.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/dev.mjs +4 -4
- package/dist/manifest.json +1 -1
- package/dist/server.mjs +5 -5
- package/dist/service-update.mjs +12 -10
- package/package.json +1 -1
- package/dist/chunks/cli-service-B939lTxw.mjs +0 -237
- package/dist/client/assets/channel-DUwjky93.js +0 -1
- package/dist/client/assets/classDiagram-JCYQIIEL-BtqFLKHp.js +0 -1
- package/dist/client/assets/classDiagram-v2-OCEON4UE-BtqFLKHp.js +0 -1
- package/dist/client/assets/swimlanesDiagram-ULZ7WXOC-DGRBYdxa.js +0 -8
package/README.md
CHANGED
|
@@ -1,37 +1,93 @@
|
|
|
1
1
|
# OpenTig CLI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**A focused workspace for reviewing code changes, in your browser.**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Browse files, inspect diffs, edit, stage, and commit on your computer or a remote server. The CLI serves the same UI as the OpenTig desktop app, without Electron.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
[Source](https://github.com/jorgefl8/opentig) · [Documentation](https://github.com/jorgefl8/opentig/blob/main/docs/README.md) · [Report a bug](https://github.com/jorgefl8/opentig/issues)
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
*The browser UI in the Dev build, reviewing a small demo repository.*
|
|
12
|
+
|
|
13
|
+
> **Early development.** I'm building OpenTig for my own daily workflow and sharing it as it takes shape. Expect bugs, rough edges, and changes. Keep important work backed up and review Git and file operations before confirming them.
|
|
14
|
+
|
|
15
|
+
## Start locally
|
|
16
|
+
|
|
17
|
+
Works on **macOS, Linux, and Windows** with **Node.js 24+** and **Git** installed.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx --yes @opentig/cli@latest
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
This starts OpenTig at `127.0.0.1:6767` and opens a one-use browser pairing link. Choose an existing repository in the visual folder picker and start reviewing. Repositories are managed in the UI; you do not need to start a separate server for each one.
|
|
24
|
+
|
|
25
|
+
Prefer Bun's package runner?
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
bunx @opentig/cli@latest
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Plain `bunx` launches the Node-shebang executable. **Node.js 24+ is still required**; running OpenTig under the Bun runtime itself is not supported.
|
|
32
|
+
|
|
33
|
+
Or install globally:
|
|
10
34
|
|
|
11
35
|
```bash
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
bunx @opentig/cli@0.1.0 serve
|
|
36
|
+
npm install -g @opentig/cli@latest
|
|
37
|
+
opentig
|
|
15
38
|
```
|
|
16
39
|
|
|
17
|
-
|
|
40
|
+
## What you can do
|
|
18
41
|
|
|
19
|
-
|
|
42
|
+
| | |
|
|
43
|
+
| --- | --- |
|
|
44
|
+
| **Review changes** | Split or unified diffs, staged and unstaged files, and merge-conflict resolution. [Details](https://github.com/jorgefl8/opentig/blob/main/docs/features.md#changes-diffs-and-commits) |
|
|
45
|
+
| **Browse and edit** | File tree, tabs, quick edits, find and replace, repository search, and Markdown, image, HTML, and SVG previews. [Details](https://github.com/jorgefl8/opentig/blob/main/docs/features.md#files-and-editing) |
|
|
46
|
+
| **Work with Git** | Stage files, commit, pull and push, inspect history, and switch branches and worktrees. [Details](https://github.com/jorgefl8/opentig/blob/main/docs/features.md#safe-pull-and-push) |
|
|
47
|
+
| **Organize repositories** | Recent repositories, project groups, and manual relocation without moving files. [Details](https://github.com/jorgefl8/opentig/blob/main/docs/features.md#repositories-and-projects) |
|
|
48
|
+
| **Review GitHub PRs** | Browse pull requests, inspect changes, and prepare new PRs through your authenticated `gh` CLI. [Details](https://github.com/jorgefl8/opentig/blob/main/docs/features.md#github-pull-requests) |
|
|
49
|
+
| **Use a remote server** | Pair a browser on your laptop or phone and work with repositories on the server. [Details](https://github.com/jorgefl8/opentig/blob/main/docs/web-access.md) |
|
|
20
50
|
|
|
21
|
-
|
|
51
|
+
**AI is optional.** Normal Git work needs no AI account or OpenTig account. If you choose to use AI assistance, a supported CLI installed and authenticated on the host can draft commit messages and pull-request descriptions for you to review. The selected CLI may send the supplied context to its provider. OpenTig does not run an autonomous coding agent or create commits on its own. See the [feature guide](https://github.com/jorgefl8/opentig/blob/main/docs/features.md) and [privacy details](https://github.com/jorgefl8/opentig/blob/main/docs/security-and-privacy.md).
|
|
52
|
+
|
|
53
|
+
## Run on a remote server
|
|
54
|
+
|
|
55
|
+
Start without opening a browser:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npx --yes @opentig/cli@latest serve
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Server mode is designed for personal access through **Cloudflare Tunnel**, a **private network such as Tailscale**, or an SSH tunnel. The default listener stays on `127.0.0.1:6767`, which a tunnel or HTTPS reverse proxy on the same machine can reach. For direct private-network access, bind to its interface with `--host`; alternatively, keep loopback and use a private HTTPS proxy such as Tailscale Serve. Tunnel and VPN setup are managed separately from OpenTig.
|
|
62
|
+
|
|
63
|
+
Pair your browser with the one-use code printed on the server. The visual folder picker selects **folders on the server**, not on your laptop or phone.
|
|
64
|
+
|
|
65
|
+
After a global installation, run `opentig pair` as the same OS user to generate a fresh five-minute pairing code for the running instance. If you use a custom data directory, pass the same `--home` value.
|
|
66
|
+
|
|
67
|
+
Paired browsers have the host user's filesystem, Git, and configured CLI authority. Keep access private and do not expose the raw HTTP port publicly. See [remote access and pairing](https://github.com/jorgefl8/opentig/blob/main/docs/web-access.md) for SSH and reverse-proxy examples.
|
|
68
|
+
|
|
69
|
+
## Keep a background server running
|
|
70
|
+
|
|
71
|
+
After installing globally, opt into a background service on Linux, macOS, or Windows:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
opentig service install --host 127.0.0.1 --port 6767
|
|
75
|
+
opentig service status
|
|
76
|
+
opentig service restart
|
|
77
|
+
```
|
|
22
78
|
|
|
23
|
-
The
|
|
79
|
+
The service uses a persistent runtime outside the temporary package cache and starts independently of any repository. Running `npx` or `bunx` alone never installs a background service. Use `opentig service uninstall` to remove it. Linux uses systemd with lingering and starts at boot. macOS uses a user LaunchAgent and Windows uses a Task Scheduler task for the current user; these start at login and stop at logout. Keep the machine awake for remote access. This service is independent of the desktop app.
|
|
24
80
|
|
|
25
|
-
|
|
81
|
+
Managed services on all three platforms can **download updates and restart from the browser UI**. Those updates replace the service's runtime, without changing the global npm package. For foreground instances, stop the process and start the desired package version. Pin an exact version instead of `latest` for repeatable deployments. See [installation, services, and updates](https://github.com/jorgefl8/opentig/blob/main/docs/getting-started.md).
|
|
26
82
|
|
|
27
|
-
|
|
83
|
+
## Configuration and scope
|
|
28
84
|
|
|
29
|
-
|
|
85
|
+
Run `opentig --help` for commands and options. Configure `--host`, `--port`, and `--home`, or their `OPENTIG_HOST`, `OPENTIG_PORT`, and `OPENTIG_HOME` environment equivalents. Data is stored under `~/.opentig` by default. An occupied port fails clearly instead of silently changing the address.
|
|
30
86
|
|
|
31
|
-
|
|
87
|
+
OpenTig opens existing repositories. Cloning and initial remote setup remain Git CLI tasks. Built-in TLS, multi-user roles, and automatic SSH or tunnel setup are not included.
|
|
32
88
|
|
|
33
|
-
|
|
89
|
+
The separate [desktop installer](https://github.com/jorgefl8/opentig/releases/latest) is currently available for Windows x64 only. **macOS and Linux users can use this CLI and the browser UI today.**
|
|
34
90
|
|
|
35
91
|
## License
|
|
36
92
|
|
|
37
|
-
MIT. Bundled third-party assets retain their own licences; see
|
|
93
|
+
[MIT](https://github.com/jorgefl8/opentig/blob/main/LICENSE). Bundled third-party assets retain their own licences; see [third-party notices](https://github.com/jorgefl8/opentig/blob/main/THIRD_PARTY_NOTICES.md).
|
package/dist/bin.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import process$1 from "node:process";
|
|
3
|
-
import { p as parseCliArguments, C as CliUsageError, r as runCli } from "./chunks/cli-
|
|
3
|
+
import { p as parseCliArguments, C as CliUsageError, O as OPEN_TIG_APP_VERSION, r as runCli } from "./chunks/cli-D_aggpu2.mjs";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
6
|
import { realpath } from "node:fs/promises";
|
|
7
7
|
import path from "node:path";
|
|
8
|
-
import { r as readInstallation, n as newerVersion, p as packageDirectory
|
|
8
|
+
import { r as readInstallation, n as newerVersion, p as packageDirectory } from "./chunks/service-manager-DkpSHxsP.mjs";
|
|
9
9
|
import "./server.mjs";
|
|
10
10
|
import "node:crypto";
|
|
11
11
|
import "node:string_decoder";
|
|
@@ -3,10 +3,11 @@ import process$1 from "node:process";
|
|
|
3
3
|
import { w as writeFileAtomically, r as redactSensitiveText, n as normalizeRuntimePlatform, a as applicationName, runOpenTigServer } from "../server.mjs";
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { realpath, readFile, mkdir, rm, mkdtemp, writeFile, rename, chmod, appendFile, stat } from "node:fs/promises";
|
|
6
|
+
import { realpath, access, mkdir, rm, readFile, cp, rename, mkdtemp, writeFile, chmod, appendFile, stat } from "node:fs/promises";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
9
8
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
9
|
+
import { e as execute, c as createServiceManager, r as readInstallation, n as newerVersion, a as assertManager, p as packageDirectory, s as saveInstallation, b as atomicWrite, d as stableVersion } from "./service-manager-DkpSHxsP.mjs";
|
|
10
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
10
11
|
var QrCodeDataType = /* @__PURE__ */ ((QrCodeDataType2) => {
|
|
11
12
|
QrCodeDataType2[QrCodeDataType2["Border"] = -1] = "Border";
|
|
12
13
|
QrCodeDataType2[QrCodeDataType2["Data"] = 0] = "Data";
|
|
@@ -65,14 +66,14 @@ class QrCode {
|
|
|
65
66
|
// error correction level, data codeword bytes, and mask number.
|
|
66
67
|
// This is a low-level API that most users should not use directly.
|
|
67
68
|
// A mid-level API is the encodeSegments() function.
|
|
68
|
-
constructor(
|
|
69
|
-
this.version =
|
|
69
|
+
constructor(version2, ecc, dataCodewords, msk) {
|
|
70
|
+
this.version = version2;
|
|
70
71
|
this.ecc = ecc;
|
|
71
|
-
if (
|
|
72
|
+
if (version2 < MIN_VERSION || version2 > MAX_VERSION)
|
|
72
73
|
throw new RangeError("Version value out of range");
|
|
73
74
|
if (msk < -1 || msk > 7)
|
|
74
75
|
throw new RangeError("Mask value out of range");
|
|
75
|
-
this.size =
|
|
76
|
+
this.size = version2 * 4 + 17;
|
|
76
77
|
const row = Array.from({ length: this.size }).fill(false);
|
|
77
78
|
for (let i = 0; i < this.size; i++) {
|
|
78
79
|
this.modules.push(row.slice());
|
|
@@ -490,10 +491,10 @@ function isNumeric(text) {
|
|
|
490
491
|
function isAlphanumeric(text) {
|
|
491
492
|
return ALPHANUMERIC_REGEX.test(text);
|
|
492
493
|
}
|
|
493
|
-
function getTotalBits(segs,
|
|
494
|
+
function getTotalBits(segs, version2) {
|
|
494
495
|
let result = 0;
|
|
495
496
|
for (const seg of segs) {
|
|
496
|
-
const ccbits = numCharCountBits(seg.mode,
|
|
497
|
+
const ccbits = numCharCountBits(seg.mode, version2);
|
|
497
498
|
if (seg.numChars >= 1 << ccbits)
|
|
498
499
|
return Number.POSITIVE_INFINITY;
|
|
499
500
|
result += 4 + ccbits + seg.bitData.length;
|
|
@@ -569,37 +570,37 @@ function encodeSegments(segs, ecl, minVersion = 1, maxVersion = 40, mask = -1, b
|
|
|
569
570
|
if (!(MIN_VERSION <= minVersion && minVersion <= maxVersion && maxVersion <= MAX_VERSION) || mask < -1 || mask > 7) {
|
|
570
571
|
throw new RangeError("Invalid value");
|
|
571
572
|
}
|
|
572
|
-
let
|
|
573
|
+
let version2;
|
|
573
574
|
let dataUsedBits;
|
|
574
|
-
for (
|
|
575
|
-
const dataCapacityBits2 = getNumDataCodewords(
|
|
576
|
-
const usedBits = getTotalBits(segs,
|
|
575
|
+
for (version2 = minVersion; ; version2++) {
|
|
576
|
+
const dataCapacityBits2 = getNumDataCodewords(version2, ecl) * 8;
|
|
577
|
+
const usedBits = getTotalBits(segs, version2);
|
|
577
578
|
if (usedBits <= dataCapacityBits2) {
|
|
578
579
|
dataUsedBits = usedBits;
|
|
579
580
|
break;
|
|
580
581
|
}
|
|
581
|
-
if (
|
|
582
|
+
if (version2 >= maxVersion)
|
|
582
583
|
throw new RangeError("Data too long");
|
|
583
584
|
}
|
|
584
585
|
for (const newEcl of [MEDIUM, QUARTILE, HIGH]) {
|
|
585
|
-
if (boostEcl && dataUsedBits <= getNumDataCodewords(
|
|
586
|
+
if (boostEcl && dataUsedBits <= getNumDataCodewords(version2, newEcl) * 8)
|
|
586
587
|
ecl = newEcl;
|
|
587
588
|
}
|
|
588
589
|
const bb = [];
|
|
589
590
|
for (const seg of segs) {
|
|
590
591
|
appendBits(seg.mode[0], 4, bb);
|
|
591
|
-
appendBits(seg.numChars, numCharCountBits(seg.mode,
|
|
592
|
+
appendBits(seg.numChars, numCharCountBits(seg.mode, version2), bb);
|
|
592
593
|
for (const b of seg.getData())
|
|
593
594
|
bb.push(b);
|
|
594
595
|
}
|
|
595
|
-
const dataCapacityBits = getNumDataCodewords(
|
|
596
|
+
const dataCapacityBits = getNumDataCodewords(version2, ecl) * 8;
|
|
596
597
|
appendBits(0, Math.min(4, dataCapacityBits - bb.length), bb);
|
|
597
598
|
appendBits(0, (8 - bb.length % 8) % 8, bb);
|
|
598
599
|
for (let padByte = 236; bb.length < dataCapacityBits; padByte ^= 236 ^ 17)
|
|
599
600
|
appendBits(padByte, 8, bb);
|
|
600
601
|
const dataCodewords = Array.from({ length: Math.ceil(bb.length / 8) }, () => 0);
|
|
601
602
|
bb.forEach((b, i) => dataCodewords[i >>> 3] |= b << 7 - (i & 7));
|
|
602
|
-
return new QrCode(
|
|
603
|
+
return new QrCode(version2, ecl, dataCodewords, mask);
|
|
603
604
|
}
|
|
604
605
|
function encode(data, options) {
|
|
605
606
|
const {
|
|
@@ -780,7 +781,7 @@ function parseCliArguments(args, environment = process.env, currentDirectory = p
|
|
|
780
781
|
if (command === "pair" && (hostValue !== void 0 || portValue !== void 0 || noBrowser)) {
|
|
781
782
|
throw new CliUsageError("The pair command only accepts --home.");
|
|
782
783
|
}
|
|
783
|
-
if (command === "service" && serviceAction === null) throw new CliUsageError("The service command requires install, status, or uninstall.");
|
|
784
|
+
if (command === "service" && serviceAction === null) throw new CliUsageError("The service command requires install, status, restart, or uninstall.");
|
|
784
785
|
if (command === "service" && serviceAction !== "install" && (hostValue !== void 0 || portValue !== void 0 || noBrowser)) {
|
|
785
786
|
throw new CliUsageError(`The service ${serviceAction} command only accepts --home.`);
|
|
786
787
|
}
|
|
@@ -807,15 +808,15 @@ function parseCliArguments(args, environment = process.env, currentDirectory = p
|
|
|
807
808
|
openBrowser: command === "start" && !noBrowser
|
|
808
809
|
};
|
|
809
810
|
}
|
|
810
|
-
function cliHelp(
|
|
811
|
-
return `OpenTig ${
|
|
811
|
+
function cliHelp(version2) {
|
|
812
|
+
return `OpenTig ${version2}
|
|
812
813
|
|
|
813
814
|
Usage:
|
|
814
815
|
opentig [options]
|
|
815
816
|
opentig start [options]
|
|
816
817
|
opentig serve [options]
|
|
817
818
|
opentig pair [--home <path>]
|
|
818
|
-
opentig service <install|status|uninstall> [options]
|
|
819
|
+
opentig service <install|status|restart|uninstall> [options]
|
|
819
820
|
opentig help
|
|
820
821
|
opentig version
|
|
821
822
|
|
|
@@ -823,7 +824,7 @@ Commands:
|
|
|
823
824
|
start Start OpenTig and open the one-use pairing link (default)
|
|
824
825
|
serve Start OpenTig without opening a browser; ideal for servers
|
|
825
826
|
pair Print a fresh five-minute code/link for a running server
|
|
826
|
-
service
|
|
827
|
+
service Manage a background server (Linux, macOS, Windows)
|
|
827
828
|
help Show this help
|
|
828
829
|
version Show the OpenTig version
|
|
829
830
|
|
|
@@ -858,7 +859,8 @@ Runtime behavior:
|
|
|
858
859
|
The configured port is never changed silently. If it is occupied, OpenTig
|
|
859
860
|
exits and asks you to stop that process or choose another --port. SIGINT and
|
|
860
861
|
SIGTERM shut the server down cleanly. npx and bunx never install a service;
|
|
861
|
-
service installation is
|
|
862
|
+
service installation is explicit. Linux uses systemd with lingering; macOS
|
|
863
|
+
and Windows start at login and stop at logout.
|
|
862
864
|
|
|
863
865
|
Requirements: Node.js 24 or later and Git. Plain bunx launches the Node shebang;
|
|
864
866
|
running OpenTig with the Bun runtime itself is not supported.`;
|
|
@@ -881,7 +883,7 @@ function isCommand(value) {
|
|
|
881
883
|
return value === "start" || value === "serve" || value === "pair" || value === "service" || value === "help" || value === "version";
|
|
882
884
|
}
|
|
883
885
|
function isServiceAction(value) {
|
|
884
|
-
return value === "install" || value === "status" || value === "uninstall";
|
|
886
|
+
return value === "install" || value === "status" || value === "restart" || value === "uninstall";
|
|
885
887
|
}
|
|
886
888
|
function validateHost(value) {
|
|
887
889
|
const normalized = value.trim();
|
|
@@ -898,22 +900,197 @@ function resolvePath(value, currentDirectory, label) {
|
|
|
898
900
|
if (!value || value.includes("\0")) throw new CliUsageError(`Invalid ${label}.`);
|
|
899
901
|
return path.resolve(currentDirectory, value);
|
|
900
902
|
}
|
|
903
|
+
const version = "0.1.5";
|
|
904
|
+
const packageMetadata = {
|
|
905
|
+
version
|
|
906
|
+
};
|
|
907
|
+
const OPEN_TIG_APP_VERSION = packageMetadata.version;
|
|
908
|
+
async function runServiceNpm(args, cwd) {
|
|
909
|
+
const candidates = [
|
|
910
|
+
process.env.npm_execpath,
|
|
911
|
+
path.join(path.dirname(process.execPath), "node_modules/npm/bin/npm-cli.js"),
|
|
912
|
+
path.resolve(path.dirname(process.execPath), "../lib/node_modules/npm/bin/npm-cli.js")
|
|
913
|
+
];
|
|
914
|
+
for (const directory of (process.env.PATH ?? "").split(path.delimiter)) {
|
|
915
|
+
if (!directory) continue;
|
|
916
|
+
candidates.push(path.join(directory, "node_modules/npm/bin/npm-cli.js"));
|
|
917
|
+
if (process.platform !== "win32") {
|
|
918
|
+
try {
|
|
919
|
+
candidates.push(await realpath(path.join(directory, "npm")));
|
|
920
|
+
} catch {
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
for (const candidate of candidates) {
|
|
925
|
+
if (!candidate || path.basename(candidate) !== "npm-cli.js") continue;
|
|
926
|
+
try {
|
|
927
|
+
await access(candidate);
|
|
928
|
+
} catch {
|
|
929
|
+
continue;
|
|
930
|
+
}
|
|
931
|
+
await execute(process.execPath, [candidate, ...args], { cwd, timeout: 18e4, maxBuffer: 2e6, windowsHide: true });
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
throw new Error("npm could not be located. Install Node.js with npm and run service install from that terminal.");
|
|
935
|
+
}
|
|
936
|
+
async function manageCliService(config, io, managerOverride) {
|
|
937
|
+
const manager = await createServiceManager(config.home);
|
|
938
|
+
const action = config.serviceAction;
|
|
939
|
+
if (!action) throw new Error("A service action is required.");
|
|
940
|
+
const installed = await readInstallation(config.home);
|
|
941
|
+
if (action !== "status" && installed && newerVersion(installed.version, OPEN_TIG_APP_VERSION)) throw new Error("A newer managed service is installed. Use its CLI to administer it; downgrades are not automatic.");
|
|
942
|
+
const definition = await manager.read();
|
|
943
|
+
if (definition && !manager.owns(definition)) throw new Error("Another OpenTig home owns this service. Use its --home before changing or removing it.");
|
|
944
|
+
if (installed) assertManager(installed, manager.kind);
|
|
945
|
+
if (action === "status") {
|
|
946
|
+
if (!definition) {
|
|
947
|
+
io.out("OpenTig service is not installed.");
|
|
948
|
+
return 1;
|
|
949
|
+
}
|
|
950
|
+
const state = await manager.status();
|
|
951
|
+
io.out(`OpenTig service: ${state.running ? "running" : "stopped"} (${state.enabled ? "enabled" : "disabled"}).`);
|
|
952
|
+
io.out(manager.location);
|
|
953
|
+
io.out(manager.startup);
|
|
954
|
+
return state.running && state.enabled ? 0 : 1;
|
|
955
|
+
}
|
|
956
|
+
if (action === "uninstall" && !definition && !installed) {
|
|
957
|
+
io.out("OpenTig service is not installed.");
|
|
958
|
+
return 0;
|
|
959
|
+
}
|
|
960
|
+
await mkdir(path.join(config.home, "service"), { recursive: true, mode: 448 });
|
|
961
|
+
const lock = path.join(config.home, "service/update-lock");
|
|
962
|
+
try {
|
|
963
|
+
await mkdir(lock, { mode: 448 });
|
|
964
|
+
} catch (error) {
|
|
965
|
+
if (error.code === "EEXIST") throw new Error("A service installation or update is already in progress.", { cause: error });
|
|
966
|
+
throw error;
|
|
967
|
+
}
|
|
968
|
+
try {
|
|
969
|
+
if (action === "restart") {
|
|
970
|
+
if (!definition || !installed) throw new Error("OpenTig service is not installed.");
|
|
971
|
+
await manager.restart();
|
|
972
|
+
await waitServiceReady(config.home, installed, manager);
|
|
973
|
+
io.out("OpenTig service restarted.");
|
|
974
|
+
return 0;
|
|
975
|
+
}
|
|
976
|
+
if (action === "uninstall") {
|
|
977
|
+
if (definition) {
|
|
978
|
+
await manager.stop();
|
|
979
|
+
await manager.remove();
|
|
980
|
+
}
|
|
981
|
+
await rm(path.join(config.home, "service"), { recursive: true, force: true });
|
|
982
|
+
io.out("OpenTig service removed. Repository settings and paired sessions were kept.");
|
|
983
|
+
return 0;
|
|
984
|
+
}
|
|
985
|
+
await manager.preflight();
|
|
986
|
+
return await installService(config, manager, installed, definition, io);
|
|
987
|
+
} finally {
|
|
988
|
+
await rm(lock, { recursive: true, force: true });
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
async function installService(config, manager, installed, previousDefinition, io) {
|
|
992
|
+
const packageRoot = await findPackageRoot(fileURLToPath(import.meta.url));
|
|
993
|
+
await assertPackageBuild(packageRoot);
|
|
994
|
+
const serviceRoot = path.join(config.home, "service");
|
|
995
|
+
const reuse = installed?.version === OPEN_TIG_APP_VERSION;
|
|
996
|
+
const target = reuse ? packageDirectory(config.home, installed) : path.join(serviceRoot, `app-${OPEN_TIG_APP_VERSION}`);
|
|
997
|
+
const temporary = path.join(serviceRoot, `.install-${process.pid}-${Date.now()}`);
|
|
998
|
+
try {
|
|
999
|
+
if (!reuse) {
|
|
1000
|
+
await mkdir(temporary, { recursive: true, mode: 448 });
|
|
1001
|
+
await cp(path.join(packageRoot, "dist"), path.join(temporary, "dist"), { recursive: true });
|
|
1002
|
+
for (const name of ["package.json", "README.md", "LICENSE", "THIRD_PARTY_NOTICES.md"]) await cp(path.join(packageRoot, name), path.join(temporary, name));
|
|
1003
|
+
await runServiceNpm(["install", "--omit=dev", "--ignore-scripts", "--no-audit", "--no-fund"], temporary);
|
|
1004
|
+
await rm(target, { recursive: true, force: true });
|
|
1005
|
+
await rename(temporary, target);
|
|
1006
|
+
}
|
|
1007
|
+
} finally {
|
|
1008
|
+
await rm(temporary, { recursive: true, force: true });
|
|
1009
|
+
}
|
|
1010
|
+
await assertPackageBuild(target);
|
|
1011
|
+
const next = { schema: 1, manager: manager.kind, version: OPEN_TIG_APP_VERSION, layout: reuse ? installed.layout : "flat", host: config.host, port: config.port, node: process.execPath, environmentPath: process.env.PATH };
|
|
1012
|
+
const definition = manager.render({ nodeExecutable: next.node, cliEntrypoint: path.join(target, "dist/bin.mjs"), home: config.home, host: next.host, port: next.port, environmentPath: next.environmentPath });
|
|
1013
|
+
try {
|
|
1014
|
+
await manager.write(definition);
|
|
1015
|
+
await saveInstallation(config.home, next);
|
|
1016
|
+
await manager.restart();
|
|
1017
|
+
await waitServiceReady(config.home, next, manager);
|
|
1018
|
+
} catch (error) {
|
|
1019
|
+
if (previousDefinition) {
|
|
1020
|
+
await manager.write(previousDefinition);
|
|
1021
|
+
if (installed) await saveInstallation(config.home, installed);
|
|
1022
|
+
else await rm(path.join(serviceRoot, "installation.json"), { force: true });
|
|
1023
|
+
await manager.restart();
|
|
1024
|
+
} else {
|
|
1025
|
+
await manager.stop();
|
|
1026
|
+
await manager.remove();
|
|
1027
|
+
await rm(path.join(serviceRoot, "installation.json"), { force: true });
|
|
1028
|
+
}
|
|
1029
|
+
throw error;
|
|
1030
|
+
}
|
|
1031
|
+
io.out(`OpenTig service installed and started on ${config.host}:${config.port}.`);
|
|
1032
|
+
io.out(manager.location);
|
|
1033
|
+
io.out(manager.startup);
|
|
1034
|
+
io.out(`Pair another browser with: opentig pair --home ${JSON.stringify(config.home)}`);
|
|
1035
|
+
return 0;
|
|
1036
|
+
}
|
|
1037
|
+
async function waitServiceReady(home, installation, manager) {
|
|
1038
|
+
const host = ["0.0.0.0", "::"].includes(installation.host) ? installation.host === "::" ? "[::1]" : "127.0.0.1" : installation.host.includes(":") ? `[${installation.host}]` : installation.host;
|
|
1039
|
+
for (let i = 0; i < 30; i++) {
|
|
1040
|
+
try {
|
|
1041
|
+
const state = await manager.status();
|
|
1042
|
+
const runtime = JSON.parse(await readFile(path.join(home, "runtime.json"), "utf8"));
|
|
1043
|
+
const response = await fetch(`http://${host}:${installation.port}/readyz`, { signal: AbortSignal.timeout(1e3) });
|
|
1044
|
+
const value = await response.json();
|
|
1045
|
+
if (state.running && (!state.pid || state.pid === runtime.pid) && runtime.appVersion === installation.version && response.ok && value.status === "ready" && value.appVersion === installation.version) return;
|
|
1046
|
+
} catch {
|
|
1047
|
+
}
|
|
1048
|
+
await setTimeout$1(1e3);
|
|
1049
|
+
}
|
|
1050
|
+
throw new Error("The managed service did not become ready. Inspect service status and logs; check Git, Node.js, and the configured port.");
|
|
1051
|
+
}
|
|
1052
|
+
async function assertPackageBuild(packageRoot) {
|
|
1053
|
+
await Promise.all([
|
|
1054
|
+
access(path.join(packageRoot, "dist", "bin.mjs")),
|
|
1055
|
+
access(path.join(packageRoot, "dist", "client", "index.html")),
|
|
1056
|
+
readFile(path.join(packageRoot, "package.json"), "utf8")
|
|
1057
|
+
]).catch(() => {
|
|
1058
|
+
throw new Error("The OpenTig package is incomplete and cannot be installed as a service.");
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
async function findPackageRoot(modulePath) {
|
|
1062
|
+
let directory = path.dirname(modulePath);
|
|
1063
|
+
for (let depth = 0; depth < 4; depth += 1) {
|
|
1064
|
+
try {
|
|
1065
|
+
await Promise.all([
|
|
1066
|
+
access(path.join(directory, "package.json")),
|
|
1067
|
+
access(path.join(directory, "dist", "bin.mjs"))
|
|
1068
|
+
]);
|
|
1069
|
+
return directory;
|
|
1070
|
+
} catch {
|
|
1071
|
+
}
|
|
1072
|
+
const parent = path.dirname(directory);
|
|
1073
|
+
if (parent === directory) break;
|
|
1074
|
+
directory = parent;
|
|
1075
|
+
}
|
|
1076
|
+
throw new Error("Could not locate the installed OpenTig package.");
|
|
1077
|
+
}
|
|
901
1078
|
const DESKTOP_UPDATE_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
|
|
902
1079
|
const REPOSITORY = "jorgefl8/opentig";
|
|
903
1080
|
const REGISTRY = "https://registry.npmjs.org";
|
|
904
1081
|
const MAX_PACKAGE_BYTES = 100 * 1024 * 1024;
|
|
905
1082
|
class ServiceUpdater {
|
|
906
|
-
constructor(
|
|
1083
|
+
constructor(version2, backend, failedUpdate = false) {
|
|
907
1084
|
this.backend = backend;
|
|
908
1085
|
this.status = {
|
|
909
1086
|
phase: backend ? failedUpdate ? "error" : "idle" : "unavailable",
|
|
910
|
-
currentVersion:
|
|
1087
|
+
currentVersion: version2,
|
|
911
1088
|
availableVersion: null,
|
|
912
1089
|
progress: null,
|
|
913
1090
|
checkedAt: null,
|
|
914
1091
|
releaseUrl: null,
|
|
915
1092
|
releaseNotes: null,
|
|
916
|
-
message: backend ? failedUpdate ? "The new server could not start. The previous version was restored. Check for updates to retry." : null : "Browser updates require
|
|
1093
|
+
message: backend ? failedUpdate ? "The new server could not start. The previous version was restored. Check for updates to retry." : null : "Browser updates require a managed OpenTig CLI service. Install with opentig service install; temporary CLI and Dev instances are updated from the terminal."
|
|
917
1094
|
};
|
|
918
1095
|
}
|
|
919
1096
|
backend;
|
|
@@ -992,18 +1169,18 @@ async function json(url) {
|
|
|
992
1169
|
async function latestCliRelease() {
|
|
993
1170
|
const release = await json(`https://api.github.com/repos/${REPOSITORY}/releases/latest`);
|
|
994
1171
|
if (!release || release.draft || release.prerelease || typeof release.tag_name !== "string") return null;
|
|
995
|
-
const
|
|
996
|
-
if (!stableVersion(
|
|
997
|
-
const pkg = await json(`${REGISTRY}/@opentig%2fcli/${
|
|
1172
|
+
const version2 = release.tag_name.replace(/^v/, "");
|
|
1173
|
+
if (!stableVersion(version2) || release.tag_name !== `v${version2}`) return null;
|
|
1174
|
+
const pkg = await json(`${REGISTRY}/@opentig%2fcli/${version2}`);
|
|
998
1175
|
if (!pkg) return null;
|
|
999
1176
|
const dist = pkg.dist;
|
|
1000
|
-
const tarball = `${REGISTRY}/@opentig/cli/-/cli-${
|
|
1001
|
-
if (pkg.name !== "@opentig/cli" || pkg.version !==
|
|
1177
|
+
const tarball = `${REGISTRY}/@opentig/cli/-/cli-${version2}.tgz`;
|
|
1178
|
+
if (pkg.name !== "@opentig/cli" || pkg.version !== version2 || dist?.tarball !== tarball || typeof dist.integrity !== "string" || !/^sha512-[A-Za-z0-9+/]{86}==$/.test(dist.integrity)) throw new Error("Invalid CLI release metadata.");
|
|
1002
1179
|
return {
|
|
1003
|
-
version,
|
|
1180
|
+
version: version2,
|
|
1004
1181
|
tarball,
|
|
1005
1182
|
integrity: dist.integrity,
|
|
1006
|
-
url: `https://github.com/${REPOSITORY}/releases/tag/v${
|
|
1183
|
+
url: `https://github.com/${REPOSITORY}/releases/tag/v${version2}`,
|
|
1007
1184
|
notes: typeof release.body === "string" ? release.body.slice(0, 64e3) : null
|
|
1008
1185
|
};
|
|
1009
1186
|
}
|
|
@@ -1041,7 +1218,7 @@ async function stageCliRelease(home, release, progress) {
|
|
|
1041
1218
|
await writeFile(tarball, bytes, { mode: 384 });
|
|
1042
1219
|
const target = path.join(temporary, "app");
|
|
1043
1220
|
await mkdir(target);
|
|
1044
|
-
await
|
|
1221
|
+
await runServiceNpm(["install", "--prefix", target, "--ignore-scripts", "--omit=dev", "--no-audit", "--no-fund", "--registry", REGISTRY, tarball]);
|
|
1045
1222
|
const pkgRoot = path.join(target, "node_modules/@opentig/cli");
|
|
1046
1223
|
const pkg = JSON.parse(await readFile(path.join(pkgRoot, "package.json"), "utf8"));
|
|
1047
1224
|
if (pkg.name !== "@opentig/cli" || pkg.version !== release.version) throw new Error("Installed CLI identity mismatch.");
|
|
@@ -1060,53 +1237,50 @@ async function stageCliRelease(home, release, progress) {
|
|
|
1060
1237
|
await rm(temporary, { recursive: true, force: true });
|
|
1061
1238
|
}
|
|
1062
1239
|
}
|
|
1063
|
-
async function createServiceUpdater(home,
|
|
1240
|
+
async function createServiceUpdater(home, version2, profile, address) {
|
|
1064
1241
|
let installation = null;
|
|
1242
|
+
let manager = null;
|
|
1065
1243
|
try {
|
|
1066
|
-
if (profile === "production"
|
|
1244
|
+
if (profile === "production") {
|
|
1245
|
+
manager = await createServiceManager(home);
|
|
1067
1246
|
installation = await readInstallation(home);
|
|
1068
|
-
if (
|
|
1247
|
+
if (installation) assertManager(installation, manager.kind);
|
|
1248
|
+
const definition = await manager.read();
|
|
1249
|
+
if (!installation || !definition || !manager.owns(definition) || installation.version !== version2 || installation.host !== address.host || installation.port !== address.port || await realpath(process.argv[1]) !== await realpath(path.join(packageDirectory(home, installation), "dist/bin.mjs")) || !await manager.ownsProcess()) installation = null;
|
|
1069
1250
|
}
|
|
1070
1251
|
} catch {
|
|
1071
1252
|
installation = null;
|
|
1072
1253
|
}
|
|
1073
|
-
if (!installation) return new ServiceUpdater(
|
|
1254
|
+
if (!installation || !manager) return new ServiceUpdater(version2, null);
|
|
1255
|
+
const supervisor = manager;
|
|
1074
1256
|
const current = installation;
|
|
1075
1257
|
let failedUpdate = false;
|
|
1076
1258
|
try {
|
|
1077
1259
|
const result = JSON.parse(await readFile(path.join(home, "service/update-result.json"), "utf8"));
|
|
1078
|
-
failedUpdate = result.success === false && result.version ===
|
|
1260
|
+
failedUpdate = result.success === false && result.version === version2;
|
|
1079
1261
|
} catch {
|
|
1080
1262
|
}
|
|
1081
|
-
const updater = new ServiceUpdater(
|
|
1263
|
+
const updater = new ServiceUpdater(version2, {
|
|
1082
1264
|
release: latestCliRelease,
|
|
1083
1265
|
stage: (release, progress) => stageCliRelease(home, release, progress),
|
|
1084
1266
|
install: async (release) => {
|
|
1085
1267
|
const lock = path.join(home, "service/update-lock");
|
|
1086
1268
|
await mkdir(lock, { mode: 448 });
|
|
1087
|
-
|
|
1269
|
+
let worker;
|
|
1088
1270
|
try {
|
|
1089
|
-
const next = { ...current, version: release.version, layout: "npm" };
|
|
1090
|
-
const previousUnit = await
|
|
1271
|
+
const next = { ...current, manager: supervisor.kind, version: release.version, layout: "npm", environmentPath: process.env.PATH ?? current.environmentPath };
|
|
1272
|
+
const previousUnit = await supervisor.read();
|
|
1273
|
+
if (!previousUnit || !supervisor.owns(previousUnit)) throw new Error("Managed service definition changed.");
|
|
1091
1274
|
await atomicWrite(path.join(lock, "plan.json"), JSON.stringify({ home, current, next, previousUnit, integrity: release.integrity }));
|
|
1092
|
-
await
|
|
1093
|
-
"--user",
|
|
1094
|
-
"--collect",
|
|
1095
|
-
"--unit",
|
|
1096
|
-
unit,
|
|
1097
|
-
"--property=Type=exec",
|
|
1098
|
-
process.execPath,
|
|
1099
|
-
path.join(packageDirectory(home, current), "dist/service-update.mjs"),
|
|
1100
|
-
home
|
|
1101
|
-
], { timeout: 15e3 });
|
|
1275
|
+
worker = await supervisor.launchWorker(process.execPath, path.join(packageDirectory(home, current), "dist/service-update.mjs"));
|
|
1102
1276
|
} catch (error) {
|
|
1103
1277
|
await rm(lock, { recursive: true, force: true });
|
|
1104
1278
|
throw error;
|
|
1105
1279
|
}
|
|
1106
1280
|
for (let attempt = 0; attempt < 120; attempt++) {
|
|
1107
1281
|
await setTimeout$1(1e3);
|
|
1108
|
-
|
|
1109
|
-
|
|
1282
|
+
if (!await worker.running()) {
|
|
1283
|
+
await worker.cleanup();
|
|
1110
1284
|
await rm(lock, { recursive: true, force: true });
|
|
1111
1285
|
throw new Error("Update worker exited without replacing the server.");
|
|
1112
1286
|
}
|
|
@@ -1466,6 +1640,7 @@ function assertGitAvailable() {
|
|
|
1466
1640
|
}
|
|
1467
1641
|
export {
|
|
1468
1642
|
CliUsageError as C,
|
|
1643
|
+
OPEN_TIG_APP_VERSION as O,
|
|
1469
1644
|
parseCliArguments as p,
|
|
1470
1645
|
runCli as r
|
|
1471
1646
|
};
|