@primitivedotdev/cli 0.28.0 → 0.29.0
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 +3 -0
- package/dist/oclif/index.js +1443 -89
- package/package.json +2 -2
package/dist/oclif/index.js
CHANGED
|
@@ -649,6 +649,7 @@ var sdk_gen_exports = /* @__PURE__ */ __exportAll({
|
|
|
649
649
|
getAccount: () => getAccount,
|
|
650
650
|
getEmail: () => getEmail,
|
|
651
651
|
getFunction: () => getFunction,
|
|
652
|
+
getFunctionTestRunTrace: () => getFunctionTestRunTrace,
|
|
652
653
|
getSendPermissions: () => getSendPermissions,
|
|
653
654
|
getSentEmail: () => getSentEmail,
|
|
654
655
|
getStorageStats: () => getStorageStats,
|
|
@@ -1635,6 +1636,24 @@ const testFunction = (options) => (options.client ?? client).post({
|
|
|
1635
1636
|
}
|
|
1636
1637
|
});
|
|
1637
1638
|
/**
|
|
1639
|
+
* Get a function test run trace
|
|
1640
|
+
*
|
|
1641
|
+
* Returns the current end-to-end trace for a function test run.
|
|
1642
|
+
* The trace is intentionally partial while the test is still in
|
|
1643
|
+
* flight: callers can poll this endpoint and watch it fill in
|
|
1644
|
+
* from send -> inbound -> webhook deliveries -> outbound
|
|
1645
|
+
* requests, logs, and replies.
|
|
1646
|
+
*
|
|
1647
|
+
*/
|
|
1648
|
+
const getFunctionTestRunTrace = (options) => (options.client ?? client).get({
|
|
1649
|
+
security: [{
|
|
1650
|
+
scheme: "bearer",
|
|
1651
|
+
type: "http"
|
|
1652
|
+
}],
|
|
1653
|
+
url: "/functions/{id}/test-runs/{run_id}/trace",
|
|
1654
|
+
...options
|
|
1655
|
+
});
|
|
1656
|
+
/**
|
|
1638
1657
|
* List a function's secrets
|
|
1639
1658
|
*
|
|
1640
1659
|
* Returns metadata for every secret bound to the function, with
|
|
@@ -3294,6 +3313,37 @@ const openapiDocument = {
|
|
|
3294
3313
|
}
|
|
3295
3314
|
}
|
|
3296
3315
|
},
|
|
3316
|
+
"/functions/{id}/test-runs/{run_id}/trace": {
|
|
3317
|
+
"parameters": [{ "$ref": "#/components/parameters/ResourceId" }, {
|
|
3318
|
+
"name": "run_id",
|
|
3319
|
+
"in": "path",
|
|
3320
|
+
"required": true,
|
|
3321
|
+
"description": "Function test run id returned by POST /functions/{id}/test.",
|
|
3322
|
+
"schema": {
|
|
3323
|
+
"type": "string",
|
|
3324
|
+
"format": "uuid"
|
|
3325
|
+
}
|
|
3326
|
+
}],
|
|
3327
|
+
"get": {
|
|
3328
|
+
"operationId": "getFunctionTestRunTrace",
|
|
3329
|
+
"summary": "Get a function test run trace",
|
|
3330
|
+
"description": "Returns the current end-to-end trace for a function test run.\nThe trace is intentionally partial while the test is still in\nflight: callers can poll this endpoint and watch it fill in\nfrom send -> inbound -> webhook deliveries -> outbound\nrequests, logs, and replies.\n",
|
|
3331
|
+
"tags": ["Functions"],
|
|
3332
|
+
"responses": {
|
|
3333
|
+
"200": {
|
|
3334
|
+
"description": "Function test run trace",
|
|
3335
|
+
"content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
|
|
3336
|
+
"type": "object",
|
|
3337
|
+
"properties": { "data": { "$ref": "#/components/schemas/FunctionTestRunTrace" } }
|
|
3338
|
+
}] } } }
|
|
3339
|
+
},
|
|
3340
|
+
"400": { "$ref": "#/components/responses/ValidationError" },
|
|
3341
|
+
"401": { "$ref": "#/components/responses/Unauthorized" },
|
|
3342
|
+
"403": { "$ref": "#/components/responses/Forbidden" },
|
|
3343
|
+
"404": { "$ref": "#/components/responses/NotFound" }
|
|
3344
|
+
}
|
|
3345
|
+
}
|
|
3346
|
+
},
|
|
3297
3347
|
"/functions/{id}/secrets": {
|
|
3298
3348
|
"parameters": [{ "$ref": "#/components/parameters/ResourceId" }],
|
|
3299
3349
|
"get": {
|
|
@@ -5594,8 +5644,13 @@ const openapiDocument = {
|
|
|
5594
5644
|
},
|
|
5595
5645
|
"TestInvocationResult": {
|
|
5596
5646
|
"type": "object",
|
|
5597
|
-
"description": "Metadata returned by POST /functions/{id}/test. The send is\nqueued;
|
|
5647
|
+
"description": "Metadata returned by POST /functions/{id}/test. The send is\nqueued; poll `trace_url` to watch the run progress through\nsend -> inbound -> webhook deliveries -> outbound requests,\nlogs, and replies.\n",
|
|
5598
5648
|
"properties": {
|
|
5649
|
+
"test_run_id": {
|
|
5650
|
+
"type": "string",
|
|
5651
|
+
"format": "uuid",
|
|
5652
|
+
"description": "Durable test run id used to fetch the run trace."
|
|
5653
|
+
},
|
|
5599
5654
|
"inbound_domain": {
|
|
5600
5655
|
"type": "string",
|
|
5601
5656
|
"description": "Verified inbound domain the test email was sent to."
|
|
@@ -5625,16 +5680,339 @@ const openapiDocument = {
|
|
|
5625
5680
|
"type": "string",
|
|
5626
5681
|
"format": "uri",
|
|
5627
5682
|
"description": "Function detail page where invocations show up live."
|
|
5683
|
+
},
|
|
5684
|
+
"trace_url": {
|
|
5685
|
+
"type": "string",
|
|
5686
|
+
"description": "Relative API URL for GET /functions/{id}/test-runs/{test_run_id}/trace."
|
|
5628
5687
|
}
|
|
5629
5688
|
},
|
|
5630
5689
|
"required": [
|
|
5690
|
+
"test_run_id",
|
|
5631
5691
|
"inbound_domain",
|
|
5632
5692
|
"to",
|
|
5633
5693
|
"from",
|
|
5634
5694
|
"send_id",
|
|
5635
5695
|
"subject",
|
|
5636
5696
|
"poll_since",
|
|
5637
|
-
"watch_url"
|
|
5697
|
+
"watch_url",
|
|
5698
|
+
"trace_url"
|
|
5699
|
+
]
|
|
5700
|
+
},
|
|
5701
|
+
"FunctionTestRunState": {
|
|
5702
|
+
"type": "string",
|
|
5703
|
+
"description": "High-level state for a function test run trace:\n - `send_failed`: the initial test email send failed.\n - `waiting_for_send`: the test run was created but no send result has been recorded yet.\n - `waiting_for_inbound`: the test send was queued and the matching inbound email has not arrived yet.\n - `waiting_for_function`: the inbound email arrived and webhook/function processing is still in flight.\n - `completed`: the function webhook completed successfully.\n - `failed`: webhook delivery exhausted retries.\n",
|
|
5704
|
+
"enum": [
|
|
5705
|
+
"send_failed",
|
|
5706
|
+
"waiting_for_send",
|
|
5707
|
+
"waiting_for_inbound",
|
|
5708
|
+
"waiting_for_function",
|
|
5709
|
+
"completed",
|
|
5710
|
+
"failed"
|
|
5711
|
+
]
|
|
5712
|
+
},
|
|
5713
|
+
"FunctionTestRun": {
|
|
5714
|
+
"type": "object",
|
|
5715
|
+
"properties": {
|
|
5716
|
+
"id": {
|
|
5717
|
+
"type": "string",
|
|
5718
|
+
"format": "uuid"
|
|
5719
|
+
},
|
|
5720
|
+
"function_id": {
|
|
5721
|
+
"type": "string",
|
|
5722
|
+
"format": "uuid"
|
|
5723
|
+
},
|
|
5724
|
+
"inbound_domain": { "type": "string" },
|
|
5725
|
+
"to": { "type": "string" },
|
|
5726
|
+
"from": { "type": "string" },
|
|
5727
|
+
"subject": { "type": "string" },
|
|
5728
|
+
"poll_since": {
|
|
5729
|
+
"type": "string",
|
|
5730
|
+
"format": "date-time"
|
|
5731
|
+
},
|
|
5732
|
+
"created_at": {
|
|
5733
|
+
"type": "string",
|
|
5734
|
+
"format": "date-time"
|
|
5735
|
+
},
|
|
5736
|
+
"sent_at": {
|
|
5737
|
+
"type": ["string", "null"],
|
|
5738
|
+
"format": "date-time"
|
|
5739
|
+
},
|
|
5740
|
+
"send_error": { "type": ["string", "null"] }
|
|
5741
|
+
},
|
|
5742
|
+
"required": [
|
|
5743
|
+
"id",
|
|
5744
|
+
"function_id",
|
|
5745
|
+
"inbound_domain",
|
|
5746
|
+
"to",
|
|
5747
|
+
"from",
|
|
5748
|
+
"subject",
|
|
5749
|
+
"poll_since",
|
|
5750
|
+
"created_at",
|
|
5751
|
+
"sent_at",
|
|
5752
|
+
"send_error"
|
|
5753
|
+
]
|
|
5754
|
+
},
|
|
5755
|
+
"FunctionTestRunSend": {
|
|
5756
|
+
"type": ["object", "null"],
|
|
5757
|
+
"properties": {
|
|
5758
|
+
"id": {
|
|
5759
|
+
"type": "string",
|
|
5760
|
+
"format": "uuid"
|
|
5761
|
+
},
|
|
5762
|
+
"status": { "$ref": "#/components/schemas/SentEmailStatus" },
|
|
5763
|
+
"queue_id": { "type": ["string", "null"] },
|
|
5764
|
+
"created_at": {
|
|
5765
|
+
"type": "string",
|
|
5766
|
+
"format": "date-time"
|
|
5767
|
+
},
|
|
5768
|
+
"updated_at": {
|
|
5769
|
+
"type": "string",
|
|
5770
|
+
"format": "date-time"
|
|
5771
|
+
}
|
|
5772
|
+
},
|
|
5773
|
+
"required": [
|
|
5774
|
+
"id",
|
|
5775
|
+
"status",
|
|
5776
|
+
"queue_id",
|
|
5777
|
+
"created_at",
|
|
5778
|
+
"updated_at"
|
|
5779
|
+
]
|
|
5780
|
+
},
|
|
5781
|
+
"FunctionTestRunInboundEmail": {
|
|
5782
|
+
"type": ["object", "null"],
|
|
5783
|
+
"properties": {
|
|
5784
|
+
"id": {
|
|
5785
|
+
"type": "string",
|
|
5786
|
+
"format": "uuid"
|
|
5787
|
+
},
|
|
5788
|
+
"status": { "$ref": "#/components/schemas/EmailStatus" },
|
|
5789
|
+
"received_at": {
|
|
5790
|
+
"type": "string",
|
|
5791
|
+
"format": "date-time"
|
|
5792
|
+
},
|
|
5793
|
+
"from": { "type": "string" },
|
|
5794
|
+
"to": { "type": "string" },
|
|
5795
|
+
"subject": { "type": ["string", "null"] },
|
|
5796
|
+
"webhook_status": { "$ref": "#/components/schemas/EmailWebhookStatus" },
|
|
5797
|
+
"webhook_attempt_count": { "type": "integer" },
|
|
5798
|
+
"webhook_last_status_code": { "type": ["integer", "null"] },
|
|
5799
|
+
"webhook_last_error": { "type": ["string", "null"] }
|
|
5800
|
+
},
|
|
5801
|
+
"required": [
|
|
5802
|
+
"id",
|
|
5803
|
+
"status",
|
|
5804
|
+
"received_at",
|
|
5805
|
+
"from",
|
|
5806
|
+
"to",
|
|
5807
|
+
"subject",
|
|
5808
|
+
"webhook_status",
|
|
5809
|
+
"webhook_attempt_count",
|
|
5810
|
+
"webhook_last_status_code",
|
|
5811
|
+
"webhook_last_error"
|
|
5812
|
+
]
|
|
5813
|
+
},
|
|
5814
|
+
"FunctionTestRunDeliveryEndpoint": {
|
|
5815
|
+
"type": ["object", "null"],
|
|
5816
|
+
"properties": {
|
|
5817
|
+
"id": {
|
|
5818
|
+
"type": "string",
|
|
5819
|
+
"format": "uuid"
|
|
5820
|
+
},
|
|
5821
|
+
"kind": {
|
|
5822
|
+
"type": "string",
|
|
5823
|
+
"description": "Endpoint kind. Current traces may include `http` or `function`; future endpoint kinds may appear."
|
|
5824
|
+
},
|
|
5825
|
+
"function_id": {
|
|
5826
|
+
"type": ["string", "null"],
|
|
5827
|
+
"format": "uuid"
|
|
5828
|
+
},
|
|
5829
|
+
"function_name": { "type": ["string", "null"] },
|
|
5830
|
+
"domain_id": {
|
|
5831
|
+
"type": ["string", "null"],
|
|
5832
|
+
"format": "uuid"
|
|
5833
|
+
},
|
|
5834
|
+
"enabled": { "type": "boolean" },
|
|
5835
|
+
"deactivated_at": {
|
|
5836
|
+
"type": ["string", "null"],
|
|
5837
|
+
"format": "date-time"
|
|
5838
|
+
},
|
|
5839
|
+
"is_current_function": { "type": "boolean" }
|
|
5840
|
+
},
|
|
5841
|
+
"required": [
|
|
5842
|
+
"id",
|
|
5843
|
+
"kind",
|
|
5844
|
+
"function_id",
|
|
5845
|
+
"function_name",
|
|
5846
|
+
"domain_id",
|
|
5847
|
+
"enabled",
|
|
5848
|
+
"deactivated_at",
|
|
5849
|
+
"is_current_function"
|
|
5850
|
+
]
|
|
5851
|
+
},
|
|
5852
|
+
"FunctionTestRunDelivery": {
|
|
5853
|
+
"type": "object",
|
|
5854
|
+
"properties": {
|
|
5855
|
+
"id": {
|
|
5856
|
+
"type": "string",
|
|
5857
|
+
"description": "Webhook delivery id."
|
|
5858
|
+
},
|
|
5859
|
+
"endpoint_id": {
|
|
5860
|
+
"type": "string",
|
|
5861
|
+
"format": "uuid"
|
|
5862
|
+
},
|
|
5863
|
+
"endpoint_url": {
|
|
5864
|
+
"type": "string",
|
|
5865
|
+
"format": "uri"
|
|
5866
|
+
},
|
|
5867
|
+
"status": {
|
|
5868
|
+
"type": "string",
|
|
5869
|
+
"enum": [
|
|
5870
|
+
"pending",
|
|
5871
|
+
"delivered",
|
|
5872
|
+
"header_confirmed",
|
|
5873
|
+
"failed"
|
|
5874
|
+
]
|
|
5875
|
+
},
|
|
5876
|
+
"attempt_count": { "type": "integer" },
|
|
5877
|
+
"duration_ms": { "type": ["integer", "null"] },
|
|
5878
|
+
"last_error": { "type": ["string", "null"] },
|
|
5879
|
+
"last_error_code": { "type": ["string", "null"] },
|
|
5880
|
+
"created_at": {
|
|
5881
|
+
"type": "string",
|
|
5882
|
+
"format": "date-time"
|
|
5883
|
+
},
|
|
5884
|
+
"updated_at": {
|
|
5885
|
+
"type": "string",
|
|
5886
|
+
"format": "date-time"
|
|
5887
|
+
},
|
|
5888
|
+
"endpoint": { "$ref": "#/components/schemas/FunctionTestRunDeliveryEndpoint" }
|
|
5889
|
+
},
|
|
5890
|
+
"required": [
|
|
5891
|
+
"id",
|
|
5892
|
+
"endpoint_id",
|
|
5893
|
+
"endpoint_url",
|
|
5894
|
+
"status",
|
|
5895
|
+
"attempt_count",
|
|
5896
|
+
"duration_ms",
|
|
5897
|
+
"last_error",
|
|
5898
|
+
"last_error_code",
|
|
5899
|
+
"created_at",
|
|
5900
|
+
"updated_at",
|
|
5901
|
+
"endpoint"
|
|
5902
|
+
]
|
|
5903
|
+
},
|
|
5904
|
+
"FunctionTestRunOutboundRequest": {
|
|
5905
|
+
"type": "object",
|
|
5906
|
+
"properties": {
|
|
5907
|
+
"id": {
|
|
5908
|
+
"type": "string",
|
|
5909
|
+
"format": "uuid"
|
|
5910
|
+
},
|
|
5911
|
+
"function_id": {
|
|
5912
|
+
"type": "string",
|
|
5913
|
+
"format": "uuid"
|
|
5914
|
+
},
|
|
5915
|
+
"webhook_delivery_id": { "type": ["string", "null"] },
|
|
5916
|
+
"email_id": {
|
|
5917
|
+
"type": ["string", "null"],
|
|
5918
|
+
"format": "uuid"
|
|
5919
|
+
},
|
|
5920
|
+
"endpoint_id": {
|
|
5921
|
+
"type": ["string", "null"],
|
|
5922
|
+
"format": "uuid"
|
|
5923
|
+
},
|
|
5924
|
+
"method": { "type": "string" },
|
|
5925
|
+
"url": {
|
|
5926
|
+
"type": "string",
|
|
5927
|
+
"format": "uri"
|
|
5928
|
+
},
|
|
5929
|
+
"host": { "type": "string" },
|
|
5930
|
+
"path": { "type": "string" },
|
|
5931
|
+
"status_code": { "type": ["integer", "null"] },
|
|
5932
|
+
"ok": { "type": ["boolean", "null"] },
|
|
5933
|
+
"duration_ms": { "type": "integer" },
|
|
5934
|
+
"error": { "type": ["string", "null"] },
|
|
5935
|
+
"ts": {
|
|
5936
|
+
"type": "string",
|
|
5937
|
+
"format": "date-time"
|
|
5938
|
+
}
|
|
5939
|
+
},
|
|
5940
|
+
"required": [
|
|
5941
|
+
"id",
|
|
5942
|
+
"function_id",
|
|
5943
|
+
"webhook_delivery_id",
|
|
5944
|
+
"email_id",
|
|
5945
|
+
"endpoint_id",
|
|
5946
|
+
"method",
|
|
5947
|
+
"url",
|
|
5948
|
+
"host",
|
|
5949
|
+
"path",
|
|
5950
|
+
"status_code",
|
|
5951
|
+
"ok",
|
|
5952
|
+
"duration_ms",
|
|
5953
|
+
"error",
|
|
5954
|
+
"ts"
|
|
5955
|
+
]
|
|
5956
|
+
},
|
|
5957
|
+
"FunctionTestRunReply": {
|
|
5958
|
+
"type": "object",
|
|
5959
|
+
"properties": {
|
|
5960
|
+
"id": {
|
|
5961
|
+
"type": "string",
|
|
5962
|
+
"format": "uuid"
|
|
5963
|
+
},
|
|
5964
|
+
"status": { "$ref": "#/components/schemas/SentEmailStatus" },
|
|
5965
|
+
"to": { "type": "string" },
|
|
5966
|
+
"subject": { "type": "string" },
|
|
5967
|
+
"queue_id": { "type": ["string", "null"] },
|
|
5968
|
+
"created_at": {
|
|
5969
|
+
"type": "string",
|
|
5970
|
+
"format": "date-time"
|
|
5971
|
+
}
|
|
5972
|
+
},
|
|
5973
|
+
"required": [
|
|
5974
|
+
"id",
|
|
5975
|
+
"status",
|
|
5976
|
+
"to",
|
|
5977
|
+
"subject",
|
|
5978
|
+
"queue_id",
|
|
5979
|
+
"created_at"
|
|
5980
|
+
]
|
|
5981
|
+
},
|
|
5982
|
+
"FunctionTestRunTrace": {
|
|
5983
|
+
"type": "object",
|
|
5984
|
+
"description": "End-to-end trace for a `POST /functions/{id}/test` run. The\nshape is stable, but many nested sections are null or empty\nuntil the corresponding phase has happened.\n",
|
|
5985
|
+
"properties": {
|
|
5986
|
+
"state": { "$ref": "#/components/schemas/FunctionTestRunState" },
|
|
5987
|
+
"test_run": { "$ref": "#/components/schemas/FunctionTestRun" },
|
|
5988
|
+
"test_send": { "$ref": "#/components/schemas/FunctionTestRunSend" },
|
|
5989
|
+
"inbound_email": { "$ref": "#/components/schemas/FunctionTestRunInboundEmail" },
|
|
5990
|
+
"deliveries": {
|
|
5991
|
+
"type": "array",
|
|
5992
|
+
"items": { "$ref": "#/components/schemas/FunctionTestRunDelivery" }
|
|
5993
|
+
},
|
|
5994
|
+
"outbound_requests": {
|
|
5995
|
+
"type": "array",
|
|
5996
|
+
"items": { "$ref": "#/components/schemas/FunctionTestRunOutboundRequest" }
|
|
5997
|
+
},
|
|
5998
|
+
"logs": {
|
|
5999
|
+
"type": "array",
|
|
6000
|
+
"items": { "$ref": "#/components/schemas/FunctionLogRow" }
|
|
6001
|
+
},
|
|
6002
|
+
"replies": {
|
|
6003
|
+
"type": "array",
|
|
6004
|
+
"items": { "$ref": "#/components/schemas/FunctionTestRunReply" }
|
|
6005
|
+
}
|
|
6006
|
+
},
|
|
6007
|
+
"required": [
|
|
6008
|
+
"state",
|
|
6009
|
+
"test_run",
|
|
6010
|
+
"test_send",
|
|
6011
|
+
"inbound_email",
|
|
6012
|
+
"deliveries",
|
|
6013
|
+
"outbound_requests",
|
|
6014
|
+
"logs",
|
|
6015
|
+
"replies"
|
|
5638
6016
|
]
|
|
5639
6017
|
},
|
|
5640
6018
|
"FunctionLogRow": {
|
|
@@ -8266,23 +8644,456 @@ const operationManifest = [
|
|
|
8266
8644
|
"type": "string",
|
|
8267
8645
|
"format": "date-time"
|
|
8268
8646
|
},
|
|
8269
|
-
"updated_at": {
|
|
8270
|
-
"type": "string",
|
|
8271
|
-
"format": "date-time"
|
|
8647
|
+
"updated_at": {
|
|
8648
|
+
"type": "string",
|
|
8649
|
+
"format": "date-time"
|
|
8650
|
+
}
|
|
8651
|
+
},
|
|
8652
|
+
"required": [
|
|
8653
|
+
"id",
|
|
8654
|
+
"name",
|
|
8655
|
+
"code",
|
|
8656
|
+
"deploy_status",
|
|
8657
|
+
"gateway_url",
|
|
8658
|
+
"created_at",
|
|
8659
|
+
"updated_at"
|
|
8660
|
+
]
|
|
8661
|
+
},
|
|
8662
|
+
"sdkName": "getFunction",
|
|
8663
|
+
"summary": "Get a function",
|
|
8664
|
+
"tag": "Functions",
|
|
8665
|
+
"tagCommand": "functions"
|
|
8666
|
+
},
|
|
8667
|
+
{
|
|
8668
|
+
"binaryResponse": false,
|
|
8669
|
+
"bodyRequired": false,
|
|
8670
|
+
"command": "get-function-test-run-trace",
|
|
8671
|
+
"description": "Returns the current end-to-end trace for a function test run.\nThe trace is intentionally partial while the test is still in\nflight: callers can poll this endpoint and watch it fill in\nfrom send -> inbound -> webhook deliveries -> outbound\nrequests, logs, and replies.\n",
|
|
8672
|
+
"hasJsonBody": false,
|
|
8673
|
+
"method": "GET",
|
|
8674
|
+
"operationId": "getFunctionTestRunTrace",
|
|
8675
|
+
"path": "/functions/{id}/test-runs/{run_id}/trace",
|
|
8676
|
+
"pathParams": [{
|
|
8677
|
+
"description": "Resource UUID",
|
|
8678
|
+
"enum": null,
|
|
8679
|
+
"name": "id",
|
|
8680
|
+
"required": true,
|
|
8681
|
+
"type": "string"
|
|
8682
|
+
}, {
|
|
8683
|
+
"description": "Function test run id returned by POST /functions/{id}/test.",
|
|
8684
|
+
"enum": null,
|
|
8685
|
+
"name": "run_id",
|
|
8686
|
+
"required": true,
|
|
8687
|
+
"type": "string"
|
|
8688
|
+
}],
|
|
8689
|
+
"queryParams": [],
|
|
8690
|
+
"requestSchema": null,
|
|
8691
|
+
"responseSchema": {
|
|
8692
|
+
"type": "object",
|
|
8693
|
+
"description": "End-to-end trace for a `POST /functions/{id}/test` run. The\nshape is stable, but many nested sections are null or empty\nuntil the corresponding phase has happened.\n",
|
|
8694
|
+
"properties": {
|
|
8695
|
+
"state": {
|
|
8696
|
+
"type": "string",
|
|
8697
|
+
"description": "High-level state for a function test run trace:\n - `send_failed`: the initial test email send failed.\n - `waiting_for_send`: the test run was created but no send result has been recorded yet.\n - `waiting_for_inbound`: the test send was queued and the matching inbound email has not arrived yet.\n - `waiting_for_function`: the inbound email arrived and webhook/function processing is still in flight.\n - `completed`: the function webhook completed successfully.\n - `failed`: webhook delivery exhausted retries.\n",
|
|
8698
|
+
"enum": [
|
|
8699
|
+
"send_failed",
|
|
8700
|
+
"waiting_for_send",
|
|
8701
|
+
"waiting_for_inbound",
|
|
8702
|
+
"waiting_for_function",
|
|
8703
|
+
"completed",
|
|
8704
|
+
"failed"
|
|
8705
|
+
]
|
|
8706
|
+
},
|
|
8707
|
+
"test_run": {
|
|
8708
|
+
"type": "object",
|
|
8709
|
+
"properties": {
|
|
8710
|
+
"id": {
|
|
8711
|
+
"type": "string",
|
|
8712
|
+
"format": "uuid"
|
|
8713
|
+
},
|
|
8714
|
+
"function_id": {
|
|
8715
|
+
"type": "string",
|
|
8716
|
+
"format": "uuid"
|
|
8717
|
+
},
|
|
8718
|
+
"inbound_domain": { "type": "string" },
|
|
8719
|
+
"to": { "type": "string" },
|
|
8720
|
+
"from": { "type": "string" },
|
|
8721
|
+
"subject": { "type": "string" },
|
|
8722
|
+
"poll_since": {
|
|
8723
|
+
"type": "string",
|
|
8724
|
+
"format": "date-time"
|
|
8725
|
+
},
|
|
8726
|
+
"created_at": {
|
|
8727
|
+
"type": "string",
|
|
8728
|
+
"format": "date-time"
|
|
8729
|
+
},
|
|
8730
|
+
"sent_at": {
|
|
8731
|
+
"type": ["string", "null"],
|
|
8732
|
+
"format": "date-time"
|
|
8733
|
+
},
|
|
8734
|
+
"send_error": { "type": ["string", "null"] }
|
|
8735
|
+
},
|
|
8736
|
+
"required": [
|
|
8737
|
+
"id",
|
|
8738
|
+
"function_id",
|
|
8739
|
+
"inbound_domain",
|
|
8740
|
+
"to",
|
|
8741
|
+
"from",
|
|
8742
|
+
"subject",
|
|
8743
|
+
"poll_since",
|
|
8744
|
+
"created_at",
|
|
8745
|
+
"sent_at",
|
|
8746
|
+
"send_error"
|
|
8747
|
+
]
|
|
8748
|
+
},
|
|
8749
|
+
"test_send": {
|
|
8750
|
+
"type": ["object", "null"],
|
|
8751
|
+
"properties": {
|
|
8752
|
+
"id": {
|
|
8753
|
+
"type": "string",
|
|
8754
|
+
"format": "uuid"
|
|
8755
|
+
},
|
|
8756
|
+
"status": {
|
|
8757
|
+
"type": "string",
|
|
8758
|
+
"description": "Lifecycle status of a sent_emails row. Possible values:\n\n - `queued`: pre-call INSERT; the outbound agent has not\n yet replied.\n - `submitted_to_agent`: agent accepted; `queue_id` is set.\n - `agent_failed`: agent rejected; `error_code` and\n `error_message` carry the reason.\n - `gate_denied`: a recipient-scope gate denied the send;\n the agent was never called. The `gates` array carries\n the denial detail. /send-mail returns 403 in this case\n so callers see the denial synchronously; /sent-emails\n additionally records the row for historical lookup,\n which is when this status appears in a listing.\n - `unknown`: terminal indeterminate; the on-box log\n poller couldn't classify the receiver's response.\n - `delivered` / `bounced` / `deferred` / `wait_timeout`:\n terminal delivery outcomes (see DeliveryStatus).\n",
|
|
8759
|
+
"enum": [
|
|
8760
|
+
"queued",
|
|
8761
|
+
"submitted_to_agent",
|
|
8762
|
+
"agent_failed",
|
|
8763
|
+
"gate_denied",
|
|
8764
|
+
"unknown",
|
|
8765
|
+
"delivered",
|
|
8766
|
+
"bounced",
|
|
8767
|
+
"deferred",
|
|
8768
|
+
"wait_timeout"
|
|
8769
|
+
]
|
|
8770
|
+
},
|
|
8771
|
+
"queue_id": { "type": ["string", "null"] },
|
|
8772
|
+
"created_at": {
|
|
8773
|
+
"type": "string",
|
|
8774
|
+
"format": "date-time"
|
|
8775
|
+
},
|
|
8776
|
+
"updated_at": {
|
|
8777
|
+
"type": "string",
|
|
8778
|
+
"format": "date-time"
|
|
8779
|
+
}
|
|
8780
|
+
},
|
|
8781
|
+
"required": [
|
|
8782
|
+
"id",
|
|
8783
|
+
"status",
|
|
8784
|
+
"queue_id",
|
|
8785
|
+
"created_at",
|
|
8786
|
+
"updated_at"
|
|
8787
|
+
]
|
|
8788
|
+
},
|
|
8789
|
+
"inbound_email": {
|
|
8790
|
+
"type": ["object", "null"],
|
|
8791
|
+
"properties": {
|
|
8792
|
+
"id": {
|
|
8793
|
+
"type": "string",
|
|
8794
|
+
"format": "uuid"
|
|
8795
|
+
},
|
|
8796
|
+
"status": {
|
|
8797
|
+
"type": "string",
|
|
8798
|
+
"description": "Lifecycle status of an INBOUND email (a row in the `emails`\ntable). Distinct from `SentEmailStatus`, which describes\nthe OUTBOUND lifecycle (the `sent_emails` table) and uses\na different vocabulary because the lifecycles differ.\nPossible values:\n\n - `pending`: the row was inserted at ingestion (mx_main)\n and has not yet completed the spam / filter / auth\n pipeline. Body and parsed fields are present; webhook\n delivery is not yet scheduled. Most rows transition out\n of `pending` within seconds.\n - `accepted`: the inbound passed the policy gates and is\n queued for webhook delivery. The `webhook_status` field\n tracks the separate webhook-delivery lifecycle from\n this point.\n - `completed`: terminal success. Webhook delivery\n attempted and acknowledged by every active endpoint, OR\n no endpoints are configured, so the row is durably\n archived.\n - `rejected`: terminal failure at ingestion (spam, blocked\n sender, filter rule, malformed). The body and metadata\n are stored for auditing but no webhook fires and the\n row is not repliable.\n\nSee also `webhook_status` (separate enum tracking the\nwebhook-delivery state machine) and `SentEmailStatus` (the\noutbound vocabulary).\n",
|
|
8799
|
+
"enum": [
|
|
8800
|
+
"pending",
|
|
8801
|
+
"accepted",
|
|
8802
|
+
"completed",
|
|
8803
|
+
"rejected"
|
|
8804
|
+
]
|
|
8805
|
+
},
|
|
8806
|
+
"received_at": {
|
|
8807
|
+
"type": "string",
|
|
8808
|
+
"format": "date-time"
|
|
8809
|
+
},
|
|
8810
|
+
"from": { "type": "string" },
|
|
8811
|
+
"to": { "type": "string" },
|
|
8812
|
+
"subject": { "type": ["string", "null"] },
|
|
8813
|
+
"webhook_status": {
|
|
8814
|
+
"type": ["string", "null"],
|
|
8815
|
+
"description": "Webhook-delivery state for an inbound email. Tracks a\nSEPARATE lifecycle from the email's `status` field; the\nsame row carries both. Possible values:\n\n - `pending`: ingestion is past `pending` (the email itself\n is `accepted`) but the webhook fan-out has not yet\n started for this row.\n - `in_flight`: at least one delivery attempt is in flight.\n - `fired`: terminal success. Every active endpoint\n acknowledged the delivery (or accepted it after retries).\n - `failed`: terminal partial-failure. At least one endpoint\n exhausted its retry budget; some endpoints may still\n have succeeded.\n - `exhausted`: terminal failure. Every endpoint exhausted\n its retry budget without success.\n - `null`: no endpoints configured, so no webhook lifecycle\n applies.\n\nNote that the value `pending` here does NOT mean the email\nis `pending`; it means the email is past ingestion but\nwebhook delivery has not yet begun. Two overlapping uses\nof the word `pending` for distinct lifecycle phases.\n",
|
|
8816
|
+
"enum": [
|
|
8817
|
+
"pending",
|
|
8818
|
+
"in_flight",
|
|
8819
|
+
"fired",
|
|
8820
|
+
"failed",
|
|
8821
|
+
"exhausted",
|
|
8822
|
+
null
|
|
8823
|
+
]
|
|
8824
|
+
},
|
|
8825
|
+
"webhook_attempt_count": { "type": "integer" },
|
|
8826
|
+
"webhook_last_status_code": { "type": ["integer", "null"] },
|
|
8827
|
+
"webhook_last_error": { "type": ["string", "null"] }
|
|
8828
|
+
},
|
|
8829
|
+
"required": [
|
|
8830
|
+
"id",
|
|
8831
|
+
"status",
|
|
8832
|
+
"received_at",
|
|
8833
|
+
"from",
|
|
8834
|
+
"to",
|
|
8835
|
+
"subject",
|
|
8836
|
+
"webhook_status",
|
|
8837
|
+
"webhook_attempt_count",
|
|
8838
|
+
"webhook_last_status_code",
|
|
8839
|
+
"webhook_last_error"
|
|
8840
|
+
]
|
|
8841
|
+
},
|
|
8842
|
+
"deliveries": {
|
|
8843
|
+
"type": "array",
|
|
8844
|
+
"items": {
|
|
8845
|
+
"type": "object",
|
|
8846
|
+
"properties": {
|
|
8847
|
+
"id": {
|
|
8848
|
+
"type": "string",
|
|
8849
|
+
"description": "Webhook delivery id."
|
|
8850
|
+
},
|
|
8851
|
+
"endpoint_id": {
|
|
8852
|
+
"type": "string",
|
|
8853
|
+
"format": "uuid"
|
|
8854
|
+
},
|
|
8855
|
+
"endpoint_url": {
|
|
8856
|
+
"type": "string",
|
|
8857
|
+
"format": "uri"
|
|
8858
|
+
},
|
|
8859
|
+
"status": {
|
|
8860
|
+
"type": "string",
|
|
8861
|
+
"enum": [
|
|
8862
|
+
"pending",
|
|
8863
|
+
"delivered",
|
|
8864
|
+
"header_confirmed",
|
|
8865
|
+
"failed"
|
|
8866
|
+
]
|
|
8867
|
+
},
|
|
8868
|
+
"attempt_count": { "type": "integer" },
|
|
8869
|
+
"duration_ms": { "type": ["integer", "null"] },
|
|
8870
|
+
"last_error": { "type": ["string", "null"] },
|
|
8871
|
+
"last_error_code": { "type": ["string", "null"] },
|
|
8872
|
+
"created_at": {
|
|
8873
|
+
"type": "string",
|
|
8874
|
+
"format": "date-time"
|
|
8875
|
+
},
|
|
8876
|
+
"updated_at": {
|
|
8877
|
+
"type": "string",
|
|
8878
|
+
"format": "date-time"
|
|
8879
|
+
},
|
|
8880
|
+
"endpoint": {
|
|
8881
|
+
"type": ["object", "null"],
|
|
8882
|
+
"properties": {
|
|
8883
|
+
"id": {
|
|
8884
|
+
"type": "string",
|
|
8885
|
+
"format": "uuid"
|
|
8886
|
+
},
|
|
8887
|
+
"kind": {
|
|
8888
|
+
"type": "string",
|
|
8889
|
+
"description": "Endpoint kind. Current traces may include `http` or `function`; future endpoint kinds may appear."
|
|
8890
|
+
},
|
|
8891
|
+
"function_id": {
|
|
8892
|
+
"type": ["string", "null"],
|
|
8893
|
+
"format": "uuid"
|
|
8894
|
+
},
|
|
8895
|
+
"function_name": { "type": ["string", "null"] },
|
|
8896
|
+
"domain_id": {
|
|
8897
|
+
"type": ["string", "null"],
|
|
8898
|
+
"format": "uuid"
|
|
8899
|
+
},
|
|
8900
|
+
"enabled": { "type": "boolean" },
|
|
8901
|
+
"deactivated_at": {
|
|
8902
|
+
"type": ["string", "null"],
|
|
8903
|
+
"format": "date-time"
|
|
8904
|
+
},
|
|
8905
|
+
"is_current_function": { "type": "boolean" }
|
|
8906
|
+
},
|
|
8907
|
+
"required": [
|
|
8908
|
+
"id",
|
|
8909
|
+
"kind",
|
|
8910
|
+
"function_id",
|
|
8911
|
+
"function_name",
|
|
8912
|
+
"domain_id",
|
|
8913
|
+
"enabled",
|
|
8914
|
+
"deactivated_at",
|
|
8915
|
+
"is_current_function"
|
|
8916
|
+
]
|
|
8917
|
+
}
|
|
8918
|
+
},
|
|
8919
|
+
"required": [
|
|
8920
|
+
"id",
|
|
8921
|
+
"endpoint_id",
|
|
8922
|
+
"endpoint_url",
|
|
8923
|
+
"status",
|
|
8924
|
+
"attempt_count",
|
|
8925
|
+
"duration_ms",
|
|
8926
|
+
"last_error",
|
|
8927
|
+
"last_error_code",
|
|
8928
|
+
"created_at",
|
|
8929
|
+
"updated_at",
|
|
8930
|
+
"endpoint"
|
|
8931
|
+
]
|
|
8932
|
+
}
|
|
8933
|
+
},
|
|
8934
|
+
"outbound_requests": {
|
|
8935
|
+
"type": "array",
|
|
8936
|
+
"items": {
|
|
8937
|
+
"type": "object",
|
|
8938
|
+
"properties": {
|
|
8939
|
+
"id": {
|
|
8940
|
+
"type": "string",
|
|
8941
|
+
"format": "uuid"
|
|
8942
|
+
},
|
|
8943
|
+
"function_id": {
|
|
8944
|
+
"type": "string",
|
|
8945
|
+
"format": "uuid"
|
|
8946
|
+
},
|
|
8947
|
+
"webhook_delivery_id": { "type": ["string", "null"] },
|
|
8948
|
+
"email_id": {
|
|
8949
|
+
"type": ["string", "null"],
|
|
8950
|
+
"format": "uuid"
|
|
8951
|
+
},
|
|
8952
|
+
"endpoint_id": {
|
|
8953
|
+
"type": ["string", "null"],
|
|
8954
|
+
"format": "uuid"
|
|
8955
|
+
},
|
|
8956
|
+
"method": { "type": "string" },
|
|
8957
|
+
"url": {
|
|
8958
|
+
"type": "string",
|
|
8959
|
+
"format": "uri"
|
|
8960
|
+
},
|
|
8961
|
+
"host": { "type": "string" },
|
|
8962
|
+
"path": { "type": "string" },
|
|
8963
|
+
"status_code": { "type": ["integer", "null"] },
|
|
8964
|
+
"ok": { "type": ["boolean", "null"] },
|
|
8965
|
+
"duration_ms": { "type": "integer" },
|
|
8966
|
+
"error": { "type": ["string", "null"] },
|
|
8967
|
+
"ts": {
|
|
8968
|
+
"type": "string",
|
|
8969
|
+
"format": "date-time"
|
|
8970
|
+
}
|
|
8971
|
+
},
|
|
8972
|
+
"required": [
|
|
8973
|
+
"id",
|
|
8974
|
+
"function_id",
|
|
8975
|
+
"webhook_delivery_id",
|
|
8976
|
+
"email_id",
|
|
8977
|
+
"endpoint_id",
|
|
8978
|
+
"method",
|
|
8979
|
+
"url",
|
|
8980
|
+
"host",
|
|
8981
|
+
"path",
|
|
8982
|
+
"status_code",
|
|
8983
|
+
"ok",
|
|
8984
|
+
"duration_ms",
|
|
8985
|
+
"error",
|
|
8986
|
+
"ts"
|
|
8987
|
+
]
|
|
8988
|
+
}
|
|
8989
|
+
},
|
|
8990
|
+
"logs": {
|
|
8991
|
+
"type": "array",
|
|
8992
|
+
"items": {
|
|
8993
|
+
"type": "object",
|
|
8994
|
+
"description": "One row from GET /functions/{id}/logs. Represents a single\ncaptured log line emitted by the running handler (e.g. via\n`console.log` / `console.error`).\n",
|
|
8995
|
+
"properties": {
|
|
8996
|
+
"id": {
|
|
8997
|
+
"type": "string",
|
|
8998
|
+
"format": "uuid",
|
|
8999
|
+
"description": "Unique log row id (stable across pages)."
|
|
9000
|
+
},
|
|
9001
|
+
"function_id": {
|
|
9002
|
+
"type": "string",
|
|
9003
|
+
"format": "uuid",
|
|
9004
|
+
"description": "The function this log row belongs to."
|
|
9005
|
+
},
|
|
9006
|
+
"level": {
|
|
9007
|
+
"type": "string",
|
|
9008
|
+
"enum": [
|
|
9009
|
+
"debug",
|
|
9010
|
+
"log",
|
|
9011
|
+
"info",
|
|
9012
|
+
"warn",
|
|
9013
|
+
"error"
|
|
9014
|
+
],
|
|
9015
|
+
"description": "Severity. `log` is the runtime's default for unannotated\n`console.log` calls; the other levels match standard\n`console.*` methods.\n"
|
|
9016
|
+
},
|
|
9017
|
+
"message": {
|
|
9018
|
+
"type": "string",
|
|
9019
|
+
"description": "The textual message body. The runtime stringifies non-string\narguments before persisting, so this is always a plain\nstring.\n"
|
|
9020
|
+
},
|
|
9021
|
+
"ts": {
|
|
9022
|
+
"type": "string",
|
|
9023
|
+
"format": "date-time",
|
|
9024
|
+
"description": "When the handler emitted this line. Newest-first ordering\non this column drives pagination; clock is the runtime's,\nnot the gateway's.\n"
|
|
9025
|
+
},
|
|
9026
|
+
"metadata": {
|
|
9027
|
+
"type": ["object", "null"],
|
|
9028
|
+
"additionalProperties": true,
|
|
9029
|
+
"description": "Optional structured payload the runtime attaches alongside\nthe message (e.g. extra args passed to `console.log`).\nShape is opaque; treat keys as untyped.\n"
|
|
9030
|
+
}
|
|
9031
|
+
},
|
|
9032
|
+
"required": [
|
|
9033
|
+
"id",
|
|
9034
|
+
"function_id",
|
|
9035
|
+
"level",
|
|
9036
|
+
"message",
|
|
9037
|
+
"ts"
|
|
9038
|
+
]
|
|
9039
|
+
}
|
|
9040
|
+
},
|
|
9041
|
+
"replies": {
|
|
9042
|
+
"type": "array",
|
|
9043
|
+
"items": {
|
|
9044
|
+
"type": "object",
|
|
9045
|
+
"properties": {
|
|
9046
|
+
"id": {
|
|
9047
|
+
"type": "string",
|
|
9048
|
+
"format": "uuid"
|
|
9049
|
+
},
|
|
9050
|
+
"status": {
|
|
9051
|
+
"type": "string",
|
|
9052
|
+
"description": "Lifecycle status of a sent_emails row. Possible values:\n\n - `queued`: pre-call INSERT; the outbound agent has not\n yet replied.\n - `submitted_to_agent`: agent accepted; `queue_id` is set.\n - `agent_failed`: agent rejected; `error_code` and\n `error_message` carry the reason.\n - `gate_denied`: a recipient-scope gate denied the send;\n the agent was never called. The `gates` array carries\n the denial detail. /send-mail returns 403 in this case\n so callers see the denial synchronously; /sent-emails\n additionally records the row for historical lookup,\n which is when this status appears in a listing.\n - `unknown`: terminal indeterminate; the on-box log\n poller couldn't classify the receiver's response.\n - `delivered` / `bounced` / `deferred` / `wait_timeout`:\n terminal delivery outcomes (see DeliveryStatus).\n",
|
|
9053
|
+
"enum": [
|
|
9054
|
+
"queued",
|
|
9055
|
+
"submitted_to_agent",
|
|
9056
|
+
"agent_failed",
|
|
9057
|
+
"gate_denied",
|
|
9058
|
+
"unknown",
|
|
9059
|
+
"delivered",
|
|
9060
|
+
"bounced",
|
|
9061
|
+
"deferred",
|
|
9062
|
+
"wait_timeout"
|
|
9063
|
+
]
|
|
9064
|
+
},
|
|
9065
|
+
"to": { "type": "string" },
|
|
9066
|
+
"subject": { "type": "string" },
|
|
9067
|
+
"queue_id": { "type": ["string", "null"] },
|
|
9068
|
+
"created_at": {
|
|
9069
|
+
"type": "string",
|
|
9070
|
+
"format": "date-time"
|
|
9071
|
+
}
|
|
9072
|
+
},
|
|
9073
|
+
"required": [
|
|
9074
|
+
"id",
|
|
9075
|
+
"status",
|
|
9076
|
+
"to",
|
|
9077
|
+
"subject",
|
|
9078
|
+
"queue_id",
|
|
9079
|
+
"created_at"
|
|
9080
|
+
]
|
|
9081
|
+
}
|
|
8272
9082
|
}
|
|
8273
9083
|
},
|
|
8274
9084
|
"required": [
|
|
8275
|
-
"
|
|
8276
|
-
"
|
|
8277
|
-
"
|
|
8278
|
-
"
|
|
8279
|
-
"
|
|
8280
|
-
"
|
|
8281
|
-
"
|
|
9085
|
+
"state",
|
|
9086
|
+
"test_run",
|
|
9087
|
+
"test_send",
|
|
9088
|
+
"inbound_email",
|
|
9089
|
+
"deliveries",
|
|
9090
|
+
"outbound_requests",
|
|
9091
|
+
"logs",
|
|
9092
|
+
"replies"
|
|
8282
9093
|
]
|
|
8283
9094
|
},
|
|
8284
|
-
"sdkName": "
|
|
8285
|
-
"summary": "Get a function",
|
|
9095
|
+
"sdkName": "getFunctionTestRunTrace",
|
|
9096
|
+
"summary": "Get a function test run trace",
|
|
8286
9097
|
"tag": "Functions",
|
|
8287
9098
|
"tagCommand": "functions"
|
|
8288
9099
|
},
|
|
@@ -8602,8 +9413,13 @@ const operationManifest = [
|
|
|
8602
9413
|
},
|
|
8603
9414
|
"responseSchema": {
|
|
8604
9415
|
"type": "object",
|
|
8605
|
-
"description": "Metadata returned by POST /functions/{id}/test. The send is\nqueued;
|
|
9416
|
+
"description": "Metadata returned by POST /functions/{id}/test. The send is\nqueued; poll `trace_url` to watch the run progress through\nsend -> inbound -> webhook deliveries -> outbound requests,\nlogs, and replies.\n",
|
|
8606
9417
|
"properties": {
|
|
9418
|
+
"test_run_id": {
|
|
9419
|
+
"type": "string",
|
|
9420
|
+
"format": "uuid",
|
|
9421
|
+
"description": "Durable test run id used to fetch the run trace."
|
|
9422
|
+
},
|
|
8607
9423
|
"inbound_domain": {
|
|
8608
9424
|
"type": "string",
|
|
8609
9425
|
"description": "Verified inbound domain the test email was sent to."
|
|
@@ -8633,16 +9449,22 @@ const operationManifest = [
|
|
|
8633
9449
|
"type": "string",
|
|
8634
9450
|
"format": "uri",
|
|
8635
9451
|
"description": "Function detail page where invocations show up live."
|
|
9452
|
+
},
|
|
9453
|
+
"trace_url": {
|
|
9454
|
+
"type": "string",
|
|
9455
|
+
"description": "Relative API URL for GET /functions/{id}/test-runs/{test_run_id}/trace."
|
|
8636
9456
|
}
|
|
8637
9457
|
},
|
|
8638
9458
|
"required": [
|
|
9459
|
+
"test_run_id",
|
|
8639
9460
|
"inbound_domain",
|
|
8640
9461
|
"to",
|
|
8641
9462
|
"from",
|
|
8642
9463
|
"send_id",
|
|
8643
9464
|
"subject",
|
|
8644
9465
|
"poll_since",
|
|
8645
|
-
"watch_url"
|
|
9466
|
+
"watch_url",
|
|
9467
|
+
"trace_url"
|
|
8646
9468
|
]
|
|
8647
9469
|
},
|
|
8648
9470
|
"sdkName": "testFunction",
|
|
@@ -11382,41 +12204,319 @@ function emitRawSendMailFetchWarning(bundleText, write) {
|
|
|
11382
12204
|
//#endregion
|
|
11383
12205
|
//#region src/oclif/secret-flags.ts
|
|
11384
12206
|
const SECRET_KEY_RE = /^[A-Z_][A-Z0-9_]*$/;
|
|
11385
|
-
function
|
|
12207
|
+
function resolveSecretFlags(input) {
|
|
11386
12208
|
const secrets = [];
|
|
11387
12209
|
const seenKeys = /* @__PURE__ */ new Set();
|
|
11388
|
-
|
|
11389
|
-
|
|
11390
|
-
|
|
12210
|
+
const env = input.env ?? process.env;
|
|
12211
|
+
const readFile = input.readFile ?? defaultReadFile;
|
|
12212
|
+
const readStdin = input.readStdin ?? defaultReadStdin;
|
|
12213
|
+
const envFileCache = /* @__PURE__ */ new Map();
|
|
12214
|
+
const reserveSecretKey = (key, sourceLabel) => {
|
|
12215
|
+
const keyError = validateKey(key, sourceLabel);
|
|
12216
|
+
if (keyError) return keyError;
|
|
12217
|
+
if (seenKeys.has(key)) return duplicateKeyError(key);
|
|
12218
|
+
seenKeys.add(key);
|
|
12219
|
+
return null;
|
|
12220
|
+
};
|
|
12221
|
+
const addSecret = (key, value, sourceLabel) => {
|
|
12222
|
+
const keyError = reserveSecretKey(key, sourceLabel);
|
|
12223
|
+
if (keyError) return keyError;
|
|
12224
|
+
secrets.push({
|
|
12225
|
+
key,
|
|
12226
|
+
value
|
|
12227
|
+
});
|
|
12228
|
+
return null;
|
|
12229
|
+
};
|
|
12230
|
+
for (const entry of input.inline ?? []) {
|
|
12231
|
+
const parsed = parseKeyValueFlag(entry, "--secret");
|
|
12232
|
+
if (parsed.kind === "error") return parsed;
|
|
12233
|
+
const error = addSecret(parsed.key, parsed.value, "--secret");
|
|
12234
|
+
if (error) return error;
|
|
12235
|
+
}
|
|
12236
|
+
for (const key of input.fromEnv ?? []) {
|
|
12237
|
+
const keyError = reserveSecretKey(key, "--secret-from-env");
|
|
12238
|
+
if (keyError) return keyError;
|
|
12239
|
+
const value = env[key];
|
|
12240
|
+
if (value === void 0) return {
|
|
11391
12241
|
kind: "error",
|
|
11392
|
-
message: `--secret
|
|
12242
|
+
message: `--secret-from-env ${key} could not read ${key}: environment variable is not set.`
|
|
11393
12243
|
};
|
|
11394
|
-
|
|
11395
|
-
|
|
11396
|
-
|
|
12244
|
+
secrets.push({
|
|
12245
|
+
key,
|
|
12246
|
+
value
|
|
12247
|
+
});
|
|
12248
|
+
}
|
|
12249
|
+
for (const entry of input.fromFile ?? []) {
|
|
12250
|
+
const parsed = parseKeyValueFlag(entry, "--secret-from-file");
|
|
12251
|
+
if (parsed.kind === "error") return parsed;
|
|
12252
|
+
const keyError = reserveSecretKey(parsed.key, "--secret-from-file");
|
|
12253
|
+
if (keyError) return keyError;
|
|
12254
|
+
const file = readSecretFile(parsed.value, "--secret-from-file", readFile);
|
|
12255
|
+
if (file.kind === "error") return file;
|
|
12256
|
+
secrets.push({
|
|
12257
|
+
key: parsed.key,
|
|
12258
|
+
value: file.value
|
|
12259
|
+
});
|
|
12260
|
+
}
|
|
12261
|
+
for (const entry of input.fromEnvFile ?? []) {
|
|
12262
|
+
const parsed = parseEnvFileKeyRef(entry, "--secret-from-env-file");
|
|
12263
|
+
if (parsed.kind === "error") return parsed;
|
|
12264
|
+
const keyError = reserveSecretKey(parsed.key, "--secret-from-env-file");
|
|
12265
|
+
if (keyError) return keyError;
|
|
12266
|
+
const file = readEnvFile(parsed.path, readFile, envFileCache);
|
|
12267
|
+
if (file.kind === "error") return file;
|
|
12268
|
+
const value = file.values.get(parsed.key);
|
|
12269
|
+
if (value === void 0) return {
|
|
11397
12270
|
kind: "error",
|
|
11398
|
-
message: `--secret
|
|
12271
|
+
message: `--secret-from-env-file ${entry} could not read ${parsed.key}: key is not present in ${parsed.path}.`
|
|
11399
12272
|
};
|
|
11400
|
-
|
|
12273
|
+
secrets.push({
|
|
12274
|
+
key: parsed.key,
|
|
12275
|
+
value
|
|
12276
|
+
});
|
|
12277
|
+
}
|
|
12278
|
+
if (input.fromStdin !== void 0) {
|
|
12279
|
+
const keyError = reserveSecretKey(input.fromStdin, "--secret-from-stdin");
|
|
12280
|
+
if (keyError) return keyError;
|
|
12281
|
+
const stdin = readSecretStdin("--secret-from-stdin", readStdin);
|
|
12282
|
+
if (stdin.kind === "error") return stdin;
|
|
12283
|
+
secrets.push({
|
|
12284
|
+
key: input.fromStdin,
|
|
12285
|
+
value: stdin.value
|
|
12286
|
+
});
|
|
12287
|
+
}
|
|
12288
|
+
return {
|
|
12289
|
+
kind: "ok",
|
|
12290
|
+
secrets
|
|
12291
|
+
};
|
|
12292
|
+
}
|
|
12293
|
+
function resolveSingleSecretValue(input) {
|
|
12294
|
+
if ([
|
|
12295
|
+
input.value !== void 0 ? "--value" : null,
|
|
12296
|
+
input.valueFromEnv !== void 0 ? "--value-from-env" : null,
|
|
12297
|
+
input.valueFile !== void 0 ? "--value-file" : null,
|
|
12298
|
+
input.valueFromEnvFile !== void 0 ? "--value-from-env-file" : null,
|
|
12299
|
+
input.stdin === true ? "--stdin" : null
|
|
12300
|
+
].filter((v) => v !== null).length !== 1) return {
|
|
12301
|
+
kind: "error",
|
|
12302
|
+
message: "Pass exactly one of --value, --value-from-env, --value-file, --value-from-env-file, or --stdin."
|
|
12303
|
+
};
|
|
12304
|
+
const env = input.env ?? process.env;
|
|
12305
|
+
const readFile = input.readFile ?? defaultReadFile;
|
|
12306
|
+
const readStdin = input.readStdin ?? defaultReadStdin;
|
|
12307
|
+
if (input.value !== void 0) return {
|
|
12308
|
+
kind: "ok",
|
|
12309
|
+
value: input.value
|
|
12310
|
+
};
|
|
12311
|
+
if (input.valueFromEnv !== void 0) {
|
|
12312
|
+
const value = env[input.valueFromEnv];
|
|
12313
|
+
if (value === void 0) return {
|
|
11401
12314
|
kind: "error",
|
|
11402
|
-
message: `--
|
|
12315
|
+
message: `--value-from-env ${input.valueFromEnv} could not read ${input.valueFromEnv}: environment variable is not set.`
|
|
12316
|
+
};
|
|
12317
|
+
return {
|
|
12318
|
+
kind: "ok",
|
|
12319
|
+
value
|
|
11403
12320
|
};
|
|
11404
|
-
|
|
12321
|
+
}
|
|
12322
|
+
if (input.valueFile !== void 0) return readSecretFile(input.valueFile, "--value-file", readFile);
|
|
12323
|
+
if (input.valueFromEnvFile !== void 0) {
|
|
12324
|
+
const parsed = parseSingleValueEnvFileRef(input.valueFromEnvFile, input.key, "--value-from-env-file");
|
|
12325
|
+
if (parsed.kind === "error") return parsed;
|
|
12326
|
+
const file = readEnvFile(parsed.path, readFile, /* @__PURE__ */ new Map());
|
|
12327
|
+
if (file.kind === "error") return file;
|
|
12328
|
+
const value = file.values.get(parsed.key);
|
|
12329
|
+
if (value === void 0) return {
|
|
11405
12330
|
kind: "error",
|
|
11406
|
-
message: `--
|
|
12331
|
+
message: `--value-from-env-file ${input.valueFromEnvFile} could not read ${parsed.key}: key is not present in ${parsed.path}.`
|
|
11407
12332
|
};
|
|
11408
|
-
|
|
11409
|
-
|
|
11410
|
-
key,
|
|
12333
|
+
return {
|
|
12334
|
+
kind: "ok",
|
|
11411
12335
|
value
|
|
11412
|
-
}
|
|
12336
|
+
};
|
|
11413
12337
|
}
|
|
12338
|
+
if (input.stdin === true) return readSecretStdin("--stdin", readStdin);
|
|
12339
|
+
return {
|
|
12340
|
+
kind: "error",
|
|
12341
|
+
message: "Pass exactly one of --value, --value-from-env, --value-file, --value-from-env-file, or --stdin."
|
|
12342
|
+
};
|
|
12343
|
+
}
|
|
12344
|
+
function defaultReadFile(path) {
|
|
12345
|
+
return readFileSync(path, "utf8");
|
|
12346
|
+
}
|
|
12347
|
+
function defaultReadStdin() {
|
|
12348
|
+
if (process.stdin.isTTY) throw new Error("stdin is a TTY; pipe a value into this command or pass a file/env source instead.");
|
|
12349
|
+
return readFileSync(0, "utf8");
|
|
12350
|
+
}
|
|
12351
|
+
function parseKeyValueFlag(entry, flagLabel) {
|
|
12352
|
+
const eq = entry.indexOf("=");
|
|
12353
|
+
if (eq === -1) return {
|
|
12354
|
+
kind: "error",
|
|
12355
|
+
message: `${flagLabel} expects KEY=VALUE (got ${JSON.stringify(entry)}). Example: ${flagLabel} API_TOKEN=abc123`
|
|
12356
|
+
};
|
|
12357
|
+
const key = entry.slice(0, eq);
|
|
12358
|
+
const value = entry.slice(eq + 1);
|
|
12359
|
+
if (key.length === 0) return {
|
|
12360
|
+
kind: "error",
|
|
12361
|
+
message: `${flagLabel} is missing a KEY before '=' (got ${JSON.stringify(entry)}). Example: ${flagLabel} API_TOKEN=abc123`
|
|
12362
|
+
};
|
|
11414
12363
|
return {
|
|
11415
12364
|
kind: "ok",
|
|
11416
|
-
|
|
12365
|
+
key,
|
|
12366
|
+
value
|
|
12367
|
+
};
|
|
12368
|
+
}
|
|
12369
|
+
function validateKey(key, flagLabel) {
|
|
12370
|
+
if (!SECRET_KEY_RE.test(key)) return {
|
|
12371
|
+
kind: "error",
|
|
12372
|
+
message: `${flagLabel} KEY ${JSON.stringify(key)} does not match ${SECRET_KEY_RE.source} (uppercase letters, digits, underscores; first character is a letter or underscore).`
|
|
12373
|
+
};
|
|
12374
|
+
return null;
|
|
12375
|
+
}
|
|
12376
|
+
function duplicateKeyError(key) {
|
|
12377
|
+
return {
|
|
12378
|
+
kind: "error",
|
|
12379
|
+
message: `Secret KEY ${JSON.stringify(key)} was passed more than once. Each key may only appear once per command.`
|
|
12380
|
+
};
|
|
12381
|
+
}
|
|
12382
|
+
function readSecretFile(path, flagLabel, readFile) {
|
|
12383
|
+
try {
|
|
12384
|
+
return {
|
|
12385
|
+
kind: "ok",
|
|
12386
|
+
value: readFile(path)
|
|
12387
|
+
};
|
|
12388
|
+
} catch (error) {
|
|
12389
|
+
return {
|
|
12390
|
+
kind: "error",
|
|
12391
|
+
message: `Could not read ${flagLabel} ${path}: ${error instanceof Error ? error.message : String(error)}`
|
|
12392
|
+
};
|
|
12393
|
+
}
|
|
12394
|
+
}
|
|
12395
|
+
function readSecretStdin(flagLabel, readStdin) {
|
|
12396
|
+
try {
|
|
12397
|
+
return {
|
|
12398
|
+
kind: "ok",
|
|
12399
|
+
value: stripOneTrailingLineEnding(readStdin())
|
|
12400
|
+
};
|
|
12401
|
+
} catch (error) {
|
|
12402
|
+
return {
|
|
12403
|
+
kind: "error",
|
|
12404
|
+
message: `Could not read ${flagLabel}: ${error instanceof Error ? error.message : String(error)}`
|
|
12405
|
+
};
|
|
12406
|
+
}
|
|
12407
|
+
}
|
|
12408
|
+
function stripOneTrailingLineEnding(value) {
|
|
12409
|
+
if (!value.endsWith("\n")) return value;
|
|
12410
|
+
const withoutLf = value.slice(0, -1);
|
|
12411
|
+
return withoutLf.endsWith("\r") ? withoutLf.slice(0, -1) : withoutLf;
|
|
12412
|
+
}
|
|
12413
|
+
function parseEnvFileKeyRef(entry, flagLabel) {
|
|
12414
|
+
const sep = entry.lastIndexOf(":");
|
|
12415
|
+
if (sep <= 0 || sep === entry.length - 1) return {
|
|
12416
|
+
kind: "error",
|
|
12417
|
+
message: `${flagLabel} expects FILE:KEY (got ${JSON.stringify(entry)}). Example: ${flagLabel} .env.local:OPENAI_KEY`
|
|
12418
|
+
};
|
|
12419
|
+
const path = entry.slice(0, sep);
|
|
12420
|
+
const key = entry.slice(sep + 1);
|
|
12421
|
+
const keyError = validateKey(key, flagLabel);
|
|
12422
|
+
if (keyError) return keyError;
|
|
12423
|
+
return {
|
|
12424
|
+
kind: "ok",
|
|
12425
|
+
key,
|
|
12426
|
+
path
|
|
12427
|
+
};
|
|
12428
|
+
}
|
|
12429
|
+
function parseSingleValueEnvFileRef(entry, fallbackKey, flagLabel) {
|
|
12430
|
+
const sep = entry.lastIndexOf(":");
|
|
12431
|
+
if (sep === -1) return {
|
|
12432
|
+
kind: "ok",
|
|
12433
|
+
key: fallbackKey,
|
|
12434
|
+
path: entry
|
|
12435
|
+
};
|
|
12436
|
+
if (sep <= 0 || sep === entry.length - 1) return {
|
|
12437
|
+
kind: "error",
|
|
12438
|
+
message: `${flagLabel} expects FILE or FILE:KEY (got ${JSON.stringify(entry)}). Example: ${flagLabel} .env.local or ${flagLabel} .env.local:OPENAI_KEY`
|
|
12439
|
+
};
|
|
12440
|
+
const path = entry.slice(0, sep);
|
|
12441
|
+
const key = entry.slice(sep + 1);
|
|
12442
|
+
const keyError = validateKey(key, flagLabel);
|
|
12443
|
+
if (keyError) return keyError;
|
|
12444
|
+
return {
|
|
12445
|
+
kind: "ok",
|
|
12446
|
+
key,
|
|
12447
|
+
path
|
|
12448
|
+
};
|
|
12449
|
+
}
|
|
12450
|
+
function readEnvFile(path, readFile, cache) {
|
|
12451
|
+
const cached = cache.get(path);
|
|
12452
|
+
if (cached) return {
|
|
12453
|
+
kind: "ok",
|
|
12454
|
+
values: cached
|
|
11417
12455
|
};
|
|
12456
|
+
let contents;
|
|
12457
|
+
try {
|
|
12458
|
+
contents = readFile(path);
|
|
12459
|
+
} catch (error) {
|
|
12460
|
+
return {
|
|
12461
|
+
kind: "error",
|
|
12462
|
+
message: `Could not read env file ${path}: ${error instanceof Error ? error.message : String(error)}`
|
|
12463
|
+
};
|
|
12464
|
+
}
|
|
12465
|
+
const values = parseEnvFile(contents);
|
|
12466
|
+
cache.set(path, values);
|
|
12467
|
+
return {
|
|
12468
|
+
kind: "ok",
|
|
12469
|
+
values
|
|
12470
|
+
};
|
|
12471
|
+
}
|
|
12472
|
+
function parseEnvFile(contents) {
|
|
12473
|
+
const values = /* @__PURE__ */ new Map();
|
|
12474
|
+
const normalized = contents.replace(/^\uFEFF/, "");
|
|
12475
|
+
for (const rawLine of normalized.split(/\r?\n/)) {
|
|
12476
|
+
let line = rawLine.trimStart();
|
|
12477
|
+
if (line.length === 0 || line.startsWith("#")) continue;
|
|
12478
|
+
if (line.startsWith("export ")) line = line.slice(7).trimStart();
|
|
12479
|
+
const match = /^([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/.exec(line);
|
|
12480
|
+
if (!match) continue;
|
|
12481
|
+
values.set(match[1], parseEnvValue(match[2] ?? ""));
|
|
12482
|
+
}
|
|
12483
|
+
return values;
|
|
12484
|
+
}
|
|
12485
|
+
function parseEnvValue(raw) {
|
|
12486
|
+
const value = raw.trimStart();
|
|
12487
|
+
if (value.startsWith("'")) {
|
|
12488
|
+
const end = value.indexOf("'", 1);
|
|
12489
|
+
return end === -1 ? value.slice(1) : value.slice(1, end);
|
|
12490
|
+
}
|
|
12491
|
+
if (value.startsWith("\"")) return parseDoubleQuotedEnvValue(value);
|
|
12492
|
+
return value.replace(/\s+#.*$/, "").trimEnd();
|
|
12493
|
+
}
|
|
12494
|
+
function parseDoubleQuotedEnvValue(value) {
|
|
12495
|
+
let out = "";
|
|
12496
|
+
let escaped = false;
|
|
12497
|
+
for (let i = 1; i < value.length; i++) {
|
|
12498
|
+
const ch = value[i];
|
|
12499
|
+
if (escaped) {
|
|
12500
|
+
if (ch === "n") out += "\n";
|
|
12501
|
+
else if (ch === "r") out += "\r";
|
|
12502
|
+
else if (ch === "t") out += " ";
|
|
12503
|
+
else out += ch;
|
|
12504
|
+
escaped = false;
|
|
12505
|
+
continue;
|
|
12506
|
+
}
|
|
12507
|
+
if (ch === "\\") {
|
|
12508
|
+
escaped = true;
|
|
12509
|
+
continue;
|
|
12510
|
+
}
|
|
12511
|
+
if (ch === "\"") break;
|
|
12512
|
+
out += ch;
|
|
12513
|
+
}
|
|
12514
|
+
if (escaped) out += "\\";
|
|
12515
|
+
return out;
|
|
11418
12516
|
}
|
|
11419
|
-
const SECRET_FLAG_SECURITY_NOTE = "Note: values passed on the command line are visible in shell history (e.g. ~/.bash_history) and to other users via `ps aux` / /proc/[pid]/cmdline. For sensitive values prefer
|
|
12517
|
+
const SECRET_FLAG_SECURITY_NOTE = "Note: values passed on the command line are visible in shell history (e.g. ~/.bash_history) and to other users via `ps aux` / /proc/[pid]/cmdline. For sensitive values prefer --secret-from-env, --secret-from-file, --secret-from-env-file, or --secret-from-stdin.";
|
|
12518
|
+
const SECRET_SOURCE_FLAGS_DESCRIPTION = "Safe sources: --secret-from-env KEY reads process.env[KEY], --secret-from-file KEY=PATH reads the full UTF-8 file contents, --secret-from-env-file FILE:KEY reads KEY from a dotenv-style file, and --secret-from-stdin KEY reads the value from stdin.";
|
|
12519
|
+
const SINGLE_SECRET_VALUE_SOURCE_DESCRIPTION = "Instead of --value, use --value-from-env ENV_VAR, --value-file PATH, --value-from-env-file FILE[:KEY], or --stdin to avoid putting the secret value in shell history or process argv. If KEY is omitted from --value-from-env-file, the command's --key is used.";
|
|
11420
12520
|
//#endregion
|
|
11421
12521
|
//#region src/oclif/commands/functions-deploy.ts
|
|
11422
12522
|
async function runDeployWithSecrets(api, params) {
|
|
@@ -11518,22 +12618,23 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
|
|
|
11518
12618
|
\`functions:create-function\` if you need the full flag surface
|
|
11519
12619
|
(raw-body JSON, etc.).
|
|
11520
12620
|
|
|
11521
|
-
Pass
|
|
11522
|
-
|
|
11523
|
-
|
|
11524
|
-
|
|
11525
|
-
|
|
11526
|
-
|
|
11527
|
-
|
|
11528
|
-
|
|
11529
|
-
|
|
11530
|
-
|
|
11531
|
-
push them live.`;
|
|
12621
|
+
Pass secret source flags to seed bindings in the same command. Keys
|
|
12622
|
+
must match \`^[A-Z_][A-Z0-9_]*$\` (uppercase letters, digits,
|
|
12623
|
+
underscores; first character is a letter or underscore). With one
|
|
12624
|
+
or more secrets the deploy fans out to multiple API calls:
|
|
12625
|
+
create-function, set-secret per pair, then a final update-function
|
|
12626
|
+
with the same bundle so the running handler picks up the bindings.
|
|
12627
|
+
If a secret write fails after the create step the function exists
|
|
12628
|
+
with whatever secrets succeeded and the redeploy has NOT fired;
|
|
12629
|
+
re-run \`primitive functions set-secret\` for the missing keys, then
|
|
12630
|
+
\`primitive functions redeploy\` to push them live. ${SECRET_SOURCE_FLAGS_DESCRIPTION}`;
|
|
11532
12631
|
static summary = "Deploy a new function from a bundled handler file";
|
|
11533
12632
|
static examples = [
|
|
11534
12633
|
"<%= config.bin %> functions deploy --name forwarder --file ./bundle.js",
|
|
11535
12634
|
"<%= config.bin %> functions deploy --name forwarder --file ./bundle.js --source-map-file ./bundle.js.map",
|
|
11536
|
-
"<%= config.bin %> functions deploy --name forwarder --file ./bundle.js --secret OPENAI_KEY=sk-... --secret OWNER_EMAIL=me@example.com"
|
|
12635
|
+
"<%= config.bin %> functions deploy --name forwarder --file ./bundle.js --secret OPENAI_KEY=sk-... --secret OWNER_EMAIL=me@example.com",
|
|
12636
|
+
"<%= config.bin %> functions deploy --name forwarder --file ./bundle.js --secret-from-env OPENAI_KEY --secret-from-env-file .env.local:OWNER_EMAIL",
|
|
12637
|
+
"printf '%s' \"$OPENAI_KEY\" | <%= config.bin %> functions deploy --name forwarder --file ./bundle.js --secret-from-stdin OPENAI_KEY"
|
|
11537
12638
|
];
|
|
11538
12639
|
static flags = {
|
|
11539
12640
|
"api-key": Flags.string({
|
|
@@ -11563,12 +12664,31 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
|
|
|
11563
12664
|
description: `Secret KEY=VALUE to seed on the deployed function. Repeatable. KEY must match \`^[A-Z_][A-Z0-9_]*$\`; VALUE may contain \`=\` (only the first \`=\` is treated as a delimiter). Each KEY may only appear once per command. Passing one or more --secret flags fans out the deploy to create-function, set-secret per pair, then a final redeploy so the running handler picks up the bindings. ${SECRET_FLAG_SECURITY_NOTE}`,
|
|
11564
12665
|
multiple: true
|
|
11565
12666
|
}),
|
|
12667
|
+
"secret-from-env": Flags.string({
|
|
12668
|
+
description: "Secret KEY to read from the environment and seed on the deployed function. Repeatable. Example: --secret-from-env OPENAI_KEY reads process.env.OPENAI_KEY.",
|
|
12669
|
+
multiple: true
|
|
12670
|
+
}),
|
|
12671
|
+
"secret-from-file": Flags.string({
|
|
12672
|
+
description: "Secret KEY=PATH to read from a UTF-8 file and seed on the deployed function. Repeatable. The full file contents become the value.",
|
|
12673
|
+
multiple: true
|
|
12674
|
+
}),
|
|
12675
|
+
"secret-from-env-file": Flags.string({
|
|
12676
|
+
description: "Secret FILE:KEY to read from a dotenv-style file and seed on the deployed function. Repeatable. Example: --secret-from-env-file .env.local:OPENAI_KEY.",
|
|
12677
|
+
multiple: true
|
|
12678
|
+
}),
|
|
12679
|
+
"secret-from-stdin": Flags.string({ description: "Secret KEY to read from stdin and seed on the deployed function. A single trailing line ending is stripped. Stdin is consumed once, so this flag is not repeatable." }),
|
|
11566
12680
|
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
11567
12681
|
};
|
|
11568
12682
|
async run() {
|
|
11569
12683
|
const { flags } = await this.parse(FunctionsDeployCommand);
|
|
11570
12684
|
await runWithTiming(flags.time, async () => {
|
|
11571
|
-
const parsedSecrets =
|
|
12685
|
+
const parsedSecrets = resolveSecretFlags({
|
|
12686
|
+
fromEnv: flags["secret-from-env"] ?? [],
|
|
12687
|
+
fromEnvFile: flags["secret-from-env-file"] ?? [],
|
|
12688
|
+
fromFile: flags["secret-from-file"] ?? [],
|
|
12689
|
+
fromStdin: flags["secret-from-stdin"],
|
|
12690
|
+
inline: flags.secret ?? []
|
|
12691
|
+
});
|
|
11572
12692
|
if (parsedSecrets.kind === "error") {
|
|
11573
12693
|
process.stderr.write(`${parsedSecrets.message}\n`);
|
|
11574
12694
|
process.exitCode = 1;
|
|
@@ -11652,14 +12772,16 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
|
|
|
11652
12772
|
}
|
|
11653
12773
|
};
|
|
11654
12774
|
//#endregion
|
|
11655
|
-
//#region src/oclif/
|
|
11656
|
-
const
|
|
11657
|
-
const
|
|
12775
|
+
//#region src/oclif/function-templates.ts
|
|
12776
|
+
const DEFAULT_FUNCTION_TEMPLATE_ID = "email-reply";
|
|
12777
|
+
const PRIMITIVE_TEAM_AUTHOR = {
|
|
12778
|
+
id: "primitive-team",
|
|
12779
|
+
name: "Primitive Team",
|
|
12780
|
+
url: "https://primitive.dev"
|
|
12781
|
+
};
|
|
12782
|
+
const SDK_VERSION_RANGE = "^0.29.0";
|
|
12783
|
+
const CLI_VERSION_RANGE = "^0.29.0";
|
|
11658
12784
|
const ESBUILD_VERSION_RANGE = "^0.27.0";
|
|
11659
|
-
const VALID_NAME = /^[a-z0-9][a-z0-9_-]{0,62}$/;
|
|
11660
|
-
function isValidFunctionName(name) {
|
|
11661
|
-
return VALID_NAME.test(name);
|
|
11662
|
-
}
|
|
11663
12785
|
function renderHandler() {
|
|
11664
12786
|
return `// env.PRIMITIVE_API_KEY is auto-injected by the Primitive Functions runtime.
|
|
11665
12787
|
//
|
|
@@ -11880,7 +13002,7 @@ Run \`primitive login\` once to save a key in your CLI config if you
|
|
|
11880
13002
|
prefer that to an env var.
|
|
11881
13003
|
`;
|
|
11882
13004
|
}
|
|
11883
|
-
function
|
|
13005
|
+
function renderEmailReplyTemplateFiles(name) {
|
|
11884
13006
|
return [
|
|
11885
13007
|
{
|
|
11886
13008
|
contents: renderHandler(),
|
|
@@ -11908,9 +13030,79 @@ function scaffoldFiles(name) {
|
|
|
11908
13030
|
}
|
|
11909
13031
|
];
|
|
11910
13032
|
}
|
|
13033
|
+
const FUNCTION_TEMPLATES = [{
|
|
13034
|
+
author: PRIMITIVE_TEAM_AUTHOR,
|
|
13035
|
+
dependencies: ["@primitivedotdev/sdk"],
|
|
13036
|
+
description: "A deployable TypeScript email handler that validates email.received events, skips likely loops, and replies with the Primitive SDK.",
|
|
13037
|
+
devDependencies: [
|
|
13038
|
+
"@primitivedotdev/cli",
|
|
13039
|
+
"esbuild",
|
|
13040
|
+
"typescript"
|
|
13041
|
+
],
|
|
13042
|
+
files: ({ name }) => renderEmailReplyTemplateFiles(name),
|
|
13043
|
+
id: DEFAULT_FUNCTION_TEMPLATE_ID,
|
|
13044
|
+
secrets: [],
|
|
13045
|
+
summary: "Reply to inbound email with the Primitive SDK.",
|
|
13046
|
+
tags: [
|
|
13047
|
+
"email",
|
|
13048
|
+
"reply",
|
|
13049
|
+
"typescript",
|
|
13050
|
+
"worker"
|
|
13051
|
+
],
|
|
13052
|
+
title: "Email Reply"
|
|
13053
|
+
}];
|
|
13054
|
+
function functionTemplateIds(templates) {
|
|
13055
|
+
return templates.map((template) => template.id);
|
|
13056
|
+
}
|
|
13057
|
+
function findFunctionTemplate(templates, id) {
|
|
13058
|
+
return templates.find((template) => template.id === id) ?? null;
|
|
13059
|
+
}
|
|
13060
|
+
function serializeFunctionTemplate(template) {
|
|
13061
|
+
return {
|
|
13062
|
+
author: { ...template.author },
|
|
13063
|
+
dependencies: [...template.dependencies],
|
|
13064
|
+
description: template.description,
|
|
13065
|
+
devDependencies: [...template.devDependencies],
|
|
13066
|
+
id: template.id,
|
|
13067
|
+
secrets: [...template.secrets],
|
|
13068
|
+
summary: template.summary,
|
|
13069
|
+
tags: [...template.tags],
|
|
13070
|
+
title: template.title
|
|
13071
|
+
};
|
|
13072
|
+
}
|
|
13073
|
+
function formatFunctionTemplateList(templates) {
|
|
13074
|
+
const lines = ["Available Primitive Function templates:", ""];
|
|
13075
|
+
for (const template of templates) {
|
|
13076
|
+
lines.push(`${template.id}`);
|
|
13077
|
+
lines.push(` title: ${template.title}`);
|
|
13078
|
+
lines.push(` author: ${template.author.name}`);
|
|
13079
|
+
lines.push(` summary: ${template.summary}`);
|
|
13080
|
+
lines.push(` tags: ${template.tags.length > 0 ? template.tags.join(", ") : "none"}`);
|
|
13081
|
+
lines.push(` secrets: ${template.secrets.length > 0 ? template.secrets.join(", ") : "none"}`);
|
|
13082
|
+
lines.push("");
|
|
13083
|
+
}
|
|
13084
|
+
lines.push("Use `primitive functions init <name> --template <id>`.");
|
|
13085
|
+
return lines.join("\n");
|
|
13086
|
+
}
|
|
13087
|
+
//#endregion
|
|
13088
|
+
//#region src/oclif/commands/functions-init.ts
|
|
13089
|
+
const VALID_NAME = /^[a-z0-9][a-z0-9_-]{0,62}$/;
|
|
13090
|
+
function isValidFunctionName(name) {
|
|
13091
|
+
return VALID_NAME.test(name);
|
|
13092
|
+
}
|
|
13093
|
+
function unknownTemplateError(templateId) {
|
|
13094
|
+
const available = functionTemplateIds(FUNCTION_TEMPLATES).join(", ");
|
|
13095
|
+
return new Errors.CLIError(`Unknown function template "${templateId}". Available templates: ${available}. Run \`primitive functions templates\` for details.`, { exit: 1 });
|
|
13096
|
+
}
|
|
13097
|
+
function scaffoldFiles(name, templateId = DEFAULT_FUNCTION_TEMPLATE_ID) {
|
|
13098
|
+
const template = findFunctionTemplate(FUNCTION_TEMPLATES, templateId);
|
|
13099
|
+
if (!template) throw unknownTemplateError(templateId);
|
|
13100
|
+
return template.files({ name });
|
|
13101
|
+
}
|
|
11911
13102
|
function writeScaffold(params) {
|
|
11912
13103
|
if (!isValidFunctionName(params.name)) throw new Errors.CLIError(`Invalid function name "${params.name}". Use lowercase letters, digits, hyphens, or underscores (1-63 chars, must start with a letter or digit).`, { exit: 1 });
|
|
11913
|
-
const
|
|
13104
|
+
const templateId = params.templateId ?? "email-reply";
|
|
13105
|
+
const files = scaffoldFiles(params.name, templateId);
|
|
11914
13106
|
const written = [];
|
|
11915
13107
|
try {
|
|
11916
13108
|
mkdirSync(params.outDir, { recursive: false });
|
|
@@ -11941,7 +13133,7 @@ function writeScaffold(params) {
|
|
|
11941
13133
|
return { written };
|
|
11942
13134
|
}
|
|
11943
13135
|
var FunctionsInitCommand = class FunctionsInitCommand extends Command {
|
|
11944
|
-
static description = `Scaffold a new Primitive Function project
|
|
13136
|
+
static description = `Scaffold a new Primitive Function project from a Primitive-owned template.
|
|
11945
13137
|
|
|
11946
13138
|
The scaffolded handler imports \`createPrimitiveClient\` from
|
|
11947
13139
|
\`@primitivedotdev/sdk/api\` and demonstrates the canonical pattern:
|
|
@@ -11950,22 +13142,35 @@ var FunctionsInitCommand = class FunctionsInitCommand extends Command {
|
|
|
11950
13142
|
./dist/handler.js, ready to hand to \`primitive functions deploy --file\`.
|
|
11951
13143
|
|
|
11952
13144
|
Refuses to overwrite an existing directory. Use --out-dir to pick a
|
|
11953
|
-
different target path than ./<name
|
|
13145
|
+
different target path than ./<name>/. Run \`primitive functions templates\`
|
|
13146
|
+
to inspect available templates.`;
|
|
11954
13147
|
static summary = "Scaffold a new Primitive Function project ready for functions deploy";
|
|
11955
|
-
static examples = [
|
|
13148
|
+
static examples = [
|
|
13149
|
+
"<%= config.bin %> functions init my-fn",
|
|
13150
|
+
"<%= config.bin %> functions init my-fn --template email-reply",
|
|
13151
|
+
"<%= config.bin %> functions init my-fn --out-dir ./functions/my-fn"
|
|
13152
|
+
];
|
|
11956
13153
|
static args = { name: Args.string({
|
|
11957
13154
|
description: "Function name. Lowercase letters, digits, hyphens, underscores. 1-63 chars. Used as the directory name (when --out-dir is not set) and as the package.json name.",
|
|
11958
13155
|
required: true
|
|
11959
13156
|
}) };
|
|
11960
|
-
static flags = {
|
|
13157
|
+
static flags = {
|
|
13158
|
+
"out-dir": Flags.string({ description: "Directory to scaffold into. Defaults to ./<name>/. Must not already exist." }),
|
|
13159
|
+
template: Flags.string({
|
|
13160
|
+
default: DEFAULT_FUNCTION_TEMPLATE_ID,
|
|
13161
|
+
description: "Function template id. Run `primitive functions templates` to list templates.",
|
|
13162
|
+
options: functionTemplateIds(FUNCTION_TEMPLATES)
|
|
13163
|
+
})
|
|
13164
|
+
};
|
|
11961
13165
|
async run() {
|
|
11962
13166
|
const { args, flags } = await this.parse(FunctionsInitCommand);
|
|
11963
13167
|
const outDir = resolve(flags["out-dir"] ?? `./${args.name}`);
|
|
11964
13168
|
writeScaffold({
|
|
11965
13169
|
name: args.name,
|
|
11966
|
-
outDir
|
|
13170
|
+
outDir,
|
|
13171
|
+
templateId: flags.template
|
|
11967
13172
|
});
|
|
11968
|
-
this.log(`Scaffolded ${outDir}.`);
|
|
13173
|
+
this.log(`Scaffolded ${outDir} from ${flags.template} template.`);
|
|
11969
13174
|
this.log("Next:");
|
|
11970
13175
|
this.log(` cd ${outDir}`);
|
|
11971
13176
|
this.log(" npm install");
|
|
@@ -12046,17 +13251,19 @@ var FunctionsRedeployCommand = class FunctionsRedeployCommand extends Command {
|
|
|
12046
13251
|
the bindings table fresh on every call, so passing the existing
|
|
12047
13252
|
bundle picks up any secret writes since the last deploy.
|
|
12048
13253
|
|
|
12049
|
-
Pass
|
|
12050
|
-
|
|
12051
|
-
|
|
12052
|
-
|
|
12053
|
-
|
|
12054
|
-
|
|
13254
|
+
Pass secret source flags to write secrets BEFORE the redeploy fires;
|
|
13255
|
+
one update-function call then refreshes every new binding. Keys must
|
|
13256
|
+
match \`^[A-Z_][A-Z0-9_]*$\` (uppercase letters, digits, underscores;
|
|
13257
|
+
first character is a letter or underscore). With one or more secrets
|
|
13258
|
+
the redeploy fans out to multiple API calls (set-secret per pair,
|
|
13259
|
+
then update-function). ${SECRET_SOURCE_FLAGS_DESCRIPTION}`;
|
|
12055
13260
|
static summary = "Redeploy a function from a bundled handler file";
|
|
12056
13261
|
static examples = [
|
|
12057
13262
|
"<%= config.bin %> functions redeploy --id <fn-id> --file ./bundle.js",
|
|
12058
13263
|
"<%= config.bin %> functions redeploy --id <fn-id> --file ./bundle.js --source-map-file ./bundle.js.map",
|
|
12059
|
-
"<%= config.bin %> functions redeploy --id <fn-id> --file ./bundle.js --secret OPENAI_KEY=sk-... --secret OWNER_EMAIL=me@example.com"
|
|
13264
|
+
"<%= config.bin %> functions redeploy --id <fn-id> --file ./bundle.js --secret OPENAI_KEY=sk-... --secret OWNER_EMAIL=me@example.com",
|
|
13265
|
+
"<%= config.bin %> functions redeploy --id <fn-id> --file ./bundle.js --secret-from-env OPENAI_KEY --secret-from-file PRIVATE_KEY=./private-key.pem",
|
|
13266
|
+
"printf '%s' \"$OPENAI_KEY\" | <%= config.bin %> functions redeploy --id <fn-id> --file ./bundle.js --secret-from-stdin OPENAI_KEY"
|
|
12060
13267
|
];
|
|
12061
13268
|
static flags = {
|
|
12062
13269
|
"api-key": Flags.string({
|
|
@@ -12086,12 +13293,31 @@ var FunctionsRedeployCommand = class FunctionsRedeployCommand extends Command {
|
|
|
12086
13293
|
description: `Secret KEY=VALUE to write on the function before the redeploy fires. Repeatable. KEY must match \`^[A-Z_][A-Z0-9_]*$\`; VALUE may contain \`=\` (only the first \`=\` is treated as a delimiter). Each KEY may only appear once per command. Passing one or more --secret flags fans out to set-secret per pair then a single update-function call so the new bindings land in the same redeploy. ${SECRET_FLAG_SECURITY_NOTE}`,
|
|
12087
13294
|
multiple: true
|
|
12088
13295
|
}),
|
|
13296
|
+
"secret-from-env": Flags.string({
|
|
13297
|
+
description: "Secret KEY to read from the environment and write before the redeploy. Repeatable. Example: --secret-from-env OPENAI_KEY reads process.env.OPENAI_KEY.",
|
|
13298
|
+
multiple: true
|
|
13299
|
+
}),
|
|
13300
|
+
"secret-from-file": Flags.string({
|
|
13301
|
+
description: "Secret KEY=PATH to read from a UTF-8 file and write before the redeploy. Repeatable. The full file contents become the value.",
|
|
13302
|
+
multiple: true
|
|
13303
|
+
}),
|
|
13304
|
+
"secret-from-env-file": Flags.string({
|
|
13305
|
+
description: "Secret FILE:KEY to read from a dotenv-style file and write before the redeploy. Repeatable. Example: --secret-from-env-file .env.local:OPENAI_KEY.",
|
|
13306
|
+
multiple: true
|
|
13307
|
+
}),
|
|
13308
|
+
"secret-from-stdin": Flags.string({ description: "Secret KEY to read from stdin and write before the redeploy. A single trailing line ending is stripped. Stdin is consumed once, so this flag is not repeatable." }),
|
|
12089
13309
|
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
12090
13310
|
};
|
|
12091
13311
|
async run() {
|
|
12092
13312
|
const { flags } = await this.parse(FunctionsRedeployCommand);
|
|
12093
13313
|
await runWithTiming(flags.time, async () => {
|
|
12094
|
-
const parsedSecrets =
|
|
13314
|
+
const parsedSecrets = resolveSecretFlags({
|
|
13315
|
+
fromEnv: flags["secret-from-env"] ?? [],
|
|
13316
|
+
fromEnvFile: flags["secret-from-env-file"] ?? [],
|
|
13317
|
+
fromFile: flags["secret-from-file"] ?? [],
|
|
13318
|
+
fromStdin: flags["secret-from-stdin"],
|
|
13319
|
+
inline: flags.secret ?? []
|
|
13320
|
+
});
|
|
12095
13321
|
if (parsedSecrets.kind === "error") {
|
|
12096
13322
|
process.stderr.write(`${parsedSecrets.message}\n`);
|
|
12097
13323
|
process.exitCode = 1;
|
|
@@ -12242,9 +13468,15 @@ var FunctionsSetSecretCommand = class FunctionsSetSecretCommand extends Command
|
|
|
12242
13468
|
|
|
12243
13469
|
Keys must match \`^[A-Z_][A-Z0-9_]*$\` (uppercase letters, digits,
|
|
12244
13470
|
underscores; first character is a letter or underscore). System-
|
|
12245
|
-
managed keys are reserved and rejected
|
|
13471
|
+
managed keys are reserved and rejected. ${SINGLE_SECRET_VALUE_SOURCE_DESCRIPTION}`;
|
|
12246
13472
|
static summary = "Write a function secret (optionally redeploying to push it live)";
|
|
12247
|
-
static examples = [
|
|
13473
|
+
static examples = [
|
|
13474
|
+
"<%= config.bin %> functions set-secret --id <fn-id> --key API_TOKEN --value abc123",
|
|
13475
|
+
"<%= config.bin %> functions set-secret --id <fn-id> --key API_TOKEN --value abc123 --redeploy",
|
|
13476
|
+
"<%= config.bin %> functions set-secret --id <fn-id> --key OPENAI_KEY --value-from-env OPENAI_KEY --redeploy",
|
|
13477
|
+
"<%= config.bin %> functions set-secret --id <fn-id> --key OPENAI_KEY --value-from-env-file .env.local --redeploy",
|
|
13478
|
+
"printf '%s' \"$OPENAI_KEY\" | <%= config.bin %> functions set-secret --id <fn-id> --key OPENAI_KEY --stdin --redeploy"
|
|
13479
|
+
];
|
|
12248
13480
|
static flags = {
|
|
12249
13481
|
"api-key": Flags.string({
|
|
12250
13482
|
description: "Primitive API key (defaults to PRIMITIVE_API_KEY or saved `primitive login` credentials)",
|
|
@@ -12268,10 +13500,11 @@ var FunctionsSetSecretCommand = class FunctionsSetSecretCommand extends Command
|
|
|
12268
13500
|
description: "Secret key. Uppercase letters, digits, underscores; must start with a letter or underscore. System-managed keys are reserved.",
|
|
12269
13501
|
required: true
|
|
12270
13502
|
}),
|
|
12271
|
-
value: Flags.string({
|
|
12272
|
-
|
|
12273
|
-
|
|
12274
|
-
}),
|
|
13503
|
+
value: Flags.string({ description: "Secret value (up to 4096 UTF-8 bytes). Encrypted at rest. Visible in shell history and process argv; prefer a non-argv source for sensitive values." }),
|
|
13504
|
+
"value-from-env": Flags.string({ description: "Environment variable to read as the secret value. Example: --value-from-env OPENAI_KEY reads process.env.OPENAI_KEY." }),
|
|
13505
|
+
"value-file": Flags.string({ description: "UTF-8 file to read as the secret value. The full file contents become the value." }),
|
|
13506
|
+
"value-from-env-file": Flags.string({ description: "Dotenv-style file to read as the secret value. Use FILE to read --key from that file, or FILE:KEY to read a different key." }),
|
|
13507
|
+
stdin: Flags.boolean({ description: "Read the secret value from stdin. A single trailing line ending is stripped. Example: printf '%s' \"$OPENAI_KEY\" | primitive functions set-secret --id <fn-id> --key OPENAI_KEY --stdin" }),
|
|
12275
13508
|
redeploy: Flags.boolean({ description: "Also redeploy the function with its current code so the new value lands in the running handler. Without this, the secret is written but not visible to the handler until the next deploy. Note: when --redeploy re-uploads the function's current live code without a sourceMap field, the API preserves the current stored source map. Use `functions redeploy --file <bundle.js> --source-map-file <bundle.js.map>` to replace or restore a map." }),
|
|
12276
13509
|
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
12277
13510
|
};
|
|
@@ -12295,6 +13528,19 @@ var FunctionsSetSecretCommand = class FunctionsSetSecretCommand extends Command
|
|
|
12295
13528
|
baseUrlOverridden,
|
|
12296
13529
|
configDir: this.config.configDir
|
|
12297
13530
|
};
|
|
13531
|
+
const resolvedValue = resolveSingleSecretValue({
|
|
13532
|
+
key: flags.key,
|
|
13533
|
+
value: flags.value,
|
|
13534
|
+
valueFile: flags["value-file"],
|
|
13535
|
+
valueFromEnv: flags["value-from-env"],
|
|
13536
|
+
valueFromEnvFile: flags["value-from-env-file"],
|
|
13537
|
+
stdin: flags.stdin
|
|
13538
|
+
});
|
|
13539
|
+
if (resolvedValue.kind === "error") {
|
|
13540
|
+
process.stderr.write(`${resolvedValue.message}\n`);
|
|
13541
|
+
process.exitCode = 1;
|
|
13542
|
+
return;
|
|
13543
|
+
}
|
|
12298
13544
|
const outcome = await runSetSecret({
|
|
12299
13545
|
getFunction: (p) => getFunction({
|
|
12300
13546
|
client: apiClient.client,
|
|
@@ -12320,7 +13566,7 @@ var FunctionsSetSecretCommand = class FunctionsSetSecretCommand extends Command
|
|
|
12320
13566
|
id: flags.id,
|
|
12321
13567
|
key: flags.key,
|
|
12322
13568
|
redeploy: flags.redeploy === true,
|
|
12323
|
-
value:
|
|
13569
|
+
value: resolvedValue.value
|
|
12324
13570
|
});
|
|
12325
13571
|
if (outcome.kind === "error") {
|
|
12326
13572
|
if (outcome.stage === "get-function") process.stderr.write("Secret was written, but reading current function code for redeploy failed; the secret is NOT yet live. Re-run with --redeploy, or call `primitive functions redeploy --id <id> --file <bundle>` once you have the bundle.\n");
|
|
@@ -12338,9 +13584,111 @@ var FunctionsSetSecretCommand = class FunctionsSetSecretCommand extends Command
|
|
|
12338
13584
|
}
|
|
12339
13585
|
};
|
|
12340
13586
|
//#endregion
|
|
13587
|
+
//#region src/oclif/commands/functions-templates.ts
|
|
13588
|
+
var FunctionsTemplatesCommand = class FunctionsTemplatesCommand extends Command {
|
|
13589
|
+
static enableJsonFlag = true;
|
|
13590
|
+
static description = `List Primitive Function templates available to \`primitive functions init\`.
|
|
13591
|
+
|
|
13592
|
+
The default table is optimized for quick terminal discovery. Use
|
|
13593
|
+
--json when an agent or script needs stable metadata for searching,
|
|
13594
|
+
ranking, or choosing a template programmatically.`;
|
|
13595
|
+
static summary = "List available Primitive Function templates";
|
|
13596
|
+
static examples = [
|
|
13597
|
+
"<%= config.bin %> functions templates",
|
|
13598
|
+
"<%= config.bin %> functions templates --json",
|
|
13599
|
+
"<%= config.bin %> functions init my-fn --template email-reply"
|
|
13600
|
+
];
|
|
13601
|
+
static flags = {};
|
|
13602
|
+
async run() {
|
|
13603
|
+
const { flags } = await this.parse(FunctionsTemplatesCommand);
|
|
13604
|
+
if (flags.json) return FUNCTION_TEMPLATES.map(serializeFunctionTemplate);
|
|
13605
|
+
this.log(formatFunctionTemplateList(FUNCTION_TEMPLATES));
|
|
13606
|
+
}
|
|
13607
|
+
};
|
|
13608
|
+
//#endregion
|
|
12341
13609
|
//#region src/oclif/commands/functions-test-function.ts
|
|
12342
13610
|
const DEFAULT_WAIT_TIMEOUT_SECONDS = 60;
|
|
12343
13611
|
const TERMINAL_WEBHOOK_STATUSES = new Set(["fired", "exhausted"]);
|
|
13612
|
+
function buildFunctionTestOutcome(params) {
|
|
13613
|
+
const outcome = {
|
|
13614
|
+
elapsed_seconds: params.elapsedSeconds,
|
|
13615
|
+
function_id: params.functionId,
|
|
13616
|
+
inbound_domain: params.invocation.inbound_domain,
|
|
13617
|
+
inbound_id: params.inboundId,
|
|
13618
|
+
inbound_to: params.invocation.to,
|
|
13619
|
+
poll_since: params.invocation.poll_since,
|
|
13620
|
+
test_run_id: params.invocation.test_run_id,
|
|
13621
|
+
test_send_id: params.invocation.send_id,
|
|
13622
|
+
test_subject: params.invocation.subject,
|
|
13623
|
+
trace_url: params.invocation.trace_url,
|
|
13624
|
+
watch_url: params.invocation.watch_url,
|
|
13625
|
+
webhook_attempt_count: params.detail.webhook_attempt_count,
|
|
13626
|
+
webhook_last_error: params.detail.webhook_last_error,
|
|
13627
|
+
webhook_last_status_code: params.detail.webhook_last_status_code,
|
|
13628
|
+
webhook_status: params.detail.webhook_status
|
|
13629
|
+
};
|
|
13630
|
+
if (params.showSends) outcome.sent_emails = params.detail.replies;
|
|
13631
|
+
return outcome;
|
|
13632
|
+
}
|
|
13633
|
+
function stringOrNull(value) {
|
|
13634
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
13635
|
+
}
|
|
13636
|
+
function findMatchingFunctionEndpoints(params) {
|
|
13637
|
+
const matches = [];
|
|
13638
|
+
for (const endpoint of params.endpoints) {
|
|
13639
|
+
if (endpoint.kind !== "function") continue;
|
|
13640
|
+
if (endpoint.enabled === false) continue;
|
|
13641
|
+
if (endpoint.deactivated_at !== null && endpoint.deactivated_at !== void 0) continue;
|
|
13642
|
+
const id = stringOrNull(endpoint.id);
|
|
13643
|
+
if (!id) continue;
|
|
13644
|
+
const domainId = stringOrNull(endpoint.domain_id);
|
|
13645
|
+
if (domainId !== null && (params.inboundDomainId === null || domainId !== params.inboundDomainId)) continue;
|
|
13646
|
+
const functionId = stringOrNull(endpoint.function_id);
|
|
13647
|
+
matches.push({
|
|
13648
|
+
function_id: functionId,
|
|
13649
|
+
id,
|
|
13650
|
+
is_current_function: functionId === params.currentFunctionId,
|
|
13651
|
+
scope: domainId === null ? "catch-all" : "domain"
|
|
13652
|
+
});
|
|
13653
|
+
}
|
|
13654
|
+
return matches;
|
|
13655
|
+
}
|
|
13656
|
+
function formatFunctionEndpointNoiseWarning(params) {
|
|
13657
|
+
if (params.endpoints.filter((endpoint) => !endpoint.is_current_function).length === 0) return null;
|
|
13658
|
+
const lines = [`Warning: ${params.endpoints.length} function endpoints may receive mail for ${params.toAddress}:`];
|
|
13659
|
+
for (const endpoint of params.endpoints) {
|
|
13660
|
+
const scope = endpoint.scope === "catch-all" ? "catch-all" : `scoped to ${params.inboundDomain}`;
|
|
13661
|
+
const current = endpoint.is_current_function ? " (this function)" : "";
|
|
13662
|
+
const target = endpoint.function_id ? ` -> function ${endpoint.function_id}` : "";
|
|
13663
|
+
lines.push(`- endpoint ${endpoint.id}${target}, ${scope}${current}`);
|
|
13664
|
+
}
|
|
13665
|
+
return lines.join("\n");
|
|
13666
|
+
}
|
|
13667
|
+
async function maybeWriteEndpointNoiseWarning(params) {
|
|
13668
|
+
try {
|
|
13669
|
+
const [domainsResult, endpointsResult] = await Promise.all([listDomains({
|
|
13670
|
+
client: params.apiClient.client,
|
|
13671
|
+
responseStyle: "fields"
|
|
13672
|
+
}), listEndpoints({
|
|
13673
|
+
client: params.apiClient.client,
|
|
13674
|
+
responseStyle: "fields"
|
|
13675
|
+
})]);
|
|
13676
|
+
if (endpointsResult.error) return;
|
|
13677
|
+
if (domainsResult.error) return;
|
|
13678
|
+
const inboundDomainId = domainsResult.data?.data?.find((domain) => domain.domain?.toLowerCase() === params.invocation.inbound_domain.toLowerCase())?.id ?? null;
|
|
13679
|
+
const endpointsEnvelope = endpointsResult.data;
|
|
13680
|
+
const warning = formatFunctionEndpointNoiseWarning({
|
|
13681
|
+
endpoints: findMatchingFunctionEndpoints({
|
|
13682
|
+
currentFunctionId: params.currentFunctionId,
|
|
13683
|
+
endpoints: endpointsEnvelope?.data ?? [],
|
|
13684
|
+
inboundDomainId
|
|
13685
|
+
}),
|
|
13686
|
+
inboundDomain: params.invocation.inbound_domain,
|
|
13687
|
+
toAddress: params.invocation.to
|
|
13688
|
+
});
|
|
13689
|
+
if (warning) params.writeStderr(`${warning}\n`);
|
|
13690
|
+
} catch {}
|
|
13691
|
+
}
|
|
12344
13692
|
var FunctionsTestFunctionCommand = class FunctionsTestFunctionCommand extends Command {
|
|
12345
13693
|
static description = "Send a real test email through MX to trigger this function. With --wait, blocks until the function has processed the inbound; with --show-sends, also prints any outbound sends the function emitted in response.";
|
|
12346
13694
|
static summary = "Trigger a test invocation; with --wait, watch it land";
|
|
@@ -12424,6 +13772,14 @@ var FunctionsTestFunctionCommand = class FunctionsTestFunctionCommand extends Co
|
|
|
12424
13772
|
this.log(JSON.stringify(invocation, null, 2));
|
|
12425
13773
|
return;
|
|
12426
13774
|
}
|
|
13775
|
+
await maybeWriteEndpointNoiseWarning({
|
|
13776
|
+
apiClient,
|
|
13777
|
+
currentFunctionId: flags.id,
|
|
13778
|
+
invocation,
|
|
13779
|
+
writeStderr: (chunk) => {
|
|
13780
|
+
process.stderr.write(chunk);
|
|
13781
|
+
}
|
|
13782
|
+
});
|
|
12427
13783
|
const startedAt = Date.now();
|
|
12428
13784
|
const timeoutMs = flags.timeout * 1e3;
|
|
12429
13785
|
const pollIntervalMs = flags["poll-interval"] * 1e3;
|
|
@@ -12486,17 +13842,14 @@ var FunctionsTestFunctionCommand = class FunctionsTestFunctionCommand extends Co
|
|
|
12486
13842
|
}
|
|
12487
13843
|
if (!detail) this.error(`Timed out after ${flags.timeout}s waiting for function webhook to fire for inbound ${inboundId}. Browse ${invocation.watch_url} for the live view.`, { exit: 2 });
|
|
12488
13844
|
const elapsedSeconds = Math.round((Date.now() - startedAt) / 1e3);
|
|
12489
|
-
const outcome = {
|
|
12490
|
-
|
|
12491
|
-
|
|
12492
|
-
|
|
12493
|
-
|
|
12494
|
-
|
|
12495
|
-
|
|
12496
|
-
|
|
12497
|
-
elapsed_seconds: elapsedSeconds
|
|
12498
|
-
};
|
|
12499
|
-
if (shouldShowSends) outcome.sent_emails = detail.replies;
|
|
13845
|
+
const outcome = buildFunctionTestOutcome({
|
|
13846
|
+
detail,
|
|
13847
|
+
elapsedSeconds,
|
|
13848
|
+
functionId: flags.id,
|
|
13849
|
+
inboundId,
|
|
13850
|
+
invocation,
|
|
13851
|
+
showSends: shouldShowSends
|
|
13852
|
+
});
|
|
12500
13853
|
this.log(JSON.stringify(outcome, null, 2));
|
|
12501
13854
|
if (detail.webhook_status === "exhausted") process.exitCode = 1;
|
|
12502
13855
|
});
|
|
@@ -13648,6 +15001,7 @@ const COMMANDS = {
|
|
|
13648
15001
|
"emails:watch": EmailsWatchCommand,
|
|
13649
15002
|
"emails:wait": EmailsWaitCommand,
|
|
13650
15003
|
"functions:init": FunctionsInitCommand,
|
|
15004
|
+
"functions:templates": FunctionsTemplatesCommand,
|
|
13651
15005
|
"functions:deploy": FunctionsDeployCommand,
|
|
13652
15006
|
"functions:redeploy": FunctionsRedeployCommand,
|
|
13653
15007
|
"functions:set-secret": FunctionsSetSecretCommand,
|