@primitivedotdev/cli 0.28.0 → 0.30.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 +2127 -226
- 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,127 +5680,203 @@ 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"
|
|
5638
5699
|
]
|
|
5639
5700
|
},
|
|
5640
|
-
"
|
|
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": {
|
|
5641
5714
|
"type": "object",
|
|
5642
|
-
"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",
|
|
5643
5715
|
"properties": {
|
|
5644
5716
|
"id": {
|
|
5645
5717
|
"type": "string",
|
|
5646
|
-
"format": "uuid"
|
|
5647
|
-
"description": "Unique log row id (stable across pages)."
|
|
5718
|
+
"format": "uuid"
|
|
5648
5719
|
},
|
|
5649
5720
|
"function_id": {
|
|
5650
5721
|
"type": "string",
|
|
5651
|
-
"format": "uuid"
|
|
5652
|
-
"description": "The function this log row belongs to."
|
|
5722
|
+
"format": "uuid"
|
|
5653
5723
|
},
|
|
5654
|
-
"
|
|
5724
|
+
"inbound_domain": { "type": "string" },
|
|
5725
|
+
"to": { "type": "string" },
|
|
5726
|
+
"from": { "type": "string" },
|
|
5727
|
+
"subject": { "type": "string" },
|
|
5728
|
+
"poll_since": {
|
|
5655
5729
|
"type": "string",
|
|
5656
|
-
"
|
|
5657
|
-
"debug",
|
|
5658
|
-
"log",
|
|
5659
|
-
"info",
|
|
5660
|
-
"warn",
|
|
5661
|
-
"error"
|
|
5662
|
-
],
|
|
5663
|
-
"description": "Severity. `log` is the runtime's default for unannotated\n`console.log` calls; the other levels match standard\n`console.*` methods.\n"
|
|
5730
|
+
"format": "date-time"
|
|
5664
5731
|
},
|
|
5665
|
-
"
|
|
5732
|
+
"created_at": {
|
|
5666
5733
|
"type": "string",
|
|
5667
|
-
"
|
|
5734
|
+
"format": "date-time"
|
|
5668
5735
|
},
|
|
5669
|
-
"
|
|
5670
|
-
"type": "string",
|
|
5671
|
-
"format": "date-time"
|
|
5672
|
-
"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"
|
|
5736
|
+
"sent_at": {
|
|
5737
|
+
"type": ["string", "null"],
|
|
5738
|
+
"format": "date-time"
|
|
5673
5739
|
},
|
|
5674
|
-
"
|
|
5675
|
-
"type": ["object", "null"],
|
|
5676
|
-
"additionalProperties": true,
|
|
5677
|
-
"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"
|
|
5678
|
-
}
|
|
5740
|
+
"send_error": { "type": ["string", "null"] }
|
|
5679
5741
|
},
|
|
5680
5742
|
"required": [
|
|
5681
5743
|
"id",
|
|
5682
5744
|
"function_id",
|
|
5683
|
-
"
|
|
5684
|
-
"
|
|
5685
|
-
"
|
|
5745
|
+
"inbound_domain",
|
|
5746
|
+
"to",
|
|
5747
|
+
"from",
|
|
5748
|
+
"subject",
|
|
5749
|
+
"poll_since",
|
|
5750
|
+
"created_at",
|
|
5751
|
+
"sent_at",
|
|
5752
|
+
"send_error"
|
|
5686
5753
|
]
|
|
5687
5754
|
},
|
|
5688
|
-
"
|
|
5689
|
-
"type": "object",
|
|
5690
|
-
"description": "One row from GET /functions/{id}/secrets. Discriminate on the\n`managed` field:\n * `managed = true` — system secret provisioned by Primitive.\n `description` is set; `created_at` / `updated_at` are\n null because the row is virtual (resolved at deploy time\n from the managed registry, not stored in the secrets\n table).\n * `managed = false` — secret the user set via the API.\n `created_at` / `updated_at` are set; `description` is\n null.\n",
|
|
5755
|
+
"FunctionTestRunSend": {
|
|
5756
|
+
"type": ["object", "null"],
|
|
5691
5757
|
"properties": {
|
|
5692
|
-
"
|
|
5693
|
-
|
|
5694
|
-
"
|
|
5695
|
-
"description": "True for managed system secrets, false for user-set entries."
|
|
5696
|
-
},
|
|
5697
|
-
"description": {
|
|
5698
|
-
"type": ["string", "null"],
|
|
5699
|
-
"description": "Set on managed entries only; null on user-set entries."
|
|
5758
|
+
"id": {
|
|
5759
|
+
"type": "string",
|
|
5760
|
+
"format": "uuid"
|
|
5700
5761
|
},
|
|
5762
|
+
"status": { "$ref": "#/components/schemas/SentEmailStatus" },
|
|
5763
|
+
"queue_id": { "type": ["string", "null"] },
|
|
5701
5764
|
"created_at": {
|
|
5702
|
-
"type":
|
|
5703
|
-
"format": "date-time"
|
|
5704
|
-
"description": "Set on user-set entries only; null on managed entries."
|
|
5765
|
+
"type": "string",
|
|
5766
|
+
"format": "date-time"
|
|
5705
5767
|
},
|
|
5706
5768
|
"updated_at": {
|
|
5707
|
-
"type":
|
|
5708
|
-
"format": "date-time"
|
|
5709
|
-
"description": "Set on user-set entries only; null on managed entries."
|
|
5769
|
+
"type": "string",
|
|
5770
|
+
"format": "date-time"
|
|
5710
5771
|
}
|
|
5711
5772
|
},
|
|
5712
|
-
"required": [
|
|
5773
|
+
"required": [
|
|
5774
|
+
"id",
|
|
5775
|
+
"status",
|
|
5776
|
+
"queue_id",
|
|
5777
|
+
"created_at",
|
|
5778
|
+
"updated_at"
|
|
5779
|
+
]
|
|
5713
5780
|
},
|
|
5714
|
-
"
|
|
5715
|
-
"type": "object",
|
|
5716
|
-
"additionalProperties": false,
|
|
5717
|
-
"description": "Body for POST /functions/{id}/secrets.",
|
|
5781
|
+
"FunctionTestRunInboundEmail": {
|
|
5782
|
+
"type": ["object", "null"],
|
|
5718
5783
|
"properties": {
|
|
5719
|
-
"
|
|
5784
|
+
"id": {
|
|
5720
5785
|
"type": "string",
|
|
5721
|
-
"
|
|
5722
|
-
"description": "Uppercase letters, digits, and underscores. Must start with\na letter or underscore. System-managed keys (e.g.\nPRIMITIVE_WEBHOOK_SECRET) are reserved.\n"
|
|
5786
|
+
"format": "uuid"
|
|
5723
5787
|
},
|
|
5724
|
-
"
|
|
5788
|
+
"status": { "$ref": "#/components/schemas/EmailStatus" },
|
|
5789
|
+
"received_at": {
|
|
5725
5790
|
"type": "string",
|
|
5726
|
-
"
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
}
|
|
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"] }
|
|
5730
5800
|
},
|
|
5731
|
-
"required": [
|
|
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
|
+
]
|
|
5732
5813
|
},
|
|
5733
|
-
"
|
|
5734
|
-
"type": "object",
|
|
5735
|
-
"
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
"
|
|
5741
|
-
|
|
5742
|
-
|
|
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
|
+
]
|
|
5743
5851
|
},
|
|
5744
|
-
"
|
|
5852
|
+
"FunctionTestRunDelivery": {
|
|
5745
5853
|
"type": "object",
|
|
5746
|
-
"description": "Returned by POST and PUT secret routes.",
|
|
5747
5854
|
"properties": {
|
|
5748
|
-
"
|
|
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"] },
|
|
5749
5880
|
"created_at": {
|
|
5750
5881
|
"type": "string",
|
|
5751
5882
|
"format": "date-time"
|
|
@@ -5754,48 +5885,295 @@ const openapiDocument = {
|
|
|
5754
5885
|
"type": "string",
|
|
5755
5886
|
"format": "date-time"
|
|
5756
5887
|
},
|
|
5757
|
-
"
|
|
5758
|
-
"type": "boolean",
|
|
5759
|
-
"description": "True if this call inserted a new row, false if it updated an existing one."
|
|
5760
|
-
}
|
|
5888
|
+
"endpoint": { "$ref": "#/components/schemas/FunctionTestRunDeliveryEndpoint" }
|
|
5761
5889
|
},
|
|
5762
5890
|
"required": [
|
|
5763
|
-
"
|
|
5891
|
+
"id",
|
|
5892
|
+
"endpoint_id",
|
|
5893
|
+
"endpoint_url",
|
|
5894
|
+
"status",
|
|
5895
|
+
"attempt_count",
|
|
5896
|
+
"duration_ms",
|
|
5897
|
+
"last_error",
|
|
5898
|
+
"last_error_code",
|
|
5764
5899
|
"created_at",
|
|
5765
5900
|
"updated_at",
|
|
5766
|
-
"
|
|
5901
|
+
"endpoint"
|
|
5767
5902
|
]
|
|
5768
|
-
}
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
"type": "
|
|
5798
|
-
"
|
|
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"
|
|
6016
|
+
]
|
|
6017
|
+
},
|
|
6018
|
+
"FunctionLogRow": {
|
|
6019
|
+
"type": "object",
|
|
6020
|
+
"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",
|
|
6021
|
+
"properties": {
|
|
6022
|
+
"id": {
|
|
6023
|
+
"type": "string",
|
|
6024
|
+
"format": "uuid",
|
|
6025
|
+
"description": "Unique log row id (stable across pages)."
|
|
6026
|
+
},
|
|
6027
|
+
"function_id": {
|
|
6028
|
+
"type": "string",
|
|
6029
|
+
"format": "uuid",
|
|
6030
|
+
"description": "The function this log row belongs to."
|
|
6031
|
+
},
|
|
6032
|
+
"level": {
|
|
6033
|
+
"type": "string",
|
|
6034
|
+
"enum": [
|
|
6035
|
+
"debug",
|
|
6036
|
+
"log",
|
|
6037
|
+
"info",
|
|
6038
|
+
"warn",
|
|
6039
|
+
"error"
|
|
6040
|
+
],
|
|
6041
|
+
"description": "Severity. `log` is the runtime's default for unannotated\n`console.log` calls; the other levels match standard\n`console.*` methods.\n"
|
|
6042
|
+
},
|
|
6043
|
+
"message": {
|
|
6044
|
+
"type": "string",
|
|
6045
|
+
"description": "The textual message body. The runtime stringifies non-string\narguments before persisting, so this is always a plain\nstring.\n"
|
|
6046
|
+
},
|
|
6047
|
+
"ts": {
|
|
6048
|
+
"type": "string",
|
|
6049
|
+
"format": "date-time",
|
|
6050
|
+
"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"
|
|
6051
|
+
},
|
|
6052
|
+
"metadata": {
|
|
6053
|
+
"type": ["object", "null"],
|
|
6054
|
+
"additionalProperties": true,
|
|
6055
|
+
"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"
|
|
6056
|
+
}
|
|
6057
|
+
},
|
|
6058
|
+
"required": [
|
|
6059
|
+
"id",
|
|
6060
|
+
"function_id",
|
|
6061
|
+
"level",
|
|
6062
|
+
"message",
|
|
6063
|
+
"ts"
|
|
6064
|
+
]
|
|
6065
|
+
},
|
|
6066
|
+
"FunctionSecretListItem": {
|
|
6067
|
+
"type": "object",
|
|
6068
|
+
"description": "One row from GET /functions/{id}/secrets. Discriminate on the\n`managed` field:\n * `managed = true` — system secret provisioned by Primitive.\n `description` is set; `created_at` / `updated_at` are\n null because the row is virtual (resolved at deploy time\n from the managed registry, not stored in the secrets\n table).\n * `managed = false` — secret the user set via the API.\n `created_at` / `updated_at` are set; `description` is\n null.\n",
|
|
6069
|
+
"properties": {
|
|
6070
|
+
"key": { "type": "string" },
|
|
6071
|
+
"managed": {
|
|
6072
|
+
"type": "boolean",
|
|
6073
|
+
"description": "True for managed system secrets, false for user-set entries."
|
|
6074
|
+
},
|
|
6075
|
+
"description": {
|
|
6076
|
+
"type": ["string", "null"],
|
|
6077
|
+
"description": "Set on managed entries only; null on user-set entries."
|
|
6078
|
+
},
|
|
6079
|
+
"created_at": {
|
|
6080
|
+
"type": ["string", "null"],
|
|
6081
|
+
"format": "date-time",
|
|
6082
|
+
"description": "Set on user-set entries only; null on managed entries."
|
|
6083
|
+
},
|
|
6084
|
+
"updated_at": {
|
|
6085
|
+
"type": ["string", "null"],
|
|
6086
|
+
"format": "date-time",
|
|
6087
|
+
"description": "Set on user-set entries only; null on managed entries."
|
|
6088
|
+
}
|
|
6089
|
+
},
|
|
6090
|
+
"required": ["key", "managed"]
|
|
6091
|
+
},
|
|
6092
|
+
"CreateFunctionSecretInput": {
|
|
6093
|
+
"type": "object",
|
|
6094
|
+
"additionalProperties": false,
|
|
6095
|
+
"description": "Body for POST /functions/{id}/secrets.",
|
|
6096
|
+
"properties": {
|
|
6097
|
+
"key": {
|
|
6098
|
+
"type": "string",
|
|
6099
|
+
"pattern": "^[A-Z_][A-Z0-9_]*$",
|
|
6100
|
+
"description": "Uppercase letters, digits, and underscores. Must start with\na letter or underscore. System-managed keys (e.g.\nPRIMITIVE_WEBHOOK_SECRET) are reserved.\n"
|
|
6101
|
+
},
|
|
6102
|
+
"value": {
|
|
6103
|
+
"type": "string",
|
|
6104
|
+
"minLength": 1,
|
|
6105
|
+
"maxLength": 4096,
|
|
6106
|
+
"description": "Secret value, up to 4096 UTF-8 bytes. Encrypted at rest.\nNever returned by any read endpoint.\n"
|
|
6107
|
+
}
|
|
6108
|
+
},
|
|
6109
|
+
"required": ["key", "value"]
|
|
6110
|
+
},
|
|
6111
|
+
"SetFunctionSecretInput": {
|
|
6112
|
+
"type": "object",
|
|
6113
|
+
"additionalProperties": false,
|
|
6114
|
+
"description": "Body for PUT /functions/{id}/secrets/{key}. Key comes from the path.",
|
|
6115
|
+
"properties": { "value": {
|
|
6116
|
+
"type": "string",
|
|
6117
|
+
"minLength": 1,
|
|
6118
|
+
"maxLength": 4096
|
|
6119
|
+
} },
|
|
6120
|
+
"required": ["value"]
|
|
6121
|
+
},
|
|
6122
|
+
"FunctionSecretWriteResult": {
|
|
6123
|
+
"type": "object",
|
|
6124
|
+
"description": "Returned by POST and PUT secret routes.",
|
|
6125
|
+
"properties": {
|
|
6126
|
+
"key": { "type": "string" },
|
|
6127
|
+
"created_at": {
|
|
6128
|
+
"type": "string",
|
|
6129
|
+
"format": "date-time"
|
|
6130
|
+
},
|
|
6131
|
+
"updated_at": {
|
|
6132
|
+
"type": "string",
|
|
6133
|
+
"format": "date-time"
|
|
6134
|
+
},
|
|
6135
|
+
"created": {
|
|
6136
|
+
"type": "boolean",
|
|
6137
|
+
"description": "True if this call inserted a new row, false if it updated an existing one."
|
|
6138
|
+
}
|
|
6139
|
+
},
|
|
6140
|
+
"required": [
|
|
6141
|
+
"key",
|
|
6142
|
+
"created_at",
|
|
6143
|
+
"updated_at",
|
|
6144
|
+
"created"
|
|
6145
|
+
]
|
|
6146
|
+
}
|
|
6147
|
+
}
|
|
6148
|
+
}
|
|
6149
|
+
};
|
|
6150
|
+
//#endregion
|
|
6151
|
+
//#region ../packages/api-core/src/openapi/operations.generated.ts
|
|
6152
|
+
const operationManifest = [
|
|
6153
|
+
{
|
|
6154
|
+
"binaryResponse": false,
|
|
6155
|
+
"bodyRequired": false,
|
|
6156
|
+
"command": "get-account",
|
|
6157
|
+
"description": null,
|
|
6158
|
+
"hasJsonBody": false,
|
|
6159
|
+
"method": "GET",
|
|
6160
|
+
"operationId": "getAccount",
|
|
6161
|
+
"path": "/account",
|
|
6162
|
+
"pathParams": [],
|
|
6163
|
+
"queryParams": [],
|
|
6164
|
+
"requestSchema": null,
|
|
6165
|
+
"responseSchema": {
|
|
6166
|
+
"type": "object",
|
|
6167
|
+
"properties": {
|
|
6168
|
+
"id": {
|
|
6169
|
+
"type": "string",
|
|
6170
|
+
"format": "uuid"
|
|
6171
|
+
},
|
|
6172
|
+
"email": { "type": "string" },
|
|
6173
|
+
"plan": { "type": "string" },
|
|
6174
|
+
"created_at": {
|
|
6175
|
+
"type": "string",
|
|
6176
|
+
"format": "date-time"
|
|
5799
6177
|
},
|
|
5800
6178
|
"onboarding_completed": { "type": "boolean" },
|
|
5801
6179
|
"onboarding_step": { "type": ["string", "null"] },
|
|
@@ -6997,8 +7375,11 @@ const operationManifest = [
|
|
|
6997
7375
|
"type": "string"
|
|
6998
7376
|
},
|
|
6999
7377
|
{
|
|
7378
|
+
"default": 50,
|
|
7000
7379
|
"description": "Number of results per page",
|
|
7001
7380
|
"enum": null,
|
|
7381
|
+
"maximum": 100,
|
|
7382
|
+
"minimum": 1,
|
|
7002
7383
|
"name": "limit",
|
|
7003
7384
|
"required": false,
|
|
7004
7385
|
"type": "integer"
|
|
@@ -7267,13 +7648,17 @@ const operationManifest = [
|
|
|
7267
7648
|
"type": "string"
|
|
7268
7649
|
},
|
|
7269
7650
|
{
|
|
7651
|
+
"default": 50,
|
|
7270
7652
|
"description": "Number of results per page",
|
|
7271
7653
|
"enum": null,
|
|
7654
|
+
"maximum": 100,
|
|
7655
|
+
"minimum": 1,
|
|
7272
7656
|
"name": "limit",
|
|
7273
7657
|
"required": false,
|
|
7274
7658
|
"type": "integer"
|
|
7275
7659
|
},
|
|
7276
7660
|
{
|
|
7661
|
+
"default": "true",
|
|
7277
7662
|
"description": "Include subject/body highlight snippets when text search is active.",
|
|
7278
7663
|
"enum": ["true", "false"],
|
|
7279
7664
|
"name": "snippet",
|
|
@@ -7281,6 +7666,7 @@ const operationManifest = [
|
|
|
7281
7666
|
"type": "string"
|
|
7282
7667
|
},
|
|
7283
7668
|
{
|
|
7669
|
+
"default": "true",
|
|
7284
7670
|
"description": "Include facet counts for sender, domain, status, and attachment presence.",
|
|
7285
7671
|
"enum": ["true", "false"],
|
|
7286
7672
|
"name": "include_facets",
|
|
@@ -8289,43 +8675,479 @@ const operationManifest = [
|
|
|
8289
8675
|
{
|
|
8290
8676
|
"binaryResponse": false,
|
|
8291
8677
|
"bodyRequired": false,
|
|
8292
|
-
"command": "
|
|
8293
|
-
"description": "Returns the
|
|
8678
|
+
"command": "get-function-test-run-trace",
|
|
8679
|
+
"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",
|
|
8294
8680
|
"hasJsonBody": false,
|
|
8295
8681
|
"method": "GET",
|
|
8296
|
-
"operationId": "
|
|
8297
|
-
"path": "/functions/{id}/
|
|
8682
|
+
"operationId": "getFunctionTestRunTrace",
|
|
8683
|
+
"path": "/functions/{id}/test-runs/{run_id}/trace",
|
|
8298
8684
|
"pathParams": [{
|
|
8299
8685
|
"description": "Resource UUID",
|
|
8300
8686
|
"enum": null,
|
|
8301
8687
|
"name": "id",
|
|
8302
8688
|
"required": true,
|
|
8303
8689
|
"type": "string"
|
|
8304
|
-
}],
|
|
8305
|
-
"queryParams": [{
|
|
8306
|
-
"description": "Maximum number of rows to return. Clamped to 1..200; default\n50.\n",
|
|
8307
|
-
"enum": null,
|
|
8308
|
-
"name": "limit",
|
|
8309
|
-
"required": false,
|
|
8310
|
-
"type": "integer"
|
|
8311
8690
|
}, {
|
|
8312
|
-
"description": "
|
|
8691
|
+
"description": "Function test run id returned by POST /functions/{id}/test.",
|
|
8313
8692
|
"enum": null,
|
|
8314
|
-
"name": "
|
|
8315
|
-
"required":
|
|
8693
|
+
"name": "run_id",
|
|
8694
|
+
"required": true,
|
|
8316
8695
|
"type": "string"
|
|
8317
8696
|
}],
|
|
8697
|
+
"queryParams": [],
|
|
8318
8698
|
"requestSchema": null,
|
|
8319
8699
|
"responseSchema": {
|
|
8320
8700
|
"type": "object",
|
|
8701
|
+
"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",
|
|
8321
8702
|
"properties": {
|
|
8322
|
-
"
|
|
8323
|
-
"type": "
|
|
8324
|
-
"
|
|
8325
|
-
|
|
8326
|
-
"
|
|
8327
|
-
"
|
|
8328
|
-
|
|
8703
|
+
"state": {
|
|
8704
|
+
"type": "string",
|
|
8705
|
+
"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",
|
|
8706
|
+
"enum": [
|
|
8707
|
+
"send_failed",
|
|
8708
|
+
"waiting_for_send",
|
|
8709
|
+
"waiting_for_inbound",
|
|
8710
|
+
"waiting_for_function",
|
|
8711
|
+
"completed",
|
|
8712
|
+
"failed"
|
|
8713
|
+
]
|
|
8714
|
+
},
|
|
8715
|
+
"test_run": {
|
|
8716
|
+
"type": "object",
|
|
8717
|
+
"properties": {
|
|
8718
|
+
"id": {
|
|
8719
|
+
"type": "string",
|
|
8720
|
+
"format": "uuid"
|
|
8721
|
+
},
|
|
8722
|
+
"function_id": {
|
|
8723
|
+
"type": "string",
|
|
8724
|
+
"format": "uuid"
|
|
8725
|
+
},
|
|
8726
|
+
"inbound_domain": { "type": "string" },
|
|
8727
|
+
"to": { "type": "string" },
|
|
8728
|
+
"from": { "type": "string" },
|
|
8729
|
+
"subject": { "type": "string" },
|
|
8730
|
+
"poll_since": {
|
|
8731
|
+
"type": "string",
|
|
8732
|
+
"format": "date-time"
|
|
8733
|
+
},
|
|
8734
|
+
"created_at": {
|
|
8735
|
+
"type": "string",
|
|
8736
|
+
"format": "date-time"
|
|
8737
|
+
},
|
|
8738
|
+
"sent_at": {
|
|
8739
|
+
"type": ["string", "null"],
|
|
8740
|
+
"format": "date-time"
|
|
8741
|
+
},
|
|
8742
|
+
"send_error": { "type": ["string", "null"] }
|
|
8743
|
+
},
|
|
8744
|
+
"required": [
|
|
8745
|
+
"id",
|
|
8746
|
+
"function_id",
|
|
8747
|
+
"inbound_domain",
|
|
8748
|
+
"to",
|
|
8749
|
+
"from",
|
|
8750
|
+
"subject",
|
|
8751
|
+
"poll_since",
|
|
8752
|
+
"created_at",
|
|
8753
|
+
"sent_at",
|
|
8754
|
+
"send_error"
|
|
8755
|
+
]
|
|
8756
|
+
},
|
|
8757
|
+
"test_send": {
|
|
8758
|
+
"type": ["object", "null"],
|
|
8759
|
+
"properties": {
|
|
8760
|
+
"id": {
|
|
8761
|
+
"type": "string",
|
|
8762
|
+
"format": "uuid"
|
|
8763
|
+
},
|
|
8764
|
+
"status": {
|
|
8765
|
+
"type": "string",
|
|
8766
|
+
"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",
|
|
8767
|
+
"enum": [
|
|
8768
|
+
"queued",
|
|
8769
|
+
"submitted_to_agent",
|
|
8770
|
+
"agent_failed",
|
|
8771
|
+
"gate_denied",
|
|
8772
|
+
"unknown",
|
|
8773
|
+
"delivered",
|
|
8774
|
+
"bounced",
|
|
8775
|
+
"deferred",
|
|
8776
|
+
"wait_timeout"
|
|
8777
|
+
]
|
|
8778
|
+
},
|
|
8779
|
+
"queue_id": { "type": ["string", "null"] },
|
|
8780
|
+
"created_at": {
|
|
8781
|
+
"type": "string",
|
|
8782
|
+
"format": "date-time"
|
|
8783
|
+
},
|
|
8784
|
+
"updated_at": {
|
|
8785
|
+
"type": "string",
|
|
8786
|
+
"format": "date-time"
|
|
8787
|
+
}
|
|
8788
|
+
},
|
|
8789
|
+
"required": [
|
|
8790
|
+
"id",
|
|
8791
|
+
"status",
|
|
8792
|
+
"queue_id",
|
|
8793
|
+
"created_at",
|
|
8794
|
+
"updated_at"
|
|
8795
|
+
]
|
|
8796
|
+
},
|
|
8797
|
+
"inbound_email": {
|
|
8798
|
+
"type": ["object", "null"],
|
|
8799
|
+
"properties": {
|
|
8800
|
+
"id": {
|
|
8801
|
+
"type": "string",
|
|
8802
|
+
"format": "uuid"
|
|
8803
|
+
},
|
|
8804
|
+
"status": {
|
|
8805
|
+
"type": "string",
|
|
8806
|
+
"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",
|
|
8807
|
+
"enum": [
|
|
8808
|
+
"pending",
|
|
8809
|
+
"accepted",
|
|
8810
|
+
"completed",
|
|
8811
|
+
"rejected"
|
|
8812
|
+
]
|
|
8813
|
+
},
|
|
8814
|
+
"received_at": {
|
|
8815
|
+
"type": "string",
|
|
8816
|
+
"format": "date-time"
|
|
8817
|
+
},
|
|
8818
|
+
"from": { "type": "string" },
|
|
8819
|
+
"to": { "type": "string" },
|
|
8820
|
+
"subject": { "type": ["string", "null"] },
|
|
8821
|
+
"webhook_status": {
|
|
8822
|
+
"type": ["string", "null"],
|
|
8823
|
+
"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",
|
|
8824
|
+
"enum": [
|
|
8825
|
+
"pending",
|
|
8826
|
+
"in_flight",
|
|
8827
|
+
"fired",
|
|
8828
|
+
"failed",
|
|
8829
|
+
"exhausted",
|
|
8830
|
+
null
|
|
8831
|
+
]
|
|
8832
|
+
},
|
|
8833
|
+
"webhook_attempt_count": { "type": "integer" },
|
|
8834
|
+
"webhook_last_status_code": { "type": ["integer", "null"] },
|
|
8835
|
+
"webhook_last_error": { "type": ["string", "null"] }
|
|
8836
|
+
},
|
|
8837
|
+
"required": [
|
|
8838
|
+
"id",
|
|
8839
|
+
"status",
|
|
8840
|
+
"received_at",
|
|
8841
|
+
"from",
|
|
8842
|
+
"to",
|
|
8843
|
+
"subject",
|
|
8844
|
+
"webhook_status",
|
|
8845
|
+
"webhook_attempt_count",
|
|
8846
|
+
"webhook_last_status_code",
|
|
8847
|
+
"webhook_last_error"
|
|
8848
|
+
]
|
|
8849
|
+
},
|
|
8850
|
+
"deliveries": {
|
|
8851
|
+
"type": "array",
|
|
8852
|
+
"items": {
|
|
8853
|
+
"type": "object",
|
|
8854
|
+
"properties": {
|
|
8855
|
+
"id": {
|
|
8856
|
+
"type": "string",
|
|
8857
|
+
"description": "Webhook delivery id."
|
|
8858
|
+
},
|
|
8859
|
+
"endpoint_id": {
|
|
8860
|
+
"type": "string",
|
|
8861
|
+
"format": "uuid"
|
|
8862
|
+
},
|
|
8863
|
+
"endpoint_url": {
|
|
8864
|
+
"type": "string",
|
|
8865
|
+
"format": "uri"
|
|
8866
|
+
},
|
|
8867
|
+
"status": {
|
|
8868
|
+
"type": "string",
|
|
8869
|
+
"enum": [
|
|
8870
|
+
"pending",
|
|
8871
|
+
"delivered",
|
|
8872
|
+
"header_confirmed",
|
|
8873
|
+
"failed"
|
|
8874
|
+
]
|
|
8875
|
+
},
|
|
8876
|
+
"attempt_count": { "type": "integer" },
|
|
8877
|
+
"duration_ms": { "type": ["integer", "null"] },
|
|
8878
|
+
"last_error": { "type": ["string", "null"] },
|
|
8879
|
+
"last_error_code": { "type": ["string", "null"] },
|
|
8880
|
+
"created_at": {
|
|
8881
|
+
"type": "string",
|
|
8882
|
+
"format": "date-time"
|
|
8883
|
+
},
|
|
8884
|
+
"updated_at": {
|
|
8885
|
+
"type": "string",
|
|
8886
|
+
"format": "date-time"
|
|
8887
|
+
},
|
|
8888
|
+
"endpoint": {
|
|
8889
|
+
"type": ["object", "null"],
|
|
8890
|
+
"properties": {
|
|
8891
|
+
"id": {
|
|
8892
|
+
"type": "string",
|
|
8893
|
+
"format": "uuid"
|
|
8894
|
+
},
|
|
8895
|
+
"kind": {
|
|
8896
|
+
"type": "string",
|
|
8897
|
+
"description": "Endpoint kind. Current traces may include `http` or `function`; future endpoint kinds may appear."
|
|
8898
|
+
},
|
|
8899
|
+
"function_id": {
|
|
8900
|
+
"type": ["string", "null"],
|
|
8901
|
+
"format": "uuid"
|
|
8902
|
+
},
|
|
8903
|
+
"function_name": { "type": ["string", "null"] },
|
|
8904
|
+
"domain_id": {
|
|
8905
|
+
"type": ["string", "null"],
|
|
8906
|
+
"format": "uuid"
|
|
8907
|
+
},
|
|
8908
|
+
"enabled": { "type": "boolean" },
|
|
8909
|
+
"deactivated_at": {
|
|
8910
|
+
"type": ["string", "null"],
|
|
8911
|
+
"format": "date-time"
|
|
8912
|
+
},
|
|
8913
|
+
"is_current_function": { "type": "boolean" }
|
|
8914
|
+
},
|
|
8915
|
+
"required": [
|
|
8916
|
+
"id",
|
|
8917
|
+
"kind",
|
|
8918
|
+
"function_id",
|
|
8919
|
+
"function_name",
|
|
8920
|
+
"domain_id",
|
|
8921
|
+
"enabled",
|
|
8922
|
+
"deactivated_at",
|
|
8923
|
+
"is_current_function"
|
|
8924
|
+
]
|
|
8925
|
+
}
|
|
8926
|
+
},
|
|
8927
|
+
"required": [
|
|
8928
|
+
"id",
|
|
8929
|
+
"endpoint_id",
|
|
8930
|
+
"endpoint_url",
|
|
8931
|
+
"status",
|
|
8932
|
+
"attempt_count",
|
|
8933
|
+
"duration_ms",
|
|
8934
|
+
"last_error",
|
|
8935
|
+
"last_error_code",
|
|
8936
|
+
"created_at",
|
|
8937
|
+
"updated_at",
|
|
8938
|
+
"endpoint"
|
|
8939
|
+
]
|
|
8940
|
+
}
|
|
8941
|
+
},
|
|
8942
|
+
"outbound_requests": {
|
|
8943
|
+
"type": "array",
|
|
8944
|
+
"items": {
|
|
8945
|
+
"type": "object",
|
|
8946
|
+
"properties": {
|
|
8947
|
+
"id": {
|
|
8948
|
+
"type": "string",
|
|
8949
|
+
"format": "uuid"
|
|
8950
|
+
},
|
|
8951
|
+
"function_id": {
|
|
8952
|
+
"type": "string",
|
|
8953
|
+
"format": "uuid"
|
|
8954
|
+
},
|
|
8955
|
+
"webhook_delivery_id": { "type": ["string", "null"] },
|
|
8956
|
+
"email_id": {
|
|
8957
|
+
"type": ["string", "null"],
|
|
8958
|
+
"format": "uuid"
|
|
8959
|
+
},
|
|
8960
|
+
"endpoint_id": {
|
|
8961
|
+
"type": ["string", "null"],
|
|
8962
|
+
"format": "uuid"
|
|
8963
|
+
},
|
|
8964
|
+
"method": { "type": "string" },
|
|
8965
|
+
"url": {
|
|
8966
|
+
"type": "string",
|
|
8967
|
+
"format": "uri"
|
|
8968
|
+
},
|
|
8969
|
+
"host": { "type": "string" },
|
|
8970
|
+
"path": { "type": "string" },
|
|
8971
|
+
"status_code": { "type": ["integer", "null"] },
|
|
8972
|
+
"ok": { "type": ["boolean", "null"] },
|
|
8973
|
+
"duration_ms": { "type": "integer" },
|
|
8974
|
+
"error": { "type": ["string", "null"] },
|
|
8975
|
+
"ts": {
|
|
8976
|
+
"type": "string",
|
|
8977
|
+
"format": "date-time"
|
|
8978
|
+
}
|
|
8979
|
+
},
|
|
8980
|
+
"required": [
|
|
8981
|
+
"id",
|
|
8982
|
+
"function_id",
|
|
8983
|
+
"webhook_delivery_id",
|
|
8984
|
+
"email_id",
|
|
8985
|
+
"endpoint_id",
|
|
8986
|
+
"method",
|
|
8987
|
+
"url",
|
|
8988
|
+
"host",
|
|
8989
|
+
"path",
|
|
8990
|
+
"status_code",
|
|
8991
|
+
"ok",
|
|
8992
|
+
"duration_ms",
|
|
8993
|
+
"error",
|
|
8994
|
+
"ts"
|
|
8995
|
+
]
|
|
8996
|
+
}
|
|
8997
|
+
},
|
|
8998
|
+
"logs": {
|
|
8999
|
+
"type": "array",
|
|
9000
|
+
"items": {
|
|
9001
|
+
"type": "object",
|
|
9002
|
+
"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",
|
|
9003
|
+
"properties": {
|
|
9004
|
+
"id": {
|
|
9005
|
+
"type": "string",
|
|
9006
|
+
"format": "uuid",
|
|
9007
|
+
"description": "Unique log row id (stable across pages)."
|
|
9008
|
+
},
|
|
9009
|
+
"function_id": {
|
|
9010
|
+
"type": "string",
|
|
9011
|
+
"format": "uuid",
|
|
9012
|
+
"description": "The function this log row belongs to."
|
|
9013
|
+
},
|
|
9014
|
+
"level": {
|
|
9015
|
+
"type": "string",
|
|
9016
|
+
"enum": [
|
|
9017
|
+
"debug",
|
|
9018
|
+
"log",
|
|
9019
|
+
"info",
|
|
9020
|
+
"warn",
|
|
9021
|
+
"error"
|
|
9022
|
+
],
|
|
9023
|
+
"description": "Severity. `log` is the runtime's default for unannotated\n`console.log` calls; the other levels match standard\n`console.*` methods.\n"
|
|
9024
|
+
},
|
|
9025
|
+
"message": {
|
|
9026
|
+
"type": "string",
|
|
9027
|
+
"description": "The textual message body. The runtime stringifies non-string\narguments before persisting, so this is always a plain\nstring.\n"
|
|
9028
|
+
},
|
|
9029
|
+
"ts": {
|
|
9030
|
+
"type": "string",
|
|
9031
|
+
"format": "date-time",
|
|
9032
|
+
"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"
|
|
9033
|
+
},
|
|
9034
|
+
"metadata": {
|
|
9035
|
+
"type": ["object", "null"],
|
|
9036
|
+
"additionalProperties": true,
|
|
9037
|
+
"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"
|
|
9038
|
+
}
|
|
9039
|
+
},
|
|
9040
|
+
"required": [
|
|
9041
|
+
"id",
|
|
9042
|
+
"function_id",
|
|
9043
|
+
"level",
|
|
9044
|
+
"message",
|
|
9045
|
+
"ts"
|
|
9046
|
+
]
|
|
9047
|
+
}
|
|
9048
|
+
},
|
|
9049
|
+
"replies": {
|
|
9050
|
+
"type": "array",
|
|
9051
|
+
"items": {
|
|
9052
|
+
"type": "object",
|
|
9053
|
+
"properties": {
|
|
9054
|
+
"id": {
|
|
9055
|
+
"type": "string",
|
|
9056
|
+
"format": "uuid"
|
|
9057
|
+
},
|
|
9058
|
+
"status": {
|
|
9059
|
+
"type": "string",
|
|
9060
|
+
"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",
|
|
9061
|
+
"enum": [
|
|
9062
|
+
"queued",
|
|
9063
|
+
"submitted_to_agent",
|
|
9064
|
+
"agent_failed",
|
|
9065
|
+
"gate_denied",
|
|
9066
|
+
"unknown",
|
|
9067
|
+
"delivered",
|
|
9068
|
+
"bounced",
|
|
9069
|
+
"deferred",
|
|
9070
|
+
"wait_timeout"
|
|
9071
|
+
]
|
|
9072
|
+
},
|
|
9073
|
+
"to": { "type": "string" },
|
|
9074
|
+
"subject": { "type": "string" },
|
|
9075
|
+
"queue_id": { "type": ["string", "null"] },
|
|
9076
|
+
"created_at": {
|
|
9077
|
+
"type": "string",
|
|
9078
|
+
"format": "date-time"
|
|
9079
|
+
}
|
|
9080
|
+
},
|
|
9081
|
+
"required": [
|
|
9082
|
+
"id",
|
|
9083
|
+
"status",
|
|
9084
|
+
"to",
|
|
9085
|
+
"subject",
|
|
9086
|
+
"queue_id",
|
|
9087
|
+
"created_at"
|
|
9088
|
+
]
|
|
9089
|
+
}
|
|
9090
|
+
}
|
|
9091
|
+
},
|
|
9092
|
+
"required": [
|
|
9093
|
+
"state",
|
|
9094
|
+
"test_run",
|
|
9095
|
+
"test_send",
|
|
9096
|
+
"inbound_email",
|
|
9097
|
+
"deliveries",
|
|
9098
|
+
"outbound_requests",
|
|
9099
|
+
"logs",
|
|
9100
|
+
"replies"
|
|
9101
|
+
]
|
|
9102
|
+
},
|
|
9103
|
+
"sdkName": "getFunctionTestRunTrace",
|
|
9104
|
+
"summary": "Get a function test run trace",
|
|
9105
|
+
"tag": "Functions",
|
|
9106
|
+
"tagCommand": "functions"
|
|
9107
|
+
},
|
|
9108
|
+
{
|
|
9109
|
+
"binaryResponse": false,
|
|
9110
|
+
"bodyRequired": false,
|
|
9111
|
+
"command": "list-function-logs",
|
|
9112
|
+
"description": "Returns the most recent `function_logs` rows for the function,\nnewest first. Each row is a single `console.log` / `console.error`\ninvocation captured from the running handler.\n\nPage through history with the opaque `cursor` returned as\n`next_cursor`; pass it back as the `cursor` query param on the\nnext call. `next_cursor` is `null` when there are no further\nrows. The cursor format is an implementation detail and should\nnot be parsed by callers.\n",
|
|
9113
|
+
"hasJsonBody": false,
|
|
9114
|
+
"method": "GET",
|
|
9115
|
+
"operationId": "listFunctionLogs",
|
|
9116
|
+
"path": "/functions/{id}/logs",
|
|
9117
|
+
"pathParams": [{
|
|
9118
|
+
"description": "Resource UUID",
|
|
9119
|
+
"enum": null,
|
|
9120
|
+
"name": "id",
|
|
9121
|
+
"required": true,
|
|
9122
|
+
"type": "string"
|
|
9123
|
+
}],
|
|
9124
|
+
"queryParams": [{
|
|
9125
|
+
"default": 50,
|
|
9126
|
+
"description": "Maximum number of rows to return. Clamped to 1..200; default\n50.\n",
|
|
9127
|
+
"enum": null,
|
|
9128
|
+
"maximum": 200,
|
|
9129
|
+
"minimum": 1,
|
|
9130
|
+
"name": "limit",
|
|
9131
|
+
"required": false,
|
|
9132
|
+
"type": "integer"
|
|
9133
|
+
}, {
|
|
9134
|
+
"description": "Opaque pagination cursor from a previous response's\n`next_cursor`. Omit on the first call.\n",
|
|
9135
|
+
"enum": null,
|
|
9136
|
+
"name": "cursor",
|
|
9137
|
+
"required": false,
|
|
9138
|
+
"type": "string"
|
|
9139
|
+
}],
|
|
9140
|
+
"requestSchema": null,
|
|
9141
|
+
"responseSchema": {
|
|
9142
|
+
"type": "object",
|
|
9143
|
+
"properties": {
|
|
9144
|
+
"items": {
|
|
9145
|
+
"type": "array",
|
|
9146
|
+
"items": {
|
|
9147
|
+
"type": "object",
|
|
9148
|
+
"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",
|
|
9149
|
+
"properties": {
|
|
9150
|
+
"id": {
|
|
8329
9151
|
"type": "string",
|
|
8330
9152
|
"format": "uuid",
|
|
8331
9153
|
"description": "Unique log row id (stable across pages)."
|
|
@@ -8602,8 +9424,13 @@ const operationManifest = [
|
|
|
8602
9424
|
},
|
|
8603
9425
|
"responseSchema": {
|
|
8604
9426
|
"type": "object",
|
|
8605
|
-
"description": "Metadata returned by POST /functions/{id}/test. The send is\nqueued;
|
|
9427
|
+
"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
9428
|
"properties": {
|
|
9429
|
+
"test_run_id": {
|
|
9430
|
+
"type": "string",
|
|
9431
|
+
"format": "uuid",
|
|
9432
|
+
"description": "Durable test run id used to fetch the run trace."
|
|
9433
|
+
},
|
|
8607
9434
|
"inbound_domain": {
|
|
8608
9435
|
"type": "string",
|
|
8609
9436
|
"description": "Verified inbound domain the test email was sent to."
|
|
@@ -8633,16 +9460,22 @@ const operationManifest = [
|
|
|
8633
9460
|
"type": "string",
|
|
8634
9461
|
"format": "uri",
|
|
8635
9462
|
"description": "Function detail page where invocations show up live."
|
|
9463
|
+
},
|
|
9464
|
+
"trace_url": {
|
|
9465
|
+
"type": "string",
|
|
9466
|
+
"description": "Relative API URL for GET /functions/{id}/test-runs/{test_run_id}/trace."
|
|
8636
9467
|
}
|
|
8637
9468
|
},
|
|
8638
9469
|
"required": [
|
|
9470
|
+
"test_run_id",
|
|
8639
9471
|
"inbound_domain",
|
|
8640
9472
|
"to",
|
|
8641
9473
|
"from",
|
|
8642
9474
|
"send_id",
|
|
8643
9475
|
"subject",
|
|
8644
9476
|
"poll_since",
|
|
8645
|
-
"watch_url"
|
|
9477
|
+
"watch_url",
|
|
9478
|
+
"trace_url"
|
|
8646
9479
|
]
|
|
8647
9480
|
},
|
|
8648
9481
|
"sdkName": "testFunction",
|
|
@@ -9128,8 +9961,11 @@ const operationManifest = [
|
|
|
9128
9961
|
"type": "string"
|
|
9129
9962
|
},
|
|
9130
9963
|
{
|
|
9964
|
+
"default": 50,
|
|
9131
9965
|
"description": "Number of results per page",
|
|
9132
9966
|
"enum": null,
|
|
9967
|
+
"maximum": 100,
|
|
9968
|
+
"minimum": 1,
|
|
9133
9969
|
"name": "limit",
|
|
9134
9970
|
"required": false,
|
|
9135
9971
|
"type": "integer"
|
|
@@ -9701,8 +10537,11 @@ const operationManifest = [
|
|
|
9701
10537
|
"type": "string"
|
|
9702
10538
|
},
|
|
9703
10539
|
{
|
|
10540
|
+
"default": 50,
|
|
9704
10541
|
"description": "Number of results per page",
|
|
9705
10542
|
"enum": null,
|
|
10543
|
+
"maximum": 100,
|
|
10544
|
+
"minimum": 1,
|
|
9706
10545
|
"name": "limit",
|
|
9707
10546
|
"required": false,
|
|
9708
10547
|
"type": "integer"
|
|
@@ -10166,6 +11005,22 @@ function flagName(parameterName) {
|
|
|
10166
11005
|
function flagDescription(parameter) {
|
|
10167
11006
|
return parameter.description ?? parameter.name;
|
|
10168
11007
|
}
|
|
11008
|
+
const numberFlag = Flags.custom({ async parse(input, _context, options) {
|
|
11009
|
+
const trimmed = input.trim();
|
|
11010
|
+
if (trimmed === "") throw new Errors.CLIError(`Expected a number but received: ${input}`);
|
|
11011
|
+
const value = Number(trimmed);
|
|
11012
|
+
if (!Number.isFinite(value)) throw new Errors.CLIError(`Expected a number but received: ${input}`);
|
|
11013
|
+
if (options.min !== void 0 && value < options.min) throw new Errors.CLIError(`Expected a number greater than or equal to ${options.min} but received: ${input}`);
|
|
11014
|
+
if (options.max !== void 0 && value > options.max) throw new Errors.CLIError(`Expected a number less than or equal to ${options.max} but received: ${input}`);
|
|
11015
|
+
return value;
|
|
11016
|
+
} });
|
|
11017
|
+
function numericFlagOptions(parameter) {
|
|
11018
|
+
return {
|
|
11019
|
+
...typeof parameter.default === "number" ? { default: parameter.default } : {},
|
|
11020
|
+
...typeof parameter.maximum === "number" ? { max: parameter.maximum } : {},
|
|
11021
|
+
...typeof parameter.minimum === "number" ? { min: parameter.minimum } : {}
|
|
11022
|
+
};
|
|
11023
|
+
}
|
|
10169
11024
|
function extractBodyFields(schema) {
|
|
10170
11025
|
if (!schema || typeof schema !== "object") return [];
|
|
10171
11026
|
const properties = schema.properties;
|
|
@@ -10181,7 +11036,8 @@ function extractBodyFields(schema) {
|
|
|
10181
11036
|
if (typeof t === "string") {
|
|
10182
11037
|
displayType = t;
|
|
10183
11038
|
if (t === "string") kind = "string";
|
|
10184
|
-
else if (t === "integer"
|
|
11039
|
+
else if (t === "integer") kind = "integer";
|
|
11040
|
+
else if (t === "number") kind = "number";
|
|
10185
11041
|
else if (t === "boolean") kind = "boolean";
|
|
10186
11042
|
else if (t === "array") {
|
|
10187
11043
|
const items = propSchema.items;
|
|
@@ -10197,7 +11053,8 @@ function extractBodyFields(schema) {
|
|
|
10197
11053
|
const single = nonNull[0];
|
|
10198
11054
|
displayType = `${single}?`;
|
|
10199
11055
|
if (single === "string") kind = "string";
|
|
10200
|
-
else if (single === "integer"
|
|
11056
|
+
else if (single === "integer") kind = "integer";
|
|
11057
|
+
else if (single === "number") kind = "number";
|
|
10201
11058
|
else if (single === "boolean") kind = "boolean";
|
|
10202
11059
|
else kind = "complex";
|
|
10203
11060
|
} else {
|
|
@@ -10214,7 +11071,9 @@ function extractBodyFields(schema) {
|
|
|
10214
11071
|
required: required.has(name),
|
|
10215
11072
|
displayType,
|
|
10216
11073
|
kind,
|
|
10217
|
-
...enumValues && enumValues.length > 0 ? { enumValues } : {}
|
|
11074
|
+
...enumValues && enumValues.length > 0 ? { enumValues } : {},
|
|
11075
|
+
...typeof propSchema.maximum === "number" ? { maximum: propSchema.maximum } : {},
|
|
11076
|
+
...typeof propSchema.minimum === "number" ? { minimum: propSchema.minimum } : {}
|
|
10218
11077
|
});
|
|
10219
11078
|
}
|
|
10220
11079
|
return fields.sort((a, b) => {
|
|
@@ -10262,7 +11121,14 @@ function flagForParameter(parameter) {
|
|
|
10262
11121
|
required: parameter.required
|
|
10263
11122
|
};
|
|
10264
11123
|
if (parameter.type === "boolean") return Flags.boolean(common);
|
|
10265
|
-
if (parameter.type === "integer") return Flags.integer(
|
|
11124
|
+
if (parameter.type === "integer") return Flags.integer({
|
|
11125
|
+
...common,
|
|
11126
|
+
...numericFlagOptions(parameter)
|
|
11127
|
+
});
|
|
11128
|
+
if (parameter.type === "number") return numberFlag({
|
|
11129
|
+
...common,
|
|
11130
|
+
...numericFlagOptions(parameter)
|
|
11131
|
+
});
|
|
10266
11132
|
if (parameter.enum && parameter.enum.length > 0) return Flags.string({
|
|
10267
11133
|
...common,
|
|
10268
11134
|
options: parameter.enum
|
|
@@ -10412,12 +11278,20 @@ const RESERVED_FLAG_NAMES = new Set([
|
|
|
10412
11278
|
"api-base-url-2",
|
|
10413
11279
|
"raw-body",
|
|
10414
11280
|
"body-file",
|
|
11281
|
+
"envelope",
|
|
10415
11282
|
"output"
|
|
10416
11283
|
]);
|
|
10417
11284
|
function bodyFieldFlag(field) {
|
|
10418
11285
|
const common = { description: field.description || field.name };
|
|
10419
11286
|
if (field.kind === "boolean") return Flags.boolean(common);
|
|
10420
|
-
if (field.kind === "integer") return Flags.integer(
|
|
11287
|
+
if (field.kind === "integer") return Flags.integer({
|
|
11288
|
+
...common,
|
|
11289
|
+
...numericFlagOptions(field)
|
|
11290
|
+
});
|
|
11291
|
+
if (field.kind === "number") return numberFlag({
|
|
11292
|
+
...common,
|
|
11293
|
+
...numericFlagOptions(field)
|
|
11294
|
+
});
|
|
10421
11295
|
if (field.enumValues) return Flags.string({
|
|
10422
11296
|
...common,
|
|
10423
11297
|
options: field.enumValues
|
|
@@ -10442,6 +11316,7 @@ function buildFlags(operation) {
|
|
|
10442
11316
|
}),
|
|
10443
11317
|
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
10444
11318
|
};
|
|
11319
|
+
if (!operation.binaryResponse) flags.envelope = Flags.boolean({ description: "Print the full response envelope, including pagination metadata such as meta.cursor. Defaults to printing only the data payload for backward compatibility." });
|
|
10445
11320
|
for (const parameter of [...operation.pathParams, ...operation.queryParams]) flags[flagName(parameter.name)] = flagForParameter(parameter);
|
|
10446
11321
|
const bodyFieldFlagToProperty = /* @__PURE__ */ new Map();
|
|
10447
11322
|
if (operation.hasJsonBody) {
|
|
@@ -10480,6 +11355,9 @@ function collectValues(parameters, flags) {
|
|
|
10480
11355
|
}
|
|
10481
11356
|
return values;
|
|
10482
11357
|
}
|
|
11358
|
+
function operationOutputPayload(envelope, includeEnvelope) {
|
|
11359
|
+
return includeEnvelope ? envelope ?? null : envelope?.data ?? null;
|
|
11360
|
+
}
|
|
10483
11361
|
const OPERATION_HINTS = {
|
|
10484
11362
|
createFunction: "Tip: prefer `primitive functions deploy --name <name> --file <bundle>` for file-input ergonomics. This raw command exists for callers passing JSON.",
|
|
10485
11363
|
updateFunction: "Tip: prefer `primitive functions redeploy --id <id> --file <bundle>` for file-input ergonomics. This raw command exists for callers passing JSON.",
|
|
@@ -10587,7 +11465,7 @@ function createOperationCommand(operation) {
|
|
|
10587
11465
|
writeIdempotentReplayBannerIfReplay(envelope?.data, { write: (chunk) => {
|
|
10588
11466
|
process.stderr.write(chunk);
|
|
10589
11467
|
} });
|
|
10590
|
-
this.log(JSON.stringify(envelope
|
|
11468
|
+
this.log(JSON.stringify(operationOutputPayload(envelope, parsedFlags.envelope === true), null, 2));
|
|
10591
11469
|
});
|
|
10592
11470
|
}
|
|
10593
11471
|
}
|
|
@@ -11345,6 +12223,92 @@ var EmailsWatchCommand = class EmailsWatchCommand extends Command {
|
|
|
11345
12223
|
}
|
|
11346
12224
|
};
|
|
11347
12225
|
//#endregion
|
|
12226
|
+
//#region src/oclif/function-deploy-wait.ts
|
|
12227
|
+
function validateDeployWaitFlags(params) {
|
|
12228
|
+
if (params.timeoutSeconds < 0) return "--timeout must be greater than or equal to 0.";
|
|
12229
|
+
if (params.pollIntervalSeconds <= 0) return "--poll-interval must be greater than 0.";
|
|
12230
|
+
return null;
|
|
12231
|
+
}
|
|
12232
|
+
function isTerminal(status) {
|
|
12233
|
+
return status === "deployed" || status === "failed";
|
|
12234
|
+
}
|
|
12235
|
+
function resultForTerminal(snapshot) {
|
|
12236
|
+
if (snapshot.deploy_status === "failed") return {
|
|
12237
|
+
function: snapshot,
|
|
12238
|
+
kind: "failed"
|
|
12239
|
+
};
|
|
12240
|
+
return {
|
|
12241
|
+
function: snapshot,
|
|
12242
|
+
kind: "ok"
|
|
12243
|
+
};
|
|
12244
|
+
}
|
|
12245
|
+
function toDeployWaitSnapshot(value) {
|
|
12246
|
+
return {
|
|
12247
|
+
...value.created_at !== void 0 ? { created_at: value.created_at } : {},
|
|
12248
|
+
...value.deploy_error !== void 0 ? { deploy_error: value.deploy_error } : {},
|
|
12249
|
+
deploy_status: value.deploy_status,
|
|
12250
|
+
...value.deployed_at !== void 0 ? { deployed_at: value.deployed_at } : {},
|
|
12251
|
+
gateway_url: value.gateway_url,
|
|
12252
|
+
id: value.id,
|
|
12253
|
+
name: value.name,
|
|
12254
|
+
...value.updated_at !== void 0 ? { updated_at: value.updated_at } : {}
|
|
12255
|
+
};
|
|
12256
|
+
}
|
|
12257
|
+
function elapsedSeconds(startedAt, now) {
|
|
12258
|
+
return Math.max(0, Math.round((now() - startedAt) / 1e3));
|
|
12259
|
+
}
|
|
12260
|
+
async function defaultSleep(ms) {
|
|
12261
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
12262
|
+
}
|
|
12263
|
+
async function waitForFunctionDeploy(params) {
|
|
12264
|
+
const now = params.now ?? Date.now;
|
|
12265
|
+
const sleep = params.sleep ?? defaultSleep;
|
|
12266
|
+
const writeStderr = params.writeStderr ?? ((chunk) => {
|
|
12267
|
+
process.stderr.write(chunk);
|
|
12268
|
+
});
|
|
12269
|
+
const startedAt = now();
|
|
12270
|
+
const timeoutMs = params.timeoutSeconds * 1e3;
|
|
12271
|
+
const pollIntervalMs = params.pollIntervalSeconds * 1e3;
|
|
12272
|
+
const hasTimeout = params.timeoutSeconds > 0;
|
|
12273
|
+
let last = params.initial ? toDeployWaitSnapshot(params.initial) : null;
|
|
12274
|
+
let lastStatus = last?.deploy_status ?? "unknown";
|
|
12275
|
+
if (last && isTerminal(last.deploy_status)) return resultForTerminal(last);
|
|
12276
|
+
writeStderr(`Waiting for function ${params.id} deploy to finish (current status: ${lastStatus})...\n`);
|
|
12277
|
+
while (true) {
|
|
12278
|
+
const elapsedMs = now() - startedAt;
|
|
12279
|
+
if (hasTimeout && elapsedMs >= timeoutMs) return {
|
|
12280
|
+
elapsedSeconds: elapsedSeconds(startedAt, now),
|
|
12281
|
+
kind: "timeout",
|
|
12282
|
+
lastFunction: last
|
|
12283
|
+
};
|
|
12284
|
+
await sleep(hasTimeout ? Math.min(pollIntervalMs, Math.max(0, timeoutMs - elapsedMs)) : pollIntervalMs);
|
|
12285
|
+
if (hasTimeout && now() - startedAt >= timeoutMs) return {
|
|
12286
|
+
elapsedSeconds: elapsedSeconds(startedAt, now),
|
|
12287
|
+
kind: "timeout",
|
|
12288
|
+
lastFunction: last
|
|
12289
|
+
};
|
|
12290
|
+
const result = await params.getFunction({ id: params.id });
|
|
12291
|
+
if (result.error) return {
|
|
12292
|
+
kind: "error",
|
|
12293
|
+
payload: extractErrorPayload(result.error)
|
|
12294
|
+
};
|
|
12295
|
+
const fetched = result.data?.data;
|
|
12296
|
+
if (!fetched) return {
|
|
12297
|
+
kind: "error",
|
|
12298
|
+
payload: {
|
|
12299
|
+
code: "client_error",
|
|
12300
|
+
message: "Get function returned no data while waiting for deploy"
|
|
12301
|
+
}
|
|
12302
|
+
};
|
|
12303
|
+
last = toDeployWaitSnapshot(fetched);
|
|
12304
|
+
if (last.deploy_status !== lastStatus) {
|
|
12305
|
+
lastStatus = last.deploy_status;
|
|
12306
|
+
writeStderr(`Function ${params.id} deploy status: ${last.deploy_status}\n`);
|
|
12307
|
+
}
|
|
12308
|
+
if (isTerminal(last.deploy_status)) return resultForTerminal(last);
|
|
12309
|
+
}
|
|
12310
|
+
}
|
|
12311
|
+
//#endregion
|
|
11348
12312
|
//#region src/oclif/lint/raw-send-mail-fetch.ts
|
|
11349
12313
|
const RAW_SEND_MAIL_FETCH_REGEX = /fetch\s*\(\s*[`'"][^`'"]*primitive\.dev[^`'"]*\/send-mail(?![A-Za-z0-9_-])/g;
|
|
11350
12314
|
const SNIPPET_PADDING = 60;
|
|
@@ -11382,41 +12346,321 @@ function emitRawSendMailFetchWarning(bundleText, write) {
|
|
|
11382
12346
|
//#endregion
|
|
11383
12347
|
//#region src/oclif/secret-flags.ts
|
|
11384
12348
|
const SECRET_KEY_RE = /^[A-Z_][A-Z0-9_]*$/;
|
|
11385
|
-
function
|
|
12349
|
+
function resolveSecretFlags(input) {
|
|
11386
12350
|
const secrets = [];
|
|
11387
12351
|
const seenKeys = /* @__PURE__ */ new Set();
|
|
11388
|
-
|
|
11389
|
-
|
|
11390
|
-
|
|
12352
|
+
const env = input.env ?? process.env;
|
|
12353
|
+
const readFile = input.readFile ?? defaultReadFile$1;
|
|
12354
|
+
const readStdin = input.readStdin ?? defaultReadStdin$1;
|
|
12355
|
+
const envFileCache = /* @__PURE__ */ new Map();
|
|
12356
|
+
const reserveSecretKey = (key, sourceLabel) => {
|
|
12357
|
+
const keyError = validateKey(key, sourceLabel);
|
|
12358
|
+
if (keyError) return keyError;
|
|
12359
|
+
if (seenKeys.has(key)) return duplicateKeyError(key);
|
|
12360
|
+
seenKeys.add(key);
|
|
12361
|
+
return null;
|
|
12362
|
+
};
|
|
12363
|
+
const addSecret = (key, value, sourceLabel) => {
|
|
12364
|
+
const keyError = reserveSecretKey(key, sourceLabel);
|
|
12365
|
+
if (keyError) return keyError;
|
|
12366
|
+
secrets.push({
|
|
12367
|
+
key,
|
|
12368
|
+
value
|
|
12369
|
+
});
|
|
12370
|
+
return null;
|
|
12371
|
+
};
|
|
12372
|
+
for (const entry of input.inline ?? []) {
|
|
12373
|
+
const parsed = parseKeyValueFlag(entry, "--secret");
|
|
12374
|
+
if (parsed.kind === "error") return parsed;
|
|
12375
|
+
const error = addSecret(parsed.key, parsed.value, "--secret");
|
|
12376
|
+
if (error) return error;
|
|
12377
|
+
}
|
|
12378
|
+
for (const key of input.fromEnv ?? []) {
|
|
12379
|
+
const keyError = reserveSecretKey(key, "--secret-from-env");
|
|
12380
|
+
if (keyError) return keyError;
|
|
12381
|
+
const value = env[key];
|
|
12382
|
+
if (value === void 0) return {
|
|
11391
12383
|
kind: "error",
|
|
11392
|
-
message: `--secret
|
|
12384
|
+
message: `--secret-from-env ${key} could not read ${key}: environment variable is not set.`
|
|
12385
|
+
};
|
|
12386
|
+
secrets.push({
|
|
12387
|
+
key,
|
|
12388
|
+
value
|
|
12389
|
+
});
|
|
12390
|
+
}
|
|
12391
|
+
for (const entry of input.fromFile ?? []) {
|
|
12392
|
+
const parsed = parseKeyValueFlag(entry, "--secret-from-file");
|
|
12393
|
+
if (parsed.kind === "error") return parsed;
|
|
12394
|
+
const keyError = reserveSecretKey(parsed.key, "--secret-from-file");
|
|
12395
|
+
if (keyError) return keyError;
|
|
12396
|
+
const file = readSecretFile(parsed.value, "--secret-from-file", readFile);
|
|
12397
|
+
if (file.kind === "error") return file;
|
|
12398
|
+
secrets.push({
|
|
12399
|
+
key: parsed.key,
|
|
12400
|
+
value: file.value
|
|
12401
|
+
});
|
|
12402
|
+
}
|
|
12403
|
+
for (const entry of input.fromEnvFile ?? []) {
|
|
12404
|
+
const parsed = parseEnvFileKeyRef(entry, "--secret-from-env-file");
|
|
12405
|
+
if (parsed.kind === "error") return parsed;
|
|
12406
|
+
const keyError = reserveSecretKey(parsed.key, "--secret-from-env-file");
|
|
12407
|
+
if (keyError) return keyError;
|
|
12408
|
+
const file = readEnvFile(parsed.path, readFile, envFileCache);
|
|
12409
|
+
if (file.kind === "error") return file;
|
|
12410
|
+
const value = file.values.get(parsed.key);
|
|
12411
|
+
if (value === void 0) return {
|
|
12412
|
+
kind: "error",
|
|
12413
|
+
message: `--secret-from-env-file ${entry} could not read ${parsed.key}: key is not present in ${parsed.path}.`
|
|
12414
|
+
};
|
|
12415
|
+
secrets.push({
|
|
12416
|
+
key: parsed.key,
|
|
12417
|
+
value
|
|
12418
|
+
});
|
|
12419
|
+
}
|
|
12420
|
+
if (input.fromStdin !== void 0) {
|
|
12421
|
+
const keyError = reserveSecretKey(input.fromStdin, "--secret-from-stdin");
|
|
12422
|
+
if (keyError) return keyError;
|
|
12423
|
+
const stdin = readSecretStdin("--secret-from-stdin", readStdin);
|
|
12424
|
+
if (stdin.kind === "error") return stdin;
|
|
12425
|
+
secrets.push({
|
|
12426
|
+
key: input.fromStdin,
|
|
12427
|
+
value: stdin.value
|
|
12428
|
+
});
|
|
12429
|
+
}
|
|
12430
|
+
return {
|
|
12431
|
+
kind: "ok",
|
|
12432
|
+
secrets
|
|
12433
|
+
};
|
|
12434
|
+
}
|
|
12435
|
+
function resolveSingleSecretValue(input) {
|
|
12436
|
+
const keyError = validateKey(input.key, "--key");
|
|
12437
|
+
if (keyError) return keyError;
|
|
12438
|
+
if ([
|
|
12439
|
+
input.value !== void 0 ? "--value" : null,
|
|
12440
|
+
input.valueFromEnv !== void 0 ? "--value-from-env" : null,
|
|
12441
|
+
input.valueFile !== void 0 ? "--value-file" : null,
|
|
12442
|
+
input.valueFromEnvFile !== void 0 ? "--value-from-env-file" : null,
|
|
12443
|
+
input.stdin === true ? "--stdin" : null
|
|
12444
|
+
].filter((v) => v !== null).length !== 1) return {
|
|
12445
|
+
kind: "error",
|
|
12446
|
+
message: "Pass exactly one of --value, --value-from-env, --value-file, --value-from-env-file, or --stdin."
|
|
12447
|
+
};
|
|
12448
|
+
const env = input.env ?? process.env;
|
|
12449
|
+
const readFile = input.readFile ?? defaultReadFile$1;
|
|
12450
|
+
const readStdin = input.readStdin ?? defaultReadStdin$1;
|
|
12451
|
+
if (input.value !== void 0) return {
|
|
12452
|
+
kind: "ok",
|
|
12453
|
+
value: input.value
|
|
12454
|
+
};
|
|
12455
|
+
if (input.valueFromEnv !== void 0) {
|
|
12456
|
+
const value = env[input.valueFromEnv];
|
|
12457
|
+
if (value === void 0) return {
|
|
12458
|
+
kind: "error",
|
|
12459
|
+
message: `--value-from-env ${input.valueFromEnv} could not read ${input.valueFromEnv}: environment variable is not set.`
|
|
12460
|
+
};
|
|
12461
|
+
return {
|
|
12462
|
+
kind: "ok",
|
|
12463
|
+
value
|
|
12464
|
+
};
|
|
12465
|
+
}
|
|
12466
|
+
if (input.valueFile !== void 0) return readSecretFile(input.valueFile, "--value-file", readFile);
|
|
12467
|
+
if (input.valueFromEnvFile !== void 0) {
|
|
12468
|
+
const parsed = parseSingleValueEnvFileRef(input.valueFromEnvFile, input.key, "--value-from-env-file");
|
|
12469
|
+
if (parsed.kind === "error") return parsed;
|
|
12470
|
+
const file = readEnvFile(parsed.path, readFile, /* @__PURE__ */ new Map());
|
|
12471
|
+
if (file.kind === "error") return file;
|
|
12472
|
+
const value = file.values.get(parsed.key);
|
|
12473
|
+
if (value === void 0) return {
|
|
12474
|
+
kind: "error",
|
|
12475
|
+
message: `--value-from-env-file ${input.valueFromEnvFile} could not read ${parsed.key}: key is not present in ${parsed.path}.`
|
|
12476
|
+
};
|
|
12477
|
+
return {
|
|
12478
|
+
kind: "ok",
|
|
12479
|
+
value
|
|
12480
|
+
};
|
|
12481
|
+
}
|
|
12482
|
+
if (input.stdin === true) return readSecretStdin("--stdin", readStdin);
|
|
12483
|
+
return {
|
|
12484
|
+
kind: "error",
|
|
12485
|
+
message: "Pass exactly one of --value, --value-from-env, --value-file, --value-from-env-file, or --stdin."
|
|
12486
|
+
};
|
|
12487
|
+
}
|
|
12488
|
+
function defaultReadFile$1(path) {
|
|
12489
|
+
return readFileSync(path, "utf8");
|
|
12490
|
+
}
|
|
12491
|
+
function defaultReadStdin$1() {
|
|
12492
|
+
if (process.stdin.isTTY) throw new Error("stdin is a TTY; pipe a value into this command or pass a file/env source instead.");
|
|
12493
|
+
return readFileSync(0, "utf8");
|
|
12494
|
+
}
|
|
12495
|
+
function parseKeyValueFlag(entry, flagLabel) {
|
|
12496
|
+
const eq = entry.indexOf("=");
|
|
12497
|
+
if (eq === -1) return {
|
|
12498
|
+
kind: "error",
|
|
12499
|
+
message: `${flagLabel} expects KEY=VALUE (got ${JSON.stringify(entry)}). Example: ${flagLabel} API_TOKEN=abc123`
|
|
12500
|
+
};
|
|
12501
|
+
const key = entry.slice(0, eq);
|
|
12502
|
+
const value = entry.slice(eq + 1);
|
|
12503
|
+
if (key.length === 0) return {
|
|
12504
|
+
kind: "error",
|
|
12505
|
+
message: `${flagLabel} is missing a KEY before '=' (got ${JSON.stringify(entry)}). Example: ${flagLabel} API_TOKEN=abc123`
|
|
12506
|
+
};
|
|
12507
|
+
return {
|
|
12508
|
+
kind: "ok",
|
|
12509
|
+
key,
|
|
12510
|
+
value
|
|
12511
|
+
};
|
|
12512
|
+
}
|
|
12513
|
+
function validateKey(key, flagLabel) {
|
|
12514
|
+
if (!SECRET_KEY_RE.test(key)) return {
|
|
12515
|
+
kind: "error",
|
|
12516
|
+
message: `${flagLabel} KEY ${JSON.stringify(key)} does not match ${SECRET_KEY_RE.source} (uppercase letters, digits, underscores; first character is a letter or underscore).`
|
|
12517
|
+
};
|
|
12518
|
+
return null;
|
|
12519
|
+
}
|
|
12520
|
+
function duplicateKeyError(key) {
|
|
12521
|
+
return {
|
|
12522
|
+
kind: "error",
|
|
12523
|
+
message: `Secret KEY ${JSON.stringify(key)} was passed more than once. Each key may only appear once per command.`
|
|
12524
|
+
};
|
|
12525
|
+
}
|
|
12526
|
+
function readSecretFile(path, flagLabel, readFile) {
|
|
12527
|
+
try {
|
|
12528
|
+
return {
|
|
12529
|
+
kind: "ok",
|
|
12530
|
+
value: readFile(path)
|
|
11393
12531
|
};
|
|
11394
|
-
|
|
11395
|
-
|
|
11396
|
-
if (key.length === 0) return {
|
|
12532
|
+
} catch (error) {
|
|
12533
|
+
return {
|
|
11397
12534
|
kind: "error",
|
|
11398
|
-
message:
|
|
12535
|
+
message: `Could not read ${flagLabel} ${path}: ${error instanceof Error ? error.message : String(error)}`
|
|
11399
12536
|
};
|
|
11400
|
-
|
|
12537
|
+
}
|
|
12538
|
+
}
|
|
12539
|
+
function readSecretStdin(flagLabel, readStdin) {
|
|
12540
|
+
try {
|
|
12541
|
+
return {
|
|
12542
|
+
kind: "ok",
|
|
12543
|
+
value: stripOneTrailingLineEnding(readStdin())
|
|
12544
|
+
};
|
|
12545
|
+
} catch (error) {
|
|
12546
|
+
return {
|
|
11401
12547
|
kind: "error",
|
|
11402
|
-
message:
|
|
12548
|
+
message: `Could not read ${flagLabel}: ${error instanceof Error ? error.message : String(error)}`
|
|
11403
12549
|
};
|
|
11404
|
-
|
|
12550
|
+
}
|
|
12551
|
+
}
|
|
12552
|
+
function stripOneTrailingLineEnding(value) {
|
|
12553
|
+
if (!value.endsWith("\n")) return value;
|
|
12554
|
+
const withoutLf = value.slice(0, -1);
|
|
12555
|
+
return withoutLf.endsWith("\r") ? withoutLf.slice(0, -1) : withoutLf;
|
|
12556
|
+
}
|
|
12557
|
+
function parseEnvFileKeyRef(entry, flagLabel) {
|
|
12558
|
+
const sep = entry.lastIndexOf(":");
|
|
12559
|
+
if (sep <= 0 || sep === entry.length - 1) return {
|
|
12560
|
+
kind: "error",
|
|
12561
|
+
message: `${flagLabel} expects FILE:KEY (got ${JSON.stringify(entry)}). Example: ${flagLabel} .env.local:OPENAI_KEY`
|
|
12562
|
+
};
|
|
12563
|
+
const path = entry.slice(0, sep);
|
|
12564
|
+
const key = entry.slice(sep + 1);
|
|
12565
|
+
const keyError = validateKey(key, flagLabel);
|
|
12566
|
+
if (keyError) return keyError;
|
|
12567
|
+
return {
|
|
12568
|
+
kind: "ok",
|
|
12569
|
+
key,
|
|
12570
|
+
path
|
|
12571
|
+
};
|
|
12572
|
+
}
|
|
12573
|
+
function parseSingleValueEnvFileRef(entry, fallbackKey, flagLabel) {
|
|
12574
|
+
const sep = entry.lastIndexOf(":");
|
|
12575
|
+
if (sep === -1) return {
|
|
12576
|
+
kind: "ok",
|
|
12577
|
+
key: fallbackKey,
|
|
12578
|
+
path: entry
|
|
12579
|
+
};
|
|
12580
|
+
if (sep <= 0 || sep === entry.length - 1) return {
|
|
12581
|
+
kind: "error",
|
|
12582
|
+
message: `${flagLabel} expects FILE or FILE:KEY (got ${JSON.stringify(entry)}). Example: ${flagLabel} .env.local or ${flagLabel} .env.local:OPENAI_KEY`
|
|
12583
|
+
};
|
|
12584
|
+
const path = entry.slice(0, sep);
|
|
12585
|
+
const key = entry.slice(sep + 1);
|
|
12586
|
+
const keyError = validateKey(key, flagLabel);
|
|
12587
|
+
if (keyError) return keyError;
|
|
12588
|
+
return {
|
|
12589
|
+
kind: "ok",
|
|
12590
|
+
key,
|
|
12591
|
+
path
|
|
12592
|
+
};
|
|
12593
|
+
}
|
|
12594
|
+
function readEnvFile(path, readFile, cache) {
|
|
12595
|
+
const cached = cache.get(path);
|
|
12596
|
+
if (cached) return {
|
|
12597
|
+
kind: "ok",
|
|
12598
|
+
values: cached
|
|
12599
|
+
};
|
|
12600
|
+
let contents;
|
|
12601
|
+
try {
|
|
12602
|
+
contents = readFile(path);
|
|
12603
|
+
} catch (error) {
|
|
12604
|
+
return {
|
|
11405
12605
|
kind: "error",
|
|
11406
|
-
message:
|
|
12606
|
+
message: `Could not read env file ${path}: ${error instanceof Error ? error.message : String(error)}`
|
|
11407
12607
|
};
|
|
11408
|
-
seenKeys.add(key);
|
|
11409
|
-
secrets.push({
|
|
11410
|
-
key,
|
|
11411
|
-
value
|
|
11412
|
-
});
|
|
11413
12608
|
}
|
|
12609
|
+
const values = parseEnvFile(contents);
|
|
12610
|
+
cache.set(path, values);
|
|
11414
12611
|
return {
|
|
11415
12612
|
kind: "ok",
|
|
11416
|
-
|
|
12613
|
+
values
|
|
11417
12614
|
};
|
|
11418
12615
|
}
|
|
11419
|
-
|
|
12616
|
+
function parseEnvFile(contents) {
|
|
12617
|
+
const values = /* @__PURE__ */ new Map();
|
|
12618
|
+
const normalized = contents.replace(/^\uFEFF/, "");
|
|
12619
|
+
for (const rawLine of normalized.split(/\r?\n/)) {
|
|
12620
|
+
let line = rawLine.trimStart();
|
|
12621
|
+
if (line.length === 0 || line.startsWith("#")) continue;
|
|
12622
|
+
if (line.startsWith("export ")) line = line.slice(7).trimStart();
|
|
12623
|
+
const match = /^([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/.exec(line);
|
|
12624
|
+
if (!match) continue;
|
|
12625
|
+
values.set(match[1], parseEnvValue(match[2] ?? ""));
|
|
12626
|
+
}
|
|
12627
|
+
return values;
|
|
12628
|
+
}
|
|
12629
|
+
function parseEnvValue(raw) {
|
|
12630
|
+
const value = raw.trimStart();
|
|
12631
|
+
if (value.startsWith("'")) {
|
|
12632
|
+
const end = value.indexOf("'", 1);
|
|
12633
|
+
return end === -1 ? value.slice(1) : value.slice(1, end);
|
|
12634
|
+
}
|
|
12635
|
+
if (value.startsWith("\"")) return parseDoubleQuotedEnvValue(value);
|
|
12636
|
+
return value.replace(/\s+#.*$/, "").trimEnd();
|
|
12637
|
+
}
|
|
12638
|
+
function parseDoubleQuotedEnvValue(value) {
|
|
12639
|
+
let out = "";
|
|
12640
|
+
let escaped = false;
|
|
12641
|
+
for (let i = 1; i < value.length; i++) {
|
|
12642
|
+
const ch = value[i];
|
|
12643
|
+
if (escaped) {
|
|
12644
|
+
if (ch === "n") out += "\n";
|
|
12645
|
+
else if (ch === "r") out += "\r";
|
|
12646
|
+
else if (ch === "t") out += " ";
|
|
12647
|
+
else out += ch;
|
|
12648
|
+
escaped = false;
|
|
12649
|
+
continue;
|
|
12650
|
+
}
|
|
12651
|
+
if (ch === "\\") {
|
|
12652
|
+
escaped = true;
|
|
12653
|
+
continue;
|
|
12654
|
+
}
|
|
12655
|
+
if (ch === "\"") break;
|
|
12656
|
+
out += ch;
|
|
12657
|
+
}
|
|
12658
|
+
if (escaped) out += "\\";
|
|
12659
|
+
return out;
|
|
12660
|
+
}
|
|
12661
|
+
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.";
|
|
12662
|
+
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.";
|
|
12663
|
+
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
12664
|
//#endregion
|
|
11421
12665
|
//#region src/oclif/commands/functions-deploy.ts
|
|
11422
12666
|
async function runDeployWithSecrets(api, params) {
|
|
@@ -11518,22 +12762,24 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
|
|
|
11518
12762
|
\`functions:create-function\` if you need the full flag surface
|
|
11519
12763
|
(raw-body JSON, etc.).
|
|
11520
12764
|
|
|
11521
|
-
Pass
|
|
11522
|
-
|
|
11523
|
-
|
|
11524
|
-
|
|
11525
|
-
|
|
11526
|
-
|
|
11527
|
-
|
|
11528
|
-
|
|
11529
|
-
|
|
11530
|
-
|
|
11531
|
-
push them live.`;
|
|
12765
|
+
Pass secret source flags to seed bindings in the same command. Keys
|
|
12766
|
+
must match \`^[A-Z_][A-Z0-9_]*$\` (uppercase letters, digits,
|
|
12767
|
+
underscores; first character is a letter or underscore). With one
|
|
12768
|
+
or more secrets the deploy fans out to multiple API calls:
|
|
12769
|
+
create-function, set-secret per pair, then a final update-function
|
|
12770
|
+
with the same bundle so the running handler picks up the bindings.
|
|
12771
|
+
If a secret write fails after the create step the function exists
|
|
12772
|
+
with whatever secrets succeeded and the redeploy has NOT fired;
|
|
12773
|
+
re-run \`primitive functions set-secret\` for the missing keys, then
|
|
12774
|
+
\`primitive functions redeploy\` to push them live. ${SECRET_SOURCE_FLAGS_DESCRIPTION}`;
|
|
11532
12775
|
static summary = "Deploy a new function from a bundled handler file";
|
|
11533
12776
|
static examples = [
|
|
11534
12777
|
"<%= config.bin %> functions deploy --name forwarder --file ./bundle.js",
|
|
12778
|
+
"<%= config.bin %> functions deploy --name forwarder --file ./bundle.js --wait",
|
|
11535
12779
|
"<%= 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"
|
|
12780
|
+
"<%= config.bin %> functions deploy --name forwarder --file ./bundle.js --secret OPENAI_KEY=sk-... --secret OWNER_EMAIL=me@example.com",
|
|
12781
|
+
"<%= config.bin %> functions deploy --name forwarder --file ./bundle.js --secret-from-env OPENAI_KEY --secret-from-env-file .env.local:OWNER_EMAIL",
|
|
12782
|
+
"printf '%s' \"$OPENAI_KEY\" | <%= config.bin %> functions deploy --name forwarder --file ./bundle.js --secret-from-stdin OPENAI_KEY"
|
|
11537
12783
|
];
|
|
11538
12784
|
static flags = {
|
|
11539
12785
|
"api-key": Flags.string({
|
|
@@ -11563,12 +12809,49 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
|
|
|
11563
12809
|
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
12810
|
multiple: true
|
|
11565
12811
|
}),
|
|
12812
|
+
"secret-from-env": Flags.string({
|
|
12813
|
+
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.",
|
|
12814
|
+
multiple: true
|
|
12815
|
+
}),
|
|
12816
|
+
"secret-from-file": Flags.string({
|
|
12817
|
+
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.",
|
|
12818
|
+
multiple: true
|
|
12819
|
+
}),
|
|
12820
|
+
"secret-from-env-file": Flags.string({
|
|
12821
|
+
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.",
|
|
12822
|
+
multiple: true
|
|
12823
|
+
}),
|
|
12824
|
+
"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." }),
|
|
12825
|
+
wait: Flags.boolean({ description: "Wait until the function deploy reaches deployed or failed. Progress is written to stderr; stdout remains the final JSON payload." }),
|
|
12826
|
+
timeout: Flags.integer({
|
|
12827
|
+
default: 120,
|
|
12828
|
+
description: "Seconds to wait when --wait is set before exiting non-zero. Use 0 to wait forever."
|
|
12829
|
+
}),
|
|
12830
|
+
"poll-interval": Flags.integer({
|
|
12831
|
+
default: 2,
|
|
12832
|
+
description: "Seconds between deploy-status polls when --wait is set."
|
|
12833
|
+
}),
|
|
11566
12834
|
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
11567
12835
|
};
|
|
11568
12836
|
async run() {
|
|
11569
12837
|
const { flags } = await this.parse(FunctionsDeployCommand);
|
|
11570
12838
|
await runWithTiming(flags.time, async () => {
|
|
11571
|
-
const
|
|
12839
|
+
const waitFlagError = validateDeployWaitFlags({
|
|
12840
|
+
pollIntervalSeconds: flags["poll-interval"],
|
|
12841
|
+
timeoutSeconds: flags.timeout
|
|
12842
|
+
});
|
|
12843
|
+
if (waitFlagError) {
|
|
12844
|
+
process.stderr.write(`${waitFlagError}\n`);
|
|
12845
|
+
process.exitCode = 1;
|
|
12846
|
+
return;
|
|
12847
|
+
}
|
|
12848
|
+
const parsedSecrets = resolveSecretFlags({
|
|
12849
|
+
fromEnv: flags["secret-from-env"] ?? [],
|
|
12850
|
+
fromEnvFile: flags["secret-from-env-file"] ?? [],
|
|
12851
|
+
fromFile: flags["secret-from-file"] ?? [],
|
|
12852
|
+
fromStdin: flags["secret-from-stdin"],
|
|
12853
|
+
inline: flags.secret ?? []
|
|
12854
|
+
});
|
|
11572
12855
|
if (parsedSecrets.kind === "error") {
|
|
11573
12856
|
process.stderr.write(`${parsedSecrets.message}\n`);
|
|
11574
12857
|
process.exitCode = 1;
|
|
@@ -11647,19 +12930,57 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
|
|
|
11647
12930
|
return;
|
|
11648
12931
|
}
|
|
11649
12932
|
const payload = outcome.result.redeploy ?? outcome.result.created;
|
|
12933
|
+
if (flags.wait) {
|
|
12934
|
+
const waitResult = await waitForFunctionDeploy({
|
|
12935
|
+
getFunction: (p) => getFunction({
|
|
12936
|
+
client: apiClient.client,
|
|
12937
|
+
path: { id: p.id },
|
|
12938
|
+
responseStyle: "fields"
|
|
12939
|
+
}),
|
|
12940
|
+
id: payload.id,
|
|
12941
|
+
initial: payload,
|
|
12942
|
+
pollIntervalSeconds: flags["poll-interval"],
|
|
12943
|
+
timeoutSeconds: flags.timeout,
|
|
12944
|
+
writeStderr: (chunk) => process.stderr.write(chunk)
|
|
12945
|
+
});
|
|
12946
|
+
if (waitResult.kind === "error") {
|
|
12947
|
+
writeErrorWithHints(waitResult.payload);
|
|
12948
|
+
removeStaleSavedCredentialOnUnauthorized({
|
|
12949
|
+
...authFailureContext,
|
|
12950
|
+
payload: waitResult.payload
|
|
12951
|
+
});
|
|
12952
|
+
process.exitCode = 1;
|
|
12953
|
+
return;
|
|
12954
|
+
}
|
|
12955
|
+
if (waitResult.kind === "timeout") {
|
|
12956
|
+
const status = waitResult.lastFunction?.deploy_status ?? "unknown";
|
|
12957
|
+
process.stderr.write(`Timed out after ${flags.timeout}s waiting for function ${payload.id} deploy to finish (last status: ${status}).\n`);
|
|
12958
|
+
process.exitCode = 2;
|
|
12959
|
+
return;
|
|
12960
|
+
}
|
|
12961
|
+
this.log(JSON.stringify(waitResult.function, null, 2));
|
|
12962
|
+
if (waitResult.kind === "failed") {
|
|
12963
|
+
const detail = waitResult.function.deploy_error ? `: ${waitResult.function.deploy_error}` : ".";
|
|
12964
|
+
process.stderr.write(`Function ${payload.id} deploy failed${detail}\n`);
|
|
12965
|
+
process.exitCode = 1;
|
|
12966
|
+
}
|
|
12967
|
+
return;
|
|
12968
|
+
}
|
|
11650
12969
|
this.log(JSON.stringify(payload, null, 2));
|
|
11651
12970
|
});
|
|
11652
12971
|
}
|
|
11653
12972
|
};
|
|
11654
12973
|
//#endregion
|
|
11655
|
-
//#region src/oclif/
|
|
11656
|
-
const
|
|
11657
|
-
const
|
|
12974
|
+
//#region src/oclif/function-templates.ts
|
|
12975
|
+
const DEFAULT_FUNCTION_TEMPLATE_ID = "email-reply";
|
|
12976
|
+
const PRIMITIVE_TEAM_AUTHOR = {
|
|
12977
|
+
id: "primitive-team",
|
|
12978
|
+
name: "Primitive Team",
|
|
12979
|
+
url: "https://primitive.dev"
|
|
12980
|
+
};
|
|
12981
|
+
const SDK_VERSION_RANGE = "^0.30.0";
|
|
12982
|
+
const CLI_VERSION_RANGE = "^0.30.0";
|
|
11658
12983
|
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
12984
|
function renderHandler() {
|
|
11664
12985
|
return `// env.PRIMITIVE_API_KEY is auto-injected by the Primitive Functions runtime.
|
|
11665
12986
|
//
|
|
@@ -11880,7 +13201,7 @@ Run \`primitive login\` once to save a key in your CLI config if you
|
|
|
11880
13201
|
prefer that to an env var.
|
|
11881
13202
|
`;
|
|
11882
13203
|
}
|
|
11883
|
-
function
|
|
13204
|
+
function renderEmailReplyTemplateFiles(name) {
|
|
11884
13205
|
return [
|
|
11885
13206
|
{
|
|
11886
13207
|
contents: renderHandler(),
|
|
@@ -11908,9 +13229,79 @@ function scaffoldFiles(name) {
|
|
|
11908
13229
|
}
|
|
11909
13230
|
];
|
|
11910
13231
|
}
|
|
13232
|
+
const FUNCTION_TEMPLATES = [{
|
|
13233
|
+
author: PRIMITIVE_TEAM_AUTHOR,
|
|
13234
|
+
dependencies: ["@primitivedotdev/sdk"],
|
|
13235
|
+
description: "A deployable TypeScript email handler that validates email.received events, skips likely loops, and replies with the Primitive SDK.",
|
|
13236
|
+
devDependencies: [
|
|
13237
|
+
"@primitivedotdev/cli",
|
|
13238
|
+
"esbuild",
|
|
13239
|
+
"typescript"
|
|
13240
|
+
],
|
|
13241
|
+
files: ({ name }) => renderEmailReplyTemplateFiles(name),
|
|
13242
|
+
id: DEFAULT_FUNCTION_TEMPLATE_ID,
|
|
13243
|
+
secrets: [],
|
|
13244
|
+
summary: "Reply to inbound email with the Primitive SDK.",
|
|
13245
|
+
tags: [
|
|
13246
|
+
"email",
|
|
13247
|
+
"reply",
|
|
13248
|
+
"typescript",
|
|
13249
|
+
"worker"
|
|
13250
|
+
],
|
|
13251
|
+
title: "Email Reply"
|
|
13252
|
+
}];
|
|
13253
|
+
function functionTemplateIds(templates) {
|
|
13254
|
+
return templates.map((template) => template.id);
|
|
13255
|
+
}
|
|
13256
|
+
function findFunctionTemplate(templates, id) {
|
|
13257
|
+
return templates.find((template) => template.id === id) ?? null;
|
|
13258
|
+
}
|
|
13259
|
+
function serializeFunctionTemplate(template) {
|
|
13260
|
+
return {
|
|
13261
|
+
author: { ...template.author },
|
|
13262
|
+
dependencies: [...template.dependencies],
|
|
13263
|
+
description: template.description,
|
|
13264
|
+
devDependencies: [...template.devDependencies],
|
|
13265
|
+
id: template.id,
|
|
13266
|
+
secrets: [...template.secrets],
|
|
13267
|
+
summary: template.summary,
|
|
13268
|
+
tags: [...template.tags],
|
|
13269
|
+
title: template.title
|
|
13270
|
+
};
|
|
13271
|
+
}
|
|
13272
|
+
function formatFunctionTemplateList(templates) {
|
|
13273
|
+
const lines = ["Available Primitive Function templates:", ""];
|
|
13274
|
+
for (const template of templates) {
|
|
13275
|
+
lines.push(`${template.id}`);
|
|
13276
|
+
lines.push(` title: ${template.title}`);
|
|
13277
|
+
lines.push(` author: ${template.author.name}`);
|
|
13278
|
+
lines.push(` summary: ${template.summary}`);
|
|
13279
|
+
lines.push(` tags: ${template.tags.length > 0 ? template.tags.join(", ") : "none"}`);
|
|
13280
|
+
lines.push(` secrets: ${template.secrets.length > 0 ? template.secrets.join(", ") : "none"}`);
|
|
13281
|
+
lines.push("");
|
|
13282
|
+
}
|
|
13283
|
+
lines.push("Use `primitive functions init <name> --template <id>`.");
|
|
13284
|
+
return lines.join("\n");
|
|
13285
|
+
}
|
|
13286
|
+
//#endregion
|
|
13287
|
+
//#region src/oclif/commands/functions-init.ts
|
|
13288
|
+
const VALID_NAME = /^[a-z0-9][a-z0-9_-]{0,62}$/;
|
|
13289
|
+
function isValidFunctionName(name) {
|
|
13290
|
+
return VALID_NAME.test(name);
|
|
13291
|
+
}
|
|
13292
|
+
function unknownTemplateError(templateId) {
|
|
13293
|
+
const available = functionTemplateIds(FUNCTION_TEMPLATES).join(", ");
|
|
13294
|
+
return new Errors.CLIError(`Unknown function template "${templateId}". Available templates: ${available}. Run \`primitive functions templates\` for details.`, { exit: 1 });
|
|
13295
|
+
}
|
|
13296
|
+
function scaffoldFiles(name, templateId = DEFAULT_FUNCTION_TEMPLATE_ID) {
|
|
13297
|
+
const template = findFunctionTemplate(FUNCTION_TEMPLATES, templateId);
|
|
13298
|
+
if (!template) throw unknownTemplateError(templateId);
|
|
13299
|
+
return template.files({ name });
|
|
13300
|
+
}
|
|
11911
13301
|
function writeScaffold(params) {
|
|
11912
13302
|
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
|
|
13303
|
+
const templateId = params.templateId ?? "email-reply";
|
|
13304
|
+
const files = scaffoldFiles(params.name, templateId);
|
|
11914
13305
|
const written = [];
|
|
11915
13306
|
try {
|
|
11916
13307
|
mkdirSync(params.outDir, { recursive: false });
|
|
@@ -11941,7 +13332,7 @@ function writeScaffold(params) {
|
|
|
11941
13332
|
return { written };
|
|
11942
13333
|
}
|
|
11943
13334
|
var FunctionsInitCommand = class FunctionsInitCommand extends Command {
|
|
11944
|
-
static description = `Scaffold a new Primitive Function project
|
|
13335
|
+
static description = `Scaffold a new Primitive Function project from a Primitive-owned template.
|
|
11945
13336
|
|
|
11946
13337
|
The scaffolded handler imports \`createPrimitiveClient\` from
|
|
11947
13338
|
\`@primitivedotdev/sdk/api\` and demonstrates the canonical pattern:
|
|
@@ -11950,22 +13341,35 @@ var FunctionsInitCommand = class FunctionsInitCommand extends Command {
|
|
|
11950
13341
|
./dist/handler.js, ready to hand to \`primitive functions deploy --file\`.
|
|
11951
13342
|
|
|
11952
13343
|
Refuses to overwrite an existing directory. Use --out-dir to pick a
|
|
11953
|
-
different target path than ./<name
|
|
13344
|
+
different target path than ./<name>/. Run \`primitive functions templates\`
|
|
13345
|
+
to inspect available templates.`;
|
|
11954
13346
|
static summary = "Scaffold a new Primitive Function project ready for functions deploy";
|
|
11955
|
-
static examples = [
|
|
13347
|
+
static examples = [
|
|
13348
|
+
"<%= config.bin %> functions init my-fn",
|
|
13349
|
+
"<%= config.bin %> functions init my-fn --template email-reply",
|
|
13350
|
+
"<%= config.bin %> functions init my-fn --out-dir ./functions/my-fn"
|
|
13351
|
+
];
|
|
11956
13352
|
static args = { name: Args.string({
|
|
11957
13353
|
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
13354
|
required: true
|
|
11959
13355
|
}) };
|
|
11960
|
-
static flags = {
|
|
13356
|
+
static flags = {
|
|
13357
|
+
"out-dir": Flags.string({ description: "Directory to scaffold into. Defaults to ./<name>/. Must not already exist." }),
|
|
13358
|
+
template: Flags.string({
|
|
13359
|
+
default: DEFAULT_FUNCTION_TEMPLATE_ID,
|
|
13360
|
+
description: "Function template id. Run `primitive functions templates` to list templates.",
|
|
13361
|
+
options: functionTemplateIds(FUNCTION_TEMPLATES)
|
|
13362
|
+
})
|
|
13363
|
+
};
|
|
11961
13364
|
async run() {
|
|
11962
13365
|
const { args, flags } = await this.parse(FunctionsInitCommand);
|
|
11963
13366
|
const outDir = resolve(flags["out-dir"] ?? `./${args.name}`);
|
|
11964
13367
|
writeScaffold({
|
|
11965
13368
|
name: args.name,
|
|
11966
|
-
outDir
|
|
13369
|
+
outDir,
|
|
13370
|
+
templateId: flags.template
|
|
11967
13371
|
});
|
|
11968
|
-
this.log(`Scaffolded ${outDir}.`);
|
|
13372
|
+
this.log(`Scaffolded ${outDir} from ${flags.template} template.`);
|
|
11969
13373
|
this.log("Next:");
|
|
11970
13374
|
this.log(` cd ${outDir}`);
|
|
11971
13375
|
this.log(" npm install");
|
|
@@ -11974,6 +13378,166 @@ var FunctionsInitCommand = class FunctionsInitCommand extends Command {
|
|
|
11974
13378
|
}
|
|
11975
13379
|
};
|
|
11976
13380
|
//#endregion
|
|
13381
|
+
//#region src/oclif/commands/functions-logs.ts
|
|
13382
|
+
const DEFAULT_LOG_LIMIT = 50;
|
|
13383
|
+
const DEFAULT_LOG_POLL_INTERVAL_SECONDS = 2;
|
|
13384
|
+
function levelLabel(level) {
|
|
13385
|
+
return level.toUpperCase().padEnd(5);
|
|
13386
|
+
}
|
|
13387
|
+
function orderFunctionLogsForDisplay(rows) {
|
|
13388
|
+
return [...rows].reverse();
|
|
13389
|
+
}
|
|
13390
|
+
function formatFunctionLogLine(row) {
|
|
13391
|
+
const metadata = row.metadata && Object.keys(row.metadata).length > 0 ? ` ${JSON.stringify(row.metadata)}` : "";
|
|
13392
|
+
return `${row.ts} ${levelLabel(row.level)} ${row.message}${metadata}`;
|
|
13393
|
+
}
|
|
13394
|
+
function collectFreshFunctionLogsFromPage(rows, seenIds) {
|
|
13395
|
+
const freshNewestFirst = [];
|
|
13396
|
+
let reachedSeen = false;
|
|
13397
|
+
for (const row of rows) {
|
|
13398
|
+
if (seenIds.has(row.id)) {
|
|
13399
|
+
reachedSeen = true;
|
|
13400
|
+
continue;
|
|
13401
|
+
}
|
|
13402
|
+
freshNewestFirst.push(row);
|
|
13403
|
+
seenIds.add(row.id);
|
|
13404
|
+
}
|
|
13405
|
+
return {
|
|
13406
|
+
freshNewestFirst,
|
|
13407
|
+
reachedSeen
|
|
13408
|
+
};
|
|
13409
|
+
}
|
|
13410
|
+
function emitLogRows(rows, jsonl) {
|
|
13411
|
+
for (const row of rows) {
|
|
13412
|
+
const line = jsonl ? JSON.stringify(row) : formatFunctionLogLine(row);
|
|
13413
|
+
process.stdout.write(`${line}\n`);
|
|
13414
|
+
}
|
|
13415
|
+
}
|
|
13416
|
+
var FunctionsLogsCommand = class FunctionsLogsCommand extends Command {
|
|
13417
|
+
static description = "List or follow function execution logs. Defaults to compact text output; use --jsonl for one JSON object per log row.";
|
|
13418
|
+
static summary = "List or follow a function's execution logs";
|
|
13419
|
+
static examples = [
|
|
13420
|
+
"<%= config.bin %> functions logs --id <fn-id>",
|
|
13421
|
+
"<%= config.bin %> functions logs --id <fn-id> --jsonl",
|
|
13422
|
+
"<%= config.bin %> functions logs --id <fn-id> --follow"
|
|
13423
|
+
];
|
|
13424
|
+
static flags = {
|
|
13425
|
+
"api-key": Flags.string({
|
|
13426
|
+
description: "Primitive API key (defaults to PRIMITIVE_API_KEY or saved `primitive login` credentials)",
|
|
13427
|
+
env: "PRIMITIVE_API_KEY"
|
|
13428
|
+
}),
|
|
13429
|
+
"api-base-url-1": Flags.string({
|
|
13430
|
+
description: "Override the primary API base URL. Internal testing only; not documented to customers.",
|
|
13431
|
+
env: "PRIMITIVE_API_BASE_URL_1",
|
|
13432
|
+
hidden: true
|
|
13433
|
+
}),
|
|
13434
|
+
"api-base-url-2": Flags.string({
|
|
13435
|
+
description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
|
|
13436
|
+
env: "PRIMITIVE_API_BASE_URL_2",
|
|
13437
|
+
hidden: true
|
|
13438
|
+
}),
|
|
13439
|
+
id: Flags.string({
|
|
13440
|
+
description: "Function id (UUID).",
|
|
13441
|
+
required: true
|
|
13442
|
+
}),
|
|
13443
|
+
limit: Flags.integer({
|
|
13444
|
+
default: DEFAULT_LOG_LIMIT,
|
|
13445
|
+
description: "Maximum rows to fetch per poll. Server clamps to 1..200."
|
|
13446
|
+
}),
|
|
13447
|
+
cursor: Flags.string({ description: "Opaque pagination cursor from a previous logs response. Not supported with --follow." }),
|
|
13448
|
+
follow: Flags.boolean({
|
|
13449
|
+
char: "f",
|
|
13450
|
+
description: "Keep polling the newest logs and print rows not seen yet."
|
|
13451
|
+
}),
|
|
13452
|
+
jsonl: Flags.boolean({ description: "Print one compact JSON object per log row." }),
|
|
13453
|
+
"poll-interval": Flags.integer({
|
|
13454
|
+
default: DEFAULT_LOG_POLL_INTERVAL_SECONDS,
|
|
13455
|
+
description: "Seconds between polls when --follow is set."
|
|
13456
|
+
}),
|
|
13457
|
+
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
13458
|
+
};
|
|
13459
|
+
async run() {
|
|
13460
|
+
const { flags } = await this.parse(FunctionsLogsCommand);
|
|
13461
|
+
if (flags.limit <= 0) this.error("--limit must be greater than 0.", { exit: 2 });
|
|
13462
|
+
if (flags["poll-interval"] <= 0) this.error("--poll-interval must be greater than 0.", { exit: 2 });
|
|
13463
|
+
if (flags.follow && flags.cursor) this.error("--cursor cannot be combined with --follow.", { exit: 2 });
|
|
13464
|
+
await runWithTiming(flags.time, async () => {
|
|
13465
|
+
const baseUrlOverridden = flags["api-base-url-1"] !== void 0 || flags["api-base-url-2"] !== void 0;
|
|
13466
|
+
const auth = resolveCliAuth({
|
|
13467
|
+
apiKey: flags["api-key"],
|
|
13468
|
+
apiBaseUrl1: flags["api-base-url-1"],
|
|
13469
|
+
apiBaseUrl2: flags["api-base-url-2"],
|
|
13470
|
+
configDir: this.config.configDir
|
|
13471
|
+
});
|
|
13472
|
+
const apiClient = new PrimitiveApiClient({
|
|
13473
|
+
apiKey: auth.apiKey,
|
|
13474
|
+
apiBaseUrl1: auth.apiBaseUrl1,
|
|
13475
|
+
apiBaseUrl2: auth.apiBaseUrl2
|
|
13476
|
+
});
|
|
13477
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
13478
|
+
let completedInitialFollowPoll = false;
|
|
13479
|
+
let hasObservedLogs = false;
|
|
13480
|
+
let wroteEmptyHint = false;
|
|
13481
|
+
while (true) {
|
|
13482
|
+
let cursor = flags.cursor;
|
|
13483
|
+
let nextCursor = null;
|
|
13484
|
+
let rows = [];
|
|
13485
|
+
while (true) {
|
|
13486
|
+
const result = await listFunctionLogs({
|
|
13487
|
+
client: apiClient.client,
|
|
13488
|
+
path: { id: flags.id },
|
|
13489
|
+
query: {
|
|
13490
|
+
...cursor ? { cursor } : {},
|
|
13491
|
+
limit: flags.limit
|
|
13492
|
+
},
|
|
13493
|
+
responseStyle: "fields"
|
|
13494
|
+
});
|
|
13495
|
+
if (result.error) {
|
|
13496
|
+
const errorPayload = extractErrorPayload(result.error);
|
|
13497
|
+
writeErrorWithHints(errorPayload);
|
|
13498
|
+
removeStaleSavedCredentialOnUnauthorized({
|
|
13499
|
+
auth,
|
|
13500
|
+
baseUrlOverridden,
|
|
13501
|
+
configDir: this.config.configDir,
|
|
13502
|
+
payload: errorPayload
|
|
13503
|
+
});
|
|
13504
|
+
process.exitCode = 1;
|
|
13505
|
+
return;
|
|
13506
|
+
}
|
|
13507
|
+
const page = result.data?.data ?? {
|
|
13508
|
+
items: [],
|
|
13509
|
+
next_cursor: null
|
|
13510
|
+
};
|
|
13511
|
+
nextCursor = page.next_cursor;
|
|
13512
|
+
if (!flags.follow) {
|
|
13513
|
+
rows = orderFunctionLogsForDisplay(page.items);
|
|
13514
|
+
break;
|
|
13515
|
+
}
|
|
13516
|
+
if (page.items.length > 0) hasObservedLogs = true;
|
|
13517
|
+
const collected = collectFreshFunctionLogsFromPage(page.items, seenIds);
|
|
13518
|
+
rows.push(...collected.freshNewestFirst);
|
|
13519
|
+
if (!completedInitialFollowPoll || collected.reachedSeen || !page.next_cursor) {
|
|
13520
|
+
rows = orderFunctionLogsForDisplay(rows);
|
|
13521
|
+
break;
|
|
13522
|
+
}
|
|
13523
|
+
cursor = page.next_cursor;
|
|
13524
|
+
}
|
|
13525
|
+
if (rows.length === 0 && !wroteEmptyHint) {
|
|
13526
|
+
process.stderr.write(flags.follow ? hasObservedLogs ? "Waiting for new function logs...\n" : "No function logs yet. Waiting for new rows...\n" : "No function logs yet. Trigger the function, then run this command again.\n");
|
|
13527
|
+
wroteEmptyHint = true;
|
|
13528
|
+
}
|
|
13529
|
+
emitLogRows(rows, flags.jsonl);
|
|
13530
|
+
if (!flags.follow) {
|
|
13531
|
+
if (nextCursor) process.stderr.write(`next cursor: ${nextCursor}\n`);
|
|
13532
|
+
return;
|
|
13533
|
+
}
|
|
13534
|
+
completedInitialFollowPoll = true;
|
|
13535
|
+
await sleep$1(flags["poll-interval"] * 1e3);
|
|
13536
|
+
}
|
|
13537
|
+
});
|
|
13538
|
+
}
|
|
13539
|
+
};
|
|
13540
|
+
//#endregion
|
|
11977
13541
|
//#region src/oclif/commands/functions-redeploy.ts
|
|
11978
13542
|
async function runRedeployWithSecrets(api, params) {
|
|
11979
13543
|
const writtenSecrets = [];
|
|
@@ -12046,17 +13610,20 @@ var FunctionsRedeployCommand = class FunctionsRedeployCommand extends Command {
|
|
|
12046
13610
|
the bindings table fresh on every call, so passing the existing
|
|
12047
13611
|
bundle picks up any secret writes since the last deploy.
|
|
12048
13612
|
|
|
12049
|
-
Pass
|
|
12050
|
-
|
|
12051
|
-
|
|
12052
|
-
|
|
12053
|
-
|
|
12054
|
-
|
|
13613
|
+
Pass secret source flags to write secrets BEFORE the redeploy fires;
|
|
13614
|
+
one update-function call then refreshes every new binding. Keys must
|
|
13615
|
+
match \`^[A-Z_][A-Z0-9_]*$\` (uppercase letters, digits, underscores;
|
|
13616
|
+
first character is a letter or underscore). With one or more secrets
|
|
13617
|
+
the redeploy fans out to multiple API calls (set-secret per pair,
|
|
13618
|
+
then update-function). ${SECRET_SOURCE_FLAGS_DESCRIPTION}`;
|
|
12055
13619
|
static summary = "Redeploy a function from a bundled handler file";
|
|
12056
13620
|
static examples = [
|
|
12057
13621
|
"<%= config.bin %> functions redeploy --id <fn-id> --file ./bundle.js",
|
|
13622
|
+
"<%= config.bin %> functions redeploy --id <fn-id> --file ./bundle.js --wait",
|
|
12058
13623
|
"<%= 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"
|
|
13624
|
+
"<%= config.bin %> functions redeploy --id <fn-id> --file ./bundle.js --secret OPENAI_KEY=sk-... --secret OWNER_EMAIL=me@example.com",
|
|
13625
|
+
"<%= config.bin %> functions redeploy --id <fn-id> --file ./bundle.js --secret-from-env OPENAI_KEY --secret-from-file PRIVATE_KEY=./private-key.pem",
|
|
13626
|
+
"printf '%s' \"$OPENAI_KEY\" | <%= config.bin %> functions redeploy --id <fn-id> --file ./bundle.js --secret-from-stdin OPENAI_KEY"
|
|
12060
13627
|
];
|
|
12061
13628
|
static flags = {
|
|
12062
13629
|
"api-key": Flags.string({
|
|
@@ -12086,12 +13653,49 @@ var FunctionsRedeployCommand = class FunctionsRedeployCommand extends Command {
|
|
|
12086
13653
|
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
13654
|
multiple: true
|
|
12088
13655
|
}),
|
|
13656
|
+
"secret-from-env": Flags.string({
|
|
13657
|
+
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.",
|
|
13658
|
+
multiple: true
|
|
13659
|
+
}),
|
|
13660
|
+
"secret-from-file": Flags.string({
|
|
13661
|
+
description: "Secret KEY=PATH to read from a UTF-8 file and write before the redeploy. Repeatable. The full file contents become the value.",
|
|
13662
|
+
multiple: true
|
|
13663
|
+
}),
|
|
13664
|
+
"secret-from-env-file": Flags.string({
|
|
13665
|
+
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.",
|
|
13666
|
+
multiple: true
|
|
13667
|
+
}),
|
|
13668
|
+
"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." }),
|
|
13669
|
+
wait: Flags.boolean({ description: "Wait until the function deploy reaches deployed or failed. Progress is written to stderr; stdout remains the final JSON payload." }),
|
|
13670
|
+
timeout: Flags.integer({
|
|
13671
|
+
default: 120,
|
|
13672
|
+
description: "Seconds to wait when --wait is set before exiting non-zero. Use 0 to wait forever."
|
|
13673
|
+
}),
|
|
13674
|
+
"poll-interval": Flags.integer({
|
|
13675
|
+
default: 2,
|
|
13676
|
+
description: "Seconds between deploy-status polls when --wait is set."
|
|
13677
|
+
}),
|
|
12089
13678
|
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
12090
13679
|
};
|
|
12091
13680
|
async run() {
|
|
12092
13681
|
const { flags } = await this.parse(FunctionsRedeployCommand);
|
|
12093
13682
|
await runWithTiming(flags.time, async () => {
|
|
12094
|
-
const
|
|
13683
|
+
const waitFlagError = validateDeployWaitFlags({
|
|
13684
|
+
pollIntervalSeconds: flags["poll-interval"],
|
|
13685
|
+
timeoutSeconds: flags.timeout
|
|
13686
|
+
});
|
|
13687
|
+
if (waitFlagError) {
|
|
13688
|
+
process.stderr.write(`${waitFlagError}\n`);
|
|
13689
|
+
process.exitCode = 1;
|
|
13690
|
+
return;
|
|
13691
|
+
}
|
|
13692
|
+
const parsedSecrets = resolveSecretFlags({
|
|
13693
|
+
fromEnv: flags["secret-from-env"] ?? [],
|
|
13694
|
+
fromEnvFile: flags["secret-from-env-file"] ?? [],
|
|
13695
|
+
fromFile: flags["secret-from-file"] ?? [],
|
|
13696
|
+
fromStdin: flags["secret-from-stdin"],
|
|
13697
|
+
inline: flags.secret ?? []
|
|
13698
|
+
});
|
|
12095
13699
|
if (parsedSecrets.kind === "error") {
|
|
12096
13700
|
process.stderr.write(`${parsedSecrets.message}\n`);
|
|
12097
13701
|
process.exitCode = 1;
|
|
@@ -12160,6 +13764,42 @@ var FunctionsRedeployCommand = class FunctionsRedeployCommand extends Command {
|
|
|
12160
13764
|
process.exitCode = 1;
|
|
12161
13765
|
return;
|
|
12162
13766
|
}
|
|
13767
|
+
if (flags.wait) {
|
|
13768
|
+
const waitResult = await waitForFunctionDeploy({
|
|
13769
|
+
getFunction: (p) => getFunction({
|
|
13770
|
+
client: apiClient.client,
|
|
13771
|
+
path: { id: p.id },
|
|
13772
|
+
responseStyle: "fields"
|
|
13773
|
+
}),
|
|
13774
|
+
id: outcome.result.redeploy.id,
|
|
13775
|
+
initial: outcome.result.redeploy,
|
|
13776
|
+
pollIntervalSeconds: flags["poll-interval"],
|
|
13777
|
+
timeoutSeconds: flags.timeout,
|
|
13778
|
+
writeStderr: (chunk) => process.stderr.write(chunk)
|
|
13779
|
+
});
|
|
13780
|
+
if (waitResult.kind === "error") {
|
|
13781
|
+
writeErrorWithHints(waitResult.payload);
|
|
13782
|
+
removeStaleSavedCredentialOnUnauthorized({
|
|
13783
|
+
...authFailureContext,
|
|
13784
|
+
payload: waitResult.payload
|
|
13785
|
+
});
|
|
13786
|
+
process.exitCode = 1;
|
|
13787
|
+
return;
|
|
13788
|
+
}
|
|
13789
|
+
if (waitResult.kind === "timeout") {
|
|
13790
|
+
const status = waitResult.lastFunction?.deploy_status ?? "unknown";
|
|
13791
|
+
process.stderr.write(`Timed out after ${flags.timeout}s waiting for function ${outcome.result.redeploy.id} deploy to finish (last status: ${status}).\n`);
|
|
13792
|
+
process.exitCode = 2;
|
|
13793
|
+
return;
|
|
13794
|
+
}
|
|
13795
|
+
this.log(JSON.stringify(waitResult.function, null, 2));
|
|
13796
|
+
if (waitResult.kind === "failed") {
|
|
13797
|
+
const detail = waitResult.function.deploy_error ? `: ${waitResult.function.deploy_error}` : ".";
|
|
13798
|
+
process.stderr.write(`Function ${outcome.result.redeploy.id} deploy failed${detail}\n`);
|
|
13799
|
+
process.exitCode = 1;
|
|
13800
|
+
}
|
|
13801
|
+
return;
|
|
13802
|
+
}
|
|
12163
13803
|
this.log(JSON.stringify(outcome.result.redeploy, null, 2));
|
|
12164
13804
|
});
|
|
12165
13805
|
}
|
|
@@ -12242,9 +13882,15 @@ var FunctionsSetSecretCommand = class FunctionsSetSecretCommand extends Command
|
|
|
12242
13882
|
|
|
12243
13883
|
Keys must match \`^[A-Z_][A-Z0-9_]*$\` (uppercase letters, digits,
|
|
12244
13884
|
underscores; first character is a letter or underscore). System-
|
|
12245
|
-
managed keys are reserved and rejected
|
|
13885
|
+
managed keys are reserved and rejected. ${SINGLE_SECRET_VALUE_SOURCE_DESCRIPTION}`;
|
|
12246
13886
|
static summary = "Write a function secret (optionally redeploying to push it live)";
|
|
12247
|
-
static examples = [
|
|
13887
|
+
static examples = [
|
|
13888
|
+
"<%= config.bin %> functions set-secret --id <fn-id> --key API_TOKEN --value abc123",
|
|
13889
|
+
"<%= config.bin %> functions set-secret --id <fn-id> --key API_TOKEN --value abc123 --redeploy",
|
|
13890
|
+
"<%= config.bin %> functions set-secret --id <fn-id> --key OPENAI_KEY --value-from-env OPENAI_KEY --redeploy",
|
|
13891
|
+
"<%= config.bin %> functions set-secret --id <fn-id> --key OPENAI_KEY --value-from-env-file .env.local --redeploy",
|
|
13892
|
+
"printf '%s' \"$OPENAI_KEY\" | <%= config.bin %> functions set-secret --id <fn-id> --key OPENAI_KEY --stdin --redeploy"
|
|
13893
|
+
];
|
|
12248
13894
|
static flags = {
|
|
12249
13895
|
"api-key": Flags.string({
|
|
12250
13896
|
description: "Primitive API key (defaults to PRIMITIVE_API_KEY or saved `primitive login` credentials)",
|
|
@@ -12268,10 +13914,11 @@ var FunctionsSetSecretCommand = class FunctionsSetSecretCommand extends Command
|
|
|
12268
13914
|
description: "Secret key. Uppercase letters, digits, underscores; must start with a letter or underscore. System-managed keys are reserved.",
|
|
12269
13915
|
required: true
|
|
12270
13916
|
}),
|
|
12271
|
-
value: Flags.string({
|
|
12272
|
-
|
|
12273
|
-
|
|
12274
|
-
}),
|
|
13917
|
+
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." }),
|
|
13918
|
+
"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." }),
|
|
13919
|
+
"value-file": Flags.string({ description: "UTF-8 file to read as the secret value. The full file contents become the value." }),
|
|
13920
|
+
"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." }),
|
|
13921
|
+
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
13922
|
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
13923
|
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
12277
13924
|
};
|
|
@@ -12295,6 +13942,19 @@ var FunctionsSetSecretCommand = class FunctionsSetSecretCommand extends Command
|
|
|
12295
13942
|
baseUrlOverridden,
|
|
12296
13943
|
configDir: this.config.configDir
|
|
12297
13944
|
};
|
|
13945
|
+
const resolvedValue = resolveSingleSecretValue({
|
|
13946
|
+
key: flags.key,
|
|
13947
|
+
value: flags.value,
|
|
13948
|
+
valueFile: flags["value-file"],
|
|
13949
|
+
valueFromEnv: flags["value-from-env"],
|
|
13950
|
+
valueFromEnvFile: flags["value-from-env-file"],
|
|
13951
|
+
stdin: flags.stdin
|
|
13952
|
+
});
|
|
13953
|
+
if (resolvedValue.kind === "error") {
|
|
13954
|
+
process.stderr.write(`${resolvedValue.message}\n`);
|
|
13955
|
+
process.exitCode = 1;
|
|
13956
|
+
return;
|
|
13957
|
+
}
|
|
12298
13958
|
const outcome = await runSetSecret({
|
|
12299
13959
|
getFunction: (p) => getFunction({
|
|
12300
13960
|
client: apiClient.client,
|
|
@@ -12320,7 +13980,7 @@ var FunctionsSetSecretCommand = class FunctionsSetSecretCommand extends Command
|
|
|
12320
13980
|
id: flags.id,
|
|
12321
13981
|
key: flags.key,
|
|
12322
13982
|
redeploy: flags.redeploy === true,
|
|
12323
|
-
value:
|
|
13983
|
+
value: resolvedValue.value
|
|
12324
13984
|
});
|
|
12325
13985
|
if (outcome.kind === "error") {
|
|
12326
13986
|
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 +13998,116 @@ var FunctionsSetSecretCommand = class FunctionsSetSecretCommand extends Command
|
|
|
12338
13998
|
}
|
|
12339
13999
|
};
|
|
12340
14000
|
//#endregion
|
|
14001
|
+
//#region src/oclif/commands/functions-templates.ts
|
|
14002
|
+
var FunctionsTemplatesCommand = class FunctionsTemplatesCommand extends Command {
|
|
14003
|
+
static enableJsonFlag = true;
|
|
14004
|
+
static description = `List Primitive Function templates available to \`primitive functions init\`.
|
|
14005
|
+
|
|
14006
|
+
The default table is optimized for quick terminal discovery. Use
|
|
14007
|
+
--json when an agent or script needs stable metadata for searching,
|
|
14008
|
+
ranking, or choosing a template programmatically.`;
|
|
14009
|
+
static summary = "List available Primitive Function templates";
|
|
14010
|
+
static examples = [
|
|
14011
|
+
"<%= config.bin %> functions templates",
|
|
14012
|
+
"<%= config.bin %> functions templates --json",
|
|
14013
|
+
"<%= config.bin %> functions init my-fn --template email-reply"
|
|
14014
|
+
];
|
|
14015
|
+
static flags = {};
|
|
14016
|
+
async run() {
|
|
14017
|
+
const { flags } = await this.parse(FunctionsTemplatesCommand);
|
|
14018
|
+
if (flags.json) return FUNCTION_TEMPLATES.map(serializeFunctionTemplate);
|
|
14019
|
+
this.log(formatFunctionTemplateList(FUNCTION_TEMPLATES));
|
|
14020
|
+
}
|
|
14021
|
+
};
|
|
14022
|
+
//#endregion
|
|
12341
14023
|
//#region src/oclif/commands/functions-test-function.ts
|
|
12342
14024
|
const DEFAULT_WAIT_TIMEOUT_SECONDS = 60;
|
|
12343
14025
|
const TERMINAL_WEBHOOK_STATUSES = new Set(["fired", "exhausted"]);
|
|
14026
|
+
function buildFunctionTestOutcome(params) {
|
|
14027
|
+
const outcome = {
|
|
14028
|
+
elapsed_seconds: params.elapsedSeconds,
|
|
14029
|
+
function_id: params.functionId,
|
|
14030
|
+
inbound_domain: params.invocation.inbound_domain,
|
|
14031
|
+
inbound_id: params.inboundId,
|
|
14032
|
+
inbound_to: params.invocation.to,
|
|
14033
|
+
poll_since: params.invocation.poll_since,
|
|
14034
|
+
test_run_id: params.invocation.test_run_id,
|
|
14035
|
+
test_send_id: params.invocation.send_id,
|
|
14036
|
+
test_subject: params.invocation.subject,
|
|
14037
|
+
trace_url: params.invocation.trace_url,
|
|
14038
|
+
watch_url: params.invocation.watch_url,
|
|
14039
|
+
webhook_attempt_count: params.detail.webhook_attempt_count,
|
|
14040
|
+
webhook_last_error: params.detail.webhook_last_error,
|
|
14041
|
+
webhook_last_status_code: params.detail.webhook_last_status_code,
|
|
14042
|
+
webhook_status: params.detail.webhook_status
|
|
14043
|
+
};
|
|
14044
|
+
if (params.showSends) outcome.sent_emails = params.detail.replies;
|
|
14045
|
+
return outcome;
|
|
14046
|
+
}
|
|
14047
|
+
function writeFunctionTestProgress(message, writeStderr = (chunk) => {
|
|
14048
|
+
process.stderr.write(chunk);
|
|
14049
|
+
}) {
|
|
14050
|
+
writeStderr(`${message}\n`);
|
|
14051
|
+
}
|
|
14052
|
+
function stringOrNull(value) {
|
|
14053
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
14054
|
+
}
|
|
14055
|
+
function findMatchingFunctionEndpoints(params) {
|
|
14056
|
+
const matches = [];
|
|
14057
|
+
for (const endpoint of params.endpoints) {
|
|
14058
|
+
if (endpoint.kind !== "function") continue;
|
|
14059
|
+
if (endpoint.enabled === false) continue;
|
|
14060
|
+
if (endpoint.deactivated_at !== null && endpoint.deactivated_at !== void 0) continue;
|
|
14061
|
+
const id = stringOrNull(endpoint.id);
|
|
14062
|
+
if (!id) continue;
|
|
14063
|
+
const domainId = stringOrNull(endpoint.domain_id);
|
|
14064
|
+
if (domainId !== null && (params.inboundDomainId === null || domainId !== params.inboundDomainId)) continue;
|
|
14065
|
+
const functionId = stringOrNull(endpoint.function_id);
|
|
14066
|
+
matches.push({
|
|
14067
|
+
function_id: functionId,
|
|
14068
|
+
id,
|
|
14069
|
+
is_current_function: functionId === params.currentFunctionId,
|
|
14070
|
+
scope: domainId === null ? "catch-all" : "domain"
|
|
14071
|
+
});
|
|
14072
|
+
}
|
|
14073
|
+
return matches;
|
|
14074
|
+
}
|
|
14075
|
+
function formatFunctionEndpointNoiseWarning(params) {
|
|
14076
|
+
if (params.endpoints.filter((endpoint) => !endpoint.is_current_function).length === 0) return null;
|
|
14077
|
+
const lines = [`Warning: ${params.endpoints.length} function endpoints may receive mail for ${params.toAddress}:`];
|
|
14078
|
+
for (const endpoint of params.endpoints) {
|
|
14079
|
+
const scope = endpoint.scope === "catch-all" ? "catch-all" : `scoped to ${params.inboundDomain}`;
|
|
14080
|
+
const current = endpoint.is_current_function ? " (this function)" : "";
|
|
14081
|
+
const target = endpoint.function_id ? ` -> function ${endpoint.function_id}` : "";
|
|
14082
|
+
lines.push(`- endpoint ${endpoint.id}${target}, ${scope}${current}`);
|
|
14083
|
+
}
|
|
14084
|
+
return lines.join("\n");
|
|
14085
|
+
}
|
|
14086
|
+
async function maybeWriteEndpointNoiseWarning(params) {
|
|
14087
|
+
try {
|
|
14088
|
+
const [domainsResult, endpointsResult] = await Promise.all([listDomains({
|
|
14089
|
+
client: params.apiClient.client,
|
|
14090
|
+
responseStyle: "fields"
|
|
14091
|
+
}), listEndpoints({
|
|
14092
|
+
client: params.apiClient.client,
|
|
14093
|
+
responseStyle: "fields"
|
|
14094
|
+
})]);
|
|
14095
|
+
if (endpointsResult.error) return;
|
|
14096
|
+
if (domainsResult.error) return;
|
|
14097
|
+
const inboundDomainId = domainsResult.data?.data?.find((domain) => domain.domain?.toLowerCase() === params.invocation.inbound_domain.toLowerCase())?.id ?? null;
|
|
14098
|
+
const endpointsEnvelope = endpointsResult.data;
|
|
14099
|
+
const warning = formatFunctionEndpointNoiseWarning({
|
|
14100
|
+
endpoints: findMatchingFunctionEndpoints({
|
|
14101
|
+
currentFunctionId: params.currentFunctionId,
|
|
14102
|
+
endpoints: endpointsEnvelope?.data ?? [],
|
|
14103
|
+
inboundDomainId
|
|
14104
|
+
}),
|
|
14105
|
+
inboundDomain: params.invocation.inbound_domain,
|
|
14106
|
+
toAddress: params.invocation.to
|
|
14107
|
+
});
|
|
14108
|
+
if (warning) params.writeStderr(`${warning}\n`);
|
|
14109
|
+
} catch {}
|
|
14110
|
+
}
|
|
12344
14111
|
var FunctionsTestFunctionCommand = class FunctionsTestFunctionCommand extends Command {
|
|
12345
14112
|
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
14113
|
static summary = "Trigger a test invocation; with --wait, watch it land";
|
|
@@ -12424,11 +14191,19 @@ var FunctionsTestFunctionCommand = class FunctionsTestFunctionCommand extends Co
|
|
|
12424
14191
|
this.log(JSON.stringify(invocation, null, 2));
|
|
12425
14192
|
return;
|
|
12426
14193
|
}
|
|
14194
|
+
await maybeWriteEndpointNoiseWarning({
|
|
14195
|
+
apiClient,
|
|
14196
|
+
currentFunctionId: flags.id,
|
|
14197
|
+
invocation,
|
|
14198
|
+
writeStderr: (chunk) => {
|
|
14199
|
+
process.stderr.write(chunk);
|
|
14200
|
+
}
|
|
14201
|
+
});
|
|
12427
14202
|
const startedAt = Date.now();
|
|
12428
14203
|
const timeoutMs = flags.timeout * 1e3;
|
|
12429
14204
|
const pollIntervalMs = flags["poll-interval"] * 1e3;
|
|
12430
14205
|
const isExpired = () => flags.timeout > 0 && Date.now() - startedAt > timeoutMs;
|
|
12431
|
-
|
|
14206
|
+
writeFunctionTestProgress(`Waiting for test inbound to arrive at ${invocation.to}...`);
|
|
12432
14207
|
let inboundId;
|
|
12433
14208
|
while (!isExpired()) {
|
|
12434
14209
|
const page = await fetchEmailSearchPage({
|
|
@@ -12457,7 +14232,7 @@ var FunctionsTestFunctionCommand = class FunctionsTestFunctionCommand extends Co
|
|
|
12457
14232
|
await sleep$1(pollIntervalMs);
|
|
12458
14233
|
}
|
|
12459
14234
|
if (!inboundId) this.error(`Timed out after ${flags.timeout}s waiting for test inbound ${invocation.to} to land. Browse ${invocation.watch_url} for the live view.`, { exit: 2 });
|
|
12460
|
-
|
|
14235
|
+
writeFunctionTestProgress(`Inbound landed (${inboundId}). Waiting for function to run...`);
|
|
12461
14236
|
let detail;
|
|
12462
14237
|
while (!isExpired()) {
|
|
12463
14238
|
const result = await getEmail({
|
|
@@ -12486,17 +14261,14 @@ var FunctionsTestFunctionCommand = class FunctionsTestFunctionCommand extends Co
|
|
|
12486
14261
|
}
|
|
12487
14262
|
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
14263
|
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;
|
|
14264
|
+
const outcome = buildFunctionTestOutcome({
|
|
14265
|
+
detail,
|
|
14266
|
+
elapsedSeconds,
|
|
14267
|
+
functionId: flags.id,
|
|
14268
|
+
inboundId,
|
|
14269
|
+
invocation,
|
|
14270
|
+
showSends: shouldShowSends
|
|
14271
|
+
});
|
|
12500
14272
|
this.log(JSON.stringify(outcome, null, 2));
|
|
12501
14273
|
if (detail.webhook_status === "exhausted") process.exitCode = 1;
|
|
12502
14274
|
});
|
|
@@ -12763,6 +14535,106 @@ var LogoutCommand = class LogoutCommand extends Command {
|
|
|
12763
14535
|
}
|
|
12764
14536
|
};
|
|
12765
14537
|
//#endregion
|
|
14538
|
+
//#region src/oclif/message-body-sources.ts
|
|
14539
|
+
function defaultReadFile(path) {
|
|
14540
|
+
return readFileSync(path, "utf8");
|
|
14541
|
+
}
|
|
14542
|
+
function defaultReadStdin() {
|
|
14543
|
+
if (process.stdin.isTTY) throw new Error("stdin is a TTY; pipe a value into this command or pass a file/string source instead.");
|
|
14544
|
+
return readFileSync(0, "utf8");
|
|
14545
|
+
}
|
|
14546
|
+
function selectedSources(sources) {
|
|
14547
|
+
return sources.filter(([, selected]) => selected).map(([label]) => label);
|
|
14548
|
+
}
|
|
14549
|
+
function readTextFile(path, label, readFile) {
|
|
14550
|
+
try {
|
|
14551
|
+
return {
|
|
14552
|
+
content: readFile(path),
|
|
14553
|
+
kind: "ok"
|
|
14554
|
+
};
|
|
14555
|
+
} catch (error) {
|
|
14556
|
+
return {
|
|
14557
|
+
kind: "error",
|
|
14558
|
+
message: `Could not read ${label} ${path}: ${error instanceof Error ? error.message : String(error)}`
|
|
14559
|
+
};
|
|
14560
|
+
}
|
|
14561
|
+
}
|
|
14562
|
+
function readTextStdin(label, readStdin) {
|
|
14563
|
+
try {
|
|
14564
|
+
return {
|
|
14565
|
+
content: readStdin(),
|
|
14566
|
+
kind: "ok"
|
|
14567
|
+
};
|
|
14568
|
+
} catch (error) {
|
|
14569
|
+
return {
|
|
14570
|
+
kind: "error",
|
|
14571
|
+
message: `Could not read ${label}: ${error instanceof Error ? error.message : String(error)}`
|
|
14572
|
+
};
|
|
14573
|
+
}
|
|
14574
|
+
}
|
|
14575
|
+
function resolveMessageBodies(input) {
|
|
14576
|
+
const bodySources = selectedSources([
|
|
14577
|
+
["--body", input.body !== void 0],
|
|
14578
|
+
["--body-file", input.bodyFile !== void 0],
|
|
14579
|
+
["--body-stdin", input.bodyStdin === true]
|
|
14580
|
+
]);
|
|
14581
|
+
if (bodySources.length > 1) return {
|
|
14582
|
+
kind: "error",
|
|
14583
|
+
message: `Pass only one plain-text body source (got ${bodySources.join(", ")}).`
|
|
14584
|
+
};
|
|
14585
|
+
const htmlSources = selectedSources([
|
|
14586
|
+
["--html", input.html !== void 0],
|
|
14587
|
+
["--html-file", input.htmlFile !== void 0],
|
|
14588
|
+
["--html-stdin", input.htmlStdin === true]
|
|
14589
|
+
]);
|
|
14590
|
+
if (htmlSources.length > 1) return {
|
|
14591
|
+
kind: "error",
|
|
14592
|
+
message: `Pass only one HTML body source (got ${htmlSources.join(", ")}).`
|
|
14593
|
+
};
|
|
14594
|
+
const stdinSources = selectedSources([["--body-stdin", input.bodyStdin === true], ["--html-stdin", input.htmlStdin === true]]);
|
|
14595
|
+
if (stdinSources.length > 1) return {
|
|
14596
|
+
kind: "error",
|
|
14597
|
+
message: `Stdin can only be consumed once (got ${stdinSources.join(", ")}).`
|
|
14598
|
+
};
|
|
14599
|
+
if (bodySources.length === 0 && htmlSources.length === 0) return {
|
|
14600
|
+
kind: "error",
|
|
14601
|
+
message: "Either a plain-text body source or an HTML body source is required."
|
|
14602
|
+
};
|
|
14603
|
+
const readFile = input.readFile ?? defaultReadFile;
|
|
14604
|
+
const readStdin = input.readStdin ?? defaultReadStdin;
|
|
14605
|
+
let body = input.body;
|
|
14606
|
+
let html = input.html;
|
|
14607
|
+
if (input.bodyFile !== void 0) {
|
|
14608
|
+
const result = readTextFile(input.bodyFile, "--body-file", readFile);
|
|
14609
|
+
if (result.kind === "error") return result;
|
|
14610
|
+
body = result.content;
|
|
14611
|
+
}
|
|
14612
|
+
if (input.bodyStdin === true) {
|
|
14613
|
+
const result = readTextStdin("--body-stdin", readStdin);
|
|
14614
|
+
if (result.kind === "error") return result;
|
|
14615
|
+
body = result.content;
|
|
14616
|
+
}
|
|
14617
|
+
if (input.htmlFile !== void 0) {
|
|
14618
|
+
const result = readTextFile(input.htmlFile, "--html-file", readFile);
|
|
14619
|
+
if (result.kind === "error") return result;
|
|
14620
|
+
html = result.content;
|
|
14621
|
+
}
|
|
14622
|
+
if (input.htmlStdin === true) {
|
|
14623
|
+
const result = readTextStdin("--html-stdin", readStdin);
|
|
14624
|
+
if (result.kind === "error") return result;
|
|
14625
|
+
html = result.content;
|
|
14626
|
+
}
|
|
14627
|
+
if (!body && !html) return {
|
|
14628
|
+
kind: "error",
|
|
14629
|
+
message: "Either a non-empty plain-text body or a non-empty HTML body is required."
|
|
14630
|
+
};
|
|
14631
|
+
return {
|
|
14632
|
+
...body !== void 0 ? { body } : {},
|
|
14633
|
+
...html !== void 0 ? { html } : {},
|
|
14634
|
+
kind: "ok"
|
|
14635
|
+
};
|
|
14636
|
+
}
|
|
14637
|
+
//#endregion
|
|
12766
14638
|
//#region src/oclif/commands/reply.ts
|
|
12767
14639
|
var ReplyCommand = class ReplyCommand extends Command {
|
|
12768
14640
|
static description = `Reply to an inbound email.
|
|
@@ -12771,6 +14643,7 @@ var ReplyCommand = class ReplyCommand extends Command {
|
|
|
12771
14643
|
static summary = "Reply to an inbound email";
|
|
12772
14644
|
static examples = [
|
|
12773
14645
|
"<%= config.bin %> reply --id <inbound-email-id> --body 'Thanks, got it.'",
|
|
14646
|
+
"<%= config.bin %> reply --id <inbound-email-id> --body-file ./reply.txt",
|
|
12774
14647
|
"<%= config.bin %> reply --id <inbound-email-id> --html '<p>Thanks, got it.</p>' --wait",
|
|
12775
14648
|
"<%= config.bin %> reply --id <inbound-email-id> --from 'Support <support@example.com>' --body 'Thanks!'"
|
|
12776
14649
|
];
|
|
@@ -12794,7 +14667,11 @@ var ReplyCommand = class ReplyCommand extends Command {
|
|
|
12794
14667
|
required: true
|
|
12795
14668
|
}),
|
|
12796
14669
|
body: Flags.string({ description: "Plain-text reply body. Either --body or --html (or both) is required." }),
|
|
14670
|
+
"body-file": Flags.string({ description: "Read the plain-text reply body from a UTF-8 file. Mutually exclusive with --body and --body-stdin." }),
|
|
14671
|
+
"body-stdin": Flags.boolean({ description: "Read the plain-text reply body from stdin. Mutually exclusive with --body and --body-file. Stdin can only be consumed once." }),
|
|
12797
14672
|
html: Flags.string({ description: "HTML reply body. Either --body or --html (or both) is required." }),
|
|
14673
|
+
"html-file": Flags.string({ description: "Read the HTML reply body from a UTF-8 file. Mutually exclusive with --html and --html-stdin." }),
|
|
14674
|
+
"html-stdin": Flags.boolean({ description: "Read the HTML reply body from stdin. Mutually exclusive with --html and --html-file. Stdin can only be consumed once." }),
|
|
12798
14675
|
from: Flags.string({ description: "Optional From header override. Defaults to the inbound recipient." }),
|
|
12799
14676
|
wait: Flags.boolean({ description: "Block until the receiving MTA returns an outcome. Without --wait, the call returns once Primitive has accepted the reply for delivery." }),
|
|
12800
14677
|
"wait-timeout-ms": Flags.integer({ description: "Maximum time to wait when --wait is set. Defaults to 30000ms." }),
|
|
@@ -12802,7 +14679,15 @@ var ReplyCommand = class ReplyCommand extends Command {
|
|
|
12802
14679
|
};
|
|
12803
14680
|
async run() {
|
|
12804
14681
|
const { flags } = await this.parse(ReplyCommand);
|
|
12805
|
-
|
|
14682
|
+
const bodies = resolveMessageBodies({
|
|
14683
|
+
body: flags.body,
|
|
14684
|
+
bodyFile: flags["body-file"],
|
|
14685
|
+
bodyStdin: flags["body-stdin"],
|
|
14686
|
+
html: flags.html,
|
|
14687
|
+
htmlFile: flags["html-file"],
|
|
14688
|
+
htmlStdin: flags["html-stdin"]
|
|
14689
|
+
});
|
|
14690
|
+
if (bodies.kind === "error") throw new Errors.CLIError(bodies.message);
|
|
12806
14691
|
await runWithTiming(flags.time, async () => {
|
|
12807
14692
|
const baseUrlOverridden = flags["api-base-url-1"] !== void 0 || flags["api-base-url-2"] !== void 0;
|
|
12808
14693
|
const auth = resolveCliAuth({
|
|
@@ -12818,8 +14703,8 @@ var ReplyCommand = class ReplyCommand extends Command {
|
|
|
12818
14703
|
});
|
|
12819
14704
|
const result = await replyToEmail({
|
|
12820
14705
|
body: {
|
|
12821
|
-
...
|
|
12822
|
-
...
|
|
14706
|
+
...bodies.body !== void 0 ? { body_text: bodies.body } : {},
|
|
14707
|
+
...bodies.html !== void 0 ? { body_html: bodies.html } : {},
|
|
12823
14708
|
...flags.from !== void 0 ? { from: flags.from } : {},
|
|
12824
14709
|
...flags.wait !== void 0 ? { wait: flags.wait } : {},
|
|
12825
14710
|
...flags["wait-timeout-ms"] !== void 0 ? { wait_timeout_ms: flags["wait-timeout-ms"] } : {}
|
|
@@ -12894,6 +14779,7 @@ var SendCommand = class SendCommand extends Command {
|
|
|
12894
14779
|
static summary = "Send an email (simplified, agent-friendly)";
|
|
12895
14780
|
static examples = [
|
|
12896
14781
|
"<%= config.bin %> send --to alice@example.com --body 'Hi Alice!'",
|
|
14782
|
+
"<%= config.bin %> send --to alice@example.com --body-file ./message.txt",
|
|
12897
14783
|
"<%= config.bin %> send --to alice@example.com --from support@yourcompany.com --subject 'Quick question' --body 'Are you free Thursday?'",
|
|
12898
14784
|
"<%= config.bin %> send --to alice@example.com --html '<p>Hello!</p>'",
|
|
12899
14785
|
"<%= config.bin %> send --to alice@example.com --body 'Confirmed' --wait",
|
|
@@ -12921,7 +14807,11 @@ var SendCommand = class SendCommand extends Command {
|
|
|
12921
14807
|
from: Flags.string({ description: "Sender address. Defaults to agent@<your-first-verified-outbound-domain>." }),
|
|
12922
14808
|
subject: Flags.string({ description: "Subject line. Defaults to the first line of --body / --html when omitted." }),
|
|
12923
14809
|
body: Flags.string({ description: "Plain-text message body. Either --body or --html (or both) is required." }),
|
|
14810
|
+
"body-file": Flags.string({ description: "Read the plain-text message body from a UTF-8 file. Mutually exclusive with --body and --body-stdin." }),
|
|
14811
|
+
"body-stdin": Flags.boolean({ description: "Read the plain-text message body from stdin. Mutually exclusive with --body and --body-file. Stdin can only be consumed once." }),
|
|
12924
14812
|
html: Flags.string({ description: "HTML message body. Either --body or --html (or both) is required." }),
|
|
14813
|
+
"html-file": Flags.string({ description: "Read the HTML message body from a UTF-8 file. Mutually exclusive with --html and --html-stdin." }),
|
|
14814
|
+
"html-stdin": Flags.boolean({ description: "Read the HTML message body from stdin. Mutually exclusive with --html and --html-file. Stdin can only be consumed once." }),
|
|
12925
14815
|
"in-reply-to": Flags.string({ description: "Message-Id of the parent email when threading a reply on the wire. For replying to an inbound message you received, prefer `primitive reply --id <inbound-id>`." }),
|
|
12926
14816
|
wait: Flags.boolean({ description: "Block until the receiving MTA returns an outcome. Without --wait, the call returns once Primitive has accepted the message for delivery." }),
|
|
12927
14817
|
"wait-timeout-ms": Flags.integer({ description: "Maximum time to wait when --wait is set. Defaults to 30000ms." }),
|
|
@@ -12929,7 +14819,15 @@ var SendCommand = class SendCommand extends Command {
|
|
|
12929
14819
|
};
|
|
12930
14820
|
async run() {
|
|
12931
14821
|
const { flags } = await this.parse(SendCommand);
|
|
12932
|
-
|
|
14822
|
+
const bodies = resolveMessageBodies({
|
|
14823
|
+
body: flags.body,
|
|
14824
|
+
bodyFile: flags["body-file"],
|
|
14825
|
+
bodyStdin: flags["body-stdin"],
|
|
14826
|
+
html: flags.html,
|
|
14827
|
+
htmlFile: flags["html-file"],
|
|
14828
|
+
htmlStdin: flags["html-stdin"]
|
|
14829
|
+
});
|
|
14830
|
+
if (bodies.kind === "error") throw new Errors.CLIError(bodies.message);
|
|
12933
14831
|
await runWithTiming(flags.time, async () => {
|
|
12934
14832
|
const baseUrlOverridden = flags["api-base-url-1"] !== void 0 || flags["api-base-url-2"] !== void 0;
|
|
12935
14833
|
const auth = resolveCliAuth({
|
|
@@ -12949,14 +14847,14 @@ var SendCommand = class SendCommand extends Command {
|
|
|
12949
14847
|
configDir: this.config.configDir
|
|
12950
14848
|
};
|
|
12951
14849
|
const from = flags.from ?? await pickDefaultFromAddress(apiClient, authFailureContext);
|
|
12952
|
-
const subject = flags.subject ?? (
|
|
14850
|
+
const subject = flags.subject ?? (bodies.body ? deriveSubject(bodies.body) : "Message");
|
|
12953
14851
|
const result = await sendEmail({
|
|
12954
14852
|
body: {
|
|
12955
14853
|
from,
|
|
12956
14854
|
to: flags.to,
|
|
12957
14855
|
subject,
|
|
12958
|
-
...
|
|
12959
|
-
...
|
|
14856
|
+
...bodies.body !== void 0 ? { body_text: bodies.body } : {},
|
|
14857
|
+
...bodies.html !== void 0 ? { body_html: bodies.html } : {},
|
|
12960
14858
|
...flags["in-reply-to"] !== void 0 ? { in_reply_to: flags["in-reply-to"] } : {},
|
|
12961
14859
|
...flags.wait !== void 0 ? { wait: flags.wait } : {},
|
|
12962
14860
|
...flags["wait-timeout-ms"] !== void 0 ? { wait_timeout_ms: flags["wait-timeout-ms"] } : {}
|
|
@@ -13496,6 +15394,7 @@ function renderFishCompletion(binName) {
|
|
|
13496
15394
|
lines.push(`complete -c ${binName} -f -n '__fish_${binName}_topic_needs_subcommand ${fishEscape(topic)}' -a '${fishEscape(operation.command)}' -d '${fishEscape(operation.summary ?? `${operation.method} ${operation.path}`)}'`);
|
|
13497
15395
|
for (const parameter of [...operation.pathParams, ...operation.queryParams]) lines.push(`complete -c ${binName} -n '${operationCondition(operation).replace(BIN_PLACEHOLDER, binName)}' -l '${fishEscape(parameter.name.replace(/_/g, "-"))}' -r -d '${fishEscape(parameter.description ?? parameter.name)}'`);
|
|
13498
15396
|
lines.push(`complete -c ${binName} -n '${operationCondition(operation).replace(BIN_PLACEHOLDER, binName)}' -l 'api-key' -r -d 'Primitive API key (defaults to PRIMITIVE_API_KEY or saved primitive login credentials)'`);
|
|
15397
|
+
if (!operation.binaryResponse) lines.push(`complete -c ${binName} -n '${operationCondition(operation).replace(BIN_PLACEHOLDER, binName)}' -l 'envelope' -d 'Print the full response envelope, including pagination metadata'`);
|
|
13499
15398
|
if (operation.hasJsonBody) lines.push(`complete -c ${binName} -n '${operationCondition(operation).replace(BIN_PLACEHOLDER, binName)}' -l 'body' -r -d 'JSON request body'`, `complete -c ${binName} -n '${operationCondition(operation).replace(BIN_PLACEHOLDER, binName)}' -l 'body-file' -r -d 'Path to a JSON file used as the request body'`);
|
|
13500
15399
|
if (operation.binaryResponse) lines.push(`complete -c ${binName} -n '${operationCondition(operation).replace(BIN_PLACEHOLDER, binName)}' -l 'output' -r -d 'Write binary response bytes to a file'`);
|
|
13501
15400
|
}
|
|
@@ -13613,7 +15512,6 @@ const CANONICAL_OPERATION_ALIASES = {
|
|
|
13613
15512
|
"functions:get": "functions:get-function",
|
|
13614
15513
|
"functions:list": "functions:list-functions",
|
|
13615
15514
|
"functions:list-secrets": "functions:list-function-secrets",
|
|
13616
|
-
"functions:logs": "functions:list-function-logs",
|
|
13617
15515
|
"sending:get": "sending:get-sent-email",
|
|
13618
15516
|
"sending:list": "sending:list-sent-emails",
|
|
13619
15517
|
"sending:permissions": "sending:get-send-permissions",
|
|
@@ -13626,6 +15524,7 @@ const CANONICAL_OPERATION_ALIASES = {
|
|
|
13626
15524
|
};
|
|
13627
15525
|
const DESCRIBE_OPERATION_ALIASES = {
|
|
13628
15526
|
...CANONICAL_OPERATION_ALIASES,
|
|
15527
|
+
"functions:logs": "functions:list-function-logs",
|
|
13629
15528
|
reply: "sending:reply-to-email"
|
|
13630
15529
|
};
|
|
13631
15530
|
function resolveOperationAlias(id) {
|
|
@@ -13648,6 +15547,7 @@ const COMMANDS = {
|
|
|
13648
15547
|
"emails:watch": EmailsWatchCommand,
|
|
13649
15548
|
"emails:wait": EmailsWaitCommand,
|
|
13650
15549
|
"functions:init": FunctionsInitCommand,
|
|
15550
|
+
"functions:templates": FunctionsTemplatesCommand,
|
|
13651
15551
|
"functions:deploy": FunctionsDeployCommand,
|
|
13652
15552
|
"functions:redeploy": FunctionsRedeployCommand,
|
|
13653
15553
|
"functions:set-secret": FunctionsSetSecretCommand,
|
|
@@ -13658,7 +15558,8 @@ const COMMANDS = {
|
|
|
13658
15558
|
if (!command) throw new Error(`Missing generated command target for alias ${alias}`);
|
|
13659
15559
|
return [alias, command];
|
|
13660
15560
|
})),
|
|
13661
|
-
...generatedCommands
|
|
15561
|
+
...generatedCommands,
|
|
15562
|
+
"functions:logs": FunctionsLogsCommand
|
|
13662
15563
|
};
|
|
13663
15564
|
//#endregion
|
|
13664
15565
|
export { CANONICAL_OPERATION_ALIASES, COMMANDS, lookupOperation };
|