@palettelab/cli 0.3.9 → 0.3.11
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 +3 -0
- package/lib/commands/publish.js +4 -0
- package/lib/commands/status.js +1 -0
- package/lib/commands/test.js +18 -6
- package/lib/ports.js +27 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -123,10 +123,13 @@ pltt publish --env staging
|
|
|
123
123
|
pltt publish --env production
|
|
124
124
|
pltt publish --env production -y
|
|
125
125
|
pltt publish --env staging --json
|
|
126
|
+
pltt publish --env staging --ttl-hours 24
|
|
126
127
|
```
|
|
127
128
|
|
|
128
129
|
Publishing first runs the same contract checks as `pltt test`. If preflight passes, it bundles frontend/backend artifacts, uploads them, creates a `pending_review` publish record, and prints review/preview URLs when the platform returns them.
|
|
129
130
|
|
|
131
|
+
`--ttl-hours` sets an expiration on the preview URL. It is mainly used by `pltt dev --cloud`, which defaults previews to 24 hours.
|
|
132
|
+
|
|
130
133
|
Environment config is read from `./palette.config.json`, `~/.palette/config.json`, or `PALETTE_<ENV>_URL` plus `PALETTE_<ENV>_TOKEN` / `PALETTE_PUBLISH_TOKEN`.
|
|
131
134
|
|
|
132
135
|
### `pltt status <publish-id>`
|
package/lib/commands/publish.js
CHANGED
|
@@ -191,6 +191,7 @@ async function run(argv, { cwd }) {
|
|
|
191
191
|
env: env.name,
|
|
192
192
|
url: env.url,
|
|
193
193
|
preview_url: record.preview_url,
|
|
194
|
+
preview_expires_at: record.preview_expires_at,
|
|
194
195
|
published_at: new Date().toISOString(),
|
|
195
196
|
},
|
|
196
197
|
null,
|
|
@@ -216,6 +217,9 @@ async function run(argv, { cwd }) {
|
|
|
216
217
|
if (record.preview_url) {
|
|
217
218
|
console.log(`[pltt] preview: ${record.preview_url}`)
|
|
218
219
|
}
|
|
220
|
+
if (record.preview_expires_at) {
|
|
221
|
+
console.log(`[pltt] preview expires: ${record.preview_expires_at}`)
|
|
222
|
+
}
|
|
219
223
|
console.log(`[pltt] once approved, live at ${env.url}${record.catalog_url}`)
|
|
220
224
|
return record
|
|
221
225
|
}
|
package/lib/commands/status.js
CHANGED
|
@@ -67,6 +67,7 @@ async function run(argv, { cwd }) {
|
|
|
67
67
|
if (data.review_decision) console.log(` decision: ${data.review_decision}`)
|
|
68
68
|
if (data.review_reason) console.log(` reason: ${data.review_reason}`)
|
|
69
69
|
if (data.preview_url) console.log(` preview: ${data.preview_url}`)
|
|
70
|
+
if (data.preview_expires_at) console.log(` expires: ${data.preview_expires_at}`)
|
|
70
71
|
console.log(` published: ${data.published_at}`)
|
|
71
72
|
if (data.report) {
|
|
72
73
|
console.log(` risk: ${data.report.risk_score.toUpperCase()}`)
|
package/lib/commands/test.js
CHANGED
|
@@ -35,20 +35,27 @@ function reporter(json, results) {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
function localBackendSdkPath() {
|
|
39
|
+
const candidate = path.resolve(__dirname, "..", "..", "..", "backend")
|
|
40
|
+
return fs.existsSync(path.join(candidate, "palette_sdk")) ? candidate : null
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function localBackendSdkPython() {
|
|
44
|
+
const sdkPath = localBackendSdkPath()
|
|
45
|
+
if (!sdkPath) return null
|
|
46
|
+
const candidate = path.join(sdkPath, ".venv", "bin", "python")
|
|
47
|
+
return fs.existsSync(candidate) ? candidate : null
|
|
48
|
+
}
|
|
49
|
+
|
|
38
50
|
function backendPython(cwd) {
|
|
39
51
|
return (
|
|
40
52
|
process.env.PALETTE_PYTHON ||
|
|
41
53
|
(fs.existsSync(path.join(cwd, ".venv", "bin", "python"))
|
|
42
54
|
? path.join(cwd, ".venv", "bin", "python")
|
|
43
|
-
: "python3")
|
|
55
|
+
: localBackendSdkPython() || "python3")
|
|
44
56
|
)
|
|
45
57
|
}
|
|
46
58
|
|
|
47
|
-
function localBackendSdkPath() {
|
|
48
|
-
const candidate = path.resolve(__dirname, "..", "..", "..", "backend")
|
|
49
|
-
return fs.existsSync(path.join(candidate, "palette_sdk")) ? candidate : null
|
|
50
|
-
}
|
|
51
|
-
|
|
52
59
|
function pythonEnv(extra = {}) {
|
|
53
60
|
const paths = []
|
|
54
61
|
if (extra.PYTHONPATH) paths.push(extra.PYTHONPATH)
|
|
@@ -76,6 +83,11 @@ function installPythonDependencies(cwd, out) {
|
|
|
76
83
|
}
|
|
77
84
|
|
|
78
85
|
const hostPython = backendPython(cwd)
|
|
86
|
+
if (hostPython === localBackendSdkPython()) {
|
|
87
|
+
out.ok("using local backend SDK virtualenv for backend import checks", { dependencies: deps })
|
|
88
|
+
return { python: hostPython, env: pythonEnv(), failures: 0, dependencies: deps }
|
|
89
|
+
}
|
|
90
|
+
|
|
79
91
|
const venvDir = path.join(cwd, ".palette", "test-venv")
|
|
80
92
|
const venvPython = path.join(venvDir, "bin", "python")
|
|
81
93
|
const lockPath = path.join(venvDir, ".palette-deps-lock")
|
package/lib/ports.js
CHANGED
|
@@ -13,13 +13,38 @@ function canBindPort(port, host = "0.0.0.0") {
|
|
|
13
13
|
})
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
function canConnectPort(port, host, timeoutMs = 250) {
|
|
17
|
+
return new Promise((resolve) => {
|
|
18
|
+
const socket = net.createConnection({ port, host })
|
|
19
|
+
const done = (connected) => {
|
|
20
|
+
socket.removeAllListeners()
|
|
21
|
+
socket.destroy()
|
|
22
|
+
resolve(connected)
|
|
23
|
+
}
|
|
24
|
+
socket.setTimeout(timeoutMs)
|
|
25
|
+
socket.once("connect", () => done(true))
|
|
26
|
+
socket.once("timeout", () => done(false))
|
|
27
|
+
socket.once("error", () => done(false))
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function isPortAvailable(port, { bindHost = "0.0.0.0" } = {}) {
|
|
32
|
+
// Docker publishes host ports on localhost-facing addresses. A process bound
|
|
33
|
+
// only to 127.0.0.1 or ::1 can be missed by a single 0.0.0.0 bind probe on
|
|
34
|
+
// some host setups, so test the addresses users actually open in browsers.
|
|
35
|
+
for (const host of ["127.0.0.1", "::1", "localhost"]) {
|
|
36
|
+
if (await canConnectPort(port, host)) return false
|
|
37
|
+
}
|
|
38
|
+
return canBindPort(port, bindHost)
|
|
39
|
+
}
|
|
40
|
+
|
|
16
41
|
async function findFreePort(preferred, { host = "0.0.0.0", maxAttempts = 100 } = {}) {
|
|
17
42
|
const start = Number(preferred)
|
|
18
43
|
if (!Number.isInteger(start) || start <= 0 || start > 65535) {
|
|
19
44
|
throw new Error(`invalid port: ${preferred}`)
|
|
20
45
|
}
|
|
21
46
|
for (let port = start; port < start + maxAttempts && port <= 65535; port++) {
|
|
22
|
-
if (await
|
|
47
|
+
if (await isPortAvailable(port, { bindHost: host })) return port
|
|
23
48
|
}
|
|
24
49
|
throw new Error(`no free port found from ${start} to ${Math.min(start + maxAttempts - 1, 65535)}`)
|
|
25
50
|
}
|
|
@@ -39,4 +64,4 @@ async function resolveDevPorts({
|
|
|
39
64
|
}
|
|
40
65
|
}
|
|
41
66
|
|
|
42
|
-
module.exports = { canBindPort, findFreePort, resolveDevPorts }
|
|
67
|
+
module.exports = { canBindPort, canConnectPort, isPortAvailable, findFreePort, resolveDevPorts }
|