@palettelab/cli 0.3.3 → 0.3.4
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 +7 -5
- package/lib/commands/dev.js +14 -6
- package/lib/commands/doctor.js +13 -25
- package/lib/ports.js +42 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -53,12 +53,14 @@ pltt dev --cloud --env staging
|
|
|
53
53
|
Under the hood this runs `docker compose up` with a bundled compose file. It starts:
|
|
54
54
|
|
|
55
55
|
- Postgres + Redis
|
|
56
|
-
- The Palette frontend on http://localhost:3000
|
|
57
|
-
- The Palette backend on http://localhost:8000
|
|
58
|
-
- Your plugin at
|
|
56
|
+
- The Palette frontend on the first available port starting at http://localhost:3000
|
|
57
|
+
- The Palette backend on the first available port starting at http://localhost:8000
|
|
58
|
+
- Your plugin at the frontend URL printed by the CLI
|
|
59
59
|
|
|
60
60
|
Your plugin directory is mounted into the container at `/plugins/<your-id>`. Edits to your frontend/backend sources hot-reload.
|
|
61
61
|
|
|
62
|
+
`3000` and `8000` are preferred defaults, not hard requirements. If either port is already in use, `pltt dev` automatically picks the next free port and prints the actual URLs.
|
|
63
|
+
|
|
62
64
|
`pltt dev --cloud` skips Docker and publishes a reviewable preview to a configured cloud sandbox. It defaults to `--env staging` unless `--env` or `PALETTE_ENV` is set.
|
|
63
65
|
|
|
64
66
|
Environment variables:
|
|
@@ -66,8 +68,8 @@ Environment variables:
|
|
|
66
68
|
| Name | Default | Purpose |
|
|
67
69
|
|---|---|---|
|
|
68
70
|
| `PALETTE_DEV_IMAGE` | `ghcr.io/palette-lab/platform-dev:latest` | Override the platform image |
|
|
69
|
-
| `PALETTE_FRONTEND_PORT` | `3000` |
|
|
70
|
-
| `PALETTE_BACKEND_PORT` | `8000` |
|
|
71
|
+
| `PALETTE_FRONTEND_PORT` | `3000` | Preferred starting host port for the frontend |
|
|
72
|
+
| `PALETTE_BACKEND_PORT` | `8000` | Preferred starting host port for the backend |
|
|
71
73
|
|
|
72
74
|
### `pltt doctor`
|
|
73
75
|
|
package/lib/commands/dev.js
CHANGED
|
@@ -5,12 +5,11 @@ const { spawn, spawnSync } = require("child_process")
|
|
|
5
5
|
const { loadManifest } = require("../manifest")
|
|
6
6
|
const { watchFrontend } = require("../bundler")
|
|
7
7
|
const { parseFlags } = require("../environments")
|
|
8
|
+
const { resolveDevPorts } = require("../ports")
|
|
8
9
|
const publish = require("./publish")
|
|
9
10
|
|
|
10
11
|
const DEFAULT_IMAGE =
|
|
11
12
|
process.env.PALETTE_DEV_IMAGE || "ghcr.io/palette-lab/platform-dev:latest"
|
|
12
|
-
const FRONTEND_PORT = process.env.PALETTE_FRONTEND_PORT || "3000"
|
|
13
|
-
const BACKEND_PORT = process.env.PALETTE_BACKEND_PORT || "8000"
|
|
14
13
|
const COMPOSE_FILE = path.resolve(__dirname, "..", "..", "platform-dev", "docker-compose.yml")
|
|
15
14
|
|
|
16
15
|
function ensureDocker() {
|
|
@@ -61,6 +60,9 @@ async function run(args, { cwd }) {
|
|
|
61
60
|
const pluginId = manifest.id
|
|
62
61
|
const frontendEntry = manifest.frontend?.entry || "./frontend/src/index.tsx"
|
|
63
62
|
const frontendBundle = path.join(cwd, ".palette", "dist", "frontend.mjs")
|
|
63
|
+
const ports = await resolveDevPorts()
|
|
64
|
+
const frontendPort = String(ports.frontend)
|
|
65
|
+
const backendPort = String(ports.backend)
|
|
64
66
|
|
|
65
67
|
ensureDocker()
|
|
66
68
|
|
|
@@ -83,8 +85,14 @@ async function run(args, { cwd }) {
|
|
|
83
85
|
|
|
84
86
|
console.log(`[pltt] starting ${DEFAULT_IMAGE} via docker compose`)
|
|
85
87
|
console.log(`[pltt] mounting ${cwd} → /plugins/${pluginId}`)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
+
if (ports.frontend !== ports.preferredFrontend) {
|
|
89
|
+
console.log(`[pltt] frontend port ${ports.preferredFrontend} is busy; using ${ports.frontend}`)
|
|
90
|
+
}
|
|
91
|
+
if (ports.backend !== ports.preferredBackend) {
|
|
92
|
+
console.log(`[pltt] backend port ${ports.preferredBackend} is busy; using ${ports.backend}`)
|
|
93
|
+
}
|
|
94
|
+
console.log(`[pltt] frontend: http://localhost:${frontendPort}/apps/${pluginId}`)
|
|
95
|
+
console.log(`[pltt] backend: http://localhost:${backendPort}/api/v1/plugins/${pluginId}`)
|
|
88
96
|
|
|
89
97
|
// Pre-pull so we can give a useful error if the image isn't reachable
|
|
90
98
|
// (common cause: maintainer hasn't pushed it yet, or `docker login ghcr.io`
|
|
@@ -107,8 +115,8 @@ async function run(args, { cwd }) {
|
|
|
107
115
|
PALETTE_DEV_IMAGE: DEFAULT_IMAGE,
|
|
108
116
|
PALETTE_ACTIVE_PLUGIN: pluginId,
|
|
109
117
|
PALETTE_PLUGIN_DIR: cwd,
|
|
110
|
-
PALETTE_FRONTEND_PORT:
|
|
111
|
-
PALETTE_BACKEND_PORT:
|
|
118
|
+
PALETTE_FRONTEND_PORT: frontendPort,
|
|
119
|
+
PALETTE_BACKEND_PORT: backendPort,
|
|
112
120
|
}
|
|
113
121
|
|
|
114
122
|
const projectName = `palette-dev-${pluginId}`
|
package/lib/commands/doctor.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"use strict"
|
|
2
2
|
|
|
3
3
|
const fs = require("fs")
|
|
4
|
-
const net = require("net")
|
|
5
4
|
const path = require("path")
|
|
6
5
|
const { spawnSync } = require("child_process")
|
|
7
6
|
const { loadManifest, validateManifest } = require("../manifest")
|
|
8
7
|
const { bundleFrontend } = require("../bundler")
|
|
8
|
+
const { resolveDevPorts } = require("../ports")
|
|
9
9
|
|
|
10
10
|
const DEFAULT_IMAGE =
|
|
11
11
|
process.env.PALETTE_DEV_IMAGE || "ghcr.io/palette-lab/platform-dev:latest"
|
|
@@ -39,17 +39,6 @@ function imageExistsLocally(image) {
|
|
|
39
39
|
return spawnSync("docker", ["image", "inspect", image], { stdio: "ignore" }).status === 0
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
function canBindPort(port) {
|
|
43
|
-
return new Promise((resolve) => {
|
|
44
|
-
const server = net.createServer()
|
|
45
|
-
server.once("error", () => resolve(false))
|
|
46
|
-
server.once("listening", () => {
|
|
47
|
-
server.close(() => resolve(true))
|
|
48
|
-
})
|
|
49
|
-
server.listen(port, "127.0.0.1")
|
|
50
|
-
})
|
|
51
|
-
}
|
|
52
|
-
|
|
53
42
|
function checkEntry(cwd, label, rel) {
|
|
54
43
|
if (!rel) return 0
|
|
55
44
|
const abs = path.resolve(cwd, rel)
|
|
@@ -81,19 +70,18 @@ async function run(args, { cwd }) {
|
|
|
81
70
|
)
|
|
82
71
|
}
|
|
83
72
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
`frontend port ${FRONTEND_PORT} is already in use`,
|
|
88
|
-
`
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
)
|
|
73
|
+
try {
|
|
74
|
+
const ports = await resolveDevPorts({ frontend: FRONTEND_PORT, backend: BACKEND_PORT })
|
|
75
|
+
if (ports.frontend === FRONTEND_PORT) ok(`frontend port ${FRONTEND_PORT} is available`)
|
|
76
|
+
else warn(`frontend port ${FRONTEND_PORT} is already in use`, `pltt dev will use ${ports.frontend}`)
|
|
77
|
+
if (ports.backend === BACKEND_PORT) ok(`backend port ${BACKEND_PORT} is available`)
|
|
78
|
+
else warn(`backend port ${BACKEND_PORT} is already in use`, `pltt dev will use ${ports.backend}`)
|
|
79
|
+
} catch (err) {
|
|
80
|
+
failures += fail(
|
|
81
|
+
`could not find free dev ports: ${err instanceof Error ? err.message : String(err)}`,
|
|
82
|
+
"Free a local port or set PALETTE_FRONTEND_PORT / PALETTE_BACKEND_PORT to a wider available range.",
|
|
83
|
+
)
|
|
84
|
+
}
|
|
97
85
|
|
|
98
86
|
let manifest
|
|
99
87
|
try {
|
package/lib/ports.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict"
|
|
2
|
+
|
|
3
|
+
const net = require("net")
|
|
4
|
+
|
|
5
|
+
function canBindPort(port, host = "127.0.0.1") {
|
|
6
|
+
return new Promise((resolve) => {
|
|
7
|
+
const server = net.createServer()
|
|
8
|
+
server.once("error", () => resolve(false))
|
|
9
|
+
server.once("listening", () => {
|
|
10
|
+
server.close(() => resolve(true))
|
|
11
|
+
})
|
|
12
|
+
server.listen(port, host)
|
|
13
|
+
})
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function findFreePort(preferred, { host = "127.0.0.1", maxAttempts = 100 } = {}) {
|
|
17
|
+
const start = Number(preferred)
|
|
18
|
+
if (!Number.isInteger(start) || start <= 0 || start > 65535) {
|
|
19
|
+
throw new Error(`invalid port: ${preferred}`)
|
|
20
|
+
}
|
|
21
|
+
for (let port = start; port < start + maxAttempts && port <= 65535; port++) {
|
|
22
|
+
if (await canBindPort(port, host)) return port
|
|
23
|
+
}
|
|
24
|
+
throw new Error(`no free port found from ${start} to ${Math.min(start + maxAttempts - 1, 65535)}`)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function resolveDevPorts({
|
|
28
|
+
frontend = process.env.PALETTE_FRONTEND_PORT || 3000,
|
|
29
|
+
backend = process.env.PALETTE_BACKEND_PORT || 8000,
|
|
30
|
+
} = {}) {
|
|
31
|
+
const frontendPort = await findFreePort(frontend)
|
|
32
|
+
const backendStart = Number(backend) === frontendPort ? frontendPort + 1 : backend
|
|
33
|
+
const backendPort = await findFreePort(backendStart)
|
|
34
|
+
return {
|
|
35
|
+
frontend: frontendPort,
|
|
36
|
+
backend: backendPort,
|
|
37
|
+
preferredFrontend: Number(frontend),
|
|
38
|
+
preferredBackend: Number(backend),
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = { canBindPort, findFreePort, resolveDevPorts }
|