phio 0.3.5 → 0.4.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.
- package/CHANGELOG.md +141 -0
- package/README.md +44 -35
- package/package.json +32 -22
- package/src/cli.ts +9 -4
- package/src/commands/DevCommand.ts +17 -6
- package/src/commands/InfoCommand.ts +27 -4
- package/src/commands/LinkCommand.ts +1 -1
- package/src/commands/LogsCommand.ts +14 -11
- package/src/commands/SftpCommand.ts +99 -0
- package/src/lib/constants.ts +2 -0
- package/src/lib/defaultInstanceId.ts +58 -36
- package/src/lib/deployKey.ts +222 -0
- package/src/lib/fetchEventSource.ts +3 -0
- package/src/lib/getClient.ts +30 -2
- package/src/lib/phioRoot.ts +67 -0
- package/src/lib/sftpConnection.ts +33 -0
- package/src/lib/sshPublicKey.ts +128 -0
- package/vendor/ftp-deploy/HashDiff.ts +122 -0
- package/vendor/ftp-deploy/LICENSE +21 -0
- package/vendor/ftp-deploy/README.md +3 -0
- package/vendor/ftp-deploy/deploy.ts +226 -0
- package/vendor/ftp-deploy/errorHandling.ts +67 -0
- package/vendor/ftp-deploy/localFiles.ts +47 -0
- package/vendor/ftp-deploy/module.ts +28 -0
- package/vendor/ftp-deploy/sftpDeploy.ts +233 -0
- package/vendor/ftp-deploy/sftpSyncProvider.ts +188 -0
- package/vendor/ftp-deploy/sshPrivateKey.ts +66 -0
- package/vendor/ftp-deploy/syncProvider.ts +189 -0
- package/vendor/ftp-deploy/types.ts +226 -0
- package/vendor/ftp-deploy/utilities.ts +217 -0
- package/src/commands/WhoAmICommand.ts +0 -15
- package/src/global.d.ts +0 -3
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# phio
|
|
2
|
+
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- SFTP deploy and dev sync (`ftp.pockethost.io:2222`, Ed25519 key auth)
|
|
8
|
+
- `phio sftp` — interactive SFTP via system client (`--print` for the command line)
|
|
9
|
+
- Auto-provision deploy key labeled `Phio` under Account → Keys
|
|
10
|
+
- Project instance linking via `.phioconfig` (migrates legacy `package.json` / `pockethost.json`)
|
|
11
|
+
- Walk up to project root from subdirectories before sync
|
|
12
|
+
|
|
13
|
+
## 0.3.5
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- Fix: add support for bun.lock
|
|
18
|
+
|
|
19
|
+
## 0.3.4
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
- 24988ee: Feat: logout command
|
|
24
|
+
- de7168b: Fix: various auth fixes
|
|
25
|
+
- 6262058: Fix: use correct token for log watching
|
|
26
|
+
- 5d28256: Enhance logs command with improved streaming and error handling
|
|
27
|
+
|
|
28
|
+
## 0.3.3
|
|
29
|
+
|
|
30
|
+
### Patch Changes
|
|
31
|
+
|
|
32
|
+
- 4bf6d1e: Fix: use client auth token instead of stored token
|
|
33
|
+
|
|
34
|
+
## 0.3.2
|
|
35
|
+
|
|
36
|
+
### Patch Changes
|
|
37
|
+
|
|
38
|
+
- b85e156: Fix: nodev22 compat
|
|
39
|
+
|
|
40
|
+
## 0.3.1
|
|
41
|
+
|
|
42
|
+
### Patch Changes
|
|
43
|
+
|
|
44
|
+
- fd128bc: Node v22 compat
|
|
45
|
+
|
|
46
|
+
## 0.3.0
|
|
47
|
+
|
|
48
|
+
### Minor Changes
|
|
49
|
+
|
|
50
|
+
- 82b4c6b: Add support for PHIO_USERNAME, PHIO_PASSWORD, and PHIO_INSTANCE_NAME env vars
|
|
51
|
+
- 82b4c6b: Migrate to instance names only (no IDs)
|
|
52
|
+
- 82b4c6b: Remove support for global instance name - local package.json or pockethost.json required now
|
|
53
|
+
|
|
54
|
+
### Patch Changes
|
|
55
|
+
|
|
56
|
+
- 1b46e08: Alphabetize instance lists
|
|
57
|
+
- 82b4c6b: Improved error message reporting
|
|
58
|
+
|
|
59
|
+
## 0.2.5
|
|
60
|
+
|
|
61
|
+
### Patch Changes
|
|
62
|
+
|
|
63
|
+
- 283da4e: Add 'info' command
|
|
64
|
+
- b17fca2: Add error handling when 'link' returns no instances
|
|
65
|
+
- 8aaa611: Add 'ls' alias
|
|
66
|
+
|
|
67
|
+
## 0.2.4
|
|
68
|
+
|
|
69
|
+
### Patch Changes
|
|
70
|
+
|
|
71
|
+
- Fix: --include and --exclude now parse comma separated values correctly
|
|
72
|
+
|
|
73
|
+
## 0.2.3
|
|
74
|
+
|
|
75
|
+
### Patch Changes
|
|
76
|
+
|
|
77
|
+
- Fix: bad include path
|
|
78
|
+
|
|
79
|
+
## 0.2.2
|
|
80
|
+
|
|
81
|
+
### Patch Changes
|
|
82
|
+
|
|
83
|
+
- 467c4ba: Enh: Validate auth before tailing instance logs
|
|
84
|
+
- 4e8ddb5: Fix: auth token now stored correctly after refresh
|
|
85
|
+
|
|
86
|
+
## 0.2.1
|
|
87
|
+
|
|
88
|
+
### Patch Changes
|
|
89
|
+
|
|
90
|
+
- Fix: Link now uses package.json
|
|
91
|
+
|
|
92
|
+
## 0.2.0
|
|
93
|
+
|
|
94
|
+
### Minor Changes
|
|
95
|
+
|
|
96
|
+
- Introduced deploy command
|
|
97
|
+
|
|
98
|
+
### Patch Changes
|
|
99
|
+
|
|
100
|
+
- Command refactoring
|
|
101
|
+
|
|
102
|
+
## 0.1.2
|
|
103
|
+
|
|
104
|
+
### Patch Changes
|
|
105
|
+
|
|
106
|
+
- Enh: add --verbose flag to dev mode
|
|
107
|
+
|
|
108
|
+
## 0.1.1
|
|
109
|
+
|
|
110
|
+
### Patch Changes
|
|
111
|
+
|
|
112
|
+
- Fix: include and exclude defaults
|
|
113
|
+
|
|
114
|
+
## 0.1.0
|
|
115
|
+
|
|
116
|
+
### Minor Changes
|
|
117
|
+
|
|
118
|
+
- afc6e91: Added `link` command to link to a specific instance
|
|
119
|
+
- fff79df: pockethost.io now runs `bun install` when uploading a `bun.lockb`
|
|
120
|
+
- 9fc57d6: Added whoami command
|
|
121
|
+
- 9fc57d6: Added list command
|
|
122
|
+
- afc6e91: Added --include and --exclude options to dev watch mode
|
|
123
|
+
|
|
124
|
+
### Patch Changes
|
|
125
|
+
|
|
126
|
+
- afc6e91: Fix: log tailer will now restart on disconnect
|
|
127
|
+
- fff79df: Enh: watcher now looks for package.json and bun.lockb
|
|
128
|
+
- fff79df: Fix: watcher was incorrectly applying --include and --exclude
|
|
129
|
+
- fff79df: Enh: watcher now queues successive deployments that happen in rapid succession
|
|
130
|
+
|
|
131
|
+
## 0.0.2
|
|
132
|
+
|
|
133
|
+
### Patch Changes
|
|
134
|
+
|
|
135
|
+
- Forgot tsx dep
|
|
136
|
+
|
|
137
|
+
## 0.0.1
|
|
138
|
+
|
|
139
|
+
### Patch Changes
|
|
140
|
+
|
|
141
|
+
- a5bf2f3: Initial version
|
package/README.md
CHANGED
|
@@ -1,66 +1,75 @@
|
|
|
1
|
-
# phio: the
|
|
1
|
+
# phio: the PocketHost CLI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Install globally (Node.js 24+):
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
|
|
7
|
-
bunx phio logout
|
|
8
|
-
bunx phio whoami
|
|
6
|
+
npm install -g phio
|
|
9
7
|
```
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
From the monorepo:
|
|
12
10
|
|
|
13
11
|
```bash
|
|
14
|
-
|
|
12
|
+
pnpm dev:phio -- --help
|
|
13
|
+
pnpm --filter phio dev -- login
|
|
15
14
|
```
|
|
16
15
|
|
|
17
|
-
|
|
16
|
+
## Commands
|
|
18
17
|
|
|
19
18
|
```bash
|
|
20
|
-
|
|
19
|
+
phio login
|
|
20
|
+
phio logout
|
|
21
|
+
phio info # alias: phio whoami
|
|
22
|
+
phio list
|
|
23
|
+
phio link [instance]
|
|
24
|
+
phio dev [instance]
|
|
25
|
+
phio deploy [instance]
|
|
26
|
+
phio sftp [instance]
|
|
27
|
+
phio logs [instance]
|
|
21
28
|
```
|
|
22
29
|
|
|
23
|
-
|
|
30
|
+
- **`phio dev`** — watch local changes and sync over SFTP
|
|
31
|
+
- **`phio deploy`** — one-shot SFTP sync
|
|
32
|
+
- **`phio sftp`** — interactive SFTP session (uses system `sftp` when available)
|
|
33
|
+
- **`phio logs`** — tail instance logs
|
|
24
34
|
|
|
25
|
-
|
|
26
|
-
bunx phio deploy [instance]
|
|
27
|
-
```
|
|
35
|
+
`dev` and `deploy` accept `-v/--verbose`, `-i/--include`, and `-e/--exclude`.
|
|
28
36
|
|
|
29
|
-
|
|
37
|
+
Default includes: `pb_*`, `package.json`, `bun.lock`, `bun.lockb`, `patches/**`.
|
|
30
38
|
|
|
31
|
-
|
|
32
|
-
bunx phio logs [instance]
|
|
33
|
-
```
|
|
39
|
+
Default excludes: `pb_data/**`.
|
|
34
40
|
|
|
35
41
|
## Configuration
|
|
36
42
|
|
|
37
|
-
|
|
43
|
+
Link a project directory to an instance:
|
|
38
44
|
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
{
|
|
42
|
-
"pockethost": {
|
|
43
|
-
"instanceName": "all-your-base"
|
|
44
|
-
}
|
|
45
|
-
}
|
|
45
|
+
```bash
|
|
46
|
+
phio link my-instance
|
|
46
47
|
```
|
|
47
48
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
Use `pockethost.json` to save your instance name so you don't need to keep typing it.
|
|
49
|
+
This writes `.phioconfig`:
|
|
51
50
|
|
|
52
51
|
```json
|
|
53
52
|
{
|
|
54
|
-
"instanceName": "
|
|
53
|
+
"instanceName": "my-instance"
|
|
55
54
|
}
|
|
56
55
|
```
|
|
57
56
|
|
|
58
|
-
|
|
57
|
+
Legacy `package.json` (`pockethost.instanceName`) and `pockethost.json` are migrated to `.phioconfig` automatically on first use.
|
|
58
|
+
|
|
59
|
+
## Deploy key
|
|
60
|
+
|
|
61
|
+
`phio dev` and `phio deploy` sync over **SFTP** (`ftp.pockethost.io:2222`) using an Ed25519 deploy key stored under `PHIO_HOME` (default `~/.config/phio/`). phio auto-registers a **`Phio`** key under Account → Keys on first use. Run `phio info` to inspect key status.
|
|
62
|
+
|
|
63
|
+
Customer docs: https://pockethost.io/docs/phio
|
|
59
64
|
|
|
60
|
-
|
|
65
|
+
## Environment variables
|
|
61
66
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
67
|
+
| Variable | Purpose |
|
|
68
|
+
| -------- | ------- |
|
|
69
|
+
| `PHIO_USERNAME` | Override saved email (non-interactive login) |
|
|
70
|
+
| `PHIO_PASSWORD` | Override saved password |
|
|
71
|
+
| `PHIO_INSTANCE_NAME` | Override linked instance name |
|
|
72
|
+
| `PHIO_MOTHERSHIP_URL` | Override mothership API URL |
|
|
73
|
+
| `PHIO_HOME` | Override phio config directory |
|
|
65
74
|
|
|
66
|
-
Environment variables take precedence over
|
|
75
|
+
Environment variables take precedence over `.phioconfig`.
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "phio",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "A CLI tool to manage your PocketHost instances",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
|
-
"url": "https://github.com/pockethost/
|
|
7
|
+
"url": "https://github.com/pockethost/pockethost.git",
|
|
8
|
+
"directory": "packages/phio"
|
|
8
9
|
},
|
|
9
|
-
"homepage": "https://
|
|
10
|
+
"homepage": "https://pockethost.io",
|
|
10
11
|
"keywords": [
|
|
11
12
|
"cli",
|
|
12
13
|
"pocketbase",
|
|
@@ -15,33 +16,34 @@
|
|
|
15
16
|
"author": "Ben Allfree",
|
|
16
17
|
"license": "MIT",
|
|
17
18
|
"bugs": {
|
|
18
|
-
"url": "https://github.com/pockethost/
|
|
19
|
+
"url": "https://github.com/pockethost/pockethost/issues"
|
|
19
20
|
},
|
|
21
|
+
"type": "module",
|
|
20
22
|
"main": "src/index.ts",
|
|
21
23
|
"module": "src/index.ts",
|
|
22
|
-
"type": "module",
|
|
23
24
|
"types": "src/index.ts",
|
|
24
|
-
"devDependencies": {
|
|
25
|
-
"@changesets/cli": "^2.28.1",
|
|
26
|
-
"@types/bun": "^1.2.3",
|
|
27
|
-
"@types/fs-extra": "^11.0.4",
|
|
28
|
-
"@types/node": "^22.13.5",
|
|
29
|
-
"prettier-plugin-organize-imports": "^4.1.0"
|
|
30
|
-
},
|
|
31
|
-
"scripts": {
|
|
32
|
-
"dev": "tsx ./src/cli.ts"
|
|
33
|
-
},
|
|
34
25
|
"bin": {
|
|
35
26
|
"phio": "src/cli.ts"
|
|
36
27
|
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=24"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"check:types": "tsc --noEmit",
|
|
33
|
+
"dev": "node --import tsx ./src/cli.ts",
|
|
34
|
+
"start": "node --import tsx ./src/cli.ts"
|
|
35
|
+
},
|
|
37
36
|
"files": [
|
|
38
|
-
"src"
|
|
37
|
+
"src",
|
|
38
|
+
"vendor",
|
|
39
|
+
"CHANGELOG.md",
|
|
40
|
+
"README.md"
|
|
39
41
|
],
|
|
40
42
|
"dependencies": {
|
|
41
43
|
"@inquirer/prompts": "^7.3.2",
|
|
44
|
+
"@microsoft/fetch-event-source": "github:pockethost/fetch-event-source#ebe3b7122647b48b93fd11effbbfb915d98956b0",
|
|
42
45
|
"@s-libs/micro-dash": "^18.0.0",
|
|
43
|
-
"
|
|
44
|
-
"@sentool/fetch-event-source": "github:pockethost/sentool-fetch-event-source#c975adc3cdf9c645bf094b0b8a28454699075e22",
|
|
46
|
+
"basic-ftp": "^5.0.5",
|
|
45
47
|
"bottleneck": "^2.19.5",
|
|
46
48
|
"chokidar": "^4.0.3",
|
|
47
49
|
"commander": "^13.1.0",
|
|
@@ -50,18 +52,26 @@
|
|
|
50
52
|
"env-var": "^7.5.0",
|
|
51
53
|
"event-stream": "^4.0.1",
|
|
52
54
|
"fs-extra": "^11.3.0",
|
|
55
|
+
"glob": "^11.0.0",
|
|
56
|
+
"lodash": "^4.17.21",
|
|
53
57
|
"multimatch": "^7.0.0",
|
|
54
58
|
"ora": "^8.2.0",
|
|
55
59
|
"pocketbase": "^0.25.1",
|
|
60
|
+
"pretty-bytes": "^5.6.0",
|
|
61
|
+
"pretty-ms": "^7.0.1",
|
|
62
|
+
"ssh2-sftp-client": "^12.1.1",
|
|
56
63
|
"tsx": "^4.19.3",
|
|
64
|
+
"yargs": "^17.7.1"
|
|
65
|
+
},
|
|
66
|
+
"devDependencies": {
|
|
67
|
+
"@types/fs-extra": "^11.0.4",
|
|
68
|
+
"@types/node": "^24.0.0",
|
|
69
|
+
"@types/ssh2-sftp-client": "^9.0.6",
|
|
57
70
|
"typescript": "^5.7.3"
|
|
58
71
|
},
|
|
59
72
|
"prettier": {
|
|
60
73
|
"semi": false,
|
|
61
74
|
"singleQuote": true,
|
|
62
|
-
"trailingComma": "es5"
|
|
63
|
-
"plugins": [
|
|
64
|
-
"prettier-plugin-organize-imports"
|
|
65
|
-
]
|
|
75
|
+
"trailingComma": "es5"
|
|
66
76
|
}
|
|
67
77
|
}
|
package/src/cli.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
2
|
import { program } from 'commander'
|
|
3
3
|
import { version } from '../package.json'
|
|
4
|
+
import { ensurePhioRoot } from './lib/phioRoot'
|
|
4
5
|
import { DeployCommand } from './commands/DeployCommand'
|
|
5
6
|
import { DevCommand } from './commands/DevCommand'
|
|
6
7
|
import { InfoCommand } from './commands/InfoCommand'
|
|
@@ -9,7 +10,9 @@ import { ListCommand } from './commands/ListCommand'
|
|
|
9
10
|
import { LoginCommand } from './commands/LoginCommand'
|
|
10
11
|
import { LogoutCommand } from './commands/LogoutCommand'
|
|
11
12
|
import { LogsCommand } from './commands/LogsCommand'
|
|
12
|
-
import {
|
|
13
|
+
import { SftpCommand } from './commands/SftpCommand'
|
|
14
|
+
|
|
15
|
+
ensurePhioRoot()
|
|
13
16
|
|
|
14
17
|
program
|
|
15
18
|
.name(`PocketHost CLI`)
|
|
@@ -18,18 +21,20 @@ program
|
|
|
18
21
|
.addCommand(LoginCommand())
|
|
19
22
|
.addCommand(LogsCommand())
|
|
20
23
|
.addCommand(DevCommand())
|
|
21
|
-
.addCommand(WhoAmICommand())
|
|
22
24
|
.addCommand(ListCommand())
|
|
23
25
|
.addCommand(LinkCommand())
|
|
24
26
|
.addCommand(DeployCommand())
|
|
27
|
+
.addCommand(SftpCommand())
|
|
25
28
|
.addCommand(LogoutCommand())
|
|
26
29
|
.addCommand(InfoCommand())
|
|
27
30
|
|
|
28
31
|
// Add error handling
|
|
29
32
|
program.exitOverride()
|
|
30
33
|
|
|
31
|
-
program.parseAsync(process.argv).catch((err) => {
|
|
32
|
-
|
|
34
|
+
program.parseAsync(process.argv).catch((err: NodeJS.ErrnoException & { code?: string }) => {
|
|
35
|
+
if (err.code === 'commander.helpDisplayed' || err.code === 'commander.help' || err.code === 'commander.version') {
|
|
36
|
+
process.exit(0)
|
|
37
|
+
}
|
|
33
38
|
if (err.code === 'commander.unknownCommand') {
|
|
34
39
|
console.error('Error: Unknown command')
|
|
35
40
|
} else if (err.code === 'commander.missingArgument') {
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { debounce } from '@s-libs/micro-dash'
|
|
2
|
-
import { deploy, excludeDefaults } from '
|
|
3
|
-
import { IFtpDeployArguments } from '
|
|
2
|
+
import { deploy, excludeDefaults } from '../../vendor/ftp-deploy/module.js'
|
|
3
|
+
import type { IFtpDeployArguments } from '../../vendor/ftp-deploy/types.js'
|
|
4
4
|
import Bottleneck from 'bottleneck'
|
|
5
5
|
import { watch } from 'chokidar'
|
|
6
6
|
import { Command } from 'commander'
|
|
7
7
|
import multimatch from 'multimatch'
|
|
8
|
+
import { ensureDeployKey } from '../lib/deployKey'
|
|
9
|
+
import { PHIO_CONFIG_FILE } from '../lib/constants'
|
|
10
|
+
import { PHIO_SFTP_HOST, PHIO_SFTP_PORT } from '../lib/sftpConnection'
|
|
8
11
|
import { ensureLoggedIn } from '../lib/ensureLoggedIn'
|
|
9
12
|
import { getClient, getInstanceBySubdomainCnameOrId } from '../lib/getClient'
|
|
10
13
|
import { savedInstanceName } from './../lib/defaultInstanceId'
|
|
@@ -37,7 +40,7 @@ export const watchAndDeploy = async (
|
|
|
37
40
|
) => {
|
|
38
41
|
if (!instanceName) {
|
|
39
42
|
throw new Error(
|
|
40
|
-
`No instance name provided and none was found in
|
|
43
|
+
`No instance name provided and none was found in ${PHIO_CONFIG_FILE}. Use 'phio link <instance>'`
|
|
41
44
|
)
|
|
42
45
|
}
|
|
43
46
|
console.log(`Dev mode`)
|
|
@@ -104,11 +107,19 @@ export async function deployMyCode(
|
|
|
104
107
|
) {
|
|
105
108
|
await ensureLoggedIn()
|
|
106
109
|
const client = await getClient()
|
|
110
|
+
const { privateKeyPath } = await ensureDeployKey(client)
|
|
111
|
+
const email = client.authStore.record?.email
|
|
112
|
+
if (!email) {
|
|
113
|
+
throw new Error(`You must be logged in first. Use 'phio login'`)
|
|
114
|
+
}
|
|
115
|
+
|
|
107
116
|
console.log(`🚚 Deploy started for ${instanceName}`)
|
|
108
117
|
const args: IFtpDeployArguments = {
|
|
109
|
-
server:
|
|
110
|
-
|
|
111
|
-
|
|
118
|
+
server: PHIO_SFTP_HOST,
|
|
119
|
+
protocol: 'sftp',
|
|
120
|
+
port: PHIO_SFTP_PORT,
|
|
121
|
+
username: email,
|
|
122
|
+
'private-key-path': privateKeyPath,
|
|
112
123
|
'server-dir': `${instanceName}/`,
|
|
113
124
|
include,
|
|
114
125
|
exclude: [...excludeDefaults, ...exclude],
|
|
@@ -1,10 +1,33 @@
|
|
|
1
1
|
import { Command } from 'commander'
|
|
2
|
+
import { DEPLOY_KEY_LABEL, ensureDeployKey, formatDeployKeyRemoteStatus, getDeployKeyStatus } from '../lib/deployKey'
|
|
2
3
|
import { PHIO_HOME } from '../lib/constants'
|
|
3
4
|
import { savedInstanceName } from '../lib/defaultInstanceId'
|
|
5
|
+
import { resolveAuthStatus } from '../lib/getClient'
|
|
6
|
+
|
|
7
|
+
export const showInfo = async () => {
|
|
8
|
+
const auth = await resolveAuthStatus()
|
|
9
|
+
|
|
10
|
+
if (auth.state !== 'authenticated') {
|
|
11
|
+
console.log(`Not logged in. Run 'phio login'.`)
|
|
12
|
+
return
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
await ensureDeployKey(auth.client)
|
|
16
|
+
const status = await getDeployKeyStatus(auth.client)
|
|
17
|
+
|
|
18
|
+
console.log(`Config root: ${PHIO_HOME()}`)
|
|
19
|
+
console.log(`Logged in as: ${auth.email}`)
|
|
20
|
+
console.log(`Instance: ${savedInstanceName() || '(not linked)'}`)
|
|
21
|
+
console.log(`Deploy key label: ${DEPLOY_KEY_LABEL}`)
|
|
22
|
+
console.log(`Deploy key private: ${status.privateKeyPath}`)
|
|
23
|
+
console.log(`Deploy key public: ${status.publicKeyPath}`)
|
|
24
|
+
console.log(`Deploy key fingerprint: ${status.fingerprint}`)
|
|
25
|
+
console.log(`Deploy key remote: ${formatDeployKeyRemoteStatus(status.remote)}`)
|
|
26
|
+
}
|
|
4
27
|
|
|
5
28
|
export const InfoCommand = () => {
|
|
6
|
-
return new Command(`info`)
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
29
|
+
return new Command(`info`)
|
|
30
|
+
.alias(`whoami`)
|
|
31
|
+
.description(`Show login and config info`)
|
|
32
|
+
.action(showInfo)
|
|
10
33
|
}
|
|
@@ -5,7 +5,7 @@ import { InstanceFields } from '../lib/InstanceFields'
|
|
|
5
5
|
import { getClient, getInstanceBySubdomainCnameOrId } from './../lib/getClient'
|
|
6
6
|
|
|
7
7
|
export const link = async (instanceName: string) => {
|
|
8
|
-
saveInstanceName(instanceName
|
|
8
|
+
saveInstanceName(instanceName)
|
|
9
9
|
const instance = await getInstanceBySubdomainCnameOrId(instanceName)
|
|
10
10
|
if (!instance) {
|
|
11
11
|
return
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { fetchEventSource } from '
|
|
1
|
+
import { fetchEventSource } from '../lib/fetchEventSource.js'
|
|
2
2
|
import { Command } from 'commander'
|
|
3
3
|
import { savedInstanceName } from '../lib/defaultInstanceId'
|
|
4
4
|
import { ensureLoggedIn } from '../lib/ensureLoggedIn'
|
|
@@ -16,7 +16,7 @@ export type InstanceLogFields = {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
type EventSourceMessage = {
|
|
19
|
-
data:
|
|
19
|
+
data: string
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
type Unsubscribe = () => void
|
|
@@ -58,6 +58,11 @@ const watchInstanceLog = async (
|
|
|
58
58
|
|
|
59
59
|
// Create promise that will resolve when streaming ends
|
|
60
60
|
const streamingPromise = new Promise<void>((resolve, reject) => {
|
|
61
|
+
signal.addEventListener('abort', () => {
|
|
62
|
+
clearPendingTimeouts()
|
|
63
|
+
resolve()
|
|
64
|
+
})
|
|
65
|
+
|
|
61
66
|
const continuallyFetchFromEventSource = () => {
|
|
62
67
|
// Don't attempt to reconnect if we're aborting
|
|
63
68
|
if (isAborting) {
|
|
@@ -80,9 +85,13 @@ const watchInstanceLog = async (
|
|
|
80
85
|
openWhenHidden: true,
|
|
81
86
|
body: JSON.stringify(body),
|
|
82
87
|
onmessage: (event: EventSourceMessage) => {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
if (isAborting) return
|
|
89
|
+
try {
|
|
90
|
+
const data = JSON.parse(event.data) as InstanceLogFields
|
|
91
|
+
update(data)
|
|
92
|
+
} catch {
|
|
93
|
+
// ignore malformed lines
|
|
94
|
+
}
|
|
86
95
|
},
|
|
87
96
|
onopen: async (response: Response) => {
|
|
88
97
|
if (!response.ok) {
|
|
@@ -102,15 +111,9 @@ const watchInstanceLog = async (
|
|
|
102
111
|
if (isAborting) return
|
|
103
112
|
console.log(`Log stream closed. Reconnecting...`)
|
|
104
113
|
|
|
105
|
-
// Clear any existing timeout before setting a new one
|
|
106
114
|
clearPendingTimeouts()
|
|
107
115
|
retryTimeout = setTimeout(continuallyFetchFromEventSource, 100)
|
|
108
116
|
},
|
|
109
|
-
onabort: () => {
|
|
110
|
-
console.log(`Log stream aborted`)
|
|
111
|
-
clearPendingTimeouts()
|
|
112
|
-
resolve()
|
|
113
|
-
},
|
|
114
117
|
signal,
|
|
115
118
|
})
|
|
116
119
|
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { spawn, spawnSync } from 'child_process'
|
|
2
|
+
import { Command } from 'commander'
|
|
3
|
+
import { ensureDeployKey } from '../lib/deployKey'
|
|
4
|
+
import { PHIO_CONFIG_FILE } from '../lib/constants'
|
|
5
|
+
import { savedInstanceName } from '../lib/defaultInstanceId'
|
|
6
|
+
import { ensureLoggedIn } from '../lib/ensureLoggedIn'
|
|
7
|
+
import { getClient, getInstanceBySubdomainCnameOrId } from '../lib/getClient'
|
|
8
|
+
import {
|
|
9
|
+
formatSftpCommand,
|
|
10
|
+
PHIO_SFTP_HOST,
|
|
11
|
+
PHIO_SFTP_PORT,
|
|
12
|
+
type SftpConnection,
|
|
13
|
+
buildSftpArgs,
|
|
14
|
+
} from '../lib/sftpConnection'
|
|
15
|
+
|
|
16
|
+
const findSftpExecutable = (): string | null => {
|
|
17
|
+
const lookup = process.platform === 'win32' ? 'where' : 'which'
|
|
18
|
+
const result = spawnSync(lookup, ['sftp'], { encoding: 'utf8' })
|
|
19
|
+
if (result.status !== 0) {
|
|
20
|
+
return null
|
|
21
|
+
}
|
|
22
|
+
const line = result.stdout.trim().split(/\r?\n/)[0]?.trim()
|
|
23
|
+
return line || null
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const resolveRemoteDir = async (instanceName?: string) => {
|
|
27
|
+
const name = instanceName || savedInstanceName()
|
|
28
|
+
if (!name) {
|
|
29
|
+
return ''
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const instance = await getInstanceBySubdomainCnameOrId(name)
|
|
34
|
+
return `${instance.subdomain}/`
|
|
35
|
+
} catch {
|
|
36
|
+
throw new Error(`Instance ${name} not found`)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const openSftpSession = async (instanceName?: string, printOnly = false) => {
|
|
41
|
+
if (!instanceName && !savedInstanceName()) {
|
|
42
|
+
console.log(`No linked instance in ${PHIO_CONFIG_FILE}. Opening SFTP at instance root.`)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
await ensureLoggedIn()
|
|
46
|
+
const client = await getClient()
|
|
47
|
+
const { privateKeyPath } = await ensureDeployKey(client)
|
|
48
|
+
const email = client.authStore.record?.email
|
|
49
|
+
if (!email) {
|
|
50
|
+
throw new Error(`You must be logged in first. Use 'phio login'`)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const connection: SftpConnection = {
|
|
54
|
+
host: PHIO_SFTP_HOST,
|
|
55
|
+
port: PHIO_SFTP_PORT,
|
|
56
|
+
username: email,
|
|
57
|
+
privateKeyPath,
|
|
58
|
+
remoteDir: await resolveRemoteDir(instanceName),
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const commandLine = formatSftpCommand(connection)
|
|
62
|
+
|
|
63
|
+
if (printOnly) {
|
|
64
|
+
console.log(commandLine)
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const sftpBin = findSftpExecutable()
|
|
69
|
+
if (!sftpBin) {
|
|
70
|
+
console.error(`Could not find 'sftp' on PATH. Install OpenSSH client tools, then run:`)
|
|
71
|
+
console.error('')
|
|
72
|
+
console.error(commandLine)
|
|
73
|
+
console.error('')
|
|
74
|
+
console.error('See https://pockethost.io/docs/ftp for client setup.')
|
|
75
|
+
process.exit(1)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const child = spawn(sftpBin, buildSftpArgs(connection), { stdio: 'inherit' })
|
|
79
|
+
await new Promise<void>((_resolve, reject) => {
|
|
80
|
+
child.on('error', reject)
|
|
81
|
+
child.on('close', (code, signal) => {
|
|
82
|
+
if (signal) {
|
|
83
|
+
process.kill(process.pid, signal)
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
process.exit(code ?? 0)
|
|
87
|
+
})
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export const SftpCommand = () => {
|
|
92
|
+
return new Command('sftp')
|
|
93
|
+
.argument(`[instanceName]`, `Instance name`, savedInstanceName())
|
|
94
|
+
.description(`Open an interactive SFTP session to your instance files`)
|
|
95
|
+
.option(`--print`, `Print the sftp command instead of running it`)
|
|
96
|
+
.action(async (instanceName, options) => {
|
|
97
|
+
await openSftpSession(instanceName || undefined, options.print)
|
|
98
|
+
})
|
|
99
|
+
}
|
package/src/lib/constants.ts
CHANGED
|
@@ -27,3 +27,5 @@ export const PHIO_USERNAME = () => env.get('PHIO_USERNAME').asString() || ''
|
|
|
27
27
|
export const PHIO_PASSWORD = () => env.get('PHIO_PASSWORD').asString() || ''
|
|
28
28
|
export const PHIO_INSTANCE_NAME = () =>
|
|
29
29
|
env.get('PHIO_INSTANCE_NAME').asString() || ''
|
|
30
|
+
|
|
31
|
+
export const PHIO_CONFIG_FILE = '.phioconfig'
|