@palettelab/cli 0.3.0 → 0.3.1

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.
Files changed (50) hide show
  1. package/README.md +11 -7
  2. package/bin/{palette.js → pltt.js} +1 -1
  3. package/lib/bundler.js +73 -4
  4. package/lib/cli.js +37 -12
  5. package/lib/commands/build.js +2 -0
  6. package/lib/commands/dev.js +37 -2
  7. package/lib/commands/doctor.js +143 -0
  8. package/lib/commands/init.js +45 -13
  9. package/lib/commands/logs.js +99 -0
  10. package/lib/commands/package.js +64 -0
  11. package/lib/commands/publish.js +50 -6
  12. package/lib/commands/status.js +80 -0
  13. package/lib/commands/test.js +376 -0
  14. package/lib/environments.js +1 -1
  15. package/lib/manifest.js +253 -8
  16. package/package.json +7 -6
  17. package/platform-dev/docker-compose.yml +4 -1
  18. package/template-fallback/backend/api/main.py +9 -3
  19. package/template-fallback/palette-plugin.json +24 -1
  20. package/template-fallback/pyproject.toml +1 -1
  21. package/template-fallback/templates/agent-tool/README.md +4 -0
  22. package/template-fallback/templates/agent-tool/backend/api/main.py +14 -0
  23. package/template-fallback/templates/agent-tool/backend/tools/echo.py +15 -0
  24. package/template-fallback/templates/agent-tool/package.json +5 -0
  25. package/template-fallback/templates/agent-tool/palette-plugin.json +29 -0
  26. package/template-fallback/templates/agent-tool/pyproject.toml +5 -0
  27. package/template-fallback/templates/dashboard/README.md +3 -0
  28. package/template-fallback/templates/dashboard/backend/api/main.py +23 -0
  29. package/template-fallback/templates/dashboard/frontend/src/index.tsx +46 -0
  30. package/template-fallback/templates/dashboard/package.json +9 -0
  31. package/template-fallback/templates/dashboard/palette-plugin.json +26 -0
  32. package/template-fallback/templates/dashboard/pyproject.toml +5 -0
  33. package/template-fallback/templates/database/README.md +7 -0
  34. package/template-fallback/templates/database/backend/api/main.py +38 -0
  35. package/template-fallback/templates/database/backend/api/models.py +11 -0
  36. package/template-fallback/templates/database/backend/migrations/001_init.py +26 -0
  37. package/template-fallback/templates/database/frontend/src/index.tsx +57 -0
  38. package/template-fallback/templates/database/package.json +6 -0
  39. package/template-fallback/templates/database/palette-plugin.json +26 -0
  40. package/template-fallback/templates/database/pyproject.toml +5 -0
  41. package/template-fallback/templates/external-service/README.md +4 -0
  42. package/template-fallback/templates/external-service/backend/api/main.py +28 -0
  43. package/template-fallback/templates/external-service/frontend/src/index.tsx +26 -0
  44. package/template-fallback/templates/external-service/package.json +6 -0
  45. package/template-fallback/templates/external-service/palette-plugin.json +26 -0
  46. package/template-fallback/templates/external-service/pyproject.toml +5 -0
  47. package/template-fallback/templates/frontend-only/README.md +7 -0
  48. package/template-fallback/templates/frontend-only/frontend/src/index.tsx +16 -0
  49. package/template-fallback/templates/frontend-only/package.json +9 -0
  50. package/template-fallback/templates/frontend-only/palette-plugin.json +25 -0
