@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/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 targets: ServerTarget[] = []
61
- for (const server of servers) {
62
- const host = hostOf(server)
63
- const cred = await getCredential(host)
64
- if (cred) targets.push({ server, host, token: cred.token })
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 { BUNDLE_FILE, FUNCTION_CONFIG_FILE, FUNCTION_FILE, SDK_PACKAGE } from "./consts.ts"
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 fnPath = path.join(bundleDir, "src", "functions", `${fn}.rs`)
91
- ensureDir(path.dirname(fnPath))
92
- await Bun.write(fnPath, rustFunctionSource(fn))
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
- atomfunctions = "0.0.1"
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 `use atomfunctions::{serve, Ctx, FunctionError, Registry};
120
- use serde::{Deserialize, Serialize};
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
- fn main() {
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(fn: string): string {
146
- return `// 函数 ${fn}:在 src/main.rs 中用 registry.function("${fn}", ${fn}) 注册,
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 ${fn}(input: Input, _ctx: &Ctx) -> Result<Output, FunctionError> {
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 rustContainerfile(bundle: string): string {
168
- return `FROM rust:1-slim AS builder
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
- FROM debian:bookworm-slim
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
- COPY --from=builder /build/target/release/${bundle} /app/bundle
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), { ...config, functions: config.functions ?? ["greet"] })
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(bundle))
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("已取消")