@layers/amba 4.1.6 → 4.1.8

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/dist/index.js CHANGED
@@ -597,12 +597,36 @@ const RESERVED_BINDING_PREFIXES = ["AMBA_", "EDGE_"];
597
597
  const RESERVED_BINDING_EXACT_NAMES = ["STORAGE", "EDGE_DB_PROXY"];
598
598
  const VALID_BINDING_NAME_RE = /^[A-Z][A-Z0-9_]*$/;
599
599
  const MAX_BINDING_NAME_LENGTH = 64;
600
+ const INJECTED_BINDINGS_BY_PREFIX = {
601
+ AMBA_: [
602
+ "AMBA_API_URL",
603
+ "AMBA_PROJECT_ID",
604
+ "AMBA_INTERNAL_TOKEN",
605
+ "AMBA_AI_GATEWAY_URL"
606
+ ],
607
+ EDGE_: ["EDGE_HEADER_SIGNING_SECRET"]
608
+ };
609
+ const RESERVED_EXACT_PURPOSE = {
610
+ STORAGE: "the platform's built-in object-storage binding (env.STORAGE)",
611
+ EDGE_DB_PROXY: "a platform database binding"
612
+ };
613
+ const SUGGESTED_BINDING_PREFIX = "APP_";
614
+ function suggestedBindingName(name) {
615
+ const reservedPrefix = RESERVED_BINDING_PREFIXES.find((prefix) => name.startsWith(prefix)) ?? "";
616
+ const rest = name.slice(reservedPrefix.length);
617
+ const candidate = `${SUGGESTED_BINDING_PREFIX}${rest}`;
618
+ return rest.length > 0 && VALID_BINDING_NAME_RE.test(candidate) ? candidate : `${SUGGESTED_BINDING_PREFIX}SECRET`;
619
+ }
600
620
  /** Return why a binding name is reserved/invalid, or `null` if acceptable. */
601
621
  function getBindingReservationReason(name) {
602
622
  if (typeof name !== "string" || name.length === 0) return "Binding name must be a non-empty string";
603
623
  if (name.length > MAX_BINDING_NAME_LENGTH) return `Binding name must be at most ${MAX_BINDING_NAME_LENGTH} characters`;
604
- for (const prefix of RESERVED_BINDING_PREFIXES) if (name.startsWith(prefix)) return `Binding name starts with reserved prefix "${prefix}" (platform namespace)`;
605
- for (const exact of RESERVED_BINDING_EXACT_NAMES) if (name === exact) return `Binding name "${exact}" is reserved by a platform binding`;
624
+ for (const exact of RESERVED_BINDING_EXACT_NAMES) if (name === exact) return `"${exact}" is reserved for ${RESERVED_EXACT_PURPOSE[exact]}. Use your own name instead, for example "${suggestedBindingName(name)}"`;
625
+ for (const prefix of RESERVED_BINDING_PREFIXES) if (name.startsWith(prefix)) {
626
+ const injected = INJECTED_BINDINGS_BY_PREFIX[prefix];
627
+ if (injected.includes(name)) return `"${name}" is injected into every function by the platform; read env.${name} instead of setting it`;
628
+ return `names starting with "${prefix}" are reserved for values the platform injects into every function (${injected.join(", ")}). Use your own prefix instead, for example "${suggestedBindingName(name)}"`;
629
+ }
606
630
  if (!VALID_BINDING_NAME_RE.test(name)) return "Binding name must match /^[A-Z][A-Z0-9_]*$/ (uppercase ASCII, digits, underscore; must start with a letter)";
607
631
  return null;
608
632
  }
@@ -660,6 +684,13 @@ function isPatShape(token) {
660
684
  */
661
685
  async function browserAuthFlow() {
662
686
  return new Promise((resolve, reject) => {
687
+ let timeout;
688
+ const settle = (outcome) => {
689
+ if (timeout !== void 0) clearTimeout(timeout);
690
+ server.close();
691
+ server.closeAllConnections();
692
+ outcome();
693
+ };
663
694
  const server = createServer((req, res) => {
664
695
  const url = new URL(req.url ?? "/", `http://localhost`);
665
696
  if (url.pathname !== "/callback") {
@@ -672,20 +703,16 @@ async function browserAuthFlow() {
672
703
  const expiresAt = url.searchParams.get("expires_at");
673
704
  if (!accessToken || !refreshToken || !expiresAt) {
674
705
  res.writeHead(400, { "Content-Type": "text/html" });
675
- res.end("<html><body><h2>Authentication failed</h2><p>Missing token parameters. Please try again.</p></body></html>");
676
- server.close();
677
- reject(/* @__PURE__ */ new Error("Missing token parameters in callback"));
706
+ res.end("<html><body><h2>Authentication failed</h2><p>Missing token parameters. Please try again.</p></body></html>", () => settle(() => reject(/* @__PURE__ */ new Error("Missing token parameters in callback"))));
678
707
  return;
679
708
  }
680
- res.writeHead(200, { "Content-Type": "text/html" });
681
- res.end("<html><body style=\"font-family:system-ui;text-align:center;padding:3rem\"><h2>Authenticated!</h2><p>You can close this window and return to the terminal.</p></body></html>");
682
709
  const creds = {
683
710
  access_token: accessToken,
684
711
  refresh_token: refreshToken,
685
712
  expires_at: expiresAt
686
713
  };
687
- server.close();
688
- resolve(creds);
714
+ res.writeHead(200, { "Content-Type": "text/html" });
715
+ res.end("<html><body style=\"font-family:system-ui;text-align:center;padding:3rem\"><h2>Authenticated!</h2><p>You can close this window and return to the terminal.</p></body></html>", () => settle(() => resolve(creds)));
689
716
  });
690
717
  server.listen(0, "127.0.0.1", () => {
691
718
  const addr = server.address();
@@ -703,9 +730,8 @@ async function browserAuthFlow() {
703
730
  console.log(pc.yellow(` Please open this URL manually:`));
704
731
  console.log(` ${pc.underline(authUrl)}`);
705
732
  });
706
- setTimeout(() => {
707
- server.close();
708
- reject(/* @__PURE__ */ new Error("Authentication timed out. Please try again."));
733
+ timeout = setTimeout(() => {
734
+ settle(() => reject(/* @__PURE__ */ new Error("Authentication timed out. Please try again.")));
709
735
  }, 12e4);
710
736
  });
711
737
  });
@@ -1298,6 +1324,8 @@ function generateSandboxEmail() {
1298
1324
  function generateSandboxPassword() {
1299
1325
  return randomBytes(24).toString("base64url");
1300
1326
  }
1327
+ /** Request header carrying AMBA_SIGNUP_TOKEN on the signup call. */
1328
+ const SIGNUP_TOKEN_HEADER = "X-Amba-Signup-Token";
1301
1329
  /**
1302
1330
  * POST the synthesized credentials at the public signup endpoint.
1303
1331
  *
@@ -1313,12 +1341,17 @@ function generateSandboxPassword() {
1313
1341
  */
1314
1342
  async function performSandboxSignup(req, options = {}) {
1315
1343
  const apiUrl = options.apiUrl ?? process.env["AMBA_API_URL"] ?? "https://api.amba.dev";
1316
- const res = await (options.fetchImpl ?? fetch)(`${apiUrl}/v1/auth/developer/signup`, {
1344
+ const fetchImpl = options.fetchImpl ?? fetch;
1345
+ const url = `${apiUrl}/v1/auth/developer/signup`;
1346
+ const headers = {
1347
+ "Content-Type": "application/json",
1348
+ "User-Agent": "amba-cli/sandbox"
1349
+ };
1350
+ const signupToken = process.env["AMBA_SIGNUP_TOKEN"]?.trim();
1351
+ if (signupToken) headers[SIGNUP_TOKEN_HEADER] = signupToken;
1352
+ const res = await fetchImpl(url, {
1317
1353
  method: "POST",
1318
- headers: {
1319
- "Content-Type": "application/json",
1320
- "User-Agent": "amba-cli/sandbox"
1321
- },
1354
+ headers,
1322
1355
  body: JSON.stringify({
1323
1356
  email: req.email,
1324
1357
  password: req.password,
@@ -9778,7 +9811,7 @@ async function diffConfigCommand(file = DEFAULT_BUNDLE_FILE, options = {}) {
9778
9811
  //#endregion
9779
9812
  //#region src/index.ts
9780
9813
  const program = new Command();
9781
- program.name("amba").description("amba — agent-native backend-as-a-service for mobile apps.").version("4.1.6");
9814
+ program.name("amba").description("amba — agent-native backend-as-a-service for mobile apps.").version("4.1.8");
9782
9815
  program.option("--token <pat>", "Use a Personal Access Token for headless / CI / agent use (overrides ~/.amba/credentials.json + AMBA_PAT env)");
9783
9816
  program.hook("preAction", (thisCommand) => {
9784
9817
  setBearerOverride(resolveTokenSource({
@@ -9983,7 +10016,7 @@ functionDomains.command("remove <name> <hostname>").description("Detach an exact
9983
10016
  await runAction(() => functionDomainsRemoveCommand(name, hostname));
9984
10017
  });
9985
10018
  const secrets = program.command("secrets").description("Function secrets (GCP canonical, Workers-Secret synced)");
9986
- secrets.command("set <name> [value]").description("Store a secret and queue its sync to your deployed function(s). Omit --function for a project-wide secret. Use --from-stdin to keep the value out of shell history.").option("--function <name>", "Function the secret binds to. OMIT this flag to set a PROJECT-WIDE secret — one value visible to every function in the project (and to any function deployed later). With it, the secret is scoped to just that function and overrides a same-name project-wide value. Use the same name as the entry passed to 'amba functions deploy'. Secrets may be set before the function is deployed; the sync drains once you deploy.").option("--env <env>", "'dev' | 'prod' — INFORMATIONAL ONLY. Both share one secret namespace per project. Use distinct secret names (e.g. STRIPE_KEY_DEV / STRIPE_KEY_PROD) or two amba projects for real env isolation.").option("--from-stdin", "Read the secret value from stdin instead of the positional arg. Mutually exclusive with <value>. Pipe in: `echo $KEY | amba secrets set NAME --from-stdin`.").action(async (name, value, opts) => {
10019
+ secrets.command("set <name> [value]").description("Store a secret and queue its sync to your deployed function(s). Omit --function for a project-wide secret. Use --from-stdin to keep the value out of shell history. Names starting with AMBA_ or EDGE_ are reserved for values every function already receives (env.AMBA_PROJECT_ID, env.AMBA_API_URL, env.AMBA_INTERNAL_TOKEN); use your own prefix, e.g. APP_CLIENT_KEY.").option("--function <name>", "Function the secret binds to. OMIT this flag to set a PROJECT-WIDE secret — one value visible to every function in the project (and to any function deployed later). With it, the secret is scoped to just that function and overrides a same-name project-wide value. Use the same name as the entry passed to 'amba functions deploy'. Secrets may be set before the function is deployed; the sync drains once you deploy.").option("--env <env>", "'dev' | 'prod' — INFORMATIONAL ONLY. Both share one secret namespace per project. Use distinct secret names (e.g. STRIPE_KEY_DEV / STRIPE_KEY_PROD) or two amba projects for real env isolation.").option("--from-stdin", "Read the secret value from stdin instead of the positional arg. Mutually exclusive with <value>. Pipe in: `echo $KEY | amba secrets set NAME --from-stdin`.").action(async (name, value, opts) => {
9987
10020
  await runAction(() => secretsSetCommand(name, value, {
9988
10021
  function: opts.function,
9989
10022
  env: opts.env ?? "dev",
package/dist/sandbox.d.ts CHANGED
@@ -146,6 +146,8 @@ export declare function generateSandboxEmail(): string;
146
146
  * accept "agent signup" with no password and we'd drop this entirely.
147
147
  */
148
148
  export declare function generateSandboxPassword(): string;
149
+ /** Request header carrying AMBA_SIGNUP_TOKEN on the signup call. */
150
+ export declare const SIGNUP_TOKEN_HEADER = "X-Amba-Signup-Token";
149
151
  /**
150
152
  * POST the synthesized credentials at the public signup endpoint.
151
153
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@layers/amba",
3
- "version": "4.1.6",
3
+ "version": "4.1.8",
4
4
  "description": "amba — agent-native backend-as-a-service. Functions, collections, storage, AI, email, queues, sites, and `amba ship` to take an Expo app live on the App Store + Google Play. `npx @layers/amba init` to start.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -49,8 +49,8 @@
49
49
  "tsdown": "^0.12.5",
50
50
  "typescript": "^5.8.3",
51
51
  "vitest": "^3.2.4",
52
- "@layers/amba-mcp": "4.0.10",
53
- "@layers/amba-shared": "4.0.5"
52
+ "@layers/amba-shared": "4.0.6",
53
+ "@layers/amba-mcp": "4.0.11"
54
54
  },
55
55
  "scripts": {
56
56
  "build": "tsdown && tsc --emitDeclarationOnly",