@palettelab/cli 0.3.39 → 0.3.41
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 +10 -7
- package/lib/commands/dev.js +18 -3
- package/lib/commands/doctor.js +3 -3
- package/lib/commands/logs.js +42 -26
- package/lib/dev-simulator.js +4 -0
- package/lib/environments.js +1 -1
- package/lib/ports.js +14 -3
- package/package.json +1 -1
- package/platform-dev/docker-compose.yml +5 -5
package/README.md
CHANGED
|
@@ -467,14 +467,16 @@ pltt dev --platform
|
|
|
467
467
|
|
|
468
468
|
By default this starts:
|
|
469
469
|
|
|
470
|
-
- A small local app shell on the first available port starting at http://localhost:
|
|
471
|
-
- A local FastAPI backend runner on the first available port starting at http://localhost:
|
|
470
|
+
- A small local app shell on the first available port starting at http://localhost:7321
|
|
471
|
+
- A local FastAPI backend runner on the first available port starting at http://localhost:8732
|
|
472
472
|
- A mock Palette platform context for `usePlatform()`, OS language, toasts, org/user data, and authenticated API calls
|
|
473
473
|
- A plugin-local SQLite database under `.palette/dev/` when `capabilities.database` or `database` is enabled
|
|
474
474
|
|
|
475
475
|
Your frontend entry is bundled and watched. Your backend entry is loaded under
|
|
476
476
|
`/api/v1/plugins/<your-id>/*` with a dev `PluginContext`, so normal SDK calls
|
|
477
|
-
work without Docker or platform source.
|
|
477
|
+
work without Docker or platform source. The terminal streams local frontend
|
|
478
|
+
requests, frontend rebuilds, and backend process output while `pltt dev` is
|
|
479
|
+
running.
|
|
478
480
|
|
|
479
481
|
The simulator also provides the same language fields that Palette OS provides:
|
|
480
482
|
`language`, `fallbackLanguage`, `supportedLanguages`, and `setLanguage()`.
|
|
@@ -487,9 +489,9 @@ local dev. The simulator imports `backend/api/models.py` when present and create
|
|
|
487
489
|
tables from `palette_sdk.db.PluginBase.metadata`. Production installs still use
|
|
488
490
|
the declared Alembic migrations and platform-managed Postgres/RLS.
|
|
489
491
|
|
|
490
|
-
`
|
|
492
|
+
`7321` and `8732` 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.
|
|
491
493
|
|
|
492
|
-
`pltt dev --sandbox` skips Docker and publishes a reviewable preview to a configured Palette sandbox. This is the payment-gateway-style flow: your app uses local SDKs, while real platform services such as login, Data Room, storage, database policies, logs, review, and publish lifecycle run in the hosted sandbox. It defaults to `--env staging` unless `--env` or `PALETTE_ENV` is set, adds a 24-hour preview TTL by default,
|
|
494
|
+
`pltt dev --sandbox` skips Docker and publishes a reviewable preview to a configured Palette sandbox. This is the payment-gateway-style flow: your app uses local SDKs, while real platform services such as login, Data Room, storage, database policies, logs, review, and publish lifecycle run in the hosted sandbox. It defaults to `--env staging` unless `--env` or `PALETTE_ENV` is set, adds a 24-hour preview TTL by default, prints the preview/status URLs, then tails hosted app logs in the same terminal. Set `PALETTE_DEV_LOG_TAIL` to change the initial hosted log tail size.
|
|
493
495
|
|
|
494
496
|
For developer sandboxes where manual approval should not block OS testing, run the sandbox backend with `APPSTORE_AUTO_APPROVE_SANDBOX_PREVIEWS=true`. Preview publishes are still checked by the automated review gates; passing preview publishes are marked active and synced immediately so backend routes, installs, logs, permissions, and platform APIs can be tested in the OS without Docker.
|
|
495
497
|
|
|
@@ -508,9 +510,10 @@ Environment variables:
|
|
|
508
510
|
| Name | Default | Purpose |
|
|
509
511
|
|---|---|---|
|
|
510
512
|
| `PALETTE_DEV_IMAGE` | `ghcr.io/palette-lab/platform-dev:latest` | Override the platform image for `--platform` |
|
|
511
|
-
| `PALETTE_FRONTEND_PORT` | `
|
|
512
|
-
| `PALETTE_BACKEND_PORT` | `
|
|
513
|
+
| `PALETTE_FRONTEND_PORT` | `7321` | Preferred starting host port for the frontend |
|
|
514
|
+
| `PALETTE_BACKEND_PORT` | `8732` | Preferred starting host port for the backend |
|
|
513
515
|
| `PALETTE_DEV_DATABASE_URL` | `.palette/dev/<plugin-id>.sqlite3` | Override the local dev database URL |
|
|
516
|
+
| `PALETTE_DEV_LOG_TAIL` | `200` | Initial hosted log events shown by `pltt dev --sandbox` / `--cloud` |
|
|
514
517
|
| `APPSTORE_AUTO_APPROVE_SANDBOX_PREVIEWS` | `false` | Backend setting for hosted sandboxes; auto-approve passing preview publishes so developers can test full OS behavior without manual review |
|
|
515
518
|
|
|
516
519
|
### `pltt secrets`
|
package/lib/commands/dev.js
CHANGED
|
@@ -4,11 +4,12 @@ const path = require("path")
|
|
|
4
4
|
const { spawn, spawnSync } = require("child_process")
|
|
5
5
|
const { loadManifest } = require("../manifest")
|
|
6
6
|
const { watchFrontend } = require("../bundler")
|
|
7
|
-
const { parseFlags } = require("../environments")
|
|
7
|
+
const { parseFlags, resolveEnvironment } = require("../environments")
|
|
8
8
|
const { resolveDevPorts } = require("../ports")
|
|
9
9
|
const { startSimulator } = require("../dev-simulator")
|
|
10
10
|
const { loadLocalEnv } = require("../secrets")
|
|
11
11
|
const publish = require("./publish")
|
|
12
|
+
const logs = require("./logs")
|
|
12
13
|
|
|
13
14
|
const DEFAULT_PLATFORM_IMAGE = "ghcr.io/palette-lab/platform-dev:latest"
|
|
14
15
|
const DEFAULT_IMAGE = process.env.PALETTE_DEV_IMAGE || DEFAULT_PLATFORM_IMAGE
|
|
@@ -102,8 +103,22 @@ async function run(args, { cwd }) {
|
|
|
102
103
|
console.log(`[pltt] preview URL: ${record.preview_url}`)
|
|
103
104
|
}
|
|
104
105
|
if (!json && record?.id) {
|
|
105
|
-
|
|
106
|
-
console.log(
|
|
106
|
+
const envName = flags.env || process.env.PALETTE_ENV || "staging"
|
|
107
|
+
console.log(`[pltt] status: pltt status ${record.id} --env ${envName}`)
|
|
108
|
+
const logPluginId = record.plugin_id || loadManifest(cwd).id
|
|
109
|
+
console.log(`[pltt] logs: streaming hosted app logs for ${logPluginId} (Ctrl+C to stop)`)
|
|
110
|
+
try {
|
|
111
|
+
const env = resolveEnvironment({ cwd, flags: { ...flags, env: envName } })
|
|
112
|
+
await logs.streamPluginLogs({
|
|
113
|
+
env,
|
|
114
|
+
pluginId: logPluginId,
|
|
115
|
+
tail: Number(process.env.PALETTE_DEV_LOG_TAIL || "200") || 200,
|
|
116
|
+
follow: true,
|
|
117
|
+
})
|
|
118
|
+
} catch (err) {
|
|
119
|
+
console.warn(`[pltt] could not stream hosted logs: ${err instanceof Error ? err.message : String(err)}`)
|
|
120
|
+
console.warn(`[pltt] run manually: pltt logs ${logPluginId} --follow --env ${envName}`)
|
|
121
|
+
}
|
|
107
122
|
}
|
|
108
123
|
return
|
|
109
124
|
}
|
package/lib/commands/doctor.js
CHANGED
|
@@ -5,13 +5,13 @@ const path = require("path")
|
|
|
5
5
|
const { spawnSync } = require("child_process")
|
|
6
6
|
const { loadManifest, validateManifest } = require("../manifest")
|
|
7
7
|
const { bundleFrontend } = require("../bundler")
|
|
8
|
-
const { resolveDevPorts } = require("../ports")
|
|
8
|
+
const { DEFAULT_BACKEND_DEV_PORT, DEFAULT_FRONTEND_DEV_PORT, resolveDevPorts } = require("../ports")
|
|
9
9
|
const { declaredSecrets, loadLocalEnv } = require("../secrets")
|
|
10
10
|
|
|
11
11
|
const DEFAULT_IMAGE =
|
|
12
12
|
process.env.PALETTE_DEV_IMAGE || "ghcr.io/palette-lab/platform-dev:latest"
|
|
13
|
-
const FRONTEND_PORT = Number(process.env.PALETTE_FRONTEND_PORT ||
|
|
14
|
-
const BACKEND_PORT = Number(process.env.PALETTE_BACKEND_PORT ||
|
|
13
|
+
const FRONTEND_PORT = Number(process.env.PALETTE_FRONTEND_PORT || DEFAULT_FRONTEND_DEV_PORT)
|
|
14
|
+
const BACKEND_PORT = Number(process.env.PALETTE_BACKEND_PORT || DEFAULT_BACKEND_DEV_PORT)
|
|
15
15
|
|
|
16
16
|
function ok(message) {
|
|
17
17
|
console.log(`OK ${message}`)
|
package/lib/commands/logs.js
CHANGED
|
@@ -31,6 +31,39 @@ async function fetchLogs(env, pluginId, tail) {
|
|
|
31
31
|
return res.json()
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
function formatLogEvent(e) {
|
|
35
|
+
const ts = e.created_at
|
|
36
|
+
const tag = String(e.event_type || "event").padEnd(11)
|
|
37
|
+
const route = e.route || "-"
|
|
38
|
+
const code = e.status_code != null ? `[${e.status_code}]` : ""
|
|
39
|
+
const err = e.error_message ? ` err=${e.error_message}` : ""
|
|
40
|
+
return `${ts} ${tag} ${route} ${code}${err}`
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function streamPluginLogs({
|
|
44
|
+
env,
|
|
45
|
+
pluginId,
|
|
46
|
+
tail = 50,
|
|
47
|
+
follow = false,
|
|
48
|
+
json = false,
|
|
49
|
+
intervalMs = 3000,
|
|
50
|
+
log = console.log,
|
|
51
|
+
}) {
|
|
52
|
+
let lastSeenId = 0
|
|
53
|
+
do {
|
|
54
|
+
const data = await fetchLogs(env, pluginId, tail)
|
|
55
|
+
const events = data.events || []
|
|
56
|
+
const fresh = events.filter((e) => e.id > lastSeenId)
|
|
57
|
+
if (fresh.length) lastSeenId = Math.max(...fresh.map((e) => e.id))
|
|
58
|
+
if (json) {
|
|
59
|
+
for (const e of fresh) log(JSON.stringify(e))
|
|
60
|
+
} else {
|
|
61
|
+
for (const e of fresh) log(formatLogEvent(e))
|
|
62
|
+
}
|
|
63
|
+
if (follow) await new Promise((r) => setTimeout(r, intervalMs))
|
|
64
|
+
} while (follow)
|
|
65
|
+
}
|
|
66
|
+
|
|
34
67
|
async function run(argv, { cwd }) {
|
|
35
68
|
const json = argv.includes("--json")
|
|
36
69
|
const follow = argv.includes("--follow") || argv.includes("-f")
|
|
@@ -68,32 +101,15 @@ async function run(argv, { cwd }) {
|
|
|
68
101
|
process.exit(1)
|
|
69
102
|
}
|
|
70
103
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
console.error(`[pltt] ${err.message}`)
|
|
78
|
-
process.exit(1)
|
|
79
|
-
}
|
|
80
|
-
const events = data.events || []
|
|
81
|
-
const fresh = events.filter((e) => e.id > lastSeenId)
|
|
82
|
-
if (fresh.length) lastSeenId = Math.max(...fresh.map((e) => e.id))
|
|
83
|
-
if (json) {
|
|
84
|
-
for (const e of fresh) console.log(JSON.stringify(e))
|
|
85
|
-
} else {
|
|
86
|
-
for (const e of fresh) {
|
|
87
|
-
const ts = e.created_at
|
|
88
|
-
const tag = e.event_type.padEnd(11)
|
|
89
|
-
const route = e.route || "-"
|
|
90
|
-
const code = e.status_code != null ? `[${e.status_code}]` : ""
|
|
91
|
-
const err = e.error_message ? ` err=${e.error_message}` : ""
|
|
92
|
-
console.log(`${ts} ${tag} ${route} ${code}${err}`)
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
if (follow) await new Promise((r) => setTimeout(r, 3000))
|
|
96
|
-
} while (follow)
|
|
104
|
+
try {
|
|
105
|
+
await streamPluginLogs({ env, pluginId, tail, follow, json })
|
|
106
|
+
} catch (err) {
|
|
107
|
+
console.error(`[pltt] ${err.message}`)
|
|
108
|
+
process.exit(1)
|
|
109
|
+
}
|
|
97
110
|
}
|
|
98
111
|
|
|
99
112
|
module.exports = run
|
|
113
|
+
module.exports.fetchLogs = fetchLogs
|
|
114
|
+
module.exports.formatLogEvent = formatLogEvent
|
|
115
|
+
module.exports.streamPluginLogs = streamPluginLogs
|
package/lib/dev-simulator.js
CHANGED
|
@@ -242,6 +242,7 @@ function startBackend(cwd, devDir, manifest, backendPort) {
|
|
|
242
242
|
if (sdkPath) {
|
|
243
243
|
env.PYTHONPATH = [sdkPath, env.PYTHONPATH].filter(Boolean).join(path.delimiter)
|
|
244
244
|
}
|
|
245
|
+
console.log(`[pltt] backend logs: ${backendEntry} via uvicorn on http://localhost:${backendPort}`)
|
|
245
246
|
const child = spawn(
|
|
246
247
|
python,
|
|
247
248
|
["-m", "uvicorn", `${path.basename(runner, ".py")}:app`, "--host", "127.0.0.1", "--port", String(backendPort), "--reload", "--reload-dir", path.dirname(absEntry)],
|
|
@@ -444,10 +445,12 @@ async function startFrontend(cwd, devDir, manifest, frontendPort, backendPort) {
|
|
|
444
445
|
const server = http.createServer((req, res) => {
|
|
445
446
|
const url = new URL(req.url || "/", `http://127.0.0.1:${frontendPort}`)
|
|
446
447
|
if (url.pathname === "/simulator.js") {
|
|
448
|
+
console.log(`[pltt] frontend GET ${url.pathname} -> 200`)
|
|
447
449
|
res.writeHead(200, { "Content-Type": "text/javascript; charset=utf-8", "Cache-Control": "no-store" })
|
|
448
450
|
res.end(fs.readFileSync(bundlePath))
|
|
449
451
|
return
|
|
450
452
|
}
|
|
453
|
+
console.log(`[pltt] frontend GET ${url.pathname} -> 200`)
|
|
451
454
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store" })
|
|
452
455
|
res.end(indexHtml(manifest))
|
|
453
456
|
})
|
|
@@ -467,6 +470,7 @@ async function startSimulator({ cwd, frontendPort, backendPort }) {
|
|
|
467
470
|
console.log("[pltt] running local SDK simulator (no Docker)")
|
|
468
471
|
console.log(`[pltt] app: http://localhost:${frontendPort}/`)
|
|
469
472
|
if (backend) console.log(`[pltt] backend: http://localhost:${backendPort}/api/v1/plugins/${manifest.id}`)
|
|
473
|
+
console.log("[pltt] logs: streaming frontend requests, frontend bundle rebuilds, and backend output")
|
|
470
474
|
console.log("[pltt] use `pltt dev --platform` for full Docker platform parity")
|
|
471
475
|
|
|
472
476
|
const stop = async () => {
|
package/lib/environments.js
CHANGED
package/lib/ports.js
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
const net = require("net")
|
|
4
4
|
|
|
5
|
+
const DEFAULT_FRONTEND_DEV_PORT = 7321
|
|
6
|
+
const DEFAULT_BACKEND_DEV_PORT = 8732
|
|
7
|
+
|
|
5
8
|
function canBindPort(port, host = "0.0.0.0") {
|
|
6
9
|
return new Promise((resolve) => {
|
|
7
10
|
const server = net.createServer()
|
|
@@ -50,8 +53,8 @@ async function findFreePort(preferred, { host = "0.0.0.0", maxAttempts = 100 } =
|
|
|
50
53
|
}
|
|
51
54
|
|
|
52
55
|
async function resolveDevPorts({
|
|
53
|
-
frontend = process.env.PALETTE_FRONTEND_PORT ||
|
|
54
|
-
backend = process.env.PALETTE_BACKEND_PORT ||
|
|
56
|
+
frontend = process.env.PALETTE_FRONTEND_PORT || DEFAULT_FRONTEND_DEV_PORT,
|
|
57
|
+
backend = process.env.PALETTE_BACKEND_PORT || DEFAULT_BACKEND_DEV_PORT,
|
|
55
58
|
host = "0.0.0.0",
|
|
56
59
|
} = {}) {
|
|
57
60
|
const frontendPort = await findFreePort(frontend, { host })
|
|
@@ -65,4 +68,12 @@ async function resolveDevPorts({
|
|
|
65
68
|
}
|
|
66
69
|
}
|
|
67
70
|
|
|
68
|
-
module.exports = {
|
|
71
|
+
module.exports = {
|
|
72
|
+
DEFAULT_FRONTEND_DEV_PORT,
|
|
73
|
+
DEFAULT_BACKEND_DEV_PORT,
|
|
74
|
+
canBindPort,
|
|
75
|
+
canConnectPort,
|
|
76
|
+
isPortAvailable,
|
|
77
|
+
findFreePort,
|
|
78
|
+
resolveDevPorts,
|
|
79
|
+
}
|
package/package.json
CHANGED
|
@@ -10,8 +10,8 @@ services:
|
|
|
10
10
|
platform:
|
|
11
11
|
image: ${PALETTE_DEV_IMAGE:-ghcr.io/palette-lab/platform-dev:latest}
|
|
12
12
|
ports:
|
|
13
|
-
- "${PALETTE_FRONTEND_PORT:-
|
|
14
|
-
- "${PALETTE_BACKEND_PORT:-
|
|
13
|
+
- "${PALETTE_FRONTEND_PORT:-7321}:3000"
|
|
14
|
+
- "${PALETTE_BACKEND_PORT:-8732}:8000"
|
|
15
15
|
environment:
|
|
16
16
|
PALETTE_DEV_MODE: "1"
|
|
17
17
|
NEXT_PUBLIC_PALETTE_DEV_MODE: "1"
|
|
@@ -19,9 +19,9 @@ services:
|
|
|
19
19
|
DATABASE_URL: "postgresql+asyncpg://postgres:postgres@postgres:5432/app"
|
|
20
20
|
REDIS_URL: "redis://redis:6379/0"
|
|
21
21
|
JWT_SECRET: "dev-secret-do-not-use-in-prod"
|
|
22
|
-
FRONTEND_URL: "http://localhost:${PALETTE_FRONTEND_PORT:-
|
|
23
|
-
BACKEND_BASE_URL: "http://localhost:${PALETTE_BACKEND_PORT:-
|
|
24
|
-
NEXT_PUBLIC_API_URL: "http://localhost:${PALETTE_BACKEND_PORT:-
|
|
22
|
+
FRONTEND_URL: "http://localhost:${PALETTE_FRONTEND_PORT:-7321}"
|
|
23
|
+
BACKEND_BASE_URL: "http://localhost:${PALETTE_BACKEND_PORT:-8732}"
|
|
24
|
+
NEXT_PUBLIC_API_URL: "http://localhost:${PALETTE_BACKEND_PORT:-8732}"
|
|
25
25
|
STORAGE_BACKEND: "local"
|
|
26
26
|
LOCAL_STORAGE_DIR: "/srv/storage"
|
|
27
27
|
# Disable optional features that need real credentials
|