@palettelab/cli 0.2.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.
- package/README.md +11 -7
- package/bin/{palette.js → pltt.js} +1 -1
- package/lib/bundler.js +73 -4
- package/lib/cli.js +37 -12
- package/lib/commands/build.js +2 -0
- package/lib/commands/dev.js +56 -1
- package/lib/commands/doctor.js +143 -0
- package/lib/commands/init.js +45 -13
- package/lib/commands/logs.js +99 -0
- package/lib/commands/package.js +64 -0
- package/lib/commands/publish.js +50 -6
- package/lib/commands/status.js +80 -0
- package/lib/commands/test.js +376 -0
- package/lib/environments.js +1 -1
- package/lib/manifest.js +253 -8
- package/package.json +7 -6
- package/platform-dev/docker-compose.yml +4 -1
- package/template-fallback/backend/api/main.py +9 -3
- package/template-fallback/palette-plugin.json +24 -1
- package/template-fallback/pyproject.toml +1 -1
- package/template-fallback/templates/agent-tool/README.md +4 -0
- package/template-fallback/templates/agent-tool/backend/api/main.py +14 -0
- package/template-fallback/templates/agent-tool/backend/tools/echo.py +15 -0
- package/template-fallback/templates/agent-tool/package.json +5 -0
- package/template-fallback/templates/agent-tool/palette-plugin.json +29 -0
- package/template-fallback/templates/agent-tool/pyproject.toml +5 -0
- package/template-fallback/templates/dashboard/README.md +3 -0
- package/template-fallback/templates/dashboard/backend/api/main.py +23 -0
- package/template-fallback/templates/dashboard/frontend/src/index.tsx +46 -0
- package/template-fallback/templates/dashboard/package.json +9 -0
- package/template-fallback/templates/dashboard/palette-plugin.json +26 -0
- package/template-fallback/templates/dashboard/pyproject.toml +5 -0
- package/template-fallback/templates/database/README.md +7 -0
- package/template-fallback/templates/database/backend/api/main.py +38 -0
- package/template-fallback/templates/database/backend/api/models.py +11 -0
- package/template-fallback/templates/database/backend/migrations/001_init.py +26 -0
- package/template-fallback/templates/database/frontend/src/index.tsx +57 -0
- package/template-fallback/templates/database/package.json +6 -0
- package/template-fallback/templates/database/palette-plugin.json +26 -0
- package/template-fallback/templates/database/pyproject.toml +5 -0
- package/template-fallback/templates/external-service/README.md +4 -0
- package/template-fallback/templates/external-service/backend/api/main.py +28 -0
- package/template-fallback/templates/external-service/frontend/src/index.tsx +26 -0
- package/template-fallback/templates/external-service/package.json +6 -0
- package/template-fallback/templates/external-service/palette-plugin.json +26 -0
- package/template-fallback/templates/external-service/pyproject.toml +5 -0
- package/template-fallback/templates/frontend-only/README.md +7 -0
- package/template-fallback/templates/frontend-only/frontend/src/index.tsx +16 -0
- package/template-fallback/templates/frontend-only/package.json +9 -0
- 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
|
package/lib/commands/publish.js
CHANGED
|
@@ -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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
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
|