@atomservice/functions-cli 0.1.7 → 0.1.9
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/package.json +4 -2
- package/src/build.ts +200 -35
- package/src/client.ts +4 -4
- package/src/commands/deploy.ts +277 -42
- package/src/commands/dev.ts +1 -1
- package/src/commands/function.ts +24 -9
- package/src/commands/init.ts +1 -0
- package/src/commands/invoke.ts +187 -16
- package/src/commands/list.ts +35 -5
- package/src/commands/logs.ts +88 -12
- package/src/commands/rollback.ts +155 -29
- package/src/commands/whoami.ts +11 -7
- package/src/consts.ts +2 -0
- package/src/context.ts +11 -6
- package/src/scaffold.ts +58 -42
- package/src/types.ts +2 -1
- package/src/utils.ts +5 -0
package/src/context.ts
CHANGED
|
@@ -57,12 +57,17 @@ export async function requireServerContext(): Promise<CliContext> {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
const servers = resolveServers(ctx.manifest)
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
60
|
+
const creds = await Promise.all(
|
|
61
|
+
servers.map(async (server) => {
|
|
62
|
+
const host = hostOf(server)
|
|
63
|
+
const cred = await getCredential(host)
|
|
64
|
+
return { server, host, token: cred?.token ?? null }
|
|
65
|
+
}),
|
|
66
|
+
)
|
|
67
|
+
const fallbackToken = creds.find((c) => c.token)?.token
|
|
68
|
+
const targets: ServerTarget[] = creds
|
|
69
|
+
.filter((c) => c.token || fallbackToken)
|
|
70
|
+
.map((c) => ({ server: c.server, host: c.host, token: (c.token || fallbackToken)! }))
|
|
66
71
|
|
|
67
72
|
return { ...ctx, targets }
|
|
68
73
|
}
|
package/src/scaffold.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from "node:path"
|
|
2
|
-
import {
|
|
2
|
+
import { snakeCase } from "change-case"
|
|
3
|
+
import { BUNDLE_FILE, FUNCTION_CONFIG_FILE, FUNCTION_FILE, RUST_SDK_VERSION, SDK_PACKAGE } from "./consts.ts"
|
|
3
4
|
import type { BundleConfig, FunctionKind, Language } from "./types.ts"
|
|
4
5
|
import { ensureDir, writeJson } from "./utils.ts"
|
|
5
6
|
|
|
@@ -87,9 +88,12 @@ export async function scaffoldFunction(
|
|
|
87
88
|
kind: FunctionKind = "function",
|
|
88
89
|
): Promise<void> {
|
|
89
90
|
if (language === "rust") {
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
const fnDir = path.join(bundleDir, "src", "functions", fn)
|
|
92
|
+
const fnPath = path.join(fnDir, "function.rs")
|
|
93
|
+
ensureDir(fnDir)
|
|
94
|
+
await Bun.write(fnPath, rustFunctionSource(snakeCase(fn)))
|
|
95
|
+
await writeJson(path.join(fnDir, FUNCTION_CONFIG_FILE), {})
|
|
96
|
+
await rustAutoRegister(bundleDir, fn)
|
|
93
97
|
return
|
|
94
98
|
}
|
|
95
99
|
const fnDir = path.join(bundleDir, fn)
|
|
@@ -109,47 +113,30 @@ name = ${JSON.stringify(bundle)}
|
|
|
109
113
|
path = "src/main.rs"
|
|
110
114
|
|
|
111
115
|
[dependencies]
|
|
112
|
-
|
|
116
|
+
atomfn = "${RUST_SDK_VERSION}"
|
|
113
117
|
serde = { version = "1", features = ["derive"] }
|
|
114
118
|
serde_json = "1"
|
|
119
|
+
validator = { version = "0.19", features = ["derive"] }
|
|
115
120
|
`
|
|
116
121
|
}
|
|
117
122
|
|
|
118
123
|
function rustMainSource(): string {
|
|
119
|
-
return `
|
|
120
|
-
use
|
|
121
|
-
|
|
122
|
-
#[derive(Deserialize)]
|
|
123
|
-
struct GreetInput {
|
|
124
|
-
name: String,
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
#[derive(Serialize)]
|
|
128
|
-
struct GreetOutput {
|
|
129
|
-
greeting: String,
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
fn greet(input: GreetInput, ctx: &Ctx) -> Result<GreetOutput, FunctionError> {
|
|
133
|
-
ctx.info("greeting");
|
|
134
|
-
Ok(GreetOutput { greeting: format!("hello, {}", input.name) })
|
|
135
|
-
}
|
|
124
|
+
return `mod functions;
|
|
125
|
+
use atomfn::serve;
|
|
126
|
+
use functions::*;
|
|
136
127
|
|
|
137
|
-
|
|
138
|
-
let mut registry = Registry::new();
|
|
139
|
-
registry.function("greet", greet);
|
|
140
|
-
serve(registry);
|
|
141
|
-
}
|
|
128
|
+
serve! {}
|
|
142
129
|
`
|
|
143
130
|
}
|
|
144
131
|
|
|
145
|
-
function rustFunctionSource(
|
|
146
|
-
return
|
|
147
|
-
// 并在 ${BUNDLE_FILE} 的 functions 数组中加入 "${fn}"。
|
|
148
|
-
use atomfunctions::{Ctx, FunctionError};
|
|
132
|
+
function rustFunctionSource(rustName: string): string {
|
|
133
|
+
return `use atomfn::{Ctx, FunctionError};
|
|
149
134
|
use serde::{Deserialize, Serialize};
|
|
135
|
+
use validator::Validate;
|
|
150
136
|
|
|
151
|
-
#[derive(Deserialize)]
|
|
137
|
+
#[derive(Deserialize, Validate)]
|
|
152
138
|
pub struct Input {
|
|
139
|
+
#[validate(length(min = 1, max = 100))]
|
|
153
140
|
pub name: String,
|
|
154
141
|
}
|
|
155
142
|
|
|
@@ -158,33 +145,62 @@ pub struct Output {
|
|
|
158
145
|
pub greeting: String,
|
|
159
146
|
}
|
|
160
147
|
|
|
161
|
-
pub fn ${
|
|
148
|
+
pub fn ${rustName}(input: Input, _ctx: &Ctx) -> Result<Output, FunctionError> {
|
|
162
149
|
Ok(Output { greeting: format!("hello, {}", input.name) })
|
|
163
150
|
}
|
|
164
151
|
`
|
|
165
152
|
}
|
|
166
153
|
|
|
167
|
-
function
|
|
168
|
-
|
|
169
|
-
WORKDIR /build
|
|
170
|
-
COPY . .
|
|
171
|
-
RUN cargo build --release
|
|
154
|
+
async function rustAutoRegister(bundleDir: string, slug: string): Promise<void> {
|
|
155
|
+
const rustName = snakeCase(slug)
|
|
172
156
|
|
|
173
|
-
|
|
157
|
+
const modPath = path.join(bundleDir, "src", "functions", "mod.rs")
|
|
158
|
+
const modContent = await Bun.file(modPath)
|
|
159
|
+
.text()
|
|
160
|
+
.catch(() => "")
|
|
161
|
+
const modDecl = `#[path = "${slug}/function.rs"]
|
|
162
|
+
pub mod ${rustName};
|
|
163
|
+
pub use ${rustName}::*;`
|
|
164
|
+
if (!modContent.includes(`pub mod ${rustName};`)) {
|
|
165
|
+
const newMod = modContent.trimEnd() ? `${modContent.trimEnd()}\n${modDecl}\n` : `${modDecl}\n`
|
|
166
|
+
await Bun.write(modPath, newMod)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const mainPath = path.join(bundleDir, "src", "main.rs")
|
|
170
|
+
const mainContent = await Bun.file(mainPath).text()
|
|
171
|
+
const macroMatch = mainContent.match(/serve!\s*\{([^}]*)\}/)
|
|
172
|
+
if (!macroMatch) return
|
|
173
|
+
|
|
174
|
+
const body = macroMatch[1] ?? ""
|
|
175
|
+
const entryRegex = /(stream\s+)?([a-z][a-z0-9_]*)/g
|
|
176
|
+
for (let match = entryRegex.exec(body); match !== null; match = entryRegex.exec(body)) {
|
|
177
|
+
if (match[2] === rustName) return
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const entries = body.trim() ? `${body.trim()}, ${rustName}` : rustName
|
|
181
|
+
const newMain = mainContent.replace(/serve!\s*\{[^}]*\}/, `serve! { ${entries} }`)
|
|
182
|
+
await Bun.write(mainPath, newMain)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function rustContainerfile(): string {
|
|
186
|
+
return `FROM debian:bookworm-slim
|
|
174
187
|
RUN apt-get update \\
|
|
175
188
|
&& apt-get install -y --no-install-recommends ca-certificates \\
|
|
176
189
|
&& rm -rf /var/lib/apt/lists/*
|
|
177
|
-
|
|
190
|
+
WORKDIR /app
|
|
191
|
+
COPY bundle /app/bundle
|
|
192
|
+
COPY functions.config.json /app/functions.config.json
|
|
178
193
|
ENTRYPOINT ["/app/bundle"]
|
|
179
194
|
`
|
|
180
195
|
}
|
|
181
196
|
|
|
182
197
|
async function scaffoldRustBundle(bundleDir: string, config: BundleConfig): Promise<void> {
|
|
183
198
|
const bundle = path.basename(bundleDir)
|
|
184
|
-
await writeJson(path.join(bundleDir, BUNDLE_FILE),
|
|
199
|
+
await writeJson(path.join(bundleDir, BUNDLE_FILE), config)
|
|
185
200
|
await Bun.write(path.join(bundleDir, "Cargo.toml"), rustCargoToml(bundle))
|
|
186
|
-
await Bun.write(path.join(bundleDir, "Containerfile"), rustContainerfile(
|
|
201
|
+
await Bun.write(path.join(bundleDir, "Containerfile"), rustContainerfile())
|
|
187
202
|
await Bun.write(path.join(bundleDir, "src", "main.rs"), rustMainSource())
|
|
203
|
+
await Bun.write(path.join(bundleDir, "src", "functions", "mod.rs"), "")
|
|
188
204
|
await Bun.write(path.join(bundleDir, ".gitignore"), "target/\n.atomfn-*\n")
|
|
189
205
|
}
|
|
190
206
|
|
package/src/types.ts
CHANGED
|
@@ -45,7 +45,6 @@ export interface BundleConfig {
|
|
|
45
45
|
cpus?: number
|
|
46
46
|
maxConcurrency?: number
|
|
47
47
|
defaults?: FunctionFileConfig
|
|
48
|
-
functions?: string[]
|
|
49
48
|
env?: Record<string, string>
|
|
50
49
|
dev?: { port?: number }
|
|
51
50
|
}
|
|
@@ -53,6 +52,7 @@ export interface BundleConfig {
|
|
|
53
52
|
export interface DeployManifest {
|
|
54
53
|
project: string
|
|
55
54
|
bundle: string
|
|
55
|
+
arch: string
|
|
56
56
|
version: string
|
|
57
57
|
artifact: "binary" | "image"
|
|
58
58
|
functions: string[]
|
|
@@ -71,6 +71,7 @@ export interface BundleRecord {
|
|
|
71
71
|
host: string
|
|
72
72
|
port: number
|
|
73
73
|
functions: string[]
|
|
74
|
+
arch: string
|
|
74
75
|
version: string
|
|
75
76
|
artifact: "binary" | "image"
|
|
76
77
|
image: string
|
package/src/utils.ts
CHANGED
|
@@ -26,6 +26,11 @@ export function isValidSlug(slug: string): boolean {
|
|
|
26
26
|
return SLUG_RE.test(slug)
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
export function bunTarget(arch: string): string {
|
|
30
|
+
if (arch === "arm64") return "bun-linux-arm64"
|
|
31
|
+
return "bun-linux-x64"
|
|
32
|
+
}
|
|
33
|
+
|
|
29
34
|
export function unwrap<T>(value: T | symbol): T {
|
|
30
35
|
if (isCancel(value)) {
|
|
31
36
|
cancel("已取消")
|