@@ -0,0 +1,99 @@
1
+ "use strict"
2
+
3
+ const fs = require("fs")
4
+ const path = require("path")
5
+ const { resolveEnvironment, parseFlags } = require("../environments")
6
+ const { loadManifest } = require("../manifest")
7
+
8
+ function readLastPublish(cwd) {
9
+ const p = path.join(cwd, ".palette", "last-publish.json")
10
+ if (!fs.existsSync(p)) return null
11
+ try {
12
+ return JSON.parse(fs.readFileSync(p, "utf8"))
13
+ } catch {
14
+ return null
15
+ }
16
+ }
17
+
18
+ function getOpt(argv, name, def) {
19
+ const i = argv.indexOf(name)
20
+ if (i >= 0 && argv[i + 1]) return argv[i + 1]
21
+ return def
22
+ }
23
+
24
+ async function fetchLogs(env, pluginId, tail) {
25
+ const url = `${env.url}/api/v1/appstore/plugins/${encodeURIComponent(pluginId)}/logs?tail=${tail}`
26
+ const res = await fetch(url, { headers: { Authorization: `Bearer ${env.token}` } })
27
+ if (!res.ok) {
28
+ const text = await res.text()
29
+ throw new Error(`GET ${url} → ${res.status}: ${text}`)
30
+ }
31
+ return res.json()
32
+ }
33
+
34
+ async function run(argv, { cwd }) {
35
+ const json = argv.includes("--json")
36
+ const follow = argv.includes("--follow") || argv.includes("-f")
37
+ const tail = parseInt(getOpt(argv, "--tail", "50"), 10) || 50
38
+
39
+ const positional = argv.filter((a) => !a.startsWith("-"))
40
+ let pluginId = positional[0]
41
+ const last = readLastPublish(cwd)
42
+ if (!pluginId) {
43
+ if (last && last.plugin_id) pluginId = last.plugin_id
44
+ else {
45
+ try {
46
+ pluginId = loadManifest(cwd).id
47
+ } catch {
48
+ // ignore
49
+ }
50
+ }
51
+ }
52
+ if (!pluginId) {
53
+ console.error("[palette] no plugin id provided and no manifest found.")
54
+ process.exit(1)
55
+ }
56
+
57
+ const { flags } = parseFlags(argv)
58
+ if (!flags.env && last && last.env) flags.env = last.env
59
+ let env
60
+ try {
61
+ env = resolveEnvironment({ cwd, flags })
62
+ } catch (err) {
63
+ console.error(`[palette] ${err.message}`)
64
+ process.exit(1)
65
+ }
66
+ if (!env.token) {
67
+ console.error(`[palette] no publish token for "${env.name}". set $${env.token_env}.`)
68
+ process.exit(1)
69
+ }
70
+
71
+ let lastSeenId = 0
72
+ do {
73
+ let data
74
+ try {
75
+ data = await fetchLogs(env, pluginId, tail)
76
+ } catch (err) {
77
+ console.error(`[palette] ${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)
97
+ }
98
+
99
+ module.exports = run
@@ -0,0 +1,64 @@
1
+ "use strict"
2
+
3
+ const fs = require("fs")
4
+ const path = require("path")
5
+ const crypto = require("crypto")
6
+ const { loadManifest, validateManifest } = require("../manifest")
7
+ const { bundleFrontend, bundleBackend } = require("../bundler")
8
+
9
+ function sha256(buf) {
10
+ return crypto.createHash("sha256").update(buf).digest("hex")
11
+ }
12
+
13
+ async function run(argv, { cwd }) {
14
+ const json = argv.includes("--json")
15
+ const manifest = loadManifest(cwd)
16
+ const errors = validateManifest(manifest)
17
+ if (errors.length) {
18
+ console.error("[palette] manifest invalid:")
19
+ for (const e of errors) console.error(` - ${e}`)
20
+ process.exit(1)
21
+ }
22
+
23
+ const distDir = path.join(cwd, "dist")
24
+ fs.mkdirSync(distDir, { recursive: true })
25
+
26
+ const frontend = manifest.frontend
27
+ ? await bundleFrontend(cwd, manifest.frontend.entry || "./frontend/src/index.tsx")
28
+ : null
29
+ const backend = manifest.backend ? await bundleBackend(cwd) : null
30
+
31
+ const out = path.join(distDir, `${manifest.id}-${manifest.version}.tar.gz`)
32
+ // Build a simple tar by piping through node-tar via spawn — avoid extra dep
33
+ // by writing files into a staging dir and shelling out to `tar`. The
34
+ // platform-dev image already ships with tar, and any developer running this
35
+ // CLI will have it too on macOS/Linux.
36
+ const stage = fs.mkdtempSync(path.join(require("os").tmpdir(), "palette-pkg-"))
37
+ fs.writeFileSync(path.join(stage, "palette-plugin.json"), JSON.stringify(manifest, null, 2))
38
+ if (frontend) fs.writeFileSync(path.join(stage, "frontend.mjs"), frontend)
39
+ if (backend) fs.writeFileSync(path.join(stage, "backend.tar.gz"), backend)
40
+
41
+ const { spawnSync } = require("child_process")
42
+ const r = spawnSync("tar", ["-czf", out, "-C", stage, "."], { stdio: "inherit" })
43
+ if (r.status !== 0) {
44
+ console.error("[palette] tar failed")
45
+ process.exit(1)
46
+ }
47
+
48
+ const buf = fs.readFileSync(out)
49
+ const result = {
50
+ id: manifest.id,
51
+ version: manifest.version,
52
+ path: out,
53
+ bytes: buf.length,
54
+ sha256: sha256(buf),
55
+ }
56
+ if (json) {
57
+ console.log(JSON.stringify(result, null, 2))
58
+ } else {
59
+ console.log(`[palette] packaged ${manifest.id}@${manifest.version} → ${out} (${buf.length} bytes)`)
60
+ console.log(`[palette] sha256: ${result.sha256}`)
61
+ }
62
+ }
63
+
64
+ module.exports = run
@@ -1,6 +1,8 @@
1
1
  "use strict"
2
2
 
3
3
  const crypto = require("crypto")
4
+ const fs = require("fs")
5
+ const path = require("path")
4
6
  const { loadManifest, validateManifest } = require("../manifest")
5
7
  const { bundleFrontend, bundleBackend } = require("../bundler")
6
8
  const {
@@ -85,9 +87,14 @@ async function run(argv, { cwd }) {
85
87
  `[palette] publishing ${manifest.id}@${manifest.version} → ${env.name} (${env.url})`,
86
88
  )
87
89
 
88
- console.log("[palette] bundling frontend")
89
- const frontend = await bundleFrontend(cwd, manifest.frontend?.entry || "./frontend/src/index.tsx")
90
- console.log(`[palette] ${frontend.length} bytes`)
90
+ let frontend = null
91
+ if (manifest.frontend?.entry) {
92
+ console.log("[palette] bundling frontend")
93
+ frontend = await bundleFrontend(cwd, manifest.frontend.entry)
94
+ console.log(`[palette] ${frontend.length} bytes`)
95
+ } else {
96
+ console.log("[palette] no frontend declared")
97
+ }
91
98
 
92
99
  console.log("[palette] bundling backend")
93
100
  const backend = await bundleBackend(cwd)
@@ -107,15 +114,18 @@ async function run(argv, { cwd }) {
107
114
  })
108
115
 
109
116
  console.log("[palette] uploading")
110
- await Promise.all([
111
- put(signed.frontend_upload_url, frontend, "application/javascript"),
117
+ const uploads = [
112
118
  put(signed.backend_upload_url, backend, "application/gzip"),
113
119
  put(
114
120
  signed.manifest_upload_url,
115
121
  Buffer.from(JSON.stringify(manifest, null, 2)),
116
122
  "application/json",
117
123
  ),
118
- ])
124
+ ]
125
+ if (frontend) {
126
+ uploads.push(put(signed.frontend_upload_url, frontend, "application/javascript"))
127
+ }
128
+ await Promise.all(uploads)
119
129
 
120
130
  console.log("[palette] finalizing")
121
131
  const record = await api("/api/v1/appstore/publish", {
@@ -129,10 +139,44 @@ async function run(argv, { cwd }) {
129
139
  },
130
140
  })
131
141
 
142
+ try {
143
+ const dir = path.join(cwd, ".palette")
144
+ fs.mkdirSync(dir, { recursive: true })
145
+ fs.writeFileSync(
146
+ path.join(dir, "last-publish.json"),
147
+ JSON.stringify(
148
+ {
149
+ id: record.id,
150
+ plugin_id: record.plugin_id,
151
+ version: record.version,
152
+ env: env.name,
153
+ url: env.url,
154
+ preview_url: record.preview_url,
155
+ published_at: new Date().toISOString(),
156
+ },
157
+ null,
158
+ 2,
159
+ ),
160
+ )
161
+ } catch (err) {
162
+ // best-effort
163
+ }
164
+
165
+ if (flags.json) {
166
+ console.log(JSON.stringify(record, null, 2))
167
+ return
168
+ }
169
+
132
170
  console.log(
133
171
  `[palette] published ${record.plugin_id}@${record.version} (status=${record.status})`,
134
172
  )
135
173
  console.log(`[palette] awaiting superadmin review on ${env.url}`)
174
+ if (record.review_url) {
175
+ console.log(`[palette] review queue: ${record.review_url}`)
176
+ }
177
+ if (record.preview_url) {
178
+ console.log(`[palette] preview: ${record.preview_url}`)
179
+ }
136
180
  console.log(`[palette] once approved, live at ${env.url}${record.catalog_url}`)
137
181
  }
138
182
 
@@ -0,0 +1,80 @@
1
+ "use strict"
2
+
3
+ const fs = require("fs")
4
+ const path = require("path")
5
+ const { resolveEnvironment, parseFlags } = require("../environments")
6
+
7
+ function readLastPublish(cwd) {
8
+ const p = path.join(cwd, ".palette", "last-publish.json")
9
+ if (!fs.existsSync(p)) return null
10
+ try {
11
+ return JSON.parse(fs.readFileSync(p, "utf8"))
12
+ } catch {
13
+ return null
14
+ }
15
+ }
16
+
17
+ async function run(argv, { cwd }) {
18
+ const json = argv.includes("--json")
19
+ const positional = argv.filter((a) => !a.startsWith("-"))
20
+ const last = readLastPublish(cwd)
21
+ const publishId = positional[0] || (last && last.id)
22
+
23
+ if (!publishId) {
24
+ console.error(
25
+ "[palette] no publish id provided and no .palette/last-publish.json found.",
26
+ )
27
+ console.error(" usage: pltt status <publish-id> [--env <name>]")
28
+ process.exit(1)
29
+ }
30
+
31
+ const { flags } = parseFlags(argv)
32
+ if (!flags.env && last && last.env) flags.env = last.env
33
+
34
+ let env
35
+ try {
36
+ env = resolveEnvironment({ cwd, flags })
37
+ } catch (err) {
38
+ console.error(`[palette] ${err.message}`)
39
+ process.exit(1)
40
+ }
41
+
42
+ if (!env.token) {
43
+ console.error(
44
+ `[palette] no publish token for environment "${env.name}". set $${env.token_env}.`,
45
+ )
46
+ process.exit(1)
47
+ }
48
+
49
+ const url = `${env.url}/api/v1/appstore/publishes/${publishId}`
50
+ const res = await fetch(url, {
51
+ headers: { Authorization: `Bearer ${env.token}` },
52
+ })
53
+ if (!res.ok) {
54
+ const text = await res.text()
55
+ console.error(`[palette] GET ${url} → ${res.status}: ${text}`)
56
+ process.exit(1)
57
+ }
58
+ const data = await res.json()
59
+
60
+ if (json) {
61
+ console.log(JSON.stringify(data, null, 2))
62
+ return
63
+ }
64
+
65
+ console.log(`[palette] ${data.plugin_id}@${data.version}`)
66
+ console.log(` status: ${data.status}`)
67
+ if (data.review_decision) console.log(` decision: ${data.review_decision}`)
68
+ if (data.review_reason) console.log(` reason: ${data.review_reason}`)
69
+ if (data.preview_url) console.log(` preview: ${data.preview_url}`)
70
+ console.log(` published: ${data.published_at}`)
71
+ if (data.report) {
72
+ console.log(` risk: ${data.report.risk_score.toUpperCase()}`)
73
+ console.log(
74
+ ` permissions=${data.report.permission_risk}, migrations=${data.report.migration_risk}, network=${data.report.external_network_risk}`,
75
+ )
76
+ console.log(` bundle=${data.report.bundle_size_bytes} bytes, sandbox=${data.report.sandbox_enabled}`)
77
+ }
78
+ }
79
+
80
+ module.exports = run