@layers/amba 4.1.3 → 4.1.5
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/api-client.d.ts
CHANGED
|
@@ -295,6 +295,8 @@ export interface FunctionDeploymentRow {
|
|
|
295
295
|
/** Opt-in public flag (#17). True = X-Api-Key gate skipped for this
|
|
296
296
|
* function so external webhook senders can reach it. Default false. */
|
|
297
297
|
is_public?: boolean;
|
|
298
|
+
/** Explicit per-deployment opt-in. Default false. */
|
|
299
|
+
forward_authorization?: boolean;
|
|
298
300
|
}
|
|
299
301
|
/** Provider-neutral lifecycle for an exact hostname bound to one function. */
|
|
300
302
|
export interface FunctionDomain {
|
|
@@ -302,8 +304,17 @@ export interface FunctionDomain {
|
|
|
302
304
|
project_id: string;
|
|
303
305
|
function_name: string;
|
|
304
306
|
hostname: string;
|
|
307
|
+
/** Required record at authoritative DNS. */
|
|
308
|
+
dns_record_type?: 'CNAME';
|
|
309
|
+
/** True for a registrable root rather than a subdomain. */
|
|
310
|
+
is_apex?: boolean;
|
|
311
|
+
/** Additive provider-neutral DNS warning, populated for apex hostnames. */
|
|
312
|
+
dns_note?: string | null;
|
|
305
313
|
cert_status: 'pending_validation' | 'pending_issuance' | 'pending_deployment' | 'active' | 'error';
|
|
306
314
|
ownership_status: 'pending' | 'active' | 'error';
|
|
315
|
+
/** Raw point-in-time upstream states returned by attach/refresh. */
|
|
316
|
+
provider_status?: string | null;
|
|
317
|
+
provider_cert_status?: string | null;
|
|
307
318
|
/** True only when certificate/ownership are active and routing reconciliation succeeded. */
|
|
308
319
|
live: boolean;
|
|
309
320
|
created_at: string;
|
|
@@ -377,10 +388,23 @@ export declare function deployFunctionViaApi(projectId: string, input: {
|
|
|
377
388
|
* HMAC inside the handler.
|
|
378
389
|
*/
|
|
379
390
|
public?: boolean;
|
|
391
|
+
/** Forward a verified client Authorization header to customer code. */
|
|
392
|
+
forward_authorization?: boolean;
|
|
380
393
|
}): Promise<DeployFunctionResult>;
|
|
381
394
|
export declare function listFunctionDeployments(projectId: string, options?: {
|
|
382
395
|
activeOnly?: boolean;
|
|
383
396
|
}): Promise<ApiListResponse<FunctionDeploymentRow>>;
|
|
397
|
+
export interface FunctionDeployStatus {
|
|
398
|
+
status: 'available' | 'scheduled' | 'paused';
|
|
399
|
+
available: boolean;
|
|
400
|
+
paused: boolean;
|
|
401
|
+
retry_after_seconds: number | null;
|
|
402
|
+
retry_after_is_poll_interval: boolean;
|
|
403
|
+
maintenance_start_at: string | null;
|
|
404
|
+
estimated_resume_at: string | null;
|
|
405
|
+
reason: string | null;
|
|
406
|
+
}
|
|
407
|
+
export declare function getFunctionDeployStatus(projectId: string): Promise<ApiResponse<FunctionDeployStatus>>;
|
|
384
408
|
/** Attach one exact hostname to one deployed function. */
|
|
385
409
|
export declare function attachFunctionDomainViaApi(projectId: string, functionName: string, hostname: string): Promise<ApiResponse<AttachedFunctionDomain>>;
|
|
386
410
|
/** List provider-neutral ownership/certificate status for a function's hostnames. */
|
|
@@ -427,6 +451,17 @@ export declare function scheduleFunction(projectId: string, input: {
|
|
|
427
451
|
}): Promise<ApiResponse<{
|
|
428
452
|
schedule_id: string;
|
|
429
453
|
}>>;
|
|
454
|
+
export interface FunctionScheduleRow {
|
|
455
|
+
schedule_id: string;
|
|
456
|
+
function_name: string;
|
|
457
|
+
enriched: boolean;
|
|
458
|
+
cron: string | null;
|
|
459
|
+
timezone: string | null;
|
|
460
|
+
next_fire_at: string | null;
|
|
461
|
+
last_fire_at: string | null;
|
|
462
|
+
paused: boolean | null;
|
|
463
|
+
}
|
|
464
|
+
export declare function listFunctionSchedules(projectId: string): Promise<ApiResponse<FunctionScheduleRow[]>>;
|
|
430
465
|
export interface FunctionLogEvent {
|
|
431
466
|
/** Sanitized log row — see `r2-log-reader.ts:SanitizedLogEvent`. Field-shape stable. */
|
|
432
467
|
Event?: {
|
|
@@ -674,6 +709,25 @@ export interface SiteDomainRow {
|
|
|
674
709
|
provider_hostname_id: string | null;
|
|
675
710
|
cert_status: 'pending_validation' | 'pending_issuance' | 'pending_deployment' | 'active' | 'error';
|
|
676
711
|
ownership_status: 'pending' | 'active' | 'error';
|
|
712
|
+
/** Raw point-in-time upstream values returned by attach/refresh. */
|
|
713
|
+
provider_status?: string | null;
|
|
714
|
+
provider_cert_status?: string | null;
|
|
715
|
+
/** Edge Worker route reconciliation. */
|
|
716
|
+
route_status: 'pending' | 'active' | 'error';
|
|
717
|
+
/** Host-to-site KV reconciliation. */
|
|
718
|
+
kv_status: 'pending' | 'active' | 'error';
|
|
719
|
+
/** Whether Amba owns and manages the authoritative DNS zone. */
|
|
720
|
+
dns_managed: boolean;
|
|
721
|
+
/** Managed-DNS reconciliation, or not_required for externally managed DNS. */
|
|
722
|
+
dns_status: 'not_required' | 'pending' | 'active' | 'error';
|
|
723
|
+
/**
|
|
724
|
+
* Authoritative reachability. True only when certificate, ownership,
|
|
725
|
+
* Worker route, KV, and (when managed) DNS are all active.
|
|
726
|
+
*/
|
|
727
|
+
live: boolean;
|
|
728
|
+
dns_record_type?: 'CNAME';
|
|
729
|
+
is_apex?: boolean;
|
|
730
|
+
dns_note?: string | null;
|
|
677
731
|
created_at: string;
|
|
678
732
|
updated_at: string;
|
|
679
733
|
}
|
|
@@ -718,6 +772,7 @@ export declare function archiveSite(projectId: string, name: string, confirm: st
|
|
|
718
772
|
export interface DeleteCascadeSummary {
|
|
719
773
|
domains_removed?: number;
|
|
720
774
|
site_runtime_removed?: boolean;
|
|
775
|
+
function_schedule_removed?: boolean;
|
|
721
776
|
runtime_script_removed?: boolean;
|
|
722
777
|
function_deployments_marked_disabled?: number;
|
|
723
778
|
}
|
|
@@ -731,8 +786,14 @@ export declare function addSiteDomainViaApi(projectId: string, siteName: string,
|
|
|
731
786
|
provider_hostname_id: string;
|
|
732
787
|
cert_status: SiteDomainRow["cert_status"];
|
|
733
788
|
ownership_status: SiteDomainRow["ownership_status"];
|
|
789
|
+
route_status: SiteDomainRow["route_status"];
|
|
790
|
+
kv_status: SiteDomainRow["kv_status"];
|
|
791
|
+
dns_managed: SiteDomainRow["dns_managed"];
|
|
792
|
+
dns_status: SiteDomainRow["dns_status"];
|
|
793
|
+
live: boolean;
|
|
734
794
|
dns_target: string;
|
|
735
795
|
is_apex: boolean;
|
|
796
|
+
dns_record_type: "CNAME";
|
|
736
797
|
dns_note: string | null;
|
|
737
798
|
ssl_validation: DomainValidationRecord[];
|
|
738
799
|
ownership_verification: DomainOwnershipVerification | null;
|
|
@@ -809,6 +870,12 @@ export interface DomainPurchaseResult {
|
|
|
809
870
|
provisioned?: boolean;
|
|
810
871
|
cert_status?: string;
|
|
811
872
|
ownership_status?: string;
|
|
873
|
+
route_status?: string;
|
|
874
|
+
kv_status?: string;
|
|
875
|
+
dns_managed?: boolean;
|
|
876
|
+
dns_status?: string;
|
|
877
|
+
/** Authoritative reachability across provider, route, KV, and managed DNS. */
|
|
878
|
+
hostname_live?: boolean;
|
|
812
879
|
dns_autoconfigured?: boolean;
|
|
813
880
|
url?: string;
|
|
814
881
|
public_url?: string;
|
|
@@ -825,7 +892,22 @@ export declare function purchaseDomainViaApi(projectId: string, body: {
|
|
|
825
892
|
privacy?: boolean;
|
|
826
893
|
years?: number;
|
|
827
894
|
}): Promise<ApiResponse<DomainPurchaseResult>>;
|
|
828
|
-
export
|
|
895
|
+
export interface PurchasedDomainStatus extends Record<string, unknown> {
|
|
896
|
+
domain: string;
|
|
897
|
+
status: string;
|
|
898
|
+
site?: string | null;
|
|
899
|
+
provisioned: boolean;
|
|
900
|
+
cert_status?: string;
|
|
901
|
+
ownership_status?: string;
|
|
902
|
+
route_status?: string;
|
|
903
|
+
kv_status?: string;
|
|
904
|
+
dns_managed?: boolean;
|
|
905
|
+
dns_status?: string;
|
|
906
|
+
/** Reachability; deliberately separate from purchase/payment completion. */
|
|
907
|
+
hostname_live: boolean;
|
|
908
|
+
aliases: Array<Record<string, unknown>>;
|
|
909
|
+
}
|
|
910
|
+
export declare function listPurchasedDomainsViaApi(projectId: string): Promise<ApiListResponse<PurchasedDomainStatus>>;
|
|
829
911
|
/**
|
|
830
912
|
* Delete a function entirely. Cascade: removes the deployed script
|
|
831
913
|
* (backend 404 treated as success), then marks every historical
|
|
@@ -845,10 +927,19 @@ export declare function attachSiteDomain(projectId: string, siteName: string, in
|
|
|
845
927
|
provider_hostname_id?: string;
|
|
846
928
|
}): Promise<ApiResponse<SiteDomainRow>>;
|
|
847
929
|
export declare function listSiteDomains(projectId: string, siteName: string): Promise<ApiListResponse<SiteDomainRow>>;
|
|
930
|
+
/** @deprecated Lifecycle is provider-owned; this delegates to authoritative refresh. */
|
|
848
931
|
export declare function updateSiteDomain(projectId: string, siteName: string, hostname: string, patch: {
|
|
849
932
|
cert_status: SiteDomainRow['cert_status'];
|
|
850
933
|
provider_hostname_id?: string;
|
|
851
|
-
}): Promise<ApiResponse<SiteDomainRow
|
|
934
|
+
}): Promise<ApiResponse<SiteDomainRow & {
|
|
935
|
+
ssl_validation: DomainValidationRecord[];
|
|
936
|
+
ownership_verification: DomainOwnershipVerification | null;
|
|
937
|
+
ownership_verification_http: {
|
|
938
|
+
http_url?: string;
|
|
939
|
+
http_body?: string;
|
|
940
|
+
} | null;
|
|
941
|
+
note?: string;
|
|
942
|
+
}>>;
|
|
852
943
|
export declare function detachSiteDomain(projectId: string, siteName: string, hostname: string): Promise<ApiResponse<{
|
|
853
944
|
deleted: boolean;
|
|
854
945
|
}>>;
|
|
@@ -42,15 +42,19 @@ export interface DeployOptions {
|
|
|
42
42
|
* the handler — public skips the credential gate only.
|
|
43
43
|
*/
|
|
44
44
|
public?: boolean;
|
|
45
|
+
/** Opt in to forwarding verified client Authorization to this function. */
|
|
46
|
+
forwardAuthorization?: boolean;
|
|
45
47
|
}
|
|
46
48
|
export declare function functionsDeployCommand(target: string, options?: DeployOptions): Promise<void>;
|
|
47
49
|
export declare function functionsListCommand(): Promise<void>;
|
|
50
|
+
export declare function functionsDeployStatusCommand(): Promise<void>;
|
|
48
51
|
export declare function functionsDeleteCommand(name: string, options?: {
|
|
49
52
|
confirm?: string;
|
|
50
53
|
}): Promise<void>;
|
|
51
54
|
export declare function functionsScheduleCommand(name: string, cron: string, options?: {
|
|
52
55
|
tz?: string;
|
|
53
56
|
}): Promise<void>;
|
|
57
|
+
export declare function functionsSchedulesCommand(): Promise<void>;
|
|
54
58
|
export interface DevOptions {
|
|
55
59
|
/** Local port. Default 8787 (overridable per project). */
|
|
56
60
|
port?: number;
|
package/dist/commands/sites.d.ts
CHANGED
|
@@ -36,7 +36,7 @@ export interface SitesDomainAddOptions {
|
|
|
36
36
|
zoneId?: string;
|
|
37
37
|
/** Skip the cert poll loop. Caller will check status later. */
|
|
38
38
|
noWait?: boolean;
|
|
39
|
-
/** Max seconds to wait for
|
|
39
|
+
/** Max seconds to wait for both ownership and certificate activation. Default 600. */
|
|
40
40
|
timeout?: number;
|
|
41
41
|
}
|
|
42
42
|
export declare function sitesDomainAddCommand(hostname: string, options: SitesDomainAddOptions): Promise<void>;
|
|
@@ -74,6 +74,10 @@ declare function collectFiles(dir: string): Promise<SiteFile[]>;
|
|
|
74
74
|
export declare class SitesStaticOnlyError extends Error {
|
|
75
75
|
readonly code: "SITES_STATIC_ONLY";
|
|
76
76
|
}
|
|
77
|
+
declare function isProviderTerminalDomainFailure(d: {
|
|
78
|
+
cert_status: string;
|
|
79
|
+
ownership_status: string;
|
|
80
|
+
}): boolean;
|
|
77
81
|
/**
|
|
78
82
|
* Test-only re-export of internal helpers. Tests reach in via this
|
|
79
83
|
* namespace so the production module surface stays clean. Not a public
|
|
@@ -83,5 +87,6 @@ export declare const __testHelpers: {
|
|
|
83
87
|
collectFiles: typeof collectFiles;
|
|
84
88
|
BLOCKED_FILE_NAMES: Set<string>;
|
|
85
89
|
BLOCKED_DIR_NAMES: Set<string>;
|
|
90
|
+
isProviderTerminalDomainFailure: typeof isProviderTerminalDomainFailure;
|
|
86
91
|
};
|
|
87
92
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -1018,7 +1018,8 @@ async function deployFunctionViaApi(projectId, input) {
|
|
|
1018
1018
|
const metadata = {
|
|
1019
1019
|
name: input.name,
|
|
1020
1020
|
...input.rate_limit !== void 0 ? { rate_limit: input.rate_limit } : {},
|
|
1021
|
-
...input.public === true ? { public: true } : {}
|
|
1021
|
+
...input.public === true ? { public: true } : {},
|
|
1022
|
+
...input.forward_authorization === true ? { forward_authorization: true } : {}
|
|
1022
1023
|
};
|
|
1023
1024
|
fd.set("metadata", new Blob([JSON.stringify(metadata)], { type: "application/json" }), "metadata.json");
|
|
1024
1025
|
return request("POST", `/projects/${projectId}/functions/deploy`, fd);
|
|
@@ -1026,6 +1027,9 @@ async function deployFunctionViaApi(projectId, input) {
|
|
|
1026
1027
|
async function listFunctionDeployments(projectId, options = {}) {
|
|
1027
1028
|
return request("GET", `/projects/${projectId}/functions/deployments${options.activeOnly ? "?active=1" : ""}`);
|
|
1028
1029
|
}
|
|
1030
|
+
async function getFunctionDeployStatus(projectId) {
|
|
1031
|
+
return request("GET", `/projects/${projectId}/functions/deploy-status`);
|
|
1032
|
+
}
|
|
1029
1033
|
function functionDomainsPath(projectId, functionName) {
|
|
1030
1034
|
return `/projects/${encodeURIComponent(projectId)}/functions/${encodeURIComponent(functionName)}/domains`;
|
|
1031
1035
|
}
|
|
@@ -1048,6 +1052,9 @@ async function removeFunctionDomainViaApi(projectId, functionName, hostname) {
|
|
|
1048
1052
|
async function scheduleFunction(projectId, input) {
|
|
1049
1053
|
return request("POST", `/projects/${projectId}/functions/schedules`, input);
|
|
1050
1054
|
}
|
|
1055
|
+
async function listFunctionSchedules(projectId) {
|
|
1056
|
+
return request("GET", `/projects/${projectId}/functions/schedules`);
|
|
1057
|
+
}
|
|
1051
1058
|
async function getFunctionLogs(projectId, functionName, options = {}) {
|
|
1052
1059
|
const params = new URLSearchParams();
|
|
1053
1060
|
if (options.since) params.set("since", options.since);
|
|
@@ -5784,6 +5791,16 @@ async function functionsDeployCommand(target, options = {}) {
|
|
|
5784
5791
|
console.log(pc.bold(` amba functions deploy ${pc.cyan(functionName)}`));
|
|
5785
5792
|
console.log(pc.dim(" ─────────────────────────────────"));
|
|
5786
5793
|
console.log();
|
|
5794
|
+
if (!options.dryRun) {
|
|
5795
|
+
let deployStatus = null;
|
|
5796
|
+
try {
|
|
5797
|
+
deployStatus = (await getFunctionDeployStatus(projectId)).data;
|
|
5798
|
+
} catch (err) {
|
|
5799
|
+
if (!(err instanceof ApiClientError && err.statusCode === 404)) throw err;
|
|
5800
|
+
}
|
|
5801
|
+
if (deployStatus && !deployStatus.available) throw new Error(formatDeployUnavailableMessage(deployStatus));
|
|
5802
|
+
if (deployStatus?.status === "scheduled") console.log(pc.yellow(" !") + ` Maintenance is scheduled for ${deployStatus.maintenance_start_at ?? "a forthcoming window"}.`);
|
|
5803
|
+
}
|
|
5787
5804
|
console.log(pc.dim(` Bundling ${entryPoint}…`));
|
|
5788
5805
|
const bundle = await bundleFunction({ entryPoint });
|
|
5789
5806
|
printBundleReport(bundle);
|
|
@@ -5798,13 +5815,20 @@ async function functionsDeployCommand(target, options = {}) {
|
|
|
5798
5815
|
name: functionName,
|
|
5799
5816
|
bundleCode: bundle.code,
|
|
5800
5817
|
rate_limit: rateLimit,
|
|
5801
|
-
public: options.public === true ? true : void 0
|
|
5818
|
+
public: options.public === true ? true : void 0,
|
|
5819
|
+
forward_authorization: options.forwardAuthorization === true ? true : void 0
|
|
5802
5820
|
});
|
|
5803
5821
|
} catch (err) {
|
|
5804
5822
|
if (err instanceof ApiClientError && err.statusCode === 503) {
|
|
5823
|
+
try {
|
|
5824
|
+
const status = (await getFunctionDeployStatus(projectId)).data;
|
|
5825
|
+
if (!status.available) throw new Error(formatDeployUnavailableMessage(status));
|
|
5826
|
+
} catch (statusErr) {
|
|
5827
|
+
if (!(statusErr instanceof ApiClientError)) throw statusErr;
|
|
5828
|
+
}
|
|
5805
5829
|
const base = err.message.trimEnd();
|
|
5806
5830
|
const sep = /[.!?]$/.test(base) ? " " : ". ";
|
|
5807
|
-
throw new Error(`${base}${sep}
|
|
5831
|
+
throw new Error(`${base}${sep}Check \`amba functions deploy-status\` before retrying.`);
|
|
5808
5832
|
}
|
|
5809
5833
|
throw err;
|
|
5810
5834
|
}
|
|
@@ -5812,8 +5836,15 @@ async function functionsDeployCommand(target, options = {}) {
|
|
|
5812
5836
|
console.log(pc.green(" ✓") + ` URL: ${pc.underline(result.fn_url)}`);
|
|
5813
5837
|
if (rateLimit) console.log(pc.dim(` Rate limit: ${rateLimit.max} per ${rateLimit.window} (key=${rateLimit.key}) — enforced pre-dispatch`));
|
|
5814
5838
|
if (options.public === true) console.log(pc.yellow(" ! Public") + pc.dim(" — no X-Api-Key required. Verify the webhook signature inside your handler."));
|
|
5839
|
+
if (options.forwardAuthorization === true) console.log(pc.yellow(" ! Authorization forwarding enabled") + pc.dim(" — the verified client Bearer is visible to this function; X-Api-Key is never forwarded."));
|
|
5815
5840
|
console.log();
|
|
5816
5841
|
}
|
|
5842
|
+
function formatDeployUnavailableMessage(status) {
|
|
5843
|
+
const reason = status.reason ? ` (${status.reason})` : "";
|
|
5844
|
+
if (status.estimated_resume_at) return `Function deployments are paused${reason}. Estimated resume: ${status.estimated_resume_at}.`;
|
|
5845
|
+
if (status.retry_after_seconds !== null) return `Function deployments are paused${reason}. ${status.retry_after_is_poll_interval ? "Poll deploy-status again" : "Retry"} in ${status.retry_after_seconds}s; no end time is known yet.`;
|
|
5846
|
+
return `Function deployments are paused${reason}. Check \`amba functions deploy-status\` before retrying.`;
|
|
5847
|
+
}
|
|
5817
5848
|
async function functionsListCommand() {
|
|
5818
5849
|
const res = await listFunctionDeployments((await loadProjectConfig()).projectId, { activeOnly: true });
|
|
5819
5850
|
console.log();
|
|
@@ -5825,12 +5856,25 @@ async function functionsListCommand() {
|
|
|
5825
5856
|
for (const d of res.data) console.log(` ${pc.bold(d.name)} ${pc.dim("v" + d.version)} ` + pc.dim(`sha=${d.bundle_sha.slice(0, 12)}… ${d.created_at}`));
|
|
5826
5857
|
console.log();
|
|
5827
5858
|
}
|
|
5859
|
+
async function functionsDeployStatusCommand() {
|
|
5860
|
+
const res = await getFunctionDeployStatus((await loadProjectConfig()).projectId);
|
|
5861
|
+
if (res.data.available) {
|
|
5862
|
+
console.log(pc.green(" ✓") + " Function deployments are available.");
|
|
5863
|
+
if (res.data.status === "scheduled") {
|
|
5864
|
+
console.log(pc.yellow(" !") + ` Maintenance is scheduled for ${res.data.maintenance_start_at ?? "a forthcoming window"}.`);
|
|
5865
|
+
if (res.data.estimated_resume_at) console.log(pc.dim(` Estimated resume: ${res.data.estimated_resume_at}.`));
|
|
5866
|
+
}
|
|
5867
|
+
return;
|
|
5868
|
+
}
|
|
5869
|
+
console.log(pc.yellow(" !") + " Function deployments are paused for platform maintenance.");
|
|
5870
|
+
if (res.data.retry_after_seconds !== null) console.log(pc.dim(res.data.retry_after_is_poll_interval ? ` Poll deploy-status again in ${res.data.retry_after_seconds}s; no end time is known yet.` : ` Estimated resume: ${res.data.estimated_resume_at} (Retry-After ${res.data.retry_after_seconds}s).`));
|
|
5871
|
+
}
|
|
5828
5872
|
async function functionsDeleteCommand(name, options = {}) {
|
|
5829
5873
|
validateFunctionName$1(name);
|
|
5830
5874
|
if (!options.confirm || options.confirm !== name) throw new Error(`Delete is destructive. Pass --confirm ${name} to proceed. Clients calling this function will start 404'ing immediately.`);
|
|
5831
5875
|
const cascade = (await deleteFunctionViaApi((await loadProjectConfig()).projectId, name, { confirm: name })).data.cascade;
|
|
5832
5876
|
console.log(pc.green(" ✓") + ` Deleted ${pc.bold(name)}.`);
|
|
5833
|
-
console.log(pc.dim(` Cascade: runtime_script_removed=${cascade.runtime_script_removed ?? false}, function_deployments_marked_disabled=${cascade.function_deployments_marked_disabled ?? 0}`));
|
|
5877
|
+
console.log(pc.dim(` Cascade: function_schedule_removed=${cascade.function_schedule_removed ?? false}, runtime_script_removed=${cascade.runtime_script_removed ?? false}, function_deployments_marked_disabled=${cascade.function_deployments_marked_disabled ?? 0}`));
|
|
5834
5878
|
}
|
|
5835
5879
|
async function functionsScheduleCommand(name, cron, options = {}) {
|
|
5836
5880
|
const projectConfig = await loadProjectConfig();
|
|
@@ -5846,6 +5890,25 @@ async function functionsScheduleCommand(name, cron, options = {}) {
|
|
|
5846
5890
|
console.log(pc.dim(` schedule_id: ${res.data.schedule_id}`));
|
|
5847
5891
|
console.log();
|
|
5848
5892
|
}
|
|
5893
|
+
async function functionsSchedulesCommand() {
|
|
5894
|
+
const res = await listFunctionSchedules((await loadProjectConfig()).projectId);
|
|
5895
|
+
console.log();
|
|
5896
|
+
if (res.data.length === 0) {
|
|
5897
|
+
console.log(pc.dim(" No function schedules registered."));
|
|
5898
|
+
console.log();
|
|
5899
|
+
return;
|
|
5900
|
+
}
|
|
5901
|
+
for (const schedule of res.data) {
|
|
5902
|
+
if (!schedule.enriched) {
|
|
5903
|
+
console.log(`${pc.bold(schedule.function_name)} ${pc.yellow("metadata unavailable")}`);
|
|
5904
|
+
continue;
|
|
5905
|
+
}
|
|
5906
|
+
const state = schedule.paused ? pc.yellow("paused") : pc.green("active");
|
|
5907
|
+
console.log(` ${pc.bold(schedule.function_name)} ${pc.cyan(schedule.cron ?? "?")} ${pc.dim(schedule.timezone ?? "UTC")} ${state}`);
|
|
5908
|
+
console.log(pc.dim(` last=${schedule.last_fire_at ?? "never"} next=${schedule.next_fire_at ?? "unknown"}`));
|
|
5909
|
+
}
|
|
5910
|
+
console.log();
|
|
5911
|
+
}
|
|
5849
5912
|
/**
|
|
5850
5913
|
* `amba functions dev <file>` — run the function locally with file-change
|
|
5851
5914
|
* hot reload.
|
|
@@ -6420,7 +6483,10 @@ function printEvents(events, asJson) {
|
|
|
6420
6483
|
console.log(`${pc.dim(ts)} ${pc.cyan(script)} ${level} ${msg}`);
|
|
6421
6484
|
}
|
|
6422
6485
|
if (e.Exceptions && e.Exceptions.length > 0) for (const ex of e.Exceptions) console.log(`${pc.dim(ts)} ${pc.cyan(script)} ${pc.red("[exception]")} ${ex.Name ?? "Error"}: ${ex.Message ?? ""}`);
|
|
6423
|
-
if ((!e.Logs || e.Logs.length === 0) && (!e.Exceptions || e.Exceptions.length === 0) && e.Outcome
|
|
6486
|
+
if ((!e.Logs || e.Logs.length === 0) && (!e.Exceptions || e.Exceptions.length === 0) && e.Outcome) {
|
|
6487
|
+
const outcome = e.Outcome === "ok" ? pc.green("[outcome]") : pc.yellow("[outcome]");
|
|
6488
|
+
console.log(`${pc.dim(ts)} ${pc.cyan(script)} ${outcome} ${e.Outcome}`);
|
|
6489
|
+
}
|
|
6424
6490
|
}
|
|
6425
6491
|
}
|
|
6426
6492
|
function renderLevel(level) {
|
|
@@ -6477,6 +6543,7 @@ function printDnsInstructions(domain) {
|
|
|
6477
6543
|
if ("cleanup_pending" in domain && domain.cleanup_pending === true) console.log(pc.yellow(" Routing cleanup is incomplete; retry with domains refresh."));
|
|
6478
6544
|
if ("dns_autoconfigured" in domain && domain.dns_autoconfigured === true) console.log(pc.dim(` DNS records published automatically${domain.dns_records_published === void 0 ? "" : ` (${domain.dns_records_published})`}.`));
|
|
6479
6545
|
if (domain.dns_target) console.log(pc.dim(` DNS target: CNAME ${domain.hostname} → ${domain.dns_target}`));
|
|
6546
|
+
if (domain.dns_note) console.log(pc.yellow(` DNS requirement: ${domain.dns_note}`));
|
|
6480
6547
|
for (const record of domain.ssl_validation ?? []) if (record.txt_name && record.txt_value) console.log(pc.dim(` Certificate TXT: ${record.txt_name} = ${record.txt_value}`));
|
|
6481
6548
|
else if (record.http_url && record.http_body) console.log(pc.dim(` Certificate HTTP: ${record.http_url} → ${record.http_body}`));
|
|
6482
6549
|
const ownership = domain.ownership_verification;
|
|
@@ -9021,7 +9088,6 @@ async function sitesDomainAddCommand(hostname, options) {
|
|
|
9021
9088
|
console.log(pc.green(" ✓") + ` Custom hostname registered`);
|
|
9022
9089
|
const validation = res.data.ssl_validation ?? [];
|
|
9023
9090
|
const ownership = res.data.ownership_verification;
|
|
9024
|
-
const isApex = res.data.is_apex ?? false;
|
|
9025
9091
|
if (validation.length > 0 || ownership) {
|
|
9026
9092
|
console.log();
|
|
9027
9093
|
console.log(pc.dim(" 1. Publish these DNS records to verify ownership:"));
|
|
@@ -9035,10 +9101,8 @@ async function sitesDomainAddCommand(hostname, options) {
|
|
|
9035
9101
|
}
|
|
9036
9102
|
console.log(` ${pc.bold("CNAME")} ${hostname} → ${pc.cyan(res.data.dns_target)}`);
|
|
9037
9103
|
console.log();
|
|
9038
|
-
if (
|
|
9039
|
-
console.log(pc.
|
|
9040
|
-
console.log(pc.dim(` flattens CNAME-at-root. If it doesn't, A-record apex support is coming soon;`));
|
|
9041
|
-
console.log(pc.dim(` use a subdomain like app.${hostname} in the meantime.`));
|
|
9104
|
+
if (res.data.dns_note) {
|
|
9105
|
+
console.log(pc.yellow(` DNS requirement: ${res.data.dns_note}`));
|
|
9042
9106
|
console.log();
|
|
9043
9107
|
}
|
|
9044
9108
|
if (options.noWait) {
|
|
@@ -9056,11 +9120,11 @@ async function sitesDomainAddCommand(hostname, options) {
|
|
|
9056
9120
|
console.log(pc.dim(` ${line}`));
|
|
9057
9121
|
lastLine = line;
|
|
9058
9122
|
}
|
|
9059
|
-
if (row.
|
|
9123
|
+
if (row.live === true) {
|
|
9060
9124
|
console.log(pc.green(" ✓") + ` ${hostname} is live with a valid certificate.`);
|
|
9061
9125
|
return;
|
|
9062
9126
|
}
|
|
9063
|
-
if (row
|
|
9127
|
+
if (isProviderTerminalDomainFailure(row)) throw new Error(`Domain setup failed for ${hostname}. Confirm the TXT record(s) above are published and the CNAME points at ${res.data.dns_target}.`);
|
|
9064
9128
|
await sleep(5e3);
|
|
9065
9129
|
}
|
|
9066
9130
|
console.log(pc.yellow(` ! Timed out after ${options.timeout ?? 600}s. Re-run \`amba sites describe ${options.site}\` to check status.`));
|
|
@@ -9193,12 +9257,18 @@ function formatCertStatus(s) {
|
|
|
9193
9257
|
return pc.yellow(s);
|
|
9194
9258
|
}
|
|
9195
9259
|
/**
|
|
9196
|
-
* Render
|
|
9197
|
-
*
|
|
9198
|
-
* showing cert alone reads "live" while ownership is still pending.
|
|
9260
|
+
* Render every serving plane. Only the server-derived `live` field is
|
|
9261
|
+
* authoritative; these states explain what is still converging.
|
|
9199
9262
|
*/
|
|
9200
9263
|
function formatDomainStatus(d) {
|
|
9201
|
-
|
|
9264
|
+
const states = [`ownership: ${formatCertStatus(d.ownership_status)}`, `cert: ${formatCertStatus(d.cert_status)}`];
|
|
9265
|
+
if (d.route_status) states.push(`route: ${formatCertStatus(d.route_status)}`);
|
|
9266
|
+
if (d.kv_status) states.push(`kv: ${formatCertStatus(d.kv_status)}`);
|
|
9267
|
+
if (d.dns_managed && d.dns_status) states.push(`dns: ${formatCertStatus(d.dns_status)}`);
|
|
9268
|
+
return states.join(" ");
|
|
9269
|
+
}
|
|
9270
|
+
function isProviderTerminalDomainFailure(d) {
|
|
9271
|
+
return d.cert_status === "error" || d.ownership_status === "error";
|
|
9202
9272
|
}
|
|
9203
9273
|
function sleep(ms) {
|
|
9204
9274
|
return new Promise((r) => setTimeout(r, ms));
|
|
@@ -9318,9 +9388,13 @@ async function domainsBuyCommand(domain, options) {
|
|
|
9318
9388
|
}
|
|
9319
9389
|
console.log(pc.green(" ✓") + ` Registered ${pc.bold(hostname)}`);
|
|
9320
9390
|
if (r.provisioned) {
|
|
9321
|
-
console.log(pc.green(" ✓") + `
|
|
9322
|
-
|
|
9323
|
-
console.log(pc.
|
|
9391
|
+
console.log(pc.green(" ✓") + ` Binding created for ${options.site}${r.dns_autoconfigured ? " — no DNS setup needed" : ""}.`);
|
|
9392
|
+
const hostnameLive = r.hostname_live === true;
|
|
9393
|
+
if (r.public_url && hostnameLive) console.log(pc.green(" ✓") + ` Live at ${pc.underline(r.public_url)}`);
|
|
9394
|
+
else if (r.public_url) {
|
|
9395
|
+
console.log(pc.yellow(" !") + ` Pending activation at ${pc.underline(r.public_url)}`);
|
|
9396
|
+
console.log(pc.dim(` certificate=${r.cert_status ?? "pending"}, ownership=${r.ownership_status ?? "pending"}, route=${r.route_status ?? "pending"}, kv=${r.kv_status ?? "pending"}, dns=${r.dns_status ?? "pending"}; wait for hostname_live=true.`));
|
|
9397
|
+
}
|
|
9324
9398
|
} else if (r.message) console.log(pc.dim(` ${r.message}`));
|
|
9325
9399
|
console.log();
|
|
9326
9400
|
}
|
|
@@ -9687,7 +9761,7 @@ async function diffConfigCommand(file = DEFAULT_BUNDLE_FILE, options = {}) {
|
|
|
9687
9761
|
//#endregion
|
|
9688
9762
|
//#region src/index.ts
|
|
9689
9763
|
const program = new Command();
|
|
9690
|
-
program.name("amba").description("amba — agent-native backend-as-a-service for mobile apps.").version("4.1.
|
|
9764
|
+
program.name("amba").description("amba — agent-native backend-as-a-service for mobile apps.").version("4.1.5");
|
|
9691
9765
|
program.option("--token <pat>", "Use a Personal Access Token for headless / CI / agent use (overrides ~/.amba/credentials.json + AMBA_PAT env)");
|
|
9692
9766
|
program.hook("preAction", (thisCommand) => {
|
|
9693
9767
|
setBearerOverride(resolveTokenSource({
|
|
@@ -9829,18 +9903,24 @@ program.command("schema").description("Schema export commands").command("export"
|
|
|
9829
9903
|
}));
|
|
9830
9904
|
});
|
|
9831
9905
|
const functions = program.command("functions").description("Customer serverless functions deployed to the edge");
|
|
9832
|
-
functions.command("deploy <target>").description("Bundle a function (a single file OR a directory) and deploy to the dispatch namespace. A directory resolves its entry by convention (index.ts/js/mjs) or via --entry.").option("--name <name>", "Function name (default: file name without extension, or the directory leaf name)").option("--entry <file>", "Entry file when <target> is a directory (relative to it). Default: index.ts/js/mjs.").option("--dry-run", "Bundle and report size without uploading").option("--rate-limit-window <duration>", "Rate-limit window: 60s | 5m | 1h").option("--rate-limit-max <int>", "Rate-limit max requests per window", (v) => Number.parseInt(v, 10)).option("--rate-limit-key <kind>", "Rate-limit bucket key: user_id | ip").option("--public", "Deploy WITHOUT the X-Api-Key gate so external webhook senders can call it. You MUST verify the webhook signature in your handler. Default: private.").action(async (target, opts) => {
|
|
9906
|
+
functions.command("deploy <target>").description("Bundle a function (a single file OR a directory) and deploy to the dispatch namespace. A directory resolves its entry by convention (index.ts/js/mjs) or via --entry.").option("--name <name>", "Function name (default: file name without extension, or the directory leaf name)").option("--entry <file>", "Entry file when <target> is a directory (relative to it). Default: index.ts/js/mjs.").option("--dry-run", "Bundle and report size without uploading").option("--rate-limit-window <duration>", "Rate-limit window: 60s | 5m | 1h").option("--rate-limit-max <int>", "Rate-limit max requests per window", (v) => Number.parseInt(v, 10)).option("--rate-limit-key <kind>", "Rate-limit bucket key: user_id | ip").option("--public", "Deploy WITHOUT the X-Api-Key gate so external webhook senders can call it. You MUST verify the webhook signature in your handler. Default: private.").option("--forward-authorization", "Forward a verified client Authorization Bearer to this function. X-Api-Key and internal trigger credentials remain stripped.").action(async (target, opts) => {
|
|
9833
9907
|
await runAction(() => functionsDeployCommand(target, opts));
|
|
9834
9908
|
});
|
|
9835
9909
|
functions.command("list").description("List active functions for the current project").action(async () => {
|
|
9836
9910
|
await runAction(functionsListCommand);
|
|
9837
9911
|
});
|
|
9912
|
+
functions.command("deploy-status").description("Show whether function deployments are available or paused for maintenance").action(async () => {
|
|
9913
|
+
await runAction(functionsDeployStatusCommand);
|
|
9914
|
+
});
|
|
9838
9915
|
functions.command("delete <name>").description("Disable + remove a function from the dispatch namespace (destructive)").option("--confirm <name>", "Pass the function name to confirm deletion").action(async (name, opts) => {
|
|
9839
9916
|
await runAction(() => functionsDeleteCommand(name, { confirm: opts.confirm }));
|
|
9840
9917
|
});
|
|
9841
9918
|
functions.command("schedule <name> <cron>").description("Register a cron schedule that invokes a deployed function").option("--tz <iana>", "IANA timezone for the schedule (default: UTC)").action(async (name, cron, opts) => {
|
|
9842
9919
|
await runAction(() => functionsScheduleCommand(name, cron, opts));
|
|
9843
9920
|
});
|
|
9921
|
+
functions.command("schedules").description("List function schedules with cron, timezone, state, and last/next fire times").action(async () => {
|
|
9922
|
+
await runAction(functionsSchedulesCommand);
|
|
9923
|
+
});
|
|
9844
9924
|
functions.command("dev <file>").description("Run a local dev server for your function with hot reload on file changes").option("--port <n>", "Port to listen on (default 8787)", (v) => parseInt(v, 10)).option("--no-watch", "Disable file-change hot reload").action(async (file, opts) => {
|
|
9845
9925
|
await runAction(() => functionsDevCommand(file, {
|
|
9846
9926
|
port: opts.port,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@layers/amba",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.5",
|
|
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-
|
|
53
|
-
"@layers/amba-
|
|
52
|
+
"@layers/amba-mcp": "4.0.9",
|
|
53
|
+
"@layers/amba-shared": "4.0.4"
|
|
54
54
|
},
|
|
55
55
|
"scripts": {
|
|
56
56
|
"build": "tsdown && tsc --emitDeclarationOnly",
|
|
@@ -40,6 +40,11 @@ Run user code in a sandbox triggered by HTTP, cron, or webhook. The function get
|
|
|
40
40
|
| `amba_function_domains_attach` | Attach an exact hostname to a function and return DNS validation records. | `{ project_id, name: "feed", hostname: "feeds.example.com" }` |
|
|
41
41
|
| `amba_function_domains_list` / `amba_function_domains_refresh` | Read or refresh ownership/certificate status. | `{ project_id, name: "feed", hostname: "feeds.example.com" }` |
|
|
42
42
|
| `amba_function_domains_remove` | Detach an exact function hostname. | `{ project_id, name: "feed", hostname: "feeds.example.com" }` |
|
|
43
|
+
|
|
44
|
+
For every custom hostname, publish a real provider-stored CNAME to the returned target and heed
|
|
45
|
+
the unconditional `dns_note`. Do not substitute ALIAS, ANAME, or copied A/AAAA addresses. Treat Site and Function hostnames
|
|
46
|
+
as ready only when `live=true` (certificate and hostname activation both active), not from a
|
|
47
|
+
successful TLS handshake or `cert_status=active` alone.
|
|
43
48
|
| `amba_functions_list` / `amba_list_functions` | List functions. | `{ project_id }` |
|
|
44
49
|
| `amba_functions_get` / `amba_get_function` | Read function metadata. | `{ project_id, function_id }` |
|
|
45
50
|
| `amba_functions_get_logs` / `amba_get_function_logs` | Recent invocation logs. | `{ project_id, function_id, limit: 100 }` |
